feat(ui): add 'Not found' catch all
[e-mobility-charging-stations-simulator.git] / ui / web / src / views / ChargingStationsView.vue
1 <template>
2 <Container id="charging-stations-container">
3 <Container id="buttons-container">
4 <Container
5 v-show="Array.isArray(uiServerConfigurations) && uiServerConfigurations.length > 1"
6 id="ui-server-container"
7 >
8 <select
9 id="ui-server-selector"
10 v-model="state.uiServerIndex"
11 @change="
12 () => {
13 if (
14 getFromLocalStorage<number>('uiServerConfigurationIndex', 0) !== state.uiServerIndex
15 ) {
16 $uiClient.setConfiguration($configuration.value.uiServer[state.uiServerIndex])
17 registerWSEventListeners()
18 $uiClient.registerWSEventListener(
19 'open',
20 () => {
21 setToLocalStorage<number>('uiServerConfigurationIndex', state.uiServerIndex)
22 clearToggleButtons()
23 $route.name !== 'charging-stations' &&
24 $router.push({ name: 'charging-stations' })
25 },
26 { once: true }
27 )
28 $uiClient.registerWSEventListener(
29 'error',
30 () => {
31 state.uiServerIndex = getFromLocalStorage<number>(
32 'uiServerConfigurationIndex',
33 0
34 )
35 $uiClient.setConfiguration(
36 $configuration.value.uiServer[
37 getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
38 ]
39 )
40 registerWSEventListeners()
41 },
42 { once: true }
43 )
44 }
45 }
46 "
47 >
48 <option
49 v-for="uiServerConfiguration in uiServerConfigurations"
50 :value="uiServerConfiguration.index"
51 >
52 {{
53 uiServerConfiguration.configuration.name ?? uiServerConfiguration.configuration.host
54 }}
55 </option>
56 </select>
57 </Container>
58 <ToggleButton
59 :id="'simulator'"
60 :key="state.renderSimulator"
61 :status="state.simulatorState?.started"
62 :on="() => startSimulator()"
63 :off="() => stopSimulator()"
64 :class="simulatorButtonClass"
65 >
66 {{ simulatorButtonMessage }}
67 </ToggleButton>
68 <ToggleButton
69 :id="'add-charging-stations'"
70 :key="state.renderAddChargingStations"
71 :shared="true"
72 :on="
73 () => {
74 $router.push({ name: 'add-charging-stations' })
75 }
76 "
77 :off="
78 () => {
79 $router.push({ name: 'charging-stations' })
80 }
81 "
82 @clicked="
83 () => {
84 state.renderChargingStations = randomUUID()
85 }
86 "
87 >
88 Add Charging Stations
89 </ToggleButton>
90 <ReloadButton
91 id="reload-button"
92 :loading="state.gettingChargingStations"
93 @click="getChargingStations()"
94 />
95 </Container>
96 <CSTable
97 v-show="Array.isArray($chargingStations.value) && $chargingStations.value.length > 0"
98 :key="state.renderChargingStations"
99 :charging-stations="$chargingStations.value"
100 @need-refresh="
101 () => {
102 state.renderAddChargingStations = randomUUID()
103 state.renderChargingStations = randomUUID()
104 }
105 "
106 />
107 </Container>
108 </template>
109
110 <script setup lang="ts">
111 import { computed, getCurrentInstance, onMounted, onUnmounted, ref } from 'vue'
112 import { useToast } from 'vue-toast-notification'
113 import CSTable from '@/components/charging-stations/CSTable.vue'
114 import type { ResponsePayload, SimulatorState, UIServerConfigurationSection } from '@/types'
115 import Container from '@/components/Container.vue'
116 import ReloadButton from '@/components/buttons/ReloadButton.vue'
117 import {
118 deleteFromLocalStorage,
119 getFromLocalStorage,
120 getLocalStorage,
121 randomUUID,
122 setToLocalStorage,
123 useUIClient
124 } from '@/composables'
125 import ToggleButton from '@/components/buttons/ToggleButton.vue'
126
127 const state = ref<{
128 renderSimulator: `${string}-${string}-${string}-${string}-${string}`
129 renderAddChargingStations: `${string}-${string}-${string}-${string}-${string}`
130 renderChargingStations: `${string}-${string}-${string}-${string}-${string}`
131 gettingSimulatorState: boolean
132 gettingTemplates: boolean
133 gettingChargingStations: boolean
134 simulatorState?: SimulatorState
135 uiServerIndex: number
136 }>({
137 renderSimulator: randomUUID(),
138 renderAddChargingStations: randomUUID(),
139 renderChargingStations: randomUUID(),
140 gettingSimulatorState: false,
141 gettingTemplates: false,
142 gettingChargingStations: false,
143 uiServerIndex: getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
144 })
145
146 const simulatorButtonClass = computed<string>(() =>
147 state.value.simulatorState?.started === true ? 'simulator-stop-button' : 'simulator-start-button'
148 )
149 const simulatorButtonMessage = computed<string>(
150 () =>
151 `${state.value.simulatorState?.started === true ? 'Stop' : 'Start'} Simulator${state.value.simulatorState?.version != null ? ` (${state.value.simulatorState.version})` : ''}`
152 )
153
154 const clearToggleButtons = (): void => {
155 for (const key in getLocalStorage()) {
156 if (key.includes('toggle-button')) {
157 deleteFromLocalStorage(key)
158 }
159 }
160 state.value.renderChargingStations = randomUUID()
161 state.value.renderAddChargingStations = randomUUID()
162 }
163
164 const app = getCurrentInstance()
165
166 const clearChargingStations = (): void => {
167 if (app != null) {
168 app.appContext.config.globalProperties.$chargingStations.value = []
169 }
170 state.value.renderChargingStations = randomUUID()
171 }
172
173 const uiClient = useUIClient()
174
175 const $toast = useToast()
176
177 const getSimulatorState = (): void => {
178 if (state.value.gettingSimulatorState === false) {
179 state.value.gettingSimulatorState = true
180 uiClient
181 .simulatorState()
182 .then((response: ResponsePayload) => {
183 state.value.simulatorState = response.state as SimulatorState
184 })
185 .catch((error: Error) => {
186 $toast.error('Error at fetching simulator state')
187 console.error('Error at fetching simulator state:', error)
188 })
189 .finally(() => {
190 state.value.renderSimulator = randomUUID()
191 state.value.gettingSimulatorState = false
192 })
193 }
194 }
195
196 const getTemplates = (): void => {
197 if (state.value.gettingTemplates === false) {
198 state.value.gettingTemplates = true
199 uiClient
200 .listTemplates()
201 .then((response: ResponsePayload) => {
202 if (app != null) {
203 app.appContext.config.globalProperties.$templates.value = response.templates
204 }
205 })
206 .catch((error: Error) => {
207 if (app != null) {
208 app.appContext.config.globalProperties.$templates.value = []
209 }
210 $toast.error('Error at fetching charging station templates')
211 console.error('Error at fetching charging station templates:', error)
212 })
213 .finally(() => {
214 state.value.gettingTemplates = false
215 })
216 }
217 }
218
219 const getChargingStations = (): void => {
220 if (state.value.gettingChargingStations === false) {
221 state.value.gettingChargingStations = true
222 uiClient
223 .listChargingStations()
224 .then((response: ResponsePayload) => {
225 if (app != null) {
226 app.appContext.config.globalProperties.$chargingStations.value = response.chargingStations
227 }
228 })
229 .catch((error: Error) => {
230 if (app != null) {
231 app.appContext.config.globalProperties.$chargingStations.value = []
232 }
233 $toast.error('Error at fetching charging stations')
234 console.error('Error at fetching charging stations:', error)
235 })
236 .finally(() => {
237 state.value.renderChargingStations = randomUUID()
238 state.value.gettingChargingStations = false
239 })
240 }
241 }
242
243 const getData = (): void => {
244 getSimulatorState()
245 getTemplates()
246 getChargingStations()
247 }
248
249 const registerWSEventListeners = () => {
250 uiClient.registerWSEventListener('open', getData)
251 uiClient.registerWSEventListener('error', clearChargingStations)
252 uiClient.registerWSEventListener('close', clearChargingStations)
253 }
254
255 const unregisterWSEventListeners = () => {
256 uiClient.unregisterWSEventListener('open', getData)
257 uiClient.unregisterWSEventListener('error', clearChargingStations)
258 uiClient.unregisterWSEventListener('close', clearChargingStations)
259 }
260
261 onMounted(() => {
262 registerWSEventListeners()
263 })
264
265 onUnmounted(() => {
266 unregisterWSEventListeners()
267 })
268
269 const uiServerConfigurations: { index: number; configuration: UIServerConfigurationSection }[] =
270 app?.appContext.config.globalProperties.$configuration.value.uiServer.map(
271 (configuration: UIServerConfigurationSection, index: number) => ({
272 index,
273 configuration
274 })
275 )
276
277 const startSimulator = (): void => {
278 uiClient
279 .startSimulator()
280 .then(() => {
281 $toast.success('Simulator successfully started')
282 })
283 .catch((error: Error) => {
284 $toast.error('Error at starting simulator')
285 console.error('Error at starting simulator:', error)
286 })
287 .finally(() => {
288 getSimulatorState()
289 })
290 }
291 const stopSimulator = (): void => {
292 uiClient
293 .stopSimulator()
294 .then(() => {
295 if (app != null) {
296 app.appContext.config.globalProperties.$chargingStations.value = []
297 }
298 $toast.success('Simulator successfully stopped')
299 })
300 .catch((error: Error) => {
301 $toast.error('Error at stopping simulator')
302 console.error('Error at stopping simulator:', error)
303 })
304 .finally(() => {
305 getSimulatorState()
306 })
307 }
308 </script>
309
310 <style>
311 #charging-stations-container {
312 height: fit-content;
313 width: 100%;
314 display: flex;
315 flex-direction: column;
316 }
317
318 #ui-server-container {
319 display: flex;
320 justify-content: center;
321 border-style: outset;
322 }
323
324 #ui-server-selector {
325 width: 100%;
326 background-color: rgb(239, 239, 239);
327 font: small-caption;
328 text-align: center;
329 }
330
331 #ui-server-selector:hover {
332 background-color: rgb(229, 229, 229);
333 }
334
335 #buttons-container {
336 display: flex;
337 flex-direction: row;
338 }
339
340 .simulator-start-button {
341 color: ivory;
342 background-color: green;
343 }
344
345 .simulator-start-button:hover {
346 background-color: rgb(0, 98, 0);
347 }
348
349 .simulator-stop-button {
350 color: ivory;
351 background-color: red;
352 }
353
354 .simulator-stop-button:hover {
355 background-color: rgb(225, 0, 0);
356 }
357
358 #action-button {
359 flex: none;
360 }
361
362 #reload-button {
363 color: ivory;
364 background-color: blue;
365 font-size: 2rem;
366 }
367
368 #reload-button:hover {
369 background-color: rgb(0, 0, 225);
370 }
371
372 #reload-button:active {
373 background-color: darkblue;
374 }
375
376 #action {
377 min-width: max-content;
378 color: ivory;
379 background-color: black;
380 padding: 0.8%;
381 }
382 </style>