Files
btpxpress-frontend/app/(main)/clients/historique/page.tsx
2025-10-01 01:39:07 +00:00

397 lines
17 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { Card } from 'primereact/card';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { InputText } from 'primereact/inputtext';
import { Calendar } from 'primereact/calendar';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown';
import { Divider } from 'primereact/divider';
import clientService from '../../../../services/clientService';
/**
* Page Historique Clients - BTP Express
* Suivi complet de l'activité clients avec métriques BTP
*/
const HistoriqueClientsPage = () => {
const [activites, setActivites] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [globalFilter, setGlobalFilter] = useState('');
const [selectedClient, setSelectedClient] = useState<any>(null);
const [dateDebut, setDateDebut] = useState<Date | null>(null);
const [dateFin, setDateFin] = useState<Date | null>(null);
const [typeActivite, setTypeActivite] = useState<string>('');
const [metriques, setMetriques] = useState({
nouveauxClients: 0,
chantiersTermines: 0,
caTotal: 0,
relancesEnvoyees: 0
});
const typesActivite = [
{ label: 'Toutes', value: '' },
{ label: 'Création client', value: 'CLIENT_CREATION' },
{ label: 'Modification client', value: 'CLIENT_UPDATE' },
{ label: 'Nouveau chantier', value: 'CHANTIER_CREATION' },
{ label: 'Chantier terminé', value: 'CHANTIER_TERMINE' },
{ label: 'Devis envoyé', value: 'DEVIS_ENVOYE' },
{ label: 'Devis accepté', value: 'DEVIS_ACCEPTE' },
{ label: 'Facture émise', value: 'FACTURE_EMISE' },
{ label: 'Paiement reçu', value: 'PAIEMENT_RECU' },
{ label: 'Relance envoyée', value: 'RELANCE_ENVOYEE' }
];
useEffect(() => {
loadHistorique();
loadMetriques();
}, [selectedClient, dateDebut, dateFin, typeActivite]);
const loadHistorique = async () => {
try {
setLoading(true);
// Simulation données historique BTP
const mockActivites = [
{
id: '1',
date: new Date('2025-01-30'),
type: 'CHANTIER_CREATION',
client: { nom: 'Dupont', prenom: 'Jean', entreprise: 'Maçonnerie Dupont' },
description: 'Nouveau chantier: Rénovation villa 150m²',
montant: 45000,
statut: 'EN_COURS',
utilisateur: 'M. Laurent (Chef de projet)'
},
{
id: '2',
date: new Date('2025-01-29'),
type: 'DEVIS_ACCEPTE',
client: { nom: 'Martin', prenom: 'Sophie', entreprise: 'Construction Martin' },
description: 'Devis DEV-2025-001 accepté - Extension maison',
montant: 28500,
statut: 'ACCEPTE',
utilisateur: 'Mme Petit (Commerciale)'
},
{
id: '3',
date: new Date('2025-01-28'),
type: 'FACTURE_EMISE',
client: { nom: 'Bernard', prenom: 'Michel', entreprise: null },
description: 'Facture FAC-2025-012 émise - Acompte 30%',
montant: 15600,
statut: 'ENVOYEE',
utilisateur: 'Mme Durand (Comptabilité)'
},
{
id: '4',
date: new Date('2025-01-27'),
type: 'CHANTIER_TERMINE',
client: { nom: 'Rousseau', prenom: 'Claire', entreprise: 'Pharmacie Rousseau' },
description: 'Chantier terminé: Aménagement officine 80m²',
montant: 32000,
statut: 'TERMINE',
utilisateur: 'M. Thomas (Chef équipe)'
},
{
id: '5',
date: new Date('2025-01-26'),
type: 'CLIENT_CREATION',
client: { nom: 'Leroy', prenom: 'Antoine', entreprise: 'Boulangerie Leroy' },
description: 'Nouveau client créé - Secteur alimentaire',
montant: 0,
statut: 'ACTIF',
utilisateur: 'Mme Petit (Commerciale)'
},
{
id: '6',
date: new Date('2025-01-25'),
type: 'PAIEMENT_RECU',
client: { nom: 'Garcia', prenom: 'Maria', entreprise: 'Restaurant Garcia' },
description: 'Paiement reçu - Solde chantier cuisine pro',
montant: 18750,
statut: 'PAYE',
utilisateur: 'Système (Automatique)'
}
];
setActivites(mockActivites);
} catch (error) {
console.error('Erreur chargement historique:', error);
} finally {
setLoading(false);
}
};
const loadMetriques = () => {
// Simulation métriques période
setMetriques({
nouveauxClients: 8,
chantiersTermines: 12,
caTotal: 185600,
relancesEnvoyees: 3
});
};
const typeActiviteBodyTemplate = (rowData: any) => {
const getConfig = (type: string) => {
switch (type) {
case 'CLIENT_CREATION': return { label: 'Nouveau client', severity: 'info', icon: 'pi-user-plus' };
case 'CLIENT_UPDATE': return { label: 'Client modifié', severity: 'warning', icon: 'pi-user-edit' };
case 'CHANTIER_CREATION': return { label: 'Nouveau chantier', severity: 'success', icon: 'pi-map' };
case 'CHANTIER_TERMINE': return { label: 'Chantier terminé', severity: 'success', icon: 'pi-check-circle' };
case 'DEVIS_ENVOYE': return { label: 'Devis envoyé', severity: 'info', icon: 'pi-send' };
case 'DEVIS_ACCEPTE': return { label: 'Devis accepté', severity: 'success', icon: 'pi-thumbs-up' };
case 'FACTURE_EMISE': return { label: 'Facture émise', severity: 'warning', icon: 'pi-file-excel' };
case 'PAIEMENT_RECU': return { label: 'Paiement reçu', severity: 'success', icon: 'pi-euro' };
case 'RELANCE_ENVOYEE': return { label: 'Relance envoyée', severity: 'danger', icon: 'pi-exclamation-triangle' };
default: return { label: type, severity: 'secondary', icon: 'pi-info-circle' };
}
};
const config = getConfig(rowData.type);
return (
<div className="flex align-items-center">
<i className={`pi ${config.icon} mr-2`} />
<Tag value={config.label} severity={config.severity} />
</div>
);
};
const clientBodyTemplate = (rowData: any) => {
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 montantBodyTemplate = (rowData: any) => {
if (rowData.montant === 0) return <span className="text-color-secondary">-</span>;
return (
<span className={rowData.montant > 0 ? 'text-green-500 font-medium' : 'text-red-500 font-medium'}>
{new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR'
}).format(rowData.montant)}
</span>
);
};
const dateBodyTemplate = (rowData: any) => {
return (
<div className="flex flex-column">
<span>{rowData.date.toLocaleDateString('fr-FR')}</span>
<span className="text-sm text-color-secondary">
{rowData.date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
</span>
</div>
);
};
const utilisateurBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center">
<i className="pi pi-user mr-2 text-color-secondary" />
<span className="text-sm">{rowData.utilisateur}</span>
</div>
);
};
const header = (
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center gap-3">
<h5 className="m-0">Historique d'Activité Clients</h5>
<div className="flex flex-wrap gap-2">
<span className="p-input-icon-left">
<i className="pi pi-search" />
<InputText
type="search"
placeholder="Rechercher..."
onInput={(e) => setGlobalFilter(e.currentTarget.value)}
style={{ width: '250px' }}
/>
</span>
<Calendar
placeholder="Date début"
value={dateDebut}
onChange={(e) => setDateDebut(e.value || null)}
dateFormat="dd/mm/yy"
showIcon
style={{ width: '150px' }}
/>
<Calendar
placeholder="Date fin"
value={dateFin}
onChange={(e) => setDateFin(e.value || null)}
dateFormat="dd/mm/yy"
showIcon
style={{ width: '150px' }}
/>
<Dropdown
value={typeActivite}
options={typesActivite}
onChange={(e) => setTypeActivite(e.value)}
placeholder="Type d'activité"
style={{ width: '180px' }}
/>
<Button
icon="pi pi-filter-slash"
className="p-button-outlined"
onClick={() => {
setDateDebut(null);
setDateFin(null);
setTypeActivite('');
setSelectedClient(null);
setGlobalFilter('');
}}
tooltip="Réinitialiser les filtres"
/>
</div>
</div>
);
return (
<div className="grid">
{/* Métriques de la période */}
<div className="col-12">
<div className="grid">
<div className="col-12 md:col-3">
<Card>
<div className="flex justify-content-between align-items-center">
<div>
<div className="text-2xl font-bold text-blue-500">{metriques.nouveauxClients}</div>
<div className="text-color-secondary">Nouveaux clients</div>
</div>
<i className="pi pi-user-plus text-blue-500 text-3xl" />
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex justify-content-between align-items-center">
<div>
<div className="text-2xl font-bold text-green-500">{metriques.chantiersTermines}</div>
<div className="text-color-secondary">Chantiers terminés</div>
</div>
<i className="pi pi-check-circle text-green-500 text-3xl" />
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex justify-content-between align-items-center">
<div>
<div className="text-2xl font-bold text-cyan-500">
{new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR',
notation: 'compact'
}).format(metriques.caTotal)}
</div>
<div className="text-color-secondary">CA généré</div>
</div>
<i className="pi pi-euro text-cyan-500 text-3xl" />
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex justify-content-between align-items-center">
<div>
<div className="text-2xl font-bold text-orange-500">{metriques.relancesEnvoyees}</div>
<div className="text-color-secondary">Relances envoyées</div>
</div>
<i className="pi pi-exclamation-triangle text-orange-500 text-3xl" />
</div>
</Card>
</div>
</div>
</div>
<div className="col-12">
<Divider />
</div>
{/* Tableau historique */}
<div className="col-12">
<Card>
<DataTable
value={activites}
paginator
rows={20}
rowsPerPageOptions={[10, 20, 50]}
className="datatable-responsive"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} activités"
globalFilter={globalFilter}
emptyMessage="Aucune activité trouvée."
header={header}
responsiveLayout="scroll"
loading={loading}
sortField="date"
sortOrder={-1}
>
<Column
field="date"
header="Date & Heure"
body={dateBodyTemplate}
sortable
style={{ minWidth: '120px' }}
/>
<Column
field="type"
header="Type d'activité"
body={typeActiviteBodyTemplate}
sortable
style={{ minWidth: '180px' }}
/>
<Column
header="Client"
body={clientBodyTemplate}
style={{ minWidth: '200px' }}
/>
<Column
field="description"
header="Description"
style={{ minWidth: '300px' }}
/>
<Column
field="montant"
header="Montant"
body={montantBodyTemplate}
sortable
style={{ minWidth: '120px' }}
/>
<Column
field="utilisateur"
header="Utilisateur"
body={utilisateurBodyTemplate}
style={{ minWidth: '180px' }}
/>
</DataTable>
</Card>
</div>
</div>
);
};
export default HistoriqueClientsPage;