Initial commit
This commit is contained in:
265
app/(main)/dashboard/phases/page.tsx
Normal file
265
app/(main)/dashboard/phases/page.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
'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;
|
||||
Reference in New Issue
Block a user