From 93cacfb376c7856e85eb8607db8cd3edb8e160f2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 17 Apr 2026 02:53:23 +0200 Subject: [PATCH] refactor(ui): move generic utilities to ui-common and add useFetchData composable - Move convertToBoolean(), convertToInt() to ui-common/src/utils/converters.ts - Move getWebSocketStateName() to ui-common/src/utils/websocket.ts using portable WebSocketReadyState enum instead of browser WebSocket constants - Add useFetchData() composable to deduplicate fetch-with-loading-guard pattern in ChargingStationsView (3 instances collapsed) - All consumers import directly from ui-common, no re-exports --- ui/common/src/index.ts | 2 + ui/common/src/utils/converters.ts | 35 +++++++ ui/common/src/utils/websocket.ts | 16 ++++ .../actions/AddChargingStations.vue | 3 +- .../components/actions/StartTransaction.vue | 4 +- .../components/charging-stations/CSData.vue | 6 +- ui/web/src/composables/Utils.ts | 96 +++++++------------ ui/web/src/composables/index.ts | 4 +- ui/web/src/views/ChargingStationsView.vue | 87 +++++------------ ui/web/tests/unit/Utils.test.ts | 4 +- 10 files changed, 116 insertions(+), 141 deletions(-) create mode 100644 ui/common/src/utils/converters.ts create mode 100644 ui/common/src/utils/websocket.ts diff --git a/ui/common/src/index.ts b/ui/common/src/index.ts index c18620e2..d79e9be3 100644 --- a/ui/common/src/index.ts +++ b/ui/common/src/index.ts @@ -10,4 +10,6 @@ export * from './types/ConfigurationType.js' export * from './types/JsonType.js' export * from './types/UIProtocol.js' export * from './types/UUID.js' +export * from './utils/converters.js' export * from './utils/UUID.js' +export * from './utils/websocket.js' diff --git a/ui/common/src/utils/converters.ts b/ui/common/src/utils/converters.ts new file mode 100644 index 00000000..f3c4e0af --- /dev/null +++ b/ui/common/src/utils/converters.ts @@ -0,0 +1,35 @@ +export const convertToBoolean = (value: unknown): boolean => { + let result = false + if (value != null) { + if (typeof value === 'boolean') { + return value + } else if (typeof value === 'string') { + const normalized = value.trim().toLowerCase() + result = normalized === 'true' || normalized === '1' + } else if (typeof value === 'number' && value === 1) { + result = true + } + } + return result +} + +export const convertToInt = (value: unknown): number => { + if (value == null) { + return 0 + } + if (Number.isSafeInteger(value)) { + return value as number + } + if (typeof value === 'number') { + return Math.trunc(value) + } + let changedValue: number = value as number + if (typeof value === 'string') { + changedValue = Number.parseInt(value) + } + if (Number.isNaN(changedValue)) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string + throw new Error(`Cannot convert to integer: '${value.toString()}'`) + } + return changedValue +} diff --git a/ui/common/src/utils/websocket.ts b/ui/common/src/utils/websocket.ts new file mode 100644 index 00000000..302f28ee --- /dev/null +++ b/ui/common/src/utils/websocket.ts @@ -0,0 +1,16 @@ +import { WebSocketReadyState } from '../client/types.js' + +export const getWebSocketStateName = (state: number | undefined): string | undefined => { + switch (state) { + case WebSocketReadyState.CLOSED: + return 'Closed' + case WebSocketReadyState.CLOSING: + return 'Closing' + case WebSocketReadyState.CONNECTING: + return 'Connecting' + case WebSocketReadyState.OPEN: + return 'Open' + default: + return undefined + } +} diff --git a/ui/web/src/components/actions/AddChargingStations.vue b/ui/web/src/components/actions/AddChargingStations.vue index 159a4006..935a0210 100644 --- a/ui/web/src/components/actions/AddChargingStations.vue +++ b/ui/web/src/components/actions/AddChargingStations.vue @@ -91,13 +91,12 @@