'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { Card } from 'primereact/card'; import { Chart } from 'primereact/chart'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Tag } from 'primereact/tag'; import { ProgressBar } from 'primereact/progressbar'; import { Button } from 'primereact/button'; import { Toast } from 'primereact/toast'; import { Dropdown } from 'primereact/dropdown'; import { Calendar } from 'primereact/calendar'; import { Badge } from 'primereact/badge'; import { Knob } from 'primereact/knob'; import { useRouter } from 'next/navigation'; interface RessourceDashboard { id: string; nom: string; type: string; categorie: string; statut: string; disponibilite: number; utilisation: number; localisation: string; chantierActuel?: string; dateFinUtilisation?: Date; coutJournalier: number; revenus: number; maintenancesPrevues: number; heuresUtilisation: number; capaciteMax: number; etatSante: number; } interface EmployeRessource { id: string; nom: string; prenom: string; metier: string; competences: string[]; disponibilite: number; chantierActuel?: string; tauxHoraire: number; heuresTravaillees: number; certifications: string[]; experience: number; } const DashboardRessources = () => { const toast = useRef(null); const router = useRouter(); const [materiels, setMateriels] = useState([]); const [employes, setEmployes] = useState([]); const [loading, setLoading] = useState(true); const [selectedType, setSelectedType] = useState(''); const [selectedCategorie, setSelectedCategorie] = useState(''); const [selectedView, setSelectedView] = useState('materiel'); const [chartData, setChartData] = useState({}); const [chartOptions, setChartOptions] = useState({}); const typeOptions = [ { label: 'Tous les types', value: '' }, { label: 'Engins de terrassement', value: 'TERRASSEMENT' }, { label: 'Grues et levage', value: 'LEVAGE' }, { label: 'Transport', value: 'TRANSPORT' }, { label: 'Outillage', value: 'OUTILLAGE' }, { label: 'Sécurité', value: 'SECURITE' } ]; const categorieOptions = [ { label: 'Toutes les catégories', value: '' }, { label: 'Lourd', value: 'LOURD' }, { label: 'Léger', value: 'LEGER' }, { label: 'Spécialisé', value: 'SPECIALISE' }, { label: 'Consommable', value: 'CONSOMMABLE' } ]; const viewOptions = [ { label: 'Matériel', value: 'materiel' }, { label: 'Employés', value: 'employes' }, { label: 'Vue globale', value: 'global' } ]; useEffect(() => { loadRessources(); initCharts(); }, [selectedType, selectedCategorie, selectedView]); const loadRessources = async () => { try { setLoading(true); // TODO: Remplacer par de vrais appels API // const [materielResponse, employeResponse] = await Promise.all([ // materielService.getDashboardData(), // employeService.getDashboardData() // ]); // Données simulées pour la démonstration const mockMateriels: RessourceDashboard[] = [ { id: '1', nom: 'Grue mobile Liebherr LTM 1050', type: 'LEVAGE', categorie: 'LOURD', statut: 'EN_SERVICE', disponibilite: 85, utilisation: 75, localisation: 'Chantier Résidence Les Jardins', chantierActuel: 'Résidence Les Jardins', dateFinUtilisation: new Date('2025-02-15'), coutJournalier: 850, revenus: 25500, maintenancesPrevues: 2, heuresUtilisation: 1250, capaciteMax: 50, etatSante: 90 }, { id: '2', nom: 'Pelleteuse CAT 320D', type: 'TERRASSEMENT', categorie: 'LOURD', statut: 'MAINTENANCE', disponibilite: 0, utilisation: 0, localisation: 'Atelier Central', coutJournalier: 650, revenus: 19500, maintenancesPrevues: 1, heuresUtilisation: 980, capaciteMax: 32, etatSante: 65 }, { id: '3', nom: 'Camion benne Volvo FMX', type: 'TRANSPORT', categorie: 'LOURD', statut: 'DISPONIBLE', disponibilite: 100, utilisation: 45, localisation: 'Dépôt Central', coutJournalier: 450, revenus: 13500, maintenancesPrevues: 1, heuresUtilisation: 750, capaciteMax: 25, etatSante: 85 }, { id: '4', nom: 'Bétonnière Schwing S36X', type: 'TRANSPORT', categorie: 'SPECIALISE', statut: 'EN_SERVICE', disponibilite: 70, utilisation: 90, localisation: 'Chantier Centre Commercial', chantierActuel: 'Centre Commercial Atlantis', dateFinUtilisation: new Date('2025-01-30'), coutJournalier: 750, revenus: 22500, maintenancesPrevues: 3, heuresUtilisation: 1100, capaciteMax: 36, etatSante: 80 } ]; const mockEmployes: EmployeRessource[] = [ { id: '1', nom: 'Dupont', prenom: 'Jean', metier: 'Chef de chantier', competences: ['Gestion équipe', 'Planning', 'Sécurité'], disponibilite: 100, chantierActuel: 'Résidence Les Jardins', tauxHoraire: 45, heuresTravaillees: 1680, certifications: ['CACES R482', 'SST'], experience: 15 }, { id: '2', nom: 'Martin', prenom: 'Marie', metier: 'Conducteur d\'engins', competences: ['Pelleteuse', 'Bulldozer', 'Compacteur'], disponibilite: 85, chantierActuel: 'Centre Commercial Atlantis', tauxHoraire: 35, heuresTravaillees: 1520, certifications: ['CACES R482', 'CACES R372'], experience: 8 }, { id: '3', nom: 'Leroy', prenom: 'Pierre', metier: 'Grutier', competences: ['Grue mobile', 'Grue tour', 'Levage'], disponibilite: 90, chantierActuel: 'Résidence Les Jardins', tauxHoraire: 40, heuresTravaillees: 1600, certifications: ['CACES R483', 'CACES R487'], experience: 12 }, { id: '4', nom: 'Bernard', prenom: 'Luc', metier: 'Maçon', competences: ['Béton', 'Coffrage', 'Ferraillage'], disponibilite: 100, tauxHoraire: 28, heuresTravaillees: 1750, certifications: ['CQP Maçon', 'SST'], experience: 10 } ]; // Filtrer selon les critères sélectionnés let filteredMateriels = mockMateriels; if (selectedType) { filteredMateriels = filteredMateriels.filter(m => m.type === selectedType); } if (selectedCategorie) { filteredMateriels = filteredMateriels.filter(m => m.categorie === selectedCategorie); } setMateriels(filteredMateriels); setEmployes(mockEmployes); } catch (error) { console.error('Erreur lors du chargement des ressources:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les données des ressources' }); } finally { setLoading(false); } }; const initCharts = () => { const documentStyle = getComputedStyle(document.documentElement); if (selectedView === 'materiel') { // Graphique d'utilisation du matériel const utilisationData = { labels: ['En service', 'Disponible', 'Maintenance', 'Hors service'], datasets: [ { data: [ materiels.filter(m => m.statut === 'EN_SERVICE').length, materiels.filter(m => m.statut === 'DISPONIBLE').length, materiels.filter(m => m.statut === 'MAINTENANCE').length, materiels.filter(m => m.statut === 'HORS_SERVICE').length ], backgroundColor: [ documentStyle.getPropertyValue('--green-500'), documentStyle.getPropertyValue('--blue-500'), documentStyle.getPropertyValue('--orange-500'), documentStyle.getPropertyValue('--red-500') ] } ] }; setChartData(utilisationData); } else if (selectedView === 'employes') { // Graphique de répartition par métier const metiers = [...new Set(employes.map(e => e.metier))]; const metierData = { labels: metiers, datasets: [ { data: metiers.map(metier => employes.filter(e => e.metier === metier).length), backgroundColor: [ documentStyle.getPropertyValue('--blue-500'), documentStyle.getPropertyValue('--green-500'), documentStyle.getPropertyValue('--orange-500'), documentStyle.getPropertyValue('--purple-500') ] } ] }; setChartData(metierData); } const options = { plugins: { legend: { labels: { usePointStyle: true, color: documentStyle.getPropertyValue('--text-color') } } } }; setChartOptions(options); }; const getStatutSeverity = (statut: string) => { switch (statut) { case 'EN_SERVICE': return 'success'; case 'DISPONIBLE': return 'info'; case 'MAINTENANCE': return 'warning'; case 'HORS_SERVICE': return 'danger'; default: return 'info'; } }; const statutBodyTemplate = (rowData: RessourceDashboard) => ( ); const utilisationBodyTemplate = (rowData: RessourceDashboard) => (
{rowData.utilisation}%
); const disponibiliteBodyTemplate = (rowData: RessourceDashboard) => { const couleur = rowData.disponibilite > 80 ? 'text-green-500' : rowData.disponibilite > 50 ? 'text-orange-500' : 'text-red-500'; return ( {rowData.disponibilite}% ); }; const revenusBodyTemplate = (rowData: RessourceDashboard) => (
{new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(rowData.revenus)}
{rowData.coutJournalier}€/jour
); const etatSanteBodyTemplate = (rowData: RessourceDashboard) => (
80 ? '#10b981' : rowData.etatSante > 60 ? '#f59e0b' : '#ef4444'} rangeColor="#e5e7eb" textColor="#374151" />
); const competencesBodyTemplate = (rowData: EmployeRessource) => (
{rowData.competences.slice(0, 2).map((comp, index) => ( ))} {rowData.competences.length > 2 && ( )}
); const experienceBodyTemplate = (rowData: EmployeRessource) => (
{rowData.experience}
ans
); const actionBodyTemplate = (rowData: any) => (
); // Calculs des métriques const tauxUtilisationMoyen = materiels.length > 0 ? materiels.reduce((sum, m) => sum + m.utilisation, 0) / materiels.length : 0; const revenusTotal = materiels.reduce((sum, m) => sum + m.revenus, 0); const materielDisponible = materiels.filter(m => m.statut === 'DISPONIBLE').length; const employesDisponibles = employes.filter(e => !e.chantierActuel).length; return (
{/* En-tête avec filtres */}

Dashboard Ressources

setSelectedView(e.value)} className="w-full md:w-12rem" />
{selectedView === 'materiel' && ( <>
setSelectedType(e.value)} className="w-full md:w-14rem" />
setSelectedCategorie(e.value)} className="w-full md:w-14rem" />
)}
{/* Métriques principales */}
{selectedView === 'materiel' ? 'Matériels' : 'Employés'}
{selectedView === 'materiel' ? materiels.length : employes.length}
Disponibles
{selectedView === 'materiel' ? materielDisponible : employesDisponibles}
{selectedView === 'materiel' ? 'Revenus' : 'Heures totales'}
{selectedView === 'materiel' ? new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', notation: 'compact' }).format(revenusTotal) : employes.reduce((sum, e) => sum + e.heuresTravaillees, 0).toLocaleString() }
Taux Utilisation
{selectedView === 'materiel' ? `${tauxUtilisationMoyen.toFixed(1)}%` : `${((employes.filter(e => e.chantierActuel).length / employes.length) * 100).toFixed(1)}%` }
{/* Graphique */}
{selectedView === 'materiel' ? 'Répartition par Statut' : 'Répartition par Métier'}
{/* Indicateurs de performance */}
Indicateurs de Performance
{selectedView === 'materiel' ? ( <>
{materiels.filter(m => m.etatSante > 80).length}
Bon état
{materiels.reduce((sum, m) => sum + m.maintenancesPrevues, 0)}
Maintenances prévues
{(materiels.reduce((sum, m) => sum + m.heuresUtilisation, 0) / materiels.length).toFixed(0)}
Heures moy./matériel
{(revenusTotal / materiels.length).toFixed(0)}€
Revenus moy./matériel
) : ( <>
{employes.filter(e => e.certifications.length > 1).length}
Multi-certifiés
{(employes.reduce((sum, e) => sum + e.experience, 0) / employes.length).toFixed(1)}
Expérience moyenne
{(employes.reduce((sum, e) => sum + e.tauxHoraire, 0) / employes.length).toFixed(0)}€
Taux horaire moyen
{employes.reduce((sum, e) => sum + e.competences.length, 0)}
Compétences totales
)}
{/* Tableau des ressources */}
{selectedView === 'materiel' ? `Liste du Matériel (${materiels.length})` : `Liste des Employés (${employes.length})` }
m.statut === 'EN_SERVICE').length} en service` : `${employes.filter(e => e.chantierActuel).length} en mission` } severity="success" />
{selectedView === 'materiel' ? ( <> ) : ( <> `${rowData.prenom} ${rowData.nom}`} sortable /> `${rowData.tauxHoraire}€`} sortable /> )}
); }; export default DashboardRessources;