feat(ui): enhance charging stations list structure
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 16 Feb 2024 15:21:47 +0000 (16:21 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 16 Feb 2024 15:21:47 +0000 (16:21 +0100)
closes #977

Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
ui/web/src/App.vue
ui/web/src/components/Modal.vue
ui/web/src/components/charging-stations/CSConnector.vue
ui/web/src/components/charging-stations/CSData.vue
ui/web/src/components/charging-stations/CSInfoModal.vue
ui/web/src/components/charging-stations/CSTable.vue
ui/web/src/types/ChargingStationType.ts
ui/web/src/types/index.ts
ui/web/src/views/ChargingStationsView.vue
ui/web/tests/unit/CSTable.spec.ts

index 87c7aa45293f9c1282cde9c51cfd38a0576d17bc..203fb47f69108690234b283569919e663f491012 100644 (file)
@@ -9,14 +9,14 @@
   font-family: Avenir, Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  color: #2c3e50;
+  color: black;
+  background-color: white;
 }
 
 body {
   height: fit-content;
   width: fit-content;
-  margin: 0.05%;
-  padding: 0.05%;
-  background-color: rgb(233, 227, 227);
+  margin: 0.001%;
+  padding: 0.001%;
 }
 </style>
index 13a4c0b22d441549468adb48ea55b59fb149e41b..746308682388cdc99adf838af6d8a77571b60629 100644 (file)
@@ -25,6 +25,6 @@ function close() {
   justify-content: center;
   align-items: center;
   inset: 0;
-  background-color: rgba(0, 0, 0, 0.2);
+  background-color: black;
 }
 </style>
index 08852683919d87167451d24c402e8866170fbf4e..f6cda45af6715868ebc8d0f312ccecd3646feec5 100644 (file)
@@ -1,39 +1,43 @@
 <template>
-  <td class="cs-table__column">
-    <Button @click="startChargingStation()">Start Charging Station</Button>
-    <Button @click="stopChargingStation()">Stop Charging Station</Button>
-    <Button @click="openConnection()">Open Connection</Button>
-    <Button @click="closeConnection()">Close Connection</Button>
-    <Button @click="startTransaction()">Start Transaction</Button>
-    <!-- <IdTagInputModal
-      :visibility="state.isIdTagModalVisible"
-      :id-tag="state.idTag"
-      @close="hideIdTagModal()"
-      @done="compose(state.transaction, hideIdTagModal)()"
-    >
-      Start Transaction
-    </IdTagInputModal> -->
-    <Button @click="stopTransaction()">Stop Transaction</Button>
-    <Button @click="startAutomaticTransactionGenerator()">Start ATG</Button>
-    <Button @click="stopAutomaticTransactionGenerator()">Stop ATG</Button>
-  </td>
-  <td class="cs-table__column">{{ connectorId }}</td>
-  <td class="cs-table__column">{{ connector.status ?? 'Ø' }}</td>
-  <td class="cs-table__column">{{ connector.transactionStarted === true ? 'Yes' : 'No' }}</td>
+  <tr class="connectors-table__row">
+    <td class="connectors-table__column">{{ connectorId }}</td>
+    <td class="connectors-table__column">{{ connector.status ?? 'Ø' }}</td>
+    <td class="connectors-table__column">
+      {{ connector.transactionStarted === true ? 'Yes' : 'No' }}
+    </td>
+    <td class="connectors-table__column">
+      {{ atgStatus?.start === true ? 'Yes' : 'No' }}
+    </td>
+    <td class="connectors-table__column">
+      <Button @click="startTransaction()">Start Transaction</Button>
+      <!-- <IdTagInputModal
+        :visibility="state.isIdTagModalVisible"
+        :id-tag="state.idTag"
+        @close="hideIdTagModal()"
+        @done="compose(state.transaction, hideIdTagModal)()"
+      >
+        Start Transaction
+      </IdTagInputModal> -->
+      <Button @click="stopTransaction()">Stop Transaction</Button>
+      <Button @click="startAutomaticTransactionGenerator()">Start ATG</Button>
+      <Button @click="stopAutomaticTransactionGenerator()">Stop ATG</Button>
+    </td>
+  </tr>
 </template>
 
 <script setup lang="ts">
 import { getCurrentInstance } from 'vue'
-// import { reactive } from 'vue';
+// import { reactive } from 'vue'
 // import IdTagInputModal from '@/components/charging-stations/IdTagInputModal.vue'
 import Button from '@/components/buttons/Button.vue'
-import type { ConnectorStatus } from '@/types'
+import type { ConnectorStatus, Status } from '@/types'
 // import { compose } from '@/composables'
 
 const props = defineProps<{
   hashId: string
-  connector: ConnectorStatus
   connectorId: number
+  connector: ConnectorStatus
+  atgStatus?: Status
   transactionId?: number
   idTag?: string
 }>()
@@ -64,18 +68,6 @@ const props = defineProps<{
 
 const UIClient = getCurrentInstance()?.appContext.config.globalProperties.$UIClient
 
-function startChargingStation(): void {
-  UIClient.startChargingStation(props.hashId)
-}
-function stopChargingStation(): void {
-  UIClient.stopChargingStation(props.hashId)
-}
-function openConnection(): void {
-  UIClient.openConnection(props.hashId)
-}
-function closeConnection(): void {
-  UIClient.closeConnection(props.hashId)
-}
 function startTransaction(): void {
   UIClient.startTransaction(props.hashId, props.connectorId, props.idTag)
 }
index d077ca6eba882f74f3ce52ae07d4b2b01ea168a3..c4fadcea51b91a7b19b7add4c9a74d070e8f54a3 100644 (file)
@@ -1,28 +1,61 @@
 <template>
-  <tr v-for="(connector, index) in getConnectors()" class="cs-table__row">
-    <CSConnector
-      :hash-id="getHashId()"
-      :connector="connector"
-      :connector-id="index + 1"
-      :transaction-id="connector.transactionId"
-      :id-tag="props.idTag"
-    />
+  <tr class="cs-table__row">
     <td class="cs-table__column">{{ getId() }}</td>
     <td class="cs-table__column">{{ getStarted() }}</td>
-    <td class="cs-table__column">{{ getSupervisionUrl() }}</td>
+    <td class="cs-table__column">
+      {{ getSupervisionUrl() }}
+    </td>
     <td class="cs-table__column">{{ getWsState() }}</td>
-    <td class="cs-table__column">{{ getRegistrationStatus() }}</td>
-    <td class="cs-table__column">{{ getInfo().templateName }}</td>
+    <td class="cs-table__column">
+      {{ getRegistrationStatus() }}
+    </td>
+    <td class="cs-table__column">
+      {{ getInfo().templateName }}
+    </td>
     <td class="cs-table__column">{{ getVendor() }}</td>
     <td class="cs-table__column">{{ getModel() }}</td>
-    <td class="cs-table__column">{{ getFirmwareVersion() }}</td>
+    <td class="cs-table__column">
+      {{ getFirmwareVersion() }}
+    </td>
+    <td class="cs-table__column">
+      <Button @click="startChargingStation()">Start Charging Station</Button>
+      <Button @click="stopChargingStation()">Stop Charging Station</Button>
+      <Button @click="openConnection()">Open Connection</Button>
+      <Button @click="closeConnection()">Close Connection</Button>
+    </td>
+    <td class="cs-table__connectors-column">
+      <table id="connectors-table">
+        <thead id="connectors-table__head">
+          <tr class="connectors-table__row">
+            <th scope="col" class="connectors-table__column">Identifier</th>
+            <th scope="col" class="connectors-table__column">Status</th>
+            <th scope="col" class="connectors-table__column">Transaction</th>
+            <th scope="col" class="connectors-table__column">ATG Started</th>
+            <th scope="col" class="connectors-table__column">Actions</th>
+          </tr>
+        </thead>
+        <tbody id="connectors-table__body">
+          <!-- eslint-disable-next-line vue/valid-v-for -->
+          <CSConnector
+            v-for="(connector, index) in getConnectors()"
+            :hash-id="getHashId()"
+            :connector-id="index + 1"
+            :connector="connector"
+            :atg-status="getATGStatus(index + 1)"
+            :transaction-id="connector.transactionId"
+            :id-tag="props.idTag"
+          />
+        </tbody>
+      </table>
+    </td>
   </tr>
 </template>
 
 <script setup lang="ts">
 // import { reactive } from 'vue'
+import { getCurrentInstance } from 'vue'
 import CSConnector from '@/components/charging-stations/CSConnector.vue'
-import type { ChargingStationData, ChargingStationInfo, ConnectorStatus } from '@/types'
+import type { ChargingStationData, ChargingStationInfo, ConnectorStatus, Status } from '@/types'
 
 const props = defineProps<{
   chargingStation: ChargingStationData
@@ -39,19 +72,12 @@ const props = defineProps<{
 //   idTag: ''
 // })
 
-function getConnectors(): ConnectorStatus[] {
-  if (Array.isArray(props.chargingStation.evses) && props.chargingStation.evses.length > 0) {
-    const connectorsStatus: ConnectorStatus[] = []
-    for (const [evseId, evseStatus] of props.chargingStation.evses.entries()) {
-      if (evseId > 0 && Array.isArray(evseStatus.connectors) && evseStatus.connectors.length > 0) {
-        for (const connectorStatus of evseStatus.connectors) {
-          connectorsStatus.push(connectorStatus)
-        }
-      }
-    }
-    return connectorsStatus
-  }
-  return props.chargingStation.connectors?.slice(1)
+function getStarted(): string {
+  return props.chargingStation.started === true ? 'Yes' : 'No'
+}
+function getATGStatus(connectorId: number): Status | undefined {
+  return props.chargingStation.automaticTransactionGenerator
+    ?.automaticTransactionGeneratorStatuses?.[connectorId - 1]
 }
 function getInfo(): ChargingStationInfo {
   return props.chargingStation.stationInfo
@@ -71,9 +97,6 @@ function getVendor(): string {
 function getFirmwareVersion(): string {
   return getInfo().firmwareVersion ?? 'Ø'
 }
-function getStarted(): string {
-  return props.chargingStation.started === true ? 'Yes' : 'No'
-}
 function getSupervisionUrl(): string {
   const supervisionUrl = new URL(props.chargingStation.supervisionUrl)
   return `${supervisionUrl.protocol}//${supervisionUrl.host.split('.').join('.\u200b')}`
@@ -95,6 +118,35 @@ function getWsState(): string {
 function getRegistrationStatus(): string {
   return props.chargingStation?.bootNotificationResponse?.status ?? 'Ø'
 }
+function getConnectors(): ConnectorStatus[] {
+  if (Array.isArray(props.chargingStation.evses) && props.chargingStation.evses.length > 0) {
+    const connectorsStatus: ConnectorStatus[] = []
+    for (const [evseId, evseStatus] of props.chargingStation.evses.entries()) {
+      if (evseId > 0 && Array.isArray(evseStatus.connectors) && evseStatus.connectors.length > 0) {
+        for (const connectorStatus of evseStatus.connectors) {
+          connectorsStatus.push(connectorStatus)
+        }
+      }
+    }
+    return connectorsStatus
+  }
+  return props.chargingStation.connectors?.slice(1)
+}
+
+const UIClient = getCurrentInstance()?.appContext.config.globalProperties.$UIClient
+
+function startChargingStation(): void {
+  UIClient.startChargingStation(getHashId())
+}
+function stopChargingStation(): void {
+  UIClient.stopChargingStation(getHashId())
+}
+function openConnection(): void {
+  UIClient.openConnection(getHashId())
+}
+function closeConnection(): void {
+  UIClient.closeConnection(getHashId())
+}
 // function showTagModal(): void {
 //   state.isTagModalVisible = true
 // }
@@ -102,3 +154,39 @@ function getRegistrationStatus(): string {
 //   state.isTagModalVisible = false
 // }
 </script>
+
+<style>
+#connectors-table {
+  display: flex;
+  background-color: white;
+  flex-direction: column;
+  overflow: auto hidden;
+  border-collapse: collapse;
+  empty-cells: show;
+}
+
+#connectors-table__head,
+#connectors-table__body {
+  display: flex;
+  flex-direction: column;
+}
+
+.connectors-table__row {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+#connectors-table__head .connectors-table__row {
+  background-color: lightgrey;
+}
+
+.connectors-table__row:nth-of-type(even) {
+  background-color: whitesmoke;
+}
+
+.connectors-table__column {
+  width: calc(100% / 5);
+  text-align: center;
+}
+</style>
index 05b4b6bdbbd0f6691215c4dc270e92a1e1f9a03b..9e0d1cd8d55386c12a88eaddb87a5f5140751614 100644 (file)
@@ -1,6 +1,6 @@
 <template>
   <Modal :visibility="props.isVisible">
-    <div class="card-info">vendor: {{ props.stationInfo.chargePointVendor }}</div>
+    <div>vendor: {{ props.stationInfo.chargePointVendor }}</div>
   </Modal>
 </template>
 
@@ -13,9 +13,3 @@ const props = defineProps<{
   isVisible: boolean
 }>()
 </script>
-
-<style>
-.card-info {
-  background-color: white;
-}
-</style>
index 30f469d99ebc3ab24cec71de311187b24f14f2cd..3d9efbbf2471cb1239f3e64c51262923f8d45021 100644 (file)
@@ -5,10 +5,6 @@
     </caption>
     <thead id="cs-table__head">
       <tr class="cs-table__row">
-        <th scope="col" class="cs-table__column">Action</th>
-        <th scope="col" class="cs-table__column">Connector</th>
-        <th scope="col" class="cs-table__column">Status</th>
-        <th scope="col" class="cs-table__column">Transaction</th>
         <th scope="col" class="cs-table__column">Name</th>
         <th scope="col" class="cs-table__column">Started</th>
         <th scope="col" class="cs-table__column">Supervision Url</th>
@@ -17,7 +13,9 @@
         <th scope="col" class="cs-table__column">Template</th>
         <th scope="col" class="cs-table__column">Vendor</th>
         <th scope="col" class="cs-table__column">Model</th>
-        <th scope="col" class="cs-table__column">Firmware Version</th>
+        <th scope="col" class="cs-table__column">Firmware</th>
+        <th scope="col" class="cs-table__column">Actions</th>
+        <th scope="col" class="cs-table__connectors-column">Connector(s)</th>
       </tr>
     </thead>
     <tbody id="cs-table__body">
@@ -61,11 +59,9 @@ const props = defineProps<{
   flex-direction: column;
 }
 
-#cs-table__body {
-  overflow: visible overlay;
-}
-
 #cs-table__caption {
+  color: white;
+  background-color: black;
   font-size: 1.5rem;
   font-weight: bold;
   padding: 0.5rem;
@@ -80,16 +76,22 @@ const props = defineProps<{
 }
 
 #cs-table__head .cs-table__row {
-  background-color: rgb(194, 188, 188);
+  background-color: lightgrey;
 }
 
 .cs-table__row:nth-of-type(even) {
-  background-color: rgb(223, 217, 217);
+  background-color: whitesmoke;
 }
 
 .cs-table__column {
   height: fit-content;
-  width: calc(100% / 13);
+  width: calc(65% / 10);
+  text-align: center;
+}
+
+.cs-table__connectors-column {
+  height: fit-content;
+  width: 35%;
   text-align: center;
 }
 </style>
index 8164bce83bddcc918aea9bab1beca4afee77fbdf..a0c1b4c734ff9f9fc586063b3389fb34fb2fcae3 100644 (file)
@@ -1,5 +1,29 @@
 import type { JsonObject } from './JsonType'
 
+export enum IdTagDistribution {
+  RANDOM = 'random',
+  ROUND_ROBIN = 'round-robin',
+  CONNECTOR_AFFINITY = 'connector-affinity'
+}
+
+export interface AutomaticTransactionGeneratorConfiguration {
+  enable: boolean
+  minDuration: number
+  maxDuration: number
+  minDelayBetweenTwoTransactions: number
+  maxDelayBetweenTwoTransactions: number
+  probabilityOfStart: number
+  stopAfterHours: number
+  stopAbsoluteDuration: boolean
+  requireAuthorize?: boolean
+  idTagDistribution?: IdTagDistribution
+}
+
+export interface ChargingStationAutomaticTransactionGeneratorConfiguration {
+  automaticTransactionGenerator?: AutomaticTransactionGeneratorConfiguration
+  automaticTransactionGeneratorStatuses?: Status[]
+}
+
 export type ChargingStationData = {
   started: boolean
   stationInfo: ChargingStationInfo
@@ -13,7 +37,7 @@ export type ChargingStationData = {
     | typeof WebSocket.CLOSING
     | typeof WebSocket.CLOSED
   bootNotificationResponse?: BootNotificationResponse
-  automaticTransactionGenerator?: Status[]
+  automaticTransactionGenerator?: ChargingStationAutomaticTransactionGeneratorConfiguration
 }
 
 export enum OCPP16FirmwareStatus {
index 85709be3c002048a8e27ec685be6d7fc0ae32af1..3e38d5c262bbe7b47b610bd72f13c0aa504a1153 100644 (file)
@@ -1,7 +1,8 @@
 export type {
   ChargingStationData,
   ChargingStationInfo,
-  ConnectorStatus
+  ConnectorStatus,
+  Status
 } from './ChargingStationType'
 export type { ConfigurationData } from './ConfigurationType'
 export {
index 9243b98febe03cc75a4f1a595a9287aeef4012f2..15564785ee2238b555d0f7bac41c1268661abb20 100644 (file)
@@ -62,7 +62,6 @@ function stopSimulator(): void {
 #charging-stations {
   height: fit-content;
   width: 100%;
-  background-color: rgb(233, 227, 227);
   display: flex;
   flex-direction: column;
 }
@@ -74,18 +73,18 @@ function stopSimulator(): void {
 
 #reload-button {
   flex: auto;
-  background-color: rgb(25, 118, 210);
   color: white;
+  background-color: blue;
   font-size: 35px;
   font-weight: bold;
 }
 
 #reload-button:hover {
-  background-color: rgb(10, 113, 195);
+  background-color: rgb(0, 0, 225);
 }
 
 #reload-button:active {
-  background-color: rgb(255, 113, 195);
+  background-color: red;
 }
 
 #simulator-button {
index a756a6e5a24b682721b0e12bd11d78a60256d0fe..0a364a6cf62fc3d184fd0dac8b35c6efbcb29b83 100644 (file)
@@ -8,10 +8,6 @@ test('renders CS table columns name', () => {
   const wrapper = shallowMount(CSTable, {
     props: { chargingStations, idTag: '0' }
   })
-  expect(wrapper.text()).to.include('Action')
-  expect(wrapper.text()).to.include('Connector')
-  expect(wrapper.text()).to.include('Status')
-  expect(wrapper.text()).to.include('Transaction')
   expect(wrapper.text()).to.include('Name')
   expect(wrapper.text()).to.include('Started')
   expect(wrapper.text()).to.include('Supervision Url')
@@ -20,5 +16,7 @@ test('renders CS table columns name', () => {
   expect(wrapper.text()).to.include('Template')
   expect(wrapper.text()).to.include('Vendor')
   expect(wrapper.text()).to.include('Model')
-  expect(wrapper.text()).to.include('Firmware Version')
+  expect(wrapper.text()).to.include('Firmware')
+  expect(wrapper.text()).to.include('Actions')
+  expect(wrapper.text()).to.include('Connector(s)')
 })