Lighten public display and show org or Ladill Queue logo.
Deploy Ladill Queue / deploy (push) Successful in 36s

Remove waiting, estimate, and service queue sidebar; replace text header with customer logo when uploaded.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-29 22:59:19 +00:00
co-authored by Cursor
parent 326769204d
commit 98d14a213a
3 changed files with 66 additions and 111 deletions
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\VoiceAnnouncement;
use App\Services\Qms\DisplayService;
use App\Services\Qms\VoiceAnnouncementService;
use App\Support\OrganizationBranding;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
@@ -23,6 +24,7 @@ class DisplayPublicController extends Controller
$this->displays->touch($screen);
$payload = $this->displays->payload($screen);
$organization = $screen->organization;
return view('qms.display.public', [
'screen' => $screen,
@@ -30,6 +32,12 @@ class DisplayPublicController extends Controller
'dataUrl' => '/display/'.$token.'/data',
'playedUrl' => '/display/'.$token.'/announcements/__ID__/played',
'pollMs' => config('qms.display_poll_ms', 1200),
'logoUrl' => $organization
? OrganizationBranding::logoUrl($organization)
: asset(OrganizationBranding::DEFAULT_LOGO),
'logoAlt' => $organization
? OrganizationBranding::logoAlt($organization)
: 'Ladill Queue',
]);
}
+13 -18
View File
@@ -268,39 +268,34 @@ html.qr-mobile-page body {
pointer-events: none;
}
/* Public queue display — waiting-area TV screens */
/* Public queue display — waiting-area TV screens (light theme) */
.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;
radial-gradient(ellipse 90% 60% at 50% -20%, rgb(238 242 255 / 0.9), transparent),
linear-gradient(180deg, rgb(248 250 252) 0%, rgb(255 255 255) 100%);
color: rgb(15 23 42);
}
.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);
linear-gradient(rgb(148 163 184 / 0.08) 1px, transparent 1px),
linear-gradient(90deg, rgb(148 163 184 / 0.08) 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%);
background: linear-gradient(180deg, #fff 0%, rgb(248 250 252) 100%);
border: 1px solid rgb(226 232 240);
box-shadow:
0 0 0 1px rgb(255 255 255 / 0.08),
0 20px 40px -12px rgb(0 0 0 / 0.45);
0 1px 2px rgb(15 23 42 / 0.04),
0 20px 40px -16px rgb(79 70 229 / 0.15);
}
.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);
color: rgb(67 56 202);
}
.qms-display__live-dot {
@@ -314,6 +309,6 @@ html.qr-mobile-page body {
.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);
radial-gradient(ellipse 60% 40% at 50% 0%, rgb(199 210 254 / 0.5), transparent),
rgb(255 255 255 / 0.97);
}
+42 -90
View File
@@ -1,18 +1,15 @@
@php
$p = $initialPayload;
@endphp
<!DOCTYPE html>
<html lang="en" class="h-full bg-slate-950">
<html lang="en" class="h-full bg-slate-50">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="theme-color" content="#020617">
<meta name="theme-color" content="#f8fafc">
<title>{{ $screen->name }} · Queue Display</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body
class="qms-display qms-display__grid-bg min-h-full bg-slate-950 text-slate-100"
class="qms-display qms-display__grid-bg min-h-full bg-slate-50 text-slate-900"
x-data="displayBoard(@js([
'dataUrl' => $dataUrl,
'playedUrl' => $playedUrl,
@@ -29,132 +26,87 @@
@click="unlockSpeech()"
@keydown.enter.prevent="unlockSpeech()"
tabindex="0"
class="qms-display__unlock 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">
<div class="max-w-lg rounded-3xl border border-slate-200 bg-white px-10 py-12 shadow-xl">
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-indigo-50 ring-1 ring-indigo-100">
<svg class="h-8 w-8 text-indigo-600" 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>
<p class="mt-6 text-2xl font-semibold tracking-tight text-slate-900">Tap to enable announcements</p>
<p class="mt-3 text-base text-slate-500">Your browser needs one interaction before this screen can call tickets aloud.</p>
</div>
</div>
<div
x-show="pollError"
x-cloak
class="fixed left-1/2 top-4 z-40 max-w-lg -translate-x-1/2 rounded-xl border border-rose-400/30 bg-rose-950/90 px-4 py-3 text-sm text-rose-100 shadow-lg"
class="fixed left-1/2 top-4 z-40 max-w-lg -translate-x-1/2 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-800 shadow-lg"
x-text="pollError"
></div>
<div class="flex min-h-screen flex-col">
{{-- Header --}}
<header class="border-b border-white/10 bg-slate-950/80 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">
{{ $p['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">
{{ $p['screen']['name'] ?? $screen->name }}
</h1>
@if (! empty($p['screen']['branch_name']))
<p class="mt-1 text-sm text-slate-400">{{ $p['screen']['branch_name'] }}</p>
@endif
<header class="border-b border-slate-200/80 bg-white/90 backdrop-blur-md">
<div class="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-6 px-6 py-6 lg:px-10">
<div class="flex min-w-0 items-center">
<img
src="{{ $logoUrl }}"
alt="{{ $logoAlt }}"
class="h-10 w-auto max-w-[min(100%,280px)] object-contain object-left lg:h-12"
>
</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>
<div class="inline-flex items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1.5 text-xs font-semibold text-emerald-700">
<span class="qms-display__live-dot h-2 w-2 rounded-full bg-emerald-500"></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>
<p class="mt-2 font-mono text-3xl font-semibold tabular-nums tracking-tight text-slate-900 lg:text-4xl" x-text="clock">--:--</p>
<p class="text-sm text-slate-500" 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-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>
<main class="mx-auto flex w-full max-w-6xl flex-1 flex-col px-6 py-10 lg:px-10 lg:py-14">
<section class="flex flex-1 flex-col">
<div class="mb-6 text-center">
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-600">Now serving</p>
<p class="mt-2 text-base text-slate-500">Please proceed to your counter when your number appears</p>
</div>
<div class="flex flex-1 flex-col gap-4" x-show="(payload?.now_serving ?? []).length">
<div class="flex flex-1 flex-col gap-6" 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>
<article class="qms-display__ticket flex flex-1 flex-col items-center justify-center rounded-3xl px-8 py-12 text-center lg:py-16">
<p class="text-sm font-semibold uppercase tracking-[0.25em] text-slate-500">Ticket number</p>
<p class="qms-display__ticket-number mt-4 text-6xl font-bold sm:text-7xl lg:text-8xl" x-text="item.ticket_number"></p>
<p class="mt-4 text-xl font-medium text-slate-700" x-text="item.queue_name"></p>
<div class="mt-10 inline-flex items-center gap-3 rounded-full border border-indigo-100 bg-indigo-50 px-6 py-3">
<span class="text-sm font-semibold uppercase tracking-widest text-indigo-600">Proceed to</span>
<span class="text-2xl font-semibold text-indigo-900" 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"
class="qms-display__ticket 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">
<div class="flex h-16 w-16 items-center justify-center rounded-2xl bg-slate-100 ring-1 ring-slate-200">
<svg class="h-8 w-8 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>
<p class="mt-6 text-3xl font-semibold text-slate-900">Please wait</p>
<p class="mt-3 max-w-md text-lg text-slate-500">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 bg-slate-900/80 p-6 text-center ring-1 ring-white/10">
<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-slate-50" x-text="formatCount(payload?.waiting_count)">{{ $p['waiting_count'] ?? 0 }}</p>
<p class="mt-1 text-sm text-slate-500">customers in queue</p>
</div>
<div class="qms-display__stat rounded-2xl bg-slate-900/80 p-6 text-center ring-1 ring-white/10">
<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-slate-50">
<span x-text="formatWait(payload?.estimated_wait_minutes)">{{ $p['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 bg-slate-900/80 p-6 ring-1 ring-white/10">
<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 + (q.prefix ?? '')">
<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-slate-100" 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/80 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">
<footer class="border-t border-slate-200 bg-white/80 px-6 py-4 lg:px-10">
<div class="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-3 text-xs text-slate-400">
<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>