Files
btpxpress-frontend/app/(main)/maintenances/en-cours/page.tsx

315 lines
13 KiB
TypeScript

'use client';
import React, { useState, useEffect, useRef } from 'react';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { InputText } from 'primereact/inputtext';
import { Toast } from 'primereact/toast';
import { Toolbar } from 'primereact/toolbar';
import { Tag } from 'primereact/tag';
import { Card } from 'primereact/card';
import { ProgressBar } from 'primereact/progressbar';
import { FilterMatchMode } from 'primereact/api';
import { maintenanceService } from '../../../../services/api';
import { MaintenanceMateriel, StatutMaintenance } from '../../../../types/btp';
import { formatDate } from '../../../../utils/formatters';
const MaintenancesEnCoursPage = () => {
const [maintenances, setMaintenances] = useState<MaintenanceMateriel[]>([]);
const [selectedMaintenances, setSelectedMaintenances] = useState<MaintenanceMateriel[]>([]);
const [loading, setLoading] = useState(true);
const [globalFilter, setGlobalFilter] = useState('');
const toast = useRef<Toast>(null);
const dt = useRef<DataTable<MaintenanceMateriel[]>>(null);
useEffect(() => {
loadMaintenancesEnCours();
}, []);
const loadMaintenancesEnCours = async () => {
try {
setLoading(true);
const data = await maintenanceService.getAll();
// Filtrer les maintenances en cours
const enCours = data.filter(m => m.statut === StatutMaintenance.EN_COURS);
setMaintenances(enCours);
} catch (error) {
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: 'Impossible de charger les maintenances en cours',
life: 3000
});
} finally {
setLoading(false);
}
};
const terminerMaintenance = async (maintenance: MaintenanceMateriel) => {
try {
const updatedMaintenance = {
...maintenance,
statut: StatutMaintenance.TERMINEE,
dateRealisee: new Date().toISOString()
};
await maintenanceService.update(maintenance.id, updatedMaintenance);
toast.current?.show({
severity: 'success',
summary: 'Succès',
detail: 'Maintenance terminée',
life: 3000
});
loadMaintenancesEnCours();
} catch (error: any) {
toast.current?.show({
severity: 'error',
summary: 'Erreur',
detail: error?.userMessage || 'Erreur lors de la finalisation',
life: 3000
});
}
};
const exportCSV = () => {
dt.current?.exportCSV();
};
// Templates pour les colonnes
const materielBodyTemplate = (rowData: MaintenanceMateriel) => {
return (
<div>
<div className="font-semibold">{rowData.materiel?.nom}</div>
<div className="text-sm text-500">{rowData.materiel?.marque} {rowData.materiel?.modele}</div>
</div>
);
};
const dureeBodyTemplate = (rowData: MaintenanceMateriel) => {
if (!rowData.dateRealisee) return 'N/A';
const debut = new Date(rowData.dateRealisee);
const maintenant = new Date();
const diffHeures = Math.floor((maintenant.getTime() - debut.getTime()) / (1000 * 60 * 60));
return (
<div>
<div>{diffHeures}h</div>
<ProgressBar value={Math.min(100, (diffHeures / 24) * 100)} className="mt-1" style={{height: '4px'}} />
</div>
);
};
const prioriteBodyTemplate = (rowData: MaintenanceMateriel) => {
const dateDebut = new Date(rowData.dateRealisee || rowData.datePrevue);
const heures = Math.floor((Date.now() - dateDebut.getTime()) / (1000 * 60 * 60));
if (heures > 48) {
return <Tag value="URGENT" severity="danger" />;
} else if (heures > 24) {
return <Tag value="PRIORITAIRE" severity="warning" />;
} else {
return <Tag value="NORMAL" severity="success" />;
}
};
const actionBodyTemplate = (rowData: MaintenanceMateriel) => {
return (
<div className="flex gap-2">
<Button
icon="pi pi-check"
label="Terminer"
size="small"
severity="success"
className="p-button-text p-button-rounded"
onClick={() => terminerMaintenance(rowData)}
/>
<Button
icon="pi pi-eye"
size="small"
severity="info"
className="p-button-text p-button-rounded"
tooltip="Voir détails"
/>
</div>
);
};
const leftToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2 align-items-center">
<h4 className="m-0">Maintenances En Cours</h4>
<span className="text-500">({maintenances.length} maintenances actives)</span>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<Button
label="Exporter"
icon="pi pi-upload"
severity="help"
className="p-button-text p-button-rounded"
onClick={exportCSV}
/>
);
};
const header = (
<div className="flex flex-wrap gap-2 align-items-center justify-content-between">
<div className="flex align-items-center gap-2">
<Button
icon="pi pi-refresh"
className="p-button-text p-button-rounded"
onClick={loadMaintenancesEnCours}
loading={loading}
/>
<span className="text-sm text-500">Dernière mise à jour: {new Date().toLocaleTimeString('fr-FR')}</span>
</div>
<span className="p-input-icon-left">
<i className="pi pi-search" />
<InputText
type="search"
placeholder="Rechercher..."
onInput={(e) => setGlobalFilter(e.currentTarget.value)}
/>
</span>
</div>
);
const filters = {
global: { value: globalFilter, matchMode: FilterMatchMode.CONTAINS }
};
// Statistiques
const maintenancesUrgentes = maintenances.filter(m => {
const heures = Math.floor((Date.now() - new Date(m.dateRealisee || m.datePrevue).getTime()) / (1000 * 60 * 60));
return heures > 48;
}).length;
const maintenancesPrioritaires = maintenances.filter(m => {
const heures = Math.floor((Date.now() - new Date(m.dateRealisee || m.datePrevue).getTime()) / (1000 * 60 * 60));
return heures > 24 && heures <= 48;
}).length;
return (
<div className="grid">
<div className="col-12">
<Toast ref={toast} />
{/* Cards de statistiques */}
<div className="grid mb-4">
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center">
<div className="mr-3">
<i className="pi pi-play-circle text-blue-500 text-3xl"></i>
</div>
<div>
<div className="text-2xl font-semibold">{maintenances.length}</div>
<div className="text-500">En cours</div>
</div>
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center">
<div className="mr-3">
<i className="pi pi-exclamation-triangle text-orange-500 text-3xl"></i>
</div>
<div>
<div className="text-2xl font-semibold">{maintenancesPrioritaires}</div>
<div className="text-500">Prioritaires</div>
</div>
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center">
<div className="mr-3">
<i className="pi pi-times-circle text-red-500 text-3xl"></i>
</div>
<div>
<div className="text-2xl font-semibold">{maintenancesUrgentes}</div>
<div className="text-500">Urgentes</div>
</div>
</div>
</Card>
</div>
<div className="col-12 md:col-3">
<Card>
<div className="flex align-items-center">
<div className="mr-3">
<i className="pi pi-clock text-green-500 text-3xl"></i>
</div>
<div>
<div className="text-2xl font-semibold">
{maintenances.length > 0
? Math.round(maintenances.reduce((acc, m) => {
const heures = Math.floor((Date.now() - new Date(m.dateRealisee || m.datePrevue).getTime()) / (1000 * 60 * 60));
return acc + heures;
}, 0) / maintenances.length)
: 0
}h
</div>
<div className="text-500">Durée moyenne</div>
</div>
</div>
</Card>
</div>
</div>
<div className="card">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
<DataTable
ref={dt}
value={maintenances}
selection={selectedMaintenances}
onSelectionChange={(e) => setSelectedMaintenances(e.value)}
dataKey="id"
paginator
rows={10}
rowsPerPageOptions={[5, 10, 25]}
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
currentPageReportTemplate="Affichage {first} à {last} de {totalRecords} maintenances"
filters={filters}
filterDisplay="menu"
loading={loading}
globalFilterFields={['description', 'materiel.nom', 'type']}
header={header}
emptyMessage="Aucune maintenance en cours."
sortField="dateRealisee"
sortOrder={-1}
>
<Column selectionMode="multiple" headerStyle={{ width: '3rem' }} />
<Column header="Matériel" body={materielBodyTemplate} />
<Column header="Priorité" body={prioriteBodyTemplate} />
<Column header="Durée" body={dureeBodyTemplate} />
<Column field="description" header="Description" />
<Column field="dateRealisee" header="Démarrée le" body={(data) => formatDate(data.dateRealisee)} sortable />
<Column body={actionBodyTemplate} exportable={false} style={{ minWidth: '8rem' }} />
</DataTable>
</div>
</div>
</div>
);
};
export default MaintenancesEnCoursPage;