/** Ligne de collection supprimable (contact / adresse / RIB). */ export interface DeletableRow { id?: number | null } /** * Indique si le bloc d'index `index` peut afficher sa poubelle (ERP-172). * * Regle metier : on ne peut supprimer un bloc QUE s'il reste au moins un AUTRE * bloc deja enregistre (`id` non null, donc persiste en base). Consequences : * - tant que rien n'est enregistre -> aucune poubelle (pas de suppression d'un * simple brouillon saisi mais pas valide) ; * - on peut jeter un brouillon non enregistre s'il reste un bloc enregistre ; * - on ne peut jamais supprimer son dernier bloc enregistre. */ export function isRowRemovable(rows: T[], index: number): boolean { return rows.some((row, i) => i !== index && row.id != null) } /** Options de {@link removeCollectionRow}. */ export interface RemoveCollectionRowOptions { /** Tableau reactif des brouillons (passer le `.value` de la ref). */ rows: T[] /** Tableau reactif des erreurs par ligne, aligne sur l'index (passer le `.value`). */ errors: Record[] /** Index de la ligne a retirer. */ index: number /** Endpoint de la sous-ressource SANS id (ex: '/client_contacts'). */ endpoint: string /** Suppression serveur : DOIT rejeter en cas d'echec (ex: url => api.delete(url, {}, { toast: false })). */ deleteRow: (url: string) => Promise /** Fabrique d'un bloc vide pour garder au moins un bloc visible apres retrait. */ makeEmpty: () => T /** Remontee d'erreur 409/422 mappee proprement (message back, pas de toast fourre-tout). */ onError: (error: unknown) => void } /** * Retire une ligne de collection (contact / adresse / RIB) sur les ecrans de * MODIFICATION, avec DELETE immediat de la sous-ressource (ERP-172). Comportement * aligne sur les 3 modules (Client / Fournisseur / Prestataire) : * * - Bloc jamais persiste (`id` null) : simple retrait local, aucun appel reseau. * - Bloc existant (`id` non null) : DELETE `/endpoint/{id}` AVANT le retrait du * tableau. On ne retire le bloc QUE si le serveur a confirme — sinon le bloc * reste affiche et l'erreur est remontee via `onError` (ex. dernier RIB d'une * LCR -> 409 back, RG-x.08). * * Etat purement local : `rows`/`errors` sont les `.value` des refs (proxies * reactifs), le `splice` declenche donc la reactivite. * * @returns `true` si la ligne a ete retiree (suppression confirmee ou bloc local), * `false` si la suppression serveur a echoue (bloc conserve). */ export async function removeCollectionRow( options: RemoveCollectionRowOptions, ): Promise { const { rows, errors, index, endpoint, deleteRow, makeEmpty, onError } = options const removed = rows[index] // Bloc existant : suppression serveur d'abord, retrait local seulement si OK. if (removed?.id != null) { try { await deleteRow(`${endpoint}/${removed.id}`) } catch (error) { onError(error) return false } } rows.splice(index, 1) errors.splice(index, 1) // Garde au moins un bloc visible (cf. amorce a l'hydratation). if (rows.length === 0) { rows.push(makeEmpty()) } return true }