Files
ladill-pos/resources/views/pos/tickets/show.blade.php
T
isaaccladandClaude Opus 4.8 d9e4b6e06e
Deploy Ladill POS / deploy (push) Successful in 30s
Add restaurant/café mode: floor, open tabs, and kitchen display (Phase 1)
Gated by a per-location service_style (retail | restaurant). Restaurant mode adds:
- Floor screen with tables (areas, seats, status) — tap a free table to open a
  dine-in tab; takeaway/counter orders open from the same screen.
- Open tickets (tabs): a sale stays pending while items are added; lines persist
  as you go (posTicket Alpine component posts each change to the server).
- Send-to-kitchen fires un-sent lines; a polling Kitchen Display (KDS) shows
  active tickets and bumps items queued → preparing → ready → served.
- Settlement reuses the existing cash / Ladill Pay flow; paying closes the tab
  and frees the table (PosSaleService::closeTicket, wired into both pay paths).
- Settings gains the mode toggle and a tables manager.

Schema is additive (new pos_tables; service_style on locations; order/kitchen
columns on sales + lines). Retail flow is untouched. Sidebar surfaces Floor +
Kitchen only in restaurant mode. New PosRestaurantTest covers the dine-in
lifecycle end to end; suite green (10 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 08:38:18 +00:00

98 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<x-app-layout title="Ticket">
<div x-data="posTicket(@js([
'base' => route('pos.tickets.show', $sale),
'lines' => $lines,
'products' => $products->map(fn ($p) => ['id' => $p->id, 'name' => $p->name, 'price_minor' => $p->price_minor])->values()->all(),
'totalMinor' => $sale->total_minor,
'currency' => $sale->currency,
]))" class="space-y-4">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<a href="{{ route('pos.floor') }}" class="inline-flex items-center gap-1 text-sm text-slate-500 hover:text-slate-700">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/></svg>
Floor
</a>
<h1 class="mt-1 text-lg font-semibold text-slate-900">
{{ $sale->table?->label ?? ucfirst(str_replace('_', ' ', $sale->order_type)) }}
</h1>
<p class="text-sm text-slate-500 capitalize">{{ str_replace('_', ' ', $sale->order_type) }}{{ $sale->covers ? ' · '.$sale->covers.' covers' : '' }} · {{ $sale->reference }}</p>
</div>
</div>
<p x-show="flash" x-text="flash" x-cloak class="rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-2 text-sm text-indigo-700"></p>
<div class="grid gap-4 lg:grid-cols-[1fr_22rem]">
{{-- Catalog --}}
<div class="rounded-2xl border border-slate-200 bg-white p-4">
<input type="search" x-model="search" placeholder="Search items…"
class="mb-3 w-full rounded-xl border-slate-200 text-sm focus:border-indigo-500 focus:ring-indigo-500">
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
<template x-for="p in filteredProducts" :key="p.id">
<button type="button" @click="addProduct(p)" :disabled="busy"
class="rounded-xl border border-slate-200 p-3 text-left transition hover:border-indigo-300 hover:bg-indigo-50/50 disabled:opacity-50">
<span class="block text-sm font-medium text-slate-900" x-text="p.name"></span>
<span class="mt-1 block text-xs text-slate-500" x-text="money(p.price_minor)"></span>
</button>
</template>
<template x-if="filteredProducts.length === 0">
<p class="col-span-full py-6 text-center text-sm text-slate-400">No products. Add some under Products.</p>
</template>
</div>
</div>
{{-- Ticket --}}
<div class="flex flex-col rounded-2xl border border-slate-200 bg-white">
<div class="flex-1 space-y-2 p-4">
<template x-if="lines.length === 0">
<p class="py-8 text-center text-sm text-slate-400">No items yet. Tap a product to add it.</p>
</template>
<template x-for="line in lines" :key="line.id">
<div class="rounded-xl border border-slate-100 p-2.5">
<div class="flex items-start justify-between gap-2">
<div class="min-w-0">
<p class="truncate text-sm font-medium text-slate-900" x-text="line.name"></p>
<p class="text-xs text-slate-400" x-text="money(line.unit_price_minor)"></p>
<span x-show="line.fired" x-cloak class="mt-1 inline-block rounded-full bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium text-amber-700">In kitchen</span>
</div>
<div class="text-right">
<p class="text-sm font-semibold text-slate-900" x-text="money(line.line_total_minor)"></p>
<div class="mt-1 flex items-center justify-end gap-1.5">
<button type="button" @click="dec(line)" :disabled="busy" class="flex h-6 w-6 items-center justify-center rounded-md border border-slate-200 text-slate-600 hover:bg-slate-50"></button>
<span class="w-5 text-center text-sm" x-text="line.quantity"></span>
<button type="button" @click="inc(line)" :disabled="busy" class="flex h-6 w-6 items-center justify-center rounded-md border border-slate-200 text-slate-600 hover:bg-slate-50">+</button>
</div>
</div>
</div>
</div>
</template>
</div>
<div class="border-t border-slate-100 p-4">
<div class="mb-3 flex items-center justify-between text-base font-semibold text-slate-900">
<span>Total</span>
<span x-text="money(totalMinor)"></span>
</div>
<button type="button" @click="send()" :disabled="busy || ! hasNew"
class="mb-2 w-full rounded-xl border border-amber-300 bg-amber-50 px-4 py-2.5 text-sm font-semibold text-amber-800 transition hover:bg-amber-100 disabled:opacity-40">
Send to kitchen
</button>
<form method="post" action="{{ route('pos.tickets.settle', $sale) }}" class="space-y-2">
@csrf
<input type="text" name="customer_name" placeholder="Customer name (optional)"
class="w-full rounded-xl border-slate-200 text-sm focus:border-indigo-500 focus:ring-indigo-500">
<div class="grid grid-cols-2 gap-2">
<button name="payment_method" value="cash" :disabled="lines.length === 0"
class="rounded-xl border border-slate-200 bg-white px-3 py-2.5 text-sm font-semibold text-slate-700 hover:bg-slate-50 disabled:opacity-40">Cash</button>
<button name="payment_method" value="pay" :disabled="lines.length === 0"
class="btn-primary disabled:opacity-40">Ladill Pay</button>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>