Files
btpxpress-frontend/components/phases/PhasesQuickPreview.tsx
2025-10-01 01:39:07 +00:00

192 lines
8.6 KiB
TypeScript

'use client';
import React from 'react';
import { Card } from 'primereact/card';
import { Badge } from 'primereact/badge';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import { ProgressBar } from 'primereact/progressbar';
import type { PhaseChantier } from '../../types/btp';
interface PhasesQuickPreviewProps {
phases: PhaseChantier[];
className?: string;
onViewDetails?: () => void;
}
const PhasesQuickPreview: React.FC<PhasesQuickPreviewProps> = ({
phases,
className = '',
onViewDetails
}) => {
const getPhaseStats = () => {
const total = phases.length;
const principales = phases.filter(p => !p.phaseParent).length;
const sousPhases = phases.filter(p => p.phaseParent).length;
const enCours = phases.filter(p => p.statut === 'EN_COURS').length;
const terminees = phases.filter(p => p.statut === 'TERMINEE').length;
const critiques = phases.filter(p => p.critique).length;
const enRetard = phases.filter(p => {
if (p.statut === 'TERMINEE') return false;
const maintenant = new Date();
const dateFinPrevue = p.dateFinPrevue ? new Date(p.dateFinPrevue) : null;
return dateFinPrevue ? dateFinPrevue < maintenant : false;
}).length;
const avancementMoyen = total > 0 ?
Math.round(phases.reduce((acc, p) => acc + (p.pourcentageAvancement || 0), 0) / total) : 0;
return { total, principales, sousPhases, enCours, terminees, critiques, enRetard, avancementMoyen };
};
const stats = getPhaseStats();
const getProgressColor = (percentage: number) => {
if (percentage >= 80) return 'success';
if (percentage >= 50) return 'info';
if (percentage >= 25) return 'warning';
return 'danger';
};
const getNextPhases = () => {
return phases
.filter(p => p.statut === 'PLANIFIEE' || p.statut === 'EN_COURS')
.sort((a, b) => (a.ordreExecution || 0) - (b.ordreExecution || 0))
.slice(0, 3);
};
const nextPhases = getNextPhases();
return (
<Card title="Aperçu des phases" className={className}>
{/* Statistiques rapides */}
<div className="grid mb-4">
<div className="col-6 md:col-3">
<div className="text-center p-2 border-1 surface-border border-round">
<div className="text-xl font-bold text-primary">{stats.total}</div>
<div className="text-600 text-sm">Total phases</div>
</div>
</div>
<div className="col-6 md:col-3">
<div className="text-center p-2 border-1 surface-border border-round">
<div className="text-xl font-bold text-green-500">{stats.terminees}</div>
<div className="text-600 text-sm">Terminées</div>
</div>
</div>
<div className="col-6 md:col-3">
<div className="text-center p-2 border-1 surface-border border-round">
<div className="text-xl font-bold text-blue-500">{stats.enCours}</div>
<div className="text-600 text-sm">En cours</div>
</div>
</div>
<div className="col-6 md:col-3">
<div className="text-center p-2 border-1 surface-border border-round">
<div className="text-xl font-bold text-red-500">{stats.critiques}</div>
<div className="text-600 text-sm">Critiques</div>
</div>
</div>
</div>
{/* Avancement global */}
<div className="mb-4">
<div className="flex justify-content-between align-items-center mb-2">
<span className="font-semibold">Avancement global</span>
<Badge value={`${stats.avancementMoyen}%`} severity={getProgressColor(stats.avancementMoyen)} />
</div>
<ProgressBar
value={stats.avancementMoyen}
color={
stats.avancementMoyen >= 80 ? '#10b981' :
stats.avancementMoyen >= 50 ? '#3b82f6' :
stats.avancementMoyen >= 25 ? '#f59e0b' : '#ef4444'
}
/>
</div>
{/* Alertes */}
{(stats.enRetard > 0 || stats.critiques > 0) && (
<div className="mb-4">
<div className="flex flex-wrap gap-2">
{stats.enRetard > 0 && (
<Tag
value={`${stats.enRetard} phase${stats.enRetard > 1 ? 's' : ''} en retard`}
severity="danger"
icon="pi pi-exclamation-triangle"
/>
)}
{stats.critiques > 0 && (
<Tag
value={`${stats.critiques} phase${stats.critiques > 1 ? 's' : ''} critique${stats.critiques > 1 ? 's' : ''}`}
severity="warning"
icon="pi pi-flag"
/>
)}
</div>
</div>
)}
{/* Prochaines phases */}
{nextPhases.length > 0 && (
<div className="mb-4">
<h4 className="text-lg font-semibold mb-3">Prochaines phases</h4>
<div className="flex flex-column gap-2">
{nextPhases.map((phase, index) => (
<div key={phase.id} className="flex align-items-center gap-3 p-2 surface-100 border-round">
<div className="flex align-items-center justify-content-center w-2rem h-2rem border-circle bg-primary text-white font-bold">
{index + 1}
</div>
<div className="flex-1">
<div className="font-semibold">{phase.nom}</div>
<div className="text-600 text-sm">
{phase.dateFinPrevue && `Prévue: ${new Date(phase.dateFinPrevue).toLocaleDateString('fr-FR')}`}
</div>
</div>
<div className="flex gap-1">
{phase.critique && (
<Tag value="Critique" severity="danger" className="text-xs" />
)}
<Tag
value={phase.statut}
severity={phase.statut === 'EN_COURS' ? 'info' : 'secondary'}
className="text-xs"
/>
</div>
</div>
))}
</div>
</div>
)}
{/* Structure hiérarchique */}
<div className="mb-4">
<div className="flex justify-content-between align-items-center">
<span className="font-semibold">Structure du projet</span>
<div className="flex gap-2">
<div className="flex align-items-center gap-1">
<div className="w-0.5rem h-0.5rem border-circle bg-primary"></div>
<span className="text-sm text-600">{stats.principales} principales</span>
</div>
<div className="flex align-items-center gap-1">
<div className="w-0.5rem h-0.5rem border-circle bg-gray-400"></div>
<span className="text-sm text-600">{stats.sousPhases} sous-phases</span>
</div>
</div>
</div>
</div>
{/* Actions */}
{onViewDetails && (
<div className="text-center">
<Button
label="Voir le détail des phases"
icon="pi pi-eye"
className="p-button-outlined"
onClick={onViewDetails}
/>
</div>
)}
</Card>
);
};
export default PhasesQuickPreview;