341 lines
13 KiB
TypeScript
341 lines
13 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { Card } from 'primereact/card';
|
|
import { Chart } from 'primereact/chart';
|
|
import { Button } from 'primereact/button';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { Tag } from 'primereact/tag';
|
|
import { useRouter } from 'next/navigation';
|
|
import { apiClient } from '../../../../services/api-client';
|
|
|
|
interface StatistiquesEmployes {
|
|
totalEmployes: number;
|
|
employesActifs: number;
|
|
employesInactifs: number;
|
|
employesEnConge: number;
|
|
employesEnFormation: number;
|
|
repartitionParMetier: { [key: string]: number };
|
|
repartitionParExperience: { [key: string]: number };
|
|
repartitionParEquipe: { [key: string]: number };
|
|
employesSansCertification: number;
|
|
employesAvecCertifications: number;
|
|
moyenneAgeEmployes: number;
|
|
ancienneteMoyenne: number;
|
|
tauxDisponibilite: number;
|
|
topCompetences: Array<{ competence: string; nombre: number }>;
|
|
topCertifications: Array<{ certification: string; nombre: number }>;
|
|
}
|
|
|
|
const StatistiquesEmployesPage = () => {
|
|
const [stats, setStats] = useState<StatistiquesEmployes | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [chartOptions] = useState({
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom'
|
|
}
|
|
}
|
|
});
|
|
const router = useRouter();
|
|
|
|
useEffect(() => {
|
|
loadStatistiques();
|
|
}, []);
|
|
|
|
const loadStatistiques = async () => {
|
|
try {
|
|
setLoading(true);
|
|
console.log('🔄 Chargement des statistiques employés...');
|
|
const response = await apiClient.get('/api/employes/statistiques');
|
|
console.log('✅ Statistiques employés chargées:', response.data);
|
|
setStats(response.data);
|
|
} catch (error) {
|
|
console.error('❌ Erreur lors du chargement des statistiques:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const getStatutChartData = () => {
|
|
if (!stats) return {};
|
|
|
|
return {
|
|
labels: ['Actifs', 'Inactifs', 'En congé', 'En formation'],
|
|
datasets: [{
|
|
data: [
|
|
stats.employesActifs,
|
|
stats.employesInactifs,
|
|
stats.employesEnConge,
|
|
stats.employesEnFormation
|
|
],
|
|
backgroundColor: [
|
|
'#4CAF50',
|
|
'#F44336',
|
|
'#FF9800',
|
|
'#2196F3'
|
|
],
|
|
borderWidth: 2
|
|
}]
|
|
};
|
|
};
|
|
|
|
const getMetierChartData = () => {
|
|
if (!stats) return {};
|
|
|
|
return {
|
|
labels: Object.keys(stats.repartitionParMetier),
|
|
datasets: [{
|
|
data: Object.values(stats.repartitionParMetier),
|
|
backgroundColor: [
|
|
'#FF6384',
|
|
'#36A2EB',
|
|
'#FFCE56',
|
|
'#4BC0C0',
|
|
'#9966FF',
|
|
'#FF9F40',
|
|
'#FF6384',
|
|
'#C9CBCF'
|
|
],
|
|
borderWidth: 2
|
|
}]
|
|
};
|
|
};
|
|
|
|
const getExperienceChartData = () => {
|
|
if (!stats) return {};
|
|
|
|
return {
|
|
labels: Object.keys(stats.repartitionParExperience),
|
|
datasets: [{
|
|
label: 'Nombre d\'employés',
|
|
data: Object.values(stats.repartitionParExperience),
|
|
backgroundColor: '#36A2EB',
|
|
borderColor: '#36A2EB',
|
|
borderWidth: 1
|
|
}]
|
|
};
|
|
};
|
|
|
|
const leftToolbarTemplate = () => {
|
|
return (
|
|
<div className="flex flex-wrap gap-2">
|
|
<Button
|
|
label="Retour aux employés"
|
|
icon="pi pi-arrow-left"
|
|
className="p-button-outlined"
|
|
onClick={() => router.push('/employes')}
|
|
/>
|
|
<Button
|
|
label="Export PDF"
|
|
icon="pi pi-file-pdf"
|
|
className="p-button-danger"
|
|
onClick={() => console.log('Export PDF')}
|
|
/>
|
|
<Button
|
|
label="Export Excel"
|
|
icon="pi pi-file-excel"
|
|
className="p-button-success"
|
|
onClick={() => console.log('Export Excel')}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const rightToolbarTemplate = () => {
|
|
return (
|
|
<Button
|
|
icon="pi pi-refresh"
|
|
className="p-button-outlined"
|
|
onClick={loadStatistiques}
|
|
tooltip="Actualiser"
|
|
/>
|
|
);
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Card>
|
|
<div className="flex justify-content-center">
|
|
<i className="pi pi-spin pi-spinner" style={{ fontSize: '2rem' }} />
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!stats) {
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Card>
|
|
<div className="text-center">
|
|
<p>Aucune donnée disponible</p>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Toolbar
|
|
className="mb-4"
|
|
left={leftToolbarTemplate}
|
|
right={rightToolbarTemplate}
|
|
/>
|
|
</div>
|
|
|
|
{/* Métriques principales */}
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="bg-blue-100">
|
|
<div className="flex justify-content-between align-items-center">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Total Employés</span>
|
|
<div className="text-900 font-medium text-xl">{stats.totalEmployes}</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-blue-500 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-users text-white text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="bg-green-100">
|
|
<div className="flex justify-content-between align-items-center">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Employés Actifs</span>
|
|
<div className="text-900 font-medium text-xl">{stats.employesActifs}</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-green-500 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-check-circle text-white text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="bg-orange-100">
|
|
<div className="flex justify-content-between align-items-center">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Taux Disponibilité</span>
|
|
<div className="text-900 font-medium text-xl">{stats.tauxDisponibilite}%</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-orange-500 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-calendar-check text-white text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-3 md:col-6">
|
|
<Card className="bg-purple-100">
|
|
<div className="flex justify-content-between align-items-center">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Ancienneté Moyenne</span>
|
|
<div className="text-900 font-medium text-xl">{stats.ancienneteMoyenne} ans</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-purple-500 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-clock text-white text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Graphiques */}
|
|
<div className="col-12 lg:col-6">
|
|
<Card title="Répartition par Statut">
|
|
<Chart type="doughnut" data={getStatutChartData()} options={chartOptions} style={{ height: '300px' }} />
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-6">
|
|
<Card title="Répartition par Métier">
|
|
<Chart type="pie" data={getMetierChartData()} options={chartOptions} style={{ height: '300px' }} />
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12">
|
|
<Card title="Répartition par Niveau d'Expérience">
|
|
<Chart type="bar" data={getExperienceChartData()} options={chartOptions} style={{ height: '300px' }} />
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Tableaux de données */}
|
|
<div className="col-12 lg:col-6">
|
|
<Card title="Top Compétences">
|
|
<DataTable value={stats.topCompetences} responsiveLayout="scroll">
|
|
<Column field="competence" header="Compétence" />
|
|
<Column
|
|
field="nombre"
|
|
header="Nombre d'employés"
|
|
body={(rowData) => <Tag value={rowData.nombre} severity="info" />}
|
|
/>
|
|
</DataTable>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 lg:col-6">
|
|
<Card title="Top Certifications">
|
|
<DataTable value={stats.topCertifications} responsiveLayout="scroll">
|
|
<Column field="certification" header="Certification" />
|
|
<Column
|
|
field="nombre"
|
|
header="Nombre d'employés"
|
|
body={(rowData) => <Tag value={rowData.nombre} severity="success" />}
|
|
/>
|
|
</DataTable>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Informations supplémentaires */}
|
|
<div className="col-12">
|
|
<Card title="Informations Complémentaires">
|
|
<div className="grid">
|
|
<div className="col-12 md:col-4">
|
|
<div className="text-center">
|
|
<i className="pi pi-graduation-cap text-4xl text-blue-500 mb-3" />
|
|
<h4>Certifications</h4>
|
|
<p className="text-600">
|
|
{stats.employesAvecCertifications} employés avec certifications<br />
|
|
{stats.employesSansCertification} sans certification
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="col-12 md:col-4">
|
|
<div className="text-center">
|
|
<i className="pi pi-calendar text-4xl text-green-500 mb-3" />
|
|
<h4>Âge Moyen</h4>
|
|
<p className="text-600">
|
|
{stats.moyenneAgeEmployes} ans
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="col-12 md:col-4">
|
|
<div className="text-center">
|
|
<i className="pi pi-users text-4xl text-orange-500 mb-3" />
|
|
<h4>Équipes</h4>
|
|
<p className="text-600">
|
|
{Object.keys(stats.repartitionParEquipe).length} équipes actives
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StatistiquesEmployesPage;
|