Initial commit

This commit is contained in:
dahoud
2025-10-01 01:39:07 +00:00
commit b430bf3b96
826 changed files with 255287 additions and 0 deletions

View 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;