399 lines
16 KiB
TypeScript
399 lines
16 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 { Tag } from 'primereact/tag';
|
|
import { Button } from 'primereact/button';
|
|
import { ProgressBar } from 'primereact/progressbar';
|
|
import { Chart } from 'primereact/chart';
|
|
import type { Client, Chantier } from '../../../../types/btp';
|
|
import type { User } from '../../../../types/auth';
|
|
import clientService from '../../../../services/clientService';
|
|
import chantierService from '../../../../services/chantierService';
|
|
import { useAuth } from '../../../../contexts/AuthContext';
|
|
|
|
interface GestionnaireStats {
|
|
clientsAttribues: number;
|
|
chantiersEnCours: number;
|
|
chantiersTermines: number;
|
|
chiffreAffairesTotal: number;
|
|
chiffreAffairesMois: number;
|
|
}
|
|
|
|
const GestionnaireDashboard = () => {
|
|
const [stats, setStats] = useState<GestionnaireStats>({
|
|
clientsAttribues: 0,
|
|
chantiersEnCours: 0,
|
|
chantiersTermines: 0,
|
|
chiffreAffairesTotal: 0,
|
|
chiffreAffairesMois: 0
|
|
});
|
|
const [clientsAttribues, setClientsAttribues] = useState<Client[]>([]);
|
|
const [chantiersRecents, setChantiersRecents] = useState<Chantier[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const { user: currentUser } = useAuth();
|
|
|
|
// Vérifier que l'utilisateur est connecté et est un gestionnaire
|
|
if (!currentUser || currentUser.role !== 'GESTIONNAIRE_PROJET') {
|
|
return (
|
|
<div className="flex align-items-center justify-content-center min-h-screen">
|
|
<div className="text-center">
|
|
<i className="pi pi-lock text-6xl text-color-secondary mb-3"></i>
|
|
<h3 className="text-color">Accès non autorisé</h3>
|
|
<p className="text-color-secondary">Vous devez être connecté en tant que gestionnaire de projet.</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadDashboardData();
|
|
}, []);
|
|
|
|
const loadDashboardData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Charger tous les clients pour filtrer ceux attribués
|
|
const allClients = await clientService.getAll();
|
|
const myClients = allClients.filter(client =>
|
|
client.gestionnairePrincipalId === currentUser.id ||
|
|
client.gestionnairesSecondaires?.includes(currentUser.id)
|
|
);
|
|
setClientsAttribues(myClients);
|
|
|
|
// Charger tous les chantiers pour filtrer ceux des clients attribués
|
|
const allChantiers = await chantierService.getAll();
|
|
const myChantiers = allChantiers.filter(chantier =>
|
|
myClients.some(client => client.id === chantier.client?.id)
|
|
);
|
|
|
|
// Prendre les 5 chantiers les plus récents
|
|
const recentChantiers = myChantiers
|
|
.sort((a, b) => new Date(b.dateCreation).getTime() - new Date(a.dateCreation).getTime())
|
|
.slice(0, 5);
|
|
setChantiersRecents(recentChantiers);
|
|
|
|
// Calculer les statistiques
|
|
const chantiersEnCours = myChantiers.filter(c => c.statut === 'EN_COURS').length;
|
|
const chantiersTermines = myChantiers.filter(c => c.statut === 'TERMINE').length;
|
|
const chiffreAffairesTotal = myChantiers.reduce((sum, c) => sum + (c.montantPrevu || 0), 0);
|
|
|
|
// CA du mois en cours
|
|
const currentMonth = new Date().getMonth();
|
|
const currentYear = new Date().getFullYear();
|
|
const chiffreAffairesMois = myChantiers
|
|
.filter(c => {
|
|
const dateDebut = new Date(c.dateDebut);
|
|
return dateDebut.getMonth() === currentMonth && dateDebut.getFullYear() === currentYear;
|
|
})
|
|
.reduce((sum, c) => sum + (c.montantPrevu || 0), 0);
|
|
|
|
setStats({
|
|
clientsAttribues: myClients.length,
|
|
chantiersEnCours,
|
|
chantiersTermines,
|
|
chiffreAffairesTotal,
|
|
chiffreAffairesMois
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement du dashboard gestionnaire:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const clientBodyTemplate = (rowData: Client) => {
|
|
return (
|
|
<div className="flex flex-column">
|
|
<span className="font-medium">{`${rowData.prenom} ${rowData.nom}`}</span>
|
|
{rowData.entreprise && (
|
|
<span className="text-sm text-color-secondary">{rowData.entreprise}</span>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const contactBodyTemplate = (rowData: Client) => {
|
|
return (
|
|
<div className="flex flex-column">
|
|
{rowData.email && (
|
|
<div className="flex align-items-center mb-1">
|
|
<i className="pi pi-envelope mr-2 text-color-secondary text-sm"></i>
|
|
<span className="text-sm">{rowData.email}</span>
|
|
</div>
|
|
)}
|
|
{rowData.telephone && (
|
|
<div className="flex align-items-center">
|
|
<i className="pi pi-phone mr-2 text-color-secondary text-sm"></i>
|
|
<span className="text-sm">{rowData.telephone}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const chantierStatutBodyTemplate = (rowData: Chantier) => {
|
|
return (
|
|
<Tag
|
|
value={chantierService.getStatutLabel(rowData.statut)}
|
|
style={{
|
|
backgroundColor: chantierService.getStatutColor(rowData.statut),
|
|
color: 'white'
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const montantBodyTemplate = (rowData: Chantier) => {
|
|
return new Intl.NumberFormat('fr-FR', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
}).format(rowData.montantPrevu || 0);
|
|
};
|
|
|
|
const avancementBodyTemplate = (rowData: Chantier) => {
|
|
const avancement = chantierService.calculateAvancement(rowData);
|
|
return (
|
|
<div className="flex align-items-center">
|
|
<ProgressBar
|
|
value={avancement}
|
|
className="w-8 mr-2"
|
|
style={{ height: '8px' }}
|
|
/>
|
|
<span className="text-sm font-medium">{Math.round(avancement)}%</span>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Données pour le graphique des chantiers par statut
|
|
const chartData = {
|
|
labels: ['En cours', 'Terminés', 'Planifiés'],
|
|
datasets: [
|
|
{
|
|
data: [stats.chantiersEnCours, stats.chantiersTermines, 0],
|
|
backgroundColor: ['#42A5F5', '#66BB6A', '#FFA726'],
|
|
borderWidth: 0
|
|
}
|
|
]
|
|
};
|
|
|
|
const chartOptions = {
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom' as const
|
|
}
|
|
},
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
};
|
|
|
|
return (
|
|
<div className="grid">
|
|
{/* Header */}
|
|
<div className="col-12">
|
|
<div className="flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2 className="text-primary m-0">Dashboard Gestionnaire</h2>
|
|
<p className="text-color-secondary m-0">
|
|
Bienvenue {currentUser.prenom} {currentUser.nom}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Statistiques principales */}
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between align-items-start">
|
|
<div>
|
|
<div className="text-2xl font-bold text-primary">
|
|
{stats.clientsAttribues}
|
|
</div>
|
|
<div className="text-color-secondary font-medium">Clients attribués</div>
|
|
</div>
|
|
<div className="border-round-md bg-blue-100 p-2">
|
|
<i className="pi pi-users text-blue-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between align-items-start">
|
|
<div>
|
|
<div className="text-2xl font-bold text-green-500">
|
|
{stats.chantiersEnCours}
|
|
</div>
|
|
<div className="text-color-secondary font-medium">Chantiers en cours</div>
|
|
</div>
|
|
<div className="border-round-md bg-green-100 p-2">
|
|
<i className="pi pi-cog text-green-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between align-items-start">
|
|
<div>
|
|
<div className="text-2xl font-bold text-purple-500">
|
|
{stats.chantiersTermines}
|
|
</div>
|
|
<div className="text-color-secondary font-medium">Chantiers terminés</div>
|
|
</div>
|
|
<div className="border-round-md bg-purple-100 p-2">
|
|
<i className="pi pi-check-circle text-purple-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="h-full">
|
|
<div className="flex justify-content-between align-items-start">
|
|
<div>
|
|
<div className="text-2xl font-bold text-cyan-500">
|
|
{new Intl.NumberFormat('fr-FR', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
notation: 'compact'
|
|
}).format(stats.chiffreAffairesTotal)}
|
|
</div>
|
|
<div className="text-color-secondary font-medium">CA Total</div>
|
|
</div>
|
|
<div className="border-round-md bg-cyan-100 p-2">
|
|
<i className="pi pi-euro text-cyan-500 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Graphique et clients */}
|
|
<div className="col-12 lg:col-4">
|
|
<Card title="Répartition des chantiers" className="h-full">
|
|
<div style={{ height: '300px' }}>
|
|
<Chart
|
|
type="doughnut"
|
|
data={chartData}
|
|
options={chartOptions}
|
|
height="300px"
|
|
/>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-8">
|
|
<Card title="Mes clients" className="h-full">
|
|
<DataTable
|
|
value={clientsAttribues}
|
|
paginator
|
|
rows={5}
|
|
dataKey="id"
|
|
emptyMessage="Aucun client attribué"
|
|
loading={loading}
|
|
>
|
|
<Column
|
|
header="Client"
|
|
body={clientBodyTemplate}
|
|
style={{ minWidth: '12rem' }}
|
|
/>
|
|
<Column
|
|
header="Contact"
|
|
body={contactBodyTemplate}
|
|
style={{ minWidth: '12rem' }}
|
|
/>
|
|
<Column
|
|
field="ville"
|
|
header="Ville"
|
|
style={{ minWidth: '8rem' }}
|
|
/>
|
|
<Column
|
|
body={(rowData) => (
|
|
<Button
|
|
icon="pi pi-eye"
|
|
className="p-button-rounded p-button-text p-button-info"
|
|
tooltip="Voir les chantiers"
|
|
/>
|
|
)}
|
|
style={{ minWidth: '4rem' }}
|
|
/>
|
|
</DataTable>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Chantiers récents */}
|
|
<div className="col-12">
|
|
<Card title="Chantiers récents">
|
|
<DataTable
|
|
value={chantiersRecents}
|
|
paginator
|
|
rows={10}
|
|
dataKey="id"
|
|
emptyMessage="Aucun chantier trouvé"
|
|
loading={loading}
|
|
>
|
|
<Column
|
|
field="nom"
|
|
header="Chantier"
|
|
style={{ minWidth: '12rem' }}
|
|
/>
|
|
<Column
|
|
header="Client"
|
|
body={(rowData) =>
|
|
rowData.client ? `${rowData.client.prenom} ${rowData.client.nom}` : ''
|
|
}
|
|
style={{ minWidth: '12rem' }}
|
|
/>
|
|
<Column
|
|
header="Statut"
|
|
body={chantierStatutBodyTemplate}
|
|
style={{ minWidth: '8rem' }}
|
|
/>
|
|
<Column
|
|
field="dateDebut"
|
|
header="Date début"
|
|
body={(rowData) => new Date(rowData.dateDebut).toLocaleDateString('fr-FR')}
|
|
style={{ minWidth: '10rem' }}
|
|
/>
|
|
<Column
|
|
header="Montant"
|
|
body={montantBodyTemplate}
|
|
style={{ minWidth: '8rem', textAlign: 'right' }}
|
|
/>
|
|
<Column
|
|
header="Avancement"
|
|
body={avancementBodyTemplate}
|
|
style={{ minWidth: '10rem' }}
|
|
/>
|
|
<Column
|
|
body={(rowData) => (
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-eye"
|
|
className="p-button-rounded p-button-text p-button-info"
|
|
tooltip="Voir le détail"
|
|
/>
|
|
<Button
|
|
icon="pi pi-pencil"
|
|
className="p-button-rounded p-button-text p-button-warning"
|
|
tooltip="Modifier"
|
|
/>
|
|
</div>
|
|
)}
|
|
style={{ minWidth: '8rem' }}
|
|
/>
|
|
</DataTable>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default GestionnaireDashboard; |