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

579 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import React, { useState, useEffect } from 'react';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
import { Badge } from 'primereact/badge';
import { Toolbar } from 'primereact/toolbar';
import { TabView, TabPanel } from 'primereact/tabview';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { ProgressBar } from 'primereact/progressbar';
import { Timeline } from 'primereact/timeline';
import { useRouter, useParams } from 'next/navigation';
import { apiClient } from '../../../../services/api-client';
interface MaintenanceDetail {
id: number;
materielId: number;
materielNom: string;
materielType: string;
materielMarque: string;
materielModele: string;
typeMaintenance: 'PREVENTIVE' | 'CORRECTIVE' | 'PLANIFIEE' | 'URGENTE';
statut: 'PLANIFIEE' | 'EN_COURS' | 'TERMINEE' | 'ANNULEE' | 'EN_ATTENTE';
priorite: 'BASSE' | 'NORMALE' | 'HAUTE' | 'CRITIQUE';
dateCreation: string;
datePlanifiee: string;
dateDebut?: string;
dateFin?: string;
technicienId?: number;
technicienNom?: string;
technicienSpecialites?: string[];
description: string;
problemeSignale?: string;
solutionApportee?: string;
dureeEstimee: number;
dureeReelle?: number;
coutEstime?: number;
coutReel?: number;
piecesUtilisees: Array<{
id: number;
nom: string;
reference: string;
quantite: number;
coutUnitaire: number;
cout: number;
fournisseur: string;
}>;
outilsUtilises: Array<{
id: number;
nom: string;
type: string;
dureeUtilisation: number;
}>;
prochaineMaintenance?: string;
observations?: string;
photos: Array<{
id: number;
nom: string;
url: string;
type: 'AVANT' | 'PENDANT' | 'APRES';
dateAjout: string;
}>;
historique: Array<{
id: number;
action: string;
utilisateur: string;
date: string;
commentaire?: string;
}>;
evaluationQualite?: number;
commentaireQualite?: string;
tempsReponse?: number;
tempsResolution?: number;
causeRacine?: string;
actionPreventive?: string;
}
const MaintenanceDetailPage = () => {
const [maintenance, setMaintenance] = useState<MaintenanceDetail | null>(null);
const [loading, setLoading] = useState(true);
const router = useRouter();
const params = useParams();
const maintenanceId = params.id;
useEffect(() => {
if (maintenanceId) {
loadMaintenanceDetail();
}
}, [maintenanceId]);
const loadMaintenanceDetail = async () => {
try {
setLoading(true);
console.log('🔄 Chargement du détail maintenance...', maintenanceId);
const response = await apiClient.get(`/api/maintenances/${maintenanceId}`);
console.log('✅ Détail maintenance chargé:', response.data);
setMaintenance(response.data);
} catch (error) {
console.error('❌ Erreur lors du chargement du détail:', error);
} finally {
setLoading(false);
}
};
const changerStatutMaintenance = async (nouveauStatut: string) => {
if (!maintenance) return;
try {
await apiClient.post(`/api/maintenances/${maintenance.id}/statut`, { statut: nouveauStatut });
setMaintenance({ ...maintenance, statut: nouveauStatut as any });
console.log(`✅ Statut maintenance changé: ${nouveauStatut}`);
} catch (error) {
console.error('❌ Erreur lors du changement de statut:', error);
}
};
const getStatutSeverity = (statut: string) => {
switch (statut) {
case 'PLANIFIEE': return 'info';
case 'EN_COURS': return 'warning';
case 'TERMINEE': return 'success';
case 'ANNULEE': return 'danger';
case 'EN_ATTENTE': return 'secondary';
default: return 'secondary';
}
};
const getTypeSeverity = (type: string) => {
switch (type) {
case 'PREVENTIVE': return 'info';
case 'CORRECTIVE': return 'warning';
case 'PLANIFIEE': return 'success';
case 'URGENTE': return 'danger';
default: return 'secondary';
}
};
const getPrioriteSeverity = (priorite: string) => {
switch (priorite) {
case 'BASSE': return 'info';
case 'NORMALE': return 'success';
case 'HAUTE': return 'warning';
case 'CRITIQUE': return 'danger';
default: return 'secondary';
}
};
const calculerCoutTotal = () => {
if (!maintenance) return 0;
const coutPieces = maintenance.piecesUtilisees.reduce((total, piece) => total + piece.cout, 0);
return (maintenance.coutReel || maintenance.coutEstime || 0) + coutPieces;
};
const calculerEfficacite = () => {
if (!maintenance || !maintenance.dureeReelle) return null;
const efficacite = (maintenance.dureeEstimee / maintenance.dureeReelle) * 100;
return Math.round(efficacite);
};
const pieceBodyTemplate = (rowData: any) => {
return (
<div className="flex flex-column gap-1">
<span className="font-medium">{rowData.nom}</span>
<span className="text-sm text-500">Réf: {rowData.reference}</span>
<span className="text-sm text-500">Fournisseur: {rowData.fournisseur}</span>
</div>
);
};
const coutPieceBodyTemplate = (rowData: any) => {
return (
<div className="flex flex-column gap-1">
<span className="font-medium">{rowData.cout.toLocaleString('fr-FR')} </span>
<span className="text-sm text-500">
{rowData.quantite} × {rowData.coutUnitaire.toLocaleString('fr-FR')}
</span>
</div>
);
};
const photoBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center gap-2">
<img
src={rowData.url}
alt={rowData.nom}
className="w-4rem h-3rem object-cover border-round"
/>
<div>
<div className="font-medium">{rowData.nom}</div>
<Tag value={rowData.type} severity="info" className="p-tag-sm" />
</div>
</div>
);
};
const historiqueItemTemplate = (item: any) => {
return (
<div className="flex flex-column gap-1">
<div className="font-medium">{item.action}</div>
<div className="text-sm text-500">Par {item.utilisateur}</div>
{item.commentaire && (
<div className="text-sm">{item.commentaire}</div>
)}
</div>
);
};
const leftToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2">
<Button
label="Retour"
icon="pi pi-arrow-left"
className="p-button-outlined"
onClick={() => router.push('/maintenance')}
/>
<Button
label="Modifier"
icon="pi pi-pencil"
className="p-button-success"
onClick={() => router.push(`/maintenance/${maintenanceId}/edit`)}
/>
{maintenance?.statut === 'PLANIFIEE' && (
<Button
label="Démarrer"
icon="pi pi-play"
className="p-button-warning"
onClick={() => changerStatutMaintenance('EN_COURS')}
/>
)}
{maintenance?.statut === 'EN_COURS' && (
<Button
label="Terminer"
icon="pi pi-check"
className="p-button-success"
onClick={() => changerStatutMaintenance('TERMINEE')}
/>
)}
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<Button
label="Matériel"
icon="pi pi-cog"
className="p-button-info"
onClick={() => router.push(`/materiels/${maintenance?.materielId}`)}
/>
<Button
label="Planifier Suivante"
icon="pi pi-calendar-plus"
className="p-button-secondary"
onClick={() => router.push(`/maintenance/nouveau?materielId=${maintenance?.materielId}`)}
/>
<Button
icon="pi pi-refresh"
className="p-button-outlined"
onClick={loadMaintenanceDetail}
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 (!maintenance) {
return (
<div className="grid">
<div className="col-12">
<Card>
<div className="text-center">
<p>Maintenance non trouvée</p>
</div>
</Card>
</div>
</div>
);
}
return (
<div className="grid">
<div className="col-12">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
</div>
{/* En-tête maintenance */}
<div className="col-12">
<Card>
<div className="flex flex-column lg:flex-row lg:align-items-center gap-4">
<div className="flex align-items-center justify-content-center bg-orange-100 border-round" style={{ width: '4rem', height: '4rem' }}>
<i className="pi pi-wrench text-orange-500 text-2xl" />
</div>
<div className="flex-1">
<h2 className="m-0 mb-2">Maintenance #{maintenance.id}</h2>
<div className="flex flex-wrap gap-2 mb-2">
<Tag value={maintenance.typeMaintenance} severity={getTypeSeverity(maintenance.typeMaintenance)} />
<Tag value={maintenance.statut} severity={getStatutSeverity(maintenance.statut)} />
<Tag value={maintenance.priorite} severity={getPrioriteSeverity(maintenance.priorite)} />
</div>
<div className="text-600">
<p className="m-0">🔧 {maintenance.materielNom} ({maintenance.materielType})</p>
<p className="m-0">📅 Planifiée: {new Date(maintenance.datePlanifiee).toLocaleDateString('fr-FR')}</p>
{maintenance.technicienNom && <p className="m-0">👤 Technicien: {maintenance.technicienNom}</p>}
</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-blue-500 mb-1">
{calculerCoutTotal().toLocaleString('fr-FR')}
</div>
<div className="text-500 mb-2">Coût total</div>
{calculerEfficacite() && (
<div>
<div className="text-lg font-bold text-green-500">{calculerEfficacite()}%</div>
<div className="text-500">Efficacité</div>
</div>
)}
</div>
</div>
</Card>
</div>
{/* Onglets de détail */}
<div className="col-12">
<Card>
<TabView>
<TabPanel header="Informations Générales">
<div className="grid">
<div className="col-12 md:col-6">
<h4>Détails de la maintenance</h4>
<div className="field">
<label className="font-medium">Description:</label>
<p>{maintenance.description}</p>
</div>
{maintenance.problemeSignale && (
<div className="field">
<label className="font-medium">Problème signalé:</label>
<p className="text-red-600">{maintenance.problemeSignale}</p>
</div>
)}
{maintenance.solutionApportee && (
<div className="field">
<label className="font-medium">Solution apportée:</label>
<p className="text-green-600">{maintenance.solutionApportee}</p>
</div>
)}
{maintenance.observations && (
<div className="field">
<label className="font-medium">Observations:</label>
<p>{maintenance.observations}</p>
</div>
)}
</div>
<div className="col-12 md:col-6">
<h4>Informations matériel</h4>
<div className="field">
<label className="font-medium">Matériel:</label>
<p>{maintenance.materielNom}</p>
</div>
<div className="field">
<label className="font-medium">Type:</label>
<p>{maintenance.materielType}</p>
</div>
<div className="field">
<label className="font-medium">Marque/Modèle:</label>
<p>{maintenance.materielMarque} {maintenance.materielModele}</p>
</div>
{maintenance.prochaineMaintenance && (
<div className="field">
<label className="font-medium">Prochaine maintenance:</label>
<p className="text-orange-500">
{new Date(maintenance.prochaineMaintenance).toLocaleDateString('fr-FR')}
</p>
</div>
)}
</div>
</div>
</TabPanel>
<TabPanel header="Temps et Coûts">
<div className="grid">
<div className="col-12 md:col-6">
<h4>Planification</h4>
<div className="field">
<label className="font-medium">Date de création:</label>
<p>{new Date(maintenance.dateCreation).toLocaleDateString('fr-FR')}</p>
</div>
<div className="field">
<label className="font-medium">Date planifiée:</label>
<p>{new Date(maintenance.datePlanifiee).toLocaleDateString('fr-FR')}</p>
</div>
{maintenance.dateDebut && (
<div className="field">
<label className="font-medium">Date de début:</label>
<p>{new Date(maintenance.dateDebut).toLocaleDateString('fr-FR')}</p>
</div>
)}
{maintenance.dateFin && (
<div className="field">
<label className="font-medium">Date de fin:</label>
<p>{new Date(maintenance.dateFin).toLocaleDateString('fr-FR')}</p>
</div>
)}
</div>
<div className="col-12 md:col-6">
<h4>Durée et coûts</h4>
<div className="field">
<label className="font-medium">Durée estimée:</label>
<p>{maintenance.dureeEstimee}h</p>
</div>
{maintenance.dureeReelle && (
<div className="field">
<label className="font-medium">Durée réelle:</label>
<p className="text-green-600">{maintenance.dureeReelle}h</p>
</div>
)}
<div className="field">
<label className="font-medium">Coût estimé:</label>
<p>{maintenance.coutEstime?.toLocaleString('fr-FR') || 0} </p>
</div>
{maintenance.coutReel && (
<div className="field">
<label className="font-medium">Coût réel:</label>
<p className="text-green-600">{maintenance.coutReel.toLocaleString('fr-FR')} </p>
</div>
)}
{calculerEfficacite() && (
<div className="field">
<label className="font-medium">Efficacité:</label>
<div className="flex align-items-center gap-2">
<ProgressBar value={calculerEfficacite()!} className="flex-1" />
<span>{calculerEfficacite()}%</span>
</div>
</div>
)}
</div>
</div>
</TabPanel>
<TabPanel header="Pièces et Outils">
<div className="grid">
<div className="col-12">
<h4>Pièces utilisées</h4>
<DataTable
value={maintenance.piecesUtilisees}
emptyMessage="Aucune pièce utilisée"
responsiveLayout="scroll"
>
<Column field="nom" header="Pièce" body={pieceBodyTemplate} />
<Column field="quantite" header="Quantité" />
<Column field="cout" header="Coût" body={coutPieceBodyTemplate} />
</DataTable>
</div>
<div className="col-12 mt-4">
<h4>Outils utilisés</h4>
<DataTable
value={maintenance.outilsUtilises}
emptyMessage="Aucun outil spécifique"
responsiveLayout="scroll"
>
<Column field="nom" header="Outil" />
<Column field="type" header="Type" />
<Column field="dureeUtilisation" header="Durée (h)" />
</DataTable>
</div>
</div>
</TabPanel>
<TabPanel header="Photos">
<DataTable
value={maintenance.photos}
emptyMessage="Aucune photo disponible"
responsiveLayout="scroll"
>
<Column field="nom" header="Photo" body={photoBodyTemplate} />
<Column field="type" header="Type" />
<Column
field="dateAjout"
header="Date"
body={(rowData) => new Date(rowData.dateAjout).toLocaleDateString('fr-FR')}
/>
</DataTable>
</TabPanel>
<TabPanel header="Historique">
<Timeline
value={maintenance.historique}
content={historiqueItemTemplate}
opposite={(item) => new Date(item.date).toLocaleDateString('fr-FR')}
/>
</TabPanel>
<TabPanel header="Qualité">
<div className="grid">
<div className="col-12 md:col-6">
<h4>Évaluation qualité</h4>
{maintenance.evaluationQualite ? (
<div>
<div className="flex align-items-center gap-2 mb-2">
<span className="text-2xl font-bold text-green-500">
{maintenance.evaluationQualite}/5
</span>
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<i
key={star}
className={`pi pi-star${star <= maintenance.evaluationQualite! ? '-fill' : ''} text-yellow-500`}
/>
))}
</div>
</div>
{maintenance.commentaireQualite && (
<p>{maintenance.commentaireQualite}</p>
)}
</div>
) : (
<p className="text-500">Évaluation en attente</p>
)}
</div>
<div className="col-12 md:col-6">
<h4>Analyse</h4>
{maintenance.causeRacine && (
<div className="field">
<label className="font-medium">Cause racine:</label>
<p>{maintenance.causeRacine}</p>
</div>
)}
{maintenance.actionPreventive && (
<div className="field">
<label className="font-medium">Action préventive:</label>
<p className="text-blue-600">{maintenance.actionPreventive}</p>
</div>
)}
{maintenance.tempsReponse && (
<div className="field">
<label className="font-medium">Temps de réponse:</label>
<p>{maintenance.tempsReponse} minutes</p>
</div>
)}
{maintenance.tempsResolution && (
<div className="field">
<label className="font-medium">Temps de résolution:</label>
<p>{maintenance.tempsResolution} minutes</p>
</div>
)}
</div>
</div>
</TabPanel>
</TabView>
</Card>
</div>
</div>
);
};
export default MaintenanceDetailPage;