Implement booking cancellation feature and refactor booking fetching logic
This commit is contained in:
@@ -17,3 +17,15 @@
|
||||
2025-10-31T20:54:03: > Ready on http://localhost:3000
|
||||
2025-10-31T21:04:52: > Ready on http://localhost:3000
|
||||
2025-10-31T21:07:41: > Ready on http://localhost:3000
|
||||
2025-10-31T21:31:48: > Ready on http://localhost:3000
|
||||
2025-10-31T21:34:11: > Ready on http://localhost:3000
|
||||
2025-10-31T21:39:45: > Ready on http://localhost:3000
|
||||
2025-10-31T21:40:36: > Ready on http://localhost:3000
|
||||
2025-10-31T21:45:41: > Ready on http://localhost:3000
|
||||
2025-10-31T21:47:51: > Ready on http://localhost:3000
|
||||
2025-10-31T21:50:59: > Ready on http://localhost:3000
|
||||
2025-10-31T21:58:30: > Ready on http://localhost:3000
|
||||
2025-10-31T22:14:18: > Ready on http://localhost:3000
|
||||
2025-10-31T22:17:17: > Ready on http://localhost:3000
|
||||
2025-10-31T22:21:03: > Ready on http://localhost:3000
|
||||
2025-10-31T22:22:22: > Ready on http://localhost:3000
|
||||
|
||||
@@ -14,22 +14,54 @@ type Booking = {
|
||||
menu_id?: string
|
||||
package_id?: string
|
||||
message?: string
|
||||
status?: string
|
||||
}
|
||||
|
||||
export default function BookingsClient() {
|
||||
const [bookings, setBookings] = useState<Booking[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [cancelling, setCancelling] = useState<string | null>(null)
|
||||
|
||||
async function fetchBookings() {
|
||||
try {
|
||||
const r = await fetch('/api/bookings')
|
||||
const data = await r.ok ? await r.json() : []
|
||||
setBookings(Array.isArray(data) ? data : [])
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
fetch('/api/bookings')
|
||||
.then(r => r.ok ? r.json() : [])
|
||||
.then(data => { if (mounted) setBookings(Array.isArray(data) ? data : []) })
|
||||
.catch(err => console.error(err))
|
||||
.finally(() => { if (mounted) setLoading(false) })
|
||||
return () => { mounted = false }
|
||||
fetchBookings()
|
||||
}, [])
|
||||
|
||||
async function cancelBooking(id: string, name: string) {
|
||||
if (!confirm(`Annuller booking for ${name}?\n\nDette kan ikke fortrydes.`)) return
|
||||
|
||||
setCancelling(id)
|
||||
try {
|
||||
const res = await fetch('/api/bookings', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
setBookings(prev => prev.filter(b => b.id !== id))
|
||||
alert(`Booking for ${name} er annulleret`)
|
||||
} else {
|
||||
alert('Kunne ikke annullere booking — prøv igen')
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Fejl ved annullering — prøv igen')
|
||||
} finally {
|
||||
setCancelling(null)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div>Indlæser bookinger...</div>
|
||||
if (bookings.length === 0) return <div>Ingen bookinger fundet.</div>
|
||||
|
||||
@@ -44,17 +76,30 @@ export default function BookingsClient() {
|
||||
<th className="p-2">Kuverter</th>
|
||||
<th className="p-2">Menu</th>
|
||||
<th className="p-2">Note</th>
|
||||
<th className="p-2">Handling</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{bookings.map(b => (
|
||||
<tr key={b.id} className="border-t">
|
||||
<tr key={b.id} className="border-t hover:bg-gray-50">
|
||||
<td className="p-2 align-top">{b.booking_date}</td>
|
||||
<td className="p-2 align-top">{b.start_time.slice(0,5)} - {b.end_time.slice(0,5)}</td>
|
||||
<td className="p-2 align-top">{b.name}<div className="text-sm text-primary-600">{b.email}{b.phone ? ` · ${b.phone}` : ''}</div></td>
|
||||
<td className="p-2 align-top">
|
||||
<div>{b.name}</div>
|
||||
<div className="text-sm text-primary-600">{b.email}{b.phone ? ` · ${b.phone}` : ''}</div>
|
||||
</td>
|
||||
<td className="p-2 align-top">{b.party_size || '-'}</td>
|
||||
<td className="p-2 align-top">{b.menu_id || b.package_id || '-'}</td>
|
||||
<td className="p-2 align-top">{b.message || '-'}</td>
|
||||
<td className="p-2 align-top">
|
||||
<button
|
||||
onClick={() => cancelBooking(b.id, b.name)}
|
||||
disabled={cancelling === b.id}
|
||||
className="px-3 py-1 bg-red-500 text-white rounded text-sm hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
{cancelling === b.id ? 'Annullerer...' : 'Annuller'}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -75,3 +75,24 @@ export async function POST(req: Request) {
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(req: Request) {
|
||||
try {
|
||||
const body = await req.json()
|
||||
const { id } = body
|
||||
if (!id) {
|
||||
return NextResponse.json({ error: 'Missing booking id' }, { status: 400 })
|
||||
}
|
||||
|
||||
const [result] = await pool.query('DELETE FROM bookings WHERE id = ?', [id])
|
||||
// @ts-ignore
|
||||
if (result.affectedRows === 0) {
|
||||
return NextResponse.json({ error: 'Booking not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ ok: true })
|
||||
} catch (error) {
|
||||
console.error('Error deleting booking', error)
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import { sampleData } from '@/config/sample-data'
|
||||
import { Menu } from '@/types/models'
|
||||
import { Menu, Package } from '@/types/models'
|
||||
import { ImagePlaceholder } from '@/components/ui/image-placeholder'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ArrowRight } from 'lucide-react'
|
||||
|
||||
export function FeaturedMenus() {
|
||||
const { packages, menus } = sampleData
|
||||
// Prefer menus explicitly marked as featured. Fall back to first 4 if none set.
|
||||
|
||||
// Force rebuild - Prefer menus explicitly marked as featured
|
||||
const featuredMenus = (menus.filter((m: Menu) => (m as any).featured) as Menu[])
|
||||
.slice(0, 4)
|
||||
.length > 0 ? (menus.filter((m: Menu) => (m as any).featured) as Menu[]).slice(0, 4) : menus.slice(0, 4)
|
||||
|
||||
console.log('=== FeaturedMenus Debug v2 ===')
|
||||
console.log('Total menus:', menus.length)
|
||||
console.log('Featured menus:', featuredMenus.map(m => m.id))
|
||||
console.log('Total packages:', packages.length)
|
||||
console.log('Package menuIds:', packages.map((p: Package) => p.menuId))
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-primary-50">
|
||||
<div className="container mx-auto px-4">
|
||||
@@ -24,7 +33,8 @@ export function FeaturedMenus() {
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||
{featuredMenus.map((menu: Menu) => {
|
||||
const basePackage = packages.find((p: { menuId: string }) => p.menuId === menu.id)
|
||||
const basePackage = packages.find((p: Package) => p.menuId === menu.id)
|
||||
console.log(`Menu: ${menu.id}, Package found: ${basePackage?.id}, Price: ${basePackage?.pricePerCover}`)
|
||||
return (
|
||||
<MenuCard
|
||||
key={menu.id}
|
||||
|
||||
@@ -1,464 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sampleData = void 0;
|
||||
exports.sampleData = {
|
||||
rates: {
|
||||
chefHourly: 350,
|
||||
helperHourly: 200,
|
||||
serverHourly: 150,
|
||||
travelPerKm: 4.5,
|
||||
minServerHours: 4
|
||||
},
|
||||
staffPool: {
|
||||
helpers: [
|
||||
{ id: 'h1', name: 'Ekstra køkkenhjælp', maxHours: 8 }
|
||||
],
|
||||
servers: [
|
||||
{ id: 's1', name: 'Servering 1' },
|
||||
{ id: 's2', name: 'Servering 2' }
|
||||
],
|
||||
minHours: 4,
|
||||
autoAssignRules: {
|
||||
serveringHoursFunction: 'ceil(covers / 25) * 4',
|
||||
helpersThreshold: [
|
||||
{ maxCovers: 35, helpers: 0 },
|
||||
{ maxCovers: 60, helpers: 1 },
|
||||
{ maxCovers: 80, helpers: 2 }
|
||||
]
|
||||
}
|
||||
},
|
||||
chef: {
|
||||
id: 'christian-warme',
|
||||
name: 'Christian Warme',
|
||||
brand: 'warme.dk',
|
||||
bio: 'Uddannet kok (3–4 år). Klassiske selskabsmenuer med ro i køkkenet og fokus på gæsternes oplevelse.',
|
||||
skills: ['Klassisk dansk', 'Nordisk', 'Italiensk', 'Dessert'],
|
||||
serviceAreaKm: 80,
|
||||
baseCity: 'København',
|
||||
photo: '/img/christian-warme.png',
|
||||
contact: {
|
||||
phone: '+45 12 34 56 78',
|
||||
email: 'kontakt@warme.dk',
|
||||
instagram: 'https://www.instagram.com/warme.dk',
|
||||
facebook: 'https://www.facebook.com/warme.dk'
|
||||
}
|
||||
},
|
||||
menus: [
|
||||
{
|
||||
id: 'klassisk-3-retter',
|
||||
title: 'Klassisk Menu',
|
||||
description: 'En traditionel dansk menu med et moderne twist. Populært valg til familiefester.',
|
||||
courses: [
|
||||
'Varmrøget laks med rygeostcreme, dild og radiser',
|
||||
'Rosastegt kalvefilet med sæsonens grøntsager og rødvinssauce',
|
||||
'Chokoladekage med bær og vaniljeis'
|
||||
],
|
||||
tags: ['Nordisk', 'Klassisk', 'Populær'],
|
||||
featured: true,
|
||||
image: '/img/klassisk.png',
|
||||
baseIngredientsPerCover: 65,
|
||||
ingredientsCostIndex: 1.0,
|
||||
baseLabourHours: 4,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 15,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 295
|
||||
},
|
||||
{
|
||||
id: 'gron-3-retter',
|
||||
title: 'Grøn Menu',
|
||||
description: 'Sæsonbaseret vegetarisk menu med fokus på smag og farver. Perfekt til både vegetarer og kødelskere.',
|
||||
courses: [
|
||||
'Grillet asparges med hollandaise og karse',
|
||||
'Svamperisotto med parmesan og urter',
|
||||
'Pocherede pærer med vaniljeis og hasselnødder'
|
||||
],
|
||||
tags: ['Vegetarisk', 'Sæson', 'Populær'],
|
||||
featured: true,
|
||||
image: '/img/veg.png',
|
||||
baseIngredientsPerCover: 55,
|
||||
ingredientsCostIndex: 0.95,
|
||||
baseLabourHours: 4,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 15,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 275
|
||||
},
|
||||
{
|
||||
id: 'italiensk-fest',
|
||||
title: 'Italiensk Aften',
|
||||
description: 'Det bedste fra det italienske køkken. Afslappet stemning med mad serveret "family style".',
|
||||
courses: [
|
||||
'Antipasti med charcuteri, oliven og bruschetta',
|
||||
'Hjemmelavet pasta med tomat og basilikum',
|
||||
'Tiramisu med mascarpone'
|
||||
],
|
||||
tags: ['Italiensk', 'Pasta', 'Afslappet'],
|
||||
featured: true,
|
||||
image: '/img/italiensk.png',
|
||||
baseIngredientsPerCover: 55,
|
||||
ingredientsCostIndex: 0.9,
|
||||
baseLabourHours: 4,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 15,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 250
|
||||
},
|
||||
{
|
||||
id: 'grill-menu',
|
||||
title: 'Grill Menu',
|
||||
description: 'Lækker grillmenu perfekt til sommeraftener og uformelle selskaber.',
|
||||
courses: [
|
||||
'Grillede peberfrugter med hvidløgsdip',
|
||||
'Marineret kylling og grillet flanksteak',
|
||||
'Grillede ferskner med vaniljeis'
|
||||
],
|
||||
tags: ['Grill', 'Sommer', 'Afslappet'],
|
||||
image: '/img/grill.png',
|
||||
baseIngredientsPerCover: 60,
|
||||
ingredientsCostIndex: 1.0,
|
||||
baseLabourHours: 4,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 20,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 275
|
||||
},
|
||||
{
|
||||
id: 'nordisk-buffet',
|
||||
title: 'Nordisk Buffet',
|
||||
description: 'Moderne nordisk buffet med sæsonens bedste råvarer. God til større selskaber.',
|
||||
courses: [
|
||||
'Koldrøget laks og marinerede sild',
|
||||
'Frikadeller med nye kartofler',
|
||||
'Klassisk æblekage med flødeskum'
|
||||
],
|
||||
tags: ['Nordisk', 'Buffet', 'Traditionel'],
|
||||
image: '/img/nordisk.png',
|
||||
baseIngredientsPerCover: 65,
|
||||
ingredientsCostIndex: 1.0,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.04,
|
||||
minCovers: 25,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 250
|
||||
},
|
||||
{
|
||||
id: 'brunch-klassisk',
|
||||
title: 'Brunch Menu',
|
||||
description: 'Klassisk brunch buffet med alt det bedste til en god start på dagen.',
|
||||
courses: [
|
||||
'Røræg og bacon',
|
||||
'Croissanter og rundstykker',
|
||||
'Yoghurt med müsli og frisk frugt'
|
||||
],
|
||||
tags: ['Brunch', 'Morgenmad', 'Buffet'],
|
||||
image: '/img/brunch.png',
|
||||
baseIngredientsPerCover: 45,
|
||||
ingredientsCostIndex: 0.85,
|
||||
baseLabourHours: 3,
|
||||
incrementalLabourPerCover: 0.04,
|
||||
minCovers: 15,
|
||||
leadTimeDays: 5,
|
||||
basePricePerCover: 225
|
||||
},
|
||||
{
|
||||
id: 'tapas-menu',
|
||||
title: 'Tapas Menu',
|
||||
description: 'Spanske tapas og pinxtos. Perfekt til reception eller let aftensmad.',
|
||||
courses: [
|
||||
'Serrano skinke og manchego',
|
||||
'Tortilla og patatas bravas',
|
||||
'Churros med chokolade'
|
||||
],
|
||||
tags: ['Spansk', 'Tapas', 'Reception'],
|
||||
image: '/img/tapas.png',
|
||||
baseIngredientsPerCover: 50,
|
||||
ingredientsCostIndex: 0.95,
|
||||
baseLabourHours: 4,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 20,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 265
|
||||
},
|
||||
{
|
||||
id: 'gourmet-upgrade',
|
||||
title: 'Gourmet Menu',
|
||||
description: 'En eksklusiv 4-retters menu til de særlige lejligheder.',
|
||||
courses: [
|
||||
'Carpaccio af oksefilet med trøffelolie',
|
||||
'Jordskokkesuppe med chips',
|
||||
'Lammeculotte med sæsonens grønt',
|
||||
'Chokoladefondant med is'
|
||||
],
|
||||
tags: ['Gourmet', 'Special', 'Luksus'],
|
||||
featured: true,
|
||||
image: '/img/gourmet.png',
|
||||
baseIngredientsPerCover: 95,
|
||||
ingredientsCostIndex: 1.2,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.06,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 495
|
||||
},
|
||||
{
|
||||
id: 'nordisk-signatur',
|
||||
title: 'Nordisk Signatur',
|
||||
description: 'En gastronomisk rejse gennem Nordens skatte med 4 serveringer.',
|
||||
courses: [
|
||||
'Rimmet kammusling med æble og dild',
|
||||
'Jordskokkesuppe med trøffel og sprødt',
|
||||
'Grillet torsk med blomkål og brunet smør',
|
||||
'Brombærdessert med hvid chokolade'
|
||||
],
|
||||
tags: ['Nordisk', 'Fisk', 'Sæson', 'Signatur'],
|
||||
image: '/img/nordisk.png',
|
||||
baseIngredientsPerCover: 95,
|
||||
ingredientsCostIndex: 1.2,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.06,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 745
|
||||
},
|
||||
{
|
||||
id: 'italiensk-fest',
|
||||
title: 'Italiensk Fest',
|
||||
description: 'Det bedste fra det italienske køkken med 4 serveringer.',
|
||||
courses: [
|
||||
'Antipasti med charcuteri og lokale oste',
|
||||
'Hjemmelavet ravioli med ricotta og salvie',
|
||||
'Braiseret kalvekød med gremolata',
|
||||
'Tiramisu med mascarpone'
|
||||
],
|
||||
tags: ['Italiensk', 'Pasta', 'Gourmet'],
|
||||
image: '/img/italiensk.png',
|
||||
baseIngredientsPerCover: 85,
|
||||
ingredientsCostIndex: 1.1,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 695
|
||||
},
|
||||
{
|
||||
id: 'fransk-gourmet',
|
||||
title: 'Fransk Gourmet',
|
||||
description: 'Klassisk fransk kogekunst med moderne touch og 5 serveringer.',
|
||||
courses: [
|
||||
'Østers med vinaigrette og citron',
|
||||
'Foie gras med brioche og fignekompot',
|
||||
'Hummerbisque med cognac',
|
||||
'Confiteret andelår med pommes sarladaises',
|
||||
'Crème brûlée med bær'
|
||||
],
|
||||
tags: ['Fransk', 'Gourmet', 'Luksus'],
|
||||
image: '/img/fransk.png',
|
||||
baseIngredientsPerCover: 125,
|
||||
ingredientsCostIndex: 1.3,
|
||||
baseLabourHours: 6,
|
||||
incrementalLabourPerCover: 0.07,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 10,
|
||||
basePricePerCover: 895
|
||||
},
|
||||
{
|
||||
id: 'tapas-deluxe',
|
||||
title: 'Tapas Deluxe',
|
||||
description: 'En luksuriøs aften med spanske specialiteter og 7 serveringer.',
|
||||
courses: [
|
||||
'Jamon Iberico de Bellota',
|
||||
'Grillede rejer med hvidløg',
|
||||
'Blæksprutteragout i egen blæk',
|
||||
'Patatas bravas med aioli',
|
||||
'Pimientos de padrón',
|
||||
'Albóndigas i tomatsauce',
|
||||
'Churros med chokolade'
|
||||
],
|
||||
tags: ['Spansk', 'Tapas', 'Deleretter', 'Luksus'],
|
||||
image: '/img/tapas.png',
|
||||
baseIngredientsPerCover: 110,
|
||||
ingredientsCostIndex: 1.2,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.06,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 745
|
||||
},
|
||||
{
|
||||
id: 'julemenu-luksus',
|
||||
title: 'Julemenu de Luxe',
|
||||
description: 'En luksuriøs fortolkning af traditionel julemad med 4 serveringer.',
|
||||
courses: [
|
||||
'Hjemmelavet gravad laks med sennepsdressing',
|
||||
'Julesuppe med and og urter',
|
||||
'Confiteret andelår med rødkål og brunede kartofler',
|
||||
'Ris a la mande med kirsebærsauce og sprød mandel'
|
||||
],
|
||||
tags: ['Jul', 'Dansk', 'Traditionel', 'Luksus'],
|
||||
image: '/img/jul.png',
|
||||
baseIngredientsPerCover: 95,
|
||||
ingredientsCostIndex: 1.15,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.06,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 14,
|
||||
basePricePerCover: 695
|
||||
},
|
||||
{
|
||||
id: 'brunch-luksus',
|
||||
title: 'Luksus Brunch',
|
||||
description: 'Overdådig brunch med 8 serveringer og bobler.',
|
||||
courses: [
|
||||
'Velkomst med mousserende vin og snacks',
|
||||
'Røræg med trøffel og hjemmebagt brød',
|
||||
'Røget laks med ærtepuré',
|
||||
'Charcuteri og nordiske oste',
|
||||
'Sprøde croissanter og wienerbrød',
|
||||
'Yoghurt med granola og friske bær',
|
||||
'Pandekager med ahornsirup',
|
||||
'Mini-dessertanretning'
|
||||
],
|
||||
tags: ['Brunch', 'Morgenmad', 'Luksus'],
|
||||
image: '/img/brunch.png',
|
||||
baseIngredientsPerCover: 85,
|
||||
ingredientsCostIndex: 1.1,
|
||||
baseLabourHours: 4,
|
||||
incrementalLabourPerCover: 0.05,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 5,
|
||||
basePricePerCover: 545
|
||||
},
|
||||
{
|
||||
id: 'grill-luksus',
|
||||
title: 'Gourmet Grill Deluxe',
|
||||
description: 'Eksklusiv grillmenu med 5 serveringer perfekt til sommeraftener.',
|
||||
courses: [
|
||||
'Grillede jomfruhummere med urtemayonnaise',
|
||||
'Wagyu sliders med trøffel',
|
||||
'Grillet lammecarré med rosmarin',
|
||||
'Grillede grøntsager med romesco',
|
||||
'Grillede ferskner med mascarpone og honning'
|
||||
],
|
||||
tags: ['Grill', 'Sommer', 'Kød', 'Luksus'],
|
||||
image: '/img/grill.png',
|
||||
baseIngredientsPerCover: 120,
|
||||
ingredientsCostIndex: 1.3,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.06,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 845
|
||||
},
|
||||
{
|
||||
id: 'sushi-signatur',
|
||||
title: 'Signatur Sushi',
|
||||
description: 'Eksklusiv omakase-inspireret sushimenu med 6 serveringer.',
|
||||
courses: [
|
||||
'Toro nigiri med trøffel',
|
||||
'Laks tataki med ponzu',
|
||||
'Kammusling nigiri med yuzu',
|
||||
'Tempura rejer med wasabi-mayo',
|
||||
'Udvalg af maki rolls',
|
||||
'Matcha is med hvid chokolade'
|
||||
],
|
||||
tags: ['Japansk', 'Sushi', 'Fisk', 'Luksus'],
|
||||
image: '/img/sushi.png',
|
||||
baseIngredientsPerCover: 130,
|
||||
ingredientsCostIndex: 1.4,
|
||||
baseLabourHours: 6,
|
||||
incrementalLabourPerCover: 0.07,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 895
|
||||
},
|
||||
{
|
||||
id: 'fusion-menu',
|
||||
title: 'Asian Fusion',
|
||||
description: 'Moderne asiatisk fusion menu med 5 serveringer.',
|
||||
courses: [
|
||||
'Tuna tartare med wasabi og sesam',
|
||||
'Dumplings med and og hoisin',
|
||||
'Thai inspireret suppe med kokos',
|
||||
'Korean BBQ short ribs',
|
||||
'Yuzu pannacotta med mango'
|
||||
],
|
||||
tags: ['Asiatisk', 'Fusion', 'Modern'],
|
||||
image: '/img/fusion.png',
|
||||
baseIngredientsPerCover: 95,
|
||||
ingredientsCostIndex: 1.2,
|
||||
baseLabourHours: 5,
|
||||
incrementalLabourPerCover: 0.06,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 745
|
||||
}
|
||||
],
|
||||
packages: [
|
||||
{
|
||||
id: "basis",
|
||||
name: "Basis Pakke",
|
||||
menuId: "klassisk-3-retter",
|
||||
pricePerCover: 295,
|
||||
minCovers: 15,
|
||||
maxCovers: 30,
|
||||
includes: [
|
||||
"Menu planlægning",
|
||||
"Indkøb af råvarer",
|
||||
"Madlavning på lokation",
|
||||
"Let oprydning i køkken"
|
||||
],
|
||||
upsells: [
|
||||
{ id: "anretning", label: "Tallerkenanretning", priceFlat: 500 },
|
||||
{ id: "kaffe", label: "Kaffe/te", pricePerCover: 25 }
|
||||
],
|
||||
notes: "God basis løsning til familiefester. Maden serveres på fade, hvor gæsterne selv tager."
|
||||
},
|
||||
{
|
||||
id: "service",
|
||||
name: "Service Pakke",
|
||||
menuId: "klassisk-3-retter",
|
||||
pricePerCover: 395,
|
||||
minCovers: 20,
|
||||
maxCovers: 50,
|
||||
includes: [
|
||||
"Menu planlægning",
|
||||
"Indkøb af råvarer",
|
||||
"Madlavning på lokation",
|
||||
"Tallerkenanretning",
|
||||
"Professionel servering",
|
||||
"Fuld oprydning"
|
||||
],
|
||||
upsells: [
|
||||
{ id: "velkomst", label: "Velkomstdrink & snacks", pricePerCover: 45 },
|
||||
{ id: "vinmenu", label: "Vinmenu (3 glas)", pricePerCover: 225 },
|
||||
{ id: "kaffe", label: "Kaffe/te med sødt", pricePerCover: 35 }
|
||||
],
|
||||
notes: "Komplet løsning med personlig service og professionel servering."
|
||||
},
|
||||
{
|
||||
id: "gourmet",
|
||||
name: "Gourmet Pakke",
|
||||
menuId: "gourmet-upgrade",
|
||||
pricePerCover: 595,
|
||||
minCovers: 10,
|
||||
maxCovers: 30,
|
||||
includes: [
|
||||
"Menu planlægning",
|
||||
"Indkøb af kvalitetsråvarer",
|
||||
"Madlavning på lokation",
|
||||
"Tallerkenanretning",
|
||||
"Professionel servering",
|
||||
"Vinmenu (3 glas)",
|
||||
"Kaffe med sødt",
|
||||
"Fuld oprydning"
|
||||
],
|
||||
upsells: [
|
||||
{ id: "champagne", label: "Champagne velkomst", pricePerCover: 95 },
|
||||
{ id: "ekstravin", label: "Ekstra glas vin", pricePerCover: 75 },
|
||||
{ id: "natmad", label: "Let natmad", pricePerCover: 75 }
|
||||
],
|
||||
notes: "Eksklusiv løsning til de særlige anledninger med gourmetmenu og vinmenu inkluderet."
|
||||
}
|
||||
],
|
||||
};
|
||||
@@ -347,28 +347,6 @@ export const sampleData = {
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 845
|
||||
},
|
||||
{
|
||||
id: 'sushi-signatur',
|
||||
title: 'Signatur Sushi',
|
||||
description: 'Eksklusiv omakase-inspireret sushimenu med 6 serveringer.',
|
||||
courses: [
|
||||
'Toro nigiri med trøffel',
|
||||
'Laks tataki med ponzu',
|
||||
'Kammusling nigiri med yuzu',
|
||||
'Tempura rejer med wasabi-mayo',
|
||||
'Udvalg af maki rolls',
|
||||
'Matcha is med hvid chokolade'
|
||||
],
|
||||
tags: ['Japansk', 'Sushi', 'Fisk', 'Luksus'],
|
||||
image: '/img/sushi.png',
|
||||
baseIngredientsPerCover: 130,
|
||||
ingredientsCostIndex: 1.4,
|
||||
baseLabourHours: 6,
|
||||
incrementalLabourPerCover: 0.07,
|
||||
minCovers: 10,
|
||||
leadTimeDays: 7,
|
||||
basePricePerCover: 895
|
||||
},
|
||||
{
|
||||
id: 'fusion-menu',
|
||||
title: 'Asian Fusion',
|
||||
@@ -411,6 +389,44 @@ export const sampleData = {
|
||||
],
|
||||
notes: "God basis løsning til familiefester. Maden serveres på fade, hvor gæsterne selv tager."
|
||||
},
|
||||
{
|
||||
id: "basis-gron",
|
||||
name: "Basis Pakke",
|
||||
menuId: "gron-3-retter",
|
||||
pricePerCover: 275,
|
||||
minCovers: 15,
|
||||
maxCovers: 30,
|
||||
includes: [
|
||||
"Menu planlægning",
|
||||
"Indkøb af råvarer",
|
||||
"Madlavning på lokation",
|
||||
"Let oprydning i køkken"
|
||||
],
|
||||
upsells: [
|
||||
{ id: "anretning", label: "Tallerkenanretning", priceFlat: 500 },
|
||||
{ id: "kaffe", label: "Kaffe/te", pricePerCover: 25 }
|
||||
],
|
||||
notes: "God basis løsning til familiefester. Maden serveres på fade, hvor gæsterne selv tager."
|
||||
},
|
||||
{
|
||||
id: "basis-italiensk",
|
||||
name: "Basis Pakke",
|
||||
menuId: "italiensk-aften",
|
||||
pricePerCover: 250,
|
||||
minCovers: 15,
|
||||
maxCovers: 30,
|
||||
includes: [
|
||||
"Menu planlægning",
|
||||
"Indkøb af råvarer",
|
||||
"Madlavning på lokation",
|
||||
"Let oprydning i køkken"
|
||||
],
|
||||
upsells: [
|
||||
{ id: "anretning", label: "Tallerkenanretning", priceFlat: 500 },
|
||||
{ id: "kaffe", label: "Kaffe/te", pricePerCover: 25 }
|
||||
],
|
||||
notes: "God basis løsning til familiefester. Maden serveres 'family style' på fade."
|
||||
},
|
||||
{
|
||||
id: "service",
|
||||
name: "Service Pakke",
|
||||
@@ -458,4 +474,4 @@ export const sampleData = {
|
||||
notes: "Eksklusiv løsning til de særlige anledninger med gourmetmenu og vinmenu inkluderet."
|
||||
}
|
||||
],
|
||||
} as const
|
||||
}
|
||||
Reference in New Issue
Block a user