refactor(front) : suites review ERP-101 — i18n libelles toast + factorisation useClientFormErrors
- 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)
This commit is contained in:
@@ -379,9 +379,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, type Ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useClient } from '~/modules/commercial/composables/useClient'
|
||||
import { useClientReferentials, type CategoryOption, type RefOption } from '~/modules/commercial/composables/useClientReferentials'
|
||||
import { useClientFormErrors } from '~/modules/commercial/composables/useClientFormErrors'
|
||||
import {
|
||||
canEditClient,
|
||||
categoryOptionsOf,
|
||||
@@ -424,7 +425,7 @@ import {
|
||||
type ContactFormDraft,
|
||||
type RibFormDraft,
|
||||
} from '~/modules/commercial/types/clientForm'
|
||||
import { extractApiErrorMessage, mapViolationsToRecord } from '~/shared/utils/api'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
|
||||
// Masques de saisie (la normalisation finale reste serveur).
|
||||
const SIREN_MASK = '#########'
|
||||
@@ -615,34 +616,20 @@ function showError(e: unknown, opts: { duplicateCompany?: boolean } = {}): void
|
||||
}
|
||||
|
||||
// ── Erreurs de validation par champ (ERP-101) ───────────────────────────────
|
||||
// Un `useFormErrors` par groupe scalaire (submit independant) + un tableau
|
||||
// d'erreurs par ligne pour chaque collection (aligne sur l'index visible).
|
||||
const mainErrors = useFormErrors()
|
||||
const informationErrors = useFormErrors()
|
||||
const accountingErrors = useFormErrors()
|
||||
const contactErrors = ref<Record<string, string>[]>([])
|
||||
const addressErrors = ref<Record<string, string>[]>([])
|
||||
const ribErrors = ref<Record<string, string>[]>([])
|
||||
|
||||
/**
|
||||
* Mappe l'erreur d'une ligne de collection sur le tableau d'erreurs cible (par
|
||||
* index). 422 exploitable → erreurs inline sous les champs de la ligne ; sinon
|
||||
* → toast de fallback. Renvoie true si mappee inline.
|
||||
*/
|
||||
function mapRowError(
|
||||
error: unknown,
|
||||
target: Ref<Record<string, string>[]>,
|
||||
index: number,
|
||||
): boolean {
|
||||
const response = (error as { response?: { status?: number, _data?: unknown } })?.response
|
||||
const mapped = response?.status === 422 ? mapViolationsToRecord(response._data) : {}
|
||||
if (Object.keys(mapped).length > 0) {
|
||||
target.value[index] = mapped
|
||||
return true
|
||||
}
|
||||
showError(error)
|
||||
return false
|
||||
}
|
||||
// Etat d'erreurs factorise avec l'ecran de creation (cf. useClientFormErrors) :
|
||||
// un `useFormErrors` par groupe scalaire + un tableau d'erreurs par ligne pour
|
||||
// chaque collection (aligne sur l'index visible). `mapRowError` mappe une 422
|
||||
// inline et retourne true ; il ne toaste pas, le fallback `showError` reste
|
||||
// local a l'edition (cf. catch des submits de collection).
|
||||
const {
|
||||
mainErrors,
|
||||
informationErrors,
|
||||
accountingErrors,
|
||||
contactErrors,
|
||||
addressErrors,
|
||||
ribErrors,
|
||||
mapRowError,
|
||||
} = useClientFormErrors()
|
||||
|
||||
// ── Bloc principal ───────────────────────────────────────────────────────────
|
||||
const isMainValid = computed(() => {
|
||||
@@ -775,7 +762,9 @@ async function submitContacts(): Promise<void> {
|
||||
}
|
||||
catch (error) {
|
||||
// 422 → erreurs inline sous les champs de CETTE ligne ; on stoppe.
|
||||
mapRowError(error, contactErrors, index)
|
||||
if (!mapRowError(error, contactErrors, index)) {
|
||||
showError(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -852,7 +841,9 @@ async function submitAddresses(): Promise<void> {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
mapRowError(error, addressErrors, index)
|
||||
if (!mapRowError(error, addressErrors, index)) {
|
||||
showError(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -949,7 +940,9 @@ async function submitAccounting(): Promise<void> {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
mapRowError(error, ribErrors, index)
|
||||
if (!mapRowError(error, ribErrors, index)) {
|
||||
showError(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,8 +376,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch, type Ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useClientReferentials, type RefOption } from '~/modules/commercial/composables/useClientReferentials'
|
||||
import { useClientFormErrors } from '~/modules/commercial/composables/useClientFormErrors'
|
||||
import {
|
||||
buildClientFormTabKeys,
|
||||
CLIENT_FORM_PLACEHOLDER_TABS,
|
||||
@@ -395,7 +396,7 @@ import {
|
||||
type ContactFormDraft,
|
||||
type RibFormDraft,
|
||||
} from '~/modules/commercial/types/clientForm'
|
||||
import { extractApiErrorMessage, mapViolationsToRecord } from '~/shared/utils/api'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
|
||||
// Masques de saisie (la normalisation finale reste serveur).
|
||||
const SIREN_MASK = '#########'
|
||||
@@ -428,36 +429,20 @@ function apiErrorMessage(error: unknown): string {
|
||||
}
|
||||
|
||||
// ── Erreurs de validation par champ (ERP-101) ───────────────────────────────
|
||||
// Un `useFormErrors` par groupe scalaire (submit independant) : les violations
|
||||
// 422 du serveur s'affichent sous le champ concerne (prop `:error`) au lieu d'un
|
||||
// simple toast. Les collections (contacts/adresses/RIB) portent une erreur PAR
|
||||
// LIGNE via des tableaux alignes sur l'index, peuples par `mapRowError`.
|
||||
const mainErrors = useFormErrors()
|
||||
const informationErrors = useFormErrors()
|
||||
const accountingErrors = useFormErrors()
|
||||
const contactErrors = ref<Record<string, string>[]>([])
|
||||
const addressErrors = ref<Record<string, string>[]>([])
|
||||
const ribErrors = ref<Record<string, string>[]>([])
|
||||
|
||||
/**
|
||||
* Mappe l'erreur d'une ligne de collection sur le tableau d'erreurs cible (par
|
||||
* index). 422 avec violations exploitables → erreurs inline sous les champs de
|
||||
* la ligne ; sinon → toast de fallback. Renvoie true si mappee inline.
|
||||
*/
|
||||
function mapRowError(
|
||||
error: unknown,
|
||||
target: Ref<Record<string, string>[]>,
|
||||
index: number,
|
||||
): boolean {
|
||||
const response = (error as { response?: { status?: number, _data?: unknown } })?.response
|
||||
const mapped = response?.status === 422 ? mapViolationsToRecord(response._data) : {}
|
||||
if (Object.keys(mapped).length > 0) {
|
||||
target.value[index] = mapped
|
||||
return true
|
||||
}
|
||||
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||
return false
|
||||
}
|
||||
// Etat d'erreurs factorise entre creation et edition (cf. useClientFormErrors) :
|
||||
// un `useFormErrors` par groupe scalaire (Principal / Information / Comptabilite)
|
||||
// + un tableau d'erreurs par ligne pour chaque collection (contacts/adresses/RIB).
|
||||
// `mapRowError` mappe une 422 inline et retourne true ; il ne toaste pas, le
|
||||
// fallback reste local a la creation (cf. catch des submits de collection).
|
||||
const {
|
||||
mainErrors,
|
||||
informationErrors,
|
||||
accountingErrors,
|
||||
contactErrors,
|
||||
addressErrors,
|
||||
ribErrors,
|
||||
mapRowError,
|
||||
} = useClientFormErrors()
|
||||
|
||||
useHead({ title: t('commercial.clients.form.title') })
|
||||
|
||||
@@ -724,7 +709,9 @@ async function submitContacts(): Promise<void> {
|
||||
catch (error) {
|
||||
// 422 → erreurs inline sous les champs de CETTE ligne ; on stoppe
|
||||
// a la premiere ligne en echec (les suivantes ne sont pas tentees).
|
||||
mapRowError(error, contactErrors, index)
|
||||
if (!mapRowError(error, contactErrors, index)) {
|
||||
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -830,7 +817,9 @@ async function submitAddresses(): Promise<void> {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
mapRowError(error, addressErrors, index)
|
||||
if (!mapRowError(error, addressErrors, index)) {
|
||||
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -941,7 +930,9 @@ async function submitAccounting(): Promise<void> {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
mapRowError(error, ribErrors, index)
|
||||
if (!mapRowError(error, ribErrors, index)) {
|
||||
toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) })
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user