Files
btpxpress-frontend/app/(main)/rapports/equipes/page.tsx
dahoud a8825a058b Fix: Corriger toutes les erreurs de build du frontend
- Correction des erreurs TypeScript dans userService.ts et workflowTester.ts
- Ajout des propriétés manquantes aux objets User mockés
- Conversion des dates de string vers objets Date
- Correction des appels asynchrones et des types incompatibles
- Ajout de dynamic rendering pour résoudre les erreurs useSearchParams
- Enveloppement de useSearchParams dans Suspense boundary
- Configuration de force-dynamic au niveau du layout principal

Build réussi: 126 pages générées avec succès

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-18 13:23:08 +00:00

987 lines
46 KiB
TypeScript

'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<Employe[]>([]);
const [equipes, setEquipes] = useState<EquipePerformance[]>([]);
const [indicateursRH, setIndicateursRH] = useState<IndicateurRH[]>([]);
const [selectedEmployes, setSelectedEmployes] = useState<Employe[]>([]);
const [dateDebut, setDateDebut] = useState<Date>(new Date(new Date().getFullYear(), 0, 1));
const [dateFin, setDateFin] = useState<Date>(new Date());
const [selectedPeriod, setSelectedPeriod] = useState('annee');
const [selectedEquipe, setSelectedEquipe] = useState('toutes');
const [globalFilter, setGlobalFilter] = useState('');
const [activeIndex, setActiveIndex] = useState(0);
const toast = useRef<Toast>(null);
const dt = useRef<DataTable<Employe[]>>(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 (
<div className="flex flex-wrap gap-2 align-items-center">
<Dropdown
value={selectedPeriod}
options={periodOptions}
onChange={onPeriodChange}
placeholder="Période"
/>
<Dropdown
value={selectedEquipe}
options={equipeOptions}
onChange={(e) => setSelectedEquipe(e.value)}
placeholder="Équipe"
/>
{selectedPeriod === 'custom' && (
<>
<Calendar
value={dateDebut}
onChange={(e) => setDateDebut(e.value || new Date())}
dateFormat="dd/mm/yy"
placeholder="Date début"
/>
<Calendar
value={dateFin}
onChange={(e) => setDateFin(e.value || new Date())}
dateFormat="dd/mm/yy"
placeholder="Date fin"
/>
</>
)}
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2">
<Button
label="PDF"
icon="pi pi-file-pdf"
severity="danger"
onClick={exportPDF}
/>
<Button
label="Excel"
icon="pi pi-file-excel"
severity="success"
onClick={exportExcel}
/>
<Button
label="Actualiser"
icon="pi pi-refresh"
onClick={loadPerformanceData}
/>
</div>
);
};
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 <Tag value={label} severity={severity} />;
};
const performanceBodyTemplate = (rowData: Employe) => {
return <Rating value={rowData.evaluationPerformance} readOnly cancel={false} />;
};
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 (
<div>
<ProgressBar value={productivite} showValue={false} />
<small className={color}>{productivite.toFixed(1)}%</small>
</div>
);
};
const reussiteBodyTemplate = (rowData: Employe) => {
const color = rowData.tauxReussite >= 90 ? 'text-green-600' :
rowData.tauxReussite >= 75 ? 'text-orange-600' : 'text-red-600';
return <span className={color}>{rowData.tauxReussite.toFixed(1)}%</span>;
};
const securiteBodyTemplate = (rowData: Employe) => {
return (
<div>
<ProgressBar value={rowData.securite} showValue={false} />
<small>{rowData.securite}%</small>
</div>
);
};
const specialitesBodyTemplate = (rowData: Employe) => {
return rowData.specialites.slice(0, 2).map((spec, index) => (
<Tag key={index} value={spec} className="mr-1 mb-1" severity="info" />
));
};
const dateEmbaucheBodyTemplate = (rowData: Employe) => {
const anciennete = Math.floor((new Date().getTime() - rowData.dateEmbauche.getTime()) / (1000 * 3600 * 24 * 365));
return (
<div>
<div>{rowData.dateEmbauche.toLocaleDateString('fr-FR')}</div>
<small className="text-color-secondary">{anciennete} an{anciennete > 1 ? 's' : ''}</small>
</div>
);
};
const salaireBodyTemplate = (rowData: Employe) => {
return (
<div>
<div>{formatCurrency(rowData.salaireBase)}</div>
{rowData.primes > 0 && (
<small className="text-green-500">+{formatCurrency(rowData.primes)} primes</small>
)}
</div>
);
};
const header = (
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
<h5 className="m-0">Performance des Équipes</h5>
<span className="block mt-2 md:mt-0 p-input-icon-left">
<i className="pi pi-search" />
<InputText
type="search"
placeholder="Rechercher..."
onInput={(e) => setGlobalFilter(e.currentTarget.value)}
/>
</span>
</div>
);
// 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 (
<div className="grid">
<div className="col-12">
<Card>
<Toast ref={toast} />
<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} />
<TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
<TabPanel header="Vue d'Ensemble" leftIcon="pi pi-chart-line mr-2">
<div className="grid">
{/* Indicateurs principaux */}
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-6xl text-primary mb-2">
<i className="pi pi-users"></i>
</div>
<div className="text-3xl font-bold text-primary mb-1">
{totalEmployes}
</div>
<div className="text-lg text-color-secondary">
Total Employés
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-6xl text-green-500 mb-2">
<i className="pi pi-check-circle"></i>
</div>
<div className="text-3xl font-bold text-green-500 mb-1">
{employesActifs}
</div>
<div className="text-lg text-color-secondary">
Employés Actifs
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-6xl text-orange-500 mb-2">
<i className="pi pi-chart-line"></i>
</div>
<div className="text-3xl font-bold text-orange-500 mb-1">
{productiviteGlobale.toFixed(1)}%
</div>
<div className="text-lg text-color-secondary">
Productivité Globale
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card className="text-center">
<div className="text-6xl text-purple-500 mb-2">
<i className="pi pi-star"></i>
</div>
<div className="text-3xl font-bold text-purple-500 mb-1">
{performanceMoyenne.toFixed(1)}/5
</div>
<div className="text-lg text-color-secondary">
Performance Moyenne
</div>
</Card>
</div>
{/* Indicateurs RH */}
<div className="col-12">
<Card title="Indicateurs RH">
<div className="grid">
{indicateursRH.map((indicateur, index) => (
<div key={index} className="col-12 md:col-6 lg:col-4">
<div className="text-center p-3">
<div className="flex justify-content-between align-items-center mb-2">
<span className="font-semibold">{indicateur.nom}</span>
<i className={`pi ${
indicateur.tendance === 'HAUSSE' ? 'pi-trending-up text-green-500' :
indicateur.tendance === 'BAISSE' ? 'pi-trending-down text-red-500' :
'pi-minus text-orange-500'
}`}></i>
</div>
<Knob
value={indicateur.valeurActuelle}
max={indicateur.unite === '/5' ? 5 : 100}
size={100}
valueColor={indicateur.couleur}
rangeColor="#E5E7EB"
textColor="#374151"
/>
<div className="mt-2">
<div className="font-bold">{indicateur.valeurActuelle}{indicateur.unite}</div>
<div className="text-sm text-color-secondary">
Objectif: {indicateur.objectif}{indicateur.unite}
</div>
</div>
</div>
</div>
))}
</div>
</Card>
</div>
{/* Performance par équipe */}
<div className="col-12">
<Card title="Performance par Équipe">
<DataTable
value={equipes}
loading={loading}
emptyMessage="Aucune équipe"
>
<Column field="nom" header="Équipe" />
<Column field="chef" header="Chef d'équipe" />
<Column field="nbMembres" header="Membres" />
<Column
field="productivite"
header="Productivité"
body={(rowData) => (
<div>
<ProgressBar value={rowData.productivite} showValue={false} />
<small>{rowData.productivite}%</small>
</div>
)}
/>
<Column
field="tauxReussite"
header="Taux Réussite"
body={(rowData) => `${rowData.tauxReussite.toFixed(1)}%`}
/>
<Column
field="delaiRespect"
header="Respect Délais"
body={(rowData) => `${rowData.delaiRespect}%`}
/>
<Column
field="securiteScore"
header="Sécurité"
body={(rowData) => `${rowData.securiteScore}%`}
/>
<Column
field="satisfactionClient"
header="Satisfaction"
body={(rowData) => (
<Rating value={rowData.satisfactionClient} readOnly cancel={false} />
)}
/>
<Column
field="rentabilite"
header="Rentabilité"
body={(rowData) => `${rowData.rentabilite.toFixed(1)}%`}
/>
</DataTable>
</Card>
</div>
{/* Graphiques */}
<div className="col-12 md:col-8">
<Card title="Comparaison Performance Équipes">
<Chart
type="bar"
data={equipePerformanceData}
options={chartOptions}
style={{ height: '400px' }}
/>
</Card>
</div>
<div className="col-12 md:col-4">
<Card title="Rentabilité par Équipe">
<Chart
type="doughnut"
data={rentabiliteEquipeData}
options={chartOptions}
style={{ height: '400px' }}
/>
</Card>
</div>
</div>
</TabPanel>
<TabPanel header="Employés Détaillés" leftIcon="pi pi-list mr-2">
<div className="grid">
<div className="col-12">
<Card>
<DataTable
ref={dt}
value={employes}
selection={selectedEmployes}
onSelectionChange={(e) => 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}
>
<Column selectionMode="multiple" headerStyle={{ width: '4rem' }} />
<Column field="prenom" header="Prénom" sortable />
<Column field="nom" header="Nom" sortable />
<Column field="poste" header="Poste" sortable />
<Column field="equipe" header="Équipe" sortable />
<Column field="dateEmbauche" header="Embauche" body={dateEmbaucheBodyTemplate} sortable />
<Column field="statut" header="Statut" body={statutBodyTemplate} sortable />
<Column field="productivite" header="Productivité" body={productiviteBodyTemplate} />
<Column field="chantiersTermines" header="Chantiers" body={(rowData) => `${rowData.chantiersTermines}/${rowData.chantiersAssignes}`} sortable />
<Column field="tauxReussite" header="Réussite" body={reussiteBodyTemplate} sortable />
<Column field="evaluationPerformance" header="Performance" body={performanceBodyTemplate} sortable />
<Column field="securite" header="Sécurité" body={securiteBodyTemplate} sortable />
<Column field="formation" header="Formation (h)" sortable />
<Column field="specialites" header="Spécialités" body={specialitesBodyTemplate} />
<Column field="salaireBase" header="Salaire" body={salaireBodyTemplate} sortable />
</DataTable>
</Card>
</div>
</div>
</TabPanel>
<TabPanel header="Formation & Développement" leftIcon="pi pi-graduation-cap mr-2">
<div className="grid">
<div className="col-12 md:col-6">
<Card title="Évolution Formation">
<Chart
type="line"
data={tendanceFormationData}
options={chartOptions}
style={{ height: '300px' }}
/>
</Card>
</div>
<div className="col-12 md:col-6">
<Card title="Répartition Formations par Employé">
<Chart
type="bar"
data={{
labels: employes.map(e => `${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' }}
/>
</Card>
</div>
<div className="col-12">
<Card title="Compétences et Certifications">
<DataTable
value={employes}
loading={loading}
>
<Column field="prenom" header="Prénom" />
<Column field="nom" header="Nom" />
<Column field="poste" header="Poste" />
<Column
field="specialites"
header="Spécialités"
body={(rowData) => rowData.specialites.join(', ')}
/>
<Column
field="certifications"
header="Certifications"
body={(rowData) => rowData.certifications.join(', ')}
/>
<Column
field="formation"
header="Formation (h)"
body={(rowData) => (
<div>
<ProgressBar value={(rowData.formation / 80) * 100} showValue={false} />
<small>{rowData.formation}h / 80h objectif</small>
</div>
)}
/>
<Column
field="evaluationPerformance"
header="Performance"
body={performanceBodyTemplate}
/>
</DataTable>
</Card>
</div>
</div>
</TabPanel>
<TabPanel header="Analyse Coûts RH" leftIcon="pi pi-money-bill mr-2">
<div className="grid">
<div className="col-12 md:col-4">
<Card title="Coût Total Masse Salariale">
<div className="text-center">
<div className="text-3xl font-bold text-primary mb-2">
{formatCurrency(employes.reduce((sum, e) => sum + e.salaireBase + e.primes, 0))}
</div>
<div className="text-color-secondary">
Salaires + Primes mensuel
</div>
</div>
</Card>
</div>
<div className="col-12 md:col-4">
<Card title="Coût par Heure">
<div className="text-center">
<div className="text-3xl font-bold text-green-500 mb-2">
{heuresTotal > 0 ?
(employes.reduce((sum, e) => sum + e.salaireBase + e.primes, 0) / heuresTotal).toFixed(2) :
'0.00'
}
</div>
<div className="text-color-secondary">
Coût horaire moyen
</div>
</div>
</Card>
</div>
<div className="col-12 md:col-4">
<Card title="ROI Formation">
<div className="text-center">
<div className="text-3xl font-bold text-orange-500 mb-2">
+12.5%
</div>
<div className="text-color-secondary">
Amélioration productivité
</div>
</div>
</Card>
</div>
<div className="col-12">
<Card title="Analyse Coûts par Employé">
<DataTable
value={employes}
loading={loading}
>
<Column field="prenom" header="Prénom" />
<Column field="nom" header="Nom" />
<Column field="poste" header="Poste" />
<Column
field="salaireBase"
header="Salaire Base"
body={(rowData) => formatCurrency(rowData.salaireBase)}
sortable
/>
<Column
field="primes"
header="Primes"
body={(rowData) => formatCurrency(rowData.primes)}
sortable
/>
<Column
field="coutTotal"
header="Coût Total"
body={(rowData) => formatCurrency(rowData.salaireBase + rowData.primes)}
sortable
/>
<Column
field="coutHeure"
header="Coût/Heure"
body={(rowData) =>
`${((rowData.salaireBase + rowData.primes) / rowData.heuresTravaillees).toFixed(2)}`
}
/>
<Column
field="productivite"
header="Productivité"
body={productiviteBodyTemplate}
/>
<Column
field="rentabilite"
header="Rentabilité"
body={(rowData) => {
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)}%`;
}}
/>
</DataTable>
</Card>
</div>
</div>
</TabPanel>
</TabView>
</Card>
</div>
</div>
);
};
export default PerformanceEquipesPage;