379 lines
15 KiB
TypeScript
379 lines
15 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { Button } from 'primereact/button';
|
|
import { Card } from 'primereact/card';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
import { Dialog } from 'primereact/dialog';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { MultiSelect } from 'primereact/multiselect';
|
|
import { Tag } from 'primereact/tag';
|
|
import { Divider } from 'primereact/divider';
|
|
import clientService from '../../../../services/clientService';
|
|
import userService from '../../../../services/userService';
|
|
import type { Client } from '../../../../types/btp';
|
|
import type { User } from '../../../../types/auth';
|
|
|
|
interface ClientGestionnaire {
|
|
client: Client;
|
|
gestionnairePrincipal?: User;
|
|
gestionnairesSecondaires: User[];
|
|
}
|
|
|
|
const AttributionsPage = () => {
|
|
const [clients, setClients] = useState<Client[]>([]);
|
|
const [gestionnaires, setGestionnaires] = useState<User[]>([]);
|
|
const [attributions, setAttributions] = useState<ClientGestionnaire[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [attributionDialog, setAttributionDialog] = useState(false);
|
|
const [selectedClient, setSelectedClient] = useState<Client | null>(null);
|
|
const [selectedGestionnairePrincipal, setSelectedGestionnairePrincipal] = useState<User | null>(null);
|
|
const [selectedGestionnairesSecondaires, setSelectedGestionnairesSecondaires] = useState<User[]>([]);
|
|
const toast = useRef<Toast>(null);
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Charger les clients
|
|
const clientsData = await clientService.getAll();
|
|
setClients(clientsData);
|
|
|
|
// Charger les gestionnaires depuis le service
|
|
const gestionnairesData = await userService.getGestionnaires();
|
|
setGestionnaires(gestionnairesData);
|
|
|
|
// Construire les attributions
|
|
const attributionsData = clientsData.map(client => ({
|
|
client,
|
|
gestionnairePrincipal: gestionnairesData.find(g => g.id === client.gestionnairePrincipalId),
|
|
gestionnairesSecondaires: gestionnairesData.filter(g =>
|
|
client.gestionnairesSecondaires?.includes(g.id)
|
|
)
|
|
}));
|
|
setAttributions(attributionsData);
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement des données:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger les données',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const openAttributionDialog = (client: Client) => {
|
|
setSelectedClient(client);
|
|
|
|
// Pré-remplir les sélections actuelles
|
|
const gestionnairePrincipal = gestionnaires.find(g => g.id === client.gestionnairePrincipalId);
|
|
setSelectedGestionnairePrincipal(gestionnairePrincipal || null);
|
|
|
|
const gestionnairesSecondaires = gestionnaires.filter(g =>
|
|
client.gestionnairesSecondaires?.includes(g.id)
|
|
);
|
|
setSelectedGestionnairesSecondaires(gestionnairesSecondaires);
|
|
|
|
setAttributionDialog(true);
|
|
};
|
|
|
|
const saveAttribution = async () => {
|
|
if (!selectedClient) return;
|
|
|
|
try {
|
|
const updatedClient = {
|
|
...selectedClient,
|
|
gestionnairePrincipalId: selectedGestionnairePrincipal?.id,
|
|
gestionnairesSecondaires: selectedGestionnairesSecondaires.map(g => g.id)
|
|
};
|
|
|
|
// Mise à jour côté serveur
|
|
await clientService.update(selectedClient.id, updatedClient);
|
|
|
|
// Mettre à jour localement
|
|
const updatedClients = clients.map(c =>
|
|
c.id === selectedClient.id ? updatedClient : c
|
|
);
|
|
setClients(updatedClients);
|
|
|
|
// Mettre à jour les attributions
|
|
const updatedAttributions = attributions.map(a =>
|
|
a.client.id === selectedClient.id
|
|
? {
|
|
...a,
|
|
client: updatedClient,
|
|
gestionnairePrincipal: selectedGestionnairePrincipal || undefined,
|
|
gestionnairesSecondaires: selectedGestionnairesSecondaires
|
|
}
|
|
: a
|
|
);
|
|
setAttributions(updatedAttributions);
|
|
|
|
setAttributionDialog(false);
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: 'Attribution mise à jour',
|
|
life: 3000
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de la sauvegarde:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de sauvegarder l\'attribution',
|
|
life: 3000
|
|
});
|
|
}
|
|
};
|
|
|
|
const hideDialog = () => {
|
|
setAttributionDialog(false);
|
|
setSelectedClient(null);
|
|
setSelectedGestionnairePrincipal(null);
|
|
setSelectedGestionnairesSecondaires([]);
|
|
};
|
|
|
|
const leftToolbarTemplate = () => {
|
|
return (
|
|
<div className="my-2">
|
|
<Button
|
|
label="Actualiser"
|
|
icon="pi pi-refresh"
|
|
className="mr-2"
|
|
onClick={loadData}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const rightToolbarTemplate = () => {
|
|
return (
|
|
<div className="flex align-items-center">
|
|
<span className="text-color-secondary mr-2">
|
|
{attributions.filter(a => a.gestionnairePrincipal).length} / {attributions.length} clients attribués
|
|
</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const actionBodyTemplate = (rowData: ClientGestionnaire) => {
|
|
return (
|
|
<Button
|
|
icon="pi pi-pencil"
|
|
className="p-button-rounded p-button-text p-button-info"
|
|
tooltip="Attribuer"
|
|
onClick={() => openAttributionDialog(rowData.client)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const clientBodyTemplate = (rowData: ClientGestionnaire) => {
|
|
return (
|
|
<div className="flex flex-column">
|
|
<span className="font-medium">{`${rowData.client.prenom} ${rowData.client.nom}`}</span>
|
|
{rowData.client.entreprise && (
|
|
<span className="text-sm text-color-secondary">{rowData.client.entreprise}</span>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const gestionnairePrincipalBodyTemplate = (rowData: ClientGestionnaire) => {
|
|
if (!rowData.gestionnairePrincipal) {
|
|
return (
|
|
<Tag
|
|
value="Non attribué"
|
|
severity="danger"
|
|
className="text-sm"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-column">
|
|
<span className="font-medium">
|
|
{`${rowData.gestionnairePrincipal.prenom} ${rowData.gestionnairePrincipal.nom}`}
|
|
</span>
|
|
<span className="text-sm text-color-secondary">
|
|
{rowData.gestionnairePrincipal.email}
|
|
</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const gestionnairesSecondairesBodyTemplate = (rowData: ClientGestionnaire) => {
|
|
if (rowData.gestionnairesSecondaires.length === 0) {
|
|
return <span className="text-color-secondary">Aucun</span>;
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-column gap-1">
|
|
{rowData.gestionnairesSecondaires.map(gestionnaire => (
|
|
<Tag
|
|
key={gestionnaire.id}
|
|
value={`${gestionnaire.prenom} ${gestionnaire.nom}`}
|
|
severity="info"
|
|
className="text-sm"
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const header = (
|
|
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
|
|
<h5 className="m-0">Attribution des Clients aux Gestionnaires</h5>
|
|
</div>
|
|
);
|
|
|
|
const attributionDialogFooter = (
|
|
<>
|
|
<Button
|
|
label="Annuler"
|
|
icon="pi pi-times"
|
|
text
|
|
onClick={hideDialog}
|
|
/>
|
|
<Button
|
|
label="Sauvegarder"
|
|
icon="pi pi-check"
|
|
text
|
|
onClick={saveAttribution}
|
|
/>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Card>
|
|
<Toast ref={toast} />
|
|
<Toolbar
|
|
className="mb-4"
|
|
left={leftToolbarTemplate}
|
|
right={rightToolbarTemplate}
|
|
/>
|
|
|
|
<DataTable
|
|
value={attributions}
|
|
dataKey="client.id"
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[5, 10, 25]}
|
|
className="datatable-responsive"
|
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
|
currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} attributions"
|
|
emptyMessage="Aucune attribution trouvée."
|
|
header={header}
|
|
responsiveLayout="scroll"
|
|
loading={loading}
|
|
>
|
|
<Column
|
|
header="Client"
|
|
body={clientBodyTemplate}
|
|
sortable
|
|
headerStyle={{ minWidth: '15rem' }}
|
|
/>
|
|
<Column
|
|
header="Gestionnaire Principal"
|
|
body={gestionnairePrincipalBodyTemplate}
|
|
headerStyle={{ minWidth: '15rem' }}
|
|
/>
|
|
<Column
|
|
header="Gestionnaires Secondaires"
|
|
body={gestionnairesSecondairesBodyTemplate}
|
|
headerStyle={{ minWidth: '12rem' }}
|
|
/>
|
|
<Column
|
|
body={actionBodyTemplate}
|
|
headerStyle={{ minWidth: '8rem' }}
|
|
/>
|
|
</DataTable>
|
|
|
|
<Dialog
|
|
visible={attributionDialog}
|
|
style={{ width: '600px' }}
|
|
header="Attribution des Gestionnaires"
|
|
modal
|
|
className="p-fluid"
|
|
footer={attributionDialogFooter}
|
|
onHide={hideDialog}
|
|
>
|
|
{selectedClient && (
|
|
<div>
|
|
<div className="field mb-4">
|
|
<h6 className="text-primary">Client</h6>
|
|
<div className="p-3 surface-100 border-round">
|
|
<div className="font-medium text-lg">
|
|
{`${selectedClient.prenom} ${selectedClient.nom}`}
|
|
</div>
|
|
{selectedClient.entreprise && (
|
|
<div className="text-color-secondary">
|
|
{selectedClient.entreprise}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<Divider />
|
|
|
|
<div className="field">
|
|
<label htmlFor="gestionnairePrincipal">
|
|
<strong>Gestionnaire Principal</strong>
|
|
</label>
|
|
<Dropdown
|
|
id="gestionnairePrincipal"
|
|
value={selectedGestionnairePrincipal}
|
|
options={gestionnaires}
|
|
onChange={(e) => setSelectedGestionnairePrincipal(e.value)}
|
|
optionLabel="nom"`n itemTemplate={(option) => option ? `${option.prenom} ${option.nom}` : ''}
|
|
placeholder="Sélectionnez un gestionnaire principal"
|
|
showClear
|
|
className="w-full"
|
|
/>
|
|
<small className="text-color-secondary">
|
|
Le gestionnaire principal est responsable de la relation client
|
|
</small>
|
|
</div>
|
|
|
|
<div className="field">
|
|
<label htmlFor="gestionnairesSecondaires">
|
|
<strong>Gestionnaires Secondaires</strong>
|
|
</label>
|
|
<MultiSelect
|
|
id="gestionnairesSecondaires"
|
|
value={selectedGestionnairesSecondaires}
|
|
options={gestionnaires.filter(g => g.id !== selectedGestionnairePrincipal?.id)}
|
|
onChange={(e) => setSelectedGestionnairesSecondaires(e.value)}
|
|
optionLabel="nom"`n itemTemplate={(option) => option ? `${option.prenom} ${option.nom}` : ''}
|
|
placeholder="Sélectionnez des gestionnaires secondaires"
|
|
className="w-full"
|
|
maxSelectedLabels={3}
|
|
/>
|
|
<small className="text-color-secondary">
|
|
Les gestionnaires secondaires peuvent consulter et collaborer sur les projets
|
|
</small>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Dialog>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AttributionsPage; |