@extends('layouts.dashboard') @section('content') @php /** * Vendor → Upsell rules: what this restaurant offers once a given dish is in the * basket. One row is one pairing — a target dish, the dish to recommend beside it, * and an optional discount to sweeten it (UpsellController::index()). * * Rules are optional. With none written the engine still recommends: it falls back * to the cheapest available dish outside the categories the basket already draws * on. That is why this page never reads as "off" when it is empty. * * @var array $rows the current pager window of rule rows, each carrying * target_name / rec_name / rec_price from the join * @var array $pager paginate() metadata * @var array $dishes this restaurant's dishes, for both selects * @var int $active active rules * @var int $covered distinct dishes that have at least one active rule * @var int $sellable dishes a customer can actually order right now * @var float|null $coverage % of sellable dishes covered (null = nothing sellable) * @var int $withOffer active rules that carry a discount * @var bool $engineOn the platform-wide switch */ @endphp @php /* .qm-d2: the dashboard density standard (app.css §35) */ @endphp
@php // Page primary action → top-bar subheader slot, exactly as Coupons does, leaving the // table card header for the title. ob_start captures the button HTML for the layout slot. ob_start(); @endphp @php \App\Support\View::slot('subhead', ob_get_clean()); @endphp @php /* No reporting-period control on this page: a rule has no history to window. Nothing on an order records that a recommendation was what sold it, so every figure below is read from the rules and the menu — a measured number or none at all. */ @endphp
@partial('kpi-card', ['variant' => 'panel', 'accent' => 'blue', 'icon' => 'fa-lightbulb', 'label' => t_raw('vendor.upsell.kpi_active'), 'value' => number_format((int) $active), 'meta' => '' . t('vendor.upsell.kpi_active_meta', [':n' => number_format((int) $pager['total'])]) . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'green', 'icon' => 'fa-hamburger', 'label' => t_raw('vendor.upsell.kpi_covered'), 'value' => number_format((int) $covered), 'href' => route('vendor.menu'), 'meta' => '' . t('vendor.upsell.kpi_covered_meta', [':n' => number_format((int) $sellable)]) . '']) @php /* Coverage only exists when there is something to cover; with no sellable dish the figure does not exist and is shown as such rather than as a zero. */ @endphp @partial('kpi-card', ['variant' => 'panel', 'accent' => 'amber', 'icon' => 'fa-percentage', 'label' => t_raw('vendor.upsell.kpi_coverage'), 'value' => $coverage !== null ? number_format($coverage, 1) . '%' : '—', 'meta' => '' . ($coverage !== null ? t('vendor.upsell.kpi_coverage_meta') : t('vendor.upsell.kpi_coverage_meta_none')) . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'rose', 'icon' => 'fa-tags', 'label' => t_raw('vendor.upsell.kpi_offers'), 'value' => number_format((int) $withOffer), 'meta' => '' . t('vendor.upsell.kpi_offers_meta') . '']) @php /* What customers are actually being shown. The vendor cannot change the platform switch, so it is reported rather than offered — a page full of rules that nothing reads should say so on the page. */ @endphp @partial('kpi-card', ['variant' => 'panel', 'accent' => 'slate', 'icon' => 'fa-lightbulb', 'label' => t_raw('vendor.upsell.kpi_engine'), 'value' => $engineOn ? t_raw('vendor.upsell.state_on') : t_raw('vendor.upsell.state_off'), 'meta' => '' . (!$engineOn ? t('vendor.upsell.kpi_engine_meta_off') : ($active > 0 ? t('vendor.upsell.kpi_engine_meta_rules') : t('vendor.upsell.kpi_engine_meta_auto'))) . ''])

{{ t_raw('vendor.upsell.table_heading') }}

@php /* The offer column mixes a percentage with a money amount, which are not one scale, so that carries no data-sort — the same call Coupons makes on its discount column. */ @endphp @if ($rows) @foreach ($rows as $r) @endforeach @else @partial('table-empty', [ 'colspan' => 6, 'icon' => 'fa-lightbulb', 'msg' => t_raw('vendor.upsell.empty'), ]) @endif
{{ t_raw('vendor.upsell.caption_table') }}
{{ t_raw('vendor.upsell.col_target') }}{{ t_raw('vendor.upsell.col_recommendation') }}{{ t_raw('vendor.upsell.col_offer') }}{{ t_raw('vendor.upsell.col_priority') }}{{ t_raw('common.active') }}{{ t_raw('common.actions') }}
{!! $r['target_name'] !== null ? e((string) $r['target_name']) : t('vendor.upsell.dish_removed') !!}
{!! $r['rec_name'] !== null ? e((string) $r['rec_name']) : t('vendor.upsell.dish_removed') !!} @php /* A rule pointing at a dish that is off the menu is not an error to fix in the database — the engine simply skips it — but the vendor should be able to see why a pairing they wrote never appears. */ @endphp @if ($r['rec_name'] !== null && (int) $r['rec_available'] !== 1){{ t_raw('vendor.upsell.rec_unavailable') }}@endif
@if ((float) $r['discount_value'] > 0){!! $r['discount_type'] === 'percent' ? e(rtrim(rtrim(number_format((float) $r['discount_value'], 2), '0'), '.')) . '%' : money($r['discount_value']) !!}@else{{ t_raw('vendor.upsell.no_offer') }}@endif {{ (int) $r['priority'] }} {!! $r['is_active'] ? t('common.yes') : t('common.no') !!}
@csrf
@partial('table-toolbar', ['pager' => $pager, 'base' => '/vendor/upsell'])
@php /* ====================================================== RULE MODAL (same shape as the Coupons modal — one form, add and edit share it, the id decides which). The populate script at the foot of this file is this page's own: app.js's shared bindModal() is registered for a fixed list of modal names and adding to that list is an edit to a file this page does not own. */ @endphp
@php /* /.qm-d2 */ @endphp @endsection