From 2243ccf95ae23613a285437d88bdf0e854d4adb2 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Mon, 29 Jun 2026 22:42:02 +0000 Subject: [PATCH] Redesign display admin show page and public waiting-area screen. 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 --- .../Qms/DisplayPublicController.php | 1 + .../Qms/DisplayScreenController.php | 18 +- app/Services/Qms/DisplayService.php | 3 + resources/css/app.css | 50 ++++++ resources/js/kiosk-flow.js | 14 ++ resources/views/qms/display/public.blade.php | 161 ++++++++++++++---- resources/views/qms/displays/show.blade.php | 148 +++++++++++++++- 7 files changed, 350 insertions(+), 45 deletions(-) diff --git a/app/Http/Controllers/Qms/DisplayPublicController.php b/app/Http/Controllers/Qms/DisplayPublicController.php index 3f9a229..2ecb10c 100644 --- a/app/Http/Controllers/Qms/DisplayPublicController.php +++ b/app/Http/Controllers/Qms/DisplayPublicController.php @@ -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', [ diff --git a/app/Http/Controllers/Qms/DisplayScreenController.php b/app/Http/Controllers/Qms/DisplayScreenController.php index 5789834..6336b92 100644 --- a/app/Http/Controllers/Qms/DisplayScreenController.php +++ b/app/Http/Controllers/Qms/DisplayScreenController.php @@ -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 diff --git a/app/Services/Qms/DisplayService.php b/app/Services/Qms/DisplayService.php index ea3fb76..5b09e41 100644 --- a/app/Services/Qms/DisplayService.php +++ b/app/Services/Qms/DisplayService.php @@ -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, diff --git a/resources/css/app.css b/resources/css/app.css index b7680e8..cb0d870 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -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); +} diff --git a/resources/js/kiosk-flow.js b/resources/js/kiosk-flow.js index e575392..dabfc82 100644 --- a/resources/js/kiosk-flow.js +++ b/resources/js/kiosk-flow.js @@ -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) => ({ diff --git a/resources/views/qms/display/public.blade.php b/resources/views/qms/display/public.blade.php index 52c9192..4f0d3a2 100644 --- a/resources/views/qms/display/public.blade.php +++ b/resources/views/qms/display/public.blade.php @@ -1,5 +1,5 @@ - + @@ -7,58 +7,145 @@ {{ $screen->name }} · Queue Display @vite(['resources/css/app.css', 'resources/js/app.js']) - + + {{-- Voice unlock — required by browsers before TTS --}}
-
-

Tap to enable voice announcements

-

Browsers require one tap before the display can speak ticket calls.

+
+
+ +
+

Tap to enable announcements

+

Your browser needs one interaction before this screen can call tickets aloud.

-
-
-

-

-
- -
-

Now serving

-
- +

+

+
-
+ -
-
-

Waiting

-

+ {{-- Main --}} +
+ {{-- Now serving --}} +
+
+
+

Now serving

+

Please proceed to your counter when called

+
+
+ +
+ +
+ +
+
+ +
+

Please wait

+

Your ticket number will appear here when it is called.

+
+
+ + {{-- Stats sidebar --}} + +
+ + {{-- Footer --}} +
+
+

Powered by Ladill Queue

+

-
-

Est. wait

-

-
-
-

Queues

- -
-
+
diff --git a/resources/views/qms/displays/show.blade.php b/resources/views/qms/displays/show.blade.php index 01d3520..3576a72 100644 --- a/resources/views/qms/displays/show.blade.php +++ b/resources/views/qms/displays/show.blade.php @@ -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 + -
+
- ← Displays -

{{ $display->name }}

+ ← Displays +
+

{{ $display->name }}

+ @if ($display->is_active) + + + Active + + @else + Inactive + @endif + @if ($isOnline) + + + Display online + + @endif +
+

+ {{ $display->branch?->name ?? 'No branch' }} + · {{ $layoutLabel }} layout + @if ($display->last_seen_at) + · Last seen {{ $display->last_seen_at->diffForHumans() }} + @endif +

+
+
+ @if ($canManage) + Edit display + @endif + Open public display
- Edit
-

Layout: {{ config('qms.display_layouts.'.$display->layout) }}

-

Open public display

+ +
+
+

Assigned queues

+

Tickets from these queues appear on this screen and trigger voice calls.

+ + @if ($queues->isEmpty()) +
+

No queues assigned

+

Edit this display and select the service queues for your waiting area.

+ @if ($canManage) + Assign queues + @endif +
+ @else +
    + @foreach ($queues as $queue) +
  • + + {{ $queue->prefix }} + +
    +

    {{ $queue->name }}

    +

    + {{ $queue->is_paused ? 'Paused' : 'Accepting tickets' }} +

    +
    +
  • + @endforeach +
+ @endif +
+ +
+
+

Display details

+
+
+
Organization
+
{{ $display->organization?->name ?? '—' }}
+
+
+
Branch
+
{{ $display->branch?->name ?? '—' }}
+
+
+
Layout
+
{{ $layoutLabel }}
+
+
+
Queues
+
{{ $queues->count() }}
+
+
+
+ +
+

Public display URL

+

Open this link on a TV, tablet, or kiosk in your waiting area.

+
+

+
+
+ + Preview +
+
+
+
+ +
+
+

Live preview

+ + + Customer view + +
+
+ +
+

+ Voice announcements require a tap on the display screen once per browser session. +

+