diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 106ee7d..a53adac 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -785,7 +785,8 @@ "auth": { "logout": "Deconnexion reussie" }, - "title": "Succès" + "title": "Succès", + "deleted": "Suppression effectuée" }, "admin": { "roles": { diff --git a/frontend/modules/commercial/pages/clients/[id]/edit.vue b/frontend/modules/commercial/pages/clients/[id]/edit.vue index 4506f0d..266879a 100644 --- a/frontend/modules/commercial/pages/clients/[id]/edit.vue +++ b/frontend/modules/commercial/pages/clients/[id]/edit.vue @@ -701,6 +701,11 @@ function showError(e: unknown, opts: { duplicateCompany?: boolean } = {}): void }) } +/** Toast de succès après suppression serveur confirmée d'un bloc (contact / adresse / RIB). */ +function notifyRemovalSuccess(): void { + toast.success({ title: t('success.title'), message: t('success.deleted') }) +} + // ── Erreurs de validation par champ (ERP-101) ─────────────────────────────── // Etat d'erreurs factorise avec l'ecran de creation (cf. useClientFormErrors) : // un `useFormErrors` par groupe scalaire + un tableau d'erreurs par ligne pour @@ -800,6 +805,7 @@ function askRemoveContact(index: number): void { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyContact, onError: showError, + onSuccess: notifyRemovalSuccess, })) } @@ -877,6 +883,7 @@ function askRemoveAddress(index: number): void { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyAddress, onError: showError, + onSuccess: notifyRemovalSuccess, })) } @@ -977,6 +984,7 @@ function askRemoveRib(index: number): void { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyRib, onError: showError, + onSuccess: notifyRemovalSuccess, })) } diff --git a/frontend/modules/commercial/pages/suppliers/[id]/edit.vue b/frontend/modules/commercial/pages/suppliers/[id]/edit.vue index 1c22fd8..2526c77 100644 --- a/frontend/modules/commercial/pages/suppliers/[id]/edit.vue +++ b/frontend/modules/commercial/pages/suppliers/[id]/edit.vue @@ -616,6 +616,11 @@ function showError(e: unknown): void { toast.error({ title: t('commercial.suppliers.toast.error'), message: apiErrorMessage(e) }) } +/** Toast de succès après suppression serveur confirmée d'un bloc (contact / adresse / RIB). */ +function notifyRemovalSuccess(): void { + toast.success({ title: t('success.title'), message: t('success.deleted') }) +} + // ── Erreurs de validation par champ (ERP-101) ─────────────────────────────── const { mainErrors, @@ -699,6 +704,7 @@ function askRemoveContact(index: number): void { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyContact, onError: showError, + onSuccess: notifyRemovalSuccess, })) } @@ -767,6 +773,7 @@ function askRemoveAddress(index: number): void { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyAddress, onError: showError, + onSuccess: notifyRemovalSuccess, })) } @@ -866,6 +873,7 @@ function askRemoveRib(index: number): void { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyRib, onError: showError, + onSuccess: notifyRemovalSuccess, })) } diff --git a/frontend/modules/technique/composables/useProviderForm.ts b/frontend/modules/technique/composables/useProviderForm.ts index 60b33a6..96820fa 100644 --- a/frontend/modules/technique/composables/useProviderForm.ts +++ b/frontend/modules/technique/composables/useProviderForm.ts @@ -84,6 +84,11 @@ export function useProviderForm() { }) } + /** Toast de succès après suppression serveur confirmée d'une sous-ressource. */ + function notifyRemovalSuccess(): void { + toast.success({ title: t('success.title'), message: t('success.deleted') }) + } + // ── Etat du prestataire cree ──────────────────────────────────────────── const providerId = ref(null) const mainLocked = ref(false) @@ -339,6 +344,7 @@ export function useProviderForm() { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyProviderContact, onError: notifyRemovalError, + onSuccess: notifyRemovalSuccess, }) } @@ -417,6 +423,7 @@ export function useProviderForm() { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyProviderAddress, onError: notifyRemovalError, + onSuccess: notifyRemovalSuccess, }) } @@ -518,6 +525,7 @@ export function useProviderForm() { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyProviderRib, onError: notifyRemovalError, + onSuccess: notifyRemovalSuccess, }) } diff --git a/frontend/modules/transport/composables/useCarrierForm.ts b/frontend/modules/transport/composables/useCarrierForm.ts index b696dc9..c8fc59e 100644 --- a/frontend/modules/transport/composables/useCarrierForm.ts +++ b/frontend/modules/transport/composables/useCarrierForm.ts @@ -416,6 +416,11 @@ export function useCarrierForm() { }) } + /** Toast de succès après suppression serveur confirmée d'une sous-ressource. */ + function notifyRemovalSuccess(): void { + toast.success({ title: t('success.title'), message: t('success.deleted') }) + } + /** * Soumet TOUS les blocs d'une collection en collectant les erreurs PAR INDEX : * on n'arrête pas au premier bloc en échec (décision ERP-101). Réinitialise la @@ -540,6 +545,7 @@ export function useCarrierForm() { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyCarrierContact, onError: notifyRemovalError, + onSuccess: notifyRemovalSuccess, }) } @@ -653,6 +659,7 @@ export function useCarrierForm() { deleteRow: url => api.delete(url, {}, { toast: false }), makeEmpty: emptyCarrierPrice, onError: notifyRemovalError, + onSuccess: notifyRemovalSuccess, }) } diff --git a/frontend/shared/utils/__tests__/collectionRow.test.ts b/frontend/shared/utils/__tests__/collectionRow.test.ts index 973af8e..d731fb2 100644 --- a/frontend/shared/utils/__tests__/collectionRow.test.ts +++ b/frontend/shared/utils/__tests__/collectionRow.test.ts @@ -22,11 +22,12 @@ describe('removeCollectionRow', () => { const errors: Record[] = [{}, {}] const deleteRow = vi.fn().mockResolvedValue(undefined) const onError = vi.fn() + const onSuccess = vi.fn() const removed = await removeCollectionRow({ rows, errors, index: 0, endpoint: '/client_contacts', - deleteRow, makeEmpty, onError, + deleteRow, makeEmpty, onError, onSuccess, }) expect(deleteRow).toHaveBeenCalledOnce() @@ -35,6 +36,8 @@ describe('removeCollectionRow', () => { expect(rows).toEqual([{ id: 11, label: 'B' }]) expect(errors).toHaveLength(1) expect(onError).not.toHaveBeenCalled() + // Toast de succes uniquement sur suppression serveur confirmee. + expect(onSuccess).toHaveBeenCalledOnce() }) it('ne fait AUCUN appel reseau pour un bloc jamais persiste (id null) — retrait local', async () => { @@ -42,16 +45,19 @@ describe('removeCollectionRow', () => { const errors: Record[] = [{}, {}] const deleteRow = vi.fn().mockResolvedValue(undefined) const onError = vi.fn() + const onSuccess = vi.fn() const removed = await removeCollectionRow({ rows, errors, index: 1, endpoint: '/client_contacts', - deleteRow, makeEmpty, onError, + deleteRow, makeEmpty, onError, onSuccess, }) expect(deleteRow).not.toHaveBeenCalled() expect(removed).toBe(true) expect(rows).toEqual([{ id: 10, label: 'A' }]) + // Retrait d'un simple brouillon local : pas de toast « supprime ». + expect(onSuccess).not.toHaveBeenCalled() }) it('conserve le bloc et remonte l\'erreur si le DELETE serveur echoue (ex. 409 dernier RIB LCR)', async () => { diff --git a/frontend/shared/utils/collectionRow.ts b/frontend/shared/utils/collectionRow.ts index 6855c81..c13b602 100644 --- a/frontend/shared/utils/collectionRow.ts +++ b/frontend/shared/utils/collectionRow.ts @@ -33,6 +33,12 @@ export interface RemoveCollectionRowOptions { makeEmpty: () => T /** Remontee d'erreur 409/422 mappee proprement (message back, pas de toast fourre-tout). */ onError: (error: unknown) => void + /** + * Callback de succes (toast) appele UNIQUEMENT apres une suppression serveur + * confirmee d'un bloc persiste (`id` non null). Pas appele sur le simple retrait + * d'un brouillon local non enregistre (aucune suppression reelle). + */ + onSuccess?: () => void } /** @@ -55,8 +61,9 @@ export interface RemoveCollectionRowOptions { export async function removeCollectionRow( options: RemoveCollectionRowOptions, ): Promise { - const { rows, errors, index, endpoint, deleteRow, makeEmpty, onError } = options + const { rows, errors, index, endpoint, deleteRow, makeEmpty, onError, onSuccess } = options const removed = rows[index] + let serverDeleted = false // Bloc existant : suppression serveur d'abord, retrait local seulement si OK. if (removed?.id != null) { @@ -67,6 +74,7 @@ export async function removeCollectionRow( onError(error) return false } + serverDeleted = true } rows.splice(index, 1) @@ -75,5 +83,9 @@ export async function removeCollectionRow( if (rows.length === 0) { rows.push(makeEmpty()) } + // Toast de succes uniquement quand le serveur a confirme une vraie suppression. + if (serverDeleted) { + onSuccess?.() + } return true }