Files
ladill-pos/resources/views/pos/tickets/show.blade.php
T
isaaccladandCursor 3ea9fdfe33
Deploy Ladill POS / deploy (push) Successful in 30s
Replace Ladill Pay POS checkouts with merchant gateways.
Register Card/MoMo payments through merchant Paystack, Flutterwave, or Hubtel settings so takings settle 100% to the business.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-15 01:26:22 +00:00

210 lines
15 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,
'totalMinor' => $sale->total_minor,
'currency' => $sale->currency,
'courseLabels' => $courses,
]))" 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 x-show="categories.length" class="mb-3 flex flex-wrap gap-1.5">
<button type="button" @click="activeCategory = ''" :class="activeCategory === '' ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-600'"
class="rounded-full px-3 py-1 text-xs font-medium">All</button>
<template x-for="c in categories" :key="c">
<button type="button" @click="activeCategory = c" :class="activeCategory === c ? 'bg-indigo-600 text-white' : 'bg-slate-100 text-slate-600'"
class="rounded-full px-3 py-1 text-xs font-medium" x-text="c"></button>
</template>
</div>
<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="tap(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>
<span x-show="p.modifier_groups.length" class="mt-1 inline-block text-[10px] font-medium text-indigo-500">options</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 x-show="line.modifiers.length" x-cloak class="text-xs text-slate-500" x-text="line.modifiers.join(', ')"></p>
<p x-show="line.notes" x-cloak class="text-xs text-rose-600" x-text="line.notes"></p>
<div class="mt-1 flex items-center gap-1.5">
<span x-show="line.course" x-cloak class="rounded-full bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-500" x-text="courseLabel(line.course)"></span>
<span x-show="line.source === 'guest'" x-cloak class="rounded-full bg-violet-50 px-1.5 py-0.5 text-[10px] font-medium text-violet-700">Guest</span>
<span x-show="line.fired" x-cloak class="rounded-full bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium text-amber-700">In kitchen</span>
</div>
</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-1 flex items-center justify-between text-sm text-slate-500">
<span>Total</span><span x-text="money(totalMinor)"></span>
</div>
@if($paidMinor > 0)
<div class="mb-1 flex items-center justify-between text-sm text-emerald-600">
<span>Paid</span><span x-text="'' + money(paidMinor)"></span>
</div>
@endif
<div class="mb-3 flex items-center justify-between text-base font-semibold text-slate-900">
<span x-text="paidMinor > 0 ? 'Balance' : 'Total'"></span>
<span x-text="money(balanceMinor)"></span>
</div>
@if($payments->isNotEmpty())
<div class="mb-3 space-y-1 rounded-xl bg-slate-50 p-2">
@foreach($payments as $p)
<div class="flex items-center justify-between text-xs text-slate-400">
<span>{{ ucfirst($p->method) }} · {{ $p->paid_at?->format('H:i') }}</span>
<span>{{ $sale->currency }} {{ number_format($p->amount_minor / 100, 2) }}</span>
</div>
@endforeach
</div>
@endif
{{-- Fire to kitchen (all, or by course when more than one course is waiting) --}}
<div x-show="hasNew" x-cloak class="mb-2 space-y-1.5">
<button type="button" @click="send()" :disabled="busy"
class="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 all to kitchen
</button>
<template x-if="newCourses.length > 1">
<div class="flex flex-wrap gap-1.5">
<template x-for="c in newCourses" :key="c">
<button type="button" @click="send(c)" :disabled="busy"
class="rounded-lg border border-amber-200 bg-white px-2.5 py-1 text-xs font-medium text-amber-700 hover:bg-amber-50"
x-text="'Fire ' + courseLabel(c)"></button>
</template>
</div>
</template>
</div>
<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="flex items-center gap-2">
<input type="number" step="0.01" min="0.01" name="amount" x-model="settleAmount"
class="w-full rounded-xl border-slate-200 text-sm focus:border-indigo-500 focus:ring-indigo-500"
placeholder="Full balance">
<div class="flex shrink-0 gap-1">
<button type="button" @click="fullAmount()" class="rounded-lg border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-50">Full</button>
<button type="button" @click="splitAmount(2)" class="rounded-lg border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-50">½</button>
<button type="button" @click="splitAmount(3)" class="rounded-lg border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-50"></button>
<button type="button" @click="splitAmount(4)" class="rounded-lg border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 hover:bg-slate-50">¼</button>
</div>
</div>
<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">Card / MoMo</button>
</div>
<p class="text-center text-[11px] text-slate-400">Pay the full balance, or a partial amount to split the bill.</p>
</form>
</div>
</div>
</div>
{{-- Modifier picker --}}
<div x-show="picker.open" x-cloak class="fixed inset-0 z-50 flex items-end justify-center bg-slate-900/40 p-0 sm:items-center sm:p-4" @click.self="closePicker()">
<div class="w-full max-w-md overflow-hidden rounded-t-2xl bg-white shadow-xl sm:rounded-2xl">
<div class="flex items-center justify-between border-b border-slate-100 px-5 py-3">
<p class="text-sm font-semibold text-slate-900" x-text="picker.product?.name"></p>
<button @click="closePicker()" class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-100">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="max-h-[60vh] space-y-4 overflow-y-auto px-5 py-4">
<template x-for="g in picker.groups" :key="g.id">
<div>
<p class="text-xs font-semibold text-slate-600">
<span x-text="g.name"></span>
<span class="font-normal text-slate-400" x-text="'(choose ' + g.min + (g.max ? '' + g.max : '+') + ')'"></span>
</p>
<div class="mt-2 flex flex-wrap gap-2">
<template x-for="m in g.modifiers" :key="m.id">
<button type="button" @click="toggleModifier(g, m)"
:class="isSelected(g, m) ? 'border-indigo-500 bg-indigo-50 text-indigo-700' : 'border-slate-200 text-slate-600'"
class="rounded-lg border px-3 py-1.5 text-sm font-medium">
<span x-text="m.name"></span>
<span x-show="m.price_delta_minor" class="text-xs opacity-70"
x-text="(m.price_delta_minor > 0 ? ' +' : ' ') + money(m.price_delta_minor).replace(currency + ' ', '')"></span>
</button>
</template>
</div>
</div>
</template>
<div>
<label class="text-xs font-semibold text-slate-600">Course</label>
<select x-model="picker.course" class="mt-1 w-full rounded-xl border-slate-200 text-sm focus:border-indigo-500 focus:ring-indigo-500">
<option value="">No course</option>
@foreach($courses as $key => $label)
<option value="{{ $key }}">{{ $label }}</option>
@endforeach
</select>
</div>
<div>
<label class="text-xs font-semibold text-slate-600">Notes</label>
<input type="text" x-model="picker.notes" maxlength="200" placeholder="e.g. no onions"
class="mt-1 w-full rounded-xl border-slate-200 text-sm focus:border-indigo-500 focus:ring-indigo-500">
</div>
</div>
<div class="flex items-center justify-between gap-3 border-t border-slate-100 px-5 py-3">
<span class="text-sm font-semibold text-slate-900" x-text="money(pickerPrice)"></span>
<button type="button" @click="confirmPicker()" :disabled="! pickerValid || busy" class="btn-primary disabled:opacity-40">Add to ticket</button>
</div>
</div>
</div>
</div>
</x-app-layout>