import { useCollection, useIdentity } from '@deplixo/sdk'; import Dashboard from './components/Dashboard.jsx'; import Products from './components/Products.jsx'; import Menus from './components/Menus.jsx'; import Recipes from './components/Recipes.jsx'; import Clients from './components/Clients.jsx'; import Orders from './components/Orders.jsx'; import Ingredients from './components/Ingredients.jsx'; import History from './components/History.jsx'; import { Stock, Production, Reminders, Payments, Calendar, Deliveries, ShoppingList, Settings } from './components/OtherSections.jsx'; import { SEED_RECIPES } from './components/seedRecipes.js'; const NAV = [ { key: 'home', label: 'Accueil', icon: 'home' }, { key: 'orders', label: 'Commandes', icon: 'orders' }, { key: 'clients', label: 'Clients', icon: 'users' }, { key: 'production', label: 'Production', icon: 'check' }, { key: 'shopping', label: 'Courses', icon: 'cart' }, { key: 'recipes', label: 'Recettes', icon: 'book' }, { key: 'more', label: 'Plus', icon: 'more' }, ]; const MORE_ITEMS = [ { key: 'products', label: 'Produits' }, { key: 'menus', label: 'Menus' }, { key: 'ingredients', label: 'Ingrédients' }, { key: 'stock', label: 'Stock' }, { key: 'calendar', label: 'Calendrier' }, { key: 'payments', label: 'Paiements' }, { key: 'deliveries', label: 'Livraisons' }, { key: 'reminders', label: 'Rappels' }, { key: 'history', label: 'Historique des modifications' }, { key: 'settings', label: 'Paramètres / Administration' }, ]; function Icon({ name }) { const paths = { home: , orders: , users: , check: , cart: , book: , more: , }; return ( ); } function LoginScreen() { return (

CROQ’UP MIGNARDISE

BY MAË

Connexion en cours…

); } // Fields to track per entity type. Everything else is ignored (or hidden as "autres"). const TRACKED_FIELDS = { product: ['name', 'price', 'category', 'description', 'recipeId'], menu: ['name', 'price', 'description', 'items'], recipe: ['name', 'yield', 'unit', 'instructions', 'ingredients', 'notes'], order: ['number', 'clientName', 'eventDate', 'eventTime', 'status', 'deliveryMode', 'address', 'lines', 'payments', 'notes'], }; function diffValues(oldV, newV, fields) { const changes = []; for (const f of fields) { const a = oldV ? oldV[f] : undefined; const b = newV ? newV[f] : undefined; const sa = JSON.stringify(a); const sb = JSON.stringify(b); if (sa !== sb) changes.push({ field: f, oldValue: a, newValue: b }); } return changes; } function App() { const { user, loading: identityLoading } = useIdentity(); const products = useCollection('products'); const menus = useCollection('menus'); const recipes = useCollection('recipes'); const clients = useCollection('clients'); const orders = useCollection('orders'); const ingredients = useCollection('ingredients'); const tasks = useCollection('tasks'); const reminders = useCollection('reminders'); const users = useCollection('users'); const history = useCollection('history'); const [view, setView] = useState('home'); const [focusId, setFocusId] = useState(null); const [toast, setToast] = useState(''); const [moreOpen, setMoreOpen] = useState(false); function notify(msg) { setToast(msg); setTimeout(() => setToast(''), 2500); } function nav(v, id) { setView(v); if (id) setFocusId(id); setMoreOpen(false); window.scrollTo(0, 0); } // Auto-register user on first load useEffect(() => { if (!user || users.loading) return; const existing = (users.items || []).find(u => u.value?.userId === user.id); if (!existing) { const isFirst = (users.items || []).length === 0; users.add({ userId: user.id, name: user.name, role: isFirst ? 'Admin' : 'Éditeur', joinedAt: new Date().toISOString() }); } // eslint-disable-next-line }, [user, users.loading, users.items?.length]); const myRole = useMemo(() => { if (!user) return 'Lecteur'; const u = (users.items || []).find(x => x.value?.userId === user.id); return u?.value?.role || 'Admin'; }, [user, users.items]); const canEdit = myRole === 'Admin' || myRole === 'Éditeur'; const canAdmin = myRole === 'Admin'; // --- History logging wrappers --- function logChange({ entityType, entityId, entityName, action, changes }) { try { history.add({ entityType, entityId: entityId || '', entityName: entityName || '', action, changes: changes || [], userName: user?.name || '', userId: user?.id || '', date: new Date().toISOString(), }); } catch (e) { /* non-blocking */ } } function nameOf(entityType, value) { if (!value) return ''; if (entityType === 'order') return `#${value.number || ''} — ${value.clientName || ''}`; return value.name || ''; } function trackedUpdate(collection, entityType) { return async (id, newValue) => { const existing = (collection.items || []).find(x => x.id === id); const oldValue = existing?.value; const fields = TRACKED_FIELDS[entityType] || []; const changes = diffValues(oldValue, newValue, fields); const result = await collection.update(id, newValue); if (changes.length > 0) { logChange({ entityType, entityId: id, entityName: nameOf(entityType, newValue) || nameOf(entityType, oldValue), action: 'update', changes, }); } return result; }; } function trackedAdd(collection, entityType) { return async (value) => { const result = await collection.add(value); logChange({ entityType, entityId: result?.id || '', entityName: nameOf(entityType, value), action: 'create', changes: [], }); return result; }; } function trackedRemove(collection, entityType) { return async (id) => { const existing = (collection.items || []).find(x => x.id === id); const oldValue = existing?.value; const result = await collection.remove(id); logChange({ entityType, entityId: id, entityName: nameOf(entityType, oldValue), action: 'delete', changes: [], }); return result; }; } const productsUpdate = trackedUpdate(products, 'product'); const productsAdd = trackedAdd(products, 'product'); const productsRemove = trackedRemove(products, 'product'); const menusUpdate = trackedUpdate(menus, 'menu'); const menusAdd = trackedAdd(menus, 'menu'); const menusRemove = trackedRemove(menus, 'menu'); const recipesUpdate = trackedUpdate(recipes, 'recipe'); const recipesAdd = trackedAdd(recipes, 'recipe'); const recipesRemove = trackedRemove(recipes, 'recipe'); const ordersUpdate = trackedUpdate(orders, 'order'); const ordersAdd = trackedAdd(orders, 'order'); const ordersRemove = trackedRemove(orders, 'order'); async function seedRecipes() { for (const r of SEED_RECIPES) { // Avoid duplicates const exists = (recipes.items || []).some(x => x.value?.name === r.name); if (!exists) await recipes.add(r); } notify('Recettes de base chargées'); } if (identityLoading || !user) { return ; } const anyLoading = products.loading || menus.loading || recipes.loading || clients.loading || orders.loading || ingredients.loading || tasks.loading || reminders.loading || users.loading; const commonProps = { products: products.items || [], menus: menus.items || [], recipes: recipes.items || [], clients: clients.items || [], orders: orders.items || [], ingredients: ingredients.items || [], tasks: tasks.items || [], reminders: reminders.items || [], canEdit, notify, }; return (
CROQ’UP MIGNARDISE BY MAË
{user.name} {myRole}
{view === 'home' && ( )} {view === 'orders' && ( setFocusId(null)} /> )} {view === 'clients' && ( )} {view === 'production' && ( )} {view === 'shopping' && ( )} {view === 'recipes' && ( )} {view === 'products' && ( )} {view === 'menus' && ( )} {view === 'ingredients' && ( )} {view === 'stock' && ( )} {view === 'calendar' && ( )} {view === 'payments' && ( )} {view === 'deliveries' && ( )} {view === 'reminders' && ( )} {view === 'history' && ( )} {view === 'settings' && ( )} {view === 'more' && (

Plus

Accès rapide
{MORE_ITEMS.map(m => ( ))}
)}
{toast &&
{toast}
}
); } ReactDOM.createRoot(document.getElementById('root')).render();