Files
btpxpress-frontend/app/(main)/maintenance/stats/page.tsx
2025-10-13 05:29:32 +02:00

570 lines
22 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
import { Chart } from 'primereact/chart';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Tag } from 'primereact/tag';
import { Badge } from 'primereact/badge';
import { Toolbar } from 'primereact/toolbar';
import { Calendar } from 'primereact/calendar';
import { Dropdown } from 'primereact/dropdown';
import { ProgressBar } from 'primereact/progressbar';
import { useRouter } from 'next/navigation';
import { apiClient } from '../../../../services/api-client';
interface StatistiquesMaintenance {
periode: {
debut: string;
fin: string;
};
resume: {
totalMaintenances: number;
maintenancesTerminees: number;
maintenancesEnCours: number;
maintenancesPlanifiees: number;
tauxReussite: number;
coutTotal: number;
dureeMovenneMaintenance: number;
tempsMovenReponse: number;
tempsMovenResolution: number;
};
repartitionParType: Array<{
type: string;
nombre: number;
pourcentage: number;
coutMoyen: number;
}>;
repartitionParPriorite: Array<{
priorite: string;
nombre: number;
pourcentage: number;
tempsMovenResolution: number;
}>;
evolutionMensuelle: Array<{
mois: string;
preventives: number;
correctives: number;
urgentes: number;
cout: number;
}>;
performanceTechniciens: Array<{
technicienId: number;
technicienNom: string;
nombreMaintenances: number;
tauxReussite: number;
dureeMovenne: number;
evaluationMoyenne: number;
specialites: string[];
}>;
materielsProblematiques: Array<{
materielId: number;
materielNom: string;
materielType: string;
nombrePannes: number;
coutMaintenance: number;
tempsArret: number;
dernierePanne: string;
fiabilite: number;
}>;
indicateursPerformance: {
mtbf: number; // Mean Time Between Failures
mttr: number; // Mean Time To Repair
disponibilite: number;
fiabilite: number;
maintenabilite: number;
};
coutParCategorie: Array<{
categorie: string;
cout: number;
pourcentage: number;
}>;
tendances: {
evolutionCouts: number; // pourcentage d'évolution
evolutionNombreMaintenances: number;
evolutionTempsReponse: number;
evolutionDisponibilite: number;
};
}
const StatistiquesMaintenancePage = () => {
const [statistiques, setStatistiques] = useState<StatistiquesMaintenance | null>(null);
const [loading, setLoading] = useState(true);
const [dateDebut, setDateDebut] = useState<Date>(new Date(Date.now() - 6 * 30 * 24 * 60 * 60 * 1000)); // -6 mois
const [dateFin, setDateFin] = useState<Date>(new Date());
const [filterType, setFilterType] = useState<string | null>(null);
const [chartOptions] = useState({
responsive: true,
plugins: {
legend: {
position: 'top' as const,
}
},
scales: {
y: {
beginAtZero: true
}
}
});
const router = useRouter();
const typeOptions = [
{ label: 'Tous', value: null },
{ label: 'Préventive', value: 'PREVENTIVE' },
{ label: 'Corrective', value: 'CORRECTIVE' },
{ label: 'Planifiée', value: 'PLANIFIEE' },
{ label: 'Urgente', value: 'URGENTE' }
];
useEffect(() => {
loadStatistiques();
}, [dateDebut, dateFin, filterType]);
const loadStatistiques = async () => {
try {
setLoading(true);
console.log('🔄 Chargement des statistiques maintenance...');
const params = new URLSearchParams();
params.append('dateDebut', dateDebut.toISOString().split('T')[0]);
params.append('dateFin', dateFin.toISOString().split('T')[0]);
if (filterType) params.append('type', filterType);
const response = await apiClient.get(`/api/maintenances/statistiques?${params.toString()}`);
console.log('✅ Statistiques chargées:', response.data);
setStatistiques(response.data);
} catch (error) {
console.error('❌ Erreur lors du chargement des statistiques:', error);
} finally {
setLoading(false);
}
};
const getEvolutionChartData = () => {
if (!statistiques?.evolutionMensuelle) return {};
return {
labels: statistiques.evolutionMensuelle.map(e => e.mois),
datasets: [
{
label: 'Préventives',
data: statistiques.evolutionMensuelle.map(e => e.preventives),
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4
},
{
label: 'Correctives',
data: statistiques.evolutionMensuelle.map(e => e.correctives),
borderColor: '#f59e0b',
backgroundColor: 'rgba(245, 158, 11, 0.1)',
tension: 0.4
},
{
label: 'Urgentes',
data: statistiques.evolutionMensuelle.map(e => e.urgentes),
borderColor: '#ef4444',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
tension: 0.4
}
]
};
};
const getRepartitionTypeChartData = () => {
if (!statistiques?.repartitionParType) return {};
return {
labels: statistiques.repartitionParType.map(r => r.type),
datasets: [{
data: statistiques.repartitionParType.map(r => r.nombre),
backgroundColor: [
'#3b82f6',
'#f59e0b',
'#10b981',
'#ef4444'
]
}]
};
};
const getCoutChartData = () => {
if (!statistiques?.coutParCategorie) return {};
return {
labels: statistiques.coutParCategorie.map(c => c.categorie),
datasets: [{
data: statistiques.coutParCategorie.map(c => c.cout),
backgroundColor: [
'#8b5cf6',
'#06b6d4',
'#84cc16',
'#f97316',
'#ec4899'
]
}]
};
};
const performanceBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center gap-2">
<ProgressBar value={rowData.tauxReussite} className="flex-1" />
<span className="text-sm font-medium">{rowData.tauxReussite}%</span>
</div>
);
};
const evaluationBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center gap-2">
<span className="font-medium">{rowData.evaluationMoyenne}/5</span>
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<i
key={star}
className={`pi pi-star${star <= rowData.evaluationMoyenne ? '-fill' : ''} text-yellow-500`}
style={{ fontSize: '0.8rem' }}
/>
))}
</div>
</div>
);
};
const fiabiliteBodyTemplate = (rowData: any) => {
const couleur = rowData.fiabilite > 80 ? 'success' :
rowData.fiabilite > 60 ? 'warning' : 'danger';
return (
<div className="flex align-items-center gap-2">
<ProgressBar
value={rowData.fiabilite}
className="flex-1"
color={couleur === 'success' ? '#10b981' : couleur === 'warning' ? '#fbbf24' : '#f87171'}
/>
<span className="text-sm font-medium">{rowData.fiabilite}%</span>
</div>
);
};
const coutBodyTemplate = (rowData: any) => {
return (
<div className="flex flex-column gap-1">
<span className="font-medium text-red-600">
{rowData.coutMaintenance.toLocaleString('fr-FR')}
</span>
<span className="text-sm text-500">
{rowData.nombrePannes} pannes
</span>
</div>
);
};
const leftToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2">
<Button
label="Retour Maintenance"
icon="pi pi-arrow-left"
className="p-button-outlined"
onClick={() => router.push('/maintenance')}
/>
<Button
label="Rapport Détaillé"
icon="pi pi-file-pdf"
className="p-button-info"
onClick={() => router.push('/maintenance/rapport-detaille')}
/>
<Button
label="Exporter Données"
icon="pi pi-download"
className="p-button-secondary"
onClick={() => router.push('/maintenance/export-statistiques')}
/>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<Button
icon="pi pi-refresh"
className="p-button-outlined"
onClick={loadStatistiques}
tooltip="Actualiser"
/>
</div>
);
};
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 (!statistiques) {
return (
<div className="grid">
<div className="col-12">
<Card>
<div className="text-center">
<p>Aucune donnée disponible pour la période sélectionnée</p>
</div>
</Card>
</div>
</div>
);
}
return (
<div className="grid">
<div className="col-12">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
</div>
{/* Filtres */}
<div className="col-12">
<Card className="mb-4">
<div className="grid">
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Date début</label>
<Calendar
value={dateDebut}
onChange={(e) => setDateDebut(e.value as Date)}
showIcon
dateFormat="dd/mm/yy"
/>
</div>
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Date fin</label>
<Calendar
value={dateFin}
onChange={(e) => setDateFin(e.value as Date)}
showIcon
dateFormat="dd/mm/yy"
/>
</div>
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Type</label>
<Dropdown
value={filterType}
options={typeOptions}
onChange={(e) => setFilterType(e.value)}
placeholder="Tous les types"
/>
</div>
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Actions</label>
<Button
label="Réinitialiser"
icon="pi pi-filter-slash"
className="p-button-outlined w-full"
onClick={() => {
setDateDebut(new Date(Date.now() - 6 * 30 * 24 * 60 * 60 * 1000));
setDateFin(new Date());
setFilterType(null);
}}
/>
</div>
</div>
</Card>
</div>
{/* Métriques principales */}
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center justify-content-between">
<div>
<div className="text-2xl font-bold text-blue-600">{statistiques.resume.totalMaintenances}</div>
<div className="text-500">Total maintenances</div>
</div>
<i className="pi pi-wrench text-blue-500 text-3xl" />
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center justify-content-between">
<div>
<div className="text-2xl font-bold text-green-600">{statistiques.resume.tauxReussite}%</div>
<div className="text-500">Taux de réussite</div>
</div>
<i className="pi pi-check-circle text-green-500 text-3xl" />
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center justify-content-between">
<div>
<div className="text-2xl font-bold text-orange-600">
{statistiques.resume.coutTotal.toLocaleString('fr-FR')}
</div>
<div className="text-500">Coût total</div>
</div>
<i className="pi pi-euro text-orange-500 text-3xl" />
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center justify-content-between">
<div>
<div className="text-2xl font-bold text-purple-600">
{statistiques.indicateursPerformance.disponibilite}%
</div>
<div className="text-500">Disponibilité</div>
</div>
<i className="pi pi-chart-line text-purple-500 text-3xl" />
</div>
</Card>
</div>
{/* Graphiques */}
<div className="col-12 lg:col-6">
<Card title="Évolution des Maintenances">
<Chart type="line" data={getEvolutionChartData()} options={chartOptions} />
</Card>
</div>
<div className="col-12 lg:col-6">
<Card title="Répartition par Type">
<Chart type="doughnut" data={getRepartitionTypeChartData()} />
</Card>
</div>
<div className="col-12 lg:col-6">
<Card title="Coûts par Catégorie">
<Chart type="pie" data={getCoutChartData()} />
</Card>
</div>
<div className="col-12 lg:col-6">
<Card title="Indicateurs de Performance">
<div className="grid">
<div className="col-6">
<div className="text-center">
<div className="text-xl font-bold text-blue-600">{statistiques.indicateursPerformance.mtbf}h</div>
<div className="text-500 text-sm">MTBF</div>
</div>
</div>
<div className="col-6">
<div className="text-center">
<div className="text-xl font-bold text-green-600">{statistiques.indicateursPerformance.mttr}h</div>
<div className="text-500 text-sm">MTTR</div>
</div>
</div>
<div className="col-6">
<div className="text-center">
<div className="text-xl font-bold text-orange-600">{statistiques.indicateursPerformance.fiabilite}%</div>
<div className="text-500 text-sm">Fiabilité</div>
</div>
</div>
<div className="col-6">
<div className="text-center">
<div className="text-xl font-bold text-purple-600">{statistiques.indicateursPerformance.maintenabilite}%</div>
<div className="text-500 text-sm">Maintenabilité</div>
</div>
</div>
</div>
</Card>
</div>
{/* Tableaux détaillés */}
<div className="col-12 lg:col-6">
<Card title="Performance des Techniciens">
<DataTable
value={statistiques.performanceTechniciens}
responsiveLayout="scroll"
paginator
rows={5}
>
<Column field="technicienNom" header="Technicien" />
<Column field="nombreMaintenances" header="Nb" />
<Column field="tauxReussite" header="Réussite" body={performanceBodyTemplate} />
<Column field="evaluationMoyenne" header="Évaluation" body={evaluationBodyTemplate} />
</DataTable>
</Card>
</div>
<div className="col-12 lg:col-6">
<Card title="Matériels Problématiques">
<DataTable
value={statistiques.materielsProblematiques}
responsiveLayout="scroll"
paginator
rows={5}
>
<Column field="materielNom" header="Matériel" />
<Column field="nombrePannes" header="Pannes" />
<Column field="fiabilite" header="Fiabilité" body={fiabiliteBodyTemplate} />
<Column field="coutMaintenance" header="Coût" body={coutBodyTemplate} />
</DataTable>
</Card>
</div>
{/* Tendances */}
<div className="col-12">
<Card title="Tendances">
<div className="grid">
<div className="col-12 md:col-3">
<div className="text-center">
<div className={`text-xl font-bold ${statistiques.tendances.evolutionCouts > 0 ? 'text-red-600' : 'text-green-600'}`}>
{statistiques.tendances.evolutionCouts > 0 ? '+' : ''}{statistiques.tendances.evolutionCouts}%
</div>
<div className="text-500">Évolution coûts</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center">
<div className={`text-xl font-bold ${statistiques.tendances.evolutionNombreMaintenances > 0 ? 'text-red-600' : 'text-green-600'}`}>
{statistiques.tendances.evolutionNombreMaintenances > 0 ? '+' : ''}{statistiques.tendances.evolutionNombreMaintenances}%
</div>
<div className="text-500">Évolution nombre</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center">
<div className={`text-xl font-bold ${statistiques.tendances.evolutionTempsReponse > 0 ? 'text-red-600' : 'text-green-600'}`}>
{statistiques.tendances.evolutionTempsReponse > 0 ? '+' : ''}{statistiques.tendances.evolutionTempsReponse}%
</div>
<div className="text-500">Temps réponse</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center">
<div className={`text-xl font-bold ${statistiques.tendances.evolutionDisponibilite > 0 ? 'text-green-600' : 'text-red-600'}`}>
{statistiques.tendances.evolutionDisponibilite > 0 ? '+' : ''}{statistiques.tendances.evolutionDisponibilite}%
</div>
<div className="text-500">Disponibilité</div>
</div>
</div>
</div>
</Card>
</div>
</div>
);
};
export default StatistiquesMaintenancePage;