fix(front) : toast de succes a la suppression d'un bloc (contact / adresse / RIB / prix) sur les 4 modules (ERP-193)
removeCollectionRow expose un callback onSuccess declenche uniquement apres une suppression serveur confirmee (pas sur le retrait d'un brouillon local). Cable sur Client / Fournisseur / Prestataire / Transporteur via notifyRemovalSuccess, avec un message i18n generique success.deleted.
This commit is contained in:
@@ -785,7 +785,8 @@
|
|||||||
"auth": {
|
"auth": {
|
||||||
"logout": "Deconnexion reussie"
|
"logout": "Deconnexion reussie"
|
||||||
},
|
},
|
||||||
"title": "Succès"
|
"title": "Succès",
|
||||||
|
"deleted": "Suppression effectuée"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"roles": {
|
"roles": {
|
||||||
|
|||||||
@@ -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) ───────────────────────────────
|
// ── Erreurs de validation par champ (ERP-101) ───────────────────────────────
|
||||||
// Etat d'erreurs factorise avec l'ecran de creation (cf. useClientFormErrors) :
|
// Etat d'erreurs factorise avec l'ecran de creation (cf. useClientFormErrors) :
|
||||||
// un `useFormErrors` par groupe scalaire + un tableau d'erreurs par ligne pour
|
// 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 }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyContact,
|
makeEmpty: emptyContact,
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -877,6 +883,7 @@ function askRemoveAddress(index: number): void {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyAddress,
|
makeEmpty: emptyAddress,
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -977,6 +984,7 @@ function askRemoveRib(index: number): void {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyRib,
|
makeEmpty: emptyRib,
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -616,6 +616,11 @@ function showError(e: unknown): void {
|
|||||||
toast.error({ title: t('commercial.suppliers.toast.error'), message: apiErrorMessage(e) })
|
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) ───────────────────────────────
|
// ── Erreurs de validation par champ (ERP-101) ───────────────────────────────
|
||||||
const {
|
const {
|
||||||
mainErrors,
|
mainErrors,
|
||||||
@@ -699,6 +704,7 @@ function askRemoveContact(index: number): void {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyContact,
|
makeEmpty: emptyContact,
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,6 +773,7 @@ function askRemoveAddress(index: number): void {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyAddress,
|
makeEmpty: emptyAddress,
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,6 +873,7 @@ function askRemoveRib(index: number): void {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyRib,
|
makeEmpty: emptyRib,
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 ────────────────────────────────────────────
|
// ── Etat du prestataire cree ────────────────────────────────────────────
|
||||||
const providerId = ref<number | null>(null)
|
const providerId = ref<number | null>(null)
|
||||||
const mainLocked = ref(false)
|
const mainLocked = ref(false)
|
||||||
@@ -339,6 +344,7 @@ export function useProviderForm() {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyProviderContact,
|
makeEmpty: emptyProviderContact,
|
||||||
onError: notifyRemovalError,
|
onError: notifyRemovalError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,6 +423,7 @@ export function useProviderForm() {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyProviderAddress,
|
makeEmpty: emptyProviderAddress,
|
||||||
onError: notifyRemovalError,
|
onError: notifyRemovalError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,6 +525,7 @@ export function useProviderForm() {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyProviderRib,
|
makeEmpty: emptyProviderRib,
|
||||||
onError: notifyRemovalError,
|
onError: notifyRemovalError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 :
|
* 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
|
* 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 }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyCarrierContact,
|
makeEmpty: emptyCarrierContact,
|
||||||
onError: notifyRemovalError,
|
onError: notifyRemovalError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,6 +659,7 @@ export function useCarrierForm() {
|
|||||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||||
makeEmpty: emptyCarrierPrice,
|
makeEmpty: emptyCarrierPrice,
|
||||||
onError: notifyRemovalError,
|
onError: notifyRemovalError,
|
||||||
|
onSuccess: notifyRemovalSuccess,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ describe('removeCollectionRow', () => {
|
|||||||
const errors: Record<string, string>[] = [{}, {}]
|
const errors: Record<string, string>[] = [{}, {}]
|
||||||
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
|
const onSuccess = vi.fn()
|
||||||
|
|
||||||
const removed = await removeCollectionRow({
|
const removed = await removeCollectionRow({
|
||||||
rows, errors, index: 0,
|
rows, errors, index: 0,
|
||||||
endpoint: '/client_contacts',
|
endpoint: '/client_contacts',
|
||||||
deleteRow, makeEmpty, onError,
|
deleteRow, makeEmpty, onError, onSuccess,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(deleteRow).toHaveBeenCalledOnce()
|
expect(deleteRow).toHaveBeenCalledOnce()
|
||||||
@@ -35,6 +36,8 @@ describe('removeCollectionRow', () => {
|
|||||||
expect(rows).toEqual([{ id: 11, label: 'B' }])
|
expect(rows).toEqual([{ id: 11, label: 'B' }])
|
||||||
expect(errors).toHaveLength(1)
|
expect(errors).toHaveLength(1)
|
||||||
expect(onError).not.toHaveBeenCalled()
|
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 () => {
|
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<string, string>[] = [{}, {}]
|
const errors: Record<string, string>[] = [{}, {}]
|
||||||
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
|
const onSuccess = vi.fn()
|
||||||
|
|
||||||
const removed = await removeCollectionRow({
|
const removed = await removeCollectionRow({
|
||||||
rows, errors, index: 1,
|
rows, errors, index: 1,
|
||||||
endpoint: '/client_contacts',
|
endpoint: '/client_contacts',
|
||||||
deleteRow, makeEmpty, onError,
|
deleteRow, makeEmpty, onError, onSuccess,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(deleteRow).not.toHaveBeenCalled()
|
expect(deleteRow).not.toHaveBeenCalled()
|
||||||
expect(removed).toBe(true)
|
expect(removed).toBe(true)
|
||||||
expect(rows).toEqual([{ id: 10, label: 'A' }])
|
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 () => {
|
it('conserve le bloc et remonte l\'erreur si le DELETE serveur echoue (ex. 409 dernier RIB LCR)', async () => {
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export interface RemoveCollectionRowOptions<T extends DeletableRow> {
|
|||||||
makeEmpty: () => T
|
makeEmpty: () => T
|
||||||
/** Remontee d'erreur 409/422 mappee proprement (message back, pas de toast fourre-tout). */
|
/** Remontee d'erreur 409/422 mappee proprement (message back, pas de toast fourre-tout). */
|
||||||
onError: (error: unknown) => void
|
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<T extends DeletableRow> {
|
|||||||
export async function removeCollectionRow<T extends DeletableRow>(
|
export async function removeCollectionRow<T extends DeletableRow>(
|
||||||
options: RemoveCollectionRowOptions<T>,
|
options: RemoveCollectionRowOptions<T>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { rows, errors, index, endpoint, deleteRow, makeEmpty, onError } = options
|
const { rows, errors, index, endpoint, deleteRow, makeEmpty, onError, onSuccess } = options
|
||||||
const removed = rows[index]
|
const removed = rows[index]
|
||||||
|
let serverDeleted = false
|
||||||
|
|
||||||
// Bloc existant : suppression serveur d'abord, retrait local seulement si OK.
|
// Bloc existant : suppression serveur d'abord, retrait local seulement si OK.
|
||||||
if (removed?.id != null) {
|
if (removed?.id != null) {
|
||||||
@@ -67,6 +74,7 @@ export async function removeCollectionRow<T extends DeletableRow>(
|
|||||||
onError(error)
|
onError(error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
serverDeleted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.splice(index, 1)
|
rows.splice(index, 1)
|
||||||
@@ -75,5 +83,9 @@ export async function removeCollectionRow<T extends DeletableRow>(
|
|||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
rows.push(makeEmpty())
|
rows.push(makeEmpty())
|
||||||
}
|
}
|
||||||
|
// Toast de succes uniquement quand le serveur a confirme une vraie suppression.
|
||||||
|
if (serverDeleted) {
|
||||||
|
onSuccess?.()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user