/**
* =========================================================================
* PLATEFORME ECO-PANNEAU.FR - VERSION 1.0.0
* Socle UI - Composants partagés, Icônes et Utilitaires
* =========================================================================
*/
const { useState, useEffect, useRef } = React;
// =========================================================================
// 1. SYSTÈME DE NOTIFICATIONS GLOBAL (TOASTS)
// =========================================================================
window.showToast = (message, type = 'success', duration = 4000) => {
let container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
container.className = 'fixed top-4 right-4 z-[9999] flex flex-col gap-2 pointer-events-none';
document.body.appendChild(container);
}
const toast = document.createElement('div');
const bgColor = type === 'success' ? 'bg-emerald-50 border-emerald-200 text-emerald-800' :
(type === 'error' ? 'bg-red-50 border-red-200 text-red-800' : 'bg-blue-50 border-blue-200 text-blue-800');
toast.className = `flex items-center gap-3 px-4 py-3 rounded-xl border shadow-lg ${bgColor} transform transition-all duration-300 translate-x-full opacity-0 pointer-events-auto`;
toast.innerHTML = `
${message}
`;
container.appendChild(toast);
// Animation d'entrée
requestAnimationFrame(() => {
toast.classList.remove('translate-x-full', 'opacity-0');
toast.classList.add('translate-x-0', 'opacity-100');
});
// Suppression automatique
setTimeout(() => {
toast.classList.remove('translate-x-0', 'opacity-100');
toast.classList.add('translate-x-full', 'opacity-0');
setTimeout(() => toast.remove(), 300);
}, duration);
};
// =========================================================================
// 2. UTILITAIRES ET UPLOADS (COMPRESSION IMAGE & APERÇU PDF CLIENT)
// =========================================================================
window.uploadFile = async (file, type, onProgress) => {
let fileToSend = file;
let previewBlob = null;
let hasLocalProcessing = false;
try {
// A. TRAITEMENT CLIENT DES IMAGES (Compression WEBP + Redimensionnement)
if (type === 'image' && file.type.startsWith('image/')) {
if (onProgress) onProgress("Optimisation de l'image en cours...", 10);
const img = new Image();
const imgUrl = URL.createObjectURL(file);
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = imgUrl;
});
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
let width = img.width;
let height = img.height;
const MAX_SIZE = 1920;
if (width > MAX_SIZE || height > MAX_SIZE) {
if (width > height) {
height = Math.round((height * MAX_SIZE) / width);
width = MAX_SIZE;
} else {
width = Math.round((width * MAX_SIZE) / height);
height = MAX_SIZE;
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
URL.revokeObjectURL(imgUrl);
// Conversion en WebP (qualité 85%)
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/webp', 0.85));
fileToSend = new File([blob], file.name.replace(/\.[^/.]+$/, "") + ".webp", { type: 'image/webp' });
hasLocalProcessing = true;
if (onProgress) onProgress("Image optimisée, préparation de l'envoi...", 30);
}
// B. TRAITEMENT CLIENT DES PDF (Génération de la miniature)
else if (type === 'pdf' && file.type === 'application/pdf' && window.pdfjsLib) {
if (onProgress) onProgress("Génération de l'aperçu PDF en cours...", 10);
const arrayBuffer = await file.arrayBuffer();
const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
const page = await pdf.getPage(1);
// Échelle 1.5 offre un bon équilibre entre lisibilité de l'aperçu et poids final
const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({ canvasContext: ctx, viewport: viewport }).promise;
// Conversion de l'aperçu en WebP (qualité 80%)
previewBlob = await new Promise(resolve => canvas.toBlob(resolve, 'image/webp', 0.8));
hasLocalProcessing = true;
if (onProgress) onProgress("Aperçu généré, préparation de l'envoi...", 30);
}
} catch (e) {
console.error("Erreur lors du traitement local du fichier :", e);
window.showToast("L'optimisation locale a échoué. Le fichier sera envoyé tel quel.", "error");
hasLocalProcessing = false;
}
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', fileToSend);
formData.append('type', type);
if (previewBlob) {
formData.append('preview', previewBlob, 'preview.webp');
}
const xhr = new XMLHttpRequest();
xhr.open('POST', window.ECO_CONFIG.apiBaseUrl + 'file/upload', true);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const base = hasLocalProcessing ? 30 : 0;
const multiplier = hasLocalProcessing ? 70 : 100;
const percent = Math.round(base + ((e.loaded / e.total) * multiplier));
if (onProgress) {
onProgress(`Envoi au serveur : ${Math.round((e.loaded / e.total) * 100)}%`, percent);
}
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const res = JSON.parse(xhr.responseText);
if (res.status === 'success') {
if (onProgress) onProgress("Traitement terminé !", 100);
resolve(res.data.id);
} else {
reject(res.message || 'Erreur upload serveur');
}
} catch (err) {
reject('Réponse invalide du serveur');
}
} else {
reject('Erreur HTTP ' + xhr.status);
}
};
xhr.onerror = () => reject("Erreur réseau (déconnexion ou blocage)");
xhr.send(formData);
});
};
// =========================================================================
// 3. BIBLIOTHÈQUE D'ICÔNES VECTORIELLES (SVG) - EXHAUSTIVE POUR ÉVITER LES CRASHS
// =========================================================================
const createIcon = (svgPaths) => ({ size = 24, className = '', ...props }) => (
);
window.Home = createIcon(<>>);
window.Users = createIcon(<>>);
window.User = createIcon(<>>);
window.Building = createIcon(<>>);
window.Settings = createIcon(<>>);
window.MessageSquare = createIcon(<>>);
window.Package = createIcon(<>>);
window.FileText = createIcon(<>>);
window.Activity = createIcon(<>>);
window.Eye = createIcon(<>>);
window.Terminal = createIcon(<>>);
window.Archive = createIcon(<>>);
window.Save = createIcon(<>>);
window.Download = createIcon(<>>);
window.RefreshCw = createIcon(<>>);
window.Trash = createIcon(<>>);
window.Trash2 = createIcon(<>>);
window.Loader = createIcon(<>>);
window.ShieldAlert = createIcon(<>>);
window.Shield = createIcon(<>>);
window.CreditCard = createIcon(<>>);
window.AlertTriangle = createIcon(<>>);
window.Zap = createIcon(<>>);
window.KeyRound = createIcon(<>>);
window.CheckCircle = createIcon(<>>);
window.Check = createIcon(<>>);
window.Mail = createIcon(<>>);
window.AlertOctagon = createIcon(<>>);
window.Power = createIcon(<>>);
window.MapPin = createIcon(<>>);
window.Edit = createIcon(<>>);
window.Plus = createIcon(<>>);
window.ArrowLeft = createIcon(<>>);
window.X = createIcon(<>>);
window.UploadCloud = createIcon(<>>);
window.FileIcon = createIcon(<>>);
window.ImageIcon = createIcon(<>>);
window.Camera = createIcon(<>>);
window.Menu = createIcon(<>>);
window.LogOut = createIcon(<>>);
window.Calendar = createIcon(<>>);
window.Clock = createIcon(<>>);
window.Phone = createIcon(<>>);
window.Link = createIcon(<>>);
window.AlignLeft = createIcon(<>>);
window.Palette = createIcon(<>>);
window.Info = createIcon(<>>);
window.ChevronRight = createIcon(<>>);
window.ChevronDown = createIcon(<>>);
window.Search = createIcon(<>>);
window.Bell = createIcon(<>>);
window.Lock = createIcon(<>>);
window.Send = createIcon(<>>);
// =========================================================================
// 4. COMPOSANTS DE PRÉSENTATION
// =========================================================================
window.StatCard = ({ title, value, icon, color, bg, onClick }) => (
);
window.PriceInput = ({ label, value, onChange }) => (
);
// =========================================================================
// 5. COMPOSANTS MODALES
// =========================================================================
window.Modal = ({ title, onClose, children, preventClose = false, zIndex = "z-[100]" }) => {
useEffect(() => {
const handleEsc = (e) => { if (e.key === 'Escape' && !preventClose) onClose(); };
window.addEventListener('keydown', handleEsc);
return () => window.removeEventListener('keydown', handleEsc);
}, [onClose, preventClose]);
return (
{ if (e.target === e.currentTarget && !preventClose) onClose(); }}>
e.stopPropagation()}>
{title}
{!preventClose && (
)}
{children}
);
};
window.ConfirmModal = ({ title, message, confirmText = "Confirmer", cancelText = "Annuler", isDestructive = false, onConfirm, onCancel, zIndex = "z-[200]" }) => {
const [isProcessing, setIsProcessing] = useState(false);
return (
{message}
);
};
window.PromptModal = ({ title, message, placeholder = "", confirmText = "Valider", cancelText = "Annuler", onConfirm, onCancel, zIndex = "z-[200]" }) => {
const [val, setVal] = useState('');
const [isProcessing, setIsProcessing] = useState(false);
return (
);
};
// =========================================================================
// 6. LAYOUT PRINCIPAL (SHELL)
// =========================================================================
window.DashboardLayout = ({ role, user, theme = 'emerald', navItems, activeTab, setActiveTab, onLogout, children }) => {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const themeConfig = {
emerald: { bg: 'bg-emerald-900', hover: 'hover:bg-emerald-800', active: 'bg-emerald-800 text-white border-emerald-400', text: 'text-emerald-100', border: 'border-emerald-800' },
purple: { bg: 'bg-slate-900', hover: 'hover:bg-slate-800', active: 'bg-purple-600 text-white border-purple-400', text: 'text-slate-300', border: 'border-slate-800' },
blue: { bg: 'bg-blue-900', hover: 'hover:bg-blue-800', active: 'bg-blue-800 text-white border-blue-400', text: 'text-blue-100', border: 'border-blue-800' }
}[theme];
return (
{/* Sidebar Desktop */}
{/* Header & Menu Mobile */}
{/* Overlay Menu Mobile */}
{isMobileMenuOpen && (
)}
{/* Contenu Principal */}
{children}
);
};
/* EOF ========== [_www/_react/_socle_ui.jsx] */