Add hero sections to Queue index pages and soften analytics cards.
Deploy Ladill Queue / deploy (push) Successful in 36s

Introduce shared x-qms.page-hero with summary stats across queues,
tickets, counters, admin pages, and insights; remove bordered cards on
Analytics, Reports, Feedback, and Branches list views.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-06 07:32:10 +00:00
co-authored by Cursor
parent 29bf896f21
commit bf879d6abe
26 changed files with 850 additions and 526 deletions
@@ -38,7 +38,17 @@ class AppointmentController extends Controller
->orderBy('scheduled_at') ->orderBy('scheduled_at')
->paginate(30); ->paginate(30);
return view('qms.appointments.index', compact('appointments', 'organization')); $appointmentQuery = QueueAppointment::owned($owner)
->where('organization_id', $organization->id)
->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope));
$heroStats = [
'today' => (clone $appointmentQuery)->whereDate('scheduled_at', today())->count(),
'scheduled' => (clone $appointmentQuery)->where('status', 'scheduled')->count(),
'checked_in' => (clone $appointmentQuery)->where('status', 'checked_in')->count(),
];
return view('qms.appointments.index', compact('appointments', 'organization', 'heroStats'));
} }
public function create(Request $request): View public function create(Request $request): View
@@ -26,7 +26,13 @@ class BranchController extends Controller
->orderBy('name') ->orderBy('name')
->get(); ->get();
return view('qms.admin.branches.index', compact('branches', 'organization')); $heroStats = [
'total' => $branches->count(),
'active' => $branches->where('is_active', true)->count(),
'departments' => $branches->sum('departments_count'),
];
return view('qms.admin.branches.index', compact('branches', 'organization', 'heroStats'));
} }
public function create(Request $request): View public function create(Request $request): View
+12 -1
View File
@@ -29,7 +29,18 @@ class CounterController extends Controller
->orderBy('name') ->orderBy('name')
->paginate(20); ->paginate(20);
return view('qms.counters.index', compact('counters', 'organization')); $counterStats = Counter::owned($owner)
->where('organization_id', $organization->id)
->when(true, fn ($q) => $this->scopeToBranch($request, $q))
->get();
$heroStats = [
'total' => $counterStats->count(),
'available' => $counterStats->where('status', 'available')->count(),
'busy' => $counterStats->where('status', 'busy')->count(),
];
return view('qms.counters.index', compact('counters', 'organization', 'heroStats'));
} }
public function show(Request $request, Counter $counter): View public function show(Request $request, Counter $counter): View
@@ -27,10 +27,17 @@ class DepartmentController extends Controller
->orderBy('name') ->orderBy('name')
->get(); ->get();
$heroStats = [
'total' => $departments->count(),
'active' => $departments->where('is_active', true)->count(),
'branches' => $departments->pluck('branch_id')->unique()->count(),
];
return view('qms.admin.departments.index', [ return view('qms.admin.departments.index', [
'departments' => $departments, 'departments' => $departments,
'organization' => $organization, 'organization' => $organization,
'types' => config('qms.department_types'), 'types' => config('qms.department_types'),
'heroStats' => $heroStats,
]); ]);
} }
@@ -38,11 +38,22 @@ class DeviceController extends Controller
->orderBy('name') ->orderBy('name')
->paginate(20); ->paginate(20);
$deviceStats = Device::owned($owner)
->where('organization_id', $organization->id)
->get();
$heroStats = [
'total' => $deviceStats->count(),
'online' => $deviceStats->where('status', 'online')->count(),
'kiosks' => $deviceStats->where('type', 'kiosk')->count(),
];
return view('qms.devices.index', [ return view('qms.devices.index', [
'devices' => $devices, 'devices' => $devices,
'organization' => $organization, 'organization' => $organization,
'deviceTypes' => config('qms.device_types'), 'deviceTypes' => config('qms.device_types'),
'canManage' => $permissions->can($member, 'displays.manage'), 'canManage' => $permissions->can($member, 'displays.manage'),
'heroStats' => $heroStats,
]); ]);
} }
@@ -33,10 +33,21 @@ class DisplayScreenController extends Controller
->orderBy('name') ->orderBy('name')
->paginate(20); ->paginate(20);
$screenStats = DisplayScreen::owned($owner)
->where('organization_id', $organization->id)
->get();
$heroStats = [
'total' => $screenStats->count(),
'active' => $screenStats->where('is_active', true)->count(),
'branches' => $screenStats->pluck('branch_id')->filter()->unique()->count(),
];
return view('qms.displays.index', [ return view('qms.displays.index', [
'screens' => $screens, 'screens' => $screens,
'organization' => $organization, 'organization' => $organization,
'canManage' => $permissions->can($member, 'displays.manage'), 'canManage' => $permissions->can($member, 'displays.manage'),
'heroStats' => $heroStats,
]); ]);
} }
@@ -32,10 +32,21 @@ class FeedbackAdminController extends Controller
->when($branchScope, fn ($q) => $q->whereHas('ticket', fn ($t) => $t->where('branch_id', $branchScope))) ->when($branchScope, fn ($q) => $q->whereHas('ticket', fn ($t) => $t->where('branch_id', $branchScope)))
->avg('rating'); ->avg('rating');
$feedbackQuery = CustomerFeedback::owned($owner)
->where('organization_id', $organization->id)
->when($branchScope, fn ($q) => $q->whereHas('ticket', fn ($t) => $t->where('branch_id', $branchScope)));
$heroStats = [
'total' => (clone $feedbackQuery)->count(),
'avg_rating' => round((float) $avgRating, 1),
'this_month' => (clone $feedbackQuery)->where('created_at', '>=', now()->startOfMonth())->count(),
];
return view('qms.feedback.index', [ return view('qms.feedback.index', [
'feedback' => $feedback, 'feedback' => $feedback,
'organization' => $organization, 'organization' => $organization,
'avgRating' => round((float) $avgRating, 1), 'avgRating' => round((float) $avgRating, 1),
'heroStats' => $heroStats,
]); ]);
} }
} }
@@ -27,10 +27,19 @@ class MemberController extends Controller
->orderBy('created_at') ->orderBy('created_at')
->get(); ->get();
$operatorRoles = ['queue_operator', 'counter_staff', 'receptionist'];
$heroStats = [
'total' => $members->count(),
'operators' => $members->whereIn('role', $operatorRoles)->count(),
'branches' => $members->whereNotNull('branch_id')->pluck('branch_id')->unique()->count(),
];
return view('qms.admin.members.index', [ return view('qms.admin.members.index', [
'members' => $members, 'members' => $members,
'organization' => $organization, 'organization' => $organization,
'roles' => config('qms.roles'), 'roles' => config('qms.roles'),
'heroStats' => $heroStats,
]); ]);
} }
@@ -36,6 +36,10 @@ class ReportController extends Controller
'organization' => $organization, 'organization' => $organization,
'branches' => $branches, 'branches' => $branches,
'reports' => config('qms.report_types'), 'reports' => config('qms.report_types'),
'heroStats' => [
'reports' => count(config('qms.report_types')),
'branches' => $branches->count(),
],
]); ]);
} }
@@ -30,7 +30,18 @@ class ServiceQueueController extends Controller
->orderBy('name') ->orderBy('name')
->paginate(20); ->paginate(20);
return view('qms.queues.index', compact('queues', 'organization')); $queueStats = ServiceQueue::owned($owner)
->where('organization_id', $organization->id)
->when(true, fn ($q) => $this->scopeToBranch($request, $q))
->get();
$heroStats = [
'total' => $queueStats->count(),
'active' => $queueStats->where('is_active', true)->where('is_paused', false)->count(),
'paused' => $queueStats->where('is_paused', true)->count(),
];
return view('qms.queues.index', compact('queues', 'organization', 'heroStats'));
} }
public function show(Request $request, ServiceQueue $serviceQueue): View public function show(Request $request, ServiceQueue $serviceQueue): View
@@ -37,7 +37,15 @@ class TicketController extends Controller
$queues = ServiceQueue::owned($owner)->where('organization_id', $organization->id)->orderBy('name')->get(); $queues = ServiceQueue::owned($owner)->where('organization_id', $organization->id)->orderBy('name')->get();
return view('qms.tickets.index', compact('tickets', 'queues', 'organization')); $ticketQuery = Ticket::owned($owner)->where('organization_id', $organization->id);
$heroStats = [
'today' => (clone $ticketQuery)->whereDate('issued_at', today())->count(),
'waiting' => (clone $ticketQuery)->where('status', 'waiting')->count(),
'serving' => (clone $ticketQuery)->whereIn('status', ['called', 'serving', 'on_hold'])->count(),
];
return view('qms.tickets.index', compact('tickets', 'queues', 'organization', 'heroStats'));
} }
public function create(Request $request): View public function create(Request $request): View
@@ -34,7 +34,13 @@ class WorkflowController extends Controller
->orderBy('name') ->orderBy('name')
->get(); ->get();
return view('qms.workflows.index', compact('workflows', 'organization')); $heroStats = [
'total' => $workflows->count(),
'active' => $workflows->where('is_active', true)->count(),
'steps' => $workflows->sum('steps_count'),
];
return view('qms.workflows.index', compact('workflows', 'organization', 'heroStats'));
} }
public function create(Request $request): View public function create(Request $request): View
@@ -0,0 +1,39 @@
@props([
'badge',
'title',
'description' => null,
'stats' => [],
])
<div {{ $attributes->merge(['class' => 'relative overflow-hidden rounded-2xl border border-slate-200 bg-white']) }}>
<div class="absolute inset-0 bg-gradient-to-br from-indigo-50/80 via-white to-violet-50/60"></div>
<div class="relative px-6 py-8 sm:px-10">
<div class="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div class="max-w-xl">
<div class="inline-flex items-center gap-1.5 rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">
{{ $badge }}
</div>
<h1 class="mt-3 text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">{{ $title }}</h1>
@if ($description)
<p class="mt-2 text-sm leading-6 text-slate-600">{{ $description }}</p>
@endif
@isset($actions)
<div class="mt-6 flex flex-wrap items-center gap-2">
{{ $actions }}
</div>
@endisset
</div>
@if (count($stats) > 0)
<div class="scrollbar-hide flex snap-x snap-mandatory gap-3 overflow-x-auto pb-2 sm:grid sm:grid-cols-3 sm:overflow-visible sm:pb-0 lg:gap-4" style="-ms-overflow-style:none;scrollbar-width:none;">
@foreach ($stats as $stat)
<div class="mobile-stats-card shrink-0 snap-start rounded-xl border border-slate-200 bg-white/90 px-4 py-3 text-center backdrop-blur-sm">
<p class="whitespace-nowrap text-xl font-bold text-slate-900 sm:text-2xl">{{ $stat['value'] }}</p>
<p class="mt-0.5 whitespace-nowrap text-[11px] font-medium text-slate-500">{{ $stat['label'] }}</p>
</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
@@ -1,31 +1,51 @@
@php
$canManageBranches = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'admin.branches.manage'
);
@endphp
<x-app-layout title="Branches"> <x-app-layout title="Branches">
<div class="mb-6 flex items-center justify-between"> <div class="space-y-6">
<h1 class="text-2xl font-semibold">Branches</h1> <x-qms.page-hero
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'admin.branches.manage')) badge="Locations · Sites · Coverage"
<a href="{{ route('qms.branches.create') }}" class="btn-primary">New branch</a> title="Branches"
@endif description="Organize queue operations by location — branches scope queues, counters, displays, and team access."
</div> :stats="[
@include('partials.flash') ['value' => number_format($heroStats['total']), 'label' => 'Branches'],
<div class="overflow-hidden rounded-2xl border bg-white dark:border-slate-700 dark:bg-slate-900"> ['value' => number_format($heroStats['active']), 'label' => 'Active'],
<table class="min-w-full divide-y divide-slate-200"> ['value' => number_format($heroStats['departments']), 'label' => 'Departments'],
<thead class="bg-slate-50"><tr> ]">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Name</th> @if ($canManageBranches)
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Code</th> <x-slot name="actions">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Departments</th> <a href="{{ route('qms.branches.create') }}" class="btn-primary">New branch</a>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th> </x-slot>
<th></th> @endif
</tr></thead> </x-qms.page-hero>
<tbody class="divide-y divide-slate-200">
@foreach ($branches as $branch) @include('partials.flash')
<tr>
<td class="px-4 py-3 text-sm font-medium">{{ $branch->name }}</td> <div class="overflow-hidden rounded-2xl bg-white">
<td class="px-4 py-3 text-sm">{{ $branch->code ?? '—' }}</td> <table class="min-w-full divide-y divide-slate-200">
<td class="px-4 py-3 text-sm">{{ $branch->departments_count }}</td> <thead class="bg-slate-50"><tr>
<td class="px-4 py-3 text-sm">{{ $branch->is_active ? 'Active' : 'Inactive' }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Name</th>
<td class="px-4 py-3 text-right"><a href="{{ route('qms.branches.edit', $branch) }}" class="text-sm text-indigo-600">Edit</a></td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Code</th>
</tr> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Departments</th>
@endforeach <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th>
</tbody> <th></th>
</table> </tr></thead>
<tbody class="divide-y divide-slate-200">
@foreach ($branches as $branch)
<tr>
<td class="px-4 py-3 text-sm font-medium">{{ $branch->name }}</td>
<td class="px-4 py-3 text-sm">{{ $branch->code ?? '—' }}</td>
<td class="px-4 py-3 text-sm">{{ $branch->departments_count }}</td>
<td class="px-4 py-3 text-sm">{{ $branch->is_active ? 'Active' : 'Inactive' }}</td>
<td class="px-4 py-3 text-right"><a href="{{ route('qms.branches.edit', $branch) }}" class="text-sm text-indigo-600">Edit</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div> </div>
</x-app-layout> </x-app-layout>
@@ -1,31 +1,51 @@
@php
$canManageDepartments = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'admin.departments.manage'
);
@endphp
<x-app-layout title="Departments"> <x-app-layout title="Departments">
<div class="mb-6 flex items-center justify-between"> <div class="space-y-6">
<h1 class="text-2xl font-semibold">Departments</h1> <x-qms.page-hero
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'admin.departments.manage')) badge="Reception · Service · Routing"
<a href="{{ route('qms.departments.create') }}" class="btn-primary">New department</a> title="Departments"
@endif description="Group queues and counters by department within each branch — reception, customer service, payments, and more."
</div> :stats="[
@include('partials.flash') ['value' => number_format($heroStats['total']), 'label' => 'Departments'],
<div class="overflow-hidden rounded-2xl border bg-white dark:border-slate-700 dark:bg-slate-900"> ['value' => number_format($heroStats['active']), 'label' => 'Active'],
<table class="min-w-full divide-y divide-slate-200"> ['value' => number_format($heroStats['branches']), 'label' => 'Branches'],
<thead class="bg-slate-50"><tr> ]">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Name</th> @if ($canManageDepartments)
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th> <x-slot name="actions">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Type</th> <a href="{{ route('qms.departments.create') }}" class="btn-primary">New department</a>
<th></th> </x-slot>
</tr></thead> @endif
<tbody class="divide-y divide-slate-200"> </x-qms.page-hero>
@forelse ($departments as $department)
<tr> @include('partials.flash')
<td class="px-4 py-3 text-sm">{{ $department->name }}</td>
<td class="px-4 py-3 text-sm">{{ $department->branch?->name }}</td> <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
<td class="px-4 py-3 text-sm">{{ $types[$department->type] ?? $department->type }}</td> <table class="min-w-full divide-y divide-slate-200">
<td class="px-4 py-3 text-right"><a href="{{ route('qms.departments.edit', $department) }}" class="text-sm text-indigo-600">Edit</a></td> <thead class="bg-slate-50"><tr>
</tr> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Name</th>
@empty <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th>
<tr><td colspan="4" class="px-6 py-12 text-center text-sm text-slate-500">No departments.</td></tr> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Type</th>
@endforelse <th></th>
</tbody> </tr></thead>
</table> <tbody class="divide-y divide-slate-200">
@forelse ($departments as $department)
<tr>
<td class="px-4 py-3 text-sm">{{ $department->name }}</td>
<td class="px-4 py-3 text-sm">{{ $department->branch?->name }}</td>
<td class="px-4 py-3 text-sm">{{ $types[$department->type] ?? $department->type }}</td>
<td class="px-4 py-3 text-right"><a href="{{ route('qms.departments.edit', $department) }}" class="text-sm text-indigo-600">Edit</a></td>
</tr>
@empty
<tr><td colspan="4" class="px-6 py-12 text-center text-sm text-slate-500">No departments.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div> </div>
</x-app-layout> </x-app-layout>
@@ -1,38 +1,58 @@
@php
$canManageMembers = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'admin.members.manage'
);
@endphp
<x-app-layout title="Team"> <x-app-layout title="Team">
<div class="mb-6 flex items-center justify-between"> <div class="space-y-6">
<h1 class="text-2xl font-semibold">Team members</h1> <x-qms.page-hero
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'admin.members.manage')) badge="Roles · Access · Branch assignment"
<a href="{{ route('qms.members.create') }}" class="btn-primary">Add member</a> title="Team"
@endif description="Manage who can access Queue, their roles, and which branch each operator or admin belongs to."
</div> :stats="[
@include('partials.flash') ['value' => number_format($heroStats['total']), 'label' => 'Members'],
<div class="overflow-hidden rounded-2xl border bg-white dark:border-slate-700 dark:bg-slate-900"> ['value' => number_format($heroStats['operators']), 'label' => 'Operators'],
<table class="min-w-full divide-y divide-slate-200"> ['value' => number_format($heroStats['branches']), 'label' => 'Branches staffed'],
<thead class="bg-slate-50"><tr> ]">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Member</th> @if ($canManageMembers)
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Role</th> <x-slot name="actions">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th> <a href="{{ route('qms.members.create') }}" class="btn-primary">Add member</a>
<th></th> </x-slot>
</tr></thead> @endif
<tbody class="divide-y divide-slate-200"> </x-qms.page-hero>
@foreach ($members as $member)
@php @include('partials.flash')
$display = str_contains($member->user_ref, '@')
? $member->user_ref <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
: (\App\Models\User::where('public_id', $member->user_ref)->value('email') ?? $member->user_ref); <table class="min-w-full divide-y divide-slate-200">
@endphp <thead class="bg-slate-50"><tr>
<tr> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Member</th>
<td class="px-4 py-3 text-sm">{{ $display }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Role</th>
<td class="px-4 py-3 text-sm">{{ $roles[$member->role] ?? $member->role }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th>
<td class="px-4 py-3 text-sm">{{ $member->branch?->name ?? 'All' }}</td> <th></th>
<td class="px-4 py-3 text-right"> </tr></thead>
@if ($member->user_ref !== auth()->user()->public_id) <tbody class="divide-y divide-slate-200">
<form method="POST" action="{{ route('qms.members.destroy', $member) }}">@csrf @method('DELETE')<button class="text-xs text-red-600">Remove</button></form> @foreach ($members as $member)
@endif @php
</td> $display = str_contains($member->user_ref, '@')
</tr> ? $member->user_ref
@endforeach : (\App\Models\User::where('public_id', $member->user_ref)->value('email') ?? $member->user_ref);
</tbody> @endphp
</table> <tr>
<td class="px-4 py-3 text-sm">{{ $display }}</td>
<td class="px-4 py-3 text-sm">{{ $roles[$member->role] ?? $member->role }}</td>
<td class="px-4 py-3 text-sm">{{ $member->branch?->name ?? 'All' }}</td>
<td class="px-4 py-3 text-right">
@if ($member->user_ref !== auth()->user()->public_id)
<form method="POST" action="{{ route('qms.members.destroy', $member) }}">@csrf @method('DELETE')<button class="text-xs text-red-600">Remove</button></form>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div> </div>
</x-app-layout> </x-app-layout>
+45 -36
View File
@@ -1,42 +1,51 @@
<x-app-layout title="Analytics"> <x-app-layout title="Analytics">
<h1 class="mb-6 text-2xl font-semibold">Analytics</h1> <div class="space-y-6">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> <x-qms.page-hero
@foreach ([ badge="Live ops · Trends · Performance"
'waiting' => 'Waiting now', title="Analytics"
'serving' => 'Being served', description="Real-time queue performance, wait times, and customer satisfaction across your organization."
'served_today' => 'Served today', :stats="[
'no_shows_today' => 'No-shows today', ['value' => number_format($overview['waiting'] ?? 0), 'label' => 'Waiting now'],
'avg_wait_seconds' => 'Avg wait (sec)', ['value' => number_format($overview['served_today'] ?? 0), 'label' => 'Served today'],
'appointments_today' => 'Appointments today', ['value' => $overview['feedback_avg_30d'] ?? '—', 'label' => 'Avg rating (30d)'],
'feedback_avg_30d' => 'Avg rating (30d)', ]" />
] as $key => $label)
<div class="rounded-2xl border bg-white p-4 dark:border-slate-700 dark:bg-slate-900"> <div class="grid gap-6 lg:grid-cols-2">
<div class="text-xs font-semibold uppercase text-slate-500">{{ $label }}</div> <div class="rounded-2xl bg-white p-6 shadow-sm">
<div class="mt-2 text-2xl font-semibold">{{ $overview[$key] ?? 0 }}</div> <h2 class="text-sm font-semibold uppercase text-slate-500">14-day trend</h2>
<table class="mt-4 w-full text-sm">
<thead><tr><th class="text-left py-1">Date</th><th class="text-right">Issued</th><th class="text-right">Completed</th></tr></thead>
<tbody>
@foreach ($trend as $day)
<tr class="border-t border-slate-100"><td class="py-1">{{ $day['date'] }}</td><td class="text-right">{{ $day['issued'] }}</td><td class="text-right">{{ $day['completed'] }}</td></tr>
@endforeach
</tbody>
</table>
</div>
<div class="rounded-2xl bg-white p-6 shadow-sm">
<h2 class="text-sm font-semibold uppercase text-slate-500">Busiest queues</h2>
<ul class="mt-4 space-y-2 text-sm">
@forelse ($overview['busiest_queues'] ?? [] as $q)
<li class="flex justify-between"><span>{{ $q['name'] }}</span><span class="font-medium">{{ $q['waiting'] }} waiting</span></li>
@empty
<li class="text-slate-500">No active queues.</li>
@endforelse
</ul>
</div> </div>
@endforeach
</div>
<div class="mt-8 grid gap-6 lg:grid-cols-2">
<div class="rounded-2xl border bg-white p-6 dark:border-slate-700 dark:bg-slate-900">
<h2 class="text-sm font-semibold uppercase text-slate-500">14-day trend</h2>
<table class="mt-4 w-full text-sm">
<thead><tr><th class="text-left py-1">Date</th><th class="text-right">Issued</th><th class="text-right">Completed</th></tr></thead>
<tbody>
@foreach ($trend as $day)
<tr class="border-t"><td class="py-1">{{ $day['date'] }}</td><td class="text-right">{{ $day['issued'] }}</td><td class="text-right">{{ $day['completed'] }}</td></tr>
@endforeach
</tbody>
</table>
</div> </div>
<div class="rounded-2xl border bg-white p-6 dark:border-slate-700 dark:bg-slate-900">
<h2 class="text-sm font-semibold uppercase text-slate-500">Busiest queues</h2> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<ul class="mt-4 space-y-2 text-sm"> @foreach ([
@forelse ($overview['busiest_queues'] ?? [] as $q) 'serving' => 'Being served',
<li class="flex justify-between"><span>{{ $q['name'] }}</span><span class="font-medium">{{ $q['waiting'] }} waiting</span></li> 'no_shows_today' => 'No-shows today',
@empty 'avg_wait_seconds' => 'Avg wait (sec)',
<li class="text-slate-500">No active queues.</li> 'appointments_today' => 'Appointments today',
@endforelse ] as $key => $label)
</ul> <div class="rounded-2xl bg-white p-4 shadow-sm">
<div class="text-xs font-semibold uppercase text-slate-500">{{ $label }}</div>
<div class="mt-2 text-2xl font-semibold text-slate-900">{{ $overview[$key] ?? 0 }}</div>
</div>
@endforeach
</div> </div>
</div> </div>
</x-app-layout> </x-app-layout>
@@ -1,47 +1,64 @@
@php
$canManageAppointments = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'appointments.manage'
);
@endphp
<x-app-layout title="Appointments"> <x-app-layout title="Appointments">
<div class="mb-6 flex items-center justify-between"> <div class="space-y-6">
<div> <x-qms.page-hero
<h1 class="text-2xl font-semibold text-slate-900 dark:text-white">Appointments</h1> badge="Pre-booked · Check-in · Tickets"
<p class="mt-1 text-sm text-slate-600 dark:text-slate-400">Schedule and check in pre-booked customers.</p> title="Appointments"
</div> description="Schedule and check in pre-booked customers — checked-in appointments can issue queue tickets automatically."
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'appointments.manage')) :stats="[
<a href="{{ route('qms.appointments.create') }}" class="btn-primary">New appointment</a> ['value' => number_format($heroStats['today']), 'label' => 'Today'],
@endif ['value' => number_format($heroStats['scheduled']), 'label' => 'Scheduled'],
</div> ['value' => number_format($heroStats['checked_in']), 'label' => 'Checked in'],
@include('partials.flash') ]">
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"> @if ($canManageAppointments)
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700"> <x-slot name="actions">
<thead class="bg-slate-50 dark:bg-slate-800"> <a href="{{ route('qms.appointments.create') }}" class="btn-primary">New appointment</a>
<tr> </x-slot>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Customer</th> @endif
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Scheduled</th> </x-qms.page-hero>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Queue</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th> @include('partials.flash')
<th class="px-4 py-3"></th>
</tr> <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
</thead> <table class="min-w-full divide-y divide-slate-200">
<tbody class="divide-y divide-slate-200 dark:divide-slate-700"> <thead class="bg-slate-50">
@forelse ($appointments as $appointment)
<tr> <tr>
<td class="px-4 py-3 text-sm">{{ $appointment->customer_name }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Customer</th>
<td class="px-4 py-3 text-sm">{{ $appointment->scheduled_at->format('M j, H:i') }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Scheduled</th>
<td class="px-4 py-3 text-sm">{{ $appointment->serviceQueue?->name ?? '—' }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Queue</th>
<td class="px-4 py-3 text-sm capitalize">{{ str_replace('_', ' ', $appointment->status) }}</td> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th>
<td class="px-4 py-3 text-right text-sm"> <th class="px-4 py-3"></th>
@if ($appointment->status === 'scheduled')
<a href="{{ route('qms.appointments.edit', $appointment) }}" class="text-indigo-600">Edit</a>
<form method="POST" action="{{ route('qms.appointments.check-in', $appointment) }}" class="inline ml-2">@csrf<button class="text-indigo-600">Check in</button></form>
<form method="POST" action="{{ route('qms.appointments.cancel', $appointment) }}" class="inline ml-2">@csrf<button class="text-amber-600">Cancel</button></form>
@elseif ($appointment->ticket)
<a href="{{ route('qms.tickets.show', $appointment->ticket) }}" class="text-indigo-600">View ticket</a>
@endif
</td>
</tr> </tr>
@empty </thead>
<tr><td colspan="5" class="px-6 py-12 text-center text-sm text-slate-500">No appointments yet.</td></tr> <tbody class="divide-y divide-slate-200">
@endforelse @forelse ($appointments as $appointment)
</tbody> <tr>
</table> <td class="px-4 py-3 text-sm">{{ $appointment->customer_name }}</td>
<td class="px-4 py-3 text-sm">{{ $appointment->scheduled_at->format('M j, H:i') }}</td>
<td class="px-4 py-3 text-sm">{{ $appointment->serviceQueue?->name ?? '—' }}</td>
<td class="px-4 py-3 text-sm capitalize">{{ str_replace('_', ' ', $appointment->status) }}</td>
<td class="px-4 py-3 text-right text-sm">
@if ($appointment->status === 'scheduled')
<a href="{{ route('qms.appointments.edit', $appointment) }}" class="text-indigo-600">Edit</a>
<form method="POST" action="{{ route('qms.appointments.check-in', $appointment) }}" class="inline ml-2">@csrf<button class="text-indigo-600">Check in</button></form>
<form method="POST" action="{{ route('qms.appointments.cancel', $appointment) }}" class="inline ml-2">@csrf<button class="text-amber-600">Cancel</button></form>
@elseif ($appointment->ticket)
<a href="{{ route('qms.tickets.show', $appointment->ticket) }}" class="text-indigo-600">View ticket</a>
@endif
</td>
</tr>
@empty
<tr><td colspan="5" class="px-6 py-12 text-center text-sm text-slate-500">No appointments yet.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $appointments->links() }}</div>
</div> </div>
<div class="mt-4">{{ $appointments->links() }}</div>
</x-app-layout> </x-app-layout>
+46 -38
View File
@@ -1,42 +1,50 @@
<x-app-layout title="Counters"> <x-app-layout title="Counters">
<div class="flex flex-wrap items-start justify-between gap-4"> <div class="space-y-6">
<div> <x-qms.page-hero
<h1 class="text-xl font-semibold text-slate-900">Counters</h1> badge="Desks · Windows · Consoles"
<p class="mt-1 text-sm text-slate-500">Service desks and windows where staff call and serve tickets</p> title="Counters"
</div> description="Service desks and windows where staff call and serve tickets — each counter links to one or more queues."
<a href="{{ route('qms.counters.create') }}" class="btn-primary">New counter</a> :stats="[
</div> ['value' => number_format($heroStats['total']), 'label' => 'Counters'],
['value' => number_format($heroStats['available']), 'label' => 'Available'],
['value' => number_format($heroStats['busy']), 'label' => 'Busy'],
]">
<x-slot name="actions">
<a href="{{ route('qms.counters.create') }}" class="btn-primary">New counter</a>
</x-slot>
</x-qms.page-hero>
<div class="mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-3"> <div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
@forelse ($counters as $counter) @forelse ($counters as $counter)
<article class="flex flex-col rounded-2xl border border-slate-200 bg-white p-5 shadow-sm"> <article class="flex flex-col rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
<div class="flex items-start justify-between gap-3"> <div class="flex items-start justify-between gap-3">
<div> <div>
<a href="{{ route('qms.counters.show', $counter) }}" class="text-lg font-semibold text-slate-900 hover:text-indigo-700">{{ $counter->name }}</a> <a href="{{ route('qms.counters.show', $counter) }}" class="text-lg font-semibold text-slate-900 hover:text-indigo-700">{{ $counter->name }}</a>
<p class="mt-1 text-sm text-slate-500">{{ $counter->branch?->name }}</p> <p class="mt-1 text-sm text-slate-500">{{ $counter->branch?->name }}</p>
</div> </div>
<x-counter-status :status="$counter->status" /> <x-counter-status :status="$counter->status" />
</div>
<p class="mt-4 text-sm text-slate-600">
<span class="font-medium text-slate-700">Queues:</span>
{{ $counter->serviceQueues->pluck('name')->join(', ') ?: 'None assigned' }}
</p>
<div class="mt-5 btn-group">
<a href="{{ route('qms.console.show', $counter) }}" class="btn-primary flex-1 text-center text-sm">Open console</a>
<a href="{{ route('qms.counters.show', $counter) }}" class="btn-secondary text-sm">Details</a>
</div>
</article>
@empty
<div class="col-span-full">
<x-empty-state title="No counters yet" description="Create a counter for each service desk or teller window.">
<x-slot:action>
<a href="{{ route('qms.counters.create') }}" class="btn-primary">Create your first counter</a>
</x-slot:action>
</x-empty-state>
</div>
@endforelse
</div> </div>
<p class="mt-4 text-sm text-slate-600"> @if ($counters->hasPages())
<span class="font-medium text-slate-700">Queues:</span> <div>{{ $counters->links() }}</div>
{{ $counter->serviceQueues->pluck('name')->join(', ') ?: 'None assigned' }} @endif
</p> </div>
<div class="mt-5 btn-group">
<a href="{{ route('qms.console.show', $counter) }}" class="btn-primary flex-1 text-center text-sm">Open console</a>
<a href="{{ route('qms.counters.show', $counter) }}" class="btn-secondary text-sm">Details</a>
</div>
</article>
@empty
<div class="col-span-full">
<x-empty-state title="No counters yet" description="Create a counter for each service desk or teller window.">
<x-slot:action>
<a href="{{ route('qms.counters.create') }}" class="btn-primary">Create your first counter</a>
</x-slot:action>
</x-empty-state>
</div>
@endforelse
</div>
@if ($counters->hasPages())
<div class="mt-4">{{ $counters->links() }}</div>
@endif
</x-app-layout> </x-app-layout>
+76 -68
View File
@@ -1,75 +1,83 @@
<x-app-layout title="Devices"> <x-app-layout title="Devices">
<div class="flex items-center justify-between gap-3"> <div class="space-y-6">
<div> <x-qms.page-hero
<h1 class="text-xl font-semibold text-slate-900">Devices</h1> badge="Kiosks · Printers · Self-service"
<p class="text-sm text-slate-500">Kiosks and ticket printers for self-service and reception</p> title="Devices"
</div> description="Kiosks and ticket printers for self-service check-in and reception — register devices and open kiosk mode from here."
@if ($canManage) :stats="[
<a href="{{ route('qms.devices.create') }}" class="btn-primary">Register device</a> ['value' => number_format($heroStats['total']), 'label' => 'Devices'],
['value' => number_format($heroStats['online']), 'label' => 'Online'],
['value' => number_format($heroStats['kiosks']), 'label' => 'Kiosks'],
]">
@if ($canManage)
<x-slot name="actions">
<a href="{{ route('qms.devices.create') }}" class="btn-primary">Register device</a>
</x-slot>
@endif
</x-qms.page-hero>
@if (session('success'))
<div class="rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ session('success') }}</div>
@endif @endif
</div>
@if (session('success')) <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
<div class="mt-4 rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ session('success') }}</div> @if ($devices->isEmpty())
@endif <x-empty-state
title="No devices registered yet"
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white"> description="Register a kiosk tablet or ticket printer so customers can join queues without staff assistance."
@if ($devices->isEmpty()) >
<x-empty-state <x-slot:icon>
title="No devices registered yet" <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
description="Register a kiosk tablet or ticket printer so customers can join queues without staff assistance." <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21" />
> </svg>
<x-slot:icon> </x-slot:icon>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> @if ($canManage)
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21" /> <x-slot:action>
</svg> <a href="{{ route('qms.devices.create') }}" class="btn-primary">Register your first device</a>
</x-slot:icon> </x-slot:action>
@if ($canManage) @endif
<x-slot:action> </x-empty-state>
<a href="{{ route('qms.devices.create') }}" class="btn-primary">Register your first device</a> @else
</x-slot:action> <table class="min-w-full divide-y divide-slate-100 text-sm">
@endif <thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
</x-empty-state>
@else
<table class="min-w-full divide-y divide-slate-100 text-sm">
<thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
<tr>
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Type</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Branch</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
@foreach ($devices as $device)
<tr> <tr>
<td class="px-4 py-3 font-medium text-slate-900">{{ $device->name }}</td> <th class="px-4 py-3">Name</th>
<td class="px-4 py-3 text-slate-600">{{ $deviceTypes[$device->type] ?? $device->type }}</td> <th class="px-4 py-3">Type</th>
<td class="px-4 py-3"> <th class="px-4 py-3">Status</th>
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium {{ $device->status === 'online' ? 'bg-emerald-100 text-emerald-800' : 'bg-slate-100 text-slate-600' }}"> <th class="px-4 py-3">Branch</th>
{{ ucfirst($device->status) }} <th class="px-4 py-3"></th>
</span>
</td>
<td class="px-4 py-3 text-slate-600">{{ $device->branch?->name ?? '—' }}</td>
<td class="px-4 py-3 text-right">
@if ($device->type === 'kiosk' && $device->device_token)
<a href="{{ route('qms.kiosk.device', $device->device_token) }}" target="_blank" class="text-indigo-700 hover:underline">Open kiosk</a>
@endif
@if ($canManage)
<form method="POST" action="{{ route('qms.devices.regenerate-token', $device) }}" class="ml-3 inline">
@csrf
<button type="submit" class="text-indigo-700 hover:underline">Regenerate token</button>
</form>
@endif
</td>
</tr> </tr>
@endforeach </thead>
</tbody> <tbody class="divide-y divide-slate-50">
</table> @foreach ($devices as $device)
<div class="border-t border-slate-200 px-4 py-3"> <tr>
{{ $devices->links() }} <td class="px-4 py-3 font-medium text-slate-900">{{ $device->name }}</td>
</div> <td class="px-4 py-3 text-slate-600">{{ $deviceTypes[$device->type] ?? $device->type }}</td>
@endif <td class="px-4 py-3">
<span class="inline-flex rounded-full px-2 py-0.5 text-xs font-medium {{ $device->status === 'online' ? 'bg-emerald-100 text-emerald-800' : 'bg-slate-100 text-slate-600' }}">
{{ ucfirst($device->status) }}
</span>
</td>
<td class="px-4 py-3 text-slate-600">{{ $device->branch?->name ?? '—' }}</td>
<td class="px-4 py-3 text-right">
@if ($device->type === 'kiosk' && $device->device_token)
<a href="{{ route('qms.kiosk.device', $device->device_token) }}" target="_blank" class="text-indigo-700 hover:underline">Open kiosk</a>
@endif
@if ($canManage)
<form method="POST" action="{{ route('qms.devices.regenerate-token', $device) }}" class="ml-3 inline">
@csrf
<button type="submit" class="text-indigo-700 hover:underline">Regenerate token</button>
</form>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="border-t border-slate-200 px-4 py-3">
{{ $devices->links() }}
</div>
@endif
</div>
</div> </div>
</x-app-layout> </x-app-layout>
+61 -53
View File
@@ -1,59 +1,67 @@
<x-app-layout title="Displays"> <x-app-layout title="Displays">
<div class="flex items-center justify-between gap-3"> <div class="space-y-6">
<div> <x-qms.page-hero
<h1 class="text-xl font-semibold text-slate-900">Display screens</h1> badge="Waiting areas · Voice · Ticket calls"
<p class="text-sm text-slate-500">Waiting-area screens and voice announcements for called tickets</p> title="Display screens"
</div> description="Waiting-area screens and voice announcements so customers see called tickets and hear when their number is up."
@if ($canManage) :stats="[
<a href="{{ route('qms.displays.create') }}" class="btn-primary">New display</a> ['value' => number_format($heroStats['total']), 'label' => 'Displays'],
@endif ['value' => number_format($heroStats['active']), 'label' => 'Active'],
</div> ['value' => number_format($heroStats['branches']), 'label' => 'Branches'],
]">
@if ($canManage)
<x-slot name="actions">
<a href="{{ route('qms.displays.create') }}" class="btn-primary">New display</a>
</x-slot>
@endif
</x-qms.page-hero>
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white"> <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
@if ($screens->isEmpty()) @if ($screens->isEmpty())
<x-empty-state <x-empty-state
title="No display screens yet" title="No display screens yet"
description="Create a display for your waiting area so customers can see the queue and hear ticket calls." description="Create a display for your waiting area so customers can see the queue and hear ticket calls."
> >
<x-slot:icon> <x-slot:icon>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 20.25h12m-7.5-3v3m3-3v3m-10.125-3H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M6 20.25h12m-7.5-3v3m3-3v3m-10.125-3H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" />
</svg> </svg>
</x-slot:icon> </x-slot:icon>
@if ($canManage) @if ($canManage)
<x-slot:action> <x-slot:action>
<a href="{{ route('qms.displays.create') }}" class="btn-primary">Create your first display</a> <a href="{{ route('qms.displays.create') }}" class="btn-primary">Create your first display</a>
</x-slot:action> </x-slot:action>
@endif @endif
</x-empty-state> </x-empty-state>
@else @else
<table class="min-w-full divide-y divide-slate-100 text-sm"> <table class="min-w-full divide-y divide-slate-100 text-sm">
<thead class="bg-slate-50 text-left text-xs uppercase text-slate-500"> <thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
<tr>
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Layout</th>
<th class="px-4 py-3">Branch</th>
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
@foreach ($screens as $screen)
<tr> <tr>
<td class="px-4 py-3 font-medium text-slate-900"> <th class="px-4 py-3">Name</th>
<a href="{{ route('qms.displays.show', $screen) }}" class="hover:text-indigo-600">{{ $screen->name }}</a> <th class="px-4 py-3">Layout</th>
</td> <th class="px-4 py-3">Branch</th>
<td class="px-4 py-3 text-slate-600">{{ config('qms.display_layouts.'.$screen->layout, $screen->layout) }}</td> <th class="px-4 py-3"></th>
<td class="px-4 py-3 text-slate-600">{{ $screen->branch?->name ?? '—' }}</td>
<td class="px-4 py-3 text-right">
<a href="{{ route('qms.display.public', $screen->access_token) }}" target="_blank" class="text-indigo-700 hover:underline">Open display</a>
</td>
</tr> </tr>
@endforeach </thead>
</tbody> <tbody class="divide-y divide-slate-50">
</table> @foreach ($screens as $screen)
<div class="border-t border-slate-200 px-4 py-3"> <tr>
{{ $screens->links() }} <td class="px-4 py-3 font-medium text-slate-900">
</div> <a href="{{ route('qms.displays.show', $screen) }}" class="hover:text-indigo-600">{{ $screen->name }}</a>
@endif </td>
<td class="px-4 py-3 text-slate-600">{{ config('qms.display_layouts.'.$screen->layout, $screen->layout) }}</td>
<td class="px-4 py-3 text-slate-600">{{ $screen->branch?->name ?? '—' }}</td>
<td class="px-4 py-3 text-right">
<a href="{{ route('qms.display.public', $screen->access_token) }}" target="_blank" class="text-indigo-700 hover:underline">Open display</a>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="border-t border-slate-200 px-4 py-3">
{{ $screens->links() }}
</div>
@endif
</div>
</div> </div>
</x-app-layout> </x-app-layout>
+34 -26
View File
@@ -1,29 +1,37 @@
<x-app-layout title="Customer feedback"> <x-app-layout title="Customer feedback">
<div class="mb-6"> <div class="space-y-6">
<h1 class="text-2xl font-semibold">Customer feedback</h1> <x-qms.page-hero
<p class="mt-1 text-sm text-slate-600">Average rating: {{ $avgRating ?: '—' }} / 5</p> badge="Ratings · Comments · Service quality"
title="Customer feedback"
description="Reviews collected after service — track average ratings and read customer comments by ticket."
:stats="[
['value' => number_format($heroStats['total']), 'label' => 'Responses'],
['value' => $heroStats['avg_rating'] ?: '—', 'label' => 'Avg rating'],
['value' => number_format($heroStats['this_month']), 'label' => 'This month'],
]" />
<div class="overflow-hidden rounded-2xl bg-white">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50"><tr>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Ticket</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Rating</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Comments</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">When</th>
</tr></thead>
<tbody class="divide-y divide-slate-200">
@forelse ($feedback as $item)
<tr>
<td class="px-4 py-3 text-sm">{{ $item->ticket?->ticket_number }}</td>
<td class="px-4 py-3 text-sm">{{ $item->rating }}/5</td>
<td class="px-4 py-3 text-sm text-slate-600">{{ Str::limit($item->comments, 80) }}</td>
<td class="px-4 py-3 text-sm">{{ $item->created_at->diffForHumans() }}</td>
</tr>
@empty
<tr><td colspan="4" class="px-6 py-12 text-center text-sm text-slate-500">No feedback yet.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div>{{ $feedback->links() }}</div>
</div> </div>
<div class="overflow-hidden rounded-2xl border bg-white dark:border-slate-700 dark:bg-slate-900">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50"><tr>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Ticket</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Rating</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Comments</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">When</th>
</tr></thead>
<tbody class="divide-y divide-slate-200">
@forelse ($feedback as $item)
<tr>
<td class="px-4 py-3 text-sm">{{ $item->ticket?->ticket_number }}</td>
<td class="px-4 py-3 text-sm">{{ $item->rating }}/5</td>
<td class="px-4 py-3 text-sm text-slate-600">{{ Str::limit($item->comments, 80) }}</td>
<td class="px-4 py-3 text-sm">{{ $item->created_at->diffForHumans() }}</td>
</tr>
@empty
<tr><td colspan="4" class="px-6 py-12 text-center text-sm text-slate-500">No feedback yet.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4">{{ $feedback->links() }}</div>
</x-app-layout> </x-app-layout>
+82 -66
View File
@@ -1,73 +1,89 @@
@php
$canManageQueues = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'queues.manage'
);
@endphp
<x-app-layout title="Queues"> <x-app-layout title="Queues">
<div class="mb-6 flex items-center justify-between gap-3"> <div class="space-y-6">
<div> <x-qms.page-hero
<h1 class="text-2xl font-semibold text-slate-900 dark:text-white">Service queues</h1> badge="Service lines · Prefixes · Routing"
<p class="mt-1 text-sm text-slate-600 dark:text-slate-400">Configure queues for reception, counters, and departments.</p> title="Service queues"
</div> description="Configure queues for reception, counters, and departments — each with its own ticket prefix and routing strategy."
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'queues.manage')) :stats="[
<a href="{{ route('qms.queues.create') }}" class="btn-primary">New queue</a> ['value' => number_format($heroStats['total']), 'label' => 'Queues'],
['value' => number_format($heroStats['active']), 'label' => 'Active'],
['value' => number_format($heroStats['paused']), 'label' => 'Paused'],
]">
@if ($canManageQueues)
<x-slot name="actions">
<a href="{{ route('qms.queues.create') }}" class="btn-primary">New queue</a>
</x-slot>
@endif
</x-qms.page-hero>
@if (session('success'))
<div class="rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ session('success') }}</div>
@endif @endif
</div>
@if (session('success')) <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
<div class="mb-4 rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ session('success') }}</div> @if ($queues->isEmpty())
@endif <div class="px-6 py-12 text-center text-sm text-slate-500">
No queues yet. Create your first service queue to start issuing tickets.
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"> </div>
@if ($queues->isEmpty()) @else
<div class="px-6 py-12 text-center text-sm text-slate-500"> <table class="min-w-full divide-y divide-slate-200">
No queues yet. Create your first service queue to start issuing tickets. <thead class="bg-slate-50">
</div>
@else
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
<thead class="bg-slate-50 dark:bg-slate-800">
<tr>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Queue</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Prefix</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Strategy</th>
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th>
<th class="px-4 py-3"></th>
</thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
@foreach ($queues as $queue)
<tr> <tr>
<td class="px-4 py-3"> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Queue</th>
<div class="flex items-center gap-2"> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th>
<span class="h-3 w-3 rounded-full" style="background-color: {{ $queue->color }}"></span> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Prefix</th>
<span class="text-sm font-medium text-slate-900 dark:text-white"> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Strategy</th>
<a href="{{ route('qms.queues.show', $queue) }}" class="hover:text-indigo-600">{{ $queue->name }}</a> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th>
</span> <th class="px-4 py-3"></th>
</div>
</td>
<td class="px-4 py-3 text-sm text-slate-600 dark:text-slate-400">{{ $queue->branch?->name }}</td>
<td class="px-4 py-3 text-sm font-mono text-slate-600 dark:text-slate-400">{{ $queue->prefix }}</td>
<td class="px-4 py-3 text-sm text-slate-600 dark:text-slate-400">{{ config('qms.queue_strategies.'.$queue->strategy, $queue->strategy) }}</td>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
@if ($queue->is_paused)
<span class="inline-flex rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-700">Paused</span>
<form method="POST" action="{{ route('qms.queues.resume', $queue) }}" class="inline">@csrf<button class="text-xs text-indigo-600">Resume</button></form>
@elseif ($queue->is_active)
<span class="inline-flex rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700">Active</span>
<form method="POST" action="{{ route('qms.queues.pause', $queue) }}" class="inline">@csrf<button class="text-xs text-amber-600">Pause</button></form>
@else
<span class="inline-flex rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">Inactive</span>
@endif
</div>
</td>
<td class="px-4 py-3 text-right">
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'rules.view'))
<a href="{{ route('qms.rules.index', $queue) }}" class="text-xs text-indigo-600">Rules</a>
@endif
</td>
</tr> </tr>
@endforeach </thead>
</tbody> <tbody class="divide-y divide-slate-200">
</table> @foreach ($queues as $queue)
<div class="border-t border-slate-200 px-4 py-3 dark:border-slate-700"> <tr>
{{ $queues->links() }} <td class="px-4 py-3">
</div> <div class="flex items-center gap-2">
@endif <span class="h-3 w-3 rounded-full" style="background-color: {{ $queue->color }}"></span>
<span class="text-sm font-medium text-slate-900">
<a href="{{ route('qms.queues.show', $queue) }}" class="hover:text-indigo-600">{{ $queue->name }}</a>
</span>
</div>
</td>
<td class="px-4 py-3 text-sm text-slate-600">{{ $queue->branch?->name }}</td>
<td class="px-4 py-3 text-sm font-mono text-slate-600">{{ $queue->prefix }}</td>
<td class="px-4 py-3 text-sm text-slate-600">{{ config('qms.queue_strategies.'.$queue->strategy, $queue->strategy) }}</td>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
@if ($queue->is_paused)
<span class="inline-flex rounded-full bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-700">Paused</span>
<form method="POST" action="{{ route('qms.queues.resume', $queue) }}" class="inline">@csrf<button class="text-xs text-indigo-600">Resume</button></form>
@elseif ($queue->is_active)
<span class="inline-flex rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700">Active</span>
<form method="POST" action="{{ route('qms.queues.pause', $queue) }}" class="inline">@csrf<button class="text-xs text-amber-600">Pause</button></form>
@else
<span class="inline-flex rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">Inactive</span>
@endif
</div>
</td>
<td class="px-4 py-3 text-right">
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'rules.view'))
<a href="{{ route('qms.rules.index', $queue) }}" class="text-xs text-indigo-600">Rules</a>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="border-t border-slate-200 px-4 py-3">
{{ $queues->links() }}
</div>
@endif
</div>
</div> </div>
</x-app-layout> </x-app-layout>
+19 -8
View File
@@ -1,11 +1,22 @@
<x-app-layout title="Reports"> <x-app-layout title="Reports">
<h1 class="mb-6 text-2xl font-semibold">Reports</h1> <div class="space-y-6">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <x-qms.page-hero
@foreach ($reports as $type => $label) badge="Export · Metrics · CSV"
<a href="{{ route('qms.reports.show', $type) }}" class="rounded-2xl border bg-white p-6 transition hover:border-indigo-300 dark:border-slate-700 dark:bg-slate-900"> title="Reports"
<h2 class="font-semibold">{{ $label }}</h2> description="Drill into tickets, wait times, counters, appointments, and feedback — view metrics and export CSV for any date range."
<p class="mt-1 text-sm text-slate-600">View metrics and export CSV</p> :stats="[
</a> ['value' => number_format($heroStats['reports']), 'label' => 'Report types'],
@endforeach ['value' => number_format($heroStats['branches']), 'label' => 'Branches'],
['value' => 'CSV', 'label' => 'Export format'],
]" />
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
@foreach ($reports as $type => $label)
<a href="{{ route('qms.reports.show', $type) }}" class="rounded-2xl bg-white p-6 shadow-sm transition hover:shadow-md">
<h2 class="font-semibold text-slate-900">{{ $label }}</h2>
<p class="mt-1 text-sm text-slate-600">View metrics and export CSV</p>
</a>
@endforeach
</div>
</div> </div>
</x-app-layout> </x-app-layout>
+81 -66
View File
@@ -1,70 +1,85 @@
@php
$canIssueTickets = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'tickets.issue'
);
@endphp
<x-app-layout title="Tickets"> <x-app-layout title="Tickets">
<div class="flex flex-wrap items-start justify-between gap-4"> <div class="space-y-6">
<div> <x-qms.page-hero
<h1 class="text-xl font-semibold text-slate-900">Tickets</h1> badge="Live · Historical · All service lines"
<p class="mt-1 text-sm text-slate-500">Live and historical queue tickets across all service lines</p> title="Tickets"
</div> description="Live and historical queue tickets across all service lines — filter by queue, status, or issue new walk-in tickets."
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'tickets.issue')) :stats="[
<a href="{{ route('qms.tickets.create') }}" class="btn-primary">Issue ticket</a> ['value' => number_format($heroStats['today']), 'label' => 'Issued today'],
@endif ['value' => number_format($heroStats['waiting']), 'label' => 'Waiting'],
</div> ['value' => number_format($heroStats['serving']), 'label' => 'Being served'],
]">
@if ($canIssueTickets)
<x-slot name="actions">
<a href="{{ route('qms.tickets.create') }}" class="btn-primary">Issue ticket</a>
</x-slot>
@endif
</x-qms.page-hero>
<form method="GET" class="mt-5 flex flex-wrap items-end gap-3"> <form method="GET" class="flex flex-wrap items-end gap-3 rounded-2xl border border-slate-200 bg-white p-4">
<div> <div>
<label class="block text-xs font-medium uppercase tracking-wide text-slate-500">Queue</label> <label class="block text-xs font-medium uppercase tracking-wide text-slate-500">Queue</label>
<select name="queue" class="mt-1 rounded-lg border-slate-300 text-sm"> <select name="queue" class="mt-1 rounded-lg border-slate-300 text-sm">
<option value="">All queues</option> <option value="">All queues</option>
@foreach ($queues as $queue) @foreach ($queues as $queue)
<option value="{{ $queue->uuid }}" @selected(request('queue') === $queue->uuid)>{{ $queue->name }}</option> <option value="{{ $queue->uuid }}" @selected(request('queue') === $queue->uuid)>{{ $queue->name }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
<div> <div>
<label class="block text-xs font-medium uppercase tracking-wide text-slate-500">Status</label> <label class="block text-xs font-medium uppercase tracking-wide text-slate-500">Status</label>
<select name="status" class="mt-1 rounded-lg border-slate-300 text-sm"> <select name="status" class="mt-1 rounded-lg border-slate-300 text-sm">
<option value="">All statuses</option> <option value="">All statuses</option>
@foreach (config('qms.ticket_statuses') as $value => $label) @foreach (config('qms.ticket_statuses') as $value => $label)
<option value="{{ $value }}" @selected(request('status') === $value)>{{ $label }}</option> <option value="{{ $value }}" @selected(request('status') === $value)>{{ $label }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
<button type="submit" class="btn-secondary text-sm">Filter</button> <button type="submit" class="btn-secondary text-sm">Filter</button>
@if (request()->hasAny(['queue', 'status'])) @if (request()->hasAny(['queue', 'status']))
<a href="{{ route('qms.tickets.index') }}" class="text-sm text-slate-500 hover:text-slate-700">Clear</a> <a href="{{ route('qms.tickets.index') }}" class="text-sm text-slate-500 hover:text-slate-700">Clear</a>
@endif @endif
</form> </form>
<div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 bg-white"> <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
@if ($tickets->isEmpty()) @if ($tickets->isEmpty())
<x-empty-state title="No tickets match your filters" description="Issue a walk-in ticket or adjust filters to see results." /> <x-empty-state title="No tickets match your filters" description="Issue a walk-in ticket or adjust filters to see results." />
@else @else
<table class="min-w-full divide-y divide-slate-100 text-sm"> <table class="min-w-full divide-y divide-slate-100 text-sm">
<thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500"> <thead class="bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500">
<tr> <tr>
<th class="px-4 py-3">Ticket</th> <th class="px-4 py-3">Ticket</th>
<th class="px-4 py-3">Customer</th> <th class="px-4 py-3">Customer</th>
<th class="px-4 py-3">Queue</th> <th class="px-4 py-3">Queue</th>
<th class="px-4 py-3">Counter</th> <th class="px-4 py-3">Counter</th>
<th class="px-4 py-3">Status</th> <th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Issued</th> <th class="px-4 py-3">Issued</th>
</tr> </tr>
</thead> </thead>
<tbody class="divide-y divide-slate-50"> <tbody class="divide-y divide-slate-50">
@foreach ($tickets as $ticket) @foreach ($tickets as $ticket)
<tr class="hover:bg-slate-50/80"> <tr class="hover:bg-slate-50/80">
<td class="px-4 py-3"> <td class="px-4 py-3">
<a href="{{ route('qms.tickets.show', $ticket) }}" class="font-mono text-base font-semibold text-indigo-700 hover:underline">{{ $ticket->ticket_number }}</a> <a href="{{ route('qms.tickets.show', $ticket) }}" class="font-mono text-base font-semibold text-indigo-700 hover:underline">{{ $ticket->ticket_number }}</a>
</td> </td>
<td class="px-4 py-3 text-slate-700">{{ $ticket->customer_name ?: 'Walk-in' }}</td> <td class="px-4 py-3 text-slate-700">{{ $ticket->customer_name ?: 'Walk-in' }}</td>
<td class="px-4 py-3 text-slate-600">{{ $ticket->serviceQueue?->name }}</td> <td class="px-4 py-3 text-slate-600">{{ $ticket->serviceQueue?->name }}</td>
<td class="px-4 py-3 text-slate-600">{{ $ticket->counter?->name ?? '—' }}</td> <td class="px-4 py-3 text-slate-600">{{ $ticket->counter?->name ?? '—' }}</td>
<td class="px-4 py-3"><x-ticket-status :status="$ticket->status" /></td> <td class="px-4 py-3"><x-ticket-status :status="$ticket->status" /></td>
<td class="px-4 py-3 text-slate-500">{{ $ticket->issued_at?->format('M j, H:i') }}</td> <td class="px-4 py-3 text-slate-500">{{ $ticket->issued_at?->format('M j, H:i') }}</td>
</tr> </tr>
@endforeach @endforeach
</tbody> </tbody>
</table> </table>
<div class="border-t border-slate-200 px-4 py-3">{{ $tickets->links() }}</div> <div class="border-t border-slate-200 px-4 py-3">{{ $tickets->links() }}</div>
@endif @endif
</div> </div>
</div>
</x-app-layout> </x-app-layout>
+46 -26
View File
@@ -1,29 +1,49 @@
@php
$canManageWorkflows = app(\App\Services\Qms\QmsPermissions::class)->can(
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
'workflows.manage'
);
@endphp
<x-app-layout title="Workflows"> <x-app-layout title="Workflows">
<div class="mb-6 flex items-center justify-between"> <div class="space-y-6">
<h1 class="text-2xl font-semibold text-slate-900 dark:text-white">Workflows</h1> <x-qms.page-hero
@if(app(\App\Services\Qms\QmsPermissions::class)->can(app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()), 'workflows.manage')) badge="Multi-step · Routing · Automation"
<a href="{{ route('qms.workflows.create') }}" class="btn-primary">New workflow</a> title="Workflows"
@endif description="Define multi-step customer journeys that route tickets through queues, counters, and departments in sequence."
</div> :stats="[
@include('partials.flash') ['value' => number_format($heroStats['total']), 'label' => 'Workflows'],
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-900"> ['value' => number_format($heroStats['active']), 'label' => 'Active'],
<table class="min-w-full divide-y divide-slate-200"> ['value' => number_format($heroStats['steps']), 'label' => 'Total steps'],
<thead class="bg-slate-50"><tr> ]">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Name</th> @if ($canManageWorkflows)
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Steps</th> <x-slot name="actions">
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th> <a href="{{ route('qms.workflows.create') }}" class="btn-primary">New workflow</a>
</tr></thead> </x-slot>
<tbody class="divide-y divide-slate-200"> @endif
@forelse ($workflows as $workflow) </x-qms.page-hero>
<tr>
<td class="px-4 py-3"><a href="{{ route('qms.workflows.show', $workflow) }}" class="text-sm font-medium text-indigo-600">{{ $workflow->name }}</a></td> @include('partials.flash')
<td class="px-4 py-3 text-sm">{{ $workflow->steps_count }}</td>
<td class="px-4 py-3 text-sm">{{ $workflow->is_active ? 'Active' : 'Inactive' }}</td> <div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
</tr> <table class="min-w-full divide-y divide-slate-200">
@empty <thead class="bg-slate-50"><tr>
<tr><td colspan="3" class="px-6 py-12 text-center text-sm text-slate-500">No workflows configured.</td></tr> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Name</th>
@endforelse <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Steps</th>
</tbody> <th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Status</th>
</table> </tr></thead>
<tbody class="divide-y divide-slate-200">
@forelse ($workflows as $workflow)
<tr>
<td class="px-4 py-3"><a href="{{ route('qms.workflows.show', $workflow) }}" class="text-sm font-medium text-indigo-600">{{ $workflow->name }}</a></td>
<td class="px-4 py-3 text-sm">{{ $workflow->steps_count }}</td>
<td class="px-4 py-3 text-sm">{{ $workflow->is_active ? 'Active' : 'Inactive' }}</td>
</tr>
@empty
<tr><td colspan="3" class="px-6 py-12 text-center text-sm text-slate-500">No workflows configured.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div> </div>
</x-app-layout> </x-app-layout>