From e19e392adb6207cdba1b303f02f9681b0be2710a Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 22 Jun 2026 13:42:47 +0200 Subject: [PATCH] feat(directory) : add client detail page with contact/address/report tabs --- .../composables/useDirectoryDetail.ts | 92 +++++++++++++++ .../pages/directory/clients/[id].vue | 107 ++++++++++++++++++ .../modules/directory/services/clients.ts | 6 +- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 frontend/modules/directory/composables/useDirectoryDetail.ts create mode 100644 frontend/modules/directory/pages/directory/clients/[id].vue diff --git a/frontend/modules/directory/composables/useDirectoryDetail.ts b/frontend/modules/directory/composables/useDirectoryDetail.ts new file mode 100644 index 0000000..122c4cf --- /dev/null +++ b/frontend/modules/directory/composables/useDirectoryDetail.ts @@ -0,0 +1,92 @@ +import type { Contact } from '~/modules/directory/services/dto/contact' +import type { Address } from '~/modules/directory/services/dto/address' +import { useContactService } from '~/modules/directory/services/contacts' +import { useAddressService } from '~/modules/directory/services/addresses' + +type Owner = { client?: string, prospect?: string } + +/** + * Logique partagée des fiches détail Client/Prospect : gestion des blocs + * répétables Contact et Adresse (chargement, ajout, édition par bloc avec + * persistance immédiate, suppression). Paramétré par l'IRI du propriétaire + * (`{ client }` ou `{ prospect }`), réutilisé tel quel par les deux pages. + */ +export function useDirectoryDetail(owner: Owner) { + const contactService = useContactService() + const addressService = useAddressService() + + const contacts = ref([]) + const addresses = ref([]) + + function emptyContact(): Contact { + return { id: 0, firstName: null, lastName: null, jobTitle: null, email: null, phonePrimary: null, phoneSecondary: null, ...owner } + } + function emptyAddress(): Address { + return { id: 0, label: null, street: null, streetComplement: null, postalCode: null, city: null, country: 'FR', ...owner } + } + + async function onContactInput(index: number, value: Contact): Promise { + contacts.value[index] = value + await persistContact(index) + } + async function persistContact(index: number): Promise { + const c = contacts.value[index] + if (!c) return + const payload = { firstName: c.firstName, lastName: c.lastName, jobTitle: c.jobTitle, email: c.email, phonePrimary: c.phonePrimary, phoneSecondary: c.phoneSecondary, ...owner } + if (c.id && c.id > 0) { + await contactService.update(c.id, payload) + } else if (c.lastName || c.firstName) { + const created = await contactService.create(payload) + contacts.value[index] = created + } + } + function addContact(): void { + contacts.value.push(emptyContact()) + } + async function removeContact(index: number): Promise { + const c = contacts.value[index] + if (c?.id && c.id > 0) await contactService.remove(c.id) + contacts.value.splice(index, 1) + } + + async function onAddressInput(index: number, value: Address): Promise { + addresses.value[index] = value + await persistAddress(index) + } + async function persistAddress(index: number): Promise { + const a = addresses.value[index] + if (!a) return + const payload = { label: a.label, street: a.street, streetComplement: a.streetComplement, postalCode: a.postalCode, city: a.city, country: a.country, ...owner } + if (a.id && a.id > 0) { + await addressService.update(a.id, payload) + } else if (a.street || a.city || a.postalCode) { + const created = await addressService.create(payload) + addresses.value[index] = created + } + } + function addAddress(): void { + addresses.value.push(emptyAddress()) + } + async function removeAddress(index: number): Promise { + const a = addresses.value[index] + if (a?.id && a.id > 0) await addressService.remove(a.id) + addresses.value.splice(index, 1) + } + + async function load(): Promise { + contacts.value = await contactService.getByOwner(owner) + addresses.value = await addressService.getByOwner(owner) + } + + return { + contacts, + addresses, + onContactInput, + addContact, + removeContact, + onAddressInput, + addAddress, + removeAddress, + load, + } +} diff --git a/frontend/modules/directory/pages/directory/clients/[id].vue b/frontend/modules/directory/pages/directory/clients/[id].vue new file mode 100644 index 0000000..28c723a --- /dev/null +++ b/frontend/modules/directory/pages/directory/clients/[id].vue @@ -0,0 +1,107 @@ + + + diff --git a/frontend/modules/directory/services/clients.ts b/frontend/modules/directory/services/clients.ts index 6a100be..05d73a4 100644 --- a/frontend/modules/directory/services/clients.ts +++ b/frontend/modules/directory/services/clients.ts @@ -10,6 +10,10 @@ export function useClientService() { return extractHydraMembers(data) } + async function getById(id: number): Promise { + return api.get(`/clients/${id}`) + } + async function create(payload: ClientWrite): Promise { return api.post('/clients', payload as Record, { toastSuccessKey: 'clients.created', @@ -28,5 +32,5 @@ export function useClientService() { }) } - return { getAll, create, update, remove } + return { getAll, getById, create, update, remove } }