DIGIY RESTO PRO V2 – Système Complet
`);
printWindow.document.close();
setTimeout(() => {
setTables(tables.map(t => t.id === selectedTable.id ? { ...t, status: 'libre', guests: 0, orders: [] } : t));
setSelectedTable(null);
setCurrentOrder([]);
setView('tables');
alert('✅ Paiement effectué ! Table libérée.');
}, 500);
};const markTableReady = (order) => {
setAllOrders(allOrders.map(o => o.id === order.id ? { ...o, items: o.items.map(i => ({ ...i, status: 'pret' })) } : o));
alert(`✅ TABLE ${order.tableNumber} PRÊTE !`);
};const addProduct = () => {
if (!newProduct.name || !newProduct.price || !newProduct.sku) return alert('Remplis tous les champs !');
const product = {
id: Date.now().toString(),
name: newProduct.name,
price: parseFloat(newProduct.price),
tax: parseFloat(newProduct.tax),
sku: newProduct.sku,
poste: newProduct.poste,
isPlat: newProduct.isPlat,
isEntree: newProduct.isEntree,
isDessert: newProduct.isDessert
};
setProducts([...products, product]);
setNewProduct({ name: '', price: '', tax: '0.10', poste: 'BAR', sku: '', isPlat: false, isEntree: false, isDessert: false });
alert('✅ Produit ajouté !');
};const addMenu = () => {
if (!newMenu.name || !newMenu.price) return alert('Remplis le nom et le prix !');
if (newMenu.entrees.length === 0 && newMenu.plats.length === 0 && newMenu.desserts.length === 0) {
return alert('Sélectionne au moins une option !');
}
const menu = {
id: `menu_${Date.now()}`,
name: newMenu.name,
price: parseFloat(newMenu.price),
tax: parseFloat(newMenu.tax),
entrees: newMenu.entrees.length > 0 ? newMenu.entrees : undefined,
plats: newMenu.plats.length > 0 ? newMenu.plats : undefined,
desserts: newMenu.desserts.length > 0 ? newMenu.desserts : undefined
};
setMenus([...menus, menu]);
setNewMenu({ name: '', price: '', tax: '0.10', entrees: [], plats: [], desserts: [] });
setShowMenuBuilder(false);
alert('✅ Menu créé !');
};const toggleMenuSelection = (category, productId) => {
setNewMenu(prev => {
const current = prev[category];
if (current.includes(productId)) {
return { ...prev, [category]: current.filter(id => id !== productId) };
} else {
return { ...prev, [category]: [...current, productId] };
}
});
};const addMenuToOrder = (menu, selections) => {
const menuItem = {
id: `menu_${Date.now()}`,
name: menu.name,
price: menu.price,
tax: menu.tax,
qty: 1,
isMenu: true,
selections: selections,
poste: 'MENU'
};
setCurrentOrder([...currentOrder, menuItem]);
setShowMenuModal(false);
setSelectedMenu(null);
setMenuSelections({});
};const addNewZone = () => {
if (!newZone.name || !newZone.tables || !newZone.startNumber) return alert('Remplis tous les champs !');
const tables = parseInt(newZone.tables);
const startNum = parseInt(newZone.startNumber);
if (tables < 1) return alert('Au moins 1 table !');
if (startNum < 1) return alert('Numéro de départ invalide !');
const zoneKey = `ZONE_${Date.now()}`;
const newZones = {
...zones,
[zoneKey]: { name: newZone.name, tables: tables, color: newZone.color, startNumber: startNum }
};
setZones(newZones);
setTables(generateTables(newZones));
setNewZone({ name: '', tables: '', color: 'from-blue-500 to-purple-500', startNumber: '' });
setEditingZone(null);
alert('✅ Zone ajoutée !');
};const updateZoneTables = (zoneKey, newTableCount) => {
const count = parseInt(newTableCount);
if (!count || count < 0) return;
setZones({...zones, [zoneKey]: {...zones[zoneKey], tables: count}});
const newZones = {...zones, [zoneKey]: {...zones[zoneKey], tables: count}};
setTables(generateTables(newZones));
setEditingZone(null);
alert('✅ Tables mises à jour !');
};const updateZoneStartNumber = (zoneKey, newStartNumber) => {
const startNum = parseInt(newStartNumber);
if (!startNum || startNum < 1) return alert('Numéro invalide !');
setZones({...zones, [zoneKey]: {...zones[zoneKey], startNumber: startNum}});
const newZones = {...zones, [zoneKey]: {...zones[zoneKey], startNumber: startNum}};
setTables(generateTables(newZones));
setEditingZone(null);
alert('✅ Numérotation mise à jour !');
};const deleteZone = (zoneKey) => {
if (!confirm('Supprimer cette zone ?')) return;
const newZones = {...zones};
delete newZones[zoneKey];
setZones(newZones);
setTables(generateTables(newZones));
alert('✅ Zone supprimée !');
};// ✅ NOUVEAU : Ajouter une réservation
const addReservation = () => {
if (!newReservation.customerName || !newReservation.phone || !newReservation.date || !newReservation.time || !newReservation.guests) {
return alert('Remplis tous les champs obligatoires !');
}
const reservation = {
id: Date.now().toString(),
...newReservation,
guests: parseInt(newReservation.guests),
status: 'confirmed',
createdAt: new Date().toISOString()
};
setReservations([...reservations, reservation]);
setNewReservation({ customerName: '', phone: '', date: '', time: '', guests: '', notes: '' });
setShowReservationModal(false);
alert('✅ Réservation confirmée !');
};// ✅ NOUVEAU : Attribuer une table à une réservation
const assignTableToReservation = (reservationId, tableId) => {
const table = tables.find(t => t.id === tableId);
if (table.status === 'occupee') return alert('Table déjà occupée !');
const reservation = reservations.find(r => r.id === reservationId);
setTables(tables.map(t =>
t.id === tableId ? { ...t, status: 'occupee', guests: reservation.guests, notes: `Résa: ${reservation.customerName}` } : t
));
setReservations(reservations.map(r =>
r.id === reservationId ? { ...r, status: 'arrived', assignedTableId: tableId } : r
));
alert(`✅ Table ${table.number} attribuée à ${reservation.customerName} !`);
};const zoneTables = tables.filter(t => t.zone === selectedZone);
const posteProducts = products.filter(p => p.poste === selectedPoste);
const total = currentOrder.reduce((s, i) => s + (i.price * (1 + i.tax) * i.qty), 0);// ✅ NOUVEAU : Vue Réservations
if (view === 'reservations') {
const today = new Date().toISOString().split('T')[0];
const todayReservations = reservations.filter(r => r.date === today && r.status !== 'cancelled');
return (
📆 Réservations du jour
{todayReservations.length === 0 ? (
Aucune réservation aujourd'hui
) : (
{todayReservations.map(resa => (
{resa.customerName}
📞 {resa.phone}
🕐 {resa.time} • 👥 {resa.guests} pers.
{resa.notes &&
📝 {resa.notes}
}
{resa.status === 'arrived' ? (
✅ Arrivé
) : (
🕐 Confirmé
)}
{resa.status !== 'arrived' && (
Attribuer une table :
{tables.filter(t => t.status === 'libre').slice(0, 8).map(t => (
))}
)}
))}
)}
📋 Toutes les réservations
{reservations.filter(r => r.status !== 'cancelled').sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 20).map(resa => (
{resa.customerName}
📅 {new Date(resa.date).toLocaleDateString('fr-FR')} • 🕐 {resa.time}
))}
{showReservationModal && (
📅 Nouvelle Réservation
)}
);
}// Vue Tables
if (view === 'tables') {
return (
{Object.entries(zones).map(([key, zone]) => (
))}
{zoneTables.map(t => (
))}
{showOpenModal && (
)}
);
}// Vue Cuisine
if (view === 'cuisine') {
const activeOrders = allOrders.filter(o => o.items.some(i => i.status !== 'pret'));
return (
👨🍳 VUE CUISINE
🔔 Zones actives :
{Object.entries(zones).map(([key, zone]) => (
))}
{Object.entries(POSTES).map(([key, poste]) => {
const posteOrders = activeOrders.filter(o => (o.byPoste[key] || []).length > 0);
return (
{poste.name}
{posteOrders.length}
{posteOrders.length === 0 ? (
Aucune commande
) : (
posteOrders.map(order => {
const allReady = (order.byPoste[key] || []).every(i => i.status === 'pret');
return (
Table {order.tableNumber}
{new Date(order.sentAt).toLocaleTimeString('fr-FR')}
{!allReady && (
)}
{allReady && (
🟢 TABLE PRÊTE
)}
{(order.byPoste[key] || []).map((item, idx) => (
{item.displayName || item.name}
{item.customOptions?.cuisson && (
🥩 {item.customOptions.cuisson}
)}
{item.customOptions?.notes && (
📝 {item.customOptions.notes}
)}
x{item.qty}
))}
);
})
)}
);
})}
);
}// Vue Programmation
if (view === 'programmation') {
return (
{/* INFOS ENTREPRISE */}
{/* ZONES ET TABLES */}
🗺️ Zones & Tables
{editingZone === 'NEW' && (
)}
{Object.entries(zones).map(([key, zone]) => (
{editingZone === key ? (
) : (
{zone.name}
Tables {zone.startNumber || 1} à {(zone.startNumber || 1) + zone.tables - 1}
{zone.tables} tables
)}
))}
{/* PRODUITS */}
{/* MENUS */}
📋 Menus ({menus.length})
{showMenuBuilder && (
)}
{menus.map(menu => (
{menu.name}
{formatCurrency(menu.price * (1 + menu.tax))} TTC
{menu.entrees &&
• {menu.entrees.length} entrée(s)
}
{menu.plats &&
• {menu.plats.length} plat(s)
}
{menu.desserts &&
• {menu.desserts.length} dessert(s)
}
))}
{/* LISTE PRODUITS */}
{Object.entries(POSTES).map(([key, poste]) => {
const posteProducts = products.filter(p => p.poste === key);
return (
{poste.name} ({posteProducts.length})
{posteProducts.map(p => (
{p.name}
{formatCurrency(p.price)} • {p.sku}
{formatCurrency(p.price * (1 + p.tax))}
))}
);
})}
);
}// Vue Commande
if (view === 'order') {
return (
Table {selectedTable?.number}
{selectedTable?.guests} couverts
{Object.entries(POSTES).map(([key, poste]) => (
))}
{posteProducts.map(p => (
))}
{currentOrder.length > 0 && (
Commande
{currentOrder.map(item => (
{item.displayName || item.name}
{item.isMenu && item.selections && (
{item.selections.entree &&
→ {item.selections.entree.name}
}
{item.selections.plat &&
→ {item.selections.plat.name}
}
{item.selections.dessert &&
→ {item.selections.dessert.name}
}
)}
{item.customOptions?.cuisson &&
{item.customOptions.cuisson}}
{item.customOptions?.notes &&
📝 {item.customOptions.notes}
}
{item.qty}
))}
Total TTC
{formatCurrency(total)}
)}
{/* Modal Changement de Table */}
{showChangeTableModal && (
🔄 Changer de Table
Table actuelle : Table {selectedTable.number}
)}{showMenuModal && (
📋 Choisir un Menu
{menus.length === 0 ? (
Aucun menu disponible
) : (
menus.map(menu => (
))
)}
)}{selectedMenu && (
{selectedMenu.name}
{selectedMenu.entrees && (
Entrée
{selectedMenu.entrees.map(id => {
const p = products.find(pr => pr.id === id);
return p && (
);
})}
)}
{selectedMenu.plats && (
Plat
{selectedMenu.plats.map(id => {
const p = products.find(pr => pr.id === id);
return p && (
);
})}
)}
{selectedMenu.desserts && (
Dessert
{selectedMenu.desserts.map(id => {
const p = products.find(pr => pr.id === id);
return p && (
);
})}
)}
)}{customizingItem && (
🍽️ {customizingItem.name}
{customizingItem.isPlat && (
🥩 Cuisson
{['Bleu', 'Saignant', 'À point', 'Bien cuit'].map(c => (
))}
)}
❌ Options
{[
{ key: 'sansSauce', label: 'Sans sauce' },
{ key: 'sansFromage', label: 'Sans fromage' },
{ key: 'sansTomate', label: 'Sans tomate' },
{ key: 'sansSalade', label: 'Sans salade' }
].map(opt => (
))}📝 Notes spéciales
)}
);
}
};ReactDOM.createRoot(document.getElementById('root')).render(
);