@extends('layouts.dashboard') @section('content') @php use App\Models\Order; /** @var iterable $orders @var array $statuses @var iterable $drivers @var array $driverNames @var array $itemCounts @var array $pager @var array $filters @var array $totals */ $drivers = $drivers ?? []; $driverNames = $driverNames ?? []; $driverAvatars = $driverAvatars ?? []; $itemCounts = $itemCounts ?? []; $pager = $pager ?? null; $filters = $filters ?? []; $totals = $totals ?? ['count' => 0, 'total' => 0.0, 'paid_total' => 0.0, 'by_tender' => []]; $f = static fn (string $k): string => (string) ($filters[$k] ?? ''); $hasFilters = trim(implode('', array_map($f, ['date_from', 'date_to', 'status', 'payment_method', 'payment_status', 'q']))) !== ''; // Preserve the active filters on the export link. $qs = http_build_query(array_filter([ 'date_from' => $f('date_from'), 'date_to' => $f('date_to'), 'status' => $f('status'), 'payment_method' => $f('payment_method'), 'payment_status' => $f('payment_status'), 'q' => $f('q'), ], static fn ($v) => $v !== '')); // Labels the payment-method filter offers (the per-order payment column was removed; // filtering by how an order was paid is still useful even when it isn't a column). // Stripe is named rather than folded into "Card": offline already carries that label, so the // select drew two options both reading "Card" and neither said which tender it would pick. // Same naming the tender strip below and the shift report use for these two keys. $methodLabel = ['cod' => t_raw('status.payment_method.cash'), 'offline' => t_raw('status.payment_method.card'), 'stripe' => 'Stripe', 'paypal' => 'PayPal']; // The totals strip reads the payment LEDGER, whose tender keys differ from // orders.payment_method (a split bill can be cash + card). $tenderLabel = ['cash' => t_raw('status.payment_method.cash'), 'card' => t_raw('status.payment_method.card'), 'offline' => t_raw('dashboard.status_offline'), 'stripe' => 'Stripe', 'paypal' => 'PayPal', 'other' => t_raw('status.payment_method.other')]; // Managers run the floor but do not see the owner's takings reconciliation // (the Collected/by-tender strip). $showMoney = $showMoney ?? true; $statusOpts = ['pending', 'confirmed', 'preparing', 'out_for_delivery', 'delivered', 'cancelled']; $kpiSparks = $kpiSparks ?? []; // Per-tender takings-card icon + accent, so this strip mirrors the analytics KPIs. $tenderIcon = ['cash' => 'fa-money-bill-wave', 'card' => 'fa-credit-card', 'offline' => 'fa-credit-card', 'stripe' => 'fa-credit-card', 'paypal' => 'fa-wallet', 'other' => 'fa-wallet']; $tenderAccent = ['cash' => 'teal', 'card' => 'violet', 'offline' => 'violet', 'stripe' => 'blue', 'paypal' => 'blue', 'other' => 'slate']; // Where the order was raised, and how it is being served — the two facts that decide // which part of the operation owns it. $sourceLabel = ['website' => t_raw('status.order_source.website'), 'kiosk' => t_raw('status.order_source.kiosk'), 'pos' => t_raw('status.order_source.pos'), 'phone' => t_raw('status.order_source.phone')]; $sourceIcon = ['website' => 'fa-globe', 'kiosk' => 'fa-tablet-alt', 'pos' => 'fa-cash-register', 'phone' => 'fa-phone']; $modeLabel = ['delivery' => t_raw('common.delivery'), 'pickup' => t_raw('common.pickup'), 'dine_in' => t_raw('common.dine_in')]; $refunds = $refunds ?? null; $canRefund = $canRefund ?? false; // THE CONTROL MUST ASK THE GUARD'S OWN QUESTION — the same mistake the refund control // carried. The Export CSV button below asked $showMoney (finance.view, the financial READ // privilege) while the endpoint it points at is guarded by 'orders.export'. Those are two // separately grantable privileges in the role grid, so a role composed with the export right // but not the figures was offered no button at all, and one composed the other way round was // offered a button the endpoint then refuses. $canExport = $canExport ?? \App\Support\Auth::can('orders.export'); // Fulfilment is judged against the kitchen's own target, so "42 min" reads as fast or // slow without the operator holding the target in their head. $prepTarget = max(1, (int) ($restaurant['avg_prep_time'] ?? 30)); @endphp @php /* .qm-d2: the dashboard density standard (app.css §35) */ @endphp
| {{ t_raw('vendor.orders.col_order') }} | {{ t_raw('common.customer') }} | {{ t_raw('common.total') }} | {{ t_raw('vendor.orders.col_placed') }} | {{ t_raw('common.status') }} | {{ t_raw('vendor.orders.col_rider') }} | {{ t_raw('common.actions') }} |
|---|---|---|---|---|---|---|
|
{{ $o['order_number'] }}
@if (!empty($o['first_item_name']))
@php $more = (int) ($itemCounts[(int) $o['id']] ?? 0) - 1; @endphp{{ (string) $o['first_item_name'] }}{!! $more > 0 ? ' ' . t('vendor.orders.n_more', [':n' => $more]) : '' !!}@endif
@php
// Who raised the order and how it is being served — the two facts that
// decide which part of the operation owns it — plus the line count.
$src = (string) ($o['source'] ?? 'website');
$mode = (string) ($o['service_mode'] ?? 'delivery');
$ic = (int) ($itemCounts[(int) $o['id']] ?? 0);
@endphp
{{ $sourceLabel[$src] ?? $src }} {{ $modeLabel[$mode] ?? $mode }} · {{ $ic }} {{ t_raw($ic === 1 ? 'vendor.orders.item_singular' : 'vendor.orders.item_plural') }}
@if (!empty($o['loyalty_reward_label'])){{ t_raw('vendor.orders.reward_prefix') }}{{ (string) $o['loyalty_reward_label'] }}@endif
@if (!empty($o['scheduled_at'])) {!! fmt_datetime($o['scheduled_at']) !!}@endif
|
{!! brandmark_html($o['customer_avatar'] ?? null, (string) $o['customer_name'], 'md', 'qm-brandmark--round') !!}
{{ $o['customer_name'] }}
@if (!empty($o['customer_phone'])){{ $o['customer_phone'] }}@endif
|
{{ money($o['total']) }} | @php /* Placed, then how long it took — the duration is measured from this very timestamp, so the two belong in one cell. Fulfilment is judged against the kitchen's own target so the number carries a verdict, not just a duration; an order still in flight has none yet. */ @endphp @php $fm = $o['fulfilment_minutes'] ?? null; @endphp
{!! fmt_datetime($o['placed_at']) !!}
@if ($fm !== null)
@php $tone = $fm <= $prepTarget ? 'green' : ($fm <= $prepTarget * 1.5 ? 'amber' : 'red'); @endphp
{{ t_raw('vendor.orders.minutes_value', [':n' => number_format($fm)]) }}
@endif
|
{{ Order::statusLabel($o['status'], $o['service_mode'] ?? 'delivery') }} |
@php
$terminalOrder = in_array($o['status'], ['delivered', 'cancelled'], true);
$assignedId = (int) ($o['driver_user_id'] ?? 0);
@endphp
@if (!$isDelivery)
{!! dash() !!}
@else
@if (!$terminalOrder)
@php /* ONE control, not three labels: the avatar dropdown IS the state.
Its selection names the rider (or Unassigned), the rider's photo
rides in the closed control and in the list (the same decoration
the cuisine picker's flags use), and choosing submits by itself —
an assignment made in a dropdown needs no Assign button. */
@endphp
@elseif ($assignedId)
@php /* Settled orders: the rider is a fact now, not a control. */
@endphp
|
@php /* Actions read the same here as on the admin orders table: the one thing
done constantly stays inline, everything else folds behind the "⋮". Advancing
an order used to be a select plus a Save press; each reachable status is now
its own entry, so the common move costs one click and the states the machine
forbids are simply absent rather than offered and rejected. */
@endphp
@php
$advance = [];
foreach (Order::nextStatuses($o['status']) as $s) {
if ($s === $o['status']) { continue; } // the status it already holds is not a move
$advance[] = [
'label' => Order::statusLabel($s, $o['service_mode'] ?? 'delivery'),
'icon' => $s === 'cancelled' ? 'fa-ban' : 'fa-arrow-right',
'danger' => $s === 'cancelled',
'form' => [
'action' => '/vendor/orders/status',
'fields' => ['order_id' => (int) $o['id'], 'status' => $s],
],
];
}
@endphp
{!! partial('row-actions', ['actions' => array_merge( [['label' => t_raw('common.view'), 'icon' => 'fa-eye', 'href' => '/vendor/orders/' . (int) $o['id'], 'primary' => true]], /* A delivered order's journey is OVER: its slot shows a green check — a state mark, not a control — where an active row shows its next-move arrow (dimmed until hovered; see the actions-cell CSS). */ $o['status'] === 'delivered' ? [[ 'label' => Order::statusLabel('delivered', $o['service_mode'] ?? 'delivery'), 'icon' => 'fa-check', 'disabled' => true, 'attrs' => ['data-status-done' => '1'], ]] : [], $advance, [ /* Refund without cancelling. A completed order that went wrong still has to be made good, and forcing the operator to cancel it to move the money would falsify the service record. Owner-only, and only where there is captured money to return. */ ($canRefund && (string) $o['payment_status'] === 'paid' && (float) $o['total'] > 0) ? [ 'label' => t_raw('vendor.orders.refund_btn'), 'icon' => 'fa-undo', 'form' => [ 'action' => '/vendor/orders/status', 'fields' => ['intent' => 'refund', 'order_id' => (int) $o['id']], 'confirm' => t_raw('vendor.orders.confirm_refund', [':amount' => money($o['total']), ':number' => $o['order_number']]), ], ] : null, ] )]) !!} |