feat : refonte des blocs adresse — 4 colonnes, titre noir, separateurs (ERP-196)

This commit is contained in:
2026-06-24 17:47:11 +02:00
parent 2eee97b2c6
commit 7547c2ca83
19 changed files with 639 additions and 569 deletions
@@ -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