Files
btpxpress-frontend/app/(main)/maintenance/signaler-panne/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

652 lines
30 KiB
TypeScript

'use client';
export const dynamic = 'force-dynamic';
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 { Button } from 'primereact/button';
import { Toolbar } from 'primereact/toolbar';
import { Message } from 'primereact/message';
import { FileUpload } from 'primereact/fileupload';
import { Tag } from 'primereact/tag';
import { Steps } from 'primereact/steps';
import { useRouter } from 'next/navigation';
import { apiClient } from '../../../../services/api-client';
interface SignalementPanne {
materielId: number;
chantierImpacte?: number;
gravite: 'MINEURE' | 'MODEREE' | 'MAJEURE' | 'CRITIQUE';
impactProduction: 'AUCUN' | 'FAIBLE' | 'MOYEN' | 'ELEVE' | 'ARRET_TOTAL';
problemeSignale: string;
symptomesObserves: string;
circonstancesApparition: string;
mesuresTemporaires?: string;
personneSignalement: string;
contactUrgence: string;
photos: File[];
prioriteUrgence: boolean;
interventionImmediate: boolean;
}
interface Materiel {
id: number;
nom: string;
type: string;
marque: string;
modele: string;
localisation: string;
statut: string;
}
interface Chantier {
id: number;
nom: string;
localisation: string;
statut: string;
}
const SignalerPannePage = () => {
const [signalement, setSignalement] = useState<SignalementPanne>({
materielId: 0,
gravite: 'MODEREE',
impactProduction: 'MOYEN',
problemeSignale: '',
symptomesObserves: '',
circonstancesApparition: '',
personneSignalement: '',
contactUrgence: '',
photos: [],
prioriteUrgence: false,
interventionImmediate: false
});
const [materiels, setMateriels] = useState<Materiel[]>([]);
const [chantiers, setChantiers] = useState<Chantier[]>([]);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<{ [key: string]: string }>({});
const [etapeActuelle, setEtapeActuelle] = useState(0);
const [signalementEnvoye, setSignalementEnvoye] = useState(false);
const router = useRouter();
const graviteOptions = [
{ label: 'Mineure - Fonctionnement dégradé', value: 'MINEURE' },
{ label: 'Modérée - Dysfonctionnement notable', value: 'MODEREE' },
{ label: 'Majeure - Panne importante', value: 'MAJEURE' },
{ label: 'Critique - Arrêt complet', value: 'CRITIQUE' }
];
const impactOptions = [
{ label: 'Aucun impact', value: 'AUCUN' },
{ label: 'Impact faible', value: 'FAIBLE' },
{ label: 'Impact moyen', value: 'MOYEN' },
{ label: 'Impact élevé', value: 'ELEVE' },
{ label: 'Arrêt total de production', value: 'ARRET_TOTAL' }
];
const etapes = [
{ label: 'Matériel' },
{ label: 'Problème' },
{ label: 'Impact' },
{ label: 'Contact' },
{ label: 'Confirmation' }
];
useEffect(() => {
loadMateriels();
loadChantiers();
}, []);
const loadMateriels = async () => {
try {
console.log('🔄 Chargement des matériels...');
const response = await apiClient.get('/api/materiels');
console.log('✅ Matériels chargés:', response.data);
setMateriels(response.data || []);
} catch (error) {
console.error('❌ Erreur lors du chargement des matériels:', error);
}
};
const loadChantiers = async () => {
try {
const response = await apiClient.get('/api/chantiers');
setChantiers(response.data || []);
} catch (error) {
console.error('❌ Erreur lors du chargement des chantiers:', error);
}
};
const validateEtape = (etape: number) => {
const newErrors: { [key: string]: string } = {};
switch (etape) {
case 0: // Matériel
if (!signalement.materielId) {
newErrors.materielId = 'Le matériel est requis';
}
break;
case 1: // Problème
if (!signalement.problemeSignale.trim()) {
newErrors.problemeSignale = 'La description du problème est requise';
}
if (!signalement.symptomesObserves.trim()) {
newErrors.symptomesObserves = 'Les symptômes observés sont requis';
}
break;
case 2: // Impact
if (!signalement.circonstancesApparition.trim()) {
newErrors.circonstancesApparition = 'Les circonstances d\'apparition sont requises';
}
break;
case 3: // Contact
if (!signalement.personneSignalement.trim()) {
newErrors.personneSignalement = 'Le nom de la personne est requis';
}
if (!signalement.contactUrgence.trim()) {
newErrors.contactUrgence = 'Le contact d\'urgence est requis';
}
break;
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const etapeSuivante = () => {
if (validateEtape(etapeActuelle)) {
setEtapeActuelle(etapeActuelle + 1);
}
};
const etapePrecedente = () => {
setEtapeActuelle(etapeActuelle - 1);
};
const handleSubmit = async () => {
if (!validateEtape(3)) {
return;
}
try {
setLoading(true);
console.log('🔄 Envoi du signalement de panne...', signalement);
// Créer FormData pour inclure les photos
const formData = new FormData();
Object.entries(signalement).forEach(([key, value]) => {
if (key === 'photos') {
signalement.photos.forEach((photo, index) => {
formData.append(`photo_${index}`, photo);
});
} else {
formData.append(key, value.toString());
}
});
const response = await apiClient.post('/api/maintenances/signaler-panne', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
console.log('✅ Signalement envoyé avec succès:', response.data);
setSignalementEnvoye(true);
setEtapeActuelle(4);
} catch (error) {
console.error('❌ Erreur lors de l\'envoi du signalement:', error);
} finally {
setLoading(false);
}
};
const materielOptionTemplate = (option: Materiel) => {
return (
<div className="flex align-items-center gap-2">
<div>
<div className="font-medium">{option.nom}</div>
<div className="text-sm text-500">{option.type} - {option.marque} {option.modele}</div>
<div className="text-sm text-500">📍 {option.localisation}</div>
</div>
<Tag
value={option.statut}
severity={option.statut === 'DISPONIBLE' ? 'success' : 'warning'}
/>
</div>
);
};
const chantierOptionTemplate = (option: Chantier) => {
return (
<div className="flex align-items-center gap-2">
<div>
<div className="font-medium">{option.nom}</div>
<div className="text-sm text-500">📍 {option.localisation}</div>
</div>
<Tag value={option.statut} severity="info" />
</div>
);
};
const onPhotoUpload = (event: any) => {
const files = Array.from(event.files) as File[];
setSignalement({ ...signalement, photos: [...signalement.photos, ...files] });
};
const supprimerPhoto = (index: number) => {
const nouvellesPhotos = signalement.photos.filter((_, i) => i !== index);
setSignalement({ ...signalement, photos: nouvellesPhotos });
};
const getGraviteSeverity = (gravite: string) => {
switch (gravite) {
case 'MINEURE': return 'info';
case 'MODEREE': return 'warning';
case 'MAJEURE': return 'danger';
case 'CRITIQUE': return 'danger';
default: return 'secondary';
}
};
const getImpactSeverity = (impact: string) => {
switch (impact) {
case 'AUCUN': return 'success';
case 'FAIBLE': return 'info';
case 'MOYEN': return 'warning';
case 'ELEVE': return 'danger';
case 'ARRET_TOTAL': return 'danger';
default: return 'secondary';
}
};
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')}
/>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<Button
label="Urgence"
icon="pi pi-exclamation-triangle"
className="p-button-danger"
onClick={() => router.push('/maintenance/urgence')}
/>
</div>
);
};
if (signalementEnvoye) {
return (
<div className="grid">
<div className="col-12">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
</div>
<div className="col-12">
<Card>
<div className="text-center">
<i className="pi pi-check-circle text-green-500 text-6xl mb-4" />
<h2 className="text-green-600 mb-4">Signalement envoyé avec succès !</h2>
<p className="text-lg mb-4">
Votre signalement de panne a é transmis à l'équipe de maintenance.
</p>
<p className="text-500 mb-4">
Un technicien sera assigné dans les plus brefs délais selon la priorité définie.
</p>
<div className="flex gap-2 justify-content-center">
<Button
label="Retour à la maintenance"
icon="pi pi-arrow-left"
className="p-button-outlined"
onClick={() => router.push('/maintenance')}
/>
<Button
label="Nouveau signalement"
icon="pi pi-plus"
className="p-button-success"
onClick={() => {
setSignalementEnvoye(false);
setEtapeActuelle(0);
setSignalement({
materielId: 0,
gravite: 'MODEREE',
impactProduction: 'MOYEN',
problemeSignale: '',
symptomesObserves: '',
circonstancesApparition: '',
personneSignalement: '',
contactUrgence: '',
photos: [],
prioriteUrgence: false,
interventionImmediate: false
});
}}
/>
</div>
</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">
<Card title="🚨 Signaler une Panne">
<Steps model={etapes} activeIndex={etapeActuelle} className="mb-4" />
{etapeActuelle === 0 && (
<div className="p-fluid">
<h4>Sélection du matériel</h4>
<div className="field">
<label htmlFor="materiel" className="font-medium">
Matériel en panne *
</label>
<Dropdown
id="materiel"
value={signalement.materielId}
options={materiels}
onChange={(e) => setSignalement({ ...signalement, materielId: e.value })}
optionLabel="nom"
optionValue="id"
placeholder="Sélectionner le matériel en panne"
itemTemplate={materielOptionTemplate}
className={errors.materielId ? 'p-invalid' : ''}
filter
/>
{errors.materielId && <small className="p-error">{errors.materielId}</small>}
</div>
<div className="field">
<label htmlFor="chantier" className="font-medium">
Chantier impacté (optionnel)
</label>
<Dropdown
id="chantier"
value={signalement.chantierImpacte}
options={chantiers}
onChange={(e) => setSignalement({ ...signalement, chantierImpacte: e.value })}
optionLabel="nom"
optionValue="id"
placeholder="Sélectionner le chantier impacté"
itemTemplate={chantierOptionTemplate}
filter
showClear
/>
</div>
</div>
)}
{etapeActuelle === 1 && (
<div className="p-fluid">
<h4>Description du problème</h4>
<div className="field">
<label htmlFor="probleme" className="font-medium">
Description du problème *
</label>
<InputTextarea
id="probleme"
value={signalement.problemeSignale}
onChange={(e) => setSignalement({ ...signalement, problemeSignale: e.target.value })}
rows={4}
placeholder="Décrivez précisément le problème rencontré..."
className={errors.problemeSignale ? 'p-invalid' : ''}
/>
{errors.problemeSignale && <small className="p-error">{errors.problemeSignale}</small>}
</div>
<div className="field">
<label htmlFor="symptomes" className="font-medium">
Symptômes observés *
</label>
<InputTextarea
id="symptomes"
value={signalement.symptomesObserves}
onChange={(e) => setSignalement({ ...signalement, symptomesObserves: e.target.value })}
rows={3}
placeholder="Bruits anormaux, fumée, vibrations, arrêt soudain..."
className={errors.symptomesObserves ? 'p-invalid' : ''}
/>
{errors.symptomesObserves && <small className="p-error">{errors.symptomesObserves}</small>}
</div>
<div className="field">
<label className="font-medium">Photos (optionnel)</label>
<FileUpload
mode="basic"
accept="image/*"
maxFileSize={5000000}
multiple
onSelect={onPhotoUpload}
chooseLabel="Ajouter des photos"
/>
{signalement.photos.length > 0 && (
<div className="flex flex-wrap gap-2 mt-2">
{signalement.photos.map((photo, index) => (
<div key={index} className="relative">
<img
src={URL.createObjectURL(photo)}
alt={`Photo ${index + 1}`}
className="w-6rem h-4rem object-cover border-round"
/>
<Button
icon="pi pi-times"
className="p-button-rounded p-button-danger p-button-sm absolute"
style={{ top: '-0.5rem', right: '-0.5rem' }}
onClick={() => supprimerPhoto(index)}
/>
</div>
))}
</div>
)}
</div>
</div>
)}
{etapeActuelle === 2 && (
<div className="p-fluid">
<h4>Évaluation de l'impact</h4>
<div className="field">
<label htmlFor="gravite" className="font-medium">
Gravité de la panne *
</label>
<Dropdown
id="gravite"
value={signalement.gravite}
options={graviteOptions}
onChange={(e) => setSignalement({ ...signalement, gravite: e.value })}
placeholder="Évaluer la gravité"
/>
</div>
<div className="field">
<label htmlFor="impact" className="font-medium">
Impact sur la production *
</label>
<Dropdown
id="impact"
value={signalement.impactProduction}
options={impactOptions}
onChange={(e) => setSignalement({ ...signalement, impactProduction: e.value })}
placeholder="Évaluer l'impact"
/>
</div>
<div className="field">
<label htmlFor="circonstances" className="font-medium">
Circonstances d'apparition *
</label>
<InputTextarea
id="circonstances"
value={signalement.circonstancesApparition}
onChange={(e) => setSignalement({ ...signalement, circonstancesApparition: e.target.value })}
rows={3}
placeholder="Quand et comment le problème est-il apparu ? Conditions météo, charge de travail..."
className={errors.circonstancesApparition ? 'p-invalid' : ''}
/>
{errors.circonstancesApparition && <small className="p-error">{errors.circonstancesApparition}</small>}
</div>
<div className="field">
<label htmlFor="mesures" className="font-medium">
Mesures temporaires prises (optionnel)
</label>
<InputTextarea
id="mesures"
value={signalement.mesuresTemporaires || ''}
onChange={(e) => setSignalement({ ...signalement, mesuresTemporaires: e.target.value })}
rows={2}
placeholder="Actions déjà entreprises pour limiter l'impact..."
/>
</div>
</div>
)}
{etapeActuelle === 3 && (
<div className="p-fluid">
<h4>Informations de contact</h4>
<div className="field">
<label htmlFor="personne" className="font-medium">
Personne signalant la panne *
</label>
<InputText
id="personne"
value={signalement.personneSignalement}
onChange={(e) => setSignalement({ ...signalement, personneSignalement: e.target.value })}
placeholder="Nom et prénom"
className={errors.personneSignalement ? 'p-invalid' : ''}
/>
{errors.personneSignalement && <small className="p-error">{errors.personneSignalement}</small>}
</div>
<div className="field">
<label htmlFor="contact" className="font-medium">
Contact d'urgence *
</label>
<InputText
id="contact"
value={signalement.contactUrgence}
onChange={(e) => setSignalement({ ...signalement, contactUrgence: e.target.value })}
placeholder="Numéro de téléphone ou email"
className={errors.contactUrgence ? 'p-invalid' : ''}
/>
{errors.contactUrgence && <small className="p-error">{errors.contactUrgence}</small>}
</div>
<div className="field-checkbox">
<input
type="checkbox"
id="priorite"
checked={signalement.prioriteUrgence}
onChange={(e) => setSignalement({ ...signalement, prioriteUrgence: e.target.checked })}
/>
<label htmlFor="priorite" className="ml-2">
🚨 Marquer comme priorité urgente
</label>
</div>
<div className="field-checkbox">
<input
type="checkbox"
id="intervention"
checked={signalement.interventionImmediate}
onChange={(e) => setSignalement({ ...signalement, interventionImmediate: e.target.checked })}
/>
<label htmlFor="intervention" className="ml-2">
⚡ Demander une intervention immédiate
</label>
</div>
</div>
)}
{etapeActuelle === 4 && (
<div>
<h4>Récapitulatif du signalement</h4>
<div className="grid">
<div className="col-12 md:col-6">
<div className="field">
<label className="font-medium">Matériel:</label>
<p>{materiels.find(m => m.id === signalement.materielId)?.nom}</p>
</div>
<div className="field">
<label className="font-medium">Gravité:</label>
<p><Tag value={signalement.gravite} severity={getGraviteSeverity(signalement.gravite)} /></p>
</div>
<div className="field">
<label className="font-medium">Impact:</label>
<p><Tag value={signalement.impactProduction} severity={getImpactSeverity(signalement.impactProduction)} /></p>
</div>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label className="font-medium">Signalé par:</label>
<p>{signalement.personneSignalement}</p>
</div>
<div className="field">
<label className="font-medium">Contact:</label>
<p>{signalement.contactUrgence}</p>
</div>
{signalement.prioriteUrgence && (
<div className="field">
<Tag value="PRIORITÉ URGENTE" severity="danger" />
</div>
)}
</div>
<div className="col-12">
<div className="field">
<label className="font-medium">Problème:</label>
<p>{signalement.problemeSignale}</p>
</div>
</div>
</div>
</div>
)}
<div className="flex justify-content-between mt-4">
<Button
label="Précédent"
icon="pi pi-chevron-left"
className="p-button-outlined"
onClick={etapePrecedente}
disabled={etapeActuelle === 0}
/>
{etapeActuelle < 4 ? (
<Button
label="Suivant"
icon="pi pi-chevron-right"
iconPos="right"
onClick={etapeSuivante}
/>
) : (
<Button
label="Envoyer le signalement"
icon="pi pi-send"
className="p-button-success"
loading={loading}
onClick={handleSubmit}
/>
)}
</div>
</Card>
</div>
</div>
);
};
export default SignalerPannePage;