Files
btpxpress-frontend/app/(main)/equipes/specialites/page.tsx

386 lines
16 KiB
TypeScript

'use client';
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 { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { TabView, TabPanel } from 'primereact/tabview';
import { ProgressBar } from 'primereact/progressbar';
import { useRouter } from 'next/navigation';
import { apiClient } from '../../../../services/api-client';
interface EquipeParSpecialite {
specialite: string;
nombreEquipes: number;
equipesActives: number;
equipesDisponibles: number;
equipesEnMission: number;
tauxOccupationMoyen: number;
competencesPrincipales: string[];
equipes: Array<{
id: number;
nom: string;
statut: string;
nombreEmployes: number;
chefEquipeNom?: string;
chantierActuel?: string;
tauxOccupation: number;
evaluationPerformance: number;
}>;
}
const EquipesSpecialitesPage = () => {
const [specialites, setSpecialites] = useState<EquipeParSpecialite[]>([]);
const [loading, setLoading] = useState(true);
const [activeIndex, setActiveIndex] = useState(0);
const router = useRouter();
const specialiteLabels: { [key: string]: string } = {
'GROS_OEUVRE': 'Gros Œuvre',
'SECOND_OEUVRE': 'Second Œuvre',
'FINITIONS': 'Finitions',
'ELECTRICITE': 'Électricité',
'PLOMBERIE': 'Plomberie',
'CHARPENTE': 'Charpente',
'COUVERTURE': 'Couverture',
'TERRASSEMENT': 'Terrassement'
};
const specialiteColors: { [key: string]: string } = {
'GROS_OEUVRE': 'danger',
'SECOND_OEUVRE': 'warning',
'FINITIONS': 'success',
'ELECTRICITE': 'info',
'PLOMBERIE': 'primary',
'CHARPENTE': 'help',
'COUVERTURE': 'secondary',
'TERRASSEMENT': 'contrast'
};
useEffect(() => {
loadEquipesParSpecialite();
}, []);
const loadEquipesParSpecialite = async () => {
try {
setLoading(true);
console.log('🔄 Chargement des équipes par spécialité...');
const response = await apiClient.get('/api/equipes/par-specialite');
console.log('✅ Équipes par spécialité chargées:', response.data);
setSpecialites(response.data || []);
} catch (error) {
console.error('❌ Erreur lors du chargement:', error);
setSpecialites([]);
} finally {
setLoading(false);
}
};
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 getOccupationColor = (taux: number) => {
if (taux >= 90) return 'danger';
if (taux >= 70) return 'warning';
if (taux >= 40) return 'success';
return 'info';
};
const statutBodyTemplate = (rowData: any) => {
return <Tag value={rowData.statut} severity={getStatutSeverity(rowData.statut) as any} />;
};
const chefEquipeBodyTemplate = (rowData: any) => {
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 occupationBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center gap-2">
<Tag value={`${rowData.tauxOccupation}%`} severity={getOccupationColor(rowData.tauxOccupation)} />
{rowData.chantierActuel && (
<small className="text-500">{rowData.chantierActuel}</small>
)}
</div>
);
};
const performanceBodyTemplate = (rowData: any) => {
return (
<div className="flex align-items-center gap-2">
<Tag value={`${rowData.evaluationPerformance}/5`} severity="success" />
<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 actionBodyTemplate = (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(`/equipes/${rowData.id}`)}
tooltip="Voir détails"
/>
<Button
icon="pi pi-calendar"
className="p-button-rounded p-button-warning p-button-sm"
onClick={() => router.push(`/planning?equipeId=${rowData.id}`)}
tooltip="Planning"
/>
</div>
);
};
const renderSpecialiteCard = (specialiteData: EquipeParSpecialite) => {
return (
<Card key={specialiteData.specialite} className="mb-4">
<div className="flex justify-content-between align-items-center mb-4">
<div className="flex align-items-center gap-3">
<Tag
value={specialiteLabels[specialiteData.specialite]}
severity={specialiteColors[specialiteData.specialite] as any}
className="text-lg"
/>
<Badge value={specialiteData.nombreEquipes} severity="info" />
<span>équipes</span>
</div>
<Button
label="Nouvelle équipe"
icon="pi pi-plus"
className="p-button-success p-button-sm"
onClick={() => router.push(`/equipes/nouvelle?specialite=${specialiteData.specialite}`)}
/>
</div>
{/* Métriques */}
<div className="grid mb-4">
<div className="col-12 md:col-3">
<div className="text-center">
<div className="text-2xl font-bold text-green-500">{specialiteData.equipesActives}</div>
<div className="text-500">Actives</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center">
<div className="text-2xl font-bold text-blue-500">{specialiteData.equipesDisponibles}</div>
<div className="text-500">Disponibles</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center">
<div className="text-2xl font-bold text-orange-500">{specialiteData.equipesEnMission}</div>
<div className="text-500">En mission</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center">
<div className="text-2xl font-bold text-purple-500">{specialiteData.tauxOccupationMoyen}%</div>
<div className="text-500">Occupation</div>
<ProgressBar
value={specialiteData.tauxOccupationMoyen}
className="mt-2"
color={getOccupationColor(specialiteData.tauxOccupationMoyen)}
/>
</div>
</div>
</div>
{/* Compétences principales */}
<div className="mb-4">
<h4>Compétences principales</h4>
<div className="flex flex-wrap gap-2">
{specialiteData.competencesPrincipales?.map((comp, index) => (
<Tag key={index} value={comp} severity="info" />
))}
</div>
</div>
{/* Liste des équipes */}
<DataTable
value={specialiteData.equipes}
responsiveLayout="scroll"
emptyMessage="Aucune équipe dans cette spécialité"
>
<Column field="nom" header="Nom" sortable />
<Column field="statut" header="Statut" body={statutBodyTemplate} sortable />
<Column field="nombreEmployes" header="Employés" sortable />
<Column field="chefEquipeNom" header="Chef d'équipe" body={chefEquipeBodyTemplate} />
<Column field="tauxOccupation" header="Occupation" body={occupationBodyTemplate} sortable />
<Column field="evaluationPerformance" header="Performance" body={performanceBodyTemplate} sortable />
<Column body={actionBodyTemplate} header="Actions" style={{ minWidth: '8rem' }} />
</DataTable>
</Card>
);
};
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="Vue globale"
icon="pi pi-th-large"
className="p-button-info"
onClick={() => router.push('/equipes')}
/>
<Button
label="Équipes disponibles"
icon="pi pi-check-circle"
className="p-button-success"
onClick={() => router.push('/equipes/disponibles')}
/>
</div>
);
};
const rightToolbarTemplate = () => {
return (
<div className="flex gap-2">
<Button
label="Statistiques"
icon="pi pi-chart-bar"
className="p-button-secondary"
onClick={() => router.push('/equipes/stats')}
/>
<Button
icon="pi pi-refresh"
className="p-button-outlined"
onClick={loadEquipesParSpecialite}
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>
);
}
return (
<div className="grid">
<div className="col-12">
<Toolbar
className="mb-4"
left={leftToolbarTemplate}
right={rightToolbarTemplate}
/>
</div>
<div className="col-12">
<Card>
<div className="flex justify-content-between align-items-center mb-4">
<h2 className="m-0">Équipes par Spécialité</h2>
<div className="flex gap-2">
<Badge value={specialites.length} severity="info" />
<span>spécialités</span>
<Badge value={specialites.reduce((acc, s) => acc + s.nombreEquipes, 0)} severity="success" />
<span>équipes total</span>
</div>
</div>
<TabView activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
<TabPanel header="Vue par Spécialité">
<div className="grid">
{specialites.map((specialiteData) => (
<div key={specialiteData.specialite} className="col-12">
{renderSpecialiteCard(specialiteData)}
</div>
))}
</div>
</TabPanel>
<TabPanel header="Vue Synthétique">
<div className="grid">
{specialites.map((specialiteData) => (
<div key={specialiteData.specialite} className="col-12 lg:col-6 xl:col-4">
<Card className="h-full">
<div className="text-center">
<Tag
value={specialiteLabels[specialiteData.specialite]}
severity={specialiteColors[specialiteData.specialite] as any}
className="mb-3"
/>
<div className="text-3xl font-bold mb-2">{specialiteData.nombreEquipes}</div>
<div className="text-500 mb-3">équipes</div>
<div className="grid text-sm">
<div className="col-6">
<div className="text-green-500 font-bold">{specialiteData.equipesActives}</div>
<div>Actives</div>
</div>
<div className="col-6">
<div className="text-blue-500 font-bold">{specialiteData.equipesDisponibles}</div>
<div>Disponibles</div>
</div>
</div>
<ProgressBar
value={specialiteData.tauxOccupationMoyen}
className="mt-3 mb-3"
/>
<Button
label="Voir détails"
icon="pi pi-eye"
className="p-button-sm w-full"
onClick={() => setActiveIndex(0)}
/>
</div>
</Card>
</div>
))}
</div>
</TabPanel>
</TabView>
</Card>
</div>
</div>
);
};
export default EquipesSpecialitesPage;