// ECO-PANNEAU.FR - _react/ui/_ui_components.jsx // 1. - DESIGN SYSTEM : COMPOSANTS UI ATOMIQUES // 1.1 - Bouton unifié (Gestion des variantes, icônes et formes) window.pano_Button = ({ children, onClick, disabled, variant = 'primary', icon: Icon, className = '', type = 'button', title = '', shape, ...props }) => { const hasPx = /(^|\s)(px-|p-)/.test(className); const hasPy = /(^|\s)(py-|p-)/.test(className); const hasH = /(^|\s)(h-|min-h-|max-h-)/.test(className); const hasW = /(^|\s)(w-|min-w-|max-w-)/.test(className); let baseClass = "text-xs font-bold transition flex items-center justify-center gap-2 shadow-sm border border-transparent select-none"; if (shape === 'square') { baseClass += " rounded-xl shrink-0 aspect-square"; if (!hasPx && !hasPy && !hasW && !hasH) baseClass += " p-2.5"; } else if (shape === 'round') { baseClass += " rounded-full shrink-0 aspect-square"; if (!hasPx && !hasPy && !hasW && !hasH) baseClass += " p-2.5"; } else { baseClass += " rounded-xl"; if (!hasPx) baseClass += " px-4"; if (!hasPy && !hasH) baseClass += " py-2.5"; } const variants = { primary: "bg-emerald-600 text-white hover:bg-emerald-700 shadow-md", success: "bg-emerald-50 text-emerald-600 hover:bg-emerald-100", successSolid: "bg-emerald-600 text-white hover:bg-emerald-700 shadow-md", danger: "bg-red-50 text-red-600 hover:bg-red-100", dangerSolid: "bg-red-600 text-white hover:bg-red-700 shadow-md", warning: "bg-orange-50 text-orange-600 hover:bg-orange-100", warningSolid: "bg-orange-500 text-white hover:bg-orange-600 shadow-md", info: "bg-blue-50 text-blue-700 hover:bg-blue-100", infoSolid: "bg-blue-600 text-white hover:bg-blue-700 shadow-md", secondary: "bg-slate-100 text-slate-700 hover:bg-slate-200 border-slate-200", outline: "bg-white border-slate-200 text-slate-600 hover:bg-slate-50", purple: "bg-purple-50 text-purple-700 hover:bg-purple-100", dark: "bg-slate-900 text-white hover:bg-slate-800 shadow-md", ghost: "bg-transparent text-slate-500 hover:text-slate-800 hover:bg-slate-100 shadow-none border-transparent" }; return ( ); }; // 1.2 - Badge d'icône (Décoratif) window.pano_IconBadge = ({ icon: Icon, variant = 'info', size = 'md', className = '' }) => { const variants = { primary: "bg-emerald-100 text-emerald-600", success: "bg-emerald-100 text-emerald-600", danger: "bg-red-100 text-red-600", warning: "bg-orange-100 text-orange-600", info: "bg-blue-100 text-blue-600", secondary: "bg-slate-100 text-slate-500", purple: "bg-purple-100 text-purple-600", dark: "bg-slate-800 text-slate-300" }; const sizes = { sm: "w-8 h-8 rounded-lg", md: "w-10 h-10 rounded-xl", lg: "w-12 h-12 rounded-2xl", xl: "w-14 h-14 rounded-2xl" }; const iconSizes = { sm: 14, md: 18, lg: 24, xl: 28 }; return (
{Icon && }
); }; // 1.3 - Badge de notification (Compteur) window.pano_NotificationBadge = ({ count, className = '' }) => { if (!count || count <= 0) return null; const displayCount = count > 99 ? '99+' : count; return ( {displayCount} ); }; // 1.4 - Interrupteur (Toggle) window.pano_Toggle = ({ checked, onChange, variant = 'success', disabled = false }) => { const variants = { success: 'bg-emerald-500', info: 'bg-blue-500', warning: 'bg-orange-500', danger: 'bg-red-500', purple: 'bg-purple-500' }; const colorClass = variants[variant] || variants.success; return (
{ if(onChange && !disabled) { e.stopPropagation(); onChange(!checked); } }} >
); }; // 1.5 - État vide (Empty State) window.pano_EmptyState = ({ icon: Icon, text, subtext, className = "" }) => (
{Icon && }

{text}

{subtext &&

{subtext}

}
); // 1.6 - Boîte d'alerte (Informations critiques) window.pano_AlertBox = ({ type = 'info', icon: Icon, title, children, className = "" }) => { const styles = { info: 'bg-blue-50 text-blue-800 border-blue-200', warning: 'bg-orange-50 text-orange-800 border-orange-200', error: 'bg-red-50 text-red-800 border-red-200', success: 'bg-emerald-50 text-emerald-800 border-emerald-200' }; const iconColors = { info: 'text-blue-500', warning: 'text-orange-500', error: 'text-red-500', success: 'text-emerald-500' }; return (
{Icon && }
{title &&

{title}

} {children}
); }; // 1.7 - Carte de statistique window.pano_StatCard = ({ title, value, icon, variant = 'info', onClick }) => { const variants = { success: { bg: 'bg-emerald-100', text: 'text-emerald-600' }, info: { bg: 'bg-blue-100', text: 'text-blue-600' }, warning: { bg: 'bg-amber-100', text: 'text-amber-600' }, danger: { bg: 'bg-red-100', text: 'text-red-600' }, secondary: { bg: 'bg-slate-200', text: 'text-slate-600' }, purple: { bg: 'bg-purple-100', text: 'text-purple-600' }, indigo: { bg: 'bg-indigo-100', text: 'text-indigo-600' } }; const v = variants[variant] || variants.info; return (
{icon}

{title}

{value}

); }; // 1.8 - Saisie de prix (HT) // CORRECTION UX : Implémentation du pattern "Controlled Input" avec état local pour ne pas bloquer les virgules (décimales) window.pano_PriceInput = ({ label, value, onChange, ...props }) => { const { useState, useEffect } = React; // On conserve la saisie exacte de l'utilisateur sous forme de chaîne (permet de taper "10.") const [localValue, setLocalValue] = useState(value === 0 ? '' : String(value)); // Si la valeur externe change "de force" (ex: reset du formulaire), on met à jour useEffect(() => { const parsedLocal = parseFloat(localValue); if (parsedLocal !== value && !(localValue === '' && value === 0)) { setLocalValue(value === 0 ? '' : String(value)); } }, [value]); const handleChange = (e) => { const val = e.target.value; setLocalValue(val); const parsed = parseFloat(val); onChange(isNaN(parsed) ? 0 : parsed); }; return (
); }; // 1.9 - Badge de statut (Coloration intelligente) window.pano_StatusBadge = ({ status, variant, className = '' }) => { let colorClass = 'bg-slate-100 text-slate-600 border-slate-200'; let displayStatus = status; if (status === 'En attente de validation') displayStatus = 'Validation'; else if (status === 'En attente de commande au fournisseur') displayStatus = 'Commande'; else if (status === "En attente d'impression") displayStatus = 'Impression'; else if (status === 'Réception confirmée') displayStatus = 'Livré'; if (variant) { const variants = { success: 'bg-emerald-100 text-emerald-700 border-emerald-200', danger: 'bg-red-100 text-red-700 border-red-200', warning: 'bg-amber-100 text-amber-700 border-amber-200', info: 'bg-blue-100 text-blue-700 border-blue-200', secondary: 'bg-slate-100 text-slate-600 border-slate-200' }; colorClass = variants[variant] || colorClass; } else { const s = (status || '').toLowerCase(); if (s.includes('actif') || s.includes('livré') || s.includes('activé') || s.includes('accept') || s === 'réception confirmée') { colorClass = 'bg-emerald-100 text-emerald-700 border-emerald-200'; } else if (s.includes('suspendu') || s.includes('erreur') || s.includes('hors ligne') || s.includes('refus') || s.includes('désactivé')) { colorClass = 'bg-red-100 text-red-700 border-red-200'; } else if (s.includes('brouillon') || s.includes('attente') || s.includes('validation') || s.includes('programmé') || s.includes('commande') || s.includes('impression')) { colorClass = 'bg-amber-100 text-amber-700 border-amber-200'; } else if (s.includes('expédié')) { colorClass = 'bg-blue-100 text-blue-700 border-blue-200'; } } return ( {displayStatus} ); }; // 1.10 - Champ de saisie (FormInput) window.pano_FormInput = ({ label, value, onChange, type = 'text', required = false, disabled = false, placeholder = '', className = '', inputClassName = '', hint = '', error = false, errorText = '', icon, ...props }) => { const [showPwd, setShowPwd] = React.useState(false); const { EyeIcon, EyeOffIcon } = window.pano_getIcons(); const isPassword = type === 'password'; const actualType = isPassword ? (showPwd ? 'text' : 'password') : type; return (
{label && ( )}
{icon && (
{icon}
)} {isPassword && (
{ e.preventDefault(); e.stopPropagation(); setShowPwd(!showPwd); }} title={showPwd ? "Masquer" : "Afficher"} > {showPwd ? (EyeOffIcon && ) : (EyeIcon && )}
)}
{hint && !errorText &&

{hint}

} {errorText &&

{errorText}

}
); }; // 1.11 - Zone de texte (FormTextarea) // CORRECTION CSS : Séparation propre de className (conteneur) et inputClassName (textarea) window.pano_FormTextarea = ({ label, value, onChange, rows = 3, required = false, disabled = false, placeholder = '', className = '', inputClassName = '', hint = '', error = false, errorText = '', ...props }) => (