Files
Starseed/frontend/modules/commercial/components/ClientContactBlock.vue
T
tristan 5834d7b225
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m0s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m19s
feat(commercial) : onglet adresse — Select « Type d'adresse » + Sites en multiselect, ligne 1 réagencée (ERP-110)
- Sites Starseed : 3 cases -> multiselect a tags « Sites » (required).
- Usage adresse : 3 cases Prospect/Livraison/Facturation -> Select unique « Type d'adresse » (Prospect / Livraison / Facturation / Adresse + Facturation), obligatoire sans option vide, conditionnant le bouton « Valider ». Pur sucre front : le back recoit toujours isProspect/isDelivery/isBilling (aucune RG modifiee), exclusivite Prospect devenue structurelle.
- Email de facturation conditionnel (Facturation / Adresse + Facturation) deplace en ligne 1.
- Ligne 1 : Type d'adresse | Sites | Contact rattache | Email ; le reste (Categorie, Pays, CP, Ville, Adresse...) en lignes suivantes.
- Email : MalioInputText -> MalioInputEmail (lowercase, ERP-101/RG-1.21) sur facturation ET contact.
- Helpers front testables addressFlagsFromType / addressTypeFromFlags + gating canValidateAddresses (type obligatoire) dans new.vue / edit.vue.
2026-06-04 16:01:41 +02:00

107 lines
4.1 KiB
Vue

<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 obligatoire RG-1.14) ou en lecture seule.
ariaLabel via v-bind objet (prop camelCase ; aria-* serait un attribut HTML). -->
<MalioButtonIcon
v-if="removable && !readonly"
icon="mdi:delete-outline"
variant="ghost"
button-class="absolute top-3 right-3"
v-bind="{ ariaLabel: t('commercial.clients.form.contact.remove') }"
@click="$emit('remove')"
/>
<MalioInputText
:model-value="model.lastName"
:label="t('commercial.clients.form.contact.lastName')"
:readonly="readonly"
:error="errors?.lastName"
@update:model-value="(v: string) => update('lastName', v)"
/>
<MalioInputText
:model-value="model.firstName"
:label="t('commercial.clients.form.contact.firstName')"
:readonly="readonly"
:error="errors?.firstName"
@update:model-value="(v: string) => update('firstName', v)"
/>
<MalioInputText
:model-value="model.jobTitle"
:label="t('commercial.clients.form.contact.jobTitle')"
:readonly="readonly"
:error="errors?.jobTitle"
@update:model-value="(v: string) => update('jobTitle', v)"
/>
<MalioInputEmail
:model-value="model.email"
:label="t('commercial.clients.form.contact.email')"
:readonly="readonly"
:lowercase="true"
:error="errors?.email"
@update:model-value="(v: string) => update('email', v)"
/>
<MalioInputPhone
:model-value="model.phonePrimary"
:label="t('commercial.clients.form.contact.phonePrimary')"
:mask="PHONE_MASK"
:readonly="readonly"
:error="errors?.phonePrimary"
:addable="!model.hasSecondaryPhone && !readonly"
:add-button-label="t('commercial.clients.form.contact.addPhone')"
@update:model-value="(v: string) => update('phonePrimary', v)"
@add="revealSecondaryPhone"
/>
<MalioInputPhone
v-if="model.hasSecondaryPhone"
:model-value="model.phoneSecondary"
:label="t('commercial.clients.form.contact.phoneSecondary')"
:mask="PHONE_MASK"
:readonly="readonly"
:error="errors?.phoneSecondary"
@update:model-value="(v: string) => update('phoneSecondary', v)"
/>
</div>
</template>
<script setup lang="ts">
import type { ContactFormDraft } from '~/modules/commercial/types/clientForm'
// Masque telephone FR : 5 groupes de 2 chiffres (la normalisation finale reste
// serveur, cf. formatPhoneFR re-applique a la valeur renvoyee).
const PHONE_MASK = '## ## ## ## ##'
const props = defineProps<{
/** Brouillon du contact (v-model). */
modelValue: ContactFormDraft
/** Titre du bloc (ex: « Contact 1 »). */
title: string
/** Affiche l'icone de suppression (1er bloc non supprimable, RG-1.14). */
removable?: boolean
/** Bloc en lecture seule (onglet valide). */
readonly?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
const emit = defineEmits<{
'update:modelValue': [value: ContactFormDraft]
'remove': []
}>()
const { t } = useI18n()
// Alias local pour la lisibilite du template.
const model = computed(() => props.modelValue)
/** Emet un nouveau brouillon avec le champ modifie (immutabilite). */
function update<K extends keyof ContactFormDraft>(field: K, value: ContactFormDraft[K]): void {
emit('update:modelValue', { ...props.modelValue, [field]: value })
}
/** Revele le 2e numero (RG-1.02/1.20 : max 1 secondaire, le « + » disparait). */
function revealSecondaryPhone(): void {
emit('update:modelValue', { ...props.modelValue, hasSecondaryPhone: true })
}
</script>