Faster voice announcements, Care/Frontdesk API, and console UI polish.
Deploy Ladill Queue / deploy (push) Successful in 28s

Poll displays every 1.2s with client-side TTS ack; expose service API for sibling apps; redesign tickets, counters, and staff console.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-29 21:51:13 +00:00
co-authored by Cursor
parent 051372672b
commit bc6bf0a07c
22 changed files with 813 additions and 140 deletions
+20 -1
View File
@@ -36,9 +36,28 @@ class CounterController extends Controller
{
$this->authorizeAbility($request, 'counters.view');
$this->authorizeOwner($request, $counter);
$owner = $this->ownerRef($request);
$counter->load(['branch', 'serviceQueues']);
return view('qms.counters.show', compact('counter'));
$currentTicket = \App\Models\Ticket::owned($owner)
->where('counter_id', $counter->id)
->whereIn('status', ['called', 'serving', 'on_hold'])
->with('serviceQueue')
->latest('called_at')
->first();
$waitingCounts = $counter->serviceQueues->mapWithKeys(function (ServiceQueue $queue) use ($owner) {
$count = \App\Models\Ticket::owned($owner)
->where('service_queue_id', $queue->id)
->where('status', 'waiting')
->count();
return [$queue->id => $count];
});
$canManage = app(\App\Services\Qms\QmsPermissions::class)->can($this->member($request), 'counters.manage');
return view('qms.counters.show', compact('counter', 'currentTicket', 'waitingCounts', 'canManage'));
}
public function create(Request $request): View
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Qms;
use App\Http\Controllers\Controller;
use App\Models\VoiceAnnouncement;
use App\Services\Qms\DisplayService;
use App\Services\Qms\VoiceAnnouncementService;
use Illuminate\Http\JsonResponse;
@@ -23,6 +24,8 @@ class DisplayPublicController extends Controller
return view('qms.display.public', [
'screen' => $screen,
'dataUrl' => route('qms.display.data', $token),
'playedUrl' => route('qms.display.announcement.played', ['token' => $token, 'announcement' => '__ID__']),
'pollMs' => config('qms.display_poll_ms', 1200),
]);
}
@@ -31,17 +34,16 @@ class DisplayPublicController extends Controller
$screen = $this->displays->findByToken($token) ?? abort(404);
$this->displays->touch($screen);
$payload = $this->displays->payload($screen);
return response()->json($this->displays->payload($screen));
}
foreach ($payload['announcements'] as $announcement) {
if (isset($announcement['id'])) {
$model = \App\Models\VoiceAnnouncement::find($announcement['id']);
if ($model) {
$this->voice->markPlayed($model);
}
}
}
public function played(string $token, VoiceAnnouncement $announcement): JsonResponse
{
$screen = $this->displays->findByToken($token) ?? abort(404);
abort_unless($announcement->branch_id === $screen->branch_id, 404);
return response()->json($payload);
$this->voice->markPlayed($announcement);
return response()->json(['ok' => true]);
}
}
@@ -46,12 +46,22 @@ class SettingsController extends Controller
'appointment_mode' => ['required', 'string', 'in:'.implode(',', array_keys(config('qms.appointment_modes')))],
'industry' => ['nullable', 'string'],
'notifications_enabled' => ['boolean'],
'care_integration_enabled' => ['nullable', 'boolean'],
'frontdesk_integration_enabled' => ['nullable', 'boolean'],
]);
$settings = $organization->settings ?? [];
$settings['appointment_mode'] = $validated['appointment_mode'];
$settings['industry'] = $validated['industry'] ?? $settings['industry'] ?? null;
$settings['notifications_enabled'] = $request->boolean('notifications_enabled', true);
$integrations = $settings['integrations'] ?? [];
if ($request->has('care_integration_enabled')) {
$integrations['care_enabled'] = $request->boolean('care_integration_enabled');
}
if ($request->has('frontdesk_integration_enabled')) {
$integrations['frontdesk_enabled'] = $request->boolean('frontdesk_integration_enabled');
}
$settings['integrations'] = $integrations;
$organization->update([
'name' => $validated['name'],
@@ -29,9 +29,11 @@ class TicketController extends Controller
$tickets = Ticket::owned($owner)
->where('organization_id', $organization->id)
->with(['serviceQueue', 'counter'])
->when($request->query('queue'), fn ($q, $uuid) => $q->whereHas('serviceQueue', fn ($sq) => $sq->where('uuid', $uuid)))
->when($request->query('status'), fn ($q, $s) => $q->where('status', $s))
->latest('issued_at')
->paginate(30);
->paginate(30)
->withQueryString();
$queues = ServiceQueue::owned($owner)->where('organization_id', $organization->id)->orderBy('name')->get();