From be079cdbe28da41100661feda404261d21553320 Mon Sep 17 00:00:00 2001 From: tristan Date: Thu, 25 Jun 2026 20:43:38 +0200 Subject: [PATCH 1/7] =?UTF-8?q?feat(directory)=20:=20conserve=20l'onglet?= =?UTF-8?q?=20actif=20au=20retour=20liste=20=E2=86=94=20fiche=20(LST-72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Au retour depuis une fiche (flèche de l'app ou du navigateur), la liste revenait toujours sur l'onglet Clients. L'onglet actif est désormais transmis via history.state (hors URL), comme sur Starseed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../pages/directory/clients/[id].vue | 3 +- .../directory/pages/directory/index.vue | 18 ++++++++-- .../pages/directory/prestataires/[id].vue | 3 +- .../pages/directory/prospects/[id].vue | 3 +- frontend/utils/historyTab.ts | 35 +++++++++++++++++++ 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 frontend/utils/historyTab.ts diff --git a/frontend/modules/directory/pages/directory/clients/[id].vue b/frontend/modules/directory/pages/directory/clients/[id].vue index 3d28983..de6a28b 100644 --- a/frontend/modules/directory/pages/directory/clients/[id].vue +++ b/frontend/modules/directory/pages/directory/clients/[id].vue @@ -192,7 +192,8 @@ async function saveInfo(): Promise { } function goBack(): void { - router.push('/directory') + // Retour sur l'onglet Clients de la liste (via history.state, hors URL). + router.push({ path: '/directory', state: { tab: 'clients' } }) } onMounted(async () => { diff --git a/frontend/modules/directory/pages/directory/index.vue b/frontend/modules/directory/pages/directory/index.vue index b4e8005..21a63a8 100644 --- a/frontend/modules/directory/pages/directory/index.vue +++ b/frontend/modules/directory/pages/directory/index.vue @@ -183,6 +183,7 @@ import type { Prospect, ProspectStatus } from '~/modules/directory/services/dto/ import { useProspectService } from '~/modules/directory/services/prospects' import type { Prestataire } from '~/modules/directory/services/dto/prestataire' import { usePrestataireService } from '~/modules/directory/services/prestataires' +import { readHistoryTab, stampHistoryTab } from '~/utils/historyTab' definePageMeta({ middleware: ['admin'] }) @@ -201,6 +202,14 @@ const tabs = [ { key: 'prospects', label: t('directory.tabs.prospects'), icon: 'mdi:account-search-outline' }, { key: 'prestataires', label: t('directory.tabs.prestataires'), icon: 'mdi:account-hard-hat-outline' }, ] +const tabKeys = tabs.map((tab) => tab.key) + +// Avant d'ouvrir une fiche : on estampille l'entrée d'historique courante avec +// l'onglet actif → la flèche « précédent » du navigateur restaure le bon onglet. +function navigateToDetail(path: string): void { + stampHistoryTab(activeTab.value) + navigateTo(path) +} // --- Clients --- const clients = ref([]) @@ -224,7 +233,7 @@ function openCreateClient() { } function openEditClient(item: Record) { - navigateTo(`/directory/clients/${(item as Client).id}`) + navigateToDetail(`/directory/clients/${(item as Client).id}`) } // --- Prospects --- @@ -282,7 +291,7 @@ function openCreateProspect() { } function openEditProspect(item: Record) { - navigateTo(`/directory/prospects/${(item as Prospect).id}`) + navigateToDetail(`/directory/prospects/${(item as Prospect).id}`) } async function convertProspect(row: ProspectRow) { @@ -319,7 +328,7 @@ function openCreatePrestataire() { } function openEditPrestataire(item: Record) { - navigateTo(`/directory/prestataires/${(item as Prestataire).id}`) + navigateToDetail(`/directory/prestataires/${(item as Prestataire).id}`) } // --- Suppression (clients, prospects & prestataires) --- @@ -392,6 +401,9 @@ async function confirmDelete() { watch(statusFilter, loadProspects) onMounted(async () => { + // Restaure l'onglet quitté lors d'un retour depuis une fiche (flèche app ou + // navigateur). `null` (deep link / reload) → onglet Clients par défaut. + activeTab.value = readHistoryTab(tabKeys) ?? 'clients' await Promise.all([loadClients(), loadProspects(), loadPrestataires()]) }) diff --git a/frontend/modules/directory/pages/directory/prestataires/[id].vue b/frontend/modules/directory/pages/directory/prestataires/[id].vue index d677e9b..8d1e2b9 100644 --- a/frontend/modules/directory/pages/directory/prestataires/[id].vue +++ b/frontend/modules/directory/pages/directory/prestataires/[id].vue @@ -190,7 +190,8 @@ async function saveInfo(): Promise { } function goBack(): void { - router.push('/directory') + // Retour sur l'onglet Prestataires de la liste (via history.state, hors URL). + router.push({ path: '/directory', state: { tab: 'prestataires' } }) } onMounted(async () => { diff --git a/frontend/modules/directory/pages/directory/prospects/[id].vue b/frontend/modules/directory/pages/directory/prospects/[id].vue index ef8d568..0bce0b6 100644 --- a/frontend/modules/directory/pages/directory/prospects/[id].vue +++ b/frontend/modules/directory/pages/directory/prospects/[id].vue @@ -226,7 +226,8 @@ async function saveInfo(): Promise { } function goBack(): void { - router.push('/directory') + // Retour sur l'onglet Prospects de la liste (via history.state, hors URL). + router.push({ path: '/directory', state: { tab: 'prospects' } }) } onMounted(async () => { diff --git a/frontend/utils/historyTab.ts b/frontend/utils/historyTab.ts new file mode 100644 index 0000000..4656aa8 --- /dev/null +++ b/frontend/utils/historyTab.ts @@ -0,0 +1,35 @@ +/** + * Onglet actif transmis d'une page à l'autre via l'état d'historique + * (`history.state`), SANS le mettre dans l'URL. Sert à préserver l'onglet courant + * du Répertoire (Clients / Prospects / Prestataires) lors de l'aller-retour + * liste ↔ fiche, dans les deux sens (flèche de l'app ET flèche du navigateur). + * + * On reste fidèle à la règle « état d'UI local, pas dans l'URL » : l'onglet + * voyage dans l'entrée d'historique de la navigation, l'URL ne change pas. + */ + +/** + * Lit la clé d'onglet posée dans `history.state.tab` si elle fait partie des + * onglets valides. Retourne `null` sinon : navigation directe / deep link, + * rechargement de page, ou onglet inexistant. + */ +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 +} + +/** + * Estampille l'entrée d'historique COURANTE avec l'onglet actif, sans créer de + * nouvelle entrée ni changer l'URL. À appeler juste avant de naviguer vers une + * fiche : au retour via la flèche du navigateur (popstate), cette entrée + * « liste » est restaurée avec son onglet. + */ +export function stampHistoryTab(tab: string): void { + if (typeof window === 'undefined') { + return + } + window.history.replaceState({ ...window.history.state, tab }, '') +} -- 2.39.5 From 6b6583906150f9f0f07bb4c0c5d71420131eacee Mon Sep 17 00:00:00 2001 From: tristan Date: Fri, 26 Jun 2026 15:43:49 +0200 Subject: [PATCH 2/7] =?UTF-8?q?feat(directory)=20:=20ent=C3=AAte=20Action,?= =?UTF-8?q?=20hover,=20modal=20de=20conversion=20et=20ajustements=20UI=20(?= =?UTF-8?q?LST-72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Colonne « Action » avec entête (alignée à droite) sur les 3 tableaux - Feedback hover sur les boutons d'action (poubelle / convertir) - Conversion prospect → client passe par une modal de confirmation - ConfirmModal basé sur MalioModal (design Starseed), remplace ConfirmDeleteModal - Nom (client/prospect/prestataire) en gras dans les modals via - Boutons « Ajouter » : label raccourci + taille standard Malio (180px) - Barres d'outils à hauteur homogène (48px) : le bouton ne saute plus entre onglets Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/i18n/locales/fr.json | 4 + .../components/ConfirmDeleteModal.vue | 58 ---------- .../directory/components/ConfirmModal.vue | 54 +++++++++ .../directory/pages/directory/index.vue | 103 +++++++++++++----- 4 files changed, 131 insertions(+), 88 deletions(-) delete mode 100644 frontend/modules/directory/components/ConfirmDeleteModal.vue create mode 100644 frontend/modules/directory/components/ConfirmModal.vue diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 9dabbc1..dd42cb2 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -424,6 +424,7 @@ "edit": "Modifier", "delete": "Supprimer", "add": "Ajouter", + "actions": "Action", "loading": "Chargement...", "archived": "Archivé", "noClient": "Aucun client", @@ -918,6 +919,9 @@ "editProspect": "Modifier un prospect", "convert": "Convertir en client", "alreadyConverted": "Déjà converti en client", + "convertConfirmTitle": "Convertir le prospect", + "convertConfirmMessage": "Êtes-vous sûr de vouloir convertir le prospect « {name} » en client ? Le prospect deviendra un client.", + "convertConfirm": "Convertir", "deleteConfirmTitle": "Supprimer le prospect", "deleteConfirmMessage": "Êtes-vous sûr de vouloir supprimer le prospect « {name} » ? Cette action est irréversible.", "fields": { diff --git a/frontend/modules/directory/components/ConfirmDeleteModal.vue b/frontend/modules/directory/components/ConfirmDeleteModal.vue deleted file mode 100644 index 983750c..0000000 --- a/frontend/modules/directory/components/ConfirmDeleteModal.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - - - diff --git a/frontend/modules/directory/components/ConfirmModal.vue b/frontend/modules/directory/components/ConfirmModal.vue new file mode 100644 index 0000000..1770d13 --- /dev/null +++ b/frontend/modules/directory/components/ConfirmModal.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/modules/directory/pages/directory/index.vue b/frontend/modules/directory/pages/directory/index.vue index 21a63a8..4f7dd5a 100644 --- a/frontend/modules/directory/pages/directory/index.vue +++ b/frontend/modules/directory/pages/directory/index.vue @@ -9,12 +9,11 @@