@extends('layouts.dashboard') @section('content') @php /** * Vendor → Devices (Terminals): self-order kiosks + POS tills in one registry. * @var array $devices rows: id, type, name, table_id, table_label, table_active, token, * order_mode, accepts_payment, has_card_reader, card_reader_ref, has_printer, * printer_transport, has_drawer, is_active * @var array $tables rows: id, label (active tables for the binding dropdown) * @var array $stats per-device order stats keyed by device id */ use App\Models\Device; $devices = $devices ?? []; $tables = $tables ?? []; $stations = $stations ?? []; $stats = $stats ?? []; $totals = $totals ?? ['orders' => 0, 'revenue' => 0.0, 'active_30d' => 0]; $liveDevices = (int) ($liveDevices ?? 0); $showMoney = $showMoney ?? true; $slug = $restaurant['slug'] ?? ''; $transportLabels = [ 'none' => t_raw('vendor.devices.transport_none'), 'usb' => t_raw('vendor.devices.transport_usb'), 'serial' => t_raw('vendor.devices.transport_serial'), 'hid' => t_raw('vendor.devices.transport_hid'), 'browser' => t_raw('vendor.devices.transport_browser'), ]; @endphp @php /* .qm-d2: the dashboard density standard (app.css §35) */ @endphp
@php // Page primary action → top-bar subheader slot. ob_start(); @endphp @php \App\Support\View::slot('subhead', ob_get_clean()); @endphp
@partial('kpi-card', ['variant' => 'panel', 'accent' => 'blue', 'icon' => 'fa-desktop', 'label' => t_raw('vendor.devices.kpi_devices'), 'value' => number_format($liveDevices), 'meta' => '' . t('vendor.devices.kpi_meta_devices', [':n' => number_format((int) $totals['active_30d'])]) . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'amber', 'icon' => 'fa-power-off', 'label' => t_raw('vendor.devices.kpi_idle'), 'value' => number_format(max(0, $liveDevices - (int) $totals['active_30d'])), 'meta' => '' . t('vendor.devices.kpi_meta_idle') . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'teal', 'icon' => 'fa-receipt', 'label' => t_raw('vendor.devices.kpi_orders'), 'value' => number_format((int) $totals['orders']), 'meta' => '' . t('vendor.devices.kpi_meta_orders') . '']) @if ($showMoney) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'green', 'icon' => 'fa-coins', 'label' => t_raw('vendor.devices.kpi_takings'), 'value' => money($totals['revenue']), 'meta' => '' . t('vendor.devices.kpi_meta_takings') . '']) @partial('kpi-card', ['variant' => 'panel', 'accent' => 'violet', 'icon' => 'fa-chart-line', 'label' => t_raw('vendor.devices.kpi_avg_order'), 'value' => $totals['orders'] > 0 ? money($totals['revenue'] / $totals['orders']) : "—", 'meta' => '' . t('vendor.devices.kpi_meta_avg_order') . '']) @endif

{{ t_raw('vendor.devices.list_heading') }}

@php $type = (string) (($filters ?? [])['type'] ?? ''); $bind = (string) (($filters ?? [])['bind'] ?? ''); $status = (string) (($filters ?? [])['status'] ?? ''); $q = (string) (($filters ?? [])['q'] ?? ''); @endphp
@partial('filter-collapse')
@if ($devices) @foreach ($devices as $d) @php $isPos = ($d['type'] ?? 'kiosk') === Device::TYPE_POS; $isKds = Device::isDisplay($d); $mode = Device::mode($d); $launchUrl = url('/restaurant/' . $slug . '/kiosk?k=' . $d['token']); $posUrl = url('/vendor/pos?device=' . $d['token']); $kdsUrl = url(Device::kdsUrl($d)); @endphp @php $s = $stats[(int) $d['id']] ?? null; @endphp @endforeach @else @partial('table-empty', [ 'colspan' => 7, 'icon' => 'fa-desktop', 'msg' => t_raw('vendor.devices.empty_none'), ]) @endif
{{ t_raw('vendor.devices.col_device') }} {{ t_raw('vendor.devices.col_binding') }} {{ t_raw('vendor.devices.col_capabilities') }} {{ t_raw('vendor.devices.col_performance') }} {{ t_raw('vendor.devices.col_access') }} {{ t_raw('common.status') }} {{ t_raw('common.actions') }}
{{ $d['name'] }}
{{ Device::types()[$d['type']] ?? $d['type'] }} @php /* A display places no orders, so the routing mode says nothing about it. */ @endphp @if (!$isKds){{ Device::orderModes()[$mode] ?? $mode }}@endif
@if ($isKds) @php /* A display bound to no station shows the whole kitchen — stated, because on a divided kitchen that is a decision rather than a gap. */ @endphp @if (!empty($d['station_name'])) {{ (string) $d['station_name'] }} @else {{ t_raw('vendor.devices.pill_whole_kitchen') }} @endif @elseif ($isPos) @elseif ($d['table_id']) {{ t_raw('vendor.devices.pill_dinein_table', [':label' => $d['table_label'] ?? '']) }} @if ($d['table_active'] === null || (int) $d['table_active'] !== 1)
{{ t_raw('vendor.devices.warn_table_inactive') }}
@endif @else {{ t_raw('vendor.devices.pill_counter_orders') }} @endif
@php $caps = []; if (Device::acceptsPayment($d)) { $caps[] = ['green', 'fa-credit-card', t_raw('vendor.devices.cap_payments')]; } if (Device::hasReader($d)) { $caps[] = ['blue', 'fa-wifi', t_raw('vendor.devices.cap_reader')]; } if (Device::hasPrinter($d)) { $caps[] = ['grey', 'fa-print', $transportLabels[Device::printerTransport($d)] ?? t_raw('vendor.devices.cap_printer')]; } if (Device::hasDrawer($d)) { $caps[] = ['grey', 'fa-cash-register', t_raw('vendor.devices.cap_drawer')]; } if (Device::hasScanner($d)) { $caps[] = ['grey', 'fa-barcode', t_raw('vendor.devices.cap_scanner')]; } @endphp @if ($caps) @foreach ($caps as $c) {{ $c[2] }} @endforeach @else {{ t_raw('vendor.devices.cap_none') }} @endif @if ($s === null) {{ t_raw('vendor.devices.perf_no_orders') }} @else {{ t_raw('vendor.devices.perf_orders', [':n' => number_format((int) $s['orders'])]) }} @if ($showMoney)
{{ money($s['revenue']) }} · {{ t_raw('vendor.devices.perf_avg', [':amount' => money($s['avg'])]) }}
@endif @if ((int) $s['orders_30d'] > 0)
{{ t_raw('vendor.devices.perf_last_30d', [':n' => number_format((int) $s['orders_30d'])]) }}
@else
{!! t_raw('vendor.devices.perf_quiet_30d', [':date' => fmt_day($s['last_order'], true)]) !!}
@endif @endif
@if ($isKds) @php /* No token: a kitchen display is staff-facing, so it opens behind the dashboard sign-in like the board itself rather than on a public link. */ @endphp {{ t_raw('vendor.devices.open_kds') }} @elseif ($isPos) {{ t_raw('vendor.devices.open_pos') }} @else {{ $launchUrl }} @endif
@csrf
@partial('row-actions', ['inline' => 0, 'actions' => array_values(array_filter([ $isKds ? [ 'label' => t_raw('common.view'), 'icon' => 'fa-eye', 'href' => Device::kdsUrl($d), 'attrs' => ['target' => '_blank', 'rel' => 'noopener'], ] : ($isPos ? null : [ 'label' => t_raw('common.view'), 'icon' => 'fa-eye', 'href' => '/restaurant/' . $slug . '/kiosk?k=' . $d['token'], 'attrs' => ['target' => '_blank', 'rel' => 'noopener'], ]), [ 'label' => t_raw('common.edit'), 'icon' => 'fa-pen', 'attrs' => [ 'data-bs-toggle' => 'modal', 'data-bs-target' => '#deviceModal', 'data-device-edit' => '1', 'data-id' => (string) (int) $d['id'], 'data-type' => (string) $d['type'], 'data-name' => (string) $d['name'], 'data-table' => (string) (int) ($d['table_id'] ?? 0), 'data-station' => (string) (int) ($d['station_id'] ?? 0), 'data-mode' => $mode, 'data-accepts' => (string) (int) ($d['accepts_payment'] ?? 0), 'data-reader' => (string) (int) ($d['has_card_reader'] ?? 0), 'data-reader-ref' => (string) ($d['card_reader_ref'] ?? ''), 'data-printer' => (string) (int) ($d['has_printer'] ?? 0), 'data-transport' => (string) ($d['printer_transport'] ?? 'none'), 'data-drawer' => (string) (int) ($d['has_drawer'] ?? 0), 'data-autoprint' => (string) (int) ($d['auto_print'] ?? 0), 'data-scanner' => (string) (int) ($d['has_scanner'] ?? 0), 'data-osk' => (string) ($d['osk_mode'] ?? 'auto'), ], ], [ 'label' => t_raw('vendor.devices.aria_remove', [':name' => $d['name']]), 'icon' => 'fa-trash-alt', 'danger' => true, 'form' => [ 'action' => '/vendor/devices/delete', 'fields' => ['id' => (int) $d['id']], 'confirm' => t_raw('vendor.devices.confirm_remove'), ], ], ]))])
@partial('table-toolbar', ['pager' => $pager, 'base' => '/vendor/devices'])
@php // Add / edit a device: one modal, filled from the row for edits. @endphp
@php /* /.qm-d2 */ @endphp @endsection