Lighten public display and show org or Ladill Queue logo.
Deploy Ladill Queue / deploy (push) Successful in 36s
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:
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 today’s 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>
|
||||
|
||||
Reference in New Issue
Block a user