Deploy Ladill POS / deploy (push) Successful in 35s
Builds on Phase 1 with the menu structure restaurants need: - Menu setup page (restaurant mode): categories, kitchen stations, and modifier groups with options (price deltas). - Products gain category, kitchen station, default course, and attached modifier groups (managed on the product form). - Ordering: the ticket groups the catalog by category and, when a product has modifier groups, opens a picker (respects min/max select) for options + notes + course before adding. Effective price = base + modifier deltas (resolved server-side; client prices are never trusted). - Fire-by-course: send the whole tab or just one course to the kitchen. - KDS: filter by station; each item shows its station, course, modifiers and notes. Schema additive (pos_categories, pos_stations, pos_modifier_groups, pos_modifiers, product↔group pivot, pos_sale_line_modifiers; category/station/course columns on products and lines). PosRestaurantTest covers modifiers, course and station routing; suite green (11 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
129 lines
8.1 KiB
PHP
129 lines
8.1 KiB
PHP
<x-app-layout title="Menu setup">
|
||
<div class="mx-auto max-w-3xl space-y-6">
|
||
<div>
|
||
<h1 class="text-lg font-semibold text-slate-900">Menu setup</h1>
|
||
<p class="mt-1 text-sm text-slate-500">Categories, kitchen stations, and modifier options for your products.</p>
|
||
</div>
|
||
|
||
<div class="grid gap-6 sm:grid-cols-2">
|
||
{{-- Categories --}}
|
||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||
<h2 class="text-sm font-semibold text-slate-900">Categories</h2>
|
||
<p class="mt-1 text-xs text-slate-400">Group products on the register (e.g. Coffee, Food).</p>
|
||
<form method="POST" action="{{ route('pos.menu.categories.store') }}" class="mt-3 flex gap-2">
|
||
@csrf
|
||
<input type="text" name="name" placeholder="New category" required
|
||
class="flex-1 rounded-xl border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<button class="btn-primary">Add</button>
|
||
</form>
|
||
<ul class="mt-3 divide-y divide-slate-100">
|
||
@forelse ($categories as $category)
|
||
<li class="flex items-center justify-between py-2 text-sm">
|
||
<span class="text-slate-800">{{ $category->name }}</span>
|
||
<form method="POST" action="{{ route('pos.menu.categories.destroy', $category) }}" onsubmit="return confirm('Remove category?');">
|
||
@csrf @method('DELETE')
|
||
<button class="text-xs font-medium text-red-500 hover:text-red-700">Remove</button>
|
||
</form>
|
||
</li>
|
||
@empty
|
||
<li class="py-2 text-xs text-slate-400">No categories yet.</li>
|
||
@endforelse
|
||
</ul>
|
||
</section>
|
||
|
||
{{-- Stations --}}
|
||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||
<h2 class="text-sm font-semibold text-slate-900">Kitchen stations</h2>
|
||
<p class="mt-1 text-xs text-slate-400">Route items to a prep station (e.g. Kitchen, Bar).</p>
|
||
<form method="POST" action="{{ route('pos.menu.stations.store') }}" class="mt-3 flex gap-2">
|
||
@csrf
|
||
<input type="text" name="name" placeholder="New station" required
|
||
class="flex-1 rounded-xl border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<button class="btn-primary">Add</button>
|
||
</form>
|
||
<ul class="mt-3 divide-y divide-slate-100">
|
||
@forelse ($stations as $station)
|
||
<li class="flex items-center justify-between py-2 text-sm">
|
||
<span class="text-slate-800">{{ $station->name }}</span>
|
||
<form method="POST" action="{{ route('pos.menu.stations.destroy', $station) }}" onsubmit="return confirm('Remove station?');">
|
||
@csrf @method('DELETE')
|
||
<button class="text-xs font-medium text-red-500 hover:text-red-700">Remove</button>
|
||
</form>
|
||
</li>
|
||
@empty
|
||
<li class="py-2 text-xs text-slate-400">No stations yet.</li>
|
||
@endforelse
|
||
</ul>
|
||
</section>
|
||
</div>
|
||
|
||
{{-- Modifier groups --}}
|
||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||
<div class="flex items-start justify-between gap-3">
|
||
<div>
|
||
<h2 class="text-sm font-semibold text-slate-900">Modifier groups</h2>
|
||
<p class="mt-1 text-xs text-slate-400">Options like Size or Add-ons. Attach groups to products on the product form.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<form method="POST" action="{{ route('pos.menu.groups.store') }}" class="mt-3 grid gap-2 sm:grid-cols-[1fr_6rem_6rem_auto]">
|
||
@csrf
|
||
<input type="text" name="name" placeholder="Group name (e.g. Size)" required
|
||
class="rounded-xl border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<input type="number" name="min_select" value="0" min="0" max="20" title="Minimum to choose"
|
||
class="rounded-xl border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Min">
|
||
<input type="number" name="max_select" min="0" max="20" title="Maximum to choose (blank = any)"
|
||
class="rounded-xl border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500" placeholder="Max">
|
||
<button class="btn-primary">Add</button>
|
||
</form>
|
||
|
||
<div class="mt-4 space-y-4">
|
||
@forelse ($groups as $group)
|
||
<div class="rounded-xl border border-slate-200 p-4">
|
||
<div class="flex items-center justify-between">
|
||
<p class="text-sm font-semibold text-slate-900">
|
||
{{ $group->name }}
|
||
<span class="ml-1 text-xs font-normal text-slate-400">
|
||
choose {{ $group->min_select }}–{{ $group->max_select ?? 'any' }}
|
||
</span>
|
||
</p>
|
||
<form method="POST" action="{{ route('pos.menu.groups.destroy', $group) }}" onsubmit="return confirm('Remove group and its options?');">
|
||
@csrf @method('DELETE')
|
||
<button class="text-xs font-medium text-red-500 hover:text-red-700">Remove group</button>
|
||
</form>
|
||
</div>
|
||
|
||
<ul class="mt-2 divide-y divide-slate-100">
|
||
@foreach ($group->modifiers as $modifier)
|
||
<li class="flex items-center justify-between py-1.5 text-sm">
|
||
<span class="text-slate-700">
|
||
{{ $modifier->name }}
|
||
@if ($modifier->price_delta_minor !== 0)
|
||
<span class="text-xs text-slate-400">({{ $modifier->price_delta_minor > 0 ? '+' : '' }}{{ number_format($modifier->price_delta_minor / 100, 2) }})</span>
|
||
@endif
|
||
</span>
|
||
<form method="POST" action="{{ route('pos.menu.modifiers.destroy', $modifier) }}">
|
||
@csrf @method('DELETE')
|
||
<button class="text-xs text-red-500 hover:text-red-700">×</button>
|
||
</form>
|
||
</li>
|
||
@endforeach
|
||
</ul>
|
||
|
||
<form method="POST" action="{{ route('pos.menu.modifiers.store', $group) }}" class="mt-2 flex gap-2">
|
||
@csrf
|
||
<input type="text" name="name" placeholder="Option (e.g. Large)" required
|
||
class="flex-1 rounded-lg border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<input type="number" name="price_delta" step="0.01" placeholder="±price"
|
||
class="w-24 rounded-lg border-slate-300 text-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<button class="rounded-lg border border-slate-200 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-50">Add option</button>
|
||
</form>
|
||
</div>
|
||
@empty
|
||
<p class="text-xs text-slate-400">No modifier groups yet.</p>
|
||
@endforelse
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</x-app-layout>
|