import { useState, useEffect, useRef } from 'react'; import { useCollection } from '@deplixo/sdk'; import { Icon } from './components/icons.jsx'; import Dashboard from './components/Dashboard.jsx'; import Clients from './components/Clients.jsx'; import Products from './components/Products.jsx'; import Menus from './components/Menus.jsx'; import Recipes from './components/Recipes.jsx'; import Orders from './components/Orders.jsx'; import { Ingredients, Stock, Payments, Reminders, Planning, Production, Shopping, Settings, Exports } from './components/Simple.jsx'; import { Login, RoleBadge, ReadOnlyBanner, ROLES, loadSession, clearSession, guardCollection } from './components/Auth.jsx'; const SEED_PRODUCTS = [ { name: 'Bao Poulet', category: 'Bao', price: null, unit: 'pièce', active: true, description: '', notes: '' }, { name: 'Bao Karaage', category: 'Bao', price: null, unit: 'pièce', active: true, description: '', notes: '' }, { name: 'Bao Kimchi', category: 'Bao', price: null, unit: 'pièce', active: true, description: '', notes: '' }, { name: 'Bao Dynamite', category: 'Bao', price: null, unit: 'pièce', active: true, description: '', notes: '' }, { name: 'Baguette Poulet Laqué', category: 'Baguette', price: null, unit: 'pièce', active: true, description: '', notes: '' }, { name: 'Babka Pesto', category: 'Babka', price: null, unit: 'pièce', active: true, description: '', notes: '' }, ]; const SEED_RECIPES = [ { name: 'Pâte Bao', category: 'Pâte', yield: 44, yieldUnit: 'pièces', prepTime: '', cookTime: '7-8 min (vapeur)', temperature: '', items: [ { name: 'Farine', qty: '465', unit: 'g' }, { name: 'Sucre', qty: '50', unit: 'g' }, { name: 'Sel', qty: '9', unit: 'g (environ)' }, { name: 'Levure boulangère sèche', qty: '10', unit: 'g' }, { name: 'Levure chimique', qty: '15', unit: 'g' }, { name: 'Eau tiède', qty: '110', unit: 'g' }, { name: 'Lait tiède', qty: '110', unit: 'g' }, { name: 'Crème fraîche', qty: '70', unit: 'g' }, { name: "Blancs d'œufs (dorure)", qty: '2', unit: 'blancs' }, ], steps: ['Portions de 18–20 g.','Cuisson vapeur 7–8 minutes.','Friture optionnelle à 180°C.','Finition Karaage : sésame blanc/doré.','Finition Spicy : piment d\'Espelette.','Finition Poulet : sésame noir.','Finition Dynamite : aucune.'], notes: 'Rendement 40–48 pièces selon portionnage.', }, { name: 'Poulet Karaage', category: 'Poulet', yield: 20, yieldUnit: 'pièces', prepTime: '', cookTime: '', temperature: '180°C (friture) · 200°C (aubergine)', items: [ { name: 'Cuisses de poulet désossées', qty: '2', unit: 'cuisses' }, { name: 'Sauce soja', qty: 'QS', unit: '' }, { name: 'Ail en poudre', qty: 'QS', unit: '' }, { name: 'Gingembre', qty: 'QS', unit: '' }, { name: 'Fécule de pomme de terre', qty: 'QS', unit: '' }, { name: 'Marinade', qty: 'QS', unit: '' }, { name: "Huile d'olive", qty: 'QS', unit: 'filet' }, { name: 'Mayonnaise (teriyaki-mayo)', qty: '160', unit: 'g' }, { name: 'Teriyaki', qty: '50', unit: 'g' }, { name: "Tranches d'aubergine", qty: '20', unit: 'tranches' }, { name: 'Miso', qty: '20', unit: 'g' }, { name: 'Huile de sésame', qty: '20', unit: 'g' }, { name: 'Miel', qty: '20', unit: 'g' }, { name: 'Bao au sésame', qty: '20', unit: 'pièces' }, { name: 'Roquette', qty: 'QS', unit: '' }, { name: 'Oignons frits', qty: 'QS', unit: '' }, ], steps: ['Friture 180°C.','Aubergine miso 200°C, 12–18 min.','Montage : 20 bao au sésame + 20 karaage + 20 tranches d\'aubergine + roquette + teriyaki-mayo + oignons frits.'], notes: '', }, { name: 'Poulet Panko', category: 'Poulet', yield: 20, yieldUnit: 'pièces', prepTime: '', cookTime: '', temperature: '180°C (friture)', items: [ { name: 'Filets de poulet', qty: 'QS', unit: '' }, { name: 'Lait fermenté', qty: 'QS', unit: '' }, { name: 'Épices cajun', qty: 'QS', unit: '' }, { name: 'Épices poulet rôti', qty: 'QS', unit: '' }, { name: 'Panko', qty: 'QS', unit: '' }, { name: 'Bao au sésame noir', qty: '20', unit: 'pièces' }, { name: 'Tranches de concombre', qty: '20', unit: 'tranches' }, { name: 'Carottes râpées', qty: 'QS', unit: '' }, { name: 'Batavia/frisée', qty: 'QS', unit: '' }, { name: 'Pickles', qty: 'QS', unit: '' }, { name: 'Sauce Brasil', qty: 'QS', unit: '' }, ], steps: ['Friture 180°C.','Montage : 20 bao sésame noir + poulet panko + concombre + carottes + salade + pickles + sauce Brasil.'], notes: '', }, { name: 'Bao Kimchi', category: 'Bao', yield: 20, yieldUnit: 'pièces', prepTime: '', cookTime: '', temperature: '', items: [ { name: 'Filets de poulet', qty: '2', unit: 'filets' }, { name: 'Lait fermenté', qty: 'QS', unit: '' }, { name: 'Sauce chili', qty: 'QS', unit: '' }, { name: 'Épices poulet rôti', qty: 'QS', unit: '' }, { name: 'Farine', qty: 'QS', unit: '' }, { name: 'Maïzena', qty: 'QS', unit: '' }, { name: 'Beurre (sauce gochujang)', qty: '20', unit: 'g' }, { name: 'Huile de sésame (sauce)', qty: '20', unit: 'g' }, { name: 'Pâte gochujang', qty: '40', unit: 'g' }, { name: 'Miel (sauce)', qty: '20', unit: 'g' }, { name: 'Eau/vinaigre de riz', qty: '20', unit: 'g' }, { name: 'Mayonnaise (mayo kimchi)', qty: '100', unit: 'g' }, { name: 'Kimchi', qty: '50', unit: 'g' }, { name: 'Chou blanc (coleslaw)', qty: '200', unit: 'g' }, { name: 'Mayonnaise (coleslaw)', qty: '3', unit: 'c.à.s' }, { name: 'Sauce soja salée', qty: '1', unit: 'c.à.s' }, { name: 'Huile de sésame (coleslaw)', qty: '1', unit: 'c.à.s' }, { name: 'Vinaigre de riz (coleslaw)', qty: '1', unit: 'c.à.s' }, { name: 'Graines de sésame', qty: 'QS', unit: '' }, { name: 'Bao spicy', qty: '20', unit: 'pièces' }, { name: 'Tranches de cheddar', qty: '5', unit: 'tranches' }, { name: 'Batavia/frisée', qty: 'QS', unit: '' }, ], steps: ['Poulet gochujang + coleslaw + mayo kimchi.','Montage : 20 bao spicy + poulet gochujang + cheddar + coleslaw + salade + mayo kimchi.'], notes: '', }, { name: 'Baguette Poulet Laqué', category: 'Poulet', yield: 20, yieldUnit: 'pièces', prepTime: 'Marinade : une nuit', cookTime: '16-20 min', temperature: '200°C chaleur tournante', items: [ { name: 'Cuisses de poulet désossées', qty: '2', unit: 'cuisses' }, { name: 'Sauce soja', qty: '1', unit: 'c.à.s' }, { name: 'Worcestershire', qty: '1', unit: 'c.à.s' }, { name: 'Vinaigre de riz', qty: '1', unit: 'c.à.s' }, { name: 'Huile de sésame', qty: '1', unit: 'c.à.s' }, { name: 'Sauce huître', qty: '1', unit: 'c.à.s' }, { name: 'Sriracha', qty: '1', unit: 'c.à.s' }, { name: 'Asian BBQ/Hoisin', qty: '1', unit: 'c.à.s' }, { name: 'Ketchup', qty: '2', unit: 'c.à.s' }, { name: 'Ail en granulés', qty: 'QS', unit: '' }, { name: 'Gingembre en poudre', qty: 'QS', unit: '' }, { name: 'Paprika fumé', qty: 'QS', unit: '' }, { name: 'Sel', qty: 'QS', unit: '' }, { name: 'Pains baguette', qty: '20', unit: 'pièces' }, { name: 'Tranches de scamorza', qty: '20', unit: 'tranches' }, { name: 'Mini rösti', qty: '20', unit: 'pièces' }, { name: 'Salade', qty: 'QS', unit: '' }, { name: 'Sauce crème', qty: 'QS', unit: '' }, ], steps: ['Marinade une nuit.','Cuisson 200°C chaleur tournante, 16–20 min.','Montage : baguette + scamorza + rösti + salade + sauce crème.'], notes: '', }, { name: 'Babka Pesto', category: 'Bao', yield: 20, yieldUnit: 'pièces', prepTime: '', cookTime: '30-40 s (friture) · 1 h (tomates)', temperature: '180°C · 160°C chaleur tournante', items: [ { name: 'Mini boules de mozzarella', qty: '10', unit: 'boules' }, { name: 'Farine', qty: 'QS', unit: '' }, { name: 'Chapelure dorée', qty: 'QS', unit: '' }, { name: 'Œuf', qty: 'QS', unit: '' }, { name: 'Eau gazeuse', qty: '300', unit: 'ml' }, { name: 'Tomates cerises', qty: '40', unit: 'pièces' }, { name: "Huile d'olive", qty: 'QS', unit: '' }, { name: 'Sel/poivre/herbes de Provence', qty: 'QS', unit: '' }, { name: 'Pains babka', qty: '20', unit: 'pièces' }, { name: 'Demi-tomates confites', qty: '60', unit: 'demi' }, { name: 'Mini mozzarella', qty: '20', unit: 'pièces' }, { name: 'Roquette', qty: 'QS', unit: '' }, { name: 'Crème balsamique', qty: 'QS', unit: '' }, { name: 'Pesto Genovese', qty: 'QS', unit: '' }, { name: 'Philadelphia', qty: 'QS', unit: '' }, ], steps: ['Mozzarella panée : friture 180°C 30–40 s.','Tomates confites : 160°C chaleur tournante 1 h.','Montage : 20 pains babka + 60 demi-tomates + 20 mozzarella + roquette + crème balsamique + pesto + Philadelphia.'], notes: '', }, ]; function App() { const [session, setSession] = useState(() => loadSession()); const [view, setView] = useState('home'); const [showMore, setShowMore] = useState(false); const [orderOpen, setOrderOpen] = useState(null); const clientsRaw = useCollection('clients'); const productsRaw = useCollection('products'); const menusRaw = useCollection('menus'); const recipesRaw = useCollection('recipes'); const ingredientsRaw = useCollection('ingredients'); const ordersRaw = useCollection('orders'); const tasksRaw = useCollection('tasks'); const shoppingRaw = useCollection('shopping'); const remindersRaw = useCollection('reminders'); const settingsRaw = useCollection('settings'); const seededRef = useRef(false); // Seed only when an owner is connected (needs write access). useEffect(() => { if (!session) return; if (seededRef.current) return; if (productsRaw.loading || recipesRaw.loading) return; const role = ROLES[session.role]; if (!role?.perms.create) { seededRef.current = true; return; } if ((productsRaw.items || []).length === 0 && (recipesRaw.items || []).length === 0) { seededRef.current = true; SEED_PRODUCTS.forEach(p => productsRaw.add(p)); SEED_RECIPES.forEach(r => recipesRaw.add(r)); } else { seededRef.current = true; } }, [session, productsRaw.loading, recipesRaw.loading, productsRaw.items, recipesRaw.items]); // ---- Auth gate ---- if (!session) { return ; } const role = ROLES[session.role]; const perms = role.perms; function handleLogout() { clearSession(); setSession(null); setView('home'); setShowMore(false); } // Wrap every collection so unauthorized mutations become no-ops. const clients = guardCollection(clientsRaw, perms); const products = guardCollection(productsRaw, perms); const menus = guardCollection(menusRaw, perms); const recipes = guardCollection(recipesRaw, perms); const ingredients = guardCollection(ingredientsRaw, perms); const orders = guardCollection(ordersRaw, perms); const tasks = guardCollection(tasksRaw, perms); const shopping = guardCollection(shoppingRaw, perms); const reminders = guardCollection(remindersRaw, perms); // Settings are owner-only. const settings = guardCollection(settingsRaw, { ...perms, create: perms.settings, update: perms.settings, remove: perms.settings }); const settingsData = (settingsRaw.items || [])[0]?.value || {}; function saveSettings(data) { if (!perms.settings) return; const first = (settingsRaw.items || [])[0]; if (first) settingsRaw.update(first.id, data); else settingsRaw.add(data); } function navTo(v) { setView(v); setShowMore(false); } const navMain = [ { id: 'home', label: 'Accueil', icon: 'Home' }, { id: 'orders', label: 'Commandes', icon: 'Orders' }, { id: 'clients', label: 'Clients', icon: 'Users' }, { id: 'production', label: 'Production', icon: 'Chef' }, { id: 'courses', label: 'Courses', icon: 'Cart' }, ]; const navMoreAll = [ { id: 'recipes', label: 'Recettes', icon: 'Book' }, { id: 'products', label: 'Produits', icon: 'Plate' }, { id: 'menus', label: 'Menus', icon: 'Menu' }, { id: 'stock', label: 'Stock', icon: 'Box' }, { id: 'ingredients', label: 'Ingrédients', icon: 'Box' }, { id: 'payments', label: 'Paiements', icon: 'Money' }, { id: 'reminders', label: 'Rappels', icon: 'Bell' }, { id: 'planning', label: 'Planning', icon: 'Calendar' }, { id: 'settings', label: 'Paramètres', icon: 'Settings', ownerOnly: true }, { id: 'exports', label: 'Exports', icon: 'Export' }, ]; const navMore = navMoreAll.filter(n => !n.ownerOnly || perms.settings); // Prevent viewing owner-only pages if role changes. if (view === 'settings' && !perms.settings) { setView('home'); } return (
CM
CROQ’UP MIGNARDISE
BY MAË
{showMore && (
{navMore.map(n => { const IconC = Icon[n.icon]; return ( ); })}
)}
{view === 'home' && ( { setOrderOpen('new'); setView('orders'); }} onNewClient={() => setView('clients')} onNewProduct={() => setView('products')} onNewRecipe={() => setView('recipes')} /> )} {view === 'orders' && ( setOrderOpen(null)} /> )} {view === 'clients' && ( )} {view === 'production' && ( )} {view === 'courses' && ( )} {view === 'recipes' && ( )} {view === 'products' && ( )} {view === 'menus' && ( )} {view === 'ingredients' && ( )} {view === 'stock' && ( )} {view === 'payments' && ( )} {view === 'reminders' && ( )} {view === 'planning' && ( )} {view === 'settings' && perms.settings && ( )} {view === 'exports' && ( )}
); } ReactDOM.createRoot(document.getElementById('root')).render();