- 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>
267 lines
10 KiB
TypeScript
267 lines
10 KiB
TypeScript
'use client';
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
|
|
import React, { useState, useRef, useEffect } from 'react';
|
|
import { Card } from 'primereact/card';
|
|
import { Button } from 'primereact/button';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { Badge } from 'primereact/badge';
|
|
import { Dialog } from 'primereact/dialog';
|
|
import { InputNumber } from 'primereact/inputnumber';
|
|
import { InputTextarea } from 'primereact/inputtextarea';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
|
|
import PhasesTable from '../../../../components/phases/PhasesTable';
|
|
import usePhasesManager from '../../../../hooks/usePhasesManager';
|
|
import { PhaseChantier } from '../../../../types/btp-extended';
|
|
|
|
const DashboardPhasesPage = () => {
|
|
const toast = useRef<Toast>(null);
|
|
const {
|
|
phases,
|
|
loading,
|
|
selectedPhase,
|
|
setSelectedPhase,
|
|
loadPhases,
|
|
startPhase,
|
|
deletePhase,
|
|
suspendPhase,
|
|
resumePhase,
|
|
completePhase,
|
|
updateProgress,
|
|
getStatistics,
|
|
setToastRef
|
|
} = usePhasesManager();
|
|
|
|
const [showAvancementDialog, setShowAvancementDialog] = useState(false);
|
|
const [showSuspendreDialog, setShowSuspendreDialog] = useState(false);
|
|
const [avancement, setAvancement] = useState<number>(0);
|
|
const [motifSuspension, setMotifSuspension] = useState('');
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
|
|
// Initialisation
|
|
useEffect(() => {
|
|
setToastRef(toast.current);
|
|
loadPhases();
|
|
}, [setToastRef, loadPhases]);
|
|
|
|
// Gestionnaires d'événements
|
|
const handleUpdateAvancement = async () => {
|
|
if (!selectedPhase) return;
|
|
|
|
try {
|
|
await updateProgress(selectedPhase.id!, avancement);
|
|
setShowAvancementDialog(false);
|
|
} catch (error) {
|
|
// Erreur déjà gérée dans le hook
|
|
}
|
|
};
|
|
|
|
const handleSuspendre = async () => {
|
|
if (!selectedPhase) return;
|
|
|
|
try {
|
|
await suspendPhase(selectedPhase.id!, motifSuspension);
|
|
setShowSuspendreDialog(false);
|
|
setMotifSuspension('');
|
|
} catch (error) {
|
|
// Erreur déjà gérée dans le hook
|
|
}
|
|
};
|
|
|
|
const handlePhaseProgress = (phase: PhaseChantier) => {
|
|
setSelectedPhase(phase);
|
|
setAvancement(phase.pourcentageAvancement || 0);
|
|
setShowAvancementDialog(true);
|
|
};
|
|
|
|
const handlePhaseSuspend = (phase: PhaseChantier) => {
|
|
setSelectedPhase(phase);
|
|
setShowSuspendreDialog(true);
|
|
};
|
|
|
|
// Récupération des statistiques
|
|
const stats = getStatistics();
|
|
|
|
// Toolbar
|
|
const toolbarStartTemplate = (
|
|
<div className="flex align-items-center gap-2">
|
|
<h5 className="m-0 text-color">Tableau de bord des phases</h5>
|
|
<Badge value={phases.length} />
|
|
</div>
|
|
);
|
|
|
|
const toolbarEndTemplate = (
|
|
<div className="flex gap-2">
|
|
<span className="p-input-icon-left">
|
|
<i className="pi pi-search" />
|
|
<InputText
|
|
value={globalFilter}
|
|
onChange={(e) => setGlobalFilter(e.target.value)}
|
|
placeholder="Rechercher..."
|
|
/>
|
|
</span>
|
|
<Button
|
|
label="Actualiser"
|
|
icon="pi pi-refresh"
|
|
onClick={loadPhases}
|
|
loading={loading}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="grid">
|
|
<Toast ref={toast} />
|
|
|
|
<div className="col-12">
|
|
<h4>Tableau de bord des phases de chantier</h4>
|
|
</div>
|
|
|
|
{/* Statistiques */}
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<Card className="mb-0">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Phases en cours</span>
|
|
<div className="text-900 font-medium text-xl">{stats.enCours}</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-blue-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-play text-blue-500 text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<Card className="mb-0">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Phases en retard</span>
|
|
<div className="text-900 font-medium text-xl">{stats.enRetard}</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-orange-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-exclamation-triangle text-orange-500 text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<Card className="mb-0">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Phases critiques</span>
|
|
<div className="text-900 font-medium text-xl">{stats.critiques}</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-red-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-exclamation-circle text-red-500 text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="col-12 md:col-6 lg:col-3">
|
|
<Card className="mb-0">
|
|
<div className="flex justify-content-between mb-3">
|
|
<div>
|
|
<span className="block text-500 font-medium mb-3">Avancement moyen</span>
|
|
<div className="text-900 font-medium text-xl">{stats.avancementMoyen}%</div>
|
|
</div>
|
|
<div className="flex align-items-center justify-content-center bg-green-100 border-round" style={{ width: '2.5rem', height: '2.5rem' }}>
|
|
<i className="pi pi-percentage text-green-500 text-xl" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Table des phases avec le composant réutilisable */}
|
|
<div className="col-12">
|
|
<Card>
|
|
<Toolbar
|
|
start={toolbarStartTemplate}
|
|
end={toolbarEndTemplate}
|
|
className="mb-4"
|
|
/>
|
|
|
|
<PhasesTable
|
|
phases={phases}
|
|
loading={loading}
|
|
showStats={false}
|
|
showChantierColumn={true}
|
|
showSubPhases={false}
|
|
showBudget={false}
|
|
showExpansion={false}
|
|
actions={['view', 'progress', 'start', 'delete']}
|
|
onRefresh={loadPhases}
|
|
onPhaseStart={startPhase}
|
|
onPhaseProgress={handlePhaseProgress}
|
|
onPhaseDelete={deletePhase}
|
|
rows={10}
|
|
globalFilter={globalFilter}
|
|
showGlobalFilter={true}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Dialog avancement */}
|
|
<Dialog
|
|
header="Mettre à jour l'avancement"
|
|
visible={showAvancementDialog}
|
|
style={{ width: '450px' }}
|
|
onHide={() => setShowAvancementDialog(false)}
|
|
footer={
|
|
<div>
|
|
<Button label="Annuler" icon="pi pi-times" className="p-button-text" onClick={() => setShowAvancementDialog(false)} />
|
|
<Button label="Mettre à jour" icon="pi pi-check" onClick={handleUpdateAvancement} />
|
|
</div>
|
|
}
|
|
>
|
|
<div className="p-fluid">
|
|
<div className="field">
|
|
<label htmlFor="avancement">Pourcentage d'avancement</label>
|
|
<InputNumber
|
|
id="avancement"
|
|
value={avancement}
|
|
onValueChange={(e) => setAvancement(e.value || 0)}
|
|
suffix="%"
|
|
min={0}
|
|
max={100}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
|
|
{/* Dialog suspension */}
|
|
<Dialog
|
|
header="Suspendre la phase"
|
|
visible={showSuspendreDialog}
|
|
style={{ width: '450px' }}
|
|
onHide={() => setShowSuspendreDialog(false)}
|
|
footer={
|
|
<div>
|
|
<Button label="Annuler" icon="pi pi-times" className="p-button-text" onClick={() => setShowSuspendreDialog(false)} />
|
|
<Button label="Suspendre" icon="pi pi-pause" className="p-button-danger" onClick={handleSuspendre} />
|
|
</div>
|
|
}
|
|
>
|
|
<div className="p-fluid">
|
|
<div className="field">
|
|
<label htmlFor="motif">Motif de suspension</label>
|
|
<InputTextarea
|
|
id="motif"
|
|
value={motifSuspension}
|
|
onChange={(e) => setMotifSuspension(e.target.value)}
|
|
rows={3}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DashboardPhasesPage; |