From 608d7962dbdde0f3b3261badca05ab6dad36d84b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 23:33:27 +0200 Subject: [PATCH] fix(web): fetch templates on dialog open via layout composable (#1837) (#1839) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Expose getTemplates from useLayoutData instead of duplicating the fetch in useAddStationsForm. Each layout triggers the refresh at the right moment: - Modern skin: watch showAddDialog → getTemplates() on open - Classic skin: watch route → getTemplates() on ADD_CHARGING_STATIONS The templatesEqual guard in useAddStationsForm prevents resetting the user's selection when the refetch returns an identical list. Co-authored-by: Jérôme Benoit --- .../shared/composables/useAddStationsForm.ts | 20 +++++++++++++++++-- .../src/shared/composables/useLayoutData.ts | 3 +++ ui/web/src/skins/classic/ClassicLayout.vue | 4 +++- ui/web/src/skins/modern/ModernLayout.vue | 8 ++++++-- .../composables/useAddStationsForm.test.ts | 9 +++++++++ .../tests/unit/skins/modern/Dialogs.test.ts | 1 + 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/ui/web/src/shared/composables/useAddStationsForm.ts b/ui/web/src/shared/composables/useAddStationsForm.ts index f6a2057d..c299fe7d 100644 --- a/ui/web/src/shared/composables/useAddStationsForm.ts +++ b/ui/web/src/shared/composables/useAddStationsForm.ts @@ -39,8 +39,13 @@ export function useAddStationsForm (options?: { onFinally?: () => void }): { const formState = ref(makeInitialState()) const pending = ref(false) - watch($templates, () => { - formState.value.renderTemplates = randomUUID() + watch($templates, (newTemplates, oldTemplates) => { + // Only regenerate the key when the template list content actually changes. + // This prevents destroying the user's current selection when an in-flight + // refresh returns the same set of templates. + if (!templatesEqual(newTemplates, oldTemplates)) { + formState.value.renderTemplates = randomUUID() + } }) /** Resets form state to initial defaults. */ @@ -127,3 +132,14 @@ function makeInitialState (): AddStationsFormState { function nonEmpty (value: string): string | undefined { return value.length > 0 ? value : undefined } + +/** + * Returns `true` when both arrays have identical length and values in the same order. + * @param a - The incoming template list. + * @param b - The previous template list. + * @returns Whether the two arrays are deeply equal. + */ +function templatesEqual (a: string[], b: string[] | undefined): boolean { + if (a.length !== b?.length) return false + return a.every((v, i) => v === b[i]) +} diff --git a/ui/web/src/shared/composables/useLayoutData.ts b/ui/web/src/shared/composables/useLayoutData.ts index dca3b8a7..e0a547dd 100644 --- a/ui/web/src/shared/composables/useLayoutData.ts +++ b/ui/web/src/shared/composables/useLayoutData.ts @@ -21,6 +21,8 @@ export interface LayoutData { getData: () => void /** Fetches only the simulator state. */ getSimulatorState: () => void + /** Fetches only the charging station templates list. */ + getTemplates: () => void /** Whether any data fetch is currently in progress. */ loading: ComputedRef /** Registers WS event listeners for open/error/close. */ @@ -136,6 +138,7 @@ export function useLayoutData (): LayoutData { getChargingStations, getData, getSimulatorState, + getTemplates, loading, // Exposed for edge cases (e.g. hot-reload); normally called via onMounted/onUnmounted. registerWSEventListeners, diff --git a/ui/web/src/skins/classic/ClassicLayout.vue b/ui/web/src/skins/classic/ClassicLayout.vue index 316bf752..395d4114 100644 --- a/ui/web/src/skins/classic/ClassicLayout.vue +++ b/ui/web/src/skins/classic/ClassicLayout.vue @@ -123,7 +123,7 @@ import CSTable from './components/charging-stations/CSTable.vue' import Container from './components/ClassicContainer.vue' const layoutData = useLayoutData() -const { simulatorStarted, simulatorState, uiServerConfigurations } = layoutData +const { getTemplates, simulatorStarted, simulatorState, uiServerConfigurations } = layoutData const startSimulatorLabel = computed( () => @@ -183,6 +183,8 @@ watch( name => { if (name === ROUTE_NAMES.CHARGING_STATIONS) { refresh() + } else if (name === ROUTE_NAMES.ADD_CHARGING_STATIONS) { + getTemplates() } } ) diff --git a/ui/web/src/skins/modern/ModernLayout.vue b/ui/web/src/skins/modern/ModernLayout.vue index 949ac6c9..a3a501d2 100644 --- a/ui/web/src/skins/modern/ModernLayout.vue +++ b/ui/web/src/skins/modern/ModernLayout.vue @@ -77,7 +77,7 @@