97fe0b39de
- libelles de toast generiques passes en i18n (errors.title/generic/unknown, success.title) dans useFormErrors, useApi et useCategoryForm - nouveau composable useClientFormErrors : factorise l'etat d'erreurs (3 useFormErrors scalaires + 3 tableaux par ligne + mapRowError) partage entre clients/new.vue et [id]/edit.vue - mapRowError retourne un booleen et ne toaste plus : chaque page garde son fallback (toast generique en creation, showError en edition)
114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
/**
|
|
* Composable d'erreurs de formulaire — convention de mapping erreur→champ pour
|
|
* tous les forms du projet (ERP-101).
|
|
*
|
|
* Le back renvoie TOUTES les violations d'une 422 d'un coup (un `propertyPath`
|
|
* + `message` par champ fautif). Ce composable centralise leur affichage
|
|
* inline : il tient un `Record<propertyPath, message>` reactif que le template
|
|
* branche directement sur la prop `:error` des composants `Malio*` (le nom du
|
|
* champ cote front = le `propertyPath` cote back, donc aucun mapping manuel).
|
|
*
|
|
* Chaque appel cree son propre etat (refs internes a la fonction) — un form =
|
|
* une instance, pas de singleton partage.
|
|
*
|
|
* Convention d'usage : les appels API qui veulent un retour inline doivent
|
|
* passer `{ toast: false }` a `useApi` (sinon le toast natif masque le mapping
|
|
* fin), puis router l'erreur via `handleApiError`. Pour les collections (1
|
|
* appel par ligne), utiliser directement `mapViolationsToRecord` par ligne.
|
|
*/
|
|
import { computed, reactive } from 'vue'
|
|
import { extractApiErrorMessage, mapViolationsToRecord } from '~/shared/utils/api'
|
|
|
|
/**
|
|
* Erreur HTTP capturee par ofetch. On n'expose que les champs lus ici (status
|
|
* + payload) pour eviter de typer toute la lib.
|
|
*/
|
|
interface ApiFetchError {
|
|
response?: {
|
|
status?: number
|
|
_data?: unknown
|
|
}
|
|
}
|
|
|
|
/** Options de `handleApiError`. */
|
|
interface HandleApiErrorOptions {
|
|
/** Message de toast si l'erreur n'est pas une 422 exploitable. */
|
|
fallbackMessage?: string
|
|
}
|
|
|
|
export function useFormErrors() {
|
|
const toast = useToast()
|
|
const { t } = useI18n()
|
|
|
|
// Etat d'erreurs indexe par propertyPath. Reactif : muter une cle suffit a
|
|
// rafraichir la prop `:error` du champ correspondant.
|
|
const errors = reactive<Record<string, string>>({})
|
|
|
|
const hasErrors = computed(() => Object.keys(errors).length > 0)
|
|
|
|
/** Pose une erreur sur un champ. */
|
|
function setError(field: string, message: string): void {
|
|
errors[field] = message
|
|
}
|
|
|
|
/** Retire l'erreur d'un champ (no-op si absente). */
|
|
function clearError(field: string): void {
|
|
delete errors[field]
|
|
}
|
|
|
|
/** Vide toutes les erreurs (a appeler en debut de submit). */
|
|
function clearErrors(): void {
|
|
for (const key of Object.keys(errors)) {
|
|
delete errors[key]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mappe les violations 422 d'un payload sur les champs. Retourne true des
|
|
* qu'au moins une violation a ete posee, false sinon (payload sans
|
|
* violation exploitable).
|
|
*/
|
|
function setServerErrors(data: unknown): boolean {
|
|
const mapped = mapViolationsToRecord(data)
|
|
const keys = Object.keys(mapped)
|
|
if (keys.length === 0) return false
|
|
for (const key of keys) {
|
|
errors[key] = mapped[key]
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Route une erreur API : 422 avec violations exploitables → mapping inline
|
|
* (pas de toast, l'erreur s'affiche sous le champ) ; sinon → toast de
|
|
* fallback (message serveur extrait, ou `fallbackMessage`).
|
|
*
|
|
* Retourne true si l'erreur a ete mappee inline, false si fallback toast.
|
|
*/
|
|
function handleApiError(e: unknown, opts: HandleApiErrorOptions = {}): boolean {
|
|
const status = (e as ApiFetchError)?.response?.status
|
|
const data = (e as ApiFetchError)?.response?._data
|
|
|
|
if (status === 422 && setServerErrors(data)) {
|
|
return true
|
|
}
|
|
|
|
const message
|
|
= extractApiErrorMessage(data)
|
|
|| opts.fallbackMessage
|
|
|| t('errors.generic')
|
|
toast.error({ title: t('errors.title'), message })
|
|
return false
|
|
}
|
|
|
|
return {
|
|
errors,
|
|
hasErrors,
|
|
setError,
|
|
clearError,
|
|
clearErrors,
|
|
setServerErrors,
|
|
handleApiError,
|
|
}
|
|
}
|