'use client'; export const dynamic = 'force-dynamic'; import React, { useState, useEffect, useRef } from 'react'; import { Card } from 'primereact/card'; import { Button } from 'primereact/button'; import { Calendar } from 'primereact/calendar'; import { Chart } from 'primereact/chart'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { Dropdown } from 'primereact/dropdown'; import { TabView, TabPanel } from 'primereact/tabview'; import { Rating } from 'primereact/rating'; import { Tag } from 'primereact/tag'; import { InputText } from 'primereact/inputtext'; import { ProgressBar } from 'primereact/progressbar'; import { Knob } from 'primereact/knob'; interface Employe { id: string; nom: string; prenom: string; poste: string; equipe: string; dateEmbauche: Date; statut: 'ACTIF' | 'CONGE' | 'ARRET' | 'FORMATION'; heuresTravaillees: number; heuresObjectif: number; chantiersAssignes: number; chantiersTermines: number; tauxReussite: number; evaluationPerformance: number; salaireBase: number; primes: number; specialites: string[]; certifications: string[]; formation: number; // heures de formation securite: number; // score sécurité satisfaction: number; // satisfaction client } interface EquipePerformance { nom: string; chef: string; nbMembres: number; heuresTravaillees: number; productivite: number; chantiersAssignes: number; chantiersTermines: number; tauxReussite: number; budgetRespect: number; delaiRespect: number; securiteScore: number; satisfactionClient: number; coutTotal: number; chiffreAffaires: number; rentabilite: number; } interface IndicateurRH { nom: string; valeurActuelle: number; objectif: number; unite: string; tendance: 'HAUSSE' | 'BAISSE' | 'STABLE'; couleur: string; } const PerformanceEquipesPage = () => { const [loading, setLoading] = useState(true); const [employes, setEmployes] = useState([]); const [equipes, setEquipes] = useState([]); const [indicateursRH, setIndicateursRH] = useState([]); const [selectedEmployes, setSelectedEmployes] = useState([]); const [dateDebut, setDateDebut] = useState(new Date(new Date().getFullYear(), 0, 1)); const [dateFin, setDateFin] = useState(new Date()); const [selectedPeriod, setSelectedPeriod] = useState('annee'); const [selectedEquipe, setSelectedEquipe] = useState('toutes'); const [globalFilter, setGlobalFilter] = useState(''); const [activeIndex, setActiveIndex] = useState(0); const toast = useRef(null); const dt = useRef>(null); const periodOptions = [ { label: 'Ce mois', value: 'mois' }, { label: 'Ce trimestre', value: 'trimestre' }, { label: 'Cette année', value: 'annee' }, { label: 'Personnalisé', value: 'custom' } ]; const equipeOptions = [ { label: 'Toutes les équipes', value: 'toutes' }, { label: 'Équipe Gros Œuvre', value: 'gros-oeuvre' }, { label: 'Équipe Second Œuvre', value: 'second-oeuvre' }, { label: 'Équipe Finitions', value: 'finitions' }, { label: 'Équipe Maintenance', value: 'maintenance' } ]; useEffect(() => { loadPerformanceData(); }, [dateDebut, dateFin, selectedEquipe]); const loadPerformanceData = async () => { try { setLoading(true); // Données mockées employés const mockEmployes: Employe[] = [ { id: '1', nom: 'Martin', prenom: 'Jean', poste: 'Chef d\'équipe', equipe: 'gros-oeuvre', dateEmbauche: new Date('2022-01-15'), statut: 'ACTIF', heuresTravaillees: 1680, heuresObjectif: 1800, chantiersAssignes: 8, chantiersTermines: 7, tauxReussite: 87.5, evaluationPerformance: 4.5, salaireBase: 3200, primes: 450, specialites: ['Maçonnerie', 'Coffrage', 'Management'], certifications: ['CACES R389', 'Sauveteur Secouriste'], formation: 35, securite: 95, satisfaction: 4.3 }, { id: '2', nom: 'Dubois', prenom: 'Marie', poste: 'Maçon', equipe: 'gros-oeuvre', dateEmbauche: new Date('2023-03-20'), statut: 'ACTIF', heuresTravaillees: 1620, heuresObjectif: 1750, chantiersAssignes: 6, chantiersTermines: 6, tauxReussite: 100, evaluationPerformance: 4.2, salaireBase: 2800, primes: 320, specialites: ['Maçonnerie', 'Carrelage'], certifications: ['Sauveteur Secouriste'], formation: 28, securite: 92, satisfaction: 4.5 }, { id: '3', nom: 'Durand', prenom: 'Pierre', poste: 'Électricien', equipe: 'second-oeuvre', dateEmbauche: new Date('2021-09-10'), statut: 'ACTIF', heuresTravaillees: 1750, heuresObjectif: 1800, chantiersAssignes: 10, chantiersTermines: 9, tauxReussite: 90, evaluationPerformance: 4.7, salaireBase: 3000, primes: 500, specialites: ['Électricité', 'Domotique', 'Photovoltaïque'], certifications: ['Habilitation B2V', 'QualiPV'], formation: 42, securite: 98, satisfaction: 4.6 }, { id: '4', nom: 'Bernard', prenom: 'Sophie', poste: 'Plombier', equipe: 'second-oeuvre', dateEmbauche: new Date('2023-01-08'), statut: 'FORMATION', heuresTravaillees: 1580, heuresObjectif: 1750, chantiersAssignes: 5, chantiersTermines: 4, tauxReussite: 80, evaluationPerformance: 3.8, salaireBase: 2700, primes: 180, specialites: ['Plomberie', 'Chauffage'], certifications: ['PGN'], formation: 65, securite: 88, satisfaction: 4.1 }, { id: '5', nom: 'Moreau', prenom: 'Luc', poste: 'Peintre', equipe: 'finitions', dateEmbauche: new Date('2022-06-15'), statut: 'ACTIF', heuresTravaillees: 1650, heuresObjectif: 1750, chantiersAssignes: 12, chantiersTermines: 11, tauxReussite: 91.7, evaluationPerformance: 4.1, salaireBase: 2600, primes: 275, specialites: ['Peinture', 'Papier peint', 'Décoration'], certifications: ['Peinture écologique'], formation: 18, securite: 90, satisfaction: 4.4 } ]; const mockEquipes: EquipePerformance[] = [ { nom: 'Équipe Gros Œuvre', chef: 'Jean Martin', nbMembres: 6, heuresTravaillees: 9800, productivite: 92, chantiersAssignes: 15, chantiersTermines: 14, tauxReussite: 93.3, budgetRespect: 88, delaiRespect: 85, securiteScore: 94, satisfactionClient: 4.3, coutTotal: 125000, chiffreAffaires: 180000, rentabilite: 30.6 }, { nom: 'Équipe Second Œuvre', chef: 'Pierre Durand', nbMembres: 8, heuresTravaillees: 13600, productivite: 95, chantiersAssignes: 20, chantiersTermines: 18, tauxReussite: 90, budgetRespect: 92, delaiRespect: 88, securiteScore: 96, satisfactionClient: 4.5, coutTotal: 168000, chiffreAffaires: 250000, rentabilite: 32.8 }, { nom: 'Équipe Finitions', chef: 'Luc Moreau', nbMembres: 4, heuresTravaillees: 6800, productivite: 89, chantiersAssignes: 25, chantiersTermines: 23, tauxReussite: 92, budgetRespect: 90, delaiRespect: 95, securiteScore: 91, satisfactionClient: 4.6, coutTotal: 88000, chiffreAffaires: 135000, rentabilite: 34.8 }, { nom: 'Équipe Maintenance', chef: 'Anne Petit', nbMembres: 3, heuresTravaillees: 5100, productivite: 87, chantiersAssignes: 18, chantiersTermines: 17, tauxReussite: 94.4, budgetRespect: 95, delaiRespect: 98, securiteScore: 97, satisfactionClient: 4.2, coutTotal: 65000, chiffreAffaires: 95000, rentabilite: 31.6 } ]; const mockIndicateursRH: IndicateurRH[] = [ { nom: 'Productivité Globale', valeurActuelle: 93.2, objectif: 95, unite: '%', tendance: 'HAUSSE', couleur: '#10B981' }, { nom: 'Taux de Réussite', valeurActuelle: 92.4, objectif: 90, unite: '%', tendance: 'STABLE', couleur: '#3B82F6' }, { nom: 'Respect Délais', valeurActuelle: 91.5, objectif: 95, unite: '%', tendance: 'HAUSSE', couleur: '#F59E0B' }, { nom: 'Score Sécurité', valeurActuelle: 94.5, objectif: 95, unite: '%', tendance: 'HAUSSE', couleur: '#EF4444' }, { nom: 'Satisfaction Client', valeurActuelle: 4.4, objectif: 4.5, unite: '/5', tendance: 'STABLE', couleur: '#8B5CF6' }, { nom: 'Heures Formation', valeurActuelle: 37.6, objectif: 40, unite: 'h', tendance: 'HAUSSE', couleur: '#06B6D4' } ]; setEmployes(selectedEquipe === 'toutes' ? mockEmployes : mockEmployes.filter(e => e.equipe === selectedEquipe)); setEquipes(mockEquipes); setIndicateursRH(mockIndicateursRH); } catch (error) { console.error('Erreur lors du chargement:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les données de performance', life: 3000 }); } finally { setLoading(false); } }; const onPeriodChange = (e: any) => { setSelectedPeriod(e.value); const now = new Date(); let debut = new Date(); switch (e.value) { case 'mois': debut = new Date(now.getFullYear(), now.getMonth(), 1); break; case 'trimestre': debut = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1); break; case 'annee': debut = new Date(now.getFullYear(), 0, 1); break; default: return; } setDateDebut(debut); setDateFin(now); }; const exportPDF = () => { toast.current?.show({ severity: 'info', summary: 'Export PDF', detail: 'Génération du rapport PDF...', life: 3000 }); }; const exportExcel = () => { dt.current?.exportCSV(); }; const leftToolbarTemplate = () => { return (
setSelectedEquipe(e.value)} placeholder="Équipe" /> {selectedPeriod === 'custom' && ( <> setDateDebut(e.value || new Date())} dateFormat="dd/mm/yy" placeholder="Date début" /> setDateFin(e.value || new Date())} dateFormat="dd/mm/yy" placeholder="Date fin" /> )}
); }; const rightToolbarTemplate = () => { return (
); }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(amount); }; const statutBodyTemplate = (rowData: Employe) => { let severity: "success" | "warning" | "danger" | "info" = 'success'; let label: string = rowData.statut; switch (rowData.statut) { case 'ACTIF': severity = 'success'; label = 'Actif'; break; case 'CONGE': severity = 'info'; label = 'Congé'; break; case 'ARRET': severity = 'danger'; label = 'Arrêt'; break; case 'FORMATION': severity = 'warning'; label = 'Formation'; break; } return ; }; const performanceBodyTemplate = (rowData: Employe) => { return ; }; const productiviteBodyTemplate = (rowData: Employe) => { const productivite = (rowData.heuresTravaillees / rowData.heuresObjectif) * 100; const color = productivite >= 95 ? 'text-green-600' : productivite >= 80 ? 'text-orange-600' : 'text-red-600'; return (
{productivite.toFixed(1)}%
); }; const reussiteBodyTemplate = (rowData: Employe) => { const color = rowData.tauxReussite >= 90 ? 'text-green-600' : rowData.tauxReussite >= 75 ? 'text-orange-600' : 'text-red-600'; return {rowData.tauxReussite.toFixed(1)}%; }; const securiteBodyTemplate = (rowData: Employe) => { return (
{rowData.securite}%
); }; const specialitesBodyTemplate = (rowData: Employe) => { return rowData.specialites.slice(0, 2).map((spec, index) => ( )); }; const dateEmbaucheBodyTemplate = (rowData: Employe) => { const anciennete = Math.floor((new Date().getTime() - rowData.dateEmbauche.getTime()) / (1000 * 3600 * 24 * 365)); return (
{rowData.dateEmbauche.toLocaleDateString('fr-FR')}
{anciennete} an{anciennete > 1 ? 's' : ''}
); }; const salaireBodyTemplate = (rowData: Employe) => { return (
{formatCurrency(rowData.salaireBase)}
{rowData.primes > 0 && ( +{formatCurrency(rowData.primes)} primes )}
); }; const header = (
Performance des Équipes
setGlobalFilter(e.currentTarget.value)} />
); // Calculs pour les indicateurs globaux const totalEmployes = employes.length; const employesActifs = employes.filter(e => e.statut === 'ACTIF').length; const heuresTotal = employes.reduce((sum, e) => sum + e.heuresTravaillees, 0); const heuresObjectifTotal = employes.reduce((sum, e) => sum + e.heuresObjectif, 0); const productiviteGlobale = heuresObjectifTotal > 0 ? (heuresTotal / heuresObjectifTotal) * 100 : 0; const performanceMoyenne = totalEmployes > 0 ? employes.reduce((sum, e) => sum + e.evaluationPerformance, 0) / totalEmployes : 0; // Données pour les graphiques const equipePerformanceData = { labels: equipes.map(e => e.nom), datasets: [ { label: 'Productivité (%)', data: equipes.map(e => e.productivite), backgroundColor: '#3B82F6', borderColor: '#1D4ED8', borderWidth: 1 }, { label: 'Taux Réussite (%)', data: equipes.map(e => e.tauxReussite), backgroundColor: '#10B981', borderColor: '#047857', borderWidth: 1 } ] }; const rentabiliteEquipeData = { labels: equipes.map(e => e.nom), datasets: [ { data: equipes.map(e => e.rentabilite), backgroundColor: ['#10B981', '#3B82F6', '#F59E0B', '#EF4444'], hoverBackgroundColor: ['#059669', '#2563EB', '#D97706', '#DC2626'] } ] }; const tendanceFormationData = { labels: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun'], datasets: [ { label: 'Heures Formation', data: [32, 28, 35, 42, 38, 45], borderColor: '#8B5CF6', backgroundColor: 'rgba(139, 92, 246, 0.1)', tension: 0.4, fill: true } ] }; const chartOptions = { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' as const } } }; return (
setActiveIndex(e.index)}>
{/* Indicateurs principaux */}
{totalEmployes}
Total Employés
{employesActifs}
Employés Actifs
{productiviteGlobale.toFixed(1)}%
Productivité Globale
{performanceMoyenne.toFixed(1)}/5
Performance Moyenne
{/* Indicateurs RH */}
{indicateursRH.map((indicateur, index) => (
{indicateur.nom}
{indicateur.valeurActuelle}{indicateur.unite}
Objectif: {indicateur.objectif}{indicateur.unite}
))}
{/* Performance par équipe */}
(
{rowData.productivite}%
)} /> `${rowData.tauxReussite.toFixed(1)}%`} /> `${rowData.delaiRespect}%`} /> `${rowData.securiteScore}%`} /> ( )} /> `${rowData.rentabilite.toFixed(1)}%`} />
{/* Graphiques */}
setSelectedEmployes(e.value)} selectionMode="checkbox" dataKey="id" paginator rows={10} rowsPerPageOptions={[5, 10, 25]} className="datatable-responsive" paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} employés" globalFilter={globalFilter} emptyMessage="Aucun employé trouvé." header={header} loading={loading} > `${rowData.chantiersTermines}/${rowData.chantiersAssignes}`} sortable />
`${e.prenom} ${e.nom}`), datasets: [ { label: 'Heures Formation', data: employes.map(e => e.formation), backgroundColor: '#8B5CF6', borderColor: '#7C3AED', borderWidth: 1 } ] }} options={chartOptions} style={{ height: '300px' }} />
rowData.specialites.join(', ')} /> rowData.certifications.join(', ')} /> (
{rowData.formation}h / 80h objectif
)} />
{formatCurrency(employes.reduce((sum, e) => sum + e.salaireBase + e.primes, 0))}
Salaires + Primes mensuel
{heuresTotal > 0 ? (employes.reduce((sum, e) => sum + e.salaireBase + e.primes, 0) / heuresTotal).toFixed(2) : '0.00' }€
Coût horaire moyen
+12.5%
Amélioration productivité
formatCurrency(rowData.salaireBase)} sortable /> formatCurrency(rowData.primes)} sortable /> formatCurrency(rowData.salaireBase + rowData.primes)} sortable /> `${((rowData.salaireBase + rowData.primes) / rowData.heuresTravaillees).toFixed(2)}€` } /> { const ca = rowData.chantiersTermines * 50000; // CA estimé const cout = rowData.salaireBase + rowData.primes; const rentabilite = ca > 0 ? ((ca - cout) / ca) * 100 : 0; return `${rentabilite.toFixed(1)}%`; }} />
); }; export default PerformanceEquipesPage;