Files
btpxpress-frontend/app/(main)/equipes/[id]/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

541 lines
23 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
export const dynamic = 'force-dynamic';
import React, { useState, useEffect } from 'react';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
import { Tag } from 'primereact/tag';
import { Badge } from 'primereact/badge';
import { Toolbar } from 'primereact/toolbar';
import { TabView, TabPanel } from 'primereact/tabview';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { ProgressBar } from 'primereact/progressbar';
import { Chart } from 'primereact/chart';
import { useRouter, useParams } from 'next/navigation';
import { apiClient } from '../../../../services/api-client';
interface EquipeDetail {
id: number;
nom: string;
description: string;
specialite: string;
statut: string;
dateCreation: string;
chefEquipeId?: number;
chefEquipeNom?: string;
nombreEmployes: number;
competencesEquipe: string[];
tauxOccupation: number;
evaluationPerformance: number;
coutJournalier: number;
localisation: string;
chantierActuel?: {
id: number;
nom: string;
dateDebut: string;
dateFin: string;
statut: string;
};
employes: Array<{
id: number;
nom: string;
prenom: string;
metier: string;
statut: string;
niveauExperience: string;
competences: string[];
dateAffectation: string;
}>;
historiqueMissions: Array<{
id: number;
chantierNom: string;
dateDebut: string;
dateFin: string;
statut: string;
evaluation: number;
role: string;
}>;
statistiques: {
nombreMissionsTerminees: number;
tauxReussite: number;
dureeMovenneMission: number;
evaluationMoyenne: number;
evolutionPerformance: Array<{
mois: string;
evaluation: number;
nombreMissions: number;
}>;
};
planning: Array<{
id: number;
chantierNom: string;
dateDebut: string;
dateFin: string;
statut: string;
role: string;
}>;
}
const EquipeDetailPage = () => {
const [equipe, setEquipe] = useState<EquipeDetail | null>(null);
const [loading, setLoading] = useState(true);
const router = useRouter();
const params = useParams();
const equipeId = params.id;
useEffect(() => {
if (equipeId) {
loadEquipeDetail();
}
}, [equipeId]);
const loadEquipeDetail = async () => {
try {
setLoading(true);
console.log('🔄 Chargement du détail équipe...', equipeId);
const response = await apiClient.get(`/api/equipes/${equipeId}`);
console.log('✅ Détail équipe chargé:', response.data);
setEquipe(response.data);
} catch (error) {
console.error('❌ Erreur lors du chargement du détail:', error);
} finally {
setLoading(false);
}
};
const toggleStatutEquipe = async () => {
if (!equipe) return;
try {
const newStatut = equipe.statut === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
const endpoint = newStatut === 'ACTIVE' ? 'activer' : 'desactiver';
await apiClient.post(`/api/equipes/${equipe.id}/${endpoint}`);
setEquipe({ ...equipe, statut: newStatut });
console.log(`✅ Statut équipe ${newStatut.toLowerCase()}`);
} catch (error) {
console.error('❌ Erreur lors du changement de statut:', error);
}
};
const getStatutSeverity = (statut: string) => {
switch (statut) {
case 'ACTIVE': return 'success';
case 'INACTIVE': return 'danger';
case 'EN_MISSION': return 'warning';
case 'DISPONIBLE': return 'info';
default: return 'secondary';
}
};
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';
};
const getMetierColor = (metier: string) => {
const colors: { [key: string]: string } = {
'MACON': 'info',
'ELECTRICIEN': 'warning',
'PLOMBIER': 'primary',
'CHARPENTIER': 'success',
'PEINTRE': 'secondary',
'CHEF_EQUIPE': 'danger',
'CONDUCTEUR': 'help'
};
return colors[metier] || 'secondary';
};
const getPerformanceChartData = () => {
if (!equipe?.statistiques.evolutionPerformance) return {};
return {
labels: equipe.statistiques.evolutionPerformance.map(e => e.mois),
datasets: [{
label: 'Évaluation',
data: equipe.statistiques.evolutionPerformance.map(e => e.evaluation),
borderColor: '#4CAF50',
backgroundColor: 'rgba(76, 175, 80, 0.1)',
tension: 0.4
}]
};
};
const metierBodyTemplate = (rowData: any) => {
return <Tag value={rowData.metier} severity={getMetierColor(rowData.metier) as any} />;
};
const statutEmployeBodyTemplate = (rowData: any) => {
return <Tag value={rowData.statut} severity={getStatutSeverity(rowData.statut) as any} />;
};
const competencesBodyTemplate = (rowData: any) => {
return (
<div className="flex flex-wrap gap-1">
{rowData.competences?.slice(0, 2).map((comp: string, index: number) => (
<Tag key={index} value={comp} className="p-tag-sm" />
))}
{rowData.competences?.length > 2 && (
<Badge value={`+${rowData.competences.length - 2}`} severity="info" />
)}
</div>
);
};
const evaluationBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center gap-2">
<Tag value={`${rowData.evaluation}/5`} severity="success" />
<div className="flex">
{[1, 2, 3, 4, 5].map((star) => (
<i
key={star}
className={`pi pi-star${star <= rowData.evaluation ? '-fill' : ''} text-yellow-500`}
style={{ fontSize: '0.8rem' }}
/>
))}
</div>
</div>
);
};
const actionEmployeBodyTemplate = (rowData: any) => {
return (
<div className="flex gap-2">
<Button
icon="pi pi-eye"
className="p-button-rounded p-button-info p-button-sm"
onClick={() => router.push(`/employes/${rowData.id}`)}
tooltip="Voir détails"
/>
<Button
icon="pi pi-user-minus"
className="p-button-rounded p-button-warning p-button-sm"
onClick={() => console.log('Retirer de l\'équipe')}
tooltip="Retirer de l'équipe"
/>
</div>
);
};
const leftToolbarTemplate = () => {
return (
<div className="flex flex-wrap gap-2">
<Button
label="Retour"
icon="pi pi-arrow-left"
className="p-button-outlined"
onClick={() => router.push('/equipes')}
/>
<Button
label="Modifier"
icon="pi pi-pencil"
className="p-button-success"
onClick={() => router.push(`/equipes/${equipeId}/edit`)}
/>
<Button
label={equipe?.statut === 'ACTIVE' ? 'Désactiver' : 'Activer'}
icon={equipe?.statut === 'ACTIVE' ? 'pi pi-pause' : 'pi pi-play'}
className={equipe?.statut === 'ACTIVE' ? 'p-button-warning' : 'p-button-success'}
onClick={toggleStatutEquipe}
/>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<Button
label="Planning"
icon="pi pi-calendar"
className="p-button-info"
onClick={() => router.push(`/planning?equipeId=${equipeId}`)}
/>
<Button
label="Assigner"
icon="pi pi-calendar-plus"
className="p-button-warning"
onClick={() => router.push(`/planning/assigner?equipeId=${equipeId}`)}
/>
<Button
icon="pi pi-refresh"
className="p-button-outlined"
onClick={loadEquipeDetail}
tooltip="Actualiser"
/>
</div>
);
};
if (loading) {
return (
<div className="grid">
<div className="col-12">
<Card>
<div className="flex justify-content-center">
<i className="pi pi-spin pi-spinner" style={{ fontSize: '2rem' }} />
</div>
</Card>
</div>
</div>
);
}
if (!equipe) {
return (
<div className="grid">
<div className="col-12">
<Card>
<div className="text-center">
<p>Équipe non trouvée</p>
</div>
</Card>
</div>
</div>
);
}
return (
<div className="grid">
<div className="col-12">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
</div>
{/* En-tête équipe */}
<div className="col-12">
<Card>
<div className="flex flex-column lg:flex-row lg:align-items-center gap-4">
<div className="flex align-items-center justify-content-center bg-blue-100 border-round" style={{ width: '4rem', height: '4rem' }}>
<i className="pi pi-users text-blue-500 text-2xl" />
</div>
<div className="flex-1">
<h2 className="m-0 mb-2">{equipe.nom}</h2>
<div className="flex flex-wrap gap-2 mb-2">
<Tag value={equipe.specialite} severity={getSpecialiteColor(equipe.specialite) as any} />
<Tag value={equipe.statut} severity={getStatutSeverity(equipe.statut) as any} />
<Badge value={`${equipe.nombreEmployes} employés`} severity="info" />
<Tag value={`${equipe.tauxOccupation}% occupation`} severity="warning" />
</div>
<div className="text-600">
<p className="m-0">📍 {equipe.localisation}</p>
<p className="m-0">💰 {equipe.coutJournalier.toLocaleString('fr-FR')} /jour</p>
{equipe.chefEquipeNom && <p className="m-0">👤 Chef: {equipe.chefEquipeNom}</p>}
{equipe.chantierActuel && <p className="m-0">🏗 Chantier actuel: {equipe.chantierActuel.nom}</p>}
</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-500 mb-1">{equipe.evaluationPerformance}/5</div>
<div className="text-500 mb-2">Performance</div>
<div className="flex justify-content-center">
{[1, 2, 3, 4, 5].map((star) => (
<i
key={star}
className={`pi pi-star${star <= equipe.evaluationPerformance ? '-fill' : ''} text-yellow-500`}
/>
))}
</div>
</div>
</div>
</Card>
</div>
{/* Onglets de détail */}
<div className="col-12">
<Card>
<TabView>
<TabPanel header="Informations Générales">
<div className="grid">
<div className="col-12 md:col-6">
<h4>Informations de base</h4>
<div className="field">
<label className="font-medium">Nom:</label>
<p>{equipe.nom}</p>
</div>
<div className="field">
<label className="font-medium">Description:</label>
<p>{equipe.description || 'Aucune description'}</p>
</div>
<div className="field">
<label className="font-medium">Spécialité:</label>
<p><Tag value={equipe.specialite} severity={getSpecialiteColor(equipe.specialite) as any} /></p>
</div>
<div className="field">
<label className="font-medium">Date de création:</label>
<p>{new Date(equipe.dateCreation).toLocaleDateString('fr-FR')}</p>
</div>
<div className="field">
<label className="font-medium">Localisation:</label>
<p>{equipe.localisation}</p>
</div>
</div>
<div className="col-12 md:col-6">
<h4>Métriques</h4>
<div className="field">
<label className="font-medium">Statut:</label>
<p><Tag value={equipe.statut} severity={getStatutSeverity(equipe.statut) as any} /></p>
</div>
<div className="field">
<label className="font-medium">Nombre d'employés:</label>
<p><Badge value={equipe.nombreEmployes} severity="info" /></p>
</div>
<div className="field">
<label className="font-medium">Taux d'occupation:</label>
<div className="flex align-items-center gap-2">
<ProgressBar value={equipe.tauxOccupation} className="flex-1" />
<span>{equipe.tauxOccupation}%</span>
</div>
</div>
<div className="field">
<label className="font-medium">Coût journalier:</label>
<p>{equipe.coutJournalier.toLocaleString('fr-FR')} </p>
</div>
<div className="field">
<label className="font-medium">Évaluation performance:</label>
<p>{equipe.evaluationPerformance}/5</p>
</div>
</div>
</div>
</TabPanel>
<TabPanel header="Compétences">
<h4>Compétences de l'équipe</h4>
{equipe.competencesEquipe && equipe.competencesEquipe.length > 0 ? (
<div className="flex flex-wrap gap-2">
{equipe.competencesEquipe.map((comp, index) => (
<Tag key={index} value={comp} severity="info" />
))}
</div>
) : (
<p className="text-500">Aucune compétence renseignée</p>
)}
</TabPanel>
<TabPanel header="Employés">
<DataTable
value={equipe.employes}
emptyMessage="Aucun employé dans cette équipe"
responsiveLayout="scroll"
>
<Column
field="nom"
header="Nom"
body={(rowData) => `${rowData.prenom} ${rowData.nom}`}
/>
<Column field="metier" header="Métier" body={metierBodyTemplate} />
<Column field="statut" header="Statut" body={statutEmployeBodyTemplate} />
<Column field="niveauExperience" header="Expérience" />
<Column field="competences" header="Compétences" body={competencesBodyTemplate} />
<Column
field="dateAffectation"
header="Affecté le"
body={(rowData) => new Date(rowData.dateAffectation).toLocaleDateString('fr-FR')}
/>
<Column body={actionEmployeBodyTemplate} header="Actions" />
</DataTable>
</TabPanel>
<TabPanel header="Planning">
<DataTable
value={equipe.planning}
emptyMessage="Aucun planning en cours"
responsiveLayout="scroll"
>
<Column field="chantierNom" header="Chantier" />
<Column
field="dateDebut"
header="Date début"
body={(rowData) => new Date(rowData.dateDebut).toLocaleDateString('fr-FR')}
/>
<Column
field="dateFin"
header="Date fin"
body={(rowData) => new Date(rowData.dateFin).toLocaleDateString('fr-FR')}
/>
<Column
field="statut"
header="Statut"
body={(rowData) => <Tag value={rowData.statut} severity="info" />}
/>
<Column field="role" header="Rôle" />
</DataTable>
</TabPanel>
<TabPanel header="Historique">
<DataTable
value={equipe.historiqueMissions}
emptyMessage="Aucun historique disponible"
responsiveLayout="scroll"
>
<Column field="chantierNom" header="Chantier" />
<Column
field="dateDebut"
header="Date début"
body={(rowData) => new Date(rowData.dateDebut).toLocaleDateString('fr-FR')}
/>
<Column
field="dateFin"
header="Date fin"
body={(rowData) => new Date(rowData.dateFin).toLocaleDateString('fr-FR')}
/>
<Column field="role" header="Rôle" />
<Column field="evaluation" header="Évaluation" body={evaluationBodyTemplate} />
</DataTable>
</TabPanel>
<TabPanel header="Statistiques">
<div className="grid">
<div className="col-12 md:col-6">
<h4>Métriques de performance</h4>
<div className="field">
<label className="font-medium">Missions terminées:</label>
<p><Badge value={equipe.statistiques?.nombreMissionsTerminees || 0} severity="success" /></p>
</div>
<div className="field">
<label className="font-medium">Taux de réussite:</label>
<p>{equipe.statistiques?.tauxReussite || 0}%</p>
</div>
<div className="field">
<label className="font-medium">Durée moyenne mission:</label>
<p>{equipe.statistiques?.dureeMovenneMission || 0} jours</p>
</div>
<div className="field">
<label className="font-medium">Évaluation moyenne:</label>
<p>{equipe.statistiques?.evaluationMoyenne || 0}/5</p>
</div>
</div>
<div className="col-12 md:col-6">
<h4>Évolution des performances</h4>
<Chart
type="line"
data={getPerformanceChartData()}
style={{ height: '300px' }}
/>
</div>
</div>
</TabPanel>
</TabView>
</Card>
</div>
</div>
);
};
export default EquipeDetailPage;