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

265 lines
10 KiB
TypeScript

'use client';
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;