57ccd9a740
LST-58 (2.4) front. Completes the Directory module.
- New frontend/modules/directory/ layer (auto-detected): /directory page with
Clients and Prospects tabs.
- Client front moved into the layer (clients service + client DTO +
ClientDrawer). New prospects service, prospect DTO and ProspectDrawer (with
a "Convert to client" action calling POST /prospects/{id}/convert).
- Consumers repointed to ~/modules/directory/... (admin client tab, PM project
drawer + project pages + project DTO, time-tracking page + export drawer).
- Sidebar admin item /directory gated by the directory module; /directory
protected by the admin middleware. i18n keys added (directory.*, prospects.*).
nuxt build passes; routes preserved.
Adds the 2.4 plan doc.
99 lines
2.6 KiB
Vue
99 lines
2.6 KiB
Vue
<template>
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-lg font-bold text-neutral-900">Clients</h2>
|
|
<MalioButton
|
|
icon-name="mdi:plus"
|
|
icon-position="left"
|
|
button-class="w-auto px-4"
|
|
label="Ajouter un client"
|
|
@click="openCreate"
|
|
/>
|
|
</div>
|
|
|
|
<DataTable
|
|
:columns="columns"
|
|
:items="clients"
|
|
:loading="isLoading"
|
|
empty-message="Aucun client trouvé."
|
|
deletable
|
|
@row-click="openEdit"
|
|
@delete="(item) => handleDelete(item.id)"
|
|
>
|
|
<template #cell-email="{ item }">
|
|
{{ item.email ?? '-' }}
|
|
</template>
|
|
<template #cell-address="{ item }">
|
|
{{ formatAddress(item) }}
|
|
</template>
|
|
<template #cell-phone="{ item }">
|
|
{{ item.phone ?? '-' }}
|
|
</template>
|
|
</DataTable>
|
|
|
|
<ClientDrawer
|
|
v-model="drawerOpen"
|
|
:client="selectedClient"
|
|
@saved="onSaved"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Client } from '~/modules/directory/services/dto/client'
|
|
import { useClientService } from '~/modules/directory/services/clients'
|
|
|
|
import type { DataTableColumn } from '~/components/ui/DataTable.vue'
|
|
|
|
const columns: DataTableColumn[] = [
|
|
{ key: 'name', label: 'Nom', primary: true },
|
|
{ key: 'email', label: 'Email', class: 'text-primary-500' },
|
|
{ key: 'address', label: 'Adresse', class: 'text-neutral-700' },
|
|
{ key: 'phone', label: 'Téléphone', class: 'text-primary-500' },
|
|
]
|
|
|
|
const { getAll, remove } = useClientService()
|
|
const clients = ref<Client[]>([])
|
|
const isLoading = ref(true)
|
|
const drawerOpen = ref(false)
|
|
const selectedClient = ref<Client | null>(null)
|
|
|
|
async function loadClients() {
|
|
isLoading.value = true
|
|
try {
|
|
clients.value = await getAll()
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
function openCreate() {
|
|
selectedClient.value = null
|
|
drawerOpen.value = true
|
|
}
|
|
|
|
function openEdit(client: Client) {
|
|
selectedClient.value = client
|
|
drawerOpen.value = true
|
|
}
|
|
|
|
function formatAddress(client: Client): string {
|
|
return [client.street, client.postalCode, client.city]
|
|
.filter(Boolean)
|
|
.join(', ') || '-'
|
|
}
|
|
|
|
async function handleDelete(id: number) {
|
|
await remove(id)
|
|
await loadClients()
|
|
}
|
|
|
|
async function onSaved() {
|
|
await loadClients()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadClients()
|
|
})
|
|
</script>
|