@php /** * Shared order-detail body — the order summary, the customer and the * placement → delivery journey timeline, plus the delivery-route map when the * order carries both endpoints. Rendered by BOTH the vendor order page * (vendor/order-detail.php) and the admin order page (admin/order-detail.php); * each wrapper supplies its own back bar and page chrome. The view-model is * assembled once by App\Services\OrderDetail::build(). * * @var array $order @var array $items @var array $journey @var array $journeyStats * @var array|null $customer @var string $driverName @var array|null $restaurant @var array|null $deliveryRoute */ use App\Models\Order; $sm = (string) $order['service_mode']; $src = (string) ($order['source'] ?? 'website'); $srcMeta = ['website' => t_raw('common.website'), 'kiosk' => t_raw('vendor.order_detail.src_kiosk'), 'pos' => t_raw('vendor.order_detail.src_pos'), 'phone' => t_raw('common.phone')]; $srcLabel = $srcMeta[$src] ?? t_raw('common.website'); $modeMeta = ['delivery' => t_raw('common.delivery'), 'pickup' => t_raw('common.pickup'), 'dine_in' => t_raw('common.dine_in')]; $modeLabel = $modeMeta[$sm] ?? t_raw('common.delivery'); // A dine-in order names its TABLE beside the mode. The table travelled on the order and no // staff-facing screen showed it, so this page said "Dine in" and never said where. $tableLabel = (string) ($tableLabel ?? ''); if ($sm === 'dine_in' && $tableLabel !== '') { $modeLabel .= ' · ' . $tableLabel; } // A dine-in table also seats a PARTY: the covers ride the same line. if ($sm === 'dine_in' && (int) ($order['covers'] ?? 0) > 0) { $modeLabel .= ' · ' . (int) $order['covers'] . ' ' . t_raw('common.covers'); } $isDelivery = $sm === 'delivery'; $couponCode = (string) ($couponCode ?? ''); $payLabels = ['cod' => t_raw('status.payment_method.cod'), 'offline' => t_raw('vendor.order_detail.pay_card_offline'), 'stripe' => t_raw('vendor.billing.card_stripe_label'), 'paypal' => 'PayPal']; $payLabel = !empty($order['payment_ref']) ? (string) $order['payment_ref'] : ($payLabels[(string) $order['payment_method']] ?? ucfirst((string) $order['payment_method'])); $itemCount = count($items); // Compact duration label: minutes under an hour, else "1h 20m". $fmtMin = static function (int $m): string { if ($m < 60) { return t_raw('common.duration_minutes', [':n' => (string) $m]); } $h = intdiv($m, 60); $r = $m % 60; return $r > 0 ? t_raw('common.duration_hours_minutes', [':h' => (string) $h, ':m' => (string) $r]) : t_raw('common.duration_hours', [':n' => (string) $h]); }; @endphp

{{ t_raw('vendor.order_detail.heading_order') }} {{ t_raw($itemCount === 1 ? 'vendor.order_detail.item_count_singular' : 'vendor.order_detail.item_count_plural', [':n' => $itemCount]) }}

@foreach ($items as $it)
{{ (string) $it['item_name'] }}
@if (!empty($it['options_text']))
{{ (string) $it['options_text'] }}
@endif @if (!empty($it['line_discount_reason']))
{{ t_raw('common.discount') }}: {{ (string) $it['line_discount_reason'] }}
@endif
{{ (int) $it['quantity'] }} × {{ money((float) $it['unit_price']) }}
{{ money((float) $it['line_total']) }}
@endforeach
{{ t_raw('checkout.subtotal') }}{{ money((float) $order['subtotal']) }}
@if ((float) $order['discount'] > 0)
{{ t_raw('common.discount') }}{!! $couponCode !== '' ? ' · ' . e($couponCode) : '' !!}−{{ money((float) $order['discount']) }}
@if (!empty($order['manual_discount_reason']))
  {{ (string) $order['manual_discount_reason'] }}
@endif @endif @if ((float) $order['loyalty_discount'] > 0)
{{ (string) ($order['loyalty_reward_label'] ?: t_raw('vendor.order_detail.loyalty_reward_fallback')) }}−{{ money((float) $order['loyalty_discount']) }}
@endif @if ($isDelivery)
{{ t_raw('checkout.delivery_fee') }}{{ money((float) $order['delivery_fee']) }}
@endif @if ((float) $order['tax'] > 0)
{{ t_raw('checkout.tax') }}{{ money((float) $order['tax']) }}
@foreach (tax_breakdown($restaurant ?? null, (float) $order['tax']) as $tc)
  {{ $tc['label'] }}{{ money($tc['amount']) }}
@endforeach @endif @if ((float) $order['service_charge'] > 0)
{{ t_raw('checkout.service_charge') }}{{ money((float) $order['service_charge']) }}
@endif @if ((float) $order['tip_amount'] > 0)
{{ t_raw('checkout.tip') }}{{ money((float) $order['tip_amount']) }}
@endif
{{ t_raw('common.total') }}{{ money((float) $order['total']) }}
@php /* The platform's share OF that total — a statement, not a sum line. */ @endphp @if ((float) ($order['commission_amount'] ?? 0) > 0)
{{ t_raw('admin.restaurantdetail.commission') }} ({{ number_format((float) $order['commission_rate'], 1) }}%){{ money((float) $order['commission_amount']) }}
@endif

{{ t_raw('common.customer') }}

@php // Same round brandmark every other person is drawn with (the orders list // renders this very customer that way). A hand-rolled here used the // square 15px radius while the placeholder art is clipped to a circle, so // the grey backing showed through the corners at a different radius. @endphp {!! brandmark_html($customer['avatar'] ?? null, (string) $order['customer_name'], 'lg', 'qm-brandmark--round') !!}
{{ (string) $order['customer_name'] }}
@if (!empty($order['customer_email']))
{{ (string) $order['customer_email'] }}
@endif {!! $customer ? t('vendor.order_detail.registered_badge') : t('vendor.order_detail.guest_badge') !!}
@if ($isDelivery && !empty($order['delivery_address']))
{{ (string) $order['delivery_address'] }}
@endif @if (!empty($order['scheduled_at']))
{!! t_raw('order.track_scheduled_for', [':time' => fmt_datetime($order['scheduled_at'])]) !!}
@endif
{{ t_raw('common.phone') }}{!! or_dash($order['customer_phone'] ?? null) !!}
{{ t_raw('vendor.orders.col_placed') }}{!! fmt_datetime($order['placed_at']) !!}
{{ t_raw('vendor.order_detail.meta_fulfilment') }}{{ $modeLabel }}
{{ t_raw('vendor.order_detail.meta_channel') }}{{ $srcLabel }}
@php // The order's own status, pilled exactly as both list screens and the customer's // tracking page render it. Without it this page carried only the PAYMENT status // below, so a cancelled order read as a live one and its state was inferable, if // at all, only from the last journey checkpoint (whose wording differs, and which // is missing entirely on orders with no history rows). @endphp
{{ t_raw('common.status') }}{{ Order::statusLabel((string) $order['status'], $sm) }}
{{ t_raw('vendor.orders.col_payment') }}{{ $payLabel }} · {{ status_label('payment', (string) $order['payment_status']) }}
@if ($driverName !== '')
{{ t_raw('vendor.orders.col_rider') }}{{ $driverName }}
@endif @if ((int) ($order['loyalty_points_earned'] ?? 0) > 0)
{{ t_raw('vendor.order_detail.points_earned') }}+{{ number_format((int) $order['loyalty_points_earned']) }}
@endif @if ((int) ($order['loyalty_points_redeemed'] ?? 0) > 0)
{{ t_raw('vendor.loyalty.kpi_points_redeemed') }}{{ number_format((int) $order['loyalty_points_redeemed']) }}
@endif @if ((string) $order['status'] === 'cancelled' && !empty($order['cancel_reason']))
{{ t_raw('common.cancelled') }}{{ ucfirst(str_replace('_', ' ', (string) $order['cancel_reason'])) }}
@endif
@if (!empty($order['notes'])) @php /* What the customer actually asked for — carried on the order since day one, and never printed on the page that answers questions about it. The address-callout style keeps it visible. */ @endphp
{{ t_raw('common.notes') }}: {{ (string) $order['notes'] }}
@endif

{{ t_raw('vendor.order_detail.heading_journey') }}

@if ($journey)
    @php $ckLabel = [ 'pending' => t_raw('vendor.order_detail.ck_order_placed'), 'confirmed' => t_raw('vendor.order_detail.ck_restaurant_accepted'), 'preparing' => t_raw('vendor.order_detail.ck_kitchen_preparing'), 'out_for_delivery' => $isDelivery ? t_raw('vendor.order_detail.ck_picked_up') : t_raw('vendor.kitchen.col_ready'), 'delivered' => $isDelivery ? t_raw('status.order.delivered') : ($sm === 'pickup' ? t_raw('common.collected') : t_raw('common.completed')), 'cancelled' => t_raw('common.cancelled'), 'kiosk' => t_raw('vendor.order_detail.ck_kiosk'), // The POS settle path writes a 'payment' history row, which is not one // of the order's own statuses — without an entry here it fell through // to Order::statusLabel() and printed a bare key on every settled bill. 'payment' => t_raw('vendor.order_detail.ck_payment'), ]; $methodLabel = ['cash' => t_raw('status.payment_method.cash'), 'card' => t_raw('status.payment_method.card'), 'offline' => t_raw('status.payment_method.card'), 'stripe' => t_raw('vendor.billing.card_stripe_label'), 'paypal' => 'PayPal', 'other' => t_raw('status.payment_method.other')]; $channelIcon = ['kiosk' => 'fa-tablet-alt', 'pos' => 'fa-cash-register', 'phone' => 'fa-phone-alt', 'website' => 'fa-globe']; /* * Each checkpoint carries a face, never an empty dot: the restaurant's * brandmark for a storefront placement, the avatar of the person behind * the step for every other checkpoint, a channel glyph for an in-store / * phone placement, and a tinted tender/status icon only as a genuine * fallback (e.g. an online gateway capture that has no staff member). * A small corner badge keeps payment / refund / hand-off legible even * when a face is shown. */ $marker = static function (string $avatar, string $name, ?string $brandHtml, string $icon, string $tint, string $badgeIcon = '', string $badgeCls = ''): string { if ($brandHtml !== null) { return '' . $brandHtml . ''; } if ($avatar !== '') { $badge = $badgeIcon !== '' ? '' : ''; return '' . brandmark_html($avatar, $name, 'sm') . '' . $badge; } return ''; }; @endphp @foreach ($journey as $ev) @php $t = (int) $ev['t']; $gap = $ev['gapMin'] ?? null; $hot = !empty($ev['longest']) && $gap !== null && $gap > 0; $who = (string) ($ev['who'] ?? ''); $avatar = (string) ($ev['avatar'] ?? ''); switch ((string) $ev['kind']) { case 'assigned': $label = t_raw('vendor.order_detail.ev_assigned_to_rider', [':name' => $who]); // The rider's name is already in the label, so it is cleared from the // meta line below — but only AFTER the marker has used it for initials. $mk = $marker($avatar, $who, null, 'fa-motorcycle', 'rider', 'fa-motorcycle', 'b-rider'); $who = ''; break; case 'payment': $m = (string) $ev['method']; $label = t_raw('vendor.order_detail.ev_paid', [':amount' => money((float) $ev['amount']), ':method' => $methodLabel[$m] ?? ucfirst($m)]); $mk = $marker($avatar, $who, null, $m === 'cash' ? 'fa-money-bill-wave' : 'fa-credit-card', 'pay', 'fa-coins', 'b-pay'); break; case 'refund': $m = (string) $ev['method']; $label = t_raw('vendor.order_detail.ev_refunded', [':amount' => money((float) $ev['amount']), ':method' => $methodLabel[$m] ?? ucfirst($m)]); $mk = $marker($avatar, $who, null, 'fa-undo', 'ref', 'fa-undo', 'b-ref'); break; case 'status': default: $st = (string) ($ev['status'] ?? ''); if ($st === 'pending' || $st === 'kiosk') { // Placement: storefront orders show the restaurant's brandmark; // in-store / phone orders show the channel they came through. $label = ($ckLabel[$st] ?? t_raw('vendor.order_detail.ck_order_placed')) . ' · ' . t_raw('vendor.order_detail.via_channel', [':channel' => $srcLabel]); $mk = $src === 'website' ? $marker('', '', restaurant_logo_html($restaurant ?? [], 'sm'), '', 'brand') : $marker('', '', null, $channelIcon[$src] ?? 'fa-globe', 's'); } elseif ($st === 'cancelled') { $label = $ckLabel['cancelled']; $mk = $marker($avatar, $who, null, 'fa-times-circle', 'ref', 'fa-times', 'b-ref'); } else { $label = $ckLabel[$st] ?? Order::statusLabel($st, $sm); $mk = $marker($avatar, $who, null, 'fa-check-circle', 's'); } }; @endphp {{ $mk }}
    {{ $label }}@if ($hot) {{ t_raw('vendor.order_detail.longest_wait_badge') }}@endif
    {!! fmt_datetime($t) !!}@if ($gap !== null && $gap > 0) · +{{ t_raw('common.duration_minutes', [':n' => (int) $gap]) }}@endif @if ($who !== '') · {{ t_raw('vendor.order_detail.by_person', [':name' => $who]) }}@endif
    @endforeach
{!! $fmtMin((int) $journeyStats['totalMin']) !!}{{ t_raw('vendor.order_detail.stat_total_time') }}
{{ (int) $journeyStats['checkpoints'] }}{{ t_raw('vendor.order_detail.stat_checkpoints') }}
{{ (int) $journeyStats['people'] }}{{ t_raw('vendor.order_detail.stat_people_involved') }}
@else

{{ t_raw('vendor.order_detail.no_journey_events') }}

@endif
@if ($deliveryRoute) @php // Same brandmark on the map pin and in the caption: the restaurant's logo for pickup, // the customer's photo for drop-off, each falling back to an initials monogram when no // image exists — reusing the shared brandmark helper, no bespoke logic. $pickupBrand = restaurant_logo_html($restaurant ?? [], 'md'); $dropoffBrand = brandmark_html($customer['avatar'] ?? null, (string) $deliveryRoute['dropoff']['name'], 'md'); $routeMarkers = [ ['lat' => $deliveryRoute['pickup']['lat'], 'lng' => $deliveryRoute['pickup']['lng'], 'label' => $deliveryRoute['pickup']['name'] . ' · ' . t_raw('common.pickup'), 'iconHtml' => $pickupBrand], ['lat' => $deliveryRoute['dropoff']['lat'], 'lng' => $deliveryRoute['dropoff']['lng'], 'label' => $deliveryRoute['dropoff']['name'] . ' · ' . t_raw('vendor.order_detail.dropoff_label'), 'iconHtml' => $dropoffBrand], ]; @endphp

{{ t_raw('vendor.order_detail.heading_delivery_route') }} {{ \App\Services\Distance::format((float) $deliveryRoute['km']) }} · {{ t_raw('vendor.order_detail.straight_line_suffix') }}

{!! $pickupBrand !!}
{{ t_raw('common.pickup') }}
{{ $deliveryRoute['pickup']['name'] }}@if ($deliveryRoute['pickup']['address'] !== '') · {{ $deliveryRoute['pickup']['address'] }}@endif
{!! $dropoffBrand !!}
{{ t_raw('vendor.order_detail.dropoff_label') }}
{{ $deliveryRoute['dropoff']['name'] }}@if ($deliveryRoute['dropoff']['address'] !== '') · {{ $deliveryRoute['dropoff']['address'] }}@endif
@endif