@extends('layouts.dashboard') @section('content') @php /** * Vendor → Menu → Structure. * * How the menu is DIVIDED and WHEN parts of it are offered. Both were on the dish list, in a * col-4/col-8 row that locked a 26-row section list and a 3-row schedule table to the same * 1,590px height and pushed the dishes two screens down the page. They are one job, they are * touched about once a month, and they both want a full-width table — so they live here. * * @var array $categories sections (id, name, sort_order, schedule_id, station_id) * @var array $catCounts dishes per section id * @var array $schedules day-part windows * @var array $scheduleCounts dishes attached per schedule id * @var array $stations kitchen stations (id, name, sort_order, is_active) * @var array $stationCounts dishes reaching each station once inheritance is resolved * @var int $unassignedDishes dishes reaching no station at all * @var string $menuName what this restaurant calls its menu * @var int $dishCount total dishes, for the tab badge * @var int $draftCount imports awaiting review, for the tab badge * @var bool $canImport whether either import route is configured */ use App\Services\MenuSchedule; $categories = $categories ?? []; $allCategories = $allCategories ?? $categories; $pager = $pager ?? []; $catCounts = $catCounts ?? []; $schedules = $schedules ?? []; $scheduleCounts = $scheduleCounts ?? []; $stations = $stations ?? []; $stationCounts = $stationCounts ?? []; $unassignedDishes = (int) ($unassignedDishes ?? 0); $menuName = (string) ($menuName ?? ''); $dishCount = (int) ($dishCount ?? 0); $draftCount = (int) ($draftCount ?? 0); $canImport = !empty($canImport); // Schedule id -> name, so a section can show which window it is attached to by name. $schedName = []; foreach ($schedules as $s) { $schedName[(int) $s['id']] = (string) $s['name']; } // Station id -> name, for the same reason on the station column. $statName = []; foreach ($stations as $s) { $statName[(int) $s['id']] = (string) $s['name']; } @endphp @php /* Vertical tabs, the same .qm-settings grid the settings pages use: a 232px sticky nav beside its panel. Serving times were below twenty-seven sections and effectively unfindable; now they are one click, and neither table pushes the other down. */ @endphp
@php /* Menu name has moved to Vendor Settings, beside the restaurant name and tagline it sits next to on the storefront. It is identity, not an action taken while managing a menu. */ @endphp @php /* ---- Sections ------------------------------------------------------------------ A dish count per section, because a section nobody filled shows nothing on the storefront and is impossible to spot in a flat list of twenty-six names. */ @endphp

{{ t_raw('vendor.menu.categories_title') }}

{{ t_raw('vendor.menu.sections_sub') }}

@php /* The arrangement, posted as one ordered list — the whole set, not the page on screen, so arranging page two cannot disturb page one. Its own form, because a row already carries a delete form and a form inside a form is dropped by the browser. Off until something moves. */ @endphp @if (count($allCategories) > 1)
@csrf
@endif
@if ($stations)@endif @if ($categories) @foreach ($categories as $c) @php $n = (int) ($catCounts[(int) $c['id']] ?? 0); $sid = (int) ($c['schedule_id'] ?? 0); $stid = (int) ($c['station_id'] ?? 0); @endphp @php /* The same grip the two admin arrangers use, at the row's own control height — .qm-icon-btn, which is what the actions in this row are. */ @endphp @php /* Amber at zero: an empty section is not an error, but it is the thing on this page most worth noticing. */ @endphp @php /* No .qm-cell-body here: that utility is white-space:pre-line, for genuinely multi-line content, and the newlines in this block would render as a real line break that drops the text below its row. */ @endphp @php /* Amber when nothing cooks it: an unmapped section still reaches the board, but it reaches the UNASSIGNED screen rather than a cook's own. */ @endphp @if ($stations) @endif @endforeach @else @partial('table-empty', ['colspan' => $stations ? 6 : 5, 'icon' => 'fa-folder-open', 'msg' => t('vendor.menu.empty_categories_body')]) @endif
{{ t_raw('vendor.menu.reorder_label') }} {{ t_raw('common.name') }} {{ t_raw('vendor.menu.col_dishes') }} {{ t_raw('vendor.menu.availability_label') }}{{ t_raw('vendor.menu.station_label') }}{{ t_raw('common.actions') }}
{{ $c['name'] }}{{ number_format($n) }}@if ($sid > 0 && isset($schedName[$sid])) {{ $schedName[$sid] }}@else{{ t_raw('vendor.menu.always_available_option') }}@endif@if ($stid > 0 && isset($statName[$stid])) {{ $statName[$stid] }}@else{{ t_raw('vendor.menu.station_unassigned_option') }}@endif @php /* The shared component, as the saved-menus table uses. This cell was hand-rolled — the fourth different action treatment in one feature, which is exactly what the partial exists to prevent. */ @endphp @partial('row-actions', ['actions' => [ ['label' => t_raw('common.edit'), 'icon' => 'fa-pen', 'primary' => true, 'attrs' => ['data-cat-edit' => '1', 'data-id' => (string) (int) $c['id'], 'data-name' => (string) $c['name'], 'data-sort' => (string) (int) $c['sort_order'], 'data-sched' => $sid > 0 ? (string) $sid : '', 'data-station' => $stid > 0 ? (string) $stid : '', 'data-bs-toggle' => 'modal', 'data-bs-target' => '#catModal']], ['label' => t_raw('common.delete'), 'icon' => 'fa-trash-alt', 'danger' => true, 'form' => ['action' => '/vendor/menu/category/delete', 'fields' => ['id' => (int) $c['id']], 'confirm' => t_raw('vendor.menu.confirm_delete_category')]], ]])
@partial('table-toolbar', ['pager' => $pager, 'base' => '/vendor/menu/structure'])
@php /* ---- Serving times --------------------------------------------------------- */ @endphp

{{ t_raw('vendor.menu.schedules_title') }}

{{ t_raw('vendor.menu.schedules_sub') }}

@if ($schedules) @foreach ($schedules as $s) @php /* The COUNT pill stays neutral. It was bound to is_active, so a schedule with nothing attached rendered "0" in green — success colouring on the one number that means the schedule is doing nothing. */ @endphp @endforeach @else @partial('table-empty', ['colspan' => 5, 'icon' => 'fa-clock', 'msg' => t('vendor.menu.empty_schedules')]) @endif
{{ t_raw('common.name') }}{{ t_raw('common.when') }}{{ t_raw('vendor.menu.col_products') }}{{ t_raw('common.status') }}{{ t_raw('common.actions') }}
{{ $s['name'] }} {{ MenuSchedule::describe($s) }}{{ (int) ($scheduleCounts[(int) $s['id']] ?? 0) }} {!! (int) $s['is_active'] === 1 ? t('vendor.menu.schedule_status_active') : t('vendor.menu.schedule_status_off') !!} @partial('row-actions', ['actions' => [ ['label' => t_raw('common.edit'), 'icon' => 'fa-pen', 'primary' => true, 'attrs' => ['data-sched-edit' => '1', 'data-id' => (string) (int) $s['id'], 'data-name' => (string) $s['name'], 'data-mask' => (string) (int) $s['days_mask'], 'data-start' => substr((string) $s['start_time'], 0, 5), 'data-end' => substr((string) $s['end_time'], 0, 5), 'data-active' => (string) (int) $s['is_active'], 'data-bs-toggle' => 'modal', 'data-bs-target' => '#schedModal']], ['label' => t_raw('common.delete'), 'icon' => 'fa-trash-alt', 'danger' => true, 'form' => ['action' => '/vendor/menu/schedules/delete', 'fields' => ['id' => (int) $s['id']], 'confirm' => t_raw('vendor.menu.confirm_delete_schedule')]], ]])
@php /* ---- Kitchen stations ------------------------------------------------------- Where each part of the menu is cooked. A section carries the mapping so a whole category reaches one screen at once; a dish that is made somewhere else overrides it on its own form. Nothing here is required — with no stations the kitchen board is one undivided screen, exactly as it was. */ @endphp

{{ t_raw('vendor.menu.stations_title') }}

{{ t_raw('vendor.menu.stations_sub') }}

@php /* The one figure that decides whether routing is safe to switch on: dishes reaching no station at all. They are not lost — they land on the Unassigned screen — but a kitchen running station screens wants this at zero. */ @endphp @if ($stations)

@if ($unassignedDishes > 0) {{ t_raw('vendor.menu.stations_unassigned_count', [':n' => number_format($unassignedDishes)]) }} @else {{ t_raw('vendor.menu.stations_all_mapped') }} @endif

@endif
@if ($stations) @foreach ($stations as $st) @php $sn = (int) ($stationCounts[(int) $st['id']] ?? 0); @endphp @endforeach @else @partial('table-empty', ['colspan' => 5, 'icon' => 'fa-fire', 'msg' => t('vendor.menu.empty_stations')]) @endif
{{ t_raw('common.name') }}{{ t_raw('vendor.menu.col_dishes') }}{{ t_raw('common.sort_order') }}{{ t_raw('common.status') }}{{ t_raw('common.actions') }}
{{ $st['name'] }} {{ number_format($sn) }} {{ (int) $st['sort_order'] }} {!! (int) $st['is_active'] === 1 ? t('vendor.menu.schedule_status_active') : t('vendor.menu.schedule_status_off') !!} @partial('row-actions', ['actions' => [ ['label' => t_raw('common.edit'), 'icon' => 'fa-pen', 'primary' => true, 'attrs' => ['data-station-edit' => '1', 'data-id' => (string) (int) $st['id'], 'data-name' => (string) $st['name'], 'data-sort' => (string) (int) $st['sort_order'], 'data-active' => (string) (int) $st['is_active'], 'data-bs-toggle' => 'modal', 'data-bs-target' => '#stationModal']], ['label' => t_raw('common.delete'), 'icon' => 'fa-trash-alt', 'danger' => true, 'form' => ['action' => '/vendor/menu/stations/delete', 'fields' => ['id' => (int) $st['id']], 'confirm' => t_raw('vendor.menu.confirm_delete_station')]], ]])
@endsection