- Correction des erreurs TypeScript dans userService.ts et workflowTester.ts - Ajout des propriétés manquantes aux objets User mockés - Conversion des dates de string vers objets Date - Correction des appels asynchrones et des types incompatibles - Ajout de dynamic rendering pour résoudre les erreurs useSearchParams - Enveloppement de useSearchParams dans Suspense boundary - Configuration de force-dynamic au niveau du layout principal Build réussi: 126 pages générées avec succès 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
261 lines
9.8 KiB
TypeScript
261 lines
9.8 KiB
TypeScript
'use client';
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
|
|
import React, { useState, useRef } from 'react';
|
|
import { DataTable } from 'primereact/datatable';
|
|
import { Column } from 'primereact/column';
|
|
import { Button } from 'primereact/button';
|
|
import { InputText } from 'primereact/inputtext';
|
|
import { Dropdown } from 'primereact/dropdown';
|
|
import { Card } from 'primereact/card';
|
|
import { Toast } from 'primereact/toast';
|
|
import { Tag } from 'primereact/tag';
|
|
import { FilterMatchMode } from 'primereact/api';
|
|
import { materielService } from '../../../../services/api';
|
|
import { Materiel, TypeMateriel, StatutMateriel } from '../../../../types/btp';
|
|
import { formatCurrency } from '../../../../utils/formatters';
|
|
|
|
const RechercheMaterielsPage = () => {
|
|
const [materiels, setMateriels] = useState<Materiel[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [searchParams, setSearchParams] = useState({
|
|
nom: '',
|
|
type: '',
|
|
marque: '',
|
|
statut: '',
|
|
localisation: ''
|
|
});
|
|
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
|
|
}))
|
|
];
|
|
|
|
const statutOptions = [
|
|
{ label: 'Tous les statuts', value: '' },
|
|
...Object.values(StatutMateriel).map(statut => ({
|
|
label: statut.replace('_', ' '),
|
|
value: statut
|
|
}))
|
|
];
|
|
|
|
const rechercher = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const data = await materielService.search(searchParams);
|
|
setMateriels(data);
|
|
|
|
toast.current?.show({
|
|
severity: 'success',
|
|
summary: 'Recherche effectuée',
|
|
detail: `${data.length} résultat(s) trouvé(s)`,
|
|
life: 3000
|
|
});
|
|
} catch (error) {
|
|
toast.current?.show({
|
|
severity: 'error',
|
|
summary: 'Erreur',
|
|
detail: 'Erreur lors de la recherche',
|
|
life: 3000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const exportCSV = () => {
|
|
dt.current?.exportCSV();
|
|
};
|
|
|
|
const onInputChange = (e: React.ChangeEvent<HTMLInputElement>, name: string) => {
|
|
const val = (e.target && e.target.value) || '';
|
|
setSearchParams(prev => ({ ...prev, [name]: val }));
|
|
};
|
|
|
|
const onDropdownChange = (e: any, name: string) => {
|
|
setSearchParams(prev => ({ ...prev, [name]: e.value }));
|
|
};
|
|
|
|
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={getStatutSeverity(rowData.statut)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const valeurBodyTemplate = (rowData: Materiel) => {
|
|
return formatCurrency(rowData.valeurActuelle || rowData.valeurAchat);
|
|
};
|
|
|
|
const getTypeSeverity = (type?: TypeMateriel) => {
|
|
switch (type) {
|
|
case TypeMateriel.ENGIN_CHANTIER:
|
|
return 'danger';
|
|
case TypeMateriel.OUTIL_ELECTRIQUE:
|
|
return 'warning';
|
|
case TypeMateriel.EQUIPEMENT_SECURITE:
|
|
return 'success';
|
|
case TypeMateriel.VEHICULE:
|
|
return 'info';
|
|
default:
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
const getStatutSeverity = (statut?: StatutMateriel) => {
|
|
switch (statut) {
|
|
case StatutMateriel.DISPONIBLE:
|
|
return 'success';
|
|
case StatutMateriel.UTILISE:
|
|
return 'warning';
|
|
case StatutMateriel.MAINTENANCE:
|
|
return 'info';
|
|
case StatutMateriel.HORS_SERVICE:
|
|
return 'danger';
|
|
default:
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="grid">
|
|
<div className="col-12">
|
|
<Toast ref={toast} />
|
|
|
|
<h2 className="mb-4">Recherche de Matériel</h2>
|
|
|
|
<Card className="mb-4">
|
|
<h5>Critères de recherche</h5>
|
|
<div className="formgrid grid">
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="nom">Nom</label>
|
|
<InputText
|
|
id="nom"
|
|
value={searchParams.nom}
|
|
onChange={(e) => onInputChange(e, 'nom')}
|
|
placeholder="Nom du matériel"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="marque">Marque</label>
|
|
<InputText
|
|
id="marque"
|
|
value={searchParams.marque}
|
|
onChange={(e) => onInputChange(e, 'marque')}
|
|
placeholder="Marque"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-4">
|
|
<label htmlFor="localisation">Localisation</label>
|
|
<InputText
|
|
id="localisation"
|
|
value={searchParams.localisation}
|
|
onChange={(e) => onInputChange(e, 'localisation')}
|
|
placeholder="Localisation"
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="type">Type</label>
|
|
<Dropdown
|
|
id="type"
|
|
value={searchParams.type}
|
|
onChange={(e) => onDropdownChange(e, 'type')}
|
|
options={typeOptions}
|
|
/>
|
|
</div>
|
|
|
|
<div className="field col-12 md:col-6">
|
|
<label htmlFor="statut">Statut</label>
|
|
<Dropdown
|
|
id="statut"
|
|
value={searchParams.statut}
|
|
onChange={(e) => onDropdownChange(e, 'statut')}
|
|
options={statutOptions}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
label="Rechercher"
|
|
icon="pi pi-search"
|
|
className="p-button-text p-button-rounded"
|
|
onClick={rechercher}
|
|
loading={loading}
|
|
/>
|
|
<Button
|
|
label="Effacer"
|
|
icon="pi pi-times"
|
|
severity={"secondary" as any}
|
|
className="p-button-text p-button-rounded"
|
|
onClick={() => setSearchParams({
|
|
nom: '',
|
|
type: '',
|
|
marque: '',
|
|
statut: '',
|
|
localisation: ''
|
|
})}
|
|
/>
|
|
</div>
|
|
</Card>
|
|
|
|
{materiels.length > 0 && (
|
|
<Card>
|
|
<div className="flex justify-content-between align-items-center mb-4">
|
|
<h5>Résultats ({materiels.length})</h5>
|
|
<Button
|
|
label="Exporter"
|
|
icon="pi pi-upload"
|
|
severity="help"
|
|
className="p-button-text p-button-rounded"
|
|
onClick={exportCSV}
|
|
/>
|
|
</div>
|
|
|
|
<DataTable
|
|
ref={dt}
|
|
value={materiels}
|
|
paginator
|
|
rows={10}
|
|
rowsPerPageOptions={[5, 10, 25]}
|
|
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
|
|
currentPageReportTemplate="Affichage {first} à {last} de {totalRecords} matériels"
|
|
emptyMessage="Aucun résultat trouvé."
|
|
>
|
|
<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 />
|
|
</DataTable>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RechercheMaterielsPage;
|