@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
@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
@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