feat : ERP-196 — refonte des blocs de formulaire (contact, adresse, compta) (#145)
Auto Tag Develop / tag (push) Successful in 11s
Auto Tag Develop / tag (push) Successful in 11s
## ERP-196 — Refonte des blocs de formulaire Refonte visuelle des blocs répétables des formulaires (clients, fournisseurs, prestataires, transporteurs), alignée sur les blocs « ticket de pesée » : à plat (sans box-shadow), titre de bloc en noir, séparation par filet noir 1px. ### ✅ Blocs Contact - Box-shadow / fond blanc / padding latéral retirés - En-tête `flex justify-between` : titre noir (« Contact 1 »…) à gauche, poubelle `button-class="p-0"` à droite - 4 colonnes, filet `border-b border-black` entre blocs (pas sous le dernier, prop `last`) - i18n `contact.title` ajouté pour transporteurs / prestataires - 9 pages câblées (new / edit / consultation des 4 répertoires) ### ✅ Blocs Adresse - Même traitement (à plat, titre noir, filet sauf dernier) - i18n `address.title` pour transporteurs / prestataires - Transporteur : adresse unique → titre « Adresse » (sans numéro) - 12 pages câblées ### ✅ Bloc Comptabilité - Bloc **infos** : titre « Informations » + filet bas (uniquement si des RIB suivent) - Blocs **RIB** : titre « RIB 1 / RIB 2… » + poubelle `p-0`, filet sauf le dernier - i18n `accounting.infoTitle` (3 modules) + `accounting.ribTitle` (fournisseurs / prestataires) - 9 pages câblées (clients / fournisseurs / prestataires) ### Vérifications - Vitest : 44/44 (specs contact + adresse) - Eslint : clean sur l'ensemble des composants et pages modifiés ### Commits - `feat : refonte des blocs contact (ERP-196)` - `feat : refonte des blocs adresse (ERP-196)` - `feat : refonte du bloc comptabilité (ERP-196)` Reviewed-on: #145 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #145.
This commit is contained in:
@@ -1,131 +1,140 @@
|
||||
<template>
|
||||
<div class="relative grid grid-cols-4 gap-x-[44px] gap-y-4 bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
|
||||
<!-- Suppression : modal de confirmation cote parent. -->
|
||||
<MalioButtonIcon
|
||||
v-if="removable && !readonly && !disabled"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.address.remove') }"
|
||||
@click="$emit('remove')"
|
||||
/>
|
||||
|
||||
<!-- Sites Starseed : multiselect a tags (>= 1 obligatoire, RG-3.05). -->
|
||||
<MalioSelectCheckbox
|
||||
v-if="!hideEmpty || isFilled(model.siteIris)"
|
||||
:model-value="model.siteIris"
|
||||
:options="siteOptions"
|
||||
:label="t('technique.providers.form.address.sites')"
|
||||
:display-tag="true"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.sites"
|
||||
@update:model-value="(v: (string | number)[]) => update('siteIris', v.map(String))"
|
||||
/>
|
||||
|
||||
<!-- Contacts rattaches (M2M, facultatif) : alimente par l'onglet Contact. -->
|
||||
<MalioSelectCheckbox
|
||||
v-if="!hideEmpty || isFilled(model.contactIris)"
|
||||
:model-value="model.contactIris"
|
||||
:options="contactOptions"
|
||||
:label="t('technique.providers.form.address.contacts')"
|
||||
:display-tag="true"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
@update:model-value="(v: (string | number)[]) => update('contactIris', v.map(String))"
|
||||
/>
|
||||
|
||||
<MalioSelect
|
||||
v-if="!hideEmpty || isFilled(model.country)"
|
||||
:model-value="model.country"
|
||||
:options="countryOptions"
|
||||
:label="t('technique.providers.form.address.country')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
@update:model-value="(v: string | number | null) => update('country', String(v ?? 'France'))"
|
||||
/>
|
||||
|
||||
<MalioInputText
|
||||
v-if="!hideEmpty || isFilled(model.postalCode)"
|
||||
:model-value="model.postalCode"
|
||||
:label="t('technique.providers.form.address.postalCode')"
|
||||
:mask="POSTAL_CODE_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.postalCode"
|
||||
@update:model-value="onPostalCodeChange"
|
||||
/>
|
||||
|
||||
<!-- Ville : MalioSelect alimente par le code postal (BAN). Saisie libre si BAN indispo. -->
|
||||
<MalioSelect
|
||||
v-if="!degraded && (!hideEmpty || isFilled(model.city))"
|
||||
:model-value="model.city"
|
||||
:options="cityOptions"
|
||||
:label="t('technique.providers.form.address.city')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
empty-option-label=""
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.city"
|
||||
@update:model-value="onCityChange"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-else-if="degraded && (!hideEmpty || isFilled(model.city))"
|
||||
:model-value="model.city"
|
||||
:label="t('technique.providers.form.address.city')"
|
||||
:mask="ADDRESS_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.city"
|
||||
@update:model-value="(v: string) => update('city', v)"
|
||||
/>
|
||||
|
||||
<!-- Adresse (BAN) sur 2 colonnes + Adresse complementaire. allow-create : le
|
||||
texte saisi est conserve si la BAN ne propose rien (saisie manuelle). -->
|
||||
<div v-if="!hideEmpty || isFilled(model.street)" class="col-span-2">
|
||||
<MalioInputAutocomplete
|
||||
v-if="!readonly && !disabled"
|
||||
:model-value="model.street"
|
||||
:options="addressOptions"
|
||||
:loading="addressLoading"
|
||||
:min-search-length="3"
|
||||
:label="t('technique.providers.form.address.street')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.street"
|
||||
:allow-create="true"
|
||||
:no-results-text="t('technique.providers.form.address.streetNotFound')"
|
||||
@update:model-value="(v: string | number | null) => update('street', v === null ? null : String(v))"
|
||||
@search="onAddressSearch"
|
||||
@select="onAddressSelect"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-else
|
||||
:model-value="model.street"
|
||||
:label="t('technique.providers.form.address.street')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.street"
|
||||
@update:model-value="(v: string) => update('street', v)"
|
||||
<!-- Bloc a plat (sans box-shadow) : un filet noir 1px le separe du suivant
|
||||
(pas de bordure sous le dernier bloc). -->
|
||||
<div class="pb-[20px]" :class="{ 'border-b border-black': !last }">
|
||||
<!-- En-tete : titre du bloc (noir) a gauche, poubelle de suppression a droite. -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ title }}</h2>
|
||||
<!-- Suppression : modal de confirmation cote parent. -->
|
||||
<MalioButtonIcon
|
||||
v-if="removable && !readonly && !disabled"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="p-0"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.address.remove') }"
|
||||
@click="$emit('remove')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="!hideEmpty || isFilled(model.streetComplement)" class="col-span-1">
|
||||
<!-- Grille 4 colonnes des champs de l'adresse. -->
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<!-- Sites Starseed : multiselect a tags (>= 1 obligatoire, RG-3.05). -->
|
||||
<MalioSelectCheckbox
|
||||
v-if="!hideEmpty || isFilled(model.siteIris)"
|
||||
:model-value="model.siteIris"
|
||||
:options="siteOptions"
|
||||
:label="t('technique.providers.form.address.sites')"
|
||||
:display-tag="true"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.sites"
|
||||
@update:model-value="(v: (string | number)[]) => update('siteIris', v.map(String))"
|
||||
/>
|
||||
|
||||
<!-- Contacts rattaches (M2M, facultatif) : alimente par l'onglet Contact. -->
|
||||
<MalioSelectCheckbox
|
||||
v-if="!hideEmpty || isFilled(model.contactIris)"
|
||||
:model-value="model.contactIris"
|
||||
:options="contactOptions"
|
||||
:label="t('technique.providers.form.address.contacts')"
|
||||
:display-tag="true"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
@update:model-value="(v: (string | number)[]) => update('contactIris', v.map(String))"
|
||||
/>
|
||||
|
||||
<MalioSelect
|
||||
v-if="!hideEmpty || isFilled(model.country)"
|
||||
:model-value="model.country"
|
||||
:options="countryOptions"
|
||||
:label="t('technique.providers.form.address.country')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
@update:model-value="(v: string | number | null) => update('country', String(v ?? 'France'))"
|
||||
/>
|
||||
|
||||
<MalioInputText
|
||||
:model-value="model.streetComplement"
|
||||
:label="t('technique.providers.form.address.streetComplement')"
|
||||
v-if="!hideEmpty || isFilled(model.postalCode)"
|
||||
:model-value="model.postalCode"
|
||||
:label="t('technique.providers.form.address.postalCode')"
|
||||
:mask="POSTAL_CODE_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.postalCode"
|
||||
@update:model-value="onPostalCodeChange"
|
||||
/>
|
||||
|
||||
<!-- Ville : MalioSelect alimente par le code postal (BAN). Saisie libre si BAN indispo. -->
|
||||
<MalioSelect
|
||||
v-if="!degraded && (!hideEmpty || isFilled(model.city))"
|
||||
:model-value="model.city"
|
||||
:options="cityOptions"
|
||||
:label="t('technique.providers.form.address.city')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
empty-option-label=""
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.city"
|
||||
@update:model-value="onCityChange"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-else-if="degraded && (!hideEmpty || isFilled(model.city))"
|
||||
:model-value="model.city"
|
||||
:label="t('technique.providers.form.address.city')"
|
||||
:mask="ADDRESS_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.streetComplement"
|
||||
@update:model-value="(v: string) => update('streetComplement', v)"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.city"
|
||||
@update:model-value="(v: string) => update('city', v)"
|
||||
/>
|
||||
|
||||
<!-- Adresse (BAN) sur 2 colonnes + Adresse complementaire. allow-create : le
|
||||
texte saisi est conserve si la BAN ne propose rien (saisie manuelle). -->
|
||||
<div v-if="!hideEmpty || isFilled(model.street)" class="col-span-2">
|
||||
<MalioInputAutocomplete
|
||||
v-if="!readonly && !disabled"
|
||||
:model-value="model.street"
|
||||
:options="addressOptions"
|
||||
:loading="addressLoading"
|
||||
:min-search-length="3"
|
||||
:label="t('technique.providers.form.address.street')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.street"
|
||||
:allow-create="true"
|
||||
:no-results-text="t('technique.providers.form.address.streetNotFound')"
|
||||
@update:model-value="(v: string | number | null) => update('street', v === null ? null : String(v))"
|
||||
@search="onAddressSearch"
|
||||
@select="onAddressSelect"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-else
|
||||
:model-value="model.street"
|
||||
:label="t('technique.providers.form.address.street')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:required="!readonly && !disabled"
|
||||
:error="errors?.street"
|
||||
@update:model-value="(v: string) => update('street', v)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="!hideEmpty || isFilled(model.streetComplement)" class="col-span-1">
|
||||
<MalioInputText
|
||||
:model-value="model.streetComplement"
|
||||
:label="t('technique.providers.form.address.streetComplement')"
|
||||
:mask="ADDRESS_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.streetComplement"
|
||||
@update:model-value="(v: string) => update('streetComplement', v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -143,6 +152,8 @@ const POSTAL_CODE_MASK = '#####'
|
||||
const props = defineProps<{
|
||||
/** Brouillon de l'adresse (v-model). */
|
||||
modelValue: ProviderAddressFormDraft
|
||||
/** Titre du bloc (ex: « Adresse 1 »). */
|
||||
title: string
|
||||
/** Sites Starseed disponibles. */
|
||||
siteOptions: RefOption[]
|
||||
/** Contacts deja saisis, rattachables a l'adresse. */
|
||||
@@ -150,6 +161,8 @@ const props = defineProps<{
|
||||
/** Pays disponibles (France par defaut). */
|
||||
countryOptions: RefOption[]
|
||||
removable?: boolean
|
||||
/** Dernier bloc de la liste : supprime le filet de separation bas. */
|
||||
last?: boolean
|
||||
readonly?: boolean
|
||||
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
|
||||
disabled?: boolean
|
||||
|
||||
@@ -1,84 +1,93 @@
|
||||
<template>
|
||||
<div class="relative grid grid-cols-4 gap-x-[44px] gap-y-4 bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
|
||||
<!-- Suppression : ouvre une modal de confirmation cote parent. Masquee si
|
||||
non supprimable (1er bloc) ou en lecture seule. -->
|
||||
<MalioButtonIcon
|
||||
v-if="removable && !readonly && !disabled"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.contact.remove') }"
|
||||
@click="$emit('remove')"
|
||||
/>
|
||||
|
||||
<MalioInputText
|
||||
v-if="!hideEmpty || isFilled(model.lastName)"
|
||||
:model-value="model.lastName"
|
||||
:label="t('technique.providers.form.contact.lastName')"
|
||||
:mask="PERSON_NAME_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.lastName"
|
||||
@update:model-value="(v: string) => update('lastName', v)"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-if="!hideEmpty || isFilled(model.firstName)"
|
||||
:model-value="model.firstName"
|
||||
:label="t('technique.providers.form.contact.firstName')"
|
||||
:mask="PERSON_NAME_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.firstName"
|
||||
@update:model-value="(v: string) => update('firstName', v)"
|
||||
/>
|
||||
<!-- Fonction sur 2 colonnes : on wrappe car MalioInputText
|
||||
(inheritAttrs:false) renvoie `class` sur l'input interne, pas sur la
|
||||
cellule de grille. Le wrapper porte le col-span-2, le champ le remplit. -->
|
||||
<div v-if="!hideEmpty || isFilled(model.jobTitle)" class="col-span-2">
|
||||
<MalioInputText
|
||||
:model-value="model.jobTitle"
|
||||
:label="t('technique.providers.form.contact.jobTitle')"
|
||||
:mask="FREE_TEXT_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.jobTitle"
|
||||
@update:model-value="(v: string) => update('jobTitle', v)"
|
||||
<!-- Bloc a plat (sans box-shadow) : un filet noir 1px le separe du suivant
|
||||
(pas de bordure sous le dernier bloc). -->
|
||||
<div class="pb-[20px]" :class="{ 'border-b border-black': !last }">
|
||||
<!-- En-tete : titre du bloc (noir) a gauche, poubelle de suppression a droite. -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ title }}</h2>
|
||||
<!-- Suppression : ouvre une modal de confirmation cote parent. Masquee si
|
||||
non supprimable (1er bloc) ou en lecture seule. -->
|
||||
<MalioButtonIcon
|
||||
v-if="removable && !readonly && !disabled"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="p-0"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.contact.remove') }"
|
||||
@click="$emit('remove')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Grille 4 colonnes des champs du contact. -->
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-if="!hideEmpty || isFilled(model.lastName)"
|
||||
:model-value="model.lastName"
|
||||
:label="t('technique.providers.form.contact.lastName')"
|
||||
:mask="PERSON_NAME_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.lastName"
|
||||
@update:model-value="(v: string) => update('lastName', v)"
|
||||
/>
|
||||
<MalioInputText
|
||||
v-if="!hideEmpty || isFilled(model.firstName)"
|
||||
:model-value="model.firstName"
|
||||
:label="t('technique.providers.form.contact.firstName')"
|
||||
:mask="PERSON_NAME_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.firstName"
|
||||
@update:model-value="(v: string) => update('firstName', v)"
|
||||
/>
|
||||
<!-- Fonction sur 2 colonnes : on wrappe car MalioInputText
|
||||
(inheritAttrs:false) renvoie `class` sur l'input interne, pas sur la
|
||||
cellule de grille. Le wrapper porte le col-span-2, le champ le remplit. -->
|
||||
<div v-if="!hideEmpty || isFilled(model.jobTitle)" class="col-span-2">
|
||||
<MalioInputText
|
||||
:model-value="model.jobTitle"
|
||||
:label="t('technique.providers.form.contact.jobTitle')"
|
||||
:mask="FREE_TEXT_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.jobTitle"
|
||||
@update:model-value="(v: string) => update('jobTitle', v)"
|
||||
/>
|
||||
</div>
|
||||
<MalioInputEmail
|
||||
v-if="!hideEmpty || isFilled(model.email)"
|
||||
:model-value="model.email"
|
||||
:label="t('technique.providers.form.contact.email')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:lowercase="true"
|
||||
:error="errors?.email"
|
||||
@update:model-value="(v: string) => update('email', v)"
|
||||
/>
|
||||
<MalioInputPhone
|
||||
v-if="!hideEmpty || isFilled(model.phonePrimary)"
|
||||
:model-value="model.phonePrimary"
|
||||
:label="t('technique.providers.form.contact.phonePrimary')"
|
||||
:mask="PHONE_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.phonePrimary"
|
||||
:addable="!model.hasSecondaryPhone && !readonly"
|
||||
:add-button-label="t('technique.providers.form.contact.addPhone')"
|
||||
@update:model-value="(v: string) => update('phonePrimary', v)"
|
||||
@add="revealSecondaryPhone"
|
||||
/>
|
||||
<!-- 2e numero : revele a la demande (max 2 telephones par contact). -->
|
||||
<MalioInputPhone
|
||||
v-if="model.hasSecondaryPhone && (!hideEmpty || isFilled(model.phoneSecondary))"
|
||||
:model-value="model.phoneSecondary"
|
||||
:label="t('technique.providers.form.contact.phoneSecondary')"
|
||||
:mask="PHONE_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.phoneSecondary"
|
||||
@update:model-value="(v: string) => update('phoneSecondary', v)"
|
||||
/>
|
||||
</div>
|
||||
<MalioInputEmail
|
||||
v-if="!hideEmpty || isFilled(model.email)"
|
||||
:model-value="model.email"
|
||||
:label="t('technique.providers.form.contact.email')"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:lowercase="true"
|
||||
:error="errors?.email"
|
||||
@update:model-value="(v: string) => update('email', v)"
|
||||
/>
|
||||
<MalioInputPhone
|
||||
v-if="!hideEmpty || isFilled(model.phonePrimary)"
|
||||
:model-value="model.phonePrimary"
|
||||
:label="t('technique.providers.form.contact.phonePrimary')"
|
||||
:mask="PHONE_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.phonePrimary"
|
||||
:addable="!model.hasSecondaryPhone && !readonly"
|
||||
:add-button-label="t('technique.providers.form.contact.addPhone')"
|
||||
@update:model-value="(v: string) => update('phonePrimary', v)"
|
||||
@add="revealSecondaryPhone"
|
||||
/>
|
||||
<!-- 2e numero : revele a la demande (max 2 telephones par contact). -->
|
||||
<MalioInputPhone
|
||||
v-if="model.hasSecondaryPhone && (!hideEmpty || isFilled(model.phoneSecondary))"
|
||||
:model-value="model.phoneSecondary"
|
||||
:label="t('technique.providers.form.contact.phoneSecondary')"
|
||||
:mask="PHONE_MASK"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
:error="errors?.phoneSecondary"
|
||||
@update:model-value="(v: string) => update('phoneSecondary', v)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -93,8 +102,12 @@ const PHONE_MASK = '## ## ## ## ##'
|
||||
const props = defineProps<{
|
||||
/** Brouillon du contact (v-model). */
|
||||
modelValue: ProviderContactFormDraft
|
||||
/** Titre du bloc (ex: « Contact 1 »). */
|
||||
title: string
|
||||
/** Affiche l'icone de suppression (1er bloc non supprimable). */
|
||||
removable?: boolean
|
||||
/** Dernier bloc de la liste : supprime le filet de separation bas. */
|
||||
last?: boolean
|
||||
/** Bloc en lecture seule (onglet valide). */
|
||||
readonly?: boolean
|
||||
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
|
||||
|
||||
@@ -46,6 +46,7 @@ function mountBlock(overrides: Record<string, unknown> = {}, errors?: Record<str
|
||||
return mount(ProviderAddressBlock, {
|
||||
props: {
|
||||
modelValue: { ...emptyProviderAddress(), ...overrides },
|
||||
title: 'Adresse 1',
|
||||
siteOptions: [],
|
||||
contactOptions: [],
|
||||
countryOptions: [],
|
||||
|
||||
@@ -29,6 +29,7 @@ function mountBlock(errors?: Record<string, string>) {
|
||||
return mount(ProviderContactBlock, {
|
||||
props: {
|
||||
modelValue: emptyProviderContact(),
|
||||
title: 'Contact 1',
|
||||
...(errors ? { errors } : {}),
|
||||
},
|
||||
global: {
|
||||
|
||||
@@ -72,7 +72,9 @@
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="index"
|
||||
:model-value="contact"
|
||||
:title="t('technique.providers.form.contact.title', { n: index + 1 })"
|
||||
:removable="isRowRemovable(contacts, index)"
|
||||
:last="index === contacts.length - 1"
|
||||
:disabled="businessReadonly"
|
||||
:errors="contactErrors[index]"
|
||||
@update:model-value="(v) => contacts[index] = v"
|
||||
@@ -104,6 +106,8 @@
|
||||
v-for="(address, index) in addresses"
|
||||
:key="index"
|
||||
:model-value="address"
|
||||
:title="t('technique.providers.form.address.title', { n: index + 1 })"
|
||||
:last="index === addresses.length - 1"
|
||||
:site-options="referentials.sites.value"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptions"
|
||||
@@ -136,8 +140,10 @@
|
||||
<!-- Onglet Comptabilite (present si accounting.view ; editable si manage). -->
|
||||
<template v-if="canAccountingView" #accounting>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<!-- Bloc infos comptables : titre + filet bas (filet uniquement s'il y a des RIB en dessous). -->
|
||||
<div class="pb-[20px]" :class="{ 'border-b border-black': visibleRibs.length > 0 }">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ t('technique.providers.form.accounting.infoTitle') }}</h2>
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-model="accounting.siren"
|
||||
:label="t('technique.providers.form.accounting.siren')"
|
||||
@@ -206,21 +212,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blocs RIB — affiches uniquement si type de reglement = LCR (RG-3.08). -->
|
||||
<!-- Blocs RIB — affiches uniquement si type de reglement = LCR (RG-3.08).
|
||||
Titre « RIB N » + poubelle, filet de separation sauf sous le dernier. -->
|
||||
<div
|
||||
v-for="(rib, index) in visibleRibs"
|
||||
:key="index"
|
||||
class="relative bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
class="pb-[20px]"
|
||||
:class="{ 'border-b border-black': index !== visibleRibs.length - 1 }"
|
||||
>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.accounting.removeRib') }"
|
||||
@click="askRemoveRib(index)"
|
||||
/>
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<!-- En-tete : titre du bloc (noir) a gauche, poubelle a droite. -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ t('technique.providers.form.accounting.ribTitle', { n: index + 1 }) }}</h2>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="p-0"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.accounting.removeRib') }"
|
||||
@click="askRemoveRib(index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-model="rib.label"
|
||||
:label="t('technique.providers.form.accounting.ribLabel')"
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="index"
|
||||
:model-value="contact"
|
||||
:title="t('technique.providers.form.contact.title', { n: index + 1 })"
|
||||
:last="index === contacts.length - 1"
|
||||
disabled
|
||||
hide-empty
|
||||
/>
|
||||
@@ -94,6 +96,8 @@
|
||||
v-for="(view, index) in addressViews"
|
||||
:key="index"
|
||||
:model-value="view.draft"
|
||||
:title="t('technique.providers.form.address.title', { n: index + 1 })"
|
||||
:last="index === addressViews.length - 1"
|
||||
:site-options="view.siteOptions"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptionsFor(view.draft.country)"
|
||||
@@ -108,8 +112,10 @@
|
||||
<!-- Onglet Comptabilite (present uniquement si accounting.view). -->
|
||||
<template v-if="canAccountingView" #accounting>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<!-- Bloc infos comptables : titre + filet bas (filet uniquement s'il y a des RIB en dessous). -->
|
||||
<div class="pb-[20px]" :class="{ 'border-b border-black': visibleRibs.length > 0 }">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ t('technique.providers.form.accounting.infoTitle') }}</h2>
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText v-if="isFilled(accounting.siren)" :model-value="accounting.siren" :label="t('technique.providers.form.accounting.siren')" disabled />
|
||||
<MalioInputText v-if="isFilled(accounting.accountNumber)" :model-value="accounting.accountNumber" :label="t('technique.providers.form.accounting.accountNumber')" disabled />
|
||||
<MalioSelect v-if="isFilled(accounting.tvaModeIri)" :model-value="accounting.tvaModeIri" :options="tvaModeOptions" :label="t('technique.providers.form.accounting.tvaMode')" disabled empty-option-label="" />
|
||||
@@ -120,13 +126,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blocs RIB (uniquement si type de reglement = LCR). -->
|
||||
<!-- Blocs RIB (uniquement si type de reglement = LCR).
|
||||
Titre « RIB N », filet de separation sauf sous le dernier. -->
|
||||
<div
|
||||
v-for="(rib, index) in visibleRibs"
|
||||
:key="index"
|
||||
class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
class="pb-[20px]"
|
||||
:class="{ 'border-b border-black': index !== visibleRibs.length - 1 }"
|
||||
>
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ t('technique.providers.form.accounting.ribTitle', { n: index + 1 }) }}</h2>
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText v-if="isFilled(rib.label)" :model-value="rib.label" :label="t('technique.providers.form.accounting.ribLabel')" disabled />
|
||||
<MalioInputText v-if="isFilled(rib.bic)" :model-value="rib.bic" :label="t('technique.providers.form.accounting.ribBic')" disabled />
|
||||
<MalioInputText v-if="isFilled(rib.iban)" :model-value="rib.iban" :label="t('technique.providers.form.accounting.ribIban')" disabled />
|
||||
|
||||
@@ -73,7 +73,9 @@
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="index"
|
||||
:model-value="contact"
|
||||
:title="t('technique.providers.form.contact.title', { n: index + 1 })"
|
||||
:removable="isRowRemovable(contacts, index)"
|
||||
:last="index === contacts.length - 1"
|
||||
:disabled="isValidated('contact')"
|
||||
:errors="contactErrors[index]"
|
||||
@update:model-value="(v) => contacts[index] = v"
|
||||
@@ -108,6 +110,8 @@
|
||||
v-for="(address, index) in addresses"
|
||||
:key="index"
|
||||
:model-value="address"
|
||||
:title="t('technique.providers.form.address.title', { n: index + 1 })"
|
||||
:last="index === addresses.length - 1"
|
||||
:site-options="referentials.sites.value"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptions"
|
||||
@@ -139,8 +143,10 @@
|
||||
<!-- Onglet Comptabilite (present uniquement si accounting.view ; editable si manage). -->
|
||||
<template v-if="canAccountingView" #accounting>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<!-- Bloc infos comptables : titre + filet bas (filet uniquement s'il y a des RIB en dessous). -->
|
||||
<div class="pb-[20px]" :class="{ 'border-b border-black': visibleRibs.length > 0 }">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ t('technique.providers.form.accounting.infoTitle') }}</h2>
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-model="accounting.siren"
|
||||
:label="t('technique.providers.form.accounting.siren')"
|
||||
@@ -210,21 +216,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blocs RIB — affiches uniquement si type de reglement = LCR (RG-3.08). -->
|
||||
<!-- Blocs RIB — affiches uniquement si type de reglement = LCR (RG-3.08).
|
||||
Titre « RIB N » + poubelle, filet de separation sauf sous le dernier. -->
|
||||
<div
|
||||
v-for="(rib, index) in visibleRibs"
|
||||
:key="index"
|
||||
class="relative bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
class="pb-[20px]"
|
||||
:class="{ 'border-b border-black': index !== visibleRibs.length - 1 }"
|
||||
>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.accounting.removeRib') }"
|
||||
@click="askRemoveRib(index)"
|
||||
/>
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<!-- En-tete : titre du bloc (noir) a gauche, poubelle a droite. -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-[20px] font-semibold text-black">{{ t('technique.providers.form.accounting.ribTitle', { n: index + 1 }) }}</h2>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="p-0"
|
||||
v-bind="{ ariaLabel: t('technique.providers.form.accounting.removeRib') }"
|
||||
@click="askRemoveRib(index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
|
||||
<MalioInputText
|
||||
v-model="rib.label"
|
||||
:label="t('technique.providers.form.accounting.ribLabel')"
|
||||
|
||||
Reference in New Issue
Block a user