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

720 lines
33 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 { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { Dropdown } from 'primereact/dropdown';
import { Calendar } from 'primereact/calendar';
import { InputNumber } from 'primereact/inputnumber';
import { Button } from 'primereact/button';
import { Toolbar } from 'primereact/toolbar';
import { Message } from 'primereact/message';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Dialog } from 'primereact/dialog';
import { useRouter, useParams } from 'next/navigation';
import { apiClient } from '../../../../../services/api-client';
interface MaintenanceEdit {
typeMaintenance: 'PREVENTIVE' | 'CORRECTIVE' | 'PLANIFIEE' | 'URGENTE';
priorite: 'BASSE' | 'NORMALE' | 'HAUTE' | 'CRITIQUE';
datePlanifiee: Date | null;
technicienId?: number;
description: string;
problemeSignale?: string;
solutionApportee?: string;
coutEstime?: number;
coutReel?: number;
dureeEstimee: number;
dureeReelle?: number;
observations?: string;
evaluationQualite?: number;
commentaireQualite?: string;
causeRacine?: string;
actionPreventive?: string;
}
interface Piece {
id: number;
nom: string;
reference: string;
quantite: number;
coutUnitaire: number;
cout: number;
fournisseur: string;
}
const EditMaintenancePage = () => {
const [maintenance, setMaintenance] = useState<MaintenanceEdit>({
typeMaintenance: 'PREVENTIVE',
priorite: 'NORMALE',
datePlanifiee: null,
description: '',
dureeEstimee: 2
});
const [maintenanceOriginale, setMaintenanceOriginale] = useState<any>(null);
const [techniciens, setTechniciens] = useState<any[]>([]);
const [pieces, setPieces] = useState<Piece[]>([]);
const [pieceDialog, setPieceDialog] = useState(false);
const [nouvellePiece, setNouvellePiece] = useState<Piece>({
id: 0,
nom: '',
reference: '',
quantite: 1,
coutUnitaire: 0,
cout: 0,
fournisseur: ''
});
const [loading, setLoading] = useState(false);
const [loadingData, setLoadingData] = useState(true);
const [errors, setErrors] = useState<{ [key: string]: string }>({});
const router = useRouter();
const params = useParams();
const maintenanceId = params.id;
const typeOptions = [
{ label: 'Préventive', value: 'PREVENTIVE' },
{ label: 'Corrective', value: 'CORRECTIVE' },
{ label: 'Planifiée', value: 'PLANIFIEE' },
{ label: 'Urgente', value: 'URGENTE' }
];
const prioriteOptions = [
{ label: 'Basse', value: 'BASSE' },
{ label: 'Normale', value: 'NORMALE' },
{ label: 'Haute', value: 'HAUTE' },
{ label: 'Critique', value: 'CRITIQUE' }
];
useEffect(() => {
if (maintenanceId) {
loadMaintenanceData();
loadTechniciens();
}
}, [maintenanceId]);
const loadMaintenanceData = async () => {
try {
console.log('🔄 Chargement des données maintenance...', maintenanceId);
const response = await apiClient.get(`/api/maintenances/${maintenanceId}`);
const maintenanceData = response.data;
console.log('✅ Données maintenance chargées:', maintenanceData);
setMaintenanceOriginale(maintenanceData);
setMaintenance({
typeMaintenance: maintenanceData.typeMaintenance,
priorite: maintenanceData.priorite,
datePlanifiee: new Date(maintenanceData.datePlanifiee),
technicienId: maintenanceData.technicienId,
description: maintenanceData.description,
problemeSignale: maintenanceData.problemeSignale || '',
solutionApportee: maintenanceData.solutionApportee || '',
coutEstime: maintenanceData.coutEstime,
coutReel: maintenanceData.coutReel,
dureeEstimee: maintenanceData.dureeEstimee,
dureeReelle: maintenanceData.dureeReelle,
observations: maintenanceData.observations || '',
evaluationQualite: maintenanceData.evaluationQualite,
commentaireQualite: maintenanceData.commentaireQualite || '',
causeRacine: maintenanceData.causeRacine || '',
actionPreventive: maintenanceData.actionPreventive || ''
});
setPieces(maintenanceData.piecesUtilisees || []);
} catch (error) {
console.error('❌ Erreur lors du chargement:', error);
} finally {
setLoadingData(false);
}
};
const loadTechniciens = async () => {
try {
const response = await apiClient.get('/api/employes/techniciens');
setTechniciens(response.data || []);
} catch (error) {
console.error('❌ Erreur lors du chargement des techniciens:', error);
}
};
const validateForm = () => {
const newErrors: { [key: string]: string } = {};
if (!maintenance.description.trim()) {
newErrors.description = 'La description est requise';
}
if (!maintenance.datePlanifiee) {
newErrors.datePlanifiee = 'La date planifiée est requise';
}
if (maintenance.dureeEstimee <= 0) {
newErrors.dureeEstimee = 'La durée estimée doit être positive';
}
if (maintenance.typeMaintenance === 'CORRECTIVE' && !maintenance.problemeSignale?.trim()) {
newErrors.problemeSignale = 'Le problème signalé est requis pour une maintenance corrective';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
return;
}
try {
setLoading(true);
console.log('🔄 Mise à jour de la maintenance...', maintenance);
const maintenanceData = {
...maintenance,
datePlanifiee: maintenance.datePlanifiee?.toISOString().split('T')[0],
piecesUtilisees: pieces
};
const response = await apiClient.put(`/api/maintenances/${maintenanceId}`, maintenanceData);
console.log('✅ Maintenance mise à jour avec succès:', response.data);
router.push(`/maintenance/${maintenanceId}`);
} catch (error) {
console.error('❌ Erreur lors de la mise à jour:', error);
} finally {
setLoading(false);
}
};
const ajouterPiece = () => {
if (nouvellePiece.nom && nouvellePiece.quantite > 0) {
const piece = {
...nouvellePiece,
id: Date.now(), // ID temporaire
cout: nouvellePiece.quantite * nouvellePiece.coutUnitaire
};
setPieces([...pieces, piece]);
setNouvellePiece({
id: 0,
nom: '',
reference: '',
quantite: 1,
coutUnitaire: 0,
cout: 0,
fournisseur: ''
});
setPieceDialog(false);
}
};
const supprimerPiece = (pieceId: number) => {
setPieces(pieces.filter(p => p.id !== pieceId));
};
const technicienOptionTemplate = (option: any) => {
return (
<div className="flex align-items-center gap-2">
<div>
<div className="font-medium">{option.prenom} {option.nom}</div>
<div className="text-sm text-500">{option.specialites?.join(', ')}</div>
</div>
</div>
);
};
const pieceBodyTemplate = (rowData: Piece) => {
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>
</div>
);
};
const coutPieceBodyTemplate = (rowData: Piece) => {
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 actionPieceBodyTemplate = (rowData: Piece) => {
return (
<Button
icon="pi pi-trash"
className="p-button-rounded p-button-danger p-button-sm"
onClick={() => supprimerPiece(rowData.id)}
tooltip="Supprimer"
/>
);
};
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/${maintenanceId}`)}
/>
<Button
label="Annuler"
icon="pi pi-times"
className="p-button-outlined"
onClick={() => router.push(`/maintenance/${maintenanceId}`)}
/>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<Button
label="Réinitialiser"
icon="pi pi-refresh"
className="p-button-outlined"
onClick={() => {
if (maintenanceOriginale) {
setMaintenance({
typeMaintenance: maintenanceOriginale.typeMaintenance,
priorite: maintenanceOriginale.priorite,
datePlanifiee: new Date(maintenanceOriginale.datePlanifiee),
technicienId: maintenanceOriginale.technicienId,
description: maintenanceOriginale.description,
problemeSignale: maintenanceOriginale.problemeSignale || '',
solutionApportee: maintenanceOriginale.solutionApportee || '',
coutEstime: maintenanceOriginale.coutEstime,
coutReel: maintenanceOriginale.coutReel,
dureeEstimee: maintenanceOriginale.dureeEstimee,
dureeReelle: maintenanceOriginale.dureeReelle,
observations: maintenanceOriginale.observations || '',
evaluationQualite: maintenanceOriginale.evaluationQualite,
commentaireQualite: maintenanceOriginale.commentaireQualite || '',
causeRacine: maintenanceOriginale.causeRacine || '',
actionPreventive: maintenanceOriginale.actionPreventive || ''
});
setPieces(maintenanceOriginale.piecesUtilisees || []);
}
}}
/>
<Button
label="Enregistrer"
icon="pi pi-check"
className="p-button-success"
loading={loading}
onClick={handleSubmit}
/>
</div>
);
};
if (loadingData) {
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>
);
}
return (
<div className="grid">
<div className="col-12">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
</div>
<div className="col-12 lg:col-8">
<Card title={`Modifier la maintenance #${maintenanceId}`}>
<form onSubmit={handleSubmit} className="p-fluid">
<div className="grid">
<div className="col-12 md:col-6">
<div className="field">
<label htmlFor="type" className="font-medium">
Type de maintenance *
</label>
<Dropdown
id="type"
value={maintenance.typeMaintenance}
options={typeOptions}
onChange={(e) => setMaintenance({ ...maintenance, typeMaintenance: e.value })}
placeholder="Sélectionner le type"
/>
</div>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label htmlFor="priorite" className="font-medium">
Priorité *
</label>
<Dropdown
id="priorite"
value={maintenance.priorite}
options={prioriteOptions}
onChange={(e) => setMaintenance({ ...maintenance, priorite: e.value })}
placeholder="Sélectionner la priorité"
/>
</div>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label htmlFor="datePlanifiee" className="font-medium">
Date planifiée *
</label>
<Calendar
id="datePlanifiee"
value={maintenance.datePlanifiee}
onChange={(e) => setMaintenance({ ...maintenance, datePlanifiee: e.value as Date })}
showIcon
dateFormat="dd/mm/yy"
placeholder="Sélectionner une date"
className={errors.datePlanifiee ? 'p-invalid' : ''}
/>
{errors.datePlanifiee && <small className="p-error">{errors.datePlanifiee}</small>}
</div>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label htmlFor="technicien" className="font-medium">
Technicien assigné
</label>
<Dropdown
id="technicien"
value={maintenance.technicienId}
options={techniciens}
onChange={(e) => setMaintenance({ ...maintenance, technicienId: e.value })}
optionLabel="nom"
optionValue="id"
placeholder="Sélectionner un technicien"
itemTemplate={technicienOptionTemplate}
filter
showClear
/>
</div>
</div>
<div className="col-12">
<div className="field">
<label htmlFor="description" className="font-medium">
Description *
</label>
<InputTextarea
id="description"
value={maintenance.description}
onChange={(e) => setMaintenance({ ...maintenance, description: e.target.value })}
rows={3}
placeholder="Description détaillée de la maintenance..."
className={errors.description ? 'p-invalid' : ''}
/>
{errors.description && <small className="p-error">{errors.description}</small>}
</div>
</div>
{maintenance.typeMaintenance === 'CORRECTIVE' && (
<div className="col-12">
<div className="field">
<label htmlFor="probleme" className="font-medium">
Problème signalé *
</label>
<InputTextarea
id="probleme"
value={maintenance.problemeSignale || ''}
onChange={(e) => setMaintenance({ ...maintenance, problemeSignale: e.target.value })}
rows={2}
placeholder="Description du problème rencontré..."
className={errors.problemeSignale ? 'p-invalid' : ''}
/>
{errors.problemeSignale && <small className="p-error">{errors.problemeSignale}</small>}
</div>
</div>
)}
{maintenanceOriginale?.statut === 'EN_COURS' || maintenanceOriginale?.statut === 'TERMINEE' ? (
<div className="col-12">
<div className="field">
<label htmlFor="solution" className="font-medium">
Solution apportée
</label>
<InputTextarea
id="solution"
value={maintenance.solutionApportee || ''}
onChange={(e) => setMaintenance({ ...maintenance, solutionApportee: e.target.value })}
rows={3}
placeholder="Décrivez la solution mise en œuvre..."
/>
</div>
</div>
) : null}
<div className="col-12 md:col-3">
<div className="field">
<label htmlFor="dureeEstimee" className="font-medium">
Durée estimée (h) *
</label>
<InputNumber
id="dureeEstimee"
value={maintenance.dureeEstimee}
onValueChange={(e) => setMaintenance({ ...maintenance, dureeEstimee: e.value || 0 })}
min={0.5}
max={100}
step={0.5}
suffix=" h"
className={errors.dureeEstimee ? 'p-invalid' : ''}
/>
{errors.dureeEstimee && <small className="p-error">{errors.dureeEstimee}</small>}
</div>
</div>
{maintenanceOriginale?.statut === 'TERMINEE' && (
<div className="col-12 md:col-3">
<div className="field">
<label htmlFor="dureeReelle" className="font-medium">
Durée réelle (h)
</label>
<InputNumber
id="dureeReelle"
value={maintenance.dureeReelle}
onValueChange={(e) => setMaintenance({ ...maintenance, dureeReelle: e.value || undefined })}
min={0.5}
max={100}
step={0.5}
suffix=" h"
/>
</div>
</div>
)}
<div className="col-12 md:col-3">
<div className="field">
<label htmlFor="coutEstime" className="font-medium">
Coût estimé ()
</label>
<InputNumber
id="coutEstime"
value={maintenance.coutEstime}
onValueChange={(e) => setMaintenance({ ...maintenance, coutEstime: e.value || undefined })}
min={0}
mode="currency"
currency="EUR"
locale="fr-FR"
/>
</div>
</div>
{maintenanceOriginale?.statut === 'TERMINEE' && (
<div className="col-12 md:col-3">
<div className="field">
<label htmlFor="coutReel" className="font-medium">
Coût réel ()
</label>
<InputNumber
id="coutReel"
value={maintenance.coutReel}
onValueChange={(e) => setMaintenance({ ...maintenance, coutReel: e.value || undefined })}
min={0}
mode="currency"
currency="EUR"
locale="fr-FR"
/>
</div>
</div>
)}
<div className="col-12">
<div className="field">
<label htmlFor="observations" className="font-medium">
Observations
</label>
<InputTextarea
id="observations"
value={maintenance.observations || ''}
onChange={(e) => setMaintenance({ ...maintenance, observations: e.target.value })}
rows={2}
placeholder="Observations particulières..."
/>
</div>
</div>
{/* Analyse post-maintenance */}
{maintenanceOriginale?.statut === 'TERMINEE' && (
<>
<div className="col-12">
<h4>Analyse post-maintenance</h4>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label htmlFor="causeRacine" className="font-medium">
Cause racine
</label>
<InputTextarea
id="causeRacine"
value={maintenance.causeRacine || ''}
onChange={(e) => setMaintenance({ ...maintenance, causeRacine: e.target.value })}
rows={2}
placeholder="Identifiez la cause racine du problème..."
/>
</div>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label htmlFor="actionPreventive" className="font-medium">
Action préventive
</label>
<InputTextarea
id="actionPreventive"
value={maintenance.actionPreventive || ''}
onChange={(e) => setMaintenance({ ...maintenance, actionPreventive: e.target.value })}
rows={2}
placeholder="Proposez une action pour éviter la récurrence..."
/>
</div>
</div>
</>
)}
</div>
</form>
</Card>
{/* Pièces utilisées */}
<Card title="Pièces utilisées" className="mt-4">
<div className="flex justify-content-end mb-3">
<Button
label="Ajouter une pièce"
icon="pi pi-plus"
className="p-button-success"
onClick={() => setPieceDialog(true)}
/>
</div>
<DataTable
value={pieces}
emptyMessage="Aucune pièce utilisée"
responsiveLayout="scroll"
>
<Column field="nom" header="Pièce" body={pieceBodyTemplate} />
<Column field="quantite" header="Quantité" />
<Column field="fournisseur" header="Fournisseur" />
<Column field="cout" header="Coût" body={coutPieceBodyTemplate} />
<Column body={actionPieceBodyTemplate} header="Actions" />
</DataTable>
</Card>
</div>
<div className="col-12 lg:col-4">
<Card title="Informations">
<div className="text-sm">
<p className="mb-2">
<i className="pi pi-info-circle text-blue-500 mr-2" />
Modifiez les informations selon l'avancement de la maintenance.
</p>
<p className="mb-2">
<i className="pi pi-clock text-orange-500 mr-2" />
Les durées et coûts réels ne sont modifiables qu'après finalisation.
</p>
<p className="mb-2">
<i className="pi pi-cog text-green-500 mr-2" />
Ajoutez les pièces utilisées pour un suivi précis des coûts.
</p>
<p>
<i className="pi pi-exclamation-triangle text-red-500 mr-2" />
L'analyse post-maintenance aide à prévenir les récurrences.
</p>
</div>
</Card>
</div>
{/* Dialog Ajouter Pièce */}
<Dialog
visible={pieceDialog}
style={{ width: '50vw' }}
header="Ajouter une pièce"
modal
onHide={() => setPieceDialog(false)}
footer={
<div>
<Button
label="Annuler"
icon="pi pi-times"
className="p-button-outlined"
onClick={() => setPieceDialog(false)}
/>
<Button
label="Ajouter"
icon="pi pi-check"
className="p-button-success"
onClick={ajouterPiece}
/>
</div>
}
>
<div className="grid p-fluid">
<div className="col-12 md:col-6">
<label className="font-medium">Nom de la pièce *</label>
<InputText
value={nouvellePiece.nom}
onChange={(e) => setNouvellePiece({ ...nouvellePiece, nom: e.target.value })}
placeholder="Nom de la pièce"
/>
</div>
<div className="col-12 md:col-6">
<label className="font-medium">Référence</label>
<InputText
value={nouvellePiece.reference}
onChange={(e) => setNouvellePiece({ ...nouvellePiece, reference: e.target.value })}
placeholder="Référence"
/>
</div>
<div className="col-12 md:col-4">
<label className="font-medium">Quantité *</label>
<InputNumber
value={nouvellePiece.quantite}
onValueChange={(e) => setNouvellePiece({ ...nouvellePiece, quantite: e.value || 1 })}
min={1}
/>
</div>
<div className="col-12 md:col-4">
<label className="font-medium">Coût unitaire ()</label>
<InputNumber
value={nouvellePiece.coutUnitaire}
onValueChange={(e) => setNouvellePiece({ ...nouvellePiece, coutUnitaire: e.value || 0 })}
min={0}
mode="currency"
currency="EUR"
locale="fr-FR"
/>
</div>
<div className="col-12 md:col-4">
<label className="font-medium">Fournisseur</label>
<InputText
value={nouvellePiece.fournisseur}
onChange={(e) => setNouvellePiece({ ...nouvellePiece, fournisseur: e.target.value })}
placeholder="Fournisseur"
/>
</div>
</div>
</Dialog>
</div>
);
};
export default EditMaintenancePage;