@extends('layouts.dashboard') @section('content') @php /** * Vendor → Coupons: this restaurant's discount codes, with what they have actually * cost. Every performance figure is read from the orders each code was applied to * (orders.coupon_id), not from a running counter, so the discount given and the * basket it bought are both real (CouponController::index()). * * @var array $rows the current pager window of coupon rows * @var array $pager paginate() metadata * @var array $rangeLinks ['options' => [days => label], 'selected' => string] * @var int $rangeDays the selected window, in days * @var bool $showMoney false for a manager: discount cost stays with the owner * @var int $redemptions orders in the window that carried a code * @var float $discountGiven * @var float|null $aovCoupon average order value with a code (null = none in window) * @var float|null $aovPlain average order value without a code * @var int $plainOrders * @var float|null $usageRate % of capped headroom used (null = no capped codes) * @var int $capUsed @var int $capTotal * @var array $byCode coupon_id => ['uses' => int, 'discount' => float] * @var array $trendChart @var array $trendMeta redemptions per day * @var array $codeChart @var array $codeMeta redemptions by code */ $showMoney = $showMoney ?? true; $byCode = $byCode ?? []; // The lift a code buys: how a coupon basket compares with an ordinary one. Only // meaningful when both sides of the comparison actually happened in the window. $lift = ($aovCoupon !== null && $aovPlain !== null && $aovPlain > 0) ? round(($aovCoupon - $aovPlain) / $aovPlain * 100, 1) : null; // The coupon table takes no filters beyond the restaurant scope (the range control drives // the analytics above, not the code list), so the export needs no query string. $qs = ''; @endphp @php /* .qm-d2: the dashboard density standard (app.css §35) */ @endphp
@php // Page primary action → top-bar subheader slot (leaves the table card header for the // title + any table-scoped utility). ob_start captures the button HTML for the layout slot. ob_start(); @endphp @php \App\Support\View::slot('subhead', ob_get_clean()); @endphp
@foreach ($rangeLinks['options'] as $val => $text) @php $on = ((string) $val === $rangeLinks['selected']); @endphp {{ $text }} @endforeach
{{ t_raw('common.export_csv') }}
@partial('kpi-card', ['variant' => 'panel', 'accent' => 'blue', 'icon' => 'fa-ticket-alt', 'label' => t_raw('vendor.coupons.kpi_redemptions'), 'value' => number_format((int) $redemptions), 'href' => route('vendor.orders'), 'spark' => $kpiSparks['redemptions'] ?? [], 'meta' => '' . t('vendor.coupons.kpi_redemptions_meta', [':days' => (int) $rangeDays]) . '']) @if ($showMoney) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'rose', 'icon' => 'fa-tags', 'label' => t_raw('vendor.coupons.kpi_discount_given'), 'value' => money($discountGiven), 'spark' => $kpiSparks['discount'] ?? [], 'meta' => '' . t('vendor.coupons.kpi_discount_given_meta') . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'green', 'icon' => 'fa-receipt', 'label' => t_raw('vendor.coupons.kpi_aov_with'), 'value' => $aovCoupon !== null ? money($aovCoupon) : '—', 'meta' => '' . ($lift !== null ? t('vendor.coupons.kpi_aov_lift', [':pct' => ($lift >= 0 ? '+' : '−') . number_format(abs($lift), 1)]) : t('vendor.coupons.kpi_aov_with_meta')) . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'slate', 'icon' => 'fa-shopping-basket', 'label' => t_raw('vendor.coupons.kpi_aov_without'), 'value' => $aovPlain !== null ? money($aovPlain) : '—', 'meta' => '' . t('vendor.coupons.kpi_aov_without_meta', [':n' => number_format((int) $plainOrders)]) . '']) @endif @php /* Only codes the restaurant actually capped can have a usage rate; with no capped code 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.coupons.kpi_usage_rate'), 'value' => $usageRate !== null ? number_format($usageRate, 1) . '%' : '—', 'meta' => '' . ($usageRate !== null ? t('vendor.coupons.kpi_usage_rate_meta', [':used' => number_format($capUsed), ':cap' => number_format($capTotal)]) : t('vendor.coupons.kpi_usage_rate_meta_none')) . ''])
@php /* Trends beside the breakdown: when codes are being spent, and which ones. Each card's caption and its accessible summary are the same sentence. */ @endphp
@partial('chart-card', ['heading' => t_raw('vendor.coupons.chart_redemptions'), 'sub' => $trendMeta['label'], 'cfg' => $trendChart, 'meta' => $trendMeta, 'accent' => 'blue', 'icon' => 'fa-chart-bar'])
@partial('chart-card', ['heading' => t_raw('vendor.coupons.chart_by_code'), 'sub' => $codeMeta['label'], 'cfg' => $codeChart, 'meta' => $codeMeta, 'accent' => 'rose', 'icon' => 'fa-ticket-alt'])

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

@php /* Window mixes a "Now"/"No end date" fallback with a date, so that cell carries the start date in data-sort-value instead — the composite text won't parse either way. */ @endphp @php /* Discount mixes a percentage with a money amount, which are not one scale, so that carries no data-sort — no honest sort key exists across the two. */ @endphp @if ($showMoney)@endif @if ($rows) @foreach ($rows as $d) @if ($showMoney) @php $spend = $byCode[(int) $d['id']]['discount'] ?? null; @endphp @endif @endforeach @else @partial('table-empty', [ 'colspan' => $showMoney ? 9 : 8, 'icon' => 'fa-tags', 'msg' => t_raw('vendor.coupons.empty'), ]) @endif
{{ t_raw('vendor.coupons.caption_table') }}
{{ t_raw('admin.deals.code_label') }}{{ t_raw('common.title') }}{{ t_raw('admin.deals.col_discount') }}{{ t_raw('admin.deals.min_order_label') }}{{ t_raw('vendor.coupons.col_window') }}{{ t_raw('admin.deals.col_used') }}{{ t_raw('vendor.coupons.col_cost_in_window') }}{{ t_raw('common.active') }}{{ t_raw('common.actions') }}
{{ $d['code'] }} {!! or_dash($d['title'] ?? null) !!} {!! $d['type'] === 'percent' ? e(rtrim(rtrim(number_format((float) $d['value'], 2), '0'), '.')) . '%' : money($d['value']) !!} {{ money($d['min_order']) }} @if (!empty($d['starts_at']) || !empty($d['ends_at'])) {!! !empty($d['starts_at']) ? fmt_day($d['starts_at']) : t('vendor.coupons.window_now') !!} → {!! !empty($d['ends_at']) ? fmt_day($d['ends_at']) : t('vendor.coupons.window_no_end') !!} @else{{ t_raw('vendor.coupons.window_always_on') }}@endif {{ (int) $d['used_count'] }}{{ (int) $d['max_uses'] > 0 ? ' / ' . (int) $d['max_uses'] : '' }}{!! $spend !== null ? money($spend) : dash() !!}{!! $d['is_active'] ? t('common.yes') : t('common.no') !!}
@csrf
@partial('table-toolbar', ['pager' => $pager, 'base' => '/vendor/coupons'])
@php /* ====================================================== COUPON MODAL (shares #dealModal + assets/js/app.js's bindModal('deal', ...) with Admin -> Deals; no restaurant picker here since scope is always this restaurant, enforced server-side) */ @endphp
@php /* /.qm-d2 */ @endphp @endsection