style(front) : ajustements layout blocs contact/adresse (ERP-63)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 1m40s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m2s

- Bloc Contact aligne sur le bloc Adresse : carte ombree en grille, bouton
  supprimer en absolute haut-droite (mdi:delete-outline).
- Bloc Adresse : champs Adresse et Adresse complementaire sur 2 colonnes
  (col-span-2 via wrapper, car les inputs Malio renvoient class sur l'input
  interne) ; cellule vide pour renvoyer Categorie a la ligne suivante.
- Grilles alignees (grid-cols-4, gaps homogenes).
This commit is contained in:
2026-06-03 10:47:15 +02:00
parent b3cc7e4ced
commit bc2ee7aac0
3 changed files with 95 additions and 88 deletions
@@ -1,5 +1,5 @@
<template> <template>
<div class="relative grid grid-cols-3 gap-x-[80px] gap-y-5 bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"> <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)]">
<!-- ariaLabel via v-bind objet (prop camelCase ; aria-* serait un attribut HTML). --> <!-- ariaLabel via v-bind objet (prop camelCase ; aria-* serait un attribut HTML). -->
<MalioButtonIcon <MalioButtonIcon
v-if="removable && !readonly" v-if="removable && !readonly"
@@ -35,6 +35,10 @@
@update:model-value="(v: boolean) => toggleFlag('isBilling', v)" @update:model-value="(v: boolean) => toggleFlag('isBilling', v)"
/> />
<!-- Cellule vide : laisse un trou en position 4 (ligne 1) pour que
Categorie reparte au debut de la ligne suivante. -->
<div aria-hidden="true" />
<MalioSelectCheckbox <MalioSelectCheckbox
:model-value="model.categoryIris" :model-value="model.categoryIris"
:options="categoryOptions" :options="categoryOptions"
@@ -79,33 +83,41 @@
@update:model-value="(v: string) => update('city', v)" @update:model-value="(v: string) => update('city', v)"
/> />
<!-- Adresse : saisie assistee (BAN) ou libre en mode degrade. --> <!-- Adresse + Adresse complementaire sur 2 colonnes : on wrappe car
<MalioInputAutocomplete MalioInputText/Autocomplete (inheritAttrs:false) renvoient `class`
v-if="!degraded" sur l'input interne, pas sur la cellule de grille. Le wrapper porte
:model-value="model.street" le col-span-2, le champ le remplit (w-full). -->
:options="addressOptions" <div class="col-span-2">
:loading="addressLoading" <!-- Adresse : saisie assistee (BAN) ou libre en mode degrade. -->
:min-search-length="3" <MalioInputAutocomplete
:label="t('commercial.clients.form.address.street')" v-if="!degraded"
:readonly="readonly" :model-value="model.street"
@update:model-value="(v: string | number | null) => update('street', v === null ? null : String(v))" :options="addressOptions"
@search="onAddressSearch" :loading="addressLoading"
@select="onAddressSelect" :min-search-length="3"
/> :label="t('commercial.clients.form.address.street')"
<MalioInputText :readonly="readonly"
v-else @update:model-value="(v: string | number | null) => update('street', v === null ? null : String(v))"
:model-value="model.street" @search="onAddressSearch"
:label="t('commercial.clients.form.address.street')" @select="onAddressSelect"
:readonly="readonly" />
@update:model-value="(v: string) => update('street', v)" <MalioInputText
/> v-else
:model-value="model.street"
:label="t('commercial.clients.form.address.street')"
:readonly="readonly"
@update:model-value="(v: string) => update('street', v)"
/>
</div>
<MalioInputText <div class="col-span-2">
:model-value="model.streetComplement" <MalioInputText
:label="t('commercial.clients.form.address.streetComplement')" :model-value="model.streetComplement"
:readonly="readonly" :label="t('commercial.clients.form.address.streetComplement')"
@update:model-value="(v: string) => update('streetComplement', v)" :readonly="readonly"
/> @update:model-value="(v: string) => update('streetComplement', v)"
/>
</div>
<!-- Sites Starseed : cases a cocher inline (>= 1 obligatoire, RG-1.10). --> <!-- Sites Starseed : cases a cocher inline (>= 1 obligatoire, RG-1.10). -->
<div class="flex justify-between"> <div class="flex justify-between">
@@ -1,64 +1,59 @@
<template> <template>
<div class="rounded-md border border-neutral-200 bg-white p-6"> <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)]">
<div class="mb-4 flex items-center justify-between"> <!-- Suppression : ouvre une modal de confirmation cote parent. Masquee si
<h3 class="text-lg font-bold">{{ title }}</h3> non supprimable (1er bloc obligatoire RG-1.14) ou en lecture seule.
<!-- Suppression : ouvre une modal de confirmation cote parent. Masquee ariaLabel via v-bind objet (prop camelCase ; aria-* serait un attribut HTML). -->
si non supprimable (1er bloc obligatoire RG-1.14) ou en lecture seule. --> <MalioButtonIcon
<!-- ariaLabel passe via v-bind objet : la prop du composant est v-if="removable && !readonly"
camelCase (aria-* serait traite en attribut HTML, jamais en prop). --> icon="mdi:delete-outline"
<MalioButtonIcon variant="ghost"
v-if="removable && !readonly" button-class="absolute top-3 right-3"
icon="mdi:trash-can-outline" v-bind="{ ariaLabel: t('commercial.clients.form.contact.remove') }"
variant="ghost" @click="$emit('remove')"
v-bind="{ ariaLabel: t('commercial.clients.form.contact.remove') }" />
@click="$emit('remove')"
/>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2"> <MalioInputText
<MalioInputText :model-value="model.lastName"
:model-value="model.lastName" :label="t('commercial.clients.form.contact.lastName')"
:label="t('commercial.clients.form.contact.lastName')" :readonly="readonly"
:readonly="readonly" @update:model-value="(v: string) => update('lastName', v)"
@update:model-value="(v: string) => update('lastName', v)" />
/> <MalioInputText
<MalioInputText :model-value="model.firstName"
:model-value="model.firstName" :label="t('commercial.clients.form.contact.firstName')"
:label="t('commercial.clients.form.contact.firstName')" :readonly="readonly"
:readonly="readonly" @update:model-value="(v: string) => update('firstName', v)"
@update:model-value="(v: string) => update('firstName', v)" />
/> <MalioInputText
<MalioInputText :model-value="model.jobTitle"
:model-value="model.jobTitle" :label="t('commercial.clients.form.contact.jobTitle')"
:label="t('commercial.clients.form.contact.jobTitle')" :readonly="readonly"
:readonly="readonly" @update:model-value="(v: string) => update('jobTitle', v)"
@update:model-value="(v: string) => update('jobTitle', v)" />
/> <MalioInputEmail
<MalioInputEmail :model-value="model.email"
:model-value="model.email" :label="t('commercial.clients.form.contact.email')"
:label="t('commercial.clients.form.contact.email')" :readonly="readonly"
:readonly="readonly" @update:model-value="(v: string) => update('email', v)"
@update:model-value="(v: string) => update('email', v)" />
/> <MalioInputPhone
<MalioInputPhone :model-value="model.phonePrimary"
:model-value="model.phonePrimary" :label="t('commercial.clients.form.contact.phonePrimary')"
:label="t('commercial.clients.form.contact.phonePrimary')" :mask="PHONE_MASK"
:mask="PHONE_MASK" :readonly="readonly"
:readonly="readonly" :addable="!model.hasSecondaryPhone && !readonly"
:addable="!model.hasSecondaryPhone && !readonly" :add-button-label="t('commercial.clients.form.contact.addPhone')"
:add-button-label="t('commercial.clients.form.contact.addPhone')" @update:model-value="(v: string) => update('phonePrimary', v)"
@update:model-value="(v: string) => update('phonePrimary', v)" @add="revealSecondaryPhone"
@add="revealSecondaryPhone" />
/> <MalioInputPhone
<MalioInputPhone v-if="model.hasSecondaryPhone"
v-if="model.hasSecondaryPhone" :model-value="model.phoneSecondary"
:model-value="model.phoneSecondary" :label="t('commercial.clients.form.contact.phoneSecondary')"
:label="t('commercial.clients.form.contact.phoneSecondary')" :mask="PHONE_MASK"
:mask="PHONE_MASK" :readonly="readonly"
:readonly="readonly" @update:model-value="(v: string) => update('phoneSecondary', v)"
@update:model-value="(v: string) => update('phoneSecondary', v)" />
/>
</div>
</div> </div>
</template> </template>
@@ -16,7 +16,7 @@
Sans validation de ce bloc, les onglets restent inaccessibles. Au Sans validation de ce bloc, les onglets restent inaccessibles. Au
succes du POST, les champs passent en lecture seule et on bascule succes du POST, les champs passent en lecture seule et on bascule
automatiquement sur l'onglet Information. --> automatiquement sur l'onglet Information. -->
<div class="mt-[48px] grid grid-cols-3 gap-x-[80px] gap-y-5"> <div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText <MalioInputText
v-model="main.companyName" v-model="main.companyName"
:label="t('commercial.clients.form.main.companyName')" :label="t('commercial.clients.form.main.companyName')"
@@ -105,7 +105,7 @@
<MalioTabList v-model="activeTab" :tabs="tabs" class="mt-[60px]"> <MalioTabList v-model="activeTab" :tabs="tabs" class="mt-[60px]">
<!-- Onglet Information --> <!-- Onglet Information -->
<template #information> <template #information>
<div class="mt-12 grid grid-cols-3 gap-x-[80px] gap-y-5 bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"> <div class="mt-12 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)]">
<!-- pt-1 : aligne le bord superieur du textarea sur celui des <!-- pt-1 : aligne le bord superieur du textarea sur celui des
inputs (centres dans un conteneur h-12, soit ~4px de retrait haut). --> inputs (centres dans un conteneur h-12, soit ~4px de retrait haut). -->
<MalioInputTextArea <MalioInputTextArea