@extends('layouts.dashboard') @section('content') @php /** * Per-item sales analytics (vendor). Every figure is computed from real order * data in MenuController::analytics() - tenant-scoped and excluding cancelled * orders - so this page is a decision tool for the owner, not a vanity board. * * @var array $item the menu item * @var array $stats units, orders, revenue, customers * @var int $totalOrders all non-cancelled restaurant orders (attach-rate base) * @var int $cancelled orders that contained this item and were cancelled * @var array $span first_sold / last_sold timestamps * @var array $topCustomers top buyers of this item * @var array $boughtWith items most often in the same order * @var array $unitsTrend ApexCharts area-chart config (60-day units) * @var bool $hasSales true when this item has at least one unit sold */ $units = (int) $stats['units']; $orders = (int) $stats['orders']; $revenue = (float) $stats['revenue']; $customers = (int) $stats['customers']; // Derived, owner-useful ratios (guard against divide-by-zero on a fresh item). $attachRate = $totalOrders > 0 ? ($orders / $totalOrders) * 100 : 0.0; $avgUnits = $orders > 0 ? $units / $orders : 0.0; $avgPerOrder = $orders > 0 ? $revenue / $orders : 0.0; $repeatBuyers = 0; foreach ($topCustomers as $c) { if (!empty($c['customer_user_id']) && (int) $c['orders'] > 1) { $repeatBuyers++; } } $lastSold = !empty($span['last_sold']) ? strtotime((string) $span['last_sold']) : 0; $daysSince = $lastSold ? (int) floor((time() - $lastSold) / 86400) : null; @endphp @php /* .qm-d2: the dashboard density standard (app.css ยง35) */ @endphp
{{ t_raw('vendor.item_analytics.eyebrow') }}
{{ $item['name'] }} {{ money($item['price']) }} @if (!$item['is_available']){{ t_raw('vendor.item_analytics.hidden_pill') }}@endif
{{ t_raw('vendor.item_analytics.back_to_menu') }}
@if (!$hasSales)

{{ t_raw('vendor.item_analytics.no_sales_title') }}

{!! t_raw('vendor.item_analytics.no_sales_text', [':name' => '' . e($item['name']) . '']) !!}

@else @php $kpiSparks = $kpiSparks ?? []; $menuHref = route('vendor.menu'); @endphp
@partial('kpi-card', ['variant'=>'panel', 'accent'=>'blue', 'icon'=>'fa-cubes', 'label'=>t_raw('vendor.dashboard.chart_top_items_sub'), 'value'=>number_format($units), 'href'=>$menuHref, 'spark'=>$kpiSparks['units'] ?? [], 'meta'=>'' . t('vendor.item_analytics.kpi_units_sold_meta') . '']) @partial('kpi-card', ['variant'=>'panel', 'accent'=>'violet', 'icon'=>'fa-receipt', 'label'=>t_raw('vendor.item_analytics.kpi_times_ordered'), 'value'=>number_format($orders), 'href'=>$menuHref, 'spark'=>$kpiSparks['orders'] ?? [], 'meta'=>'' . t('vendor.item_analytics.meta_orders_with_item') . '']) @partial('kpi-card', ['variant'=>'panel', 'accent'=>'green', 'icon'=>'fa-coins', 'label'=>t_raw('common.revenue'), 'value'=>money($revenue), 'href'=>$menuHref, 'spark'=>$kpiSparks['revenue'] ?? [], 'meta'=>'' . t('vendor.item_analytics.meta_from_item') . '']) @partial('kpi-card', ['variant'=>'panel', 'accent'=>'teal', 'icon'=>'fa-users', 'label'=>t_raw('vendor.item_analytics.kpi_unique_customers'), 'value'=>number_format($customers), 'href'=>$menuHref, 'spark'=>$kpiSparks['customers'] ?? [], 'meta'=>'' . t('vendor.item_analytics.meta_distinct_buyers') . '']) @partial('kpi-card', ['variant'=>'panel', 'accent'=>'amber', 'icon'=>'fa-percent', 'label'=>t_raw('vendor.item_analytics.kpi_attach_rate'), 'value'=>number_format($attachRate, 1).'%', 'spark'=>$kpiSparks['attach'] ?? [], 'meta'=>'' . t('vendor.item_analytics.meta_of_all_orders') . '']) @partial('kpi-card', ['variant'=>'panel', 'accent'=>'orange', 'icon'=>'fa-dollar-sign', 'label'=>t_raw('vendor.item_analytics.kpi_avg_per_order'), 'value'=>money($avgPerOrder), 'spark'=>$kpiSparks['avg'] ?? [], 'meta'=>'' . t('vendor.item_analytics.meta_units_each', [':n' => number_format($avgUnits, 1)]) . ''])
{{ t_raw('vendor.item_analytics.stat_first_sold') }}
{!! fmt_day($span['first_sold'] ?? null) !!}
{{ t_raw('vendor.item_analytics.stat_last_sold') }}
@if ($daysSince === null)-@elseif ($daysSince === 0){{ t_raw('vendor.item_analytics.last_sold_today') }}@elseif ($daysSince === 1){{ t_raw('vendor.item_analytics.last_sold_yesterday') }}@else{{ t_raw('vendor.item_analytics.last_sold_days_ago', [':n' => $daysSince]) }}@endif @if ($daysSince !== null && $daysSince >= 14) @endif
{{ t_raw('vendor.item_analytics.stat_repeat_buyers') }}
{{ $repeatBuyers }}
{{ t_raw('vendor.item_analytics.stat_in_cancelled') }}
{{ number_format($cancelled) }}
@php $unitsRanges = $unitsRanges ?? []; $unitsOpts = ['7' => t_raw('common.range_7d'), '14' => t_raw('common.range_14d'), '30' => t_raw('common.range_30d'), '90' => t_raw('common.range_qtr')]; // The caption rides on the chart's own toolbar row (ApexCharts `subtitle`) and // says what the plot measures. The card heading stays in the

below. $unitsTrend['subtitle'] = t_raw('vendor.item_analytics.units_caption'); @endphp

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

@php /* (string) $val, because PHP turns a numeric-string array key into an INT: the strict compare against '30' could never be true, so the bar opened with no window marked active while the chart itself is drawn from the 30-day series (MenuController::analytics). dashboard-charts.js only syncs on a click, so nothing put the state right afterwards either. */ @endphp @foreach ($unitsOpts as $val => $text) @php $on = ((string) $val === '30'); @endphp @endforeach
{{ t_raw('vendor.item_analytics.units_chart_caption') }}

{{ t_raw('vendor.item_analytics.heading_top_customers') }}

@if ($topCustomers) @foreach ($topCustomers as $c) @php $isGuest = empty($c['customer_user_id']); @endphp @endforeach @else @partial('table-empty', ['colspan' => 4, 'icon' => 'fa-user', 'msg' => t_raw('vendor.item_analytics.empty_no_customer_data')]) @endif
{{ t_raw('common.customer') }}{{ t_raw('common.orders') }}{{ t_raw('common.units') }}{{ t_raw('vendor.item_analytics.col_spent') }}
{!! brandmark_html($c['avatar'] ?? null, (string) $c['name'], 'md', 'qm-brandmark--round') !!}
{{ $c['name'] }}@if ($isGuest) {{ t_raw('vendor.item_analytics.pill_guest_checkouts') }}@elseif ((int) $c['orders'] > 1) {{ t_raw('vendor.item_analytics.pill_repeat') }}@endif @if (!$isGuest && !empty($c['email'])){{ $c['email'] }}@endif
{{ (int) $c['orders'] }} {{ (int) $c['units'] }} {{ money($c['spend']) }}

{{ t_raw('vendor.item_analytics.heading_bought_together') }}

@if ($boughtWith) @foreach ($boughtWith as $bw) @endforeach @else @partial('table-empty', ['colspan' => 3, 'icon' => 'fa-utensils', 'msg' => t_raw('vendor.item_analytics.empty_not_bought_with')]) @endif
{{ t_raw('vendor.item_analytics.col_item') }}{{ t_raw('vendor.item_analytics.col_shared_orders') }}{{ t_raw('common.units') }}
{{ $bw['item_name'] }}
{{ (int) $bw['co_orders'] }} {{ (int) $bw['units'] }}
@endif

@php /* /.qm-d2 */ @endphp @endsection