Redesign display admin show page and public waiting-area screen.
Deploy Ladill Queue / deploy (push) Successful in 33s
Deploy Ladill Queue / deploy (push) Successful in 33s
Add corporate layout, live preview, URL copy, queue details, and a polished TV display with clock, stats sidebar, and branded now-serving cards. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,6 +19,7 @@ class DisplayPublicController extends Controller
|
||||
public function show(string $token): View
|
||||
{
|
||||
$screen = $this->displays->findByToken($token) ?? abort(404);
|
||||
$screen->loadMissing(['organization', 'branch']);
|
||||
$this->displays->touch($screen);
|
||||
|
||||
return view('qms.display.public', [
|
||||
|
||||
@@ -44,9 +44,23 @@ class DisplayScreenController extends Controller
|
||||
{
|
||||
$this->authorizeAbility($request, 'displays.view');
|
||||
$this->authorizeOwner($request, $display);
|
||||
$display->load('branch');
|
||||
$display->load(['branch', 'organization']);
|
||||
|
||||
return view('qms.displays.show', compact('display'));
|
||||
$member = app(OrganizationResolver::class)->memberFor($request->user());
|
||||
$permissions = app(QmsPermissions::class);
|
||||
|
||||
$queueIds = array_map('intval', $display->service_queue_ids ?? []);
|
||||
$queues = ServiceQueue::query()
|
||||
->whereIn('id', $queueIds)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('qms.displays.show', [
|
||||
'display' => $display,
|
||||
'queues' => $queues,
|
||||
'publicUrl' => route('qms.display.public', $display->access_token),
|
||||
'canManage' => $permissions->can($member, 'displays.manage'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
|
||||
@@ -26,6 +26,7 @@ class DisplayService
|
||||
*/
|
||||
public function payload(DisplayScreen $screen): array
|
||||
{
|
||||
$screen->loadMissing(['branch', 'organization']);
|
||||
$queueIds = $screen->service_queue_ids ?? [];
|
||||
$queues = ServiceQueue::query()
|
||||
->whereIn('id', $queueIds)
|
||||
@@ -62,6 +63,8 @@ class DisplayService
|
||||
'screen' => [
|
||||
'name' => $screen->name,
|
||||
'layout' => $screen->layout,
|
||||
'branch_name' => $screen->branch?->name,
|
||||
'organization_name' => $screen->organization?->name,
|
||||
],
|
||||
'now_serving' => $nowServing,
|
||||
'waiting_count' => $waiting,
|
||||
|
||||
@@ -267,3 +267,53 @@ html.qr-mobile-page body {
|
||||
box-shadow: 0 1px 2px rgb(15 23 42 / 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Public queue display — waiting-area TV screens */
|
||||
.qms-display {
|
||||
min-height: 100vh;
|
||||
background:
|
||||
radial-gradient(ellipse 80% 50% at 50% -10%, rgb(49 46 129 / 0.45), transparent),
|
||||
linear-gradient(180deg, rgb(15 23 42) 0%, rgb(2 6 23) 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.qms-display__grid-bg {
|
||||
background-image:
|
||||
linear-gradient(rgb(255 255 255 / 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgb(255 255 255 / 0.03) 1px, transparent 1px);
|
||||
background-size: 48px 48px;
|
||||
}
|
||||
|
||||
.qms-display__ticket {
|
||||
background: linear-gradient(145deg, rgb(67 56 202) 0%, rgb(79 70 229) 45%, rgb(99 102 241) 100%);
|
||||
box-shadow:
|
||||
0 0 0 1px rgb(255 255 255 / 0.08),
|
||||
0 20px 40px -12px rgb(0 0 0 / 0.45);
|
||||
}
|
||||
|
||||
.qms-display__ticket-number {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.04em;
|
||||
text-shadow: 0 2px 16px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
.qms-display__stat {
|
||||
background: rgb(255 255 255 / 0.04);
|
||||
border: 1px solid rgb(255 255 255 / 0.08);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.qms-display__live-dot {
|
||||
animation: qms-display-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes qms-display-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.45; }
|
||||
}
|
||||
|
||||
.qms-display__unlock {
|
||||
background:
|
||||
radial-gradient(ellipse 60% 40% at 50% 0%, rgb(79 70 229 / 0.35), transparent),
|
||||
rgb(2 6 23 / 0.96);
|
||||
}
|
||||
|
||||
@@ -99,6 +99,8 @@ export function registerKioskFlow(Alpine) {
|
||||
isAnnouncing: false,
|
||||
speechReady: false,
|
||||
voices: [],
|
||||
clock: '',
|
||||
dateLabel: '',
|
||||
dataUrl: typeof config === 'string' ? config : config.dataUrl,
|
||||
playedUrlTemplate: config.playedUrl ?? null,
|
||||
pollMs: config.pollMs ?? 1200,
|
||||
@@ -267,6 +269,8 @@ export function registerKioskFlow(Alpine) {
|
||||
|
||||
init() {
|
||||
this.primeSpeech();
|
||||
this.updateClock();
|
||||
setInterval(() => this.updateClock(), 1000);
|
||||
|
||||
try {
|
||||
if (sessionStorage.getItem(DISPLAY_AUDIO_KEY) === '1') {
|
||||
@@ -279,6 +283,16 @@ export function registerKioskFlow(Alpine) {
|
||||
this.poll();
|
||||
setInterval(() => this.poll(), this.pollMs);
|
||||
},
|
||||
|
||||
updateClock() {
|
||||
const now = new Date();
|
||||
this.clock = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
this.dateLabel = now.toLocaleDateString([], {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.data('mobileTracker', (pollUrl) => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full bg-slate-950 text-white">
|
||||
<html lang="en" class="h-full">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -7,58 +7,145 @@
|
||||
<title>{{ $screen->name }} · Queue Display</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen p-8" x-data="displayBoard({ dataUrl: '{{ $dataUrl }}', playedUrl: '{{ $playedUrl }}', pollMs: {{ $pollMs }}, csrf: '{{ csrf_token() }}' })" x-init="init()">
|
||||
<body
|
||||
class="qms-display qms-display__grid-bg"
|
||||
x-data="displayBoard({ dataUrl: '{{ $dataUrl }}', playedUrl: '{{ $playedUrl }}', pollMs: {{ $pollMs }}, csrf: '{{ csrf_token() }}' })"
|
||||
x-init="init()"
|
||||
>
|
||||
{{-- Voice unlock — required by browsers before TTS --}}
|
||||
<div
|
||||
x-cloak
|
||||
x-show="!speechReady"
|
||||
@click="unlockSpeech()"
|
||||
@keydown.enter.prevent="unlockSpeech()"
|
||||
tabindex="0"
|
||||
class="fixed inset-0 z-50 flex cursor-pointer items-center justify-center bg-slate-950/95 p-8 text-center outline-none"
|
||||
class="qms-display__unlock fixed inset-0 z-50 flex cursor-pointer items-center justify-center p-8 text-center outline-none"
|
||||
role="button"
|
||||
aria-label="Enable voice announcements"
|
||||
>
|
||||
<div class="max-w-lg">
|
||||
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-indigo-500/20 ring-1 ring-indigo-400/30">
|
||||
<svg class="h-8 w-8 text-indigo-300" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 0 1 0 12.728M16.463 8.288a5.25 5.25 0 0 1 0 7.424M6.75 8.25l4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-6 text-3xl font-semibold tracking-tight text-white">Tap to enable announcements</p>
|
||||
<p class="mt-3 text-base text-slate-400">Your browser needs one interaction before this screen can call tickets aloud.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-screen flex-col">
|
||||
{{-- Header --}}
|
||||
<header class="border-b border-white/10 bg-slate-950/60 backdrop-blur-md">
|
||||
<div class="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4 px-6 py-5 lg:px-10">
|
||||
<div class="min-w-0">
|
||||
<p
|
||||
class="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-300"
|
||||
x-text="payload?.screen?.organization_name ?? '{{ $screen->organization?->name ?? 'Queue display' }}'"
|
||||
></p>
|
||||
<h1
|
||||
class="mt-1 truncate text-2xl font-semibold tracking-tight text-white lg:text-3xl"
|
||||
x-text="payload?.screen?.name ?? '{{ $screen->name }}'"
|
||||
></h1>
|
||||
<p
|
||||
class="mt-1 text-sm text-slate-400"
|
||||
x-show="payload?.screen?.branch_name"
|
||||
x-text="payload?.screen?.branch_name"
|
||||
></p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-xs font-medium text-emerald-300">
|
||||
<span class="qms-display__live-dot h-2 w-2 rounded-full bg-emerald-400"></span>
|
||||
Live
|
||||
</div>
|
||||
<p class="mt-2 font-mono text-4xl font-semibold tabular-nums tracking-tight text-white" x-text="clock"></p>
|
||||
<p class="text-sm text-slate-400" x-text="dateLabel"></p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- Main --}}
|
||||
<main class="mx-auto flex w-full max-w-7xl flex-1 flex-col gap-6 px-6 py-8 lg:flex-row lg:gap-8 lg:px-10 lg:py-10">
|
||||
{{-- Now serving --}}
|
||||
<section class="flex flex-1 flex-col lg:min-h-[28rem]">
|
||||
<div class="mb-4 flex items-end justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-3xl font-semibold text-white">Tap to enable voice announcements</p>
|
||||
<p class="mt-3 text-slate-400">Browsers require one tap before the display can speak ticket calls.</p>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-300">Now serving</p>
|
||||
<p class="mt-1 text-sm text-slate-400">Please proceed to your counter when called</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<div class="mb-8 flex items-center justify-between">
|
||||
<h1 class="text-4xl font-bold" x-text="payload?.screen?.name ?? '{{ $screen->name }}'"></h1>
|
||||
<p class="text-slate-400" x-text="payload?.updated_at ? 'Updated ' + new Date(payload.updated_at).toLocaleTimeString() : ''"></p>
|
||||
</div>
|
||||
|
||||
<div class="mb-10">
|
||||
<h2 class="mb-4 text-sm font-semibold uppercase tracking-widest text-indigo-300">Now serving</h2>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<template x-for="item in (payload?.now_serving ?? [])" :key="item.ticket_number + item.counter">
|
||||
<div class="rounded-2xl bg-indigo-600 p-8 text-center shadow-lg">
|
||||
<p class="text-5xl font-bold" x-text="item.ticket_number"></p>
|
||||
<p class="mt-2 text-xl" x-text="item.queue_name"></p>
|
||||
<p class="mt-4 text-2xl font-semibold" x-text="'→ ' + (item.counter ?? '')"></p>
|
||||
<div class="flex flex-1 flex-col gap-4" x-show="(payload?.now_serving ?? []).length">
|
||||
<template x-for="item in (payload?.now_serving ?? [])" :key="item.ticket_number + (item.counter ?? '')">
|
||||
<article class="qms-display__ticket flex flex-1 flex-col items-center justify-center rounded-3xl px-8 py-10 text-center">
|
||||
<p class="text-sm font-semibold uppercase tracking-[0.25em] text-indigo-200">Ticket number</p>
|
||||
<p class="qms-display__ticket-number mt-3 text-6xl font-bold text-white sm:text-7xl lg:text-8xl" x-text="item.ticket_number"></p>
|
||||
<p class="mt-4 text-xl text-indigo-100" x-text="item.queue_name"></p>
|
||||
<div class="mt-8 inline-flex items-center gap-3 rounded-full bg-black/20 px-6 py-3 ring-1 ring-white/10">
|
||||
<span class="text-sm font-medium uppercase tracking-widest text-indigo-200">Proceed to</span>
|
||||
<span class="text-2xl font-semibold text-white" x-text="item.counter ?? '—'"></span>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="qms-display__stat flex flex-1 flex-col items-center justify-center rounded-3xl px-8 py-16 text-center"
|
||||
x-show="!(payload?.now_serving ?? []).length"
|
||||
>
|
||||
<div class="flex h-14 w-14 items-center justify-center rounded-2xl bg-white/5 ring-1 ring-white/10">
|
||||
<svg class="h-7 w-7 text-slate-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="mt-5 text-2xl font-semibold text-white">Please wait</p>
|
||||
<p class="mt-2 max-w-sm text-slate-400">Your ticket number will appear here when it is called.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- Stats sidebar --}}
|
||||
<aside class="grid w-full gap-4 lg:w-80 lg:shrink-0">
|
||||
<div class="qms-display__stat rounded-2xl p-6 text-center">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Waiting</p>
|
||||
<p class="mt-2 text-5xl font-bold tabular-nums text-white" x-text="payload?.waiting_count ?? '—'"></p>
|
||||
<p class="mt-1 text-sm text-slate-500">customers in queue</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-3">
|
||||
<div class="rounded-2xl border border-slate-700 bg-slate-900 p-6 text-center">
|
||||
<p class="text-sm uppercase text-slate-400">Waiting</p>
|
||||
<p class="mt-2 text-5xl font-bold" x-text="payload?.waiting_count ?? '—'"></p>
|
||||
<div class="qms-display__stat rounded-2xl p-6 text-center">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Estimated wait</p>
|
||||
<p class="mt-2 text-5xl font-bold tabular-nums text-white">
|
||||
<span x-text="payload?.estimated_wait_minutes ? payload.estimated_wait_minutes : '—'"></span>
|
||||
<span class="text-2xl font-medium text-slate-400" x-show="payload?.estimated_wait_minutes">min</span>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-slate-500">based on today’s average</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-700 bg-slate-900 p-6 text-center">
|
||||
<p class="text-sm uppercase text-slate-400">Est. wait</p>
|
||||
<p class="mt-2 text-5xl font-bold" x-text="payload?.estimated_wait_minutes ? payload.estimated_wait_minutes + ' min' : '—'"></p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-700 bg-slate-900 p-6">
|
||||
<p class="mb-3 text-sm uppercase text-slate-400">Queues</p>
|
||||
|
||||
<div class="qms-display__stat rounded-2xl p-6">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Service queues</p>
|
||||
<ul class="mt-4 space-y-2">
|
||||
<template x-for="q in (payload?.queues ?? [])" :key="q.name">
|
||||
<p class="text-lg" x-text="q.name + (q.is_paused ? ' (paused)' : '')"></p>
|
||||
<li class="flex items-center justify-between gap-3 rounded-xl bg-white/5 px-3 py-2.5 ring-1 ring-white/5">
|
||||
<span class="truncate font-medium text-white" x-text="q.name"></span>
|
||||
<span
|
||||
class="shrink-0 rounded-full px-2 py-0.5 text-xs font-semibold"
|
||||
:class="q.is_paused ? 'bg-amber-500/15 text-amber-300' : 'bg-emerald-500/15 text-emerald-300'"
|
||||
x-text="q.is_paused ? 'Paused' : 'Open'"
|
||||
></span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<p class="mt-4 text-xs text-slate-500" x-show="!(payload?.queues ?? []).length">No queues configured for this display.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
{{-- Footer --}}
|
||||
<footer class="border-t border-white/10 bg-slate-950/60 px-6 py-4 lg:px-10">
|
||||
<div class="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-3 text-xs text-slate-500">
|
||||
<p>Powered by Ladill Queue</p>
|
||||
<p x-show="payload?.updated_at" x-text="payload?.updated_at ? 'Updated ' + new Date(payload.updated_at).toLocaleTimeString() : ''"></p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,147 @@
|
||||
@php
|
||||
$layoutLabel = config('qms.display_layouts.'.$display->layout, $display->layout);
|
||||
$isOnline = $display->last_seen_at && $display->last_seen_at->gt(now()->subMinutes(2));
|
||||
@endphp
|
||||
|
||||
<x-app-layout :title="$display->name">
|
||||
<div class="mb-6 flex justify-between">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<a href="{{ route('qms.displays.index') }}" class="text-sm text-indigo-600">← Displays</a>
|
||||
<h1 class="mt-2 text-2xl font-semibold">{{ $display->name }}</h1>
|
||||
<a href="{{ route('qms.displays.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Displays</a>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-3">
|
||||
<h1 class="text-xl font-semibold text-slate-900">{{ $display->name }}</h1>
|
||||
@if ($display->is_active)
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700 ring-1 ring-emerald-200">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-emerald-500"></span>
|
||||
Active
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-600 ring-1 ring-slate-200">Inactive</span>
|
||||
@endif
|
||||
@if ($isOnline)
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full bg-sky-50 px-2.5 py-1 text-xs font-semibold text-sky-700 ring-1 ring-sky-200">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-sky-500"></span>
|
||||
Display online
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<a href="{{ route('qms.displays.edit', $display) }}" class="btn-secondary">Edit</a>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
{{ $display->branch?->name ?? 'No branch' }}
|
||||
· {{ $layoutLabel }} layout
|
||||
@if ($display->last_seen_at)
|
||||
· Last seen {{ $display->last_seen_at->diffForHumans() }}
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm text-slate-600">Layout: {{ config('qms.display_layouts.'.$display->layout) }}</p>
|
||||
<p class="mt-2 text-sm"><a href="{{ route('qms.display.public', $display->access_token) }}" class="text-indigo-600" target="_blank">Open public display</a></p>
|
||||
<div class="btn-group">
|
||||
@if ($canManage)
|
||||
<a href="{{ route('qms.displays.edit', $display) }}" class="btn-secondary">Edit display</a>
|
||||
@endif
|
||||
<a href="{{ $publicUrl }}" target="_blank" rel="noopener" class="btn-primary">Open public display</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid gap-4 lg:grid-cols-3">
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5 lg:col-span-2">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Assigned queues</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Tickets from these queues appear on this screen and trigger voice calls.</p>
|
||||
|
||||
@if ($queues->isEmpty())
|
||||
<div class="mt-4 rounded-xl border border-dashed border-slate-200 bg-slate-50 px-4 py-8 text-center">
|
||||
<p class="text-sm font-medium text-slate-700">No queues assigned</p>
|
||||
<p class="mt-1 text-sm text-slate-500">Edit this display and select the service queues for your waiting area.</p>
|
||||
@if ($canManage)
|
||||
<a href="{{ route('qms.displays.edit', $display) }}" class="btn-secondary mt-4">Assign queues</a>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<ul class="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
@foreach ($queues as $queue)
|
||||
<li class="flex items-start gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
|
||||
<span class="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-white text-xs font-bold text-indigo-700 ring-1 ring-indigo-100">
|
||||
{{ $queue->prefix }}
|
||||
</span>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate font-medium text-slate-900">{{ $queue->name }}</p>
|
||||
<p class="text-xs text-slate-500">
|
||||
{{ $queue->is_paused ? 'Paused' : 'Accepting tickets' }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Display details</h2>
|
||||
<dl class="mt-4 space-y-3 text-sm">
|
||||
<div class="flex justify-between gap-4">
|
||||
<dt class="text-slate-500">Organization</dt>
|
||||
<dd class="text-right font-medium text-slate-900">{{ $display->organization?->name ?? '—' }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<dt class="text-slate-500">Branch</dt>
|
||||
<dd class="text-right font-medium text-slate-900">{{ $display->branch?->name ?? '—' }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<dt class="text-slate-500">Layout</dt>
|
||||
<dd class="text-right font-medium text-slate-900">{{ $layoutLabel }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<dt class="text-slate-500">Queues</dt>
|
||||
<dd class="text-right font-medium text-slate-900">{{ $queues->count() }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-2xl border border-slate-200 bg-white p-5"
|
||||
x-data="{
|
||||
url: @js($publicUrl),
|
||||
copied: false,
|
||||
async copy() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.url);
|
||||
this.copied = true;
|
||||
setTimeout(() => this.copied = false, 2000);
|
||||
} catch (e) {
|
||||
window.prompt('Copy this display URL:', this.url);
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
<h2 class="text-sm font-semibold text-slate-900">Public display URL</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Open this link on a TV, tablet, or kiosk in your waiting area.</p>
|
||||
<div class="mt-4 rounded-xl border border-slate-200 bg-slate-50 p-3">
|
||||
<p class="break-all font-mono text-xs text-slate-700" x-text="url"></p>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button type="button" class="btn-secondary" @click="copy()" x-text="copied ? 'Copied!' : 'Copy URL'"></button>
|
||||
<a href="{{ $publicUrl }}" target="_blank" rel="noopener" class="btn-secondary">Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-slate-900">
|
||||
<div class="flex items-center justify-between border-b border-white/10 px-5 py-3">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Live preview</p>
|
||||
<span class="inline-flex items-center gap-1.5 text-xs text-slate-400">
|
||||
<span class="qms-display__live-dot h-1.5 w-1.5 rounded-full bg-emerald-400"></span>
|
||||
Customer view
|
||||
</span>
|
||||
</div>
|
||||
<div class="relative aspect-video w-full bg-slate-950">
|
||||
<iframe
|
||||
src="{{ $publicUrl }}"
|
||||
title="Display preview"
|
||||
class="absolute inset-0 h-full w-full border-0"
|
||||
loading="lazy"
|
||||
></iframe>
|
||||
</div>
|
||||
<p class="border-t border-white/10 px-5 py-3 text-xs text-slate-500">
|
||||
Voice announcements require a tap on the display screen once per browser session.
|
||||
</p>
|
||||
</section>
|
||||
</x-app-layout>
|
||||
|
||||
Reference in New Issue
Block a user