diff --git a/frontend/modules/commercial/pages/clients/[id]/edit.vue b/frontend/modules/commercial/pages/clients/[id]/edit.vue index bc824b0..0fc367f 100644 --- a/frontend/modules/commercial/pages/clients/[id]/edit.vue +++ b/frontend/modules/commercial/pages/clients/[id]/edit.vue @@ -440,6 +440,7 @@ import { type RibFormDraft, } from '~/modules/commercial/types/clientForm' import { extractApiErrorMessage } from '~/shared/utils/api' +import { readHistoryTab } from '~/shared/utils/historyTab' // Masques de saisie (la normalisation finale reste serveur). const SIREN_MASK = '#########' @@ -626,11 +627,13 @@ const tabs = computed(() => tabKeys.value.map(key => ({ icon: TAB_ICONS[key], }))) -const activeTab = ref('information') +// Onglet initial : repris de la consultation (history.state), sinon Information. +const activeTab = ref(readHistoryTab(tabKeys.value) ?? 'information') // ── Navigation ────────────────────────────────────────────────────────────── +/** Retour consultation en conservant l'onglet courant (via history.state). */ function goBack(): void { - router.push(`/clients/${clientId}`) + router.push({ path: `/clients/${clientId}`, state: { tab: activeTab.value } }) } /** diff --git a/frontend/modules/commercial/pages/clients/[id]/index.vue b/frontend/modules/commercial/pages/clients/[id]/index.vue index 357ff11..fe05227 100644 --- a/frontend/modules/commercial/pages/clients/[id]/index.vue +++ b/frontend/modules/commercial/pages/clients/[id]/index.vue @@ -281,6 +281,7 @@ import { computed, onMounted, ref } from 'vue' import { useClient } from '~/modules/commercial/composables/useClient' import { buildClientFormTabKeys } from '~/modules/commercial/utils/clientFormRules' +import { readHistoryTab } from '~/shared/utils/historyTab' import { canEditClient, categoryOptionsOf, @@ -416,15 +417,17 @@ const tabs = computed(() => tabKeys.value.map(key => ({ icon: TAB_ICONS[key], }))) -const activeTab = ref('information') +// Onglet initial : repris de l'edition au retour (history.state), sinon Information. +const activeTab = ref(readHistoryTab(tabKeys.value) ?? 'information') // ── Navigation ───────────────────────────────────────────────────────────── function goBack(): void { router.push('/clients') } +/** Bascule en edition en conservant l'onglet courant (via history.state). */ function goEdit(): void { - router.push(`/clients/${clientId}/edit`) + router.push({ path: `/clients/${clientId}/edit`, state: { tab: activeTab.value } }) } // ── Archivage / Restauration ──────────────────────────────────────────────── diff --git a/frontend/shared/utils/__tests__/historyTab.test.ts b/frontend/shared/utils/__tests__/historyTab.test.ts new file mode 100644 index 0000000..3c3cec1 --- /dev/null +++ b/frontend/shared/utils/__tests__/historyTab.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, afterEach } from 'vitest' +import { readHistoryTab } from '../historyTab' + +const KEYS = ['information', 'contact', 'address', 'accounting'] + +describe('readHistoryTab', () => { + afterEach(() => { + window.history.replaceState(null, '') + }) + + it('retourne la cle d\'onglet quand elle est presente et valide', () => { + window.history.replaceState({ tab: 'address' }, '') + expect(readHistoryTab(KEYS)).toBe('address') + }) + + it('retourne null quand l\'onglet n\'est pas dans les cles valides (ex: role sans Comptabilite)', () => { + window.history.replaceState({ tab: 'accounting' }, '') + expect(readHistoryTab(['information', 'contact', 'address'])).toBeNull() + }) + + it('retourne null sans onglet dans l\'etat d\'historique (navigation directe / refresh)', () => { + window.history.replaceState(null, '') + expect(readHistoryTab(KEYS)).toBeNull() + + window.history.replaceState({ foo: 'bar' }, '') + expect(readHistoryTab(KEYS)).toBeNull() + }) + + it('retourne null quand la valeur n\'est pas une chaine', () => { + window.history.replaceState({ tab: 42 }, '') + expect(readHistoryTab(KEYS)).toBeNull() + }) +}) diff --git a/frontend/shared/utils/historyTab.ts b/frontend/shared/utils/historyTab.ts new file mode 100644 index 0000000..dd11480 --- /dev/null +++ b/frontend/shared/utils/historyTab.ts @@ -0,0 +1,22 @@ +/** + * Onglet actif transmis d'une page a l'autre via l'etat d'historique + * (`history.state`), SANS le mettre dans l'URL. Sert a preserver l'onglet courant + * au passage consultation <-> edition d'un client (dans les deux sens). + * + * On reste donc fidele a la regle « etat d'UI local, pas dans l'URL » : l'onglet + * voyage dans l'entree d'historique de la navigation, l'URL ne change pas. + */ + +/** + * Lit la cle d'onglet posee par la page precedente (`history.state.tab`) si elle + * fait partie des onglets valides pour l'utilisateur. Retourne `null` sinon : + * navigation directe / deep link, rechargement de page, ou onglet inexistant + * pour ce role (ex: Comptabilite sans la permission). + */ +export function readHistoryTab(validKeys: string[]): string | null { + if (typeof window === 'undefined') { + return null + } + const tab = (window.history.state as Record | null)?.tab + return typeof tab === 'string' && validKeys.includes(tab) ? tab : null +}