Files
btpxpress-frontend/app/(main)/equipes/disponibles/page.tsx
dahoud a8825a058b Fix: Corriger toutes les erreurs de build du frontend
- 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>
2025-10-18 13:23:08 +00:00

377 lines
15 KiB
TypeScript

'use client';
export const dynamic = 'force-dynamic';
import React, { useState, useEffect } from 'react';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Button } from 'primereact/button';
import { InputText } from 'primereact/inputtext';
import { Tag } from 'primereact/tag';
import { Card } from 'primereact/card';
import { Toolbar } from 'primereact/toolbar';
import { Badge } from 'primereact/badge';
import { Calendar } from 'primereact/calendar';
import { Dropdown } from 'primereact/dropdown';
import { useRouter } from 'next/navigation';
import { apiClient } from '../../../../services/api-client';
interface EquipeDisponible {
id: number;
nom: string;
specialite: string;
nombreEmployes: number;
chefEquipeNom?: string;
competencesEquipe: string[];
prochaineDispo: string;
dureeDisponibilite: number; // en jours
tauxOccupation: number;
dernierChantier?: string;
evaluationPerformance: number; // sur 5
coutJournalier: number;
localisation: string;
}
const EquipesDisponiblesPage = () => {
const [equipes, setEquipes] = useState<EquipeDisponible[]>([]);
const [loading, setLoading] = useState(true);
const [globalFilter, setGlobalFilter] = useState('');
const [dateRecherche, setDateRecherche] = useState<Date | null>(null);
const [specialiteFilter, setSpecialiteFilter] = useState<string | null>(null);
const [dureeMinimale, setDureeMinimale] = useState<number | null>(null);
const router = useRouter();
const specialiteOptions = [
{ label: 'Toutes', value: null },
{ label: 'Gros œuvre', value: 'GROS_OEUVRE' },
{ label: 'Second œuvre', value: 'SECOND_OEUVRE' },
{ label: 'Finitions', value: 'FINITIONS' },
{ label: 'Électricité', value: 'ELECTRICITE' },
{ label: 'Plomberie', value: 'PLOMBERIE' },
{ label: 'Charpente', value: 'CHARPENTE' },
{ label: 'Couverture', value: 'COUVERTURE' },
{ label: 'Terrassement', value: 'TERRASSEMENT' }
];
const dureeOptions = [
{ label: 'Toutes durées', value: null },
{ label: '1 semaine minimum', value: 7 },
{ label: '2 semaines minimum', value: 14 },
{ label: '1 mois minimum', value: 30 },
{ label: '2 mois minimum', value: 60 },
{ label: '3 mois minimum', value: 90 }
];
useEffect(() => {
loadEquipesDisponibles();
}, [dateRecherche, specialiteFilter, dureeMinimale]);
const loadEquipesDisponibles = async () => {
try {
setLoading(true);
console.log('🔄 Chargement des équipes disponibles...');
const params = new URLSearchParams();
if (dateRecherche) {
params.append('date', dateRecherche.toISOString().split('T')[0]);
}
if (specialiteFilter) {
params.append('specialite', specialiteFilter);
}
if (dureeMinimale) {
params.append('dureeMin', dureeMinimale.toString());
}
const response = await apiClient.get(`/api/equipes/disponibles?${params.toString()}`);
console.log('✅ Équipes disponibles chargées:', response.data);
setEquipes(response.data || []);
} catch (error) {
console.error('❌ Erreur lors du chargement:', error);
setEquipes([]);
} finally {
setLoading(false);
}
};
const assignerEquipe = async (equipe: EquipeDisponible) => {
// Rediriger vers la page d'assignation avec l'ID de l'équipe
router.push(`/planning/assigner?equipeId=${equipe.id}`);
};
const specialiteBodyTemplate = (rowData: EquipeDisponible) => {
const getSpecialiteColor = (specialite: string) => {
const colors: { [key: string]: string } = {
'GROS_OEUVRE': 'danger',
'SECOND_OEUVRE': 'warning',
'FINITIONS': 'success',
'ELECTRICITE': 'info',
'PLOMBERIE': 'primary',
'CHARPENTE': 'help',
'COUVERTURE': 'secondary',
'TERRASSEMENT': 'contrast'
};
return colors[specialite] || 'secondary';
};
return <Tag value={rowData.specialite} severity={getSpecialiteColor(rowData.specialite) as any} />;
};
const chefEquipeBodyTemplate = (rowData: EquipeDisponible) => {
if (rowData.chefEquipeNom) {
return (
<div className="flex align-items-center gap-2">
<i className="pi pi-user text-blue-500" />
<span>{rowData.chefEquipeNom}</span>
</div>
);
}
return <span className="text-500">Non assigné</span>;
};
const disponibiliteBodyTemplate = (rowData: EquipeDisponible) => {
const dateProchaine = new Date(rowData.prochaineDispo);
const isDispoMaintenant = dateProchaine <= new Date();
return (
<div className="flex flex-column gap-1">
<div className="flex align-items-center gap-2">
<i className={`pi ${isDispoMaintenant ? 'pi-check-circle text-green-500' : 'pi-clock text-orange-500'}`} />
<span className="font-medium">
{isDispoMaintenant ? 'Disponible maintenant' : dateProchaine.toLocaleDateString('fr-FR')}
</span>
</div>
<div className="text-sm text-500">
Durée: {rowData.dureeDisponibilite} jours
</div>
</div>
);
};
const performanceBodyTemplate = (rowData: EquipeDisponible) => {
const getPerformanceColor = (note: number) => {
if (note >= 4.5) return 'success';
if (note >= 3.5) return 'warning';
if (note >= 2.5) return 'info';
return 'danger';
};
return (
<div className="flex align-items-center gap-2">
<Tag value={`${rowData.evaluationPerformance}/5`} severity={getPerformanceColor(rowData.evaluationPerformance)} />
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<i
key={star}
className={`pi pi-star${star <= rowData.evaluationPerformance ? '-fill' : ''} text-yellow-500`}
style={{ fontSize: '0.8rem' }}
/>
))}
</div>
</div>
);
};
const competencesBodyTemplate = (rowData: EquipeDisponible) => {
return (
<div className="flex flex-wrap gap-1">
{rowData.competencesEquipe?.slice(0, 3).map((comp, index) => (
<Tag key={index} value={comp} className="p-tag-sm" />
))}
{rowData.competencesEquipe?.length > 3 && (
<Badge value={`+${rowData.competencesEquipe.length - 3}`} severity="info" />
)}
</div>
);
};
const coutBodyTemplate = (rowData: EquipeDisponible) => {
return (
<div className="flex flex-column gap-1">
<span className="font-medium">{rowData.coutJournalier.toLocaleString('fr-FR')} /jour</span>
<span className="text-sm text-500">
{rowData.nombreEmployes} employés
</span>
</div>
);
};
const localisationBodyTemplate = (rowData: EquipeDisponible) => {
return (
<div className="flex align-items-center gap-2">
<i className="pi pi-map-marker text-red-500" />
<span>{rowData.localisation}</span>
</div>
);
};
const actionBodyTemplate = (rowData: EquipeDisponible) => {
return (
<div className="flex gap-2">
<Button
icon="pi pi-eye"
className="p-button-rounded p-button-info p-button-sm"
onClick={() => router.push(`/equipes/${rowData.id}`)}
tooltip="Voir détails"
/>
<Button
icon="pi pi-calendar-plus"
className="p-button-rounded p-button-success p-button-sm"
onClick={() => assignerEquipe(rowData)}
tooltip="Assigner à un chantier"
/>
<Button
icon="pi pi-chart-line"
className="p-button-rounded p-button-warning p-button-sm"
onClick={() => router.push(`/equipes/${rowData.id}/performance`)}
tooltip="Voir performance"
/>
</div>
);
};
const leftToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2">
<Button
label="Retour aux équipes"
icon="pi pi-arrow-left"
className="p-button-outlined"
onClick={() => router.push('/equipes')}
/>
<Button
label="Planifier Mission"
icon="pi pi-calendar-plus"
className="p-button-success"
onClick={() => router.push('/planning/nouvelle-mission')}
/>
<Button
label="Équipe Optimale"
icon="pi pi-star"
className="p-button-warning"
onClick={() => router.push('/equipes/optimal')}
/>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<span className="p-input-icon-left">
<i className="pi pi-search" />
<InputText
type="search"
placeholder="Rechercher..."
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
/>
</span>
<Button
icon="pi pi-refresh"
className="p-button-outlined"
onClick={loadEquipesDisponibles}
tooltip="Actualiser"
/>
</div>
);
};
const header = (
<div className="flex flex-column md:flex-row md:justify-content-between md:align-items-center">
<h2 className="m-0">Équipes Disponibles</h2>
<div className="flex gap-2 mt-2 md:mt-0">
<Badge value={equipes.length} severity="success" />
<span>équipes disponibles</span>
</div>
</div>
);
return (
<div className="grid">
<div className="col-12">
<Card>
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
{/* Filtres */}
<div className="grid mb-4">
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Date de disponibilité</label>
<Calendar
value={dateRecherche}
onChange={(e) => setDateRecherche(e.value as Date)}
placeholder="Sélectionner une date"
showIcon
dateFormat="dd/mm/yy"
showButtonBar
/>
</div>
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Spécialité</label>
<Dropdown
value={specialiteFilter}
options={specialiteOptions}
onChange={(e) => setSpecialiteFilter(e.value)}
placeholder="Toutes spécialités"
/>
</div>
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Durée minimale</label>
<Dropdown
value={dureeMinimale}
options={dureeOptions}
onChange={(e) => setDureeMinimale(e.value)}
placeholder="Toutes durées"
/>
</div>
<div className="col-12 md:col-3">
<label className="font-medium mb-2 block">Actions</label>
<Button
label="Réinitialiser"
icon="pi pi-filter-slash"
className="p-button-outlined w-full"
onClick={() => {
setDateRecherche(null);
setSpecialiteFilter(null);
setDureeMinimale(null);
}}
/>
</div>
</div>
<DataTable
value={equipes}
loading={loading}
dataKey="id"
paginator
rows={10}
rowsPerPageOptions={[5, 10, 25]}
className="datatable-responsive"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
currentPageReportTemplate="Affichage de {first} à {last} sur {totalRecords} équipes"
globalFilter={globalFilter}
emptyMessage="Aucune équipe disponible trouvée."
header={header}
responsiveLayout="scroll"
>
<Column field="nom" header="Nom" sortable />
<Column field="specialite" header="Spécialité" body={specialiteBodyTemplate} sortable />
<Column field="chefEquipeNom" header="Chef d'équipe" body={chefEquipeBodyTemplate} />
<Column field="prochaineDispo" header="Disponibilité" body={disponibiliteBodyTemplate} sortable />
<Column field="evaluationPerformance" header="Performance" body={performanceBodyTemplate} sortable />
<Column field="competencesEquipe" header="Compétences" body={competencesBodyTemplate} />
<Column field="coutJournalier" header="Coût" body={coutBodyTemplate} sortable />
<Column field="localisation" header="Localisation" body={localisationBodyTemplate} sortable />
<Column body={actionBodyTemplate} header="Actions" style={{ minWidth: '10rem' }} />
</DataTable>
</Card>
</div>
</div>
);
};
export default EquipesDisponiblesPage;