334 lines
14 KiB
TypeScript
334 lines
14 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 { Calendar } from 'primereact/calendar';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Toolbar } from 'primereact/toolbar';
|
|
import { Tag } from 'primereact/tag';
|
|
import { Card } from 'primereact/card';
|
|
import { FilterMatchMode } from 'primereact/api';
|
|
import { materielService } from '../../../../services/api';
|
|
import { Materiel, TypeMateriel, StatutMateriel } from '../../../../types/btp';
|
|
import { formatDate, formatCurrency } from '../../../../utils/formatters';
|
|
|
|
const MaterielsDisponiblesPage = () => {
|
|
const [materiels, setMateriels] = useState<Materiel[]>([]);
|
|
const [selectedMateriels, setSelectedMateriels] = useState<Materiel[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [globalFilter, setGlobalFilter] = useState('');
|
|
const [dateDebut, setDateDebut] = useState<Date | null>(new Date());
|
|
const [dateFin, setDateFin] = useState<Date | null>(new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)); // +7 jours
|
|
const [typeFilter, setTypeFilter] = useState<string>('');
|
|
const toast = useRef<Toast>(null);
|
|
const dt = useRef<DataTable<Materiel[]>>(null);
|
|
|
|
const typeOptions = [
|
|
{ label: 'Tous les types', value: '' },
|
|
...Object.values(TypeMateriel).map(type => ({
|
|
label: type.replace('_', ' '),
|
|
value: type
|
|
}))
|
|
];
|
|
|
|
useEffect(() => {
|
|
loadMaterielsDisponibles();
|
|
}, [dateDebut, dateFin, typeFilter]);
|
|
|
|
const loadMaterielsDisponibles = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const dateDebutStr = dateDebut ? dateDebut.toISOString().split('T')[0] : undefined;
|
|
const dateFinStr = dateFin ? dateFin.toISOString().split('T')[0] : undefined;
|
|
|
|
const data = await materielService.getDisponibles(dateDebutStr, dateFinStr, typeFilter || undefined);
|
|
setMateriels(data);
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement:', error);
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Impossible de charger les matériels disponibles',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const reserverMateriel = async (materiel: Materiel) => {
|
|
if (!dateDebut || !dateFin) {
|
|
toast.current?.show({
|
|
severity: 'warn',
|
|
summary: 'Attention',
|
|
detail: 'Veuillez sélectionner une période',
|
|
life: 3000
|
|
});
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await materielService.reserver(
|
|
materiel.id,
|
|
dateDebut.toISOString().split('T')[0],
|
|
dateFin.toISOString().split('T')[0]
|
|
);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Succès',
|
|
detail: `Matériel ${materiel.nom} réservé`,
|
|
life: 3000
|
|
});
|
|
|
|
// Recharger la liste
|
|
loadMaterielsDisponibles();
|
|
} catch (error: any) {
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: error?.userMessage || 'Erreur lors de la réservation',
|
|
life: 3000
|
|
});
|
|
}
|
|
};
|
|
|
|
const exportCSV = () => {
|
|
dt.current?.exportCSV();
|
|
};
|
|
|
|
// Templates pour les colonnes
|
|
const typeBodyTemplate = (rowData: Materiel) => {
|
|
return (
|
|
<Tag
|
|
value={rowData.type?.replace('_', ' ')}
|
|
severity={getTypeSeverity(rowData.type)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const statutBodyTemplate = (rowData: Materiel) => {
|
|
return (
|
|
<Tag
|
|
value={rowData.statut?.replace('_', ' ')}
|
|
severity="success"
|
|
/>
|
|
);
|
|
};
|
|
|
|
const valeurBodyTemplate = (rowData: Materiel) => {
|
|
return formatCurrency(rowData.valeurActuelle || rowData.valeurAchat);
|
|
};
|
|
|
|
const actionBodyTemplate = (rowData: Materiel) => {
|
|
return (
|
|
<div className="flex gap-2">
|
|
<Button
|
|
icon="pi pi-calendar-plus"
|
|
label="Réserver"
|
|
size="small"
|
|
className="p-button-text p-button-rounded"
|
|
onClick={() => reserverMateriel(rowData)}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const getTypeSeverity = (type?: TypeMateriel) => {
|
|
switch (type) {
|
|
case TypeMateriel.ENGIN_CHANTIER:
|
|
return 'danger';
|
|
case TypeMateriel.OUTIL_ELECTRIQUE:
|
|
case TypeMateriel.OUTIL_MANUEL:
|
|
return 'warning';
|
|
case TypeMateriel.EQUIPEMENT_SECURITE:
|
|
return 'success';
|
|
case TypeMateriel.VEHICULE:
|
|
return 'info';
|
|
case TypeMateriel.GRUE:
|
|
case TypeMateriel.BETONIERE:
|
|
return 'danger';
|
|
default:
|
|
return 'secondary';
|
|
}
|
|
};
|
|
|
|
const leftToolbarTemplate = () => {
|
|
return (
|
|
<div className="flex flex-wrap gap-2 align-items-center">
|
|
<h4 className="m-0">Matériels Disponibles</h4>
|
|
<span className="text-500">({materiels.length} matériels)</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 flex-wrap gap-2 align-items-center">
|
|
<Calendar
|
|
placeholder="Date début"
|
|
value={dateDebut}
|
|
onChange={(e) => setDateDebut(e.value)}
|
|
dateFormat="dd/mm/yy"
|
|
showIcon
|
|
/>
|
|
<Calendar
|
|
placeholder="Date fin"
|
|
value={dateFin}
|
|
onChange={(e) => setDateFin(e.value)}
|
|
dateFormat="dd/mm/yy"
|
|
showIcon
|
|
/>
|
|
<Dropdown
|
|
value={typeFilter}
|
|
onChange={(e) => setTypeFilter(e.value)}
|
|
options={typeOptions}
|
|
placeholder="Tous les types"
|
|
style={{ minWidth: '150px' }}
|
|
/>
|
|
</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 }
|
|
};
|
|
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Toast ref={toast} />
|
|
|
|
<Card>
|
|
<div className="mb-4">
|
|
<div className="grid">
|
|
<div className="col-12 md:col-6">
|
|
<h5>Critères de disponibilité</h5>
|
|
<div className="flex flex-wrap gap-3 align-items-center">
|
|
<div className="flex flex-column gap-2">
|
|
<label htmlFor="dateDebut">Date début</label>
|
|
<Calendar
|
|
id="dateDebut"
|
|
value={dateDebut}
|
|
onChange={(e) => setDateDebut(e.value)}
|
|
dateFormat="dd/mm/yy"
|
|
showIcon
|
|
/>
|
|
</div>
|
|
<div className="flex flex-column gap-2">
|
|
<label htmlFor="dateFin">Date fin</label>
|
|
<Calendar
|
|
id="dateFin"
|
|
value={dateFin}
|
|
onChange={(e) => setDateFin(e.value)}
|
|
dateFormat="dd/mm/yy"
|
|
showIcon
|
|
/>
|
|
</div>
|
|
<div className="flex flex-column gap-2">
|
|
<label htmlFor="typeFilter">Type</label>
|
|
<Dropdown
|
|
id="typeFilter"
|
|
value={typeFilter}
|
|
onChange={(e) => setTypeFilter(e.value)}
|
|
options={typeOptions}
|
|
style={{ minWidth: '150px' }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-12 md:col-6">
|
|
<h5>Statistiques</h5>
|
|
<div className="grid text-center">
|
|
<div className="col-6">
|
|
<div className="surface-card p-3 border-round">
|
|
<div className="text-2xl font-semibold text-green-500">{materiels.length}</div>
|
|
<div className="text-500">Disponibles</div>
|
|
</div>
|
|
</div>
|
|
<div className="col-6">
|
|
<div className="surface-card p-3 border-round">
|
|
<div className="text-2xl font-semibold text-blue-500">
|
|
{materiels.reduce((sum, m) => sum + (m.valeurActuelle || m.valeurAchat || 0), 0).toLocaleString('fr-FR', {
|
|
style: 'currency',
|
|
currency: 'EUR'
|
|
})}
|
|
</div>
|
|
<div className="text-500">Valeur totale</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<div className="card">
|
|
<Toolbar
|
|
className="mb-4"
|
|
left={leftToolbarTemplate}
|
|
right={rightToolbarTemplate}
|
|
/>
|
|
|
|
<DataTable
|
|
ref={dt}
|
|
value={materiels}
|
|
selection={selectedMateriels}
|
|
onSelectionChange={(e) => setSelectedMateriels(e.value)}
|
|
selectionMode="checkbox"
|
|
dataKey="id"
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[5, 10, 25, 50]}
|
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
|
currentPageReportTemplate="Affichage {first} à {last} de {totalRecords} matériels disponibles"
|
|
filters={filters}
|
|
filterDisplay="menu"
|
|
loading={loading}
|
|
globalFilterFields={['nom', 'marque', 'modele', 'type', 'localisation']}
|
|
header={header}
|
|
emptyMessage="Aucun matériel disponible pour cette période."
|
|
>
|
|
<Column selectionMode="multiple" headerStyle={{ width: '3rem' }} />
|
|
<Column field="nom" header="Nom" sortable />
|
|
<Column field="marque" header="Marque" sortable />
|
|
<Column field="modele" header="Modèle" sortable />
|
|
<Column field="type" header="Type" body={typeBodyTemplate} sortable />
|
|
<Column field="statut" header="Statut" body={statutBodyTemplate} sortable />
|
|
<Column field="localisation" header="Localisation" sortable />
|
|
<Column field="valeurActuelle" header="Valeur" body={valeurBodyTemplate} sortable />
|
|
<Column body={actionBodyTemplate} exportable={false} style={{ minWidth: '8rem' }} />
|
|
</DataTable>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MaterielsDisponiblesPage;
|
|
|