Redesign display admin show page and public waiting-area screen.
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:
isaacclad
2026-06-29 22:42:02 +00:00
co-authored by Cursor
parent af764f77fb
commit 2243ccf95a
7 changed files with 350 additions and 45 deletions
+124 -37
View File
@@ -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>
<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>
<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="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 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>
</template>
<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>
</div>
</header>
<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>
{{-- 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-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="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="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 todays average</p>
</div>
<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">
<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>
<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>
<template x-for="q in (payload?.queues ?? [])" :key="q.name">
<p class="text-lg" x-text="q.name + (q.is_paused ? ' (paused)' : '')"></p>
</template>
</div>
</div>
</footer>
</div>
</body>
</html>