@extends('layouts.dashboard')
@section('content')
@php
use App\Models\Order;
/**
* Admin → Restaurants → one restaurant. The operator's whole view of a partner:
* who owns it, what it is billed, what it has traded, what it sells, who works
* there and what has been done to it. Built from the shared dashboard components
* (.qm-card / kpi-card / .qm-table / .qm-order-totals / plan_tag / [data-qm-map])
* — read-only bar the status + featured forms carried over from the list.
*
* @var array $restaurant the restaurant row (+ cuisine_name / cuisine_slug)
* @var array|null $owner owner account (id, name, email, avatar, status) or null
* @var array|null $plan plan behind the latest subscription, or the signup tier
* @var array|null $sub the latest subscription row, or null
* @var string $subState active | lapsed | pending | none
* @var int $ordersC lifetime order count
* @var float $collected lifetime paid revenue
* @var float $commission lifetime platform commission stamped on this partner's orders
* @var array $recent30 orders / paid / gross / refunds / net for the last 30 days
* @var array $staff staff + drivers attached to this restaurant
* @var int $staffCount how many of them
* @var int $menuCount menu items
* @var int $menuLive menu items currently available
* @var array $recentOrders the last 8 orders
* @var array $activity the last 10 privileged actions against this restaurant
*/
$status = (string) $restaurant['status'];
$statusPill = ['approved' => 'qm-pill-green', 'pending' => 'qm-pill-amber', 'suspended' => 'qm-pill-red'][$status] ?? 'qm-pill-grey';
$actions = $status === 'approved'
? ['suspended' => t_raw('admin.restaurants.action_suspend')]
: ($status === 'pending' ? ['approved' => t_raw('admin.campaigns.approve'), 'suspended' => t_raw('admin.restaurants.action_reject')] : ['approved' => t_raw('admin.restaurants.action_reinstate')]);
$cuisineFlag = !empty($restaurant['cuisine_slug']) ? cuisine_flag((string) $restaurant['cuisine_slug']) : null;
$subState = $subState ?? 'none';
$recent30 = $recent30 ?? ['orders' => 0, 'paid' => 0, 'gross' => 0.0, 'refunds' => 0.0, 'net' => 0.0];
$staff = $staff ?? [];
$recentOrders = $recentOrders ?? [];
$activity = $activity ?? [];
$subPill = ['active' => 'qm-pill-green', 'lapsed' => 'qm-pill-red', 'pending' => 'qm-pill-amber', 'none' => 'qm-pill-grey'];
$payPill = ['paid' => 'qm-pill-green', 'unpaid' => 'qm-pill-amber', 'refunded' => 'qm-pill-red'];
$userPill = ['active' => 'qm-pill-green', 'pending' => 'qm-pill-amber', 'suspended' => 'qm-pill-red'];
@endphp
@php /* .qm-d2: the dashboard density standard (app.css §35) */
@endphp
{{ t_raw('admin.restaurantdetail.back') }}
@php // Opens THIS restaurant's vendor dashboard by adopting its owner account —
// the tenant scope follows the identity, so no separate "view as" plumbing.
// The prompt names the ACCOUNT being signed into, so it reads $owner: the
// restaurant row is r.* plus the cuisine columns and carries no owner_name,
// so asking it for one always fell through to the RESTAURANT's own name.
@endphp
@php /* The partner's identity wears the same hero the vendor plan card wears:
a brand-wash band (mark, name, pills — actions on the right) over the
facts as one measured stat band. Same classes, same voice. */
@endphp
@php /* Same identity move as the restaurant cards: the cover leads, and the
venue's mark straddles its lower edge. */
@endphp
 ($restaurant['cover_image'] ?? ''), 'assets/client/covers/shop-1.png') }})
@if ($cuisineFlag)
![{{ (string) ($restaurant['cuisine_name'] ?? '') }}]({{ $cuisineFlag }})
@endif
@php /* Standing rides the cover, as it does on the card - it was repeating
below the name while this band carried nothing but a flag. */
@endphp
{{ status_label('restaurant', $status) }}
{!! !empty($restaurant['is_featured']) ? t('admin.restaurants.pill_featured') : t('admin.restaurants.pill_not_featured') !!}
{{ t_raw('admin.restaurants.billing_' . $subState) }}
{!! restaurant_logo_html($restaurant, 'lg', 'qm-brandmark--round') !!}
{{ $restaurant['name'] }}
@if (!empty($restaurant['cuisine_name']) || !empty($restaurant['city']))
{{ trim((string) ($restaurant['cuisine_name'] ?? '') . ' · ' . (string) ($restaurant['city'] ?? ''), ' ·') }}
@endif
- {{ t_raw('common.contact') }}
-
{!! or_dash($restaurant['phone'] ?? null) !!}
{!! or_dash($restaurant['email'] ?? null) !!}
- {{ t_raw('admin.restaurantdetail.plan') }}
-
@if ($plan)
{!! plan_tag((string) $plan['plan_slug'], (string) $plan['plan_name']) !!}
{{ money($plan['price']) }} · {{ t_raw('status.billing_cycle.' . (string) $plan['billing_cycle']) }}
@else
{{ t_raw('admin.restaurantdetail.no_plan') }}
@endif
- {{ t_raw('admin.restaurantdetail.subscription_term') }}
-
@if ($sub)
@php // A term with no end date is a lifetime plan, not a missing value.
@endphp
{!! fmt_day($sub['starts_at']) !!} – {!! empty($sub['ends_at']) ? t('admin.subscriptions.lifetime') : fmt_day($sub['ends_at']) !!}
{{ t_raw('admin.restaurantdetail.last_payment', [':amount' => money($sub['amount_paid'])]) }}
@else
{{ t_raw('admin.restaurantdetail.never_subscribed') }}
@endif
- {{ t_raw('admin.restaurantdetail.commission') }}
-
{!! $plan ? e(number_format((float) $plan['commission_percent'], 1)) . '%' : '' . t('common.none') . '' !!}
{{ t_raw('admin.restaurantdetail.commission_earned', [':amount' => money($commission ?? 0)]) }}
@php /* The one thing an admin comes here to DO, sitting where the facts end:
same solid call-to-action the restaurant cards and the dashboard's
Payouts / Subscriptions panels close with. */
@endphp
@if (!empty($restaurant['owner_user_id']))
@endif
@partial('kpi-card', ['accent' => 'blue', 'icon' => 'fa-receipt', 'label' => t_raw('admin.restaurantdetail.kpi_orders'), 'value' => number_format($ordersC)])
@partial('kpi-card', ['accent' => 'green', 'icon' => 'fa-money-bill-wave', 'label' => t_raw('admin.restaurantdetail.kpi_revenue'), 'value' => money($collected)])
@partial('kpi-card', ['accent' => 'amber', 'icon' => 'fa-star', 'label' => t_raw('admin.restaurantdetail.kpi_rating'), 'value' => number_format((float) $restaurant['rating_cache'], 1) . ' (' . (int) $restaurant['rating_count'] . ')'])
@partial('kpi-card', ['accent' => 'violet', 'icon' => 'fa-users', 'label' => t_raw('admin.restaurantdetail.kpi_staff'), 'value' => number_format($staffCount)])
@partial('kpi-card', ['accent' => 'teal', 'icon' => 'fa-utensils', 'label' => t_raw('admin.restaurantdetail.kpi_menu'), 'value' => number_format($menuLive) . ' / ' . number_format($menuCount)])
@php $showPayee = \App\Services\BusinessModel::producesPayouts(); $showGateway = \App\Services\BusinessModel::restaurantOwnsGateway();
@endphp
{{ t_raw('admin.restaurantdetail.sec_trading') }}
{{ t_raw('admin.dashboard.last_30_days') }}
@php /* Whether this partner is trading NOW — the lifetime KPIs above cannot say. */
@endphp
{{ t_raw('admin.orders.total_orders') }}{{ number_format((int) $recent30['orders']) }}
{{ t_raw('common.paid_orders') }}{{ number_format((int) $recent30['paid']) }}
{{ t_raw('admin.orders.total_gross') }}{{ money($recent30['gross']) }}
{{ t_raw('common.refunded') }}{{ (float) $recent30['refunds'] > 0 ? '−' . money($recent30['refunds']) : money(0) }}
{{ t_raw('admin.orders.total_net') }}{{ money($recent30['net']) }}
@php // Payee — read-only. Where this restaurant's payout is sent (marketplace); the vendor
// sets it on their own settings. Only shown when the platform actually pays them.
@endphp
@if ($showPayee)
@php $poMethod = (string) ($restaurant['payout_method'] ?? 'none'); @endphp
{{ t_raw('admin.restaurantdetail.sec_payee') }}
@if ($poMethod === 'none'){{ t_raw('admin.restaurantdetail.payee_missing') }}@else{{ t_raw('admin.restaurantdetail.payee_on_file') }}@endif
@if ($poMethod !== 'none')
{{ t_raw('vendor.settings.payee_method') }}{{ ucfirst($poMethod) }}
{{ t_raw('vendor.settings.payee_account') }}{{ (string) ($restaurant['payout_account'] ?? '') }}
@if (!empty($restaurant['payout_name'])){{ t_raw('vendor.settings.payee_name') }}{{ (string) $restaurant['payout_name'] }}@endif
@else
{{ t_raw('admin.restaurantdetail.payee_missing_hint') }}
@endif
@endif
@php /* Card payment — the OTHER half of the same readiness question the payee card answers, and
the half that was silent. Marketplace asks "can the platform SEND this restaurant its
money", and reports the answer above; franchise asks "can this restaurant TAKE the
customer's", because BusinessModel::restaurantOwnsGateway() routes every online charge to
ITS OWN keys. A venue that has stored none offers no card tender at all at its checkout,
while the platform's own pair — which that checkout never touches — still reads as
configured on the Payments tab, so nothing anywhere said the till had closed.
Gated on restaurantOwnsGateway() exactly as the block above is gated on producesPayouts():
it is the one model where this is the restaurant's own credential, and it already folds in
the split-payments overlay (under split the platform collects on its keys again, so the
franchisee's are unused and the question does not apply).
The verdict is Payment::anyGatewayEnabled() — the same call the storefront makes — so the
platform enable flags, the split-gateway lock and the cURL requirement stay answered in one
place rather than re-derived here. Only WHETHER a pair is stored is ever shown: these are
secret keys and no value is printed. */
@endphp
@if ($showGateway)
@php $gwReady = \App\Services\Payment::anyGatewayEnabled((int) $restaurant['id']);
// Both halves of a pair are required, matching Payment::isEnabled(): a secret with no
// publishable key renders a checkout the customer can never complete.
$hasStripe = trim((string) ($restaurant['stripe_secret_key'] ?? '')) !== '' && trim((string) ($restaurant['stripe_public_key'] ?? '')) !== '';
$hasPaypal = trim((string) ($restaurant['paypal_client_id'] ?? '')) !== '' && trim((string) ($restaurant['paypal_client_secret'] ?? '')) !== ''; @endphp
{{ t_raw('admin.restaurantdetail.sec_gateway') }}
@if ($gwReady){{ t_raw('admin.restaurantdetail.gateway_on_file') }}@else{{ t_raw('admin.restaurantdetail.gateway_missing') }}@endif
Stripe{!! $hasStripe ? t('admin.restaurantdetail.payee_on_file') : t('admin.restaurantdetail.payee_missing') !!}
PayPal{!! $hasPaypal ? t('admin.restaurantdetail.payee_on_file') : t('admin.restaurantdetail.payee_missing') !!}
@if (!$gwReady)
{{ t_raw('admin.restaurantdetail.gateway_missing_hint') }}
@endif
@endif
| {{ t_raw('admin.dashboard.col_order') }} | {{ t_raw('common.customer') }} | {{ t_raw('admin.orders.col_total') }} | {{ t_raw('admin.orders.col_payment') }} | {{ t_raw('common.status') }} | {{ t_raw('admin.orders.col_placed') }} | {{ t_raw('common.actions') }} |
@if ($recentOrders)
@foreach ($recentOrders as $o)
| {{ $o['order_number'] }} |
@php $custName = trim((string) ($o['customer_name'] ?? '')) ?: t_raw('common.customer');
@endphp
{!! brandmark_html($o['customer_avatar'] ?? null, $custName, 'md', 'qm-brandmark--round') !!}
{{ $custName }}
|
{{ money($o['total']) }} |
{{ t_raw('status.payment.' . $o['payment_status']) }}
@php $m = (string) $o['payment_method']; $ico = payment_method_icon($m); echo $ico !== '' ? $ico : t('status.payment_method.' . $m);
@endphp
|
{{ Order::statusLabel((string) $o['status'], (string) ($o['service_mode'] ?? 'delivery')) }} |
{!! fmt_datetime($o['placed_at']) !!} |
@partial('row-actions', ['actions' => [
['label' => t_raw('common.view'), 'icon' => 'fa-eye', 'href' => '/admin/orders/' . (int) $o['id'], 'primary' => true],
]])
|
@endforeach
@else
@partial('table-empty', ['colspan' => 7, 'icon' => 'fa-receipt', 'msg' => t('admin.restaurantdetail.no_orders')])
@endif
@partial('table-toolbar', ['pager' => $ordersPager ?? []])
@php /* Reviews: the list page owns moderation, this is the restaurant-scoped read.
Same card, table and pager language as Recent orders above it. */
@endphp
{{ t_raw('admin.reviews.title') }}
@if (!empty($reviewsPager['total'])){{ t_raw('common.count_total', [':count' => (int) $reviewsPager['total']]) }}@endif
{{ t_raw('common.view_all') }}
| {{ t_raw('common.customer') }} |
{{ t_raw('admin.restaurants.col_rating') }} |
{{ t_raw('admin.reviews.col_review') }} |
{{ t_raw('common.status') }} |
{{ t_raw('admin.reviews.col_posted') }} |
@if (!empty($reviews))
@foreach ($reviews as $rvw)
@php $rvName = trim((string) ($rvw['customer_name'] ?? '')) !== '' ? (string) $rvw['customer_name'] : (string) ($rvw['author_name'] ?? '');
@endphp
{!! brandmark_html($rvw['customer_avatar'] ?? null, $rvName, 'md', 'qm-brandmark--round') !!}
{{ $rvName }}
|
{!! rating_stars_html((float) $rvw['rating']) !!} |
{{ (string) ($rvw['comment'] ?? '') }} |
{{ status_label('review', (string) $rvw['status']) }} |
{!! fmt_day($rvw['created_at']) !!} |
@endforeach
@else
@partial('table-empty', ['colspan' => 5, 'icon' => 'fa-star', 'msg' => t('admin.reviews.empty_none')])
@endif
@partial('table-toolbar', ['pager' => $reviewsPager, 'base' => '/admin/restaurants/' . (int) $restaurant['id']])
{{ t_raw('admin.restaurantdetail.sec_team') }}
@php // The whole roster, on the Users screen, scoped to THIS restaurant — the same
// ?restaurant_id= the Recent orders panel above uses. It asked by ?q= before,
// which searches a name or an email and so could never match a venue: the
// button below a full team panel always landed on "no users match".
@endphp
{{ t_raw('nav.users') }}
| {{ t_raw('common.name') }} | {{ t_raw('admin.users.f_role') }} | {{ t_raw('common.status') }} | {{ t_raw('common.actions') }} |
@if ($staff)
@foreach ($staff as $m)
{!! brandmark_html($m['avatar'] ?? null, (string) $m['name'], 'md', 'qm-brandmark--round') !!}
{!! $m['name'] !!}
{!! $m['email'] !!}
|
{!! role_pill((string) $m['role']) !!} |
{{ status_label('user', (string) $m['status']) }} |
@partial('row-actions', ['actions' => [
['label' => t_raw('common.view'), 'icon' => 'fa-eye', 'href' => '/admin/users/' . (int) $m['id'], 'primary' => true],
]])
|
@endforeach
@else
@partial('table-empty', ['colspan' => 4, 'icon' => 'fa-users', 'msg' => t('admin.restaurantdetail.no_staff')])
@endif
@partial('table-toolbar', ['pager' => $staffPager ?? []])
@php /* The platform audit trail, scoped to this partner: approvals, suspensions,
refunds and moderation, newest first. */
@endphp
| {{ t_raw('admin.restaurantdetail.col_action') }} | {{ t_raw('admin.restaurantdetail.col_by') }} | {{ t_raw('common.description') }} | {{ t_raw('common.when') }} |
@if ($activity)
@foreach ($activity as $a)
| {{ activity_label((string) $a['action']) }} |
@if (!empty($a['actor_name']))
{!! brandmark_html($a['actor_avatar'] ?? null, (string) $a['actor_name'], 'sm', 'qm-brandmark--round') !!}
{{ $a['actor_name'] }}@if (!empty($a['role'])){{ role_label((string) $a['role']) }}@endif
@else{!! dash() !!}@endif
|
{!! or_dash($a['detail'] ?? null) !!} |
{{ time_ago($a['created_at']) }} |
@endforeach
@else
@partial('table-empty', ['colspan' => 4, 'icon' => 'fa-clipboard-list', 'msg' => t('admin.restaurantdetail.no_activity')])
@endif
@partial('table-toolbar', ['pager' => $activityPager ?? []])
{{ t_raw('admin.restaurantdetail.sec_location') }}
@if (!empty($restaurant['address']) || !empty($restaurant['city']))
{{ trim(($restaurant['address'] ?? '') . ' ' . ($restaurant['city'] ?? '')) }}
@endif
@if ($restaurant['lat'] !== null && $restaurant['lng'] !== null)
@php // The pin is the partner's own mark — the same brand pin every dashboard map draws.
$locMarker = [[
'lat' => (float) $restaurant['lat'],
'lng' => (float) $restaurant['lng'],
'label' => (string) $restaurant['name'],
'iconHtml' => restaurant_logo_html($restaurant, 'sm', '', ''),
]];
@endphp
@else
{{ t_raw('admin.restaurantdetail.no_location') }}
@endif
@partial('restaurant-status-reason')
@php /* The delete dialog: what goes, and the admin's own password before it does. The dialog IS the
confirmation — a password typed on purpose is a stronger "yes" than a second popup. */
@endphp
@php /* /.qm-d2 */
@endphp
@endsection