Initial commit
This commit is contained in:
399
app/(main)/gestionnaire/dashboard/page.tsx
Normal file
399
app/(main)/gestionnaire/dashboard/page.tsx
Normal file
@@ -0,0 +1,399 @@
|
||||
'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;
|
||||
Reference in New Issue
Block a user