feat(directory) : refonte UI du Répertoire (LST-72) (#27)
Auto Tag Develop / tag (push) Successful in 9s
Auto Tag Develop / tag (push) Successful in 9s
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
This commit was merged in pull request #27.
This commit is contained in:
@@ -2,7 +2,14 @@
|
||||
<div>
|
||||
<PageHeader>
|
||||
<span class="inline-flex items-center gap-3">
|
||||
<MalioButtonIcon icon="mdi:arrow-left" :aria-label="$t('common.back')" @click="goBack" />
|
||||
<MalioButtonIcon
|
||||
icon="mdi:arrow-left-bold"
|
||||
icon-size="24"
|
||||
variant="ghost"
|
||||
:title="$t('common.back')"
|
||||
:aria-label="$t('common.back')"
|
||||
@click="goBack"
|
||||
/>
|
||||
{{ client?.name ?? '…' }}
|
||||
</span>
|
||||
</PageHeader>
|
||||
@@ -13,7 +20,7 @@
|
||||
<MalioTabList v-model="activeTab" :tabs="tabs">
|
||||
<template #info>
|
||||
<div class="flex flex-col gap-4 pt-6">
|
||||
<div class="relative grid grid-cols-2 gap-x-[44px] gap-y-4 rounded-lg bg-white px-7 py-5 shadow-[0_4px_4px_0_rgba(0,0,0,0.10)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4 pb-5">
|
||||
<MalioInputText
|
||||
v-model="info.name"
|
||||
class="col-span-2"
|
||||
@@ -21,12 +28,12 @@
|
||||
:error="infoTouched.name && !info.name.trim() ? $t('directory.validation.nameRequired') : ''"
|
||||
@blur="infoTouched.name = true"
|
||||
/>
|
||||
<MalioInputText
|
||||
<MalioInputEmail
|
||||
v-model="info.email"
|
||||
:label="$t('directory.info.fields.email')"
|
||||
:error="emailError"
|
||||
/>
|
||||
<MalioInputText
|
||||
<MalioInputPhone
|
||||
v-model="info.phone"
|
||||
:label="$t('directory.info.fields.phone')"
|
||||
:error="phoneError"
|
||||
@@ -40,7 +47,6 @@
|
||||
</div>
|
||||
<div class="flex justify-center pt-2">
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingInfo || !infoValid"
|
||||
@click="saveInfo"
|
||||
@@ -57,12 +63,13 @@
|
||||
:model-value="contact"
|
||||
:title="$t('directory.contacts.item', { n: i + 1 })"
|
||||
:removable="contacts.length > 0"
|
||||
:last="i === contacts.length - 1"
|
||||
@update:model-value="(v) => onContactInput(i, v)"
|
||||
@remove="removeContact(i)"
|
||||
@remove="askRemoveContact(i)"
|
||||
/>
|
||||
<div class="flex justify-center gap-3 pt-2">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@@ -70,7 +77,6 @@
|
||||
@click="addContact"
|
||||
/>
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingContacts"
|
||||
@click="saveContacts"
|
||||
@@ -87,12 +93,13 @@
|
||||
:model-value="address"
|
||||
:title="$t('directory.addresses.item', { n: i + 1 })"
|
||||
:removable="addresses.length > 0"
|
||||
:last="i === addresses.length - 1"
|
||||
@update:model-value="(v) => onAddressInput(i, v)"
|
||||
@remove="removeAddress(i)"
|
||||
@remove="askRemoveAddress(i)"
|
||||
/>
|
||||
<div class="flex justify-center gap-3 pt-2">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@@ -100,7 +107,6 @@
|
||||
@click="addAddress"
|
||||
/>
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingAddresses"
|
||||
@click="saveAddresses"
|
||||
@@ -115,6 +121,13 @@
|
||||
</MalioTabList>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-model="removeModalOpen"
|
||||
:title="removeModalTitle"
|
||||
:message="removeModalMessage"
|
||||
@confirm="confirmRemove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -141,13 +154,17 @@ const {
|
||||
savingAddresses,
|
||||
onContactInput,
|
||||
addContact,
|
||||
removeContact,
|
||||
askRemoveContact,
|
||||
saveContacts,
|
||||
onAddressInput,
|
||||
addAddress,
|
||||
removeAddress,
|
||||
askRemoveAddress,
|
||||
saveAddresses,
|
||||
load,
|
||||
removeModalOpen,
|
||||
removeModalTitle,
|
||||
removeModalMessage,
|
||||
confirmRemove,
|
||||
} = useDirectoryDetail(owner)
|
||||
|
||||
const { can } = usePermissions()
|
||||
@@ -192,7 +209,8 @@ async function saveInfo(): Promise<void> {
|
||||
}
|
||||
|
||||
function goBack(): void {
|
||||
router.push('/directory')
|
||||
// Retour sur l'onglet Clients de la liste (via history.state, hors URL).
|
||||
router.push({ path: '/directory', state: { tab: 'clients' } })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
<!-- Clients -->
|
||||
<template #clients>
|
||||
<div class="flex min-h-[30rem] flex-col gap-4 pt-10">
|
||||
<div class="flex items-center justify-end">
|
||||
<div class="flex min-h-[48px] items-center justify-end">
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
:label="$t('directory.clients.add')"
|
||||
:label="$t('common.add')"
|
||||
@click="openCreateClient"
|
||||
/>
|
||||
</div>
|
||||
@@ -26,6 +25,9 @@
|
||||
:empty-message="$t('directory.clients.empty')"
|
||||
@row-click="openEditClient"
|
||||
>
|
||||
<template #header-actions>
|
||||
<span class="block text-right font-semibold text-black">{{ $t('common.actions') }}</span>
|
||||
</template>
|
||||
<template #cell-email="{ item }">
|
||||
{{ (item as Client).email ?? '—' }}
|
||||
</template>
|
||||
@@ -37,7 +39,7 @@
|
||||
<MalioButtonIcon
|
||||
icon="mdi:trash-can-outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
button-class="!bg-red-100 !text-red-700"
|
||||
button-class="!bg-red-100 !text-red-700 hover:!bg-red-200"
|
||||
:icon-size="18"
|
||||
@click="askDeleteClient(item as Client)"
|
||||
/>
|
||||
@@ -50,7 +52,7 @@
|
||||
<!-- Prospects -->
|
||||
<template #prospects>
|
||||
<div class="flex min-h-[30rem] flex-col gap-4 pt-10">
|
||||
<div class="flex flex-wrap items-end justify-between gap-3">
|
||||
<div class="flex min-h-[48px] flex-wrap items-center justify-between gap-3">
|
||||
<MalioSelect
|
||||
v-model="statusFilter"
|
||||
:label="$t('prospects.fields.status')"
|
||||
@@ -61,8 +63,7 @@
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
:label="$t('directory.prospects.add')"
|
||||
:label="$t('common.add')"
|
||||
@click="openCreateProspect"
|
||||
/>
|
||||
</div>
|
||||
@@ -74,6 +75,9 @@
|
||||
:empty-message="$t('directory.prospects.empty')"
|
||||
@row-click="openEditProspect"
|
||||
>
|
||||
<template #header-actions>
|
||||
<span class="block text-right font-semibold text-black">{{ $t('common.actions') }}</span>
|
||||
</template>
|
||||
<template #cell-status="{ item }">
|
||||
<StatusBadge
|
||||
:label="statusLabel((item as ProspectRow).status)"
|
||||
@@ -92,14 +96,14 @@
|
||||
v-if="!(item as ProspectRow).convertedClient"
|
||||
icon="mdi:account-convert"
|
||||
:aria-label="$t('prospects.convert')"
|
||||
button-class="!bg-green-100 !text-green-700"
|
||||
button-class="!bg-green-100 !text-green-700 hover:!bg-green-200"
|
||||
:icon-size="18"
|
||||
@click="convertProspect(item as ProspectRow)"
|
||||
@click="askConvertProspect(item as ProspectRow)"
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:trash-can-outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
button-class="!bg-red-100 !text-red-700"
|
||||
button-class="!bg-red-100 !text-red-700 hover:!bg-red-200"
|
||||
:icon-size="18"
|
||||
@click="askDeleteProspect(item as ProspectRow)"
|
||||
/>
|
||||
@@ -111,12 +115,11 @@
|
||||
<!-- Prestataires -->
|
||||
<template #prestataires>
|
||||
<div class="flex min-h-[30rem] flex-col gap-4 pt-10">
|
||||
<div class="flex items-center justify-end">
|
||||
<div class="flex min-h-[48px] items-center justify-end">
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
:label="$t('directory.prestataires.add')"
|
||||
:label="$t('common.add')"
|
||||
@click="openCreatePrestataire"
|
||||
/>
|
||||
</div>
|
||||
@@ -128,6 +131,9 @@
|
||||
:empty-message="$t('directory.prestataires.empty')"
|
||||
@row-click="openEditPrestataire"
|
||||
>
|
||||
<template #header-actions>
|
||||
<span class="block text-right font-semibold text-black">{{ $t('common.actions') }}</span>
|
||||
</template>
|
||||
<template #cell-email="{ item }">
|
||||
{{ (item as Prestataire).email ?? '—' }}
|
||||
</template>
|
||||
@@ -139,7 +145,7 @@
|
||||
<MalioButtonIcon
|
||||
icon="mdi:trash-can-outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
button-class="!bg-red-100 !text-red-700"
|
||||
button-class="!bg-red-100 !text-red-700 hover:!bg-red-200"
|
||||
:icon-size="18"
|
||||
@click="askDeletePrestataire(item as Prestataire)"
|
||||
/>
|
||||
@@ -166,12 +172,31 @@
|
||||
@saved="loadPrestataires"
|
||||
/>
|
||||
|
||||
<ConfirmDeleteModal
|
||||
<ConfirmModal
|
||||
v-model="deleteModalOpen"
|
||||
:title="deleteModalTitle"
|
||||
:message="deleteModalMessage"
|
||||
@confirm="confirmDelete"
|
||||
/>
|
||||
>
|
||||
<i18n-t :keypath="deleteModalKeypath" tag="p" scope="global">
|
||||
<template #name>
|
||||
<strong class="font-semibold">{{ deleteTargetName }}</strong>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</ConfirmModal>
|
||||
|
||||
<ConfirmModal
|
||||
v-model="convertModalOpen"
|
||||
:title="$t('prospects.convertConfirmTitle')"
|
||||
:confirm-label="$t('prospects.convertConfirm')"
|
||||
confirm-variant="primary"
|
||||
@confirm="confirmConvert"
|
||||
>
|
||||
<i18n-t keypath="prospects.convertConfirmMessage" tag="p" scope="global">
|
||||
<template #name>
|
||||
<strong class="font-semibold">{{ convertTarget?.company }}</strong>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</ConfirmModal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -183,6 +208,7 @@ import type { Prospect, ProspectStatus } from '~/modules/directory/services/dto/
|
||||
import { useProspectService } from '~/modules/directory/services/prospects'
|
||||
import type { Prestataire } from '~/modules/directory/services/dto/prestataire'
|
||||
import { usePrestataireService } from '~/modules/directory/services/prestataires'
|
||||
import { readHistoryTab, stampHistoryTab } from '~/utils/historyTab'
|
||||
|
||||
definePageMeta({ middleware: ['admin'] })
|
||||
|
||||
@@ -201,6 +227,14 @@ const tabs = [
|
||||
{ key: 'prospects', label: t('directory.tabs.prospects'), icon: 'mdi:account-search-outline' },
|
||||
{ key: 'prestataires', label: t('directory.tabs.prestataires'), icon: 'mdi:account-hard-hat-outline' },
|
||||
]
|
||||
const tabKeys = tabs.map((tab) => tab.key)
|
||||
|
||||
// Avant d'ouvrir une fiche : on estampille l'entrée d'historique courante avec
|
||||
// l'onglet actif → la flèche « précédent » du navigateur restaure le bon onglet.
|
||||
function navigateToDetail(path: string): void {
|
||||
stampHistoryTab(activeTab.value)
|
||||
navigateTo(path)
|
||||
}
|
||||
|
||||
// --- Clients ---
|
||||
const clients = ref<Client[]>([])
|
||||
@@ -211,7 +245,7 @@ const clientColumns = [
|
||||
{ key: 'name', label: t('prospects.fields.company') },
|
||||
{ key: 'email', label: t('prospects.fields.email') },
|
||||
{ key: 'phone', label: t('prospects.fields.phone') },
|
||||
{ key: 'actions', label: '' },
|
||||
{ key: 'actions', label: t('common.actions') },
|
||||
]
|
||||
|
||||
async function loadClients() {
|
||||
@@ -224,7 +258,7 @@ function openCreateClient() {
|
||||
}
|
||||
|
||||
function openEditClient(item: Record<string, unknown>) {
|
||||
navigateTo(`/directory/clients/${(item as Client).id}`)
|
||||
navigateToDetail(`/directory/clients/${(item as Client).id}`)
|
||||
}
|
||||
|
||||
// --- Prospects ---
|
||||
@@ -246,7 +280,7 @@ const prospectColumns = [
|
||||
{ key: 'status', label: t('prospects.fields.status') },
|
||||
{ key: 'email', label: t('prospects.fields.email') },
|
||||
{ key: 'phone', label: t('prospects.fields.phone') },
|
||||
{ key: 'actions', label: '' },
|
||||
{ key: 'actions', label: t('common.actions') },
|
||||
]
|
||||
|
||||
const prospectRows = computed<ProspectRow[]>(() => prospects.value)
|
||||
@@ -282,13 +316,26 @@ function openCreateProspect() {
|
||||
}
|
||||
|
||||
function openEditProspect(item: Record<string, unknown>) {
|
||||
navigateTo(`/directory/prospects/${(item as Prospect).id}`)
|
||||
navigateToDetail(`/directory/prospects/${(item as Prospect).id}`)
|
||||
}
|
||||
|
||||
async function convertProspect(row: ProspectRow) {
|
||||
// La conversion passe par une modal de confirmation (le prospect devient client).
|
||||
const convertModalOpen = ref(false)
|
||||
const convertTarget = ref<ProspectRow | null>(null)
|
||||
|
||||
function askConvertProspect(row: ProspectRow) {
|
||||
convertTarget.value = row
|
||||
convertModalOpen.value = true
|
||||
}
|
||||
|
||||
async function confirmConvert() {
|
||||
const row = convertTarget.value
|
||||
if (!row) return
|
||||
await prospectService.convert(row.id)
|
||||
// La conversion crée un client et retire le prospect : rafraîchir les deux listes.
|
||||
await Promise.all([loadProspects(), loadClients()])
|
||||
convertModalOpen.value = false
|
||||
convertTarget.value = null
|
||||
}
|
||||
|
||||
// Le ProspectDrawer porte aussi le bouton « Convertir » : son event 'saved' peut
|
||||
@@ -306,7 +353,7 @@ const prestataireColumns = [
|
||||
{ key: 'name', label: t('prospects.fields.company') },
|
||||
{ key: 'email', label: t('prospects.fields.email') },
|
||||
{ key: 'phone', label: t('prospects.fields.phone') },
|
||||
{ key: 'actions', label: '' },
|
||||
{ key: 'actions', label: t('common.actions') },
|
||||
]
|
||||
|
||||
async function loadPrestataires() {
|
||||
@@ -319,7 +366,7 @@ function openCreatePrestataire() {
|
||||
}
|
||||
|
||||
function openEditPrestataire(item: Record<string, unknown>) {
|
||||
navigateTo(`/directory/prestataires/${(item as Prestataire).id}`)
|
||||
navigateToDetail(`/directory/prestataires/${(item as Prestataire).id}`)
|
||||
}
|
||||
|
||||
// --- Suppression (clients, prospects & prestataires) ---
|
||||
@@ -342,17 +389,22 @@ const deleteModalTitle = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const deleteModalMessage = computed(() => {
|
||||
// Clé i18n du message (le nom y est injecté en gras via <i18n-t> côté template).
|
||||
const deleteModalKeypath = computed(() => {
|
||||
switch (deleteTarget.value?.type) {
|
||||
case 'prospect':
|
||||
return 'prospects.deleteConfirmMessage'
|
||||
case 'prestataire':
|
||||
return 'prestataires.deleteConfirmMessage'
|
||||
default:
|
||||
return 'clients.deleteConfirmMessage'
|
||||
}
|
||||
})
|
||||
|
||||
const deleteTargetName = computed(() => {
|
||||
const target = deleteTarget.value
|
||||
if (!target) return ''
|
||||
switch (target.type) {
|
||||
case 'prospect':
|
||||
return t('prospects.deleteConfirmMessage', { name: target.item.company })
|
||||
case 'prestataire':
|
||||
return t('prestataires.deleteConfirmMessage', { name: target.item.name })
|
||||
default:
|
||||
return t('clients.deleteConfirmMessage', { name: target.item.name })
|
||||
}
|
||||
return target.type === 'prospect' ? target.item.company : target.item.name
|
||||
})
|
||||
|
||||
function askDeleteClient(item: Client) {
|
||||
@@ -392,6 +444,9 @@ async function confirmDelete() {
|
||||
watch(statusFilter, loadProspects)
|
||||
|
||||
onMounted(async () => {
|
||||
// Restaure l'onglet quitté lors d'un retour depuis une fiche (flèche app ou
|
||||
// navigateur). `null` (deep link / reload) → onglet Clients par défaut.
|
||||
activeTab.value = readHistoryTab(tabKeys) ?? 'clients'
|
||||
await Promise.all([loadClients(), loadProspects(), loadPrestataires()])
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<div>
|
||||
<PageHeader>
|
||||
<span class="inline-flex items-center gap-3">
|
||||
<MalioButtonIcon icon="mdi:arrow-left" :aria-label="$t('common.back')" @click="goBack" />
|
||||
<MalioButtonIcon
|
||||
icon="mdi:arrow-left-bold"
|
||||
icon-size="24"
|
||||
variant="ghost"
|
||||
:title="$t('common.back')"
|
||||
:aria-label="$t('common.back')"
|
||||
@click="goBack"
|
||||
/>
|
||||
{{ prestataire?.name ?? '…' }}
|
||||
</span>
|
||||
</PageHeader>
|
||||
@@ -13,7 +20,7 @@
|
||||
<MalioTabList v-model="activeTab" :tabs="tabs">
|
||||
<template #info>
|
||||
<div class="flex flex-col gap-4 pt-6">
|
||||
<div class="relative grid grid-cols-2 gap-x-[44px] gap-y-4 rounded-lg bg-white px-7 py-5 shadow-[0_4px_4px_0_rgba(0,0,0,0.10)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4 pb-5">
|
||||
<MalioInputText
|
||||
v-model="info.name"
|
||||
class="col-span-2"
|
||||
@@ -21,12 +28,12 @@
|
||||
:error="infoTouched.name && !info.name.trim() ? $t('directory.validation.nameRequired') : ''"
|
||||
@blur="infoTouched.name = true"
|
||||
/>
|
||||
<MalioInputText
|
||||
<MalioInputEmail
|
||||
v-model="info.email"
|
||||
:label="$t('directory.info.fields.email')"
|
||||
:error="emailError"
|
||||
/>
|
||||
<MalioInputText
|
||||
<MalioInputPhone
|
||||
v-model="info.phone"
|
||||
:label="$t('directory.info.fields.phone')"
|
||||
:error="phoneError"
|
||||
@@ -40,7 +47,6 @@
|
||||
</div>
|
||||
<div class="flex justify-center pt-2">
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingInfo || !infoValid"
|
||||
@click="saveInfo"
|
||||
@@ -57,12 +63,13 @@
|
||||
:model-value="contact"
|
||||
:title="$t('directory.contacts.item', { n: i + 1 })"
|
||||
:removable="contacts.length > 0"
|
||||
:last="i === contacts.length - 1"
|
||||
@update:model-value="(v) => onContactInput(i, v)"
|
||||
@remove="removeContact(i)"
|
||||
@remove="askRemoveContact(i)"
|
||||
/>
|
||||
<div class="flex justify-center gap-3 pt-2">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@@ -70,7 +77,6 @@
|
||||
@click="addContact"
|
||||
/>
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingContacts"
|
||||
@click="saveContacts"
|
||||
@@ -87,12 +93,13 @@
|
||||
:model-value="address"
|
||||
:title="$t('directory.addresses.item', { n: i + 1 })"
|
||||
:removable="addresses.length > 0"
|
||||
:last="i === addresses.length - 1"
|
||||
@update:model-value="(v) => onAddressInput(i, v)"
|
||||
@remove="removeAddress(i)"
|
||||
@remove="askRemoveAddress(i)"
|
||||
/>
|
||||
<div class="flex justify-center gap-3 pt-2">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@@ -100,7 +107,6 @@
|
||||
@click="addAddress"
|
||||
/>
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingAddresses"
|
||||
@click="saveAddresses"
|
||||
@@ -115,6 +121,13 @@
|
||||
</MalioTabList>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-model="removeModalOpen"
|
||||
:title="removeModalTitle"
|
||||
:message="removeModalMessage"
|
||||
@confirm="confirmRemove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -141,13 +154,17 @@ const {
|
||||
savingAddresses,
|
||||
onContactInput,
|
||||
addContact,
|
||||
removeContact,
|
||||
askRemoveContact,
|
||||
saveContacts,
|
||||
onAddressInput,
|
||||
addAddress,
|
||||
removeAddress,
|
||||
askRemoveAddress,
|
||||
saveAddresses,
|
||||
load,
|
||||
removeModalOpen,
|
||||
removeModalTitle,
|
||||
removeModalMessage,
|
||||
confirmRemove,
|
||||
} = useDirectoryDetail(owner)
|
||||
|
||||
const { can } = usePermissions()
|
||||
@@ -190,7 +207,8 @@ async function saveInfo(): Promise<void> {
|
||||
}
|
||||
|
||||
function goBack(): void {
|
||||
router.push('/directory')
|
||||
// Retour sur l'onglet Prestataires de la liste (via history.state, hors URL).
|
||||
router.push({ path: '/directory', state: { tab: 'prestataires' } })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
<div>
|
||||
<PageHeader>
|
||||
<span class="inline-flex items-center gap-3">
|
||||
<MalioButtonIcon icon="mdi:arrow-left" :aria-label="$t('common.back')" @click="goBack" />
|
||||
<MalioButtonIcon
|
||||
icon="mdi:arrow-left-bold"
|
||||
icon-size="24"
|
||||
variant="ghost"
|
||||
:title="$t('common.back')"
|
||||
:aria-label="$t('common.back')"
|
||||
@click="goBack"
|
||||
/>
|
||||
{{ prospect?.company ?? '…' }}
|
||||
</span>
|
||||
</PageHeader>
|
||||
@@ -13,7 +20,7 @@
|
||||
<MalioTabList v-model="activeTab" :tabs="tabs">
|
||||
<template #info>
|
||||
<div class="flex flex-col gap-4 pt-6">
|
||||
<div class="relative grid grid-cols-2 gap-x-[44px] gap-y-4 rounded-lg bg-white px-7 py-5 shadow-[0_4px_4px_0_rgba(0,0,0,0.10)]">
|
||||
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4 pb-5">
|
||||
<MalioInputText
|
||||
v-model="info.company"
|
||||
class="col-span-2"
|
||||
@@ -32,12 +39,12 @@
|
||||
:label="$t('prospects.fields.website')"
|
||||
:error="websiteError"
|
||||
/>
|
||||
<MalioInputText
|
||||
<MalioInputEmail
|
||||
v-model="info.email"
|
||||
:label="$t('prospects.fields.email')"
|
||||
:error="emailError"
|
||||
/>
|
||||
<MalioInputText
|
||||
<MalioInputPhone
|
||||
v-model="info.phone"
|
||||
:label="$t('prospects.fields.phone')"
|
||||
:error="phoneError"
|
||||
@@ -47,15 +54,21 @@
|
||||
class="col-span-2"
|
||||
:label="$t('prospects.fields.source')"
|
||||
/>
|
||||
<!-- Notes : 2 colonnes, hauteur fixe (~2 lignes) avec scroll
|
||||
interne. Pas de row-span (il déréglait l'auto-placement).
|
||||
!max-w-none : neutralise le max-width:640px inline du
|
||||
composant Malio (sinon la textarea ne remplit pas 2 colonnes). -->
|
||||
<MalioInputTextArea
|
||||
v-model="info.notes"
|
||||
class="col-span-2"
|
||||
group-class="col-span-2"
|
||||
text-input="!h-28 !max-w-none text-lg"
|
||||
resize="none"
|
||||
:reserve-message-space="false"
|
||||
:label="$t('prospects.fields.notes')"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center pt-2">
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingInfo || !infoValid"
|
||||
@click="saveInfo"
|
||||
@@ -72,12 +85,13 @@
|
||||
:model-value="contact"
|
||||
:title="$t('directory.contacts.item', { n: i + 1 })"
|
||||
:removable="contacts.length > 0"
|
||||
:last="i === contacts.length - 1"
|
||||
@update:model-value="(v) => onContactInput(i, v)"
|
||||
@remove="removeContact(i)"
|
||||
@remove="askRemoveContact(i)"
|
||||
/>
|
||||
<div class="flex justify-center gap-3 pt-2">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@@ -85,7 +99,6 @@
|
||||
@click="addContact"
|
||||
/>
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingContacts"
|
||||
@click="saveContacts"
|
||||
@@ -102,12 +115,13 @@
|
||||
:model-value="address"
|
||||
:title="$t('directory.addresses.item', { n: i + 1 })"
|
||||
:removable="addresses.length > 0"
|
||||
:last="i === addresses.length - 1"
|
||||
@update:model-value="(v) => onAddressInput(i, v)"
|
||||
@remove="removeAddress(i)"
|
||||
@remove="askRemoveAddress(i)"
|
||||
/>
|
||||
<div class="flex justify-center gap-3 pt-2">
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@@ -115,7 +129,6 @@
|
||||
@click="addAddress"
|
||||
/>
|
||||
<MalioButton
|
||||
button-class="w-auto px-6"
|
||||
:label="$t('common.save')"
|
||||
:disabled="savingAddresses"
|
||||
@click="saveAddresses"
|
||||
@@ -130,6 +143,13 @@
|
||||
</MalioTabList>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-model="removeModalOpen"
|
||||
:title="removeModalTitle"
|
||||
:message="removeModalMessage"
|
||||
@confirm="confirmRemove"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -156,13 +176,17 @@ const {
|
||||
savingAddresses,
|
||||
onContactInput,
|
||||
addContact,
|
||||
removeContact,
|
||||
askRemoveContact,
|
||||
saveContacts,
|
||||
onAddressInput,
|
||||
addAddress,
|
||||
removeAddress,
|
||||
askRemoveAddress,
|
||||
saveAddresses,
|
||||
load,
|
||||
removeModalOpen,
|
||||
removeModalTitle,
|
||||
removeModalMessage,
|
||||
confirmRemove,
|
||||
} = useDirectoryDetail(owner)
|
||||
|
||||
const { can } = usePermissions()
|
||||
@@ -226,7 +250,8 @@ async function saveInfo(): Promise<void> {
|
||||
}
|
||||
|
||||
function goBack(): void {
|
||||
router.push('/directory')
|
||||
// Retour sur l'onglet Prospects de la liste (via history.state, hors URL).
|
||||
router.push({ path: '/directory', state: { tab: 'prospects' } })
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in New Issue
Block a user