@extends('layouts.dashboard') @section('content') @php use App\Models\Order; /** @var array $kpis @var array $recent @var array|null $restaurant */ /** @var array $revenueChart @var array $ordersChart @var array $statusChart @var array $topItemsChart @var array $chartMeta */ /** @var array $revenueRanges @var array $ordersRanges @var array $statusRanges @var array $kpiTrends */ $chartMeta = $chartMeta ?? []; $chartMeta += ['revenue' => [], 'status' => [], 'top' => [], 'orders' => []]; $kpiTrends = $kpiTrends ?? ['orders' => ['pct' => 0, 'dir' => 'flat'], 'revenue' => ['pct' => 0, 'dir' => 'flat']]; $kpiSparks = $kpiSparks ?? []; // Financial figures are owner-only: managers run operations without them. $showMoney = $showMoney ?? true; @endphp @php /* .qm-d2 scopes the dashboard density layer (app.css §35) — the same standard the admin dashboard set: same partials, same keys. */ @endphp
@if (!$restaurant)

{{ t_raw('vendor.dashboard.no_restaurant_msg') }}

@else @if ($restaurant['status'] === 'pending')
{!! t_raw('vendor.dashboard.pending_review_banner', [':name' => '' . e($restaurant['name']) . '']) !!}
@endif @php $rightNow = $rightNow ?? null; $stockAlerts = $stockAlerts ?? ['out' => 0, 'low' => 0]; @endphp @if ($stockAlerts['out'] > 0 || $stockAlerts['low'] > 0)
@if ($stockAlerts['out'] > 0){!! t_raw($stockAlerts['out'] > 1 ? 'vendor.dashboard.stock_alert_out_many' : 'vendor.dashboard.stock_alert_out_one', [':n' => '' . (int) $stockAlerts['out'] . '']) !!}@endif @if ($stockAlerts['out'] > 0 && $stockAlerts['low'] > 0) · @endif @if ($stockAlerts['low'] > 0){!! t_raw('vendor.dashboard.stock_alert_low', [':n' => '' . (int) $stockAlerts['low'] . '']) !!}@endif — {{ t_raw('vendor.dashboard.stock_alert_review_link') }}.
@endif @if ($rightNow) @php /* Triage semantics (same as the admin strip): a queue with work is `is-hot`, an empty one `is-calm`; the drawer tile is a status, so it carries neither. */ @endphp @php $rnState = static fn (int $n): string => $n > 0 ? ' is-hot' : ' is-calm'; @endphp

{{ t_raw('admin.dashboard.right_now') }}

{{ t_raw('common.live') }}
@endif

{{ t_raw('dashboard.sec.snapshot') }}

@partial('kpi-card', ['variant' => 'panel', 'accent' => 'blue', 'icon' => 'fa-receipt', 'label' => t_raw('vendor.dashboard.kpi_total_orders'), 'href' => route('vendor.orders'), 'value' => number_format((int) $kpis['orders']), 'spark' => $kpiSparks['orders'] ?? [], 'meta' => kpi_trend((int) $kpiTrends['orders']['pct'], (string) $kpiTrends['orders']['dir']) . '' . t('vendor.dashboard.kpi_vs_last_week') . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'amber', 'icon' => 'fa-bolt', 'label' => t_raw('vendor.dashboard.kpi_active_orders'), 'href' => route('vendor.orders'), 'value' => number_format((int) $kpis['active']), 'spark' => $kpiSparks['active'] ?? [], 'meta' => '' . t('vendor.dashboard.kpi_in_progress_now') . '']) @if ($showMoney) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'green', 'icon' => 'fa-coins', 'label' => t_raw('common.revenue'), 'href' => route('vendor.orders'), 'value' => money($kpis['revenue']), 'spark' => $kpiSparks['revenue'] ?? [], 'meta' => kpi_trend((int) $kpiTrends['revenue']['pct'], (string) $kpiTrends['revenue']['dir']) . '' . t('vendor.dashboard.kpi_vs_last_week') . '']) @endif @partial('kpi-card', ['variant' => 'panel', 'accent' => 'violet', 'icon' => 'fa-utensils', 'label' => t_raw('vendor.dashboard.kpi_menu_items'), 'href' => route('vendor.menu'), 'value' => number_format((int) $kpis['menu']), 'spark' => $kpiSparks['menu'] ?? [], 'meta' => '' . t('vendor.dashboard.kpi_on_your_menu') . '']) @if (!empty($loyaltyOn)) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'rose', 'icon' => 'fa-gift', 'label' => t_raw('vendor.dashboard.kpi_loyalty_points'), 'href' => route('vendor.loyalty'), 'value' => number_format((int) ($loyaltyOutstanding ?? 0)), 'spark' => $kpiSparks['loyalty'] ?? [], 'meta' => '' . t('vendor.dashboard.kpi_points_outstanding') . '']) @endif
@php $trendOptions = ['7' => t_raw('common.range_7d'), '14' => t_raw('common.range_14d'), '30' => t_raw('common.range_30d'), '90' => t_raw('common.range_qtr')]; @endphp

{{ t_raw('dashboard.sec.trends') }}

@if ($showMoney)
@partial('chart-card', [ 'heading' => t_raw('admin.dashboard.chart_revenue_trend_title'), 'sub' => t_raw('vendor.dashboard.chart_revenue_caption'), 'cfg' => $revenueChart, 'meta' => $chartMeta['revenue'], 'icon' => 'fa-chart-line', 'accent' => 'green', 'opts' => ['id' => 'revChart', 'ranges' => $revenueRanges, 'selected' => '90', 'options' => $trendOptions], ])
@else
@endif @partial('chart-card', [ 'heading' => t_raw('admin.dashboard.chart_orders_per_day_title'), 'sub' => t_raw('vendor.dashboard.chart_orders_caption'), 'cfg' => $ordersChart, 'meta' => $chartMeta['orders'], 'icon' => 'fa-chart-bar', 'accent' => 'blue', 'opts' => ['id' => 'ordersChart', 'ranges' => $ordersRanges, 'selected' => '90', 'options' => $trendOptions], ])

{{ t_raw('dashboard.sec.breakdown') }}

@php /* The Breakdown at 8 columns with the live-delivery map riding the other 4: the four analytic cards keep their own 2x2 grid, and the map stretches the section's full height beside them. */ @endphp
@partial('chart-card', [ 'heading' => t_raw('admin.dashboard.chart_orders_status_title'), 'sub' => t_raw('vendor.dashboard.chart_status_caption'), 'cfg' => $statusChart, 'meta' => $chartMeta['status'], 'icon' => 'fa-chart-pie', 'accent' => 'violet', 'opts' => ['id' => 'statusChart', 'ranges' => $statusRanges, 'selected' => '90', 'options' => $trendOptions], ])
@partial('chart-card', [ 'heading' => t_raw('vendor.dashboard.chart_top_items_title'), 'sub' => t_raw('vendor.dashboard.chart_top_items_caption'), 'cfg' => $topItemsChart, 'meta' => $chartMeta['top'], 'icon' => 'fa-chart-bar', 'accent' => 'amber', 'opts' => ['id' => 'topItemsChart', 'ranges' => $topItemsRanges ?? [], 'selected' => '90', 'options' => $trendOptions], ])
@if ($showMoney)
@partial('chart-card', [ 'heading' => t_raw('vendor.dashboard.chart_tender_title'), 'sub' => t_raw('vendor.dashboard.chart_tender_caption'), 'cfg' => $tenderChart ?? [], 'meta' => $tenderMeta ?? [], 'icon' => 'fa-chart-pie', 'accent' => 'blue', 'opts' => ['id' => 'tenderChart', 'ranges' => $tenderRanges ?? [], 'selected' => '90', 'options' => $trendOptions], ])
@else
@endif @partial('chart-card', [ 'heading' => t_raw('vendor.dashboard.chart_hours_title'), 'sub' => t_raw('vendor.dashboard.chart_hours_caption'), 'cfg' => $hoursChart ?? [], 'meta' => $hoursMeta ?? [], 'icon' => 'fa-chart-bar', 'accent' => 'violet', 'opts' => ['id' => 'hoursChart', 'ranges' => $hoursRanges ?? [], 'selected' => '90', 'options' => $trendOptions], ])
@if (!empty($mapCenter)) @php /* The live tally rides the title row's trailing side. It counts ORDERS out of the door — one rider carrying two orders is 2 deliveries here, 1 rider in the foot, and one pin on the map whose card lists both. */ @endphp

{{ t_raw('status.order.out_for_delivery') }}

{{ count($liveDeliveries ?? []) }} {{ t_raw('common.active') }}

@else

{{ t_raw('status.order.out_for_delivery') }}

{{ count($liveDeliveries ?? []) }} {{ t_raw('common.active') }}

{{ t_raw('common.view_all') }} @endif
@if (!empty($mapCenter)) @php // The restaurant anchors the room; every live driver rides as a labelled pin — // the same driver_locations positions the customer tracking map shows. $dlvPoints = [[ 'lat' => (float) $mapCenter['lat'], 'lng' => (float) $mapCenter['lng'], 'label' => (string) ($mapRestaurant['name'] ?? ''), 'iconHtml' => restaurant_logo_html($mapRestaurant ?? [], 'sm', '', ''), ]]; /* ONE pin per rider, not per order: a rider carrying two orders reports one position, and two pins stacked there leave the lower card unreachable. The rider's avatar is the pin; the card lists EVERY order they carry — see partials/map-driver-popup.php. */ $byRider = []; foreach (($liveDeliveries ?? []) as $dlv) { $key = (int) ($dlv['driver_user_id'] ?? 0); $byRider[$key]['rider'] ??= $dlv; $byRider[$key]['orders'][] = $dlv; } foreach ($byRider as $grp) { $dlvPoints[] = [ 'lat' => (float) $grp['rider']['lat'], 'lng' => (float) $grp['rider']['lng'], 'iconHtml' => brandmark_html($grp['rider']['driver_avatar'] ?? null, (string) $grp['rider']['driver_name'], 'sm', 'qm-brandmark--round'), 'popupHtml' => partial('map-driver-popup', ['dlv' => $grp['rider'], 'orders' => $grp['orders']]), ]; } @endphp @php $dlvRiders = count(array_unique(array_filter(array_map( static fn (array $d): string => trim((string) ($d['driver_name'] ?? '')), $liveDeliveries ?? [] )))); @endphp @php /* Same floating foot as the admin locations map — the live tally and the View-all corner riding the map's lower edge. */ @endphp
{{ t_raw('nav.deliveries') }} {{ number_format(count($liveDeliveries ?? [])) }}
{{ t_raw('common.riders') }} {{ number_format($dlvRiders) }}
{{ t_raw('common.view_all') }}
@endif
@php /* Business position — reputation, money owed, sponsored reach. Each card is an excerpt of its own screen (same figures, same keys) with a View-all into it; a card with nothing to say is simply not rendered. */ @endphp @if (!empty($reviewSummary) || !empty($payoutSummary) || !empty($adSummary)) @php // The row shares its width among the cards that actually render: roles that // don't see Payouts (managers) get two half-width cards, not a hole. $bizCards = (int) !empty($reviewSummary) + (int) !empty($payoutSummary) + (int) !empty($adSummary); $bizCol = [1 => 'col-lg-12', 2 => 'col-lg-6', 3 => 'col-lg-4'][$bizCards]; @endphp
@if (!empty($reviewSummary))
@if ((int) $reviewSummary['count'] === 0)

{{ t_raw('vendor.customers.col_reviews') }}

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

{{ t_raw('vendor.dashboard.reviews_empty') }}

@else

{{ t_raw('vendor.customers.col_reviews') }}

@foreach ($reviewSummary['latest'] as $rvw)
{{ trim((string) $rvw['author_name']) !== '' ? $rvw['author_name'] : t_raw('vendor.reservations.guest_fallback') }} {!! rating_stars_html((float) $rvw['rating']) !!}

{{ (string) $rvw['comment'] }}

{{ time_ago($rvw['created_at']) }}
@endforeach
@php /* The rating summary anchors the FOOT, sponsored-style: the average with its stars, the review count, and View-all in the corner. */ @endphp
{{ t_raw('admin.restaurants.col_rating') }} {{ number_format((float) $reviewSummary['avg'], 1) }} {!! rating_stars_html((float) $reviewSummary['avg']) !!}
{{ t_raw('vendor.customers.col_reviews') }} {{ number_format((int) $reviewSummary['count']) }}
{{ t_raw('common.view_all') }}
@endif
@endif @if (!empty($payoutSummary))

{{ t_raw($payoutSummary['invoice'] ? 'vendor.royalty.title' : 'vendor.payouts.title') }}

@if (!empty($payoutSummary['latest'])) @php /* The last statements, the way the Reviews card quotes its latest reviews: the period, what it settled at, where it stands — each row an excerpt of the payouts page's own table. */ @endphp
@php $stPill = static fn (string $st): string => match ($st) { 'paid' => 'qm-pill-green', 'issued' => 'qm-pill-blue', 'disputed' => 'qm-pill-red', default => 'qm-pill-amber', }; @endphp @foreach ($payoutSummary['latest'] as $stmt)
{!! fmt_day($stmt['period_start']) !!} – {!! fmt_day($stmt['period_end']) !!} {{ money((float) $stmt['net_payable']) }}
{{ status_label('payout', (string) $stmt['status']) }}@if (!empty($stmt['paid_at'])) {!! fmt_day($stmt['paid_at']) !!}@endif
@endforeach
@else

{{ t_raw('vendor.payouts.empty') }}

@endif @php /* The same foot its siblings wear: the two figures the payouts page leads with, and View-all docked in the card's own corner. */ @endphp
{{ t_raw($payoutSummary['invoice'] ? 'vendor.royalty.kpi_pending' : 'vendor.payouts.kpi_pending') }} {{ money((float) $payoutSummary['pendingNet']) }}
{{ t_raw('vendor.payouts.kpi_this_month') }} {{ money((float) $payoutSummary['paidThisMonth']) }}
{{ t_raw('common.view_all') }}
@endif @if (!empty($adSummary))
@if ((int) $adSummary['live'] === 0)

{{ t_raw('admin.campaigns.title') }}

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

{{ t_raw('vendor.campaigns.active_empty') }}

{{ t_raw('vendor.campaigns.empty_cta') }}
@else @php /* The SAME chart-card head as every chart on the page: title + timeline pills on the first row, the live count as the Apex caption row — View all moves to the card's corner CTA below. */ @endphp @partial('chart-card', [ 'heading' => t_raw('admin.campaigns.title'), 'sub' => (int) $adSummary['live'] . ' ' . t_raw('common.active'), 'cfg' => $adSummary['chart'], 'meta' => ['has' => true, 'label' => t_raw('vendor.campaigns.kpi_impressions')], 'icon' => 'fa-bullhorn', 'accent' => 'rose', 'opts' => ['id' => 'adReachChart', 'ranges' => $adSummary['ranges'], 'selected' => '14', 'options' => $trendOptions ?? []], ])
{{ t_raw('vendor.campaigns.kpi_impressions') }} {{ number_format((int) $adSummary['impressions']) }}
{{ t_raw('vendor.campaigns.kpi_clicks') }} {{ number_format((int) $adSummary['clicks']) }}
{{ t_raw('vendor.campaigns.kpi_ctr') }} {!! $adSummary['ctr'] !== null ? number_format((float) $adSummary['ctr'], 2) . '%' : t('vendor.campaigns.active_no_data') !!}
@php /* View all docked in the card's own corner: red, square except the bottom-right, riding the card's radius. */ @endphp {{ t_raw('common.view_all') }}
@endif
@endif
@endif @if (!empty($acceptsReservations)) @php /* Today's book beside its shape: the table (8 cols) with the covers-by-hour bars (4 cols) — the same hour-bar form as Busiest hours, pointed at the NEXT fortnight of live bookings, because a reservation book is a plan. */ @endphp

{{ t_raw('vendor.dashboard.todays_reservations_title') }}

@if (count($todayReservations) > 0)

{{ t_raw('vendor.dashboard.n_booked_today', [':n' => (int) ($reservationsPager['total'] ?? 0)]) }}

@endif
{{ t_raw('common.view_all') }}
@if (count($todayReservations) > 0) @foreach ($todayReservations as $rv) @endforeach @else @partial('table-empty', ['colspan' => 4, 'icon' => 'fa-calendar-day', 'msg' => t_raw('vendor.dashboard.no_reservations_today')]) @endif
{{ t_raw('common.time') }}{{ t_raw('vendor.reservations.col_guest') }}{{ t_raw('vendor.reservations.party_label') }}{{ t_raw('common.status') }}
{!! fmt_clock($rv['reserved_at']) !!}
@php /* A walk-in seated without a name has none stored — the display placeholder belongs here, not in the row. Same fallback the reservations list uses. */ @endphp @php $rvName = trim((string) ($rv['guest_name'] ?? '')) !== '' ? (string) $rv['guest_name'] : t_raw('vendor.reservations.guest_fallback'); @endphp {!! brandmark_html($rv['avatar'] ?? null, $rvName, 'md', 'qm-brandmark--round') !!}
{{ $rvName }}
{{ (int) $rv['party_size'] }} {{ t_raw('vendor.dashboard.guests_suffix') }} {{ \App\Models\Reservation::statusLabel($rv['status']) }}
@partial('table-toolbar', ['pager' => $reservationsPager ?? []])
@partial('chart-card', [ 'heading' => t_raw('vendor.dashboard.chart_resv_load_title'), 'sub' => t_raw('vendor.dashboard.chart_resv_load_sub'), 'cfg' => $resvLoadChart ?? [], 'meta' => $resvLoadMeta ?? [], 'icon' => 'fa-calendar-check', 'accent' => 'blue', 'opts' => ['id' => 'resvLoadChart', 'ranges' => $resvLoadRanges ?? [], 'selected' => '14', 'options' => $trendOptions], ])
@endif @php /* Client locations beside Recent orders: the Customers analytics page's map (same rows, same avatar pins - see analytics-customers.php, whose builder this copies) wearing the delivery map's card anatomy. WHO the restaurant feeds on the left; WHAT is happening on the right. */ @endphp
@php $cliPoints = []; foreach (($clientLocations ?? []) as $cl) { if ($cl['delivery_lat'] === null || $cl['delivery_lng'] === null) { continue; } $addr = trim((string) ($cl['delivery_address'] ?? '')); $cliPoints[] = [ 'lat' => (float) $cl['delivery_lat'], 'lng' => (float) $cl['delivery_lng'], 'label' => $cl['name'] . ($addr !== '' ? ' — ' . $addr : ''), 'iconHtml' => brandmark_html($cl['avatar'] ?? null, (string) $cl['name'], 'sm'), ]; } $cliCenter = $cliPoints ? $cliPoints[0] : null; @endphp

{{ t_raw('vendor.analytics.heading_client_locations') }}

{{ number_format(count($cliPoints)) }} {{ t_raw('common.customers') }}

@if ($cliCenter !== null) @php /* Same floating foot as the delivery map - the tally and the View-all corner riding the map's lower edge. */ @endphp
{{ t_raw('common.customers') }} {{ number_format(count($cliPoints)) }}
{{ t_raw('common.last_order') }} {!! fmt_day((string) $clientLocations[0]['placed_at']) !!}
{{ t_raw('common.view_all') }}
@else

{{ t_raw('vendor.analytics.empty_no_client_locations') }}

@endif

{{ t_raw('admin.dashboard.recent_orders_title') }}

{{ t_raw('common.view_all') }}
@if (count($recent)) @foreach ($recent as $o) @endforeach @else @partial('table-empty', ['colspan' => 6, 'icon' => 'fa-receipt', 'msg' => t_raw('admin.dashboard.empty_no_orders')]) @endif
{{ t_raw('admin.dashboard.col_order') }}{{ t_raw('common.customer') }}{{ t_raw('common.total') }}{{ t_raw('admin.analytics_sales.col_mode') }}{{ t_raw('common.status') }}{{ t_raw('admin.dashboard.col_placed') }}
@php /* Same link the full orders list and the admin dashboard already use — this card was the only place an order number was a dead end. */ @endphp {{ $o['order_number'] }} @if (!empty($o['first_item_name'])){{ $o['first_item_name'] }}@if (($o['item_count'] ?? 0) > 1) {{ t_raw('admin.dashboard.items_more_suffix', [':n' => (int) $o['item_count'] - 1]) }}@endif@endif
{!! brandmark_html($o['customer_avatar'] ?? null, (string) $o['customer_name'], 'md', 'qm-brandmark--round') !!}
{{ $o['customer_name'] }}
{{ money($o['total']) }} {{ status_label('service_mode', $o['service_mode']) }} {{ Order::statusLabel($o['status'], $o['service_mode'] ?? 'delivery') }} {!! fmt_datetime($o['placed_at']) !!}
@partial('table-toolbar', ['pager' => $recentPager ?? []])
@endif
@php /* /.qm-d2 */ @endphp @endsection