166 lines
7.1 KiB
TypeScript
166 lines
7.1 KiB
TypeScript
// Base de données en mémoire (démo). Tout est remis à zéro au redémarrage du serveur.
|
||
// En production : remplacer par Supabase / Postgres.
|
||
|
||
export type Size = 'L' | 'XL'
|
||
export type Category = 'wok' | 'baguette' | 'boissons' | 'desserts'
|
||
export type Status = 'new' | 'prep' | 'ready' | 'done' | 'refused'
|
||
export type Payment = 'carte' | 'apple' | 'google' | 'especes'
|
||
|
||
export interface Dish {
|
||
id: string
|
||
name: string
|
||
desc: string
|
||
cat: Category
|
||
prices: Partial<Record<Size, number | null>> // centimes ; null = prix à définir
|
||
meats: string[]
|
||
sauces?: string[]
|
||
badge?: { label: string, tone: 'best' | 'spicy' | 'veggie' }
|
||
img?: string
|
||
available: boolean
|
||
}
|
||
|
||
export interface Extra { id: string, label: string, price: number }
|
||
|
||
export interface OrderItem {
|
||
dishId: string
|
||
name: string
|
||
size: Size
|
||
meat: string
|
||
sauce?: string
|
||
extras: string[]
|
||
note?: string
|
||
qty: number
|
||
unit: number
|
||
}
|
||
|
||
export interface Order {
|
||
id: string
|
||
num: string
|
||
createdAt: number
|
||
client: { name: string, phone: string }
|
||
mode: 'livraison' | 'retrait'
|
||
address?: string
|
||
slot?: string
|
||
items: OrderItem[]
|
||
subtotal: number
|
||
fee: number
|
||
total: number
|
||
payment: Payment
|
||
paid: boolean
|
||
status: Status
|
||
prepMinutes?: number
|
||
readyBy?: number
|
||
refunded?: boolean
|
||
history: { at: number, label: string }[]
|
||
}
|
||
|
||
export interface DayHours { day: string, open: boolean, midi: string, soir: string }
|
||
|
||
export interface Settings {
|
||
open: boolean
|
||
wait: number
|
||
deliveryFee: number
|
||
freeFrom: number
|
||
minOrder: number
|
||
radiusKm: number
|
||
hours: DayHours[]
|
||
closures: { date: string, label: string }[]
|
||
}
|
||
|
||
interface DB {
|
||
dishes: Dish[]
|
||
extras: Extra[]
|
||
orders: Order[]
|
||
settings: Settings
|
||
seq: number
|
||
}
|
||
|
||
const MEATS = ['Bœuf', 'Crevettes', 'Poulet']
|
||
|
||
function seedOrders(): Order[] {
|
||
const now = Date.now()
|
||
const m = (min: number) => now - min * 60000
|
||
const mk = (n: number, min: number, name: string, mode: Order['mode'], items: OrderItem[], status: Status, payment: Payment = 'carte', extra: Partial<Order> = {}): Order => {
|
||
const subtotal = items.reduce((a, i) => a + i.unit * i.qty, 0)
|
||
const fee = mode === 'livraison' ? 250 : 0
|
||
const createdAt = m(min)
|
||
return {
|
||
id: 'seed' + n, num: 'PZ-0' + n, createdAt, client: { name, phone: '06 12 34 56 78' }, mode,
|
||
address: mode === 'livraison' ? '12 rue des Arts, 74200 Thonon-les-Bains' : undefined,
|
||
slot: mode === 'retrait' ? 'Dès que possible' : undefined,
|
||
items, subtotal, fee, total: subtotal + fee, payment, paid: payment !== 'especes', status,
|
||
history: [
|
||
{ at: createdAt, label: 'Commande reçue' },
|
||
...(status !== 'new' ? [{ at: createdAt + 60000, label: 'Acceptée · en préparation au wok' }] : []),
|
||
...(status === 'ready' || status === 'done' ? [{ at: createdAt + 18 * 60000, label: 'Prête' }] : []),
|
||
...(status === 'done' ? [{ at: createdAt + 25 * 60000, label: mode === 'livraison' ? 'Remise au livreur' : 'Récupérée par le client' }] : [])
|
||
],
|
||
...extra
|
||
}
|
||
}
|
||
const it = (dishId: string, name: string, size: Size, meat: string, unit: number, qty = 1, sauce?: string, note?: string): OrderItem =>
|
||
({ dishId, name, size, meat, unit, qty, sauce, note, extras: [] })
|
||
return [
|
||
mk(412, 380, 'Emma T.', 'retrait', [it('pad', 'Le Pad Thaï', 'L', 'Poulet', 1350)], 'done'),
|
||
mk(413, 350, 'Hugo L.', 'livraison', [it('crousty', 'Le Crousty', 'XL', 'Poulet', 1350, 2, 'BBQ')], 'done', 'google'),
|
||
mk(414, 320, 'Nadia K.', 'livraison', [it('nouilles', 'Les Nouilles', 'XL', 'Bœuf', 1500), it('crousty', 'Le Crousty', 'L', 'Poulet', 1100, 1, 'Chili thaï')], 'done'),
|
||
mk(415, 60, 'Karim D.', 'livraison', [it('crousty', 'Le Crousty', 'L', 'Poulet', 1100, 1, 'BBQ'), it('nouilles', 'Les Nouilles', 'XL', 'Bœuf', 1500)], 'done', 'apple'),
|
||
mk(416, 22, 'Lina M.', 'retrait', [it('crousty', 'Le Crousty', 'XL', 'Bœuf', 1350, 1, 'Aigre-douce'), it('baguette', 'La Baguette', 'XL', 'Poulet', 1000)], 'ready', 'carte', { prepMinutes: 20 }),
|
||
mk(417, 12, 'Sami B.', 'livraison', [it('pad', 'Le Pad Thaï', 'XL', 'Crevettes', 1590, 1, undefined, 'sans oignons frits'), it('nouilles', 'Les Nouilles', 'L', 'Poulet', 1250)], 'prep', 'apple', { prepMinutes: 20, readyBy: m(12) + 20 * 60000 })
|
||
]
|
||
}
|
||
|
||
function seed(): DB {
|
||
return {
|
||
seq: 418,
|
||
extras: [
|
||
{ id: 'sauce', label: 'Sauce en plus', price: 50 },
|
||
{ id: 'oignons', label: 'Oignons frits en plus', price: 50 },
|
||
{ id: 'viande', label: 'Double viande', price: 300 }
|
||
],
|
||
dishes: [
|
||
{ id: 'crousty', name: 'Le Crousty', desc: 'Poulet crousty, riz, sauce wow, oignons frits.', cat: 'wok', prices: { L: 1100, XL: 1350 }, meats: MEATS, sauces: ['BBQ', 'Chili thaï', 'Aigre-douce'], badge: { label: 'Best-seller', tone: 'best' }, img: '/img/crousty.png', available: true },
|
||
{ id: 'nouilles', name: 'Les Nouilles', desc: 'Nouilles, légumes, herbes thaï, oignons frits.', cat: 'wok', prices: { L: 1250, XL: 1500 }, meats: MEATS, img: '/img/nouilles.png', available: true },
|
||
{ id: 'riz', name: 'Le Riz Thaï', desc: 'Riz sauté, légumes, sauce soja, herbes thaï, oignons frits.', cat: 'wok', prices: { L: 1150, XL: 1400 }, meats: MEATS, img: '/img/riz-thai.png', available: true },
|
||
{ id: 'pad', name: 'Le Pad Thaï', desc: 'Nouilles de riz, herbes thaï, soja, oignons frits.', cat: 'wok', prices: { L: 1350, XL: 1590 }, meats: MEATS, badge: { label: 'Épicé 🌶️', tone: 'spicy' }, img: '/img/pad-thai.png', available: true },
|
||
{ id: 'veggie', name: 'Le Veggie', desc: 'Riz, légumes (brocolis), herbes thaï, oignons frits.', cat: 'wok', prices: { L: null, XL: null }, meats: [], badge: { label: 'Veggie', tone: 'veggie' }, available: true },
|
||
{ id: 'baguette', name: 'La Baguette', desc: 'Bœuf ou poulet, légumes, sauce, frites.', cat: 'baguette', prices: { XL: 1000 }, meats: ['Bœuf', 'Poulet'], available: true }
|
||
],
|
||
orders: seedOrders(),
|
||
settings: {
|
||
open: true,
|
||
wait: 20,
|
||
deliveryFee: 250,
|
||
freeFrom: 3500,
|
||
minOrder: 1500,
|
||
radiusKm: 5,
|
||
hours: [
|
||
{ day: 'Lundi', open: true, midi: '11h30 – 14h00', soir: '18h30 – 22h00' },
|
||
{ day: 'Mardi', open: true, midi: '11h30 – 14h00', soir: '18h30 – 22h00' },
|
||
{ day: 'Mercredi', open: true, midi: '11h30 – 14h00', soir: '18h30 – 22h00' },
|
||
{ day: 'Jeudi', open: true, midi: '11h30 – 14h00', soir: '18h30 – 22h00' },
|
||
{ day: 'Vendredi', open: true, midi: '11h30 – 14h00', soir: '18h30 – 23h00' },
|
||
{ day: 'Samedi', open: true, midi: '11h30 – 14h30', soir: '18h30 – 23h00' },
|
||
{ day: 'Dimanche', open: true, midi: 'Fermé', soir: '18h30 – 22h00' }
|
||
],
|
||
closures: [
|
||
{ date: '24–25 déc.', label: 'Fêtes de fin d\'année' },
|
||
{ date: '1 janv.', label: 'Jour de l\'an' }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
const g = globalThis as unknown as { __panzaiDb?: DB }
|
||
if (!g.__panzaiDb) g.__panzaiDb = seed()
|
||
export const db = g.__panzaiDb
|
||
|
||
export function resetDb() {
|
||
Object.assign(db, seed())
|
||
}
|
||
|
||
export function findOrder(id: string) {
|
||
const o = db.orders.find(x => x.id === id)
|
||
if (!o) throw createError({ statusCode: 404, statusMessage: 'Commande introuvable' })
|
||
return o
|
||
}
|