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

371 lines
14 KiB
TypeScript

'use client';
import React, { useState, useContext } from 'react';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Tag } from 'primereact/tag';
import { Badge } from 'primereact/badge';
import { Button } from 'primereact/button';
import { Toolbar } from 'primereact/toolbar';
import { Dropdown } from 'primereact/dropdown';
import { InputSwitch } from 'primereact/inputswitch';
import { LayoutContext } from '../../layout/context/layoutcontext';
import type { PhaseChantier } from '../../types/btp';
import phaseValidationService from '../../services/phaseValidationService';
interface AtlantisResponsivePhasesTableProps {
phases: PhaseChantier[];
onPhaseSelect?: (phase: PhaseChantier) => void;
onPhaseStart?: (phaseId: string) => void;
onPhaseValidate?: (phase: PhaseChantier) => void;
className?: string;
}
const AtlantisResponsivePhasesTable: React.FC<AtlantisResponsivePhasesTableProps> = ({
phases,
onPhaseSelect,
onPhaseStart,
onPhaseValidate,
className = ''
}) => {
const [selectedPhase, setSelectedPhase] = useState<PhaseChantier | null>(null);
const [globalFilter, setGlobalFilter] = useState<string>('');
const [filters, setFilters] = useState<any>({});
const { layoutConfig, isDesktop } = useContext(LayoutContext);
// Options de filtrage responsive
const [compactView, setCompactView] = useState(!isDesktop());
const [showSubPhases, setShowSubPhases] = useState(true);
const handlePhaseSelect = (phase: PhaseChantier) => {
setSelectedPhase(phase);
onPhaseSelect?.(phase);
};
// Template pour le nom des phases avec hiérarchie Atlantis
const nameBodyTemplate = (rowData: PhaseChantier) => {
const isSubPhase = !!rowData.phaseParent;
return (
<div className={`flex align-items-center gap-2 ${isSubPhase ? 'ml-4' : ''}`}>
{isSubPhase && (
<i className="pi pi-arrow-right text-color-secondary text-sm" />
)}
<span className={`${isSubPhase ? 'text-color-secondary' : 'font-semibold text-color'}`}>
{rowData.nom}
</span>
{rowData.critique && (
<Tag
value="Critique"
severity="danger"
className="text-xs"
/>
)}
</div>
);
};
// Template pour le statut avec Tag PrimeReact
const statusBodyTemplate = (rowData: PhaseChantier) => {
const getSeverity = () => {
switch (rowData.statut) {
case 'TERMINEE': return 'success';
case 'EN_COURS': return 'info';
default: return 'secondary';
}
};
return (
<Tag
value={rowData.statut}
severity={getSeverity()}
icon={`pi pi-${rowData.statut === 'TERMINEE' ? 'check' : rowData.statut === 'EN_COURS' ? 'clock' : 'calendar'}`}
/>
);
};
// Template pour l'avancement avec style Atlantis
const progressBodyTemplate = (rowData: PhaseChantier) => {
const progress = rowData.pourcentageAvancement || 0;
return (
<div className="flex align-items-center gap-2">
<div className="w-full bg-surface-200 border-round" style={{ height: '8px' }}>
<div
className="bg-primary border-round h-full transition-all transition-duration-300"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-sm font-semibold text-color-secondary min-w-max">
{progress}%
</span>
</div>
);
};
// Template pour la validation avec couleurs Atlantis
const validationBodyTemplate = (rowData: PhaseChantier) => {
const validation = phaseValidationService.validatePhaseStart(rowData, phases);
const getValidationButton = () => {
if (validation.readyToStart) {
return (
<Button
icon="pi pi-check-circle"
className="p-button-success p-button-rounded p-button-text"
onClick={() => onPhaseValidate?.(rowData)}
tooltip="Prête à démarrer"
/>
);
} else if (validation.canStart) {
return (
<Button
icon="pi pi-exclamation-triangle"
className="p-button-warning p-button-rounded p-button-text"
onClick={() => onPhaseValidate?.(rowData)}
tooltip="Peut démarrer avec précautions"
/>
);
} else {
return (
<Button
icon="pi pi-times-circle"
className="p-button-danger p-button-rounded p-button-text"
onClick={() => onPhaseValidate?.(rowData)}
tooltip="Ne peut pas démarrer"
/>
);
}
};
return (
<div className="flex align-items-center gap-2">
{getValidationButton()}
{validation.errors.length > 0 && (
<Badge value={validation.errors.length} severity="danger" />
)}
{validation.warnings.length > 0 && (
<Badge value={validation.warnings.length} severity="warning" />
)}
</div>
);
};
// Template pour les actions
const actionsBodyTemplate = (rowData: PhaseChantier) => {
const validation = phaseValidationService.validatePhaseStart(rowData, phases);
return (
<div className="flex gap-1">
<Button
icon="pi pi-play"
className="p-button-success p-button-sm"
disabled={!validation.canStart || rowData.statut === 'TERMINEE'}
onClick={() => onPhaseStart?.(rowData.id!)}
tooltip="Démarrer"
/>
<Button
icon="pi pi-eye"
className="p-button-outlined p-button-sm"
onClick={() => onPhaseValidate?.(rowData)}
tooltip="Détails"
/>
</div>
);
};
// Template pour les dates avec style Atlantis
const dateBodyTemplate = (field: string) => (rowData: PhaseChantier) => {
const date = rowData[field as keyof PhaseChantier] as string;
if (!date) return <span className="text-color-secondary">-</span>;
const formattedDate = new Date(date).toLocaleDateString('fr-FR');
const isOverdue = field.includes('Fin') && rowData.statut !== 'TERMINEE' && new Date(date) < new Date();
return (
<span className={isOverdue ? 'text-red-500 font-semibold' : 'text-color'}>
{formattedDate}
{isOverdue && <i className="pi pi-exclamation-triangle ml-2 text-red-500" />}
</span>
);
};
// Barre d'outils responsive Atlantis
const toolbarStart = (
<div className="flex align-items-center gap-2">
<h5 className="m-0 text-color">Phases du chantier</h5>
{!isDesktop() && (
<Badge value={phases.length} className="ml-2" />
)}
</div>
);
const toolbarEnd = (
<div className="flex align-items-center gap-2">
<div className="field-checkbox">
<InputSwitch
inputId="compactView"
checked={compactView}
onChange={(e) => setCompactView(e.value)}
/>
<label htmlFor="compactView" className="ml-2 text-sm">Vue compacte</label>
</div>
<div className="field-checkbox">
<InputSwitch
inputId="showSubPhases"
checked={showSubPhases}
onChange={(e) => setShowSubPhases(e.value)}
/>
<label htmlFor="showSubPhases" className="ml-2 text-sm">Sous-phases</label>
</div>
</div>
);
// Filtrer les phases selon les options
const filteredPhases = phases.filter(phase => {
if (!showSubPhases && phase.phaseParent) return false;
return true;
});
// Déterminer les colonnes à afficher selon la taille d'écran
const getVisibleColumns = () => {
if (compactView) {
return ['nom', 'statut', 'pourcentageAvancement', 'actions'];
} else if (!isDesktop()) {
return ['nom', 'statut', 'pourcentageAvancement', 'validation', 'actions'];
} else {
return ['nom', 'statut', 'pourcentageAvancement', 'dateDebutPrevue', 'dateFinPrevue', 'validation', 'actions'];
}
};
const visibleColumns = getVisibleColumns();
return (
<div className={`card ${className}`}>
<Toolbar
start={toolbarStart}
end={toolbarEnd}
className="mb-4"
/>
<DataTable
value={filteredPhases}
selection={selectedPhase}
onSelectionChange={(e) => setSelectedPhase(e.value)}
selectionMode="single"
dataKey="id"
size={compactView ? 'small' : 'normal'}
stripedRows
responsiveLayout="scroll"
className="datatable-responsive"
emptyMessage="Aucune phase trouvée"
globalFilter={globalFilter}
header={
isDesktop() ? (
<div className="flex justify-content-between align-items-center">
<span className="text-xl font-semibold text-color">
Gestion des phases ({filteredPhases.length})
</span>
<span className="p-input-icon-left">
<i className="pi pi-search" />
<input
type="text"
className="p-inputtext p-component"
placeholder="Rechercher..."
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
/>
</span>
</div>
) : undefined
}
>
{visibleColumns.includes('nom') && (
<Column
field="nom"
header="Phase"
body={nameBodyTemplate}
sortable
style={{ minWidth: compactView ? '200px' : '250px' }}
/>
)}
{visibleColumns.includes('statut') && (
<Column
field="statut"
header="Statut"
body={statusBodyTemplate}
sortable
style={{ width: '120px' }}
/>
)}
{visibleColumns.includes('pourcentageAvancement') && (
<Column
field="pourcentageAvancement"
header="Avancement"
body={progressBodyTemplate}
sortable
style={{ width: compactView ? '120px' : '150px' }}
/>
)}
{visibleColumns.includes('dateDebutPrevue') && (
<Column
field="dateDebutPrevue"
header="Début prévu"
body={dateBodyTemplate('dateDebutPrevue')}
sortable
style={{ width: '130px' }}
/>
)}
{visibleColumns.includes('dateFinPrevue') && (
<Column
field="dateFinPrevue"
header="Fin prévue"
body={dateBodyTemplate('dateFinPrevue')}
sortable
style={{ width: '130px' }}
/>
)}
{visibleColumns.includes('validation') && (
<Column
header="Validation"
body={validationBodyTemplate}
style={{ width: '140px' }}
/>
)}
{visibleColumns.includes('actions') && (
<Column
header="Actions"
body={actionsBodyTemplate}
exportable={false}
style={{ width: '100px' }}
/>
)}
</DataTable>
{/* Informations sur la phase sélectionnée - Style Atlantis */}
{selectedPhase && (
<div className="card mt-3">
<div className="card-header">
<h6 className="m-0">Phase sélectionnée</h6>
</div>
<p className="m-0 mt-2">
<strong>{selectedPhase.nom}</strong> - {selectedPhase.statut} -
{selectedPhase.pourcentageAvancement || 0}% d'avancement
</p>
{selectedPhase.description && (
<p className="mt-2 mb-0 text-color-secondary">{selectedPhase.description}</p>
)}
</div>
)}
</div>
);
};
export default AtlantisResponsivePhasesTable;