Initial commit
This commit is contained in:
107
components/chantiers/ActionButton.tsx
Normal file
107
components/chantiers/ActionButton.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Bouton d'action distinctif avec couleurs et animations personnalisées
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Tooltip } from 'primereact/tooltip';
|
||||
import {
|
||||
ACTION_BUTTON_THEMES,
|
||||
BUTTON_SIZES,
|
||||
BUTTON_VARIANTS,
|
||||
ActionButtonType
|
||||
} from './ActionButtonStyles';
|
||||
import ChantierMenuActions from './ChantierMenuActions';
|
||||
import { ChantierActif } from '../../hooks/useDashboard';
|
||||
|
||||
interface ActionButtonProps {
|
||||
type: ActionButtonType;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||
showLabel?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
tooltipPosition?: 'top' | 'bottom' | 'left' | 'right';
|
||||
// Propriétés pour le menu
|
||||
chantier?: ChantierActif;
|
||||
onMenuAction?: (action: string, chantier: ChantierActif) => void;
|
||||
}
|
||||
|
||||
const ActionButton: React.FC<ActionButtonProps> = ({
|
||||
type,
|
||||
onClick,
|
||||
disabled = false,
|
||||
loading = false,
|
||||
size = 'md',
|
||||
showLabel = false,
|
||||
className = '',
|
||||
style = {},
|
||||
tooltipPosition = 'top',
|
||||
chantier,
|
||||
onMenuAction
|
||||
}) => {
|
||||
const theme = ACTION_BUTTON_THEMES[type];
|
||||
|
||||
// ID unique pour le tooltip
|
||||
const buttonId = `action-btn-${type.toLowerCase()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
// Style minimal Atlantis Rounded Text - pas de styles custom
|
||||
const getButtonStyles = () => {
|
||||
return {
|
||||
...style // Seulement les styles passés en props
|
||||
};
|
||||
};
|
||||
|
||||
// Classes CSS strictes Atlantis Rounded Text
|
||||
const buttonClasses = [
|
||||
'p-button-text p-button-rounded p-button-sm', // Strict Atlantis + taille compacte
|
||||
disabled ? 'p-disabled' : '',
|
||||
className
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
// Rendu minimal Atlantis
|
||||
const buttonContent = (
|
||||
<>
|
||||
<i className={theme.icon} style={{ color: theme.colors.primary }} />
|
||||
{showLabel && <span className="ml-1">{theme.name}</span>}
|
||||
</>
|
||||
);
|
||||
|
||||
// Si c'est le bouton MENU et qu'on a un chantier, utiliser le composant menu
|
||||
if (type === 'MENU' && chantier && onMenuAction) {
|
||||
return (
|
||||
<ChantierMenuActions
|
||||
chantier={chantier}
|
||||
onAction={onMenuAction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
id={buttonId}
|
||||
className={buttonClasses}
|
||||
style={getButtonStyles()}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
loading={loading}
|
||||
aria-label={theme.name}
|
||||
>
|
||||
{buttonContent}
|
||||
</Button>
|
||||
|
||||
<Tooltip
|
||||
target={`#${buttonId}`}
|
||||
content={theme.name}
|
||||
position={tooltipPosition}
|
||||
showDelay={300}
|
||||
hideDelay={100}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionButton;
|
||||
73
components/chantiers/ActionButtonGroup.tsx
Normal file
73
components/chantiers/ActionButtonGroup.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Groupe de boutons d'action avec animations et espacement optimisé
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ActionButton from './ActionButton';
|
||||
import { ActionButtonType } from './ActionButtonStyles';
|
||||
import { ChantierActif } from '../../hooks/useDashboard';
|
||||
|
||||
interface ActionButtonGroupProps {
|
||||
chantier: ChantierActif;
|
||||
actions: ActionButtonType[];
|
||||
onAction: (action: ActionButtonType, chantier: ChantierActif) => void;
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
spacing?: 'none' | 'sm' | 'md' | 'lg';
|
||||
showLabels?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ActionButtonGroup: React.FC<ActionButtonGroupProps> = ({
|
||||
chantier,
|
||||
actions,
|
||||
onAction,
|
||||
size = 'md',
|
||||
orientation = 'horizontal',
|
||||
spacing = 'sm',
|
||||
showLabels = false,
|
||||
className = ''
|
||||
}) => {
|
||||
// Classes pour l'espacement - plus compact
|
||||
const spacingClasses = {
|
||||
none: 'gap-0',
|
||||
sm: 'gap-1',
|
||||
md: 'gap-1',
|
||||
lg: 'gap-2'
|
||||
};
|
||||
|
||||
// Classes pour l'orientation
|
||||
const orientationClasses = {
|
||||
horizontal: 'flex-row',
|
||||
vertical: 'flex-column'
|
||||
};
|
||||
|
||||
const containerClasses = [
|
||||
'flex',
|
||||
'align-items-center',
|
||||
orientationClasses[orientation],
|
||||
spacingClasses[spacing],
|
||||
'action-button-group',
|
||||
className
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div className={containerClasses}>
|
||||
{actions.map((actionType, index) => (
|
||||
<ActionButton
|
||||
key={`${actionType}-${chantier.id}-${index}`}
|
||||
type={actionType}
|
||||
size={size}
|
||||
showLabel={showLabels}
|
||||
onClick={() => onAction(actionType, chantier)}
|
||||
className="action-button-group-item"
|
||||
// Props pour le menu
|
||||
chantier={chantier}
|
||||
onMenuAction={actionType === 'MENU' ? onAction : undefined}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionButtonGroup;
|
||||
115
components/chantiers/ActionButtonStyles.ts
Normal file
115
components/chantiers/ActionButtonStyles.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Styles distinctifs pour chaque bouton d'action
|
||||
* Couleurs spécifiques et animations pour une UX premium
|
||||
*/
|
||||
|
||||
export const ACTION_BUTTON_THEMES = {
|
||||
VIEW: {
|
||||
name: 'Vue Rapide',
|
||||
icon: 'pi pi-eye',
|
||||
colors: {
|
||||
primary: '#3B82F6', // Bleu moderne
|
||||
light: '#DBEAFE', // Bleu très clair
|
||||
dark: '#1D4ED8', // Bleu foncé
|
||||
text: '#1E40AF' // Texte bleu
|
||||
},
|
||||
className: ''
|
||||
},
|
||||
PHASES: {
|
||||
name: 'Phases',
|
||||
icon: 'pi pi-sitemap',
|
||||
colors: {
|
||||
primary: '#10B981', // Vert émeraude
|
||||
light: '#D1FAE5', // Vert très clair
|
||||
dark: '#059669', // Vert foncé
|
||||
text: '#047857' // Texte vert
|
||||
},
|
||||
className: ''
|
||||
},
|
||||
PLANNING: {
|
||||
name: 'Planning',
|
||||
icon: 'pi pi-calendar',
|
||||
colors: {
|
||||
primary: '#8B5CF6', // Violet moderne
|
||||
light: '#EDE9FE', // Violet très clair
|
||||
dark: '#7C3AED', // Violet foncé
|
||||
text: '#6D28D9' // Texte violet
|
||||
},
|
||||
className: ''
|
||||
},
|
||||
STATS: {
|
||||
name: 'Statistiques',
|
||||
icon: 'pi pi-chart-bar',
|
||||
colors: {
|
||||
primary: '#06B6D4', // Cyan moderne
|
||||
light: '#CFFAFE', // Cyan très clair
|
||||
dark: '#0891B2', // Cyan foncé
|
||||
text: '#0E7490' // Texte cyan
|
||||
},
|
||||
className: ''
|
||||
},
|
||||
MENU: {
|
||||
name: 'Plus d\'actions',
|
||||
icon: 'pi pi-ellipsis-v',
|
||||
colors: {
|
||||
primary: '#6B7280', // Gris moderne
|
||||
light: '#F3F4F6', // Gris très clair
|
||||
dark: '#4B5563', // Gris foncé
|
||||
text: '#374151' // Texte gris
|
||||
},
|
||||
className: ''
|
||||
},
|
||||
EDIT: {
|
||||
name: 'Modifier',
|
||||
icon: 'pi pi-pencil',
|
||||
colors: {
|
||||
primary: '#F59E0B', // Orange/Ambre
|
||||
light: '#FEF3C7', // Orange très clair
|
||||
dark: '#D97706', // Orange foncé
|
||||
text: '#B45309' // Texte orange
|
||||
},
|
||||
className: ''
|
||||
},
|
||||
DELETE: {
|
||||
name: 'Supprimer',
|
||||
icon: 'pi pi-trash',
|
||||
colors: {
|
||||
primary: '#EF4444', // Rouge moderne
|
||||
light: '#FEE2E2', // Rouge très clair
|
||||
dark: '#DC2626', // Rouge foncé
|
||||
text: '#B91C1C' // Texte rouge
|
||||
},
|
||||
className: ''
|
||||
}
|
||||
} as const;
|
||||
|
||||
export type ActionButtonType = keyof typeof ACTION_BUTTON_THEMES;
|
||||
|
||||
// Tailles compactes pour DataTable optimisée
|
||||
export const BUTTON_SIZES = {
|
||||
xs: {
|
||||
padding: 'p-1',
|
||||
iconSize: 'text-xs',
|
||||
width: 'w-5 h-5' // Extra compact
|
||||
},
|
||||
sm: {
|
||||
padding: 'p-1',
|
||||
iconSize: 'text-sm',
|
||||
width: 'w-6 h-6' // Compact
|
||||
},
|
||||
md: {
|
||||
padding: 'p-2',
|
||||
iconSize: 'text-base',
|
||||
width: 'w-8 h-8'
|
||||
},
|
||||
lg: {
|
||||
padding: 'p-2',
|
||||
iconSize: 'text-lg',
|
||||
width: 'w-10 h-10'
|
||||
}
|
||||
} as const;
|
||||
|
||||
// Style Rounded Text uniquement - autres variantes supprimées
|
||||
export const BUTTON_VARIANTS = {
|
||||
roundedText: 'p-button-text p-button-rounded'
|
||||
} as const;
|
||||
215
components/chantiers/ChantierActions.tsx
Normal file
215
components/chantiers/ChantierActions.tsx
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Composant réutilisable pour les actions sur les chantiers
|
||||
*/
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { ACTION_BUTTONS, COMMON_CLASSES, ActionButtonType } from './ChantierStyles';
|
||||
import { ChantierActif } from '../../hooks/useDashboard';
|
||||
|
||||
interface ChantierActionsProps {
|
||||
chantier: ChantierActif;
|
||||
onQuickView?: (chantier: ChantierActif) => void;
|
||||
onManagePhases?: (chantier: ChantierActif) => void;
|
||||
onViewPlanning?: (chantier: ChantierActif) => void;
|
||||
onViewStats?: (chantier: ChantierActif) => void;
|
||||
onMenuAction?: (action: string, chantier: ChantierActif) => void;
|
||||
showLabels?: boolean;
|
||||
size?: 'small' | 'normal' | 'large';
|
||||
layout?: 'horizontal' | 'vertical';
|
||||
buttons?: ActionButtonType[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ChantierActions: React.FC<ChantierActionsProps> = ({
|
||||
chantier,
|
||||
onQuickView,
|
||||
onManagePhases,
|
||||
onViewPlanning,
|
||||
onViewStats,
|
||||
onMenuAction,
|
||||
showLabels = false,
|
||||
size = 'normal',
|
||||
layout = 'horizontal',
|
||||
buttons = ['VIEW', 'PHASES', 'PLANNING', 'STATS', 'MENU'],
|
||||
className = ''
|
||||
}) => {
|
||||
const menuRef = useRef<Menu>(null);
|
||||
|
||||
const sizeClasses = {
|
||||
small: 'p-button-sm',
|
||||
normal: '',
|
||||
large: 'p-button-lg'
|
||||
};
|
||||
|
||||
const layoutClasses = {
|
||||
horizontal: COMMON_CLASSES.BUTTON_GROUP,
|
||||
vertical: 'flex flex-column gap-2'
|
||||
};
|
||||
|
||||
// Configuration du menu contextuel
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
label: 'Navigation',
|
||||
items: [
|
||||
{
|
||||
label: 'Détails complets',
|
||||
icon: 'pi pi-external-link',
|
||||
command: () => onMenuAction?.('details', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Documents',
|
||||
icon: 'pi pi-file',
|
||||
command: () => onMenuAction?.('documents', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Photos',
|
||||
icon: 'pi pi-images',
|
||||
command: () => onMenuAction?.('photos', chantier)
|
||||
}
|
||||
]
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Ressources',
|
||||
items: [
|
||||
{
|
||||
label: 'Équipe assignée',
|
||||
icon: 'pi pi-users',
|
||||
command: () => onMenuAction?.('team', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Matériel utilisé',
|
||||
icon: 'pi pi-cog',
|
||||
command: () => onMenuAction?.('equipment', chantier)
|
||||
}
|
||||
]
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Rapports',
|
||||
items: [
|
||||
{
|
||||
label: 'Générer rapport',
|
||||
icon: 'pi pi-chart-bar',
|
||||
command: () => onMenuAction?.('report', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Export PDF',
|
||||
icon: 'pi pi-file-pdf',
|
||||
command: () => onMenuAction?.('export-pdf', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Export Excel',
|
||||
icon: 'pi pi-file-excel',
|
||||
command: () => onMenuAction?.('export-excel', chantier)
|
||||
}
|
||||
]
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
label: 'Actions',
|
||||
items: [
|
||||
{
|
||||
label: chantier.statut === 'SUSPENDU' ? 'Reprendre' : 'Suspendre',
|
||||
icon: chantier.statut === 'SUSPENDU' ? 'pi pi-play' : 'pi pi-pause',
|
||||
command: () => onMenuAction?.('toggle-suspend', chantier),
|
||||
className: 'text-orange-500'
|
||||
},
|
||||
{
|
||||
label: 'Clôturer',
|
||||
icon: 'pi pi-check-circle',
|
||||
command: () => onMenuAction?.('close', chantier),
|
||||
disabled: chantier.statut === 'TERMINE',
|
||||
className: 'text-green-500'
|
||||
},
|
||||
{
|
||||
label: 'Archiver',
|
||||
icon: 'pi pi-inbox',
|
||||
command: () => onMenuAction?.('archive', chantier),
|
||||
className: 'text-gray-500'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const handleMenuClick = (e: React.MouseEvent) => {
|
||||
menuRef.current?.toggle(e);
|
||||
};
|
||||
|
||||
const renderButton = (buttonType: ActionButtonType) => {
|
||||
const config = ACTION_BUTTONS[buttonType];
|
||||
const baseClassName = `${config.className} ${sizeClasses[size]}`;
|
||||
|
||||
const buttonProps = {
|
||||
icon: config.icon,
|
||||
tooltip: config.tooltip,
|
||||
tooltipOptions: { position: 'top' as const, showDelay: 500 },
|
||||
className: baseClassName,
|
||||
'aria-label': config.tooltip
|
||||
};
|
||||
|
||||
switch (buttonType) {
|
||||
case 'VIEW':
|
||||
return (
|
||||
<Button
|
||||
key="view"
|
||||
{...buttonProps}
|
||||
onClick={() => onQuickView?.(chantier)}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'PHASES':
|
||||
return (
|
||||
<Button
|
||||
key="phases"
|
||||
{...buttonProps}
|
||||
onClick={() => onManagePhases?.(chantier)}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'PLANNING':
|
||||
return (
|
||||
<Button
|
||||
key="planning"
|
||||
{...buttonProps}
|
||||
onClick={() => onViewPlanning?.(chantier)}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'STATS':
|
||||
return (
|
||||
<Button
|
||||
key="stats"
|
||||
{...buttonProps}
|
||||
onClick={() => onViewStats?.(chantier)}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'MENU':
|
||||
return (
|
||||
<Button
|
||||
key="menu"
|
||||
{...buttonProps}
|
||||
onClick={handleMenuClick}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`${layoutClasses[layout]} ${className}`}>
|
||||
{buttons.map(buttonType => renderButton(buttonType))}
|
||||
</div>
|
||||
<Menu ref={menuRef} model={menuItems} popup />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChantierActions;
|
||||
67
components/chantiers/ChantierActionsSimple.tsx
Normal file
67
components/chantiers/ChantierActionsSimple.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Version simplifiée du composant ChantierActions pour debug
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { ChantierActif } from '../../hooks/useDashboard';
|
||||
|
||||
interface ChantierActionsSimpleProps {
|
||||
chantier: ChantierActif;
|
||||
onQuickView?: (chantier: ChantierActif) => void;
|
||||
onManagePhases?: (chantier: ChantierActif) => void;
|
||||
onViewPlanning?: (chantier: ChantierActif) => void;
|
||||
onViewStats?: (chantier: ChantierActif) => void;
|
||||
onMenuAction?: (action: string, chantier: ChantierActif) => void;
|
||||
}
|
||||
|
||||
const ChantierActionsSimple: React.FC<ChantierActionsSimpleProps> = ({
|
||||
chantier,
|
||||
onQuickView,
|
||||
onManagePhases,
|
||||
onViewPlanning,
|
||||
onViewStats,
|
||||
onMenuAction
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
className="p-button-rounded p-button-text"
|
||||
tooltip="Vue rapide"
|
||||
onClick={() => onQuickView?.(chantier)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-sitemap"
|
||||
className="p-button-rounded p-button-text"
|
||||
tooltip="Gérer les phases"
|
||||
onClick={() => onManagePhases?.(chantier)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-calendar"
|
||||
className="p-button-rounded p-button-text"
|
||||
tooltip="Planning"
|
||||
onClick={() => onViewPlanning?.(chantier)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-chart-bar"
|
||||
className="p-button-rounded p-button-text"
|
||||
tooltip="Statistiques"
|
||||
onClick={() => onViewStats?.(chantier)}
|
||||
size="small"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-ellipsis-v"
|
||||
className="p-button-rounded p-button-text"
|
||||
tooltip="Plus d'actions"
|
||||
onClick={() => onMenuAction?.('details', chantier)}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChantierActionsSimple;
|
||||
100
components/chantiers/ChantierMenuActions.tsx
Normal file
100
components/chantiers/ChantierMenuActions.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Menu d'actions pour les chantiers - Actions prioritaires BTP
|
||||
*/
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { MenuItem } from 'primereact/menuitem';
|
||||
import { ChantierActif } from '../../hooks/useDashboard';
|
||||
|
||||
interface ChantierMenuActionsProps {
|
||||
chantier: ChantierActif;
|
||||
onAction: (action: string, chantier: ChantierActif) => void;
|
||||
}
|
||||
|
||||
const ChantierMenuActions: React.FC<ChantierMenuActionsProps> = ({
|
||||
chantier,
|
||||
onAction
|
||||
}) => {
|
||||
const menuRef = useRef<Menu>(null);
|
||||
|
||||
// Actions prioritaires pour le workflow BTP
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
label: 'Gestion chantier',
|
||||
items: [
|
||||
{
|
||||
label: 'Suspendre le chantier',
|
||||
icon: 'pi pi-pause',
|
||||
command: () => onAction('suspend', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Clôturer le chantier',
|
||||
icon: 'pi pi-check-circle',
|
||||
command: () => onAction('close', chantier)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
{
|
||||
label: 'Communication',
|
||||
items: [
|
||||
{
|
||||
label: 'Notification client',
|
||||
icon: 'pi pi-send',
|
||||
command: () => onAction('notify-client', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Rapport de synthèse',
|
||||
icon: 'pi pi-file-pdf',
|
||||
command: () => onAction('generate-report', chantier)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
{
|
||||
label: 'Financier',
|
||||
items: [
|
||||
{
|
||||
label: 'Facture intermédiaire',
|
||||
icon: 'pi pi-euro',
|
||||
command: () => onAction('generate-invoice', chantier)
|
||||
},
|
||||
{
|
||||
label: 'Créer un avenant',
|
||||
icon: 'pi pi-file-plus',
|
||||
command: () => onAction('create-amendment', chantier)
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const toggleMenu = (event: React.MouseEvent) => {
|
||||
menuRef.current?.toggle(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon="pi pi-ellipsis-v"
|
||||
className="p-button-text p-button-rounded p-button-sm"
|
||||
onClick={toggleMenu}
|
||||
aria-label="Plus d'actions"
|
||||
style={{ color: '#6B7280' }}
|
||||
/>
|
||||
<Menu
|
||||
ref={menuRef}
|
||||
model={menuItems}
|
||||
popup
|
||||
style={{ minWidth: '250px' }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChantierMenuActions;
|
||||
70
components/chantiers/ChantierProgressBar.tsx
Normal file
70
components/chantiers/ChantierProgressBar.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Composant réutilisable pour afficher l'avancement d'un chantier
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ProgressBar } from 'primereact/progressbar';
|
||||
import { AVANCEMENT_CONFIG, COMMON_CLASSES } from './ChantierStyles';
|
||||
|
||||
interface ChantierProgressBarProps {
|
||||
value: number;
|
||||
showValue?: boolean;
|
||||
showPercentage?: boolean;
|
||||
size?: 'small' | 'normal' | 'large';
|
||||
showCompletionIcon?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const ChantierProgressBar: React.FC<ChantierProgressBarProps> = ({
|
||||
value,
|
||||
showValue = false,
|
||||
showPercentage = true,
|
||||
size = 'normal',
|
||||
showCompletionIcon = true,
|
||||
className = '',
|
||||
style = {}
|
||||
}) => {
|
||||
// Déterminer la configuration couleur selon l'avancement
|
||||
const getProgressConfig = () => {
|
||||
if (value < AVANCEMENT_CONFIG.CRITIQUE.threshold) return AVANCEMENT_CONFIG.CRITIQUE;
|
||||
if (value < AVANCEMENT_CONFIG.ATTENTION.threshold) return AVANCEMENT_CONFIG.ATTENTION;
|
||||
if (value < AVANCEMENT_CONFIG.PROGRES.threshold) return AVANCEMENT_CONFIG.PROGRES;
|
||||
return AVANCEMENT_CONFIG.TERMINE;
|
||||
};
|
||||
|
||||
const config = getProgressConfig();
|
||||
|
||||
const sizeConfig = {
|
||||
small: { height: '8px', fontSize: 'text-xs' },
|
||||
normal: { height: '16px', fontSize: 'text-sm' },
|
||||
large: { height: '20px', fontSize: 'text-base' }
|
||||
};
|
||||
|
||||
const currentSize = sizeConfig[size];
|
||||
|
||||
return (
|
||||
<div className={`${COMMON_CLASSES.PROGRESS_CONTAINER} ${className}`} style={style}>
|
||||
<div className="w-full">
|
||||
<ProgressBar
|
||||
value={value}
|
||||
showValue={showValue}
|
||||
style={{ height: currentSize.height }}
|
||||
className={`progress-${size}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showPercentage && (
|
||||
<span className={`ml-2 font-bold ${currentSize.fontSize} ${config.textColor}`}>
|
||||
{value}%
|
||||
</span>
|
||||
)}
|
||||
|
||||
{showCompletionIcon && value === 100 && (
|
||||
<i className="pi pi-check-circle text-green-500 text-xl ml-2" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChantierProgressBar;
|
||||
41
components/chantiers/ChantierStatusBadge.tsx
Normal file
41
components/chantiers/ChantierStatusBadge.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Composant réutilisable pour afficher le statut d'un chantier
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { CHANTIER_STATUTS, ChantierStatut } from './ChantierStyles';
|
||||
|
||||
interface ChantierStatusBadgeProps {
|
||||
statut: string;
|
||||
showIcon?: boolean;
|
||||
size?: 'small' | 'normal' | 'large';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ChantierStatusBadge: React.FC<ChantierStatusBadgeProps> = ({
|
||||
statut,
|
||||
showIcon = true,
|
||||
size = 'normal',
|
||||
className = ''
|
||||
}) => {
|
||||
const statutKey = statut as ChantierStatut;
|
||||
const config = CHANTIER_STATUTS[statutKey] || CHANTIER_STATUTS.PLANIFIE;
|
||||
|
||||
const sizeClasses = {
|
||||
small: 'text-xs px-2 py-1',
|
||||
normal: '',
|
||||
large: 'text-lg px-3 py-2'
|
||||
};
|
||||
|
||||
return (
|
||||
<Tag
|
||||
value={config.label}
|
||||
severity={config.severity}
|
||||
icon={showIcon ? config.icon : undefined}
|
||||
className={`${sizeClasses[size]} ${className}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChantierStatusBadge;
|
||||
111
components/chantiers/ChantierStyles.ts
Normal file
111
components/chantiers/ChantierStyles.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Constantes de style pour les composants Chantier
|
||||
* Facilite la maintenance et la cohérence visuelle
|
||||
*/
|
||||
|
||||
// Configuration des statuts avec couleurs et icônes
|
||||
export const CHANTIER_STATUTS = {
|
||||
EN_COURS: {
|
||||
label: 'En cours',
|
||||
severity: 'success' as const,
|
||||
icon: 'pi pi-play-circle',
|
||||
color: 'text-green-500',
|
||||
bgColor: 'bg-green-100'
|
||||
},
|
||||
PLANIFIE: {
|
||||
label: 'Planifié',
|
||||
severity: 'info' as const,
|
||||
icon: 'pi pi-calendar',
|
||||
color: 'text-blue-500',
|
||||
bgColor: 'bg-blue-100'
|
||||
},
|
||||
EN_RETARD: {
|
||||
label: 'En retard',
|
||||
severity: 'danger' as const,
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
color: 'text-red-500',
|
||||
bgColor: 'bg-red-100'
|
||||
},
|
||||
TERMINE: {
|
||||
label: 'Terminé',
|
||||
severity: 'success' as const,
|
||||
icon: 'pi pi-check-circle',
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-100'
|
||||
},
|
||||
SUSPENDU: {
|
||||
label: 'Suspendu',
|
||||
severity: 'warning' as const,
|
||||
icon: 'pi pi-pause-circle',
|
||||
color: 'text-orange-500',
|
||||
bgColor: 'bg-orange-100'
|
||||
}
|
||||
} as const;
|
||||
|
||||
// Configuration des niveaux d'avancement
|
||||
export const AVANCEMENT_CONFIG = {
|
||||
CRITIQUE: { threshold: 30, color: 'bg-red-500', textColor: 'text-red-600' },
|
||||
ATTENTION: { threshold: 60, color: 'bg-orange-500', textColor: 'text-orange-600' },
|
||||
PROGRES: { threshold: 90, color: 'bg-blue-500', textColor: 'text-blue-600' },
|
||||
TERMINE: { threshold: 100, color: 'bg-green-500', textColor: 'text-green-600' }
|
||||
} as const;
|
||||
|
||||
// Configuration des boutons d'action
|
||||
export const ACTION_BUTTONS = {
|
||||
VIEW: {
|
||||
icon: 'pi pi-eye',
|
||||
tooltip: 'Vue rapide',
|
||||
className: 'p-button-rounded p-button-text p-button-plain',
|
||||
color: 'text-blue-500'
|
||||
},
|
||||
PHASES: {
|
||||
icon: 'pi pi-sitemap',
|
||||
tooltip: 'Gérer les phases',
|
||||
className: 'p-button-rounded p-button-text p-button-plain',
|
||||
color: 'text-green-500'
|
||||
},
|
||||
PLANNING: {
|
||||
icon: 'pi pi-calendar',
|
||||
tooltip: 'Planning',
|
||||
className: 'p-button-rounded p-button-text p-button-plain',
|
||||
color: 'text-purple-500'
|
||||
},
|
||||
STATS: {
|
||||
icon: 'pi pi-chart-bar',
|
||||
tooltip: 'Statistiques',
|
||||
className: 'p-button-rounded p-button-text p-button-plain',
|
||||
color: 'text-cyan-500'
|
||||
},
|
||||
MENU: {
|
||||
icon: 'pi pi-ellipsis-v',
|
||||
tooltip: 'Plus d\'actions',
|
||||
className: 'p-button-rounded p-button-text p-button-plain',
|
||||
color: 'text-gray-600'
|
||||
}
|
||||
} as const;
|
||||
|
||||
// Configuration des indicateurs d'urgence
|
||||
export const URGENCE_INDICATORS = {
|
||||
RETARD: {
|
||||
icon: 'pi pi-exclamation-circle',
|
||||
color: 'text-red-500',
|
||||
tooltip: 'En retard'
|
||||
},
|
||||
BIENTOT_TERMINE: {
|
||||
icon: 'pi pi-flag-fill',
|
||||
color: 'text-green-500',
|
||||
tooltip: 'Bientôt terminé'
|
||||
}
|
||||
} as const;
|
||||
|
||||
// Classes CSS communes
|
||||
export const COMMON_CLASSES = {
|
||||
BUTTON_GROUP: 'flex gap-2',
|
||||
CARD_ICON: 'flex align-items-center justify-content-center border-round',
|
||||
PROGRESS_CONTAINER: 'flex align-items-center',
|
||||
STATUS_TAG: 'inline-flex align-items-center gap-2'
|
||||
} as const;
|
||||
|
||||
// Types pour TypeScript
|
||||
export type ChantierStatut = keyof typeof CHANTIER_STATUTS;
|
||||
export type ActionButtonType = keyof typeof ACTION_BUTTONS;
|
||||
52
components/chantiers/ChantierUrgencyIndicator.tsx
Normal file
52
components/chantiers/ChantierUrgencyIndicator.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Composant pour les indicateurs d'urgence des chantiers
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { URGENCE_INDICATORS } from './ChantierStyles';
|
||||
import { ChantierActif } from '../../hooks/useDashboard';
|
||||
|
||||
interface ChantierUrgencyIndicatorProps {
|
||||
chantier: ChantierActif;
|
||||
size?: 'small' | 'normal' | 'large';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ChantierUrgencyIndicator: React.FC<ChantierUrgencyIndicatorProps> = ({
|
||||
chantier,
|
||||
size = 'normal',
|
||||
className = ''
|
||||
}) => {
|
||||
const sizeClasses = {
|
||||
small: 'text-sm',
|
||||
normal: 'text-base',
|
||||
large: 'text-xl'
|
||||
};
|
||||
|
||||
const getUrgencyType = () => {
|
||||
if (chantier.statut === 'EN_RETARD') {
|
||||
return 'RETARD';
|
||||
}
|
||||
if (chantier.avancement >= 90) {
|
||||
return 'BIENTOT_TERMINE';
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const urgencyType = getUrgencyType();
|
||||
|
||||
if (!urgencyType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indicator = URGENCE_INDICATORS[urgencyType];
|
||||
|
||||
return (
|
||||
<i
|
||||
className={`${indicator.icon} ${indicator.color} ${sizeClasses[size]} ${className}`}
|
||||
title={indicator.tooltip}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChantierUrgencyIndicator;
|
||||
13
components/chantiers/index.ts
Normal file
13
components/chantiers/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Export des composants Chantier réutilisables
|
||||
*/
|
||||
|
||||
export { default as ChantierActions } from './ChantierActions';
|
||||
export { default as ChantierStatusBadge } from './ChantierStatusBadge';
|
||||
export { default as ChantierProgressBar } from './ChantierProgressBar';
|
||||
export { default as ChantierUrgencyIndicator } from './ChantierUrgencyIndicator';
|
||||
|
||||
export * from './ChantierStyles';
|
||||
|
||||
// Types communs
|
||||
export type { ChantierStatut, ActionButtonType } from './ChantierStyles';
|
||||
Reference in New Issue
Block a user