Initial commit
This commit is contained in:
385
app/(main)/equipes/specialites/page.tsx
Normal file
385
app/(main)/equipes/specialites/page.tsx
Normal file
@@ -0,0 +1,385 @@
|
||||
'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)} />;
|
||||
};
|
||||
|
||||
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]}
|
||||
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]}
|
||||
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;
|
||||
Reference in New Issue
Block a user