Initial commit
This commit is contained in:
639
components/phases/PhasesTable.tsx
Normal file
639
components/phases/PhasesTable.tsx
Normal file
@@ -0,0 +1,639 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { DataTable, DataTableExpandedRows } from 'primereact/datatable';
|
||||
import { Column } from 'primereact/column';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { ProgressBar } from 'primereact/progressbar';
|
||||
import { Badge } from 'primereact/badge';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { TabView, TabPanel } from 'primereact/tabview';
|
||||
import {
|
||||
ActionButtonGroup,
|
||||
ViewButton,
|
||||
EditButton,
|
||||
DeleteButton,
|
||||
StartButton,
|
||||
CompleteButton,
|
||||
ProgressButton,
|
||||
BudgetPlanButton,
|
||||
BudgetTrackButton
|
||||
} from '../ui/ActionButton';
|
||||
|
||||
import { PhaseChantier, StatutPhase } from '../../types/btp-extended';
|
||||
import phaseService from '../../services/phaseService';
|
||||
import materielPhaseService from '../../services/materielPhaseService';
|
||||
import fournisseurPhaseService from '../../services/fournisseurPhaseService';
|
||||
|
||||
export interface PhasesTableProps {
|
||||
// Données
|
||||
phases: PhaseChantier[];
|
||||
loading?: boolean;
|
||||
chantierId?: string;
|
||||
|
||||
// Affichage
|
||||
showStats?: boolean;
|
||||
showChantierColumn?: boolean;
|
||||
showSubPhases?: boolean;
|
||||
showBudget?: boolean;
|
||||
showExpansion?: boolean;
|
||||
showGlobalFilter?: boolean;
|
||||
|
||||
// Actions disponibles
|
||||
actions?: Array<'view' | 'edit' | 'delete' | 'start' | 'complete' | 'progress' | 'budget-plan' | 'budget-track' | 'all'>;
|
||||
|
||||
// Callbacks
|
||||
onRefresh?: () => void;
|
||||
onPhaseSelect?: (phase: PhaseChantier) => void;
|
||||
onPhaseEdit?: (phase: PhaseChantier) => void;
|
||||
onPhaseDelete?: (phaseId: string) => void;
|
||||
onPhaseStart?: (phaseId: string) => void;
|
||||
onPhaseProgress?: (phase: PhaseChantier) => void;
|
||||
onPhaseBudgetPlan?: (phase: PhaseChantier) => void;
|
||||
onPhaseBudgetTrack?: (phase: PhaseChantier) => void;
|
||||
onSubPhaseAdd?: (parentPhase: PhaseChantier) => void;
|
||||
|
||||
// Configuration
|
||||
rows?: number;
|
||||
emptyMessage?: string;
|
||||
className?: string;
|
||||
globalFilter?: string;
|
||||
}
|
||||
|
||||
const PhasesTable: React.FC<PhasesTableProps> = ({
|
||||
phases,
|
||||
loading = false,
|
||||
chantierId,
|
||||
showStats = false,
|
||||
showChantierColumn = false,
|
||||
showSubPhases = true,
|
||||
showBudget = true,
|
||||
showExpansion = true,
|
||||
showGlobalFilter = false,
|
||||
actions = ['all'],
|
||||
onRefresh,
|
||||
onPhaseSelect,
|
||||
onPhaseEdit,
|
||||
onPhaseDelete,
|
||||
onPhaseStart,
|
||||
onPhaseProgress,
|
||||
onPhaseBudgetPlan,
|
||||
onPhaseBudgetTrack,
|
||||
onSubPhaseAdd,
|
||||
rows = 15,
|
||||
emptyMessage = "Aucune phase trouvée",
|
||||
className = "p-datatable-lg",
|
||||
globalFilter = ''
|
||||
}) => {
|
||||
const toast = useRef<Toast>(null);
|
||||
const [expandedRows, setExpandedRows] = useState<DataTableExpandedRows | undefined>(undefined);
|
||||
const [materielsPhase, setMaterielsPhase] = useState<any[]>([]);
|
||||
const [fournisseursPhase, setFournisseursPhase] = useState<any[]>([]);
|
||||
|
||||
// Déterminer quelles actions afficher
|
||||
const shouldShowAction = (action: string) => {
|
||||
return actions.includes('all') || actions.includes(action as any);
|
||||
};
|
||||
|
||||
// Templates de colonnes
|
||||
const statutBodyTemplate = (rowData: PhaseChantier) => {
|
||||
const severityMap: Record<string, any> = {
|
||||
'PLANIFIEE': 'secondary',
|
||||
'EN_ATTENTE': 'warning',
|
||||
'EN_COURS': 'info',
|
||||
'SUSPENDUE': 'warning',
|
||||
'TERMINEE': 'success',
|
||||
'ANNULEE': 'danger'
|
||||
};
|
||||
|
||||
return <Tag value={rowData.statut} severity={severityMap[rowData.statut]} />;
|
||||
};
|
||||
|
||||
const avancementBodyTemplate = (rowData: PhaseChantier) => {
|
||||
const progress = rowData.pourcentageAvancement || 0;
|
||||
const color = progress === 100 ? 'var(--green-500)' : progress >= 50 ? 'var(--blue-500)' : 'var(--orange-500)';
|
||||
|
||||
return (
|
||||
<div className="flex align-items-center gap-2">
|
||||
<ProgressBar
|
||||
value={progress}
|
||||
style={{ width: '100px', height: '8px' }}
|
||||
color={color}
|
||||
showValue={false}
|
||||
/>
|
||||
<span className="text-sm font-semibold">{progress}%</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const dateBodyTemplate = (rowData: PhaseChantier, field: keyof PhaseChantier) => {
|
||||
const date = rowData[field] as string;
|
||||
if (!date) return <span className="text-color-secondary">-</span>;
|
||||
|
||||
const dateObj = new Date(date);
|
||||
const isOverdue = field === 'dateFinPrevue' && dateObj < new Date() && rowData.statut !== 'TERMINEE';
|
||||
|
||||
return (
|
||||
<span className={isOverdue ? 'text-red-500 font-semibold' : ''}>
|
||||
{dateObj.toLocaleDateString('fr-FR')}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const prioriteBodyTemplate = (rowData: PhaseChantier) => {
|
||||
const severityMap: Record<string, any> = {
|
||||
'FAIBLE': 'secondary',
|
||||
'MOYENNE': 'info',
|
||||
'ELEVEE': 'warning',
|
||||
'CRITIQUE': 'danger'
|
||||
};
|
||||
|
||||
return rowData.priorite ? <Tag value={rowData.priorite} severity={severityMap[rowData.priorite]} /> : null;
|
||||
};
|
||||
|
||||
const budgetBodyTemplate = (rowData: PhaseChantier) => {
|
||||
return (
|
||||
<span className="text-900 font-semibold">
|
||||
{new Intl.NumberFormat('fr-FR', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(rowData.budgetPrevu || 0)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const coutReelBodyTemplate = (rowData: PhaseChantier) => {
|
||||
const cout = rowData.coutReel || 0;
|
||||
const budget = rowData.budgetPrevu || 0;
|
||||
const depassement = cout > budget;
|
||||
|
||||
return (
|
||||
<span className={depassement ? 'text-red-500 font-semibold' : 'text-900'}>
|
||||
{new Intl.NumberFormat('fr-FR', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
minimumFractionDigits: 0
|
||||
}).format(cout)}
|
||||
{depassement && (
|
||||
<i className="pi pi-exclamation-triangle text-red-500 ml-2" title="Dépassement budgétaire"></i>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const chantierBodyTemplate = (rowData: PhaseChantier) => {
|
||||
return rowData.chantier?.nom || '-';
|
||||
};
|
||||
|
||||
const phaseNameBodyTemplate = (rowData: PhaseChantier) => {
|
||||
return (
|
||||
<div className="flex align-items-center gap-2">
|
||||
{showSubPhases && (
|
||||
<Badge
|
||||
value={rowData.ordreExecution || 0}
|
||||
className="bg-primary text-primary-50"
|
||||
style={{ minWidth: '1.5rem' }}
|
||||
/>
|
||||
)}
|
||||
<i className="pi pi-sitemap text-sm text-color-secondary"></i>
|
||||
<div className="flex flex-column flex-1">
|
||||
<span className="font-semibold text-color">
|
||||
{rowData.nom}
|
||||
</span>
|
||||
{rowData.description && (
|
||||
<small className="text-color-secondary text-xs mt-1">
|
||||
{rowData.description.length > 60
|
||||
? rowData.description.substring(0, 60) + '...'
|
||||
: rowData.description
|
||||
}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
{rowData.critique && (
|
||||
<Badge
|
||||
value="Critique"
|
||||
severity="danger"
|
||||
className="text-xs"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Chargement du matériel et fournisseurs pour l'expansion
|
||||
const loadMaterielPhase = async (phaseId: string) => {
|
||||
try {
|
||||
const materiels = await materielPhaseService.getByPhase(phaseId);
|
||||
setMaterielsPhase(materiels);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement du matériel:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadFournisseursPhase = async (phaseId: string) => {
|
||||
try {
|
||||
const fournisseurs = await fournisseurPhaseService.getByPhase(phaseId);
|
||||
setFournisseursPhase(fournisseurs);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des fournisseurs:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Template d'expansion
|
||||
const rowExpansionTemplate = (data: PhaseChantier) => {
|
||||
if (data.phaseParent || !showSubPhases) return null;
|
||||
|
||||
const sousPhases = phases.filter(p => p.phaseParent === data.id);
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-surface-50">
|
||||
<TabView>
|
||||
<TabPanel header="Sous-phases" leftIcon="pi pi-sitemap">
|
||||
<div className="flex justify-content-between align-items-center mb-4">
|
||||
<h6 className="m-0 text-color">
|
||||
Sous-phases de "{data.nom}" ({sousPhases.length})
|
||||
</h6>
|
||||
{onSubPhaseAdd && (
|
||||
<Button
|
||||
label="Ajouter une sous-phase"
|
||||
icon="pi pi-plus"
|
||||
className="p-button-text p-button-rounded p-button-success p-button-sm"
|
||||
onClick={() => onSubPhaseAdd(data)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{sousPhases.length > 0 ? (
|
||||
<DataTable
|
||||
value={sousPhases}
|
||||
size="small"
|
||||
className="p-datatable-sm"
|
||||
emptyMessage="Aucune sous-phase"
|
||||
>
|
||||
<Column
|
||||
field="nom"
|
||||
header="Sous-phase"
|
||||
style={{ minWidth: '15rem' }}
|
||||
body={(rowData) => (
|
||||
<div className="flex align-items-center gap-2">
|
||||
<Badge
|
||||
value={rowData.ordreExecution || 0}
|
||||
className="bg-surface-100 text-surface-700 text-xs"
|
||||
style={{ minWidth: '1.2rem', fontSize: '0.7rem' }}
|
||||
/>
|
||||
<i className="pi pi-minus text-xs text-color-secondary"></i>
|
||||
<span className="font-semibold flex-1">{rowData.nom}</span>
|
||||
{rowData.critique && (
|
||||
<Tag value="Critique" severity="danger" className="text-xs" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Column
|
||||
field="statut"
|
||||
header="Statut"
|
||||
style={{ width: '8rem' }}
|
||||
body={statutBodyTemplate}
|
||||
/>
|
||||
<Column
|
||||
field="pourcentageAvancement"
|
||||
header="Avancement"
|
||||
style={{ width: '10rem' }}
|
||||
body={avancementBodyTemplate}
|
||||
/>
|
||||
<Column
|
||||
field="dateDebutPrevue"
|
||||
header="Début prévu"
|
||||
style={{ width: '10rem' }}
|
||||
body={(rowData) => dateBodyTemplate(rowData, 'dateDebutPrevue')}
|
||||
/>
|
||||
<Column
|
||||
field="dateFinPrevue"
|
||||
header="Fin prévue"
|
||||
style={{ width: '10rem' }}
|
||||
body={(rowData) => dateBodyTemplate(rowData, 'dateFinPrevue')}
|
||||
/>
|
||||
{showBudget && (
|
||||
<>
|
||||
<Column
|
||||
field="budgetPrevu"
|
||||
header="Budget"
|
||||
style={{ width: '8rem' }}
|
||||
body={budgetBodyTemplate}
|
||||
/>
|
||||
<Column
|
||||
field="coutReel"
|
||||
header="Coût réel"
|
||||
style={{ width: '8rem' }}
|
||||
body={coutReelBodyTemplate}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Column
|
||||
header="Actions"
|
||||
style={{ width: '10rem' }}
|
||||
body={(rowData) => (
|
||||
<ActionButtonGroup>
|
||||
{shouldShowAction('view') && onPhaseSelect && (
|
||||
<ViewButton
|
||||
tooltip="Voir détails"
|
||||
onClick={() => onPhaseSelect(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('edit') && onPhaseEdit && (
|
||||
<EditButton
|
||||
tooltip="Modifier"
|
||||
onClick={() => onPhaseEdit(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('start') && onPhaseStart && (
|
||||
<StartButton
|
||||
tooltip="Démarrer"
|
||||
disabled={rowData.statut !== 'PLANIFIEE'}
|
||||
onClick={() => onPhaseStart(rowData.id!)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('delete') && onPhaseDelete && (
|
||||
<DeleteButton
|
||||
tooltip="Supprimer"
|
||||
onClick={() => onPhaseDelete(rowData.id!)}
|
||||
/>
|
||||
)}
|
||||
</ActionButtonGroup>
|
||||
)}
|
||||
/>
|
||||
</DataTable>
|
||||
) : (
|
||||
<div className="text-center p-4">
|
||||
<i className="pi pi-info-circle text-4xl text-color-secondary mb-3"></i>
|
||||
<p className="text-color-secondary m-0">
|
||||
Aucune sous-phase définie pour cette phase.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Matériel" leftIcon="pi pi-wrench">
|
||||
<Button
|
||||
label="Charger le matériel"
|
||||
icon="pi pi-refresh"
|
||||
onClick={() => loadMaterielPhase(data.id!)}
|
||||
className="mb-3 p-button-text p-button-rounded"
|
||||
/>
|
||||
<DataTable
|
||||
value={materielsPhase}
|
||||
size="small"
|
||||
emptyMessage="Aucun matériel assigné"
|
||||
>
|
||||
<Column field="nom" header="Matériel" />
|
||||
<Column field="quantite" header="Quantité" />
|
||||
<Column field="statut" header="Statut" />
|
||||
</DataTable>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel header="Fournisseurs" leftIcon="pi pi-users">
|
||||
<Button
|
||||
label="Charger les fournisseurs"
|
||||
icon="pi pi-refresh"
|
||||
onClick={() => loadFournisseursPhase(data.id!)}
|
||||
className="mb-3 p-button-text p-button-rounded"
|
||||
/>
|
||||
<DataTable
|
||||
value={fournisseursPhase}
|
||||
size="small"
|
||||
emptyMessage="Aucun fournisseur recommandé"
|
||||
>
|
||||
<Column field="nom" header="Fournisseur" />
|
||||
<Column field="specialite" header="Spécialité" />
|
||||
<Column field="notation" header="Note" />
|
||||
</DataTable>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Template des actions principales
|
||||
const actionBodyTemplate = (rowData: PhaseChantier) => {
|
||||
const handleDelete = () => {
|
||||
confirmDialog({
|
||||
message: `Êtes-vous sûr de vouloir supprimer la phase "${rowData.nom}" ?`,
|
||||
header: 'Confirmer la suppression',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
acceptClassName: 'p-button-danger',
|
||||
acceptLabel: 'Supprimer',
|
||||
rejectLabel: 'Annuler',
|
||||
accept: async () => {
|
||||
try {
|
||||
await phaseService.delete(rowData.id!);
|
||||
if (onRefresh) onRefresh();
|
||||
toast.current?.show({
|
||||
severity: 'success',
|
||||
summary: 'Suppression réussie',
|
||||
detail: 'La phase a été supprimée',
|
||||
life: 3000
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
toast.current?.show({
|
||||
severity: 'error',
|
||||
summary: 'Erreur',
|
||||
detail: 'Impossible de supprimer la phase',
|
||||
life: 5000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButtonGroup>
|
||||
{shouldShowAction('view') && onPhaseSelect && (
|
||||
<ViewButton
|
||||
tooltip="Voir détails"
|
||||
onClick={() => onPhaseSelect(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('edit') && onPhaseEdit && (
|
||||
<EditButton
|
||||
tooltip="Modifier"
|
||||
onClick={() => onPhaseEdit(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('start') && onPhaseStart && (
|
||||
<StartButton
|
||||
tooltip="Démarrer"
|
||||
disabled={rowData.statut !== 'PLANIFIEE'}
|
||||
onClick={() => onPhaseStart(rowData.id!)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('progress') && onPhaseProgress && (
|
||||
<ProgressButton
|
||||
tooltip="Mettre à jour avancement"
|
||||
onClick={() => onPhaseProgress(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('budget-plan') && onPhaseBudgetPlan && (
|
||||
<BudgetPlanButton
|
||||
tooltip="Planifier le budget"
|
||||
onClick={() => onPhaseBudgetPlan(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('budget-track') && onPhaseBudgetTrack && (
|
||||
<BudgetTrackButton
|
||||
tooltip="Suivi des dépenses"
|
||||
onClick={() => onPhaseBudgetTrack(rowData)}
|
||||
/>
|
||||
)}
|
||||
{shouldShowAction('delete') && (onPhaseDelete || true) && (
|
||||
<DeleteButton
|
||||
tooltip="Supprimer"
|
||||
onClick={() => onPhaseDelete ? onPhaseDelete(rowData.id!) : handleDelete()}
|
||||
/>
|
||||
)}
|
||||
</ActionButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
// Style des lignes
|
||||
const phaseRowClassName = (rowData: PhaseChantier) => {
|
||||
let className = '';
|
||||
|
||||
if (rowData.phaseParent) {
|
||||
className += ' bg-surface-card border-left-4 border-surface-300';
|
||||
} else {
|
||||
className += ' bg-surface-ground border-left-4 border-primary font-semibold';
|
||||
}
|
||||
|
||||
if (rowData.critique) {
|
||||
className += ' border-red-500';
|
||||
}
|
||||
|
||||
return className;
|
||||
};
|
||||
|
||||
// Filtrer les phases principales seulement si subPhases est activé
|
||||
const displayPhases = showSubPhases ? phases.filter(p => !p.phaseParent) : phases;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast ref={toast} />
|
||||
<ConfirmDialog />
|
||||
|
||||
<DataTable
|
||||
value={displayPhases}
|
||||
loading={loading}
|
||||
paginator
|
||||
rows={rows}
|
||||
globalFilter={showGlobalFilter ? globalFilter : undefined}
|
||||
emptyMessage={emptyMessage}
|
||||
className={className}
|
||||
dataKey="id"
|
||||
expandedRows={showExpansion ? expandedRows : undefined}
|
||||
onRowToggle={showExpansion ? (e) => setExpandedRows(e.data) : undefined}
|
||||
rowExpansionTemplate={showExpansion ? rowExpansionTemplate : undefined}
|
||||
rowClassName={phaseRowClassName}
|
||||
>
|
||||
{showExpansion && showSubPhases && <Column expander style={{ width: '3rem' }} />}
|
||||
|
||||
<Column
|
||||
field="nom"
|
||||
header="Phase"
|
||||
sortable
|
||||
style={{ minWidth: '20rem' }}
|
||||
body={phaseNameBodyTemplate}
|
||||
/>
|
||||
|
||||
{showChantierColumn && (
|
||||
<Column
|
||||
field="chantier.nom"
|
||||
header="Chantier"
|
||||
sortable
|
||||
style={{ width: '15rem' }}
|
||||
body={chantierBodyTemplate}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Column
|
||||
field="statut"
|
||||
header="Statut"
|
||||
body={statutBodyTemplate}
|
||||
sortable
|
||||
style={{ width: '10rem' }}
|
||||
/>
|
||||
|
||||
<Column
|
||||
field="priorite"
|
||||
header="Priorité"
|
||||
body={prioriteBodyTemplate}
|
||||
sortable
|
||||
style={{ width: '8rem' }}
|
||||
/>
|
||||
|
||||
<Column
|
||||
field="pourcentageAvancement"
|
||||
header="Avancement"
|
||||
body={avancementBodyTemplate}
|
||||
style={{ width: '12rem' }}
|
||||
/>
|
||||
|
||||
<Column
|
||||
field="dateDebutPrevue"
|
||||
header="Début prévu"
|
||||
body={(rowData) => dateBodyTemplate(rowData, 'dateDebutPrevue')}
|
||||
sortable
|
||||
style={{ width: '10rem' }}
|
||||
/>
|
||||
|
||||
<Column
|
||||
field="dateFinPrevue"
|
||||
header="Fin prévue"
|
||||
body={(rowData) => dateBodyTemplate(rowData, 'dateFinPrevue')}
|
||||
sortable
|
||||
style={{ width: '10rem' }}
|
||||
/>
|
||||
|
||||
<Column
|
||||
field="dureeEstimeeHeures"
|
||||
header="Durée (h)"
|
||||
sortable
|
||||
style={{ width: '8rem' }}
|
||||
body={(rowData) => (
|
||||
<span>{rowData.dureeEstimeeHeures || 0}h</span>
|
||||
)}
|
||||
/>
|
||||
|
||||
{showBudget && (
|
||||
<>
|
||||
<Column
|
||||
field="budgetPrevu"
|
||||
header="Budget prévu"
|
||||
sortable
|
||||
style={{ width: '10rem' }}
|
||||
body={budgetBodyTemplate}
|
||||
/>
|
||||
<Column
|
||||
field="coutReel"
|
||||
header="Coût réel"
|
||||
sortable
|
||||
style={{ width: '10rem' }}
|
||||
body={coutReelBodyTemplate}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Column
|
||||
header="Actions"
|
||||
style={{ width: '12rem' }}
|
||||
body={actionBodyTemplate}
|
||||
/>
|
||||
</DataTable>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PhasesTable;
|
||||
Reference in New Issue
Block a user