ba462a091b
Fiches Client / Prospect / Prestataire (onglet Rapport mis à part) : - Champs email/téléphone : composants MalioInputEmail / MalioInputPhone - Grilles en 4 colonnes (Info + blocs Contact/Adresse) - Boutons « Nouveau contact/adresse » en secondary ; « Enregistrer » en taille Malio standard ; marge form↔bouton homogène entre onglets - Bouton retour ghost (mdi:arrow-left-bold) comme Starseed - 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 (logique centralisée dans useDirectoryDetail) Onglet Rapport : - Bouton d'ajout en taille Malio standard, label « Ajouter » - Suppression compte-rendu : passe à la ConfirmModal partagée (remplace l'ancienne ConfirmDeleteReportModal, supprimée) - Suppression d'un document joint : ajout d'une modal de confirmation - Upload via MalioInputUpload ; bouton supprimer document aligné (mdi:delete-outline ghost) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
163 lines
6.5 KiB
TypeScript
163 lines
6.5 KiB
TypeScript
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, prestataire?: string }
|
|
|
|
/**
|
|
* Logique partagée des fiches détail Client/Prospect : blocs répétables Contact
|
|
* et Adresse (chargement, ajout, suppression). L'édition est tenue en mémoire
|
|
* localement ; la persistance se fait au clic sur « Enregistrer » (saveContacts/
|
|
* saveAddresses), comme les formulaires de tâche — pas d'enregistrement au blur.
|
|
* 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 { t } = useI18n()
|
|
const contactService = useContactService()
|
|
const addressService = useAddressService()
|
|
|
|
const contacts = ref<Contact[]>([])
|
|
const addresses = ref<Address[]>([])
|
|
const savingContacts = ref(false)
|
|
const savingAddresses = ref(false)
|
|
|
|
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 }
|
|
}
|
|
|
|
// Édition locale uniquement : on remplace le bloc en mémoire, rien n'est
|
|
// persisté tant que l'utilisateur n'a pas cliqué sur « Enregistrer ».
|
|
function onContactInput(index: number, value: Contact): void {
|
|
contacts.value[index] = value
|
|
}
|
|
function onAddressInput(index: number, value: Address): void {
|
|
addresses.value[index] = value
|
|
}
|
|
|
|
function addContact(): void {
|
|
contacts.value.push(emptyContact())
|
|
}
|
|
function addAddress(): void {
|
|
addresses.value.push(emptyAddress())
|
|
}
|
|
|
|
// Suppression immédiate (comme la corbeille du formulaire de tâche) : un bloc
|
|
// déjà enregistré est supprimé côté serveur, une amorce non enregistrée est
|
|
// simplement retirée de la liste.
|
|
async function removeContact(index: number): Promise<void> {
|
|
const c = contacts.value[index]
|
|
if (c?.id && c.id > 0) await contactService.remove(c.id)
|
|
contacts.value.splice(index, 1)
|
|
}
|
|
async function removeAddress(index: number): Promise<void> {
|
|
const a = addresses.value[index]
|
|
if (a?.id && a.id > 0) await addressService.remove(a.id)
|
|
addresses.value.splice(index, 1)
|
|
}
|
|
|
|
// Confirmation de suppression d'un bloc (contact / adresse) : la corbeille du
|
|
// bloc ouvre une modal ; la suppression effective n'a lieu qu'à la confirmation.
|
|
const removeModalOpen = ref(false)
|
|
const pendingRemoval = ref<{ type: 'contact' | 'address', index: number } | null>(null)
|
|
|
|
const removeModalTitle = computed(() =>
|
|
pendingRemoval.value?.type === 'address'
|
|
? t('directory.addresses.deleteConfirmTitle')
|
|
: t('directory.contacts.deleteConfirmTitle'),
|
|
)
|
|
const removeModalMessage = computed(() =>
|
|
pendingRemoval.value?.type === 'address'
|
|
? t('directory.addresses.deleteConfirmMessage')
|
|
: t('directory.contacts.deleteConfirmMessage'),
|
|
)
|
|
|
|
function askRemoveContact(index: number): void {
|
|
pendingRemoval.value = { type: 'contact', index }
|
|
removeModalOpen.value = true
|
|
}
|
|
function askRemoveAddress(index: number): void {
|
|
pendingRemoval.value = { type: 'address', index }
|
|
removeModalOpen.value = true
|
|
}
|
|
async function confirmRemove(): Promise<void> {
|
|
const p = pendingRemoval.value
|
|
if (!p) return
|
|
if (p.type === 'contact') await removeContact(p.index)
|
|
else await removeAddress(p.index)
|
|
removeModalOpen.value = false
|
|
pendingRemoval.value = null
|
|
}
|
|
|
|
// Persistance au clic : met à jour les blocs existants, crée les nouveaux
|
|
// blocs renseignés. Les amorces vides (sans contenu) sont ignorées.
|
|
async function saveContacts(): Promise<void> {
|
|
if (savingContacts.value) return
|
|
savingContacts.value = true
|
|
try {
|
|
for (let i = 0; i < contacts.value.length; i++) {
|
|
const c = contacts.value[i]
|
|
if (!c) continue
|
|
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) {
|
|
contacts.value[i] = await contactService.update(c.id, payload)
|
|
} else if (c.lastName || c.firstName) {
|
|
contacts.value[i] = await contactService.create(payload)
|
|
}
|
|
}
|
|
} finally {
|
|
savingContacts.value = false
|
|
}
|
|
}
|
|
async function saveAddresses(): Promise<void> {
|
|
if (savingAddresses.value) return
|
|
savingAddresses.value = true
|
|
try {
|
|
for (let i = 0; i < addresses.value.length; i++) {
|
|
const a = addresses.value[i]
|
|
if (!a) continue
|
|
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) {
|
|
addresses.value[i] = await addressService.update(a.id, payload)
|
|
} else if (a.street || a.city || a.postalCode) {
|
|
addresses.value[i] = await addressService.create(payload)
|
|
}
|
|
}
|
|
} finally {
|
|
savingAddresses.value = false
|
|
}
|
|
}
|
|
|
|
async function load(): Promise<void> {
|
|
contacts.value = await contactService.getByOwner(owner)
|
|
addresses.value = await addressService.getByOwner(owner)
|
|
}
|
|
|
|
return {
|
|
contacts,
|
|
addresses,
|
|
savingContacts,
|
|
savingAddresses,
|
|
onContactInput,
|
|
addContact,
|
|
removeContact,
|
|
saveContacts,
|
|
onAddressInput,
|
|
addAddress,
|
|
removeAddress,
|
|
saveAddresses,
|
|
load,
|
|
// Suppression de bloc avec confirmation (modal partagée contact/adresse).
|
|
removeModalOpen,
|
|
removeModalTitle,
|
|
removeModalMessage,
|
|
askRemoveContact,
|
|
askRemoveAddress,
|
|
confirmRemove,
|
|
}
|
|
}
|