fix: Change Button size from normal to small

This commit is contained in:
DahoudG
2025-10-15 20:12:57 +00:00
parent bd3c342acd
commit 6269c0d096

View File

@@ -1,497 +1,497 @@
'use client'; 'use client';
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { DataTable } from 'primereact/datatable'; import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column'; import { Column } from 'primereact/column';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast'; import { Toast } from 'primereact/toast';
import { Card } from 'primereact/card'; import { Card } from 'primereact/card';
import { Tag } from 'primereact/tag'; import { Tag } from 'primereact/tag';
import { Badge } from 'primereact/badge'; import { Badge } from 'primereact/badge';
import { ProgressBar } from 'primereact/progressbar'; import { ProgressBar } from 'primereact/progressbar';
import { Toolbar } from 'primereact/toolbar'; import { Toolbar } from 'primereact/toolbar';
import { Panel } from 'primereact/panel'; import { Panel } from 'primereact/panel';
import { Chart } from 'primereact/chart'; import { Chart } from 'primereact/chart';
import { Timeline } from 'primereact/timeline'; import { Timeline } from 'primereact/timeline';
import { Message } from 'primereact/message'; import { Message } from 'primereact/message';
import { Page } from '@/types'; import { Page } from '@/types';
import phaseChantierService from '@/services/phaseChantierService'; import phaseChantierService from '@/services/phaseChantierService';
import { PhaseChantier } from '@/types/btp-extended'; import { PhaseChantier } from '@/types/btp-extended';
const PhasesEnRetardPage: Page = () => { const PhasesEnRetardPage: Page = () => {
const [phasesEnRetard, setPhasesEnRetard] = useState<PhaseChantier[]>([]); const [phasesEnRetard, setPhasesEnRetard] = useState<PhaseChantier[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [statistiques, setStatistiques] = useState({ const [statistiques, setStatistiques] = useState({
total: 0, total: 0,
retardMoyen: 0, retardMoyen: 0,
impactBudget: 0, impactBudget: 0,
phasesUrgentes: 0 phasesUrgentes: 0
}); });
const [chartData, setChartData] = useState<any>({}); const [chartData, setChartData] = useState<any>({});
const [chartOptions, setChartOptions] = useState<any>({}); const [chartOptions, setChartOptions] = useState<any>({});
const toast = useRef<Toast>(null); const toast = useRef<Toast>(null);
useEffect(() => { useEffect(() => {
loadPhasesEnRetard(); loadPhasesEnRetard();
initChart(); initChart();
}, []); }, []);
const loadPhasesEnRetard = async () => { const loadPhasesEnRetard = async () => {
try { try {
setLoading(true); setLoading(true);
const data = await phaseChantierService.getEnRetard(); const data = await phaseChantierService.getEnRetard();
setPhasesEnRetard(data || []); setPhasesEnRetard(data || []);
// Calculer les statistiques // Calculer les statistiques
if (data && data.length > 0) { if (data && data.length > 0) {
const retards = data.map(phase => phaseChantierService.calculateRetard(phase)); const retards = data.map(phase => phaseChantierService.calculateRetard(phase));
const retardMoyen = retards.reduce((a, b) => a + b, 0) / retards.length; const retardMoyen = retards.reduce((a, b) => a + b, 0) / retards.length;
const impactBudget = data.reduce((total, phase) => { const impactBudget = data.reduce((total, phase) => {
const ecart = (phase.coutReel || 0) - (phase.budgetPrevu || 0); const ecart = (phase.coutReel || 0) - (phase.budgetPrevu || 0);
return total + (ecart > 0 ? ecart : 0); return total + (ecart > 0 ? ecart : 0);
}, 0); }, 0);
const phasesUrgentes = data.filter(phase => const phasesUrgentes = data.filter(phase =>
phaseChantierService.calculateRetard(phase) > 30 phaseChantierService.calculateRetard(phase) > 30
).length; ).length;
setStatistiques({ setStatistiques({
total: data.length, total: data.length,
retardMoyen: Math.round(retardMoyen), retardMoyen: Math.round(retardMoyen),
impactBudget, impactBudget,
phasesUrgentes phasesUrgentes
}); });
} }
} catch (error) { } catch (error) {
console.error('Erreur lors du chargement des phases en retard:', error); console.error('Erreur lors du chargement des phases en retard:', error);
toast.current?.show({ toast.current?.show({
severity: 'error', severity: 'error',
summary: 'Erreur', summary: 'Erreur',
detail: 'Impossible de charger les phases en retard', detail: 'Impossible de charger les phases en retard',
life: 3000 life: 3000
}); });
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const initChart = () => { const initChart = () => {
const documentStyle = getComputedStyle(document.documentElement); const documentStyle = getComputedStyle(document.documentElement);
const textColor = documentStyle.getPropertyValue('--text-color'); const textColor = documentStyle.getPropertyValue('--text-color');
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary'); const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
const surfaceBorder = documentStyle.getPropertyValue('--surface-border'); const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
const data = { const data = {
labels: ['1-7 jours', '8-15 jours', '16-30 jours', '> 30 jours'], labels: ['1-7 jours', '8-15 jours', '16-30 jours', '> 30 jours'],
datasets: [ datasets: [
{ {
label: 'Phases en retard', label: 'Phases en retard',
backgroundColor: ['#FFF3CD', '#FCF8E3', '#F8D7DA', '#D32F2F'], backgroundColor: ['#FFF3CD', '#FCF8E3', '#F8D7DA', '#D32F2F'],
borderColor: ['#856404', '#856404', '#721C24', '#B71C1C'], borderColor: ['#856404', '#856404', '#721C24', '#B71C1C'],
data: [0, 0, 0, 0] // Sera calculé dynamiquement data: [0, 0, 0, 0] // Sera calculé dynamiquement
} }
] ]
}; };
const options = { const options = {
maintainAspectRatio: false, maintainAspectRatio: false,
aspectRatio: 0.6, aspectRatio: 0.6,
plugins: { plugins: {
legend: { legend: {
labels: { labels: {
fontColor: textColor fontColor: textColor
} }
} }
}, },
scales: { scales: {
x: { x: {
ticks: { ticks: {
color: textColorSecondary color: textColorSecondary
}, },
grid: { grid: {
color: surfaceBorder color: surfaceBorder
} }
}, },
y: { y: {
ticks: { ticks: {
color: textColorSecondary color: textColorSecondary
}, },
grid: { grid: {
color: surfaceBorder color: surfaceBorder
} }
} }
} }
}; };
setChartData(data); setChartData(data);
setChartOptions(options); setChartOptions(options);
}; };
const actionBodyTemplate = (rowData: PhaseChantier) => { const actionBodyTemplate = (rowData: PhaseChantier) => {
return ( return (
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button
icon="pi pi-play" icon="pi pi-play"
rounded rounded
severity="warning" severity="warning"
onClick={() => relancerPhase(rowData)} onClick={() => relancerPhase(rowData)}
tooltip="Relancer la phase" tooltip="Relancer la phase"
size="small" size="small"
/> />
<Button <Button
icon="pi pi-calendar" icon="pi pi-calendar"
rounded rounded
severity="info" severity="info"
onClick={() => replanifierPhase(rowData)} onClick={() => replanifierPhase(rowData)}
tooltip="Replanifier" tooltip="Replanifier"
size="small" size="small"
/> />
<Button <Button
icon="pi pi-exclamation-triangle" icon="pi pi-exclamation-triangle"
rounded rounded
severity="danger" severity="danger"
onClick={() => escaladerPhase(rowData)} onClick={() => escaladerPhase(rowData)}
tooltip="Escalader" tooltip="Escalader"
size="small" size="small"
/> />
</div> </div>
); );
}; };
const retardBodyTemplate = (rowData: PhaseChantier) => { const retardBodyTemplate = (rowData: PhaseChantier) => {
const retard = phaseChantierService.calculateRetard(rowData); const retard = phaseChantierService.calculateRetard(rowData);
let severity: 'info' | 'warning' | 'danger' = 'info'; let severity: 'info' | 'warning' | 'danger' = 'info';
if (retard > 30) severity = 'danger'; if (retard > 30) severity = 'danger';
else if (retard > 15) severity = 'warning'; else if (retard > 15) severity = 'warning';
return ( return (
<Badge <Badge
value={`${retard} jours`} value={`${retard} jours`}
severity={severity} severity={severity}
size="large" size="large"
/> />
); );
}; };
const impactBodyTemplate = (rowData: PhaseChantier) => { const impactBodyTemplate = (rowData: PhaseChantier) => {
const budgetPrevu = rowData.budgetPrevu || 0; const budgetPrevu = rowData.budgetPrevu || 0;
const coutReel = rowData.coutReel || 0; const coutReel = rowData.coutReel || 0;
const ecart = coutReel - budgetPrevu; const ecart = coutReel - budgetPrevu;
if (ecart <= 0) return <Tag value="Budget respecté" severity="success" />; if (ecart <= 0) return <Tag value="Budget respecté" severity="success" />;
const pourcentageEcart = budgetPrevu > 0 ? (ecart / budgetPrevu * 100) : 0; const pourcentageEcart = budgetPrevu > 0 ? (ecart / budgetPrevu * 100) : 0;
return ( return (
<div className="flex flex-column gap-1"> <div className="flex flex-column gap-1">
<Tag <Tag
value={`+${new Intl.NumberFormat('fr-FR', { value={`+${new Intl.NumberFormat('fr-FR', {
style: 'currency', style: 'currency',
currency: 'EUR' currency: 'EUR'
}).format(ecart)}`} }).format(ecart)}`}
severity="danger" severity="danger"
/> />
<small className="text-color-secondary"> <small className="text-color-secondary">
+{pourcentageEcart.toFixed(1)}% +{pourcentageEcart.toFixed(1)}%
</small> </small>
</div> </div>
); );
}; };
const prioriteBodyTemplate = (rowData: PhaseChantier) => { const prioriteBodyTemplate = (rowData: PhaseChantier) => {
const retard = phaseChantierService.calculateRetard(rowData); const retard = phaseChantierService.calculateRetard(rowData);
const critique = rowData.critique; const critique = rowData.critique;
let priorite = 'Normale'; let priorite = 'Normale';
let severity: 'info' | 'warning' | 'danger' = 'info'; let severity: 'info' | 'warning' | 'danger' = 'info';
if (critique && retard > 15) { if (critique && retard > 15) {
priorite = 'URGENTE'; priorite = 'URGENTE';
severity = 'danger'; severity = 'danger';
} else if (retard > 30) { } else if (retard > 30) {
priorite = 'Très haute'; priorite = 'Très haute';
severity = 'danger'; severity = 'danger';
} else if (retard > 15) { } else if (retard > 15) {
priorite = 'Haute'; priorite = 'Haute';
severity = 'warning'; severity = 'warning';
} }
return <Tag value={priorite} severity={severity} />; return <Tag value={priorite} severity={severity} />;
}; };
const responsableBodyTemplate = (rowData: PhaseChantier) => { const responsableBodyTemplate = (rowData: PhaseChantier) => {
return rowData.responsable ? return rowData.responsable ?
`${rowData.responsable.prenom} ${rowData.responsable.nom}` : `${rowData.responsable.prenom} ${rowData.responsable.nom}` :
'Non assigné'; 'Non assigné';
}; };
const relancerPhase = async (phase: PhaseChantier) => { const relancerPhase = async (phase: PhaseChantier) => {
try { try {
if (phase.id) { if (phase.id) {
await phaseChantierService.resume(phase.id); await phaseChantierService.resume(phase.id);
await loadPhasesEnRetard(); await loadPhasesEnRetard();
toast.current?.show({ toast.current?.show({
severity: 'success', severity: 'success',
summary: 'Succès', summary: 'Succès',
detail: 'Phase relancée avec succès', detail: 'Phase relancée avec succès',
life: 3000 life: 3000
}); });
} }
} catch (error) { } catch (error) {
toast.current?.show({ toast.current?.show({
severity: 'error', severity: 'error',
summary: 'Erreur', summary: 'Erreur',
detail: 'Erreur lors de la relance', detail: 'Erreur lors de la relance',
life: 3000 life: 3000
}); });
} }
}; };
const replanifierPhase = (phase: PhaseChantier) => { const replanifierPhase = (phase: PhaseChantier) => {
// TODO: Ouvrir un dialog de replanification // TODO: Ouvrir un dialog de replanification
toast.current?.show({ toast.current?.show({
severity: 'info', severity: 'info',
summary: 'Fonctionnalité', summary: 'Fonctionnalité',
detail: 'Replanification à implémenter', detail: 'Replanification à implémenter',
life: 3000 life: 3000
}); });
}; };
const escaladerPhase = (phase: PhaseChantier) => { const escaladerPhase = (phase: PhaseChantier) => {
// TODO: Implémenter l'escalade (notification aux responsables) // TODO: Implémenter l'escalade (notification aux responsables)
toast.current?.show({ toast.current?.show({
severity: 'warn', severity: 'warn',
summary: 'Escalade', summary: 'Escalade',
detail: `Phase ${phase.nom} escaladée vers la direction`, detail: `Phase ${phase.nom} escaladée vers la direction`,
life: 3000 life: 3000
}); });
}; };
const exportData = () => { const exportData = () => {
// TODO: Implémenter l'export des données // TODO: Implémenter l'export des données
toast.current?.show({ toast.current?.show({
severity: 'info', severity: 'info',
summary: 'Export', summary: 'Export',
detail: 'Export en cours...', detail: 'Export en cours...',
life: 3000 life: 3000
}); });
}; };
const leftToolbarTemplate = () => { const leftToolbarTemplate = () => {
return ( return (
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<Button <Button
label="Actualiser" label="Actualiser"
icon="pi pi-refresh" icon="pi pi-refresh"
onClick={loadPhasesEnRetard} onClick={loadPhasesEnRetard}
/> />
<Button <Button
label="Exporter" label="Exporter"
icon="pi pi-download" icon="pi pi-download"
severity="help" severity="help"
onClick={exportData} onClick={exportData}
/> />
</div> </div>
); );
}; };
const rightToolbarTemplate = () => { const rightToolbarTemplate = () => {
return ( return (
<div className="flex align-items-center gap-2"> <div className="flex align-items-center gap-2">
<Tag <Tag
value={`${phasesEnRetard.length} phases en retard`} value={`${phasesEnRetard.length} phases en retard`}
severity="danger" severity="danger"
icon="pi pi-exclamation-triangle" icon="pi pi-exclamation-triangle"
/> />
</div> </div>
); );
}; };
// Timeline des actions recommandées // Timeline des actions recommandées
const actionsRecommandees = [ const actionsRecommandees = [
{ {
status: 'Immédiat', status: 'Immédiat',
date: 'Aujourd\'hui', date: 'Aujourd\'hui',
icon: 'pi pi-exclamation-triangle', icon: 'pi pi-exclamation-triangle',
color: '#FF6B6B', color: '#FF6B6B',
description: `${statistiques.phasesUrgentes} phases critiques à traiter en urgence` description: `${statistiques.phasesUrgentes} phases critiques à traiter en urgence`
}, },
{ {
status: 'Cette semaine', status: 'Cette semaine',
date: '7 jours', date: '7 jours',
icon: 'pi pi-calendar', icon: 'pi pi-calendar',
color: '#4ECDC4', color: '#4ECDC4',
description: 'Replanification des phases avec retard modéré' description: 'Replanification des phases avec retard modéré'
}, },
{ {
status: 'Ce mois', status: 'Ce mois',
date: '30 jours', date: '30 jours',
icon: 'pi pi-chart-line', icon: 'pi pi-chart-line',
color: '#45B7D1', color: '#45B7D1',
description: 'Analyse des causes et mise en place d\'actions préventives' description: 'Analyse des causes et mise en place d\'actions préventives'
} }
]; ];
return ( return (
<div className="grid"> <div className="grid">
<div className="col-12"> <div className="col-12">
<Toast ref={toast} /> <Toast ref={toast} />
{/* Alerte si phases critiques */} {/* Alerte si phases critiques */}
{statistiques.phasesUrgentes > 0 && ( {statistiques.phasesUrgentes > 0 && (
<Message <Message
severity="error" severity="error"
text={`⚠️ ALERTE: ${statistiques.phasesUrgentes} phases critiques nécessitent une intervention immédiate!`} text={`⚠️ ALERTE: ${statistiques.phasesUrgentes} phases critiques nécessitent une intervention immédiate!`}
className="w-full mb-4" className="w-full mb-4"
/> />
)} )}
{/* Statistiques de retard */} {/* Statistiques de retard */}
<div className="grid mb-4"> <div className="grid mb-4">
<div className="col-12 md:col-3"> <div className="col-12 md:col-3">
<Card> <Card>
<div className="text-center"> <div className="text-center">
<div className="text-3xl font-bold text-red-500 mb-2"> <div className="text-3xl font-bold text-red-500 mb-2">
<i className="pi pi-exclamation-triangle mr-2"></i> <i className="pi pi-exclamation-triangle mr-2"></i>
{statistiques.total} {statistiques.total}
</div> </div>
<div className="text-color-secondary">Phases en retard</div> <div className="text-color-secondary">Phases en retard</div>
</div> </div>
</Card> </Card>
</div> </div>
<div className="col-12 md:col-3"> <div className="col-12 md:col-3">
<Card> <Card>
<div className="text-center"> <div className="text-center">
<div className="text-3xl font-bold text-orange-500 mb-2"> <div className="text-3xl font-bold text-orange-500 mb-2">
{statistiques.retardMoyen}j {statistiques.retardMoyen}j
</div> </div>
<div className="text-color-secondary">Retard moyen</div> <div className="text-color-secondary">Retard moyen</div>
</div> </div>
</Card> </Card>
</div> </div>
<div className="col-12 md:col-3"> <div className="col-12 md:col-3">
<Card> <Card>
<div className="text-center"> <div className="text-center">
<div className="text-3xl font-bold text-red-600 mb-2"> <div className="text-3xl font-bold text-red-600 mb-2">
{new Intl.NumberFormat('fr-FR', { {new Intl.NumberFormat('fr-FR', {
style: 'currency', style: 'currency',
currency: 'EUR', currency: 'EUR',
notation: 'compact' notation: 'compact'
}).format(statistiques.impactBudget)} }).format(statistiques.impactBudget)}
</div> </div>
<div className="text-color-secondary">Impact budget</div> <div className="text-color-secondary">Impact budget</div>
</div> </div>
</Card> </Card>
</div> </div>
<div className="col-12 md:col-3"> <div className="col-12 md:col-3">
<Card> <Card>
<div className="text-center"> <div className="text-center">
<div className="text-3xl font-bold text-purple-500 mb-2"> <div className="text-3xl font-bold text-purple-500 mb-2">
{statistiques.phasesUrgentes} {statistiques.phasesUrgentes}
</div> </div>
<div className="text-color-secondary">Phases urgentes</div> <div className="text-color-secondary">Phases urgentes</div>
</div> </div>
</Card> </Card>
</div> </div>
</div> </div>
<div className="grid"> <div className="grid">
{/* Liste des phases en retard */} {/* Liste des phases en retard */}
<div className="col-12 lg:col-8"> <div className="col-12 lg:col-8">
<Card title="Phases en Retard - Actions Requises"> <Card title="Phases en Retard - Actions Requises">
<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} /> <Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate} />
<DataTable <DataTable
value={phasesEnRetard} value={phasesEnRetard}
paginator paginator
rows={10} rows={10}
dataKey="id" dataKey="id"
loading={loading} loading={loading}
emptyMessage="Aucune phase en retard (excellente nouvelle !)" emptyMessage="Aucune phase en retard (excellente nouvelle !)"
className="datatable-responsive" className="datatable-responsive"
> >
<Column field="nom" header="Phase" sortable style={{ minWidth: '12rem' }} /> <Column field="nom" header="Phase" sortable style={{ minWidth: '12rem' }} />
<Column <Column
header="Chantier" header="Chantier"
body={(rowData) => rowData.chantier?.nom || 'N/A'} body={(rowData) => rowData.chantier?.nom || 'N/A'}
sortable sortable
style={{ minWidth: '10rem' }} style={{ minWidth: '10rem' }}
/> />
<Column <Column
header="Responsable" header="Responsable"
body={responsableBodyTemplate} body={responsableBodyTemplate}
style={{ minWidth: '10rem' }} style={{ minWidth: '10rem' }}
/> />
<Column <Column
header="Retard" header="Retard"
body={retardBodyTemplate} body={retardBodyTemplate}
sortable sortable
style={{ minWidth: '8rem' }} style={{ minWidth: '8rem' }}
/> />
<Column <Column
header="Priorité" header="Priorité"
body={prioriteBodyTemplate} body={prioriteBodyTemplate}
style={{ minWidth: '8rem' }} style={{ minWidth: '8rem' }}
/> />
<Column <Column
header="Impact Budget" header="Impact Budget"
body={impactBodyTemplate} body={impactBodyTemplate}
style={{ minWidth: '10rem' }} style={{ minWidth: '10rem' }}
/> />
<Column <Column
body={actionBodyTemplate} body={actionBodyTemplate}
exportable={false} exportable={false}
style={{ minWidth: '12rem' }} style={{ minWidth: '12rem' }}
header="Actions" header="Actions"
/> />
</DataTable> </DataTable>
</Card> </Card>
</div> </div>
{/* Actions recommandées */} {/* Actions recommandées */}
<div className="col-12 lg:col-4"> <div className="col-12 lg:col-4">
<Card title="Plan d'Action Recommandé"> <Card title="Plan d'Action Recommandé">
<Timeline <Timeline
value={actionsRecommandees} value={actionsRecommandees}
align="left" align="left"
className="customized-timeline" className="customized-timeline"
marker={(item) => ( marker={(item) => (
<span <span
className="flex w-2rem h-2rem align-items-center justify-content-center text-white border-circle z-1 shadow-1" className="flex w-2rem h-2rem align-items-center justify-content-center text-white border-circle z-1 shadow-1"
style={{ backgroundColor: item.color }} style={{ backgroundColor: item.color }}
> >
<i className={item.icon}></i> <i className={item.icon}></i>
</span> </span>
)} )}
content={(item) => ( content={(item) => (
<Card className="mt-3"> <Card className="mt-3">
<div className="flex flex-column"> <div className="flex flex-column">
<div className="text-lg font-bold mb-2" style={{ color: item.color }}> <div className="text-lg font-bold mb-2" style={{ color: item.color }}>
{item.status} {item.status}
</div> </div>
<div className="text-sm text-color-secondary mb-2"> <div className="text-sm text-color-secondary mb-2">
{item.date} {item.date}
</div> </div>
<div className="text-color"> <div className="text-color">
{item.description} {item.description}
</div> </div>
</div> </div>
</Card> </Card>
)} )}
/> />
</Card> </Card>
<Card title="Répartition des Retards" className="mt-4"> <Card title="Répartition des Retards" className="mt-4">
<Chart type="doughnut" data={chartData} options={chartOptions} style={{ height: '300px' }} /> <Chart type="doughnut" data={chartData} options={chartOptions} style={{ height: '300px' }} />
</Card> </Card>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
}; };
export default PhasesEnRetardPage; export default PhasesEnRetardPage;