Files
Lesstime/frontend/modules/directory/components/DirectoryContactBlock.vue
T
tristan bbd8a38c95
Auto Tag Develop / tag (push) Successful in 9s
feat(directory) : refonte UI du Répertoire (LST-72) (#27)
Améliorations frontend de la partie **Répertoire** (Client / Prospect / Prestataire). Onglet **Rapport** retravaillé en fin de parcours ; le reste de la logique métier inchangé.

## Navigation & liste
- Onglet actif conservé au retour liste ↔ fiche (flèche app **et** navigateur) via `history.state` (hors URL) — util `historyTab.ts`
- Colonne « Action » (entête alignée) + feedback hover sur les boutons d'action
- Conversion prospect → client : modal de confirmation
- Boutons « Ajouter » : label court + taille Malio standard ; barres d'outils à hauteur homogène (plus de saut entre onglets)

## Fiches (Info / Contact / Adresse)
- Style **plat** sans box-shadow (comme Starseed)
- Champs email/téléphone : `MalioInputEmail` / `MalioInputPhone`
- Grilles en **4 colonnes** (Info + blocs)
- Boutons « Nouveau contact/adresse » en secondary ; « Enregistrer » en taille Malio ; marge form↔bouton homogène
- Bouton retour **ghost** (`mdi:arrow-left-bold`)
- **Adresse** : flux CP → ville → rue (rue conditionnée au CP+ville, cascade de reset) ; titre du bloc = libellé saisi
- Suppression d'un bloc Contact/Adresse : **modal** de confirmation (centralisée dans `useDirectoryDetail`)
- Modals (suppression, conversion) basées sur `MalioModal` (design Starseed) avec nom en gras

## Onglet Rapport
- Bouton d'ajout en taille Malio (« Ajouter »)
- Suppression compte-rendu : `ConfirmModal` partagée (remplace l'ancienne modal maison)
- Suppression d'un document joint : ajout d'une modal de confirmation
- Upload via `MalioInputUpload` ; bouton supprimer document aligné (`mdi:delete-outline` ghost)

## Divers
- `fix(auth)` : cookie JWT renommé `BEARER_LESSTIME` (collision localhost avec d'autres apps Symfony)
- `fix(infra)` : target makefile `fix-uploads-perm` (volume `uploads_data` root → upload local OK)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: #27
2026-06-27 13:29:56 +00:00

96 lines
3.6 KiB
Vue

<template>
<!-- Bloc à plat (sans box-shadow) : un filet noir 1px le sépare du suivant
(pas de bordure sous le dernier bloc), comme sur Starseed. -->
<div class="pb-5" :class="{ 'border-b border-black': !last }">
<div class="flex items-center justify-between">
<h3 class="text-[20px] font-semibold text-black">{{ title }}</h3>
<MalioButtonIcon
v-if="removable && !readonly"
icon="mdi:delete-outline"
variant="ghost"
button-class="p-0"
:aria-label="$t('common.delete')"
@click="$emit('remove')"
/>
</div>
<div class="mt-6 grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
:label="$t('directory.contacts.fields.lastName')"
:model-value="modelValue.lastName ?? ''"
:readonly="readonly"
@update:model-value="update('lastName', $event)"
/>
<MalioInputText
:label="$t('directory.contacts.fields.firstName')"
:model-value="modelValue.firstName ?? ''"
:readonly="readonly"
@update:model-value="update('firstName', $event)"
/>
<MalioInputText
class="col-span-2"
:label="$t('directory.contacts.fields.jobTitle')"
:model-value="modelValue.jobTitle ?? ''"
:readonly="readonly"
@update:model-value="update('jobTitle', $event)"
/>
<MalioInputEmail
:label="$t('directory.contacts.fields.email')"
:model-value="modelValue.email ?? ''"
:readonly="readonly"
:error="emailError"
@update:model-value="update('email', $event)"
/>
<MalioInputPhone
:label="$t('directory.contacts.fields.phonePrimary')"
:model-value="modelValue.phonePrimary ?? ''"
:readonly="readonly"
:error="phonePrimaryError"
@update:model-value="update('phonePrimary', $event)"
/>
<MalioInputPhone
:label="$t('directory.contacts.fields.phoneSecondary')"
:model-value="modelValue.phoneSecondary ?? ''"
:readonly="readonly"
:error="phoneSecondaryError"
@update:model-value="update('phoneSecondary', $event)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import type { Contact } from '~/modules/directory/services/dto/contact'
import { isValidEmail, isValidFrPhone } from '~/modules/directory/utils/validation'
const props = defineProps<{
modelValue: Contact
title: string
removable?: boolean
readonly?: boolean
/** Dernier bloc de la liste : supprime le filet de séparation bas. */
last?: boolean
}>()
const emit = defineEmits<{
'update:modelValue': [value: Contact]
'remove': []
}>()
const { t } = useI18n()
const emailError = computed(() =>
isValidEmail(props.modelValue.email) ? '' : t('directory.validation.emailInvalid'),
)
const phonePrimaryError = computed(() =>
isValidFrPhone(props.modelValue.phonePrimary) ? '' : t('directory.validation.phoneInvalid'),
)
const phoneSecondaryError = computed(() =>
isValidFrPhone(props.modelValue.phoneSecondary) ? '' : t('directory.validation.phoneInvalid'),
)
function update(field: keyof Contact, value: string): void {
emit('update:modelValue', { ...props.modelValue, [field]: value === '' ? null : value })
}
</script>