Nest Branches and Team under Settings as Pro features.
Deploy Ladill Queue / deploy (push) Successful in 50s
Deploy Ladill Queue / deploy (push) Successful in 50s
Match Frontdesk: settings cards and nested routes, Pro-gated access, and legacy /admin redirects to /settings/branches and /settings/team.
This commit is contained in:
@@ -1,52 +1,66 @@
|
||||
<x-app-layout title="Add member">
|
||||
<div class="mx-auto max-w-lg">
|
||||
<h1 class="text-xl font-semibold text-slate-900">Invite team member</h1>
|
||||
<x-settings.page title="Invite team member" description="They get access only to Ladill Queue (plus Mail if they already have a Ladill mailbox).">
|
||||
<div class="mb-4 flex flex-wrap items-center gap-2 text-sm">
|
||||
<a href="{{ route('qms.settings.edit') }}" class="text-slate-500 hover:text-slate-800">Settings</a>
|
||||
<span class="text-slate-300">/</span>
|
||||
<a href="{{ route('qms.members.index') }}" class="text-slate-500 hover:text-slate-800">Team</a>
|
||||
<span class="text-slate-300">/</span>
|
||||
<span class="font-medium text-slate-900">Invite</span>
|
||||
</div>
|
||||
|
||||
<form method="POST" x-data action="{{ route('qms.members.store') }}" class="mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
@csrf
|
||||
<x-settings.card title="Invitation">
|
||||
<form method="POST" action="{{ route('qms.members.store') }}" class="space-y-4" x-data>
|
||||
@csrf
|
||||
|
||||
@if (! empty($mailboxOptions))
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">From a Ladill mailbox</label>
|
||||
<select x-on:change="if ($event.target.value) { $refs.email.value = $event.target.value }"
|
||||
class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
<option value="">Choose a mailbox…</option>
|
||||
@foreach ($mailboxOptions as $address)
|
||||
<option value="{{ $address }}">{{ $address }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-slate-500">Or type any email below.</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! empty($mailboxOptions))
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">From a Ladill mailbox</label>
|
||||
<select x-on:change="if ($event.target.value) { $refs.email.value = $event.target.value }"
|
||||
class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
<option value="">Choose a mailbox…</option>
|
||||
@foreach ($mailboxOptions as $address)
|
||||
<option value="{{ $address }}">{{ $address }}</option>
|
||||
<label class="block text-sm font-medium text-slate-700">Email address</label>
|
||||
<input type="email" name="email" x-ref="email" value="{{ old('email') }}" required
|
||||
class="mt-1 w-full rounded-lg border-slate-300 text-sm"
|
||||
placeholder="colleague@company.com">
|
||||
<p class="mt-1 text-xs text-slate-500">They will receive an email to accept and join your Queue organization.</p>
|
||||
@error('email')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Role</label>
|
||||
<select name="role" required class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
@foreach ($roles as $v => $l)
|
||||
<option value="{{ $v }}" @selected(old('role') === $v)>{{ $l }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-slate-500">Or type any email below.</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Email address</label>
|
||||
<input type="email" name="email" x-ref="email" value="{{ old('email') }}" required
|
||||
class="mt-1 w-full rounded-lg border-slate-300 text-sm"
|
||||
placeholder="colleague@company.com">
|
||||
<p class="mt-1 text-xs text-slate-500">They will receive an email to accept and join your Queue organization.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Branch scope (optional)</label>
|
||||
<select name="branch_id" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
<option value="">Organization-wide</option>
|
||||
@foreach ($branches as $b)
|
||||
<option value="{{ $b->id }}" @selected(old('branch_id') == $b->id)>{{ $b->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Role</label>
|
||||
<select name="role" required class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
@foreach ($roles as $v => $l)
|
||||
<option value="{{ $v }}" @selected(old('role') === $v)>{{ $l }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Branch scope (optional)</label>
|
||||
<select name="branch_id" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
<option value="">Organization-wide</option>
|
||||
@foreach ($branches as $b)
|
||||
<option value="{{ $b->id }}" @selected(old('branch_id') == $b->id)>{{ $b->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary w-full">Send invitation</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3">
|
||||
<a href="{{ route('qms.members.index') }}" class="rounded-xl border border-slate-200 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Cancel</a>
|
||||
<button type="submit" class="btn-primary">Send invitation</button>
|
||||
</div>
|
||||
</form>
|
||||
</x-settings.card>
|
||||
</x-settings.page>
|
||||
</x-app-layout>
|
||||
|
||||
@@ -3,56 +3,69 @@
|
||||
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
|
||||
'admin.members.manage'
|
||||
);
|
||||
$canViewBranches = app(\App\Services\Qms\QmsPermissions::class)->can(
|
||||
app(\App\Services\Qms\OrganizationResolver::class)->memberFor(auth()->user()),
|
||||
'admin.branches.view'
|
||||
);
|
||||
@endphp
|
||||
|
||||
<x-app-layout title="Team">
|
||||
<div class="space-y-6">
|
||||
<x-qms.page-hero
|
||||
badge="Roles · Access · Branch assignment"
|
||||
title="Team"
|
||||
description="Manage who can access Queue, their roles, and which branch each operator or admin belongs to."
|
||||
:stats="[
|
||||
['value' => number_format($heroStats['total']), 'label' => 'Members'],
|
||||
['value' => number_format($heroStats['operators']), 'label' => 'Operators'],
|
||||
['value' => number_format($heroStats['branches']), 'label' => 'Branches staffed'],
|
||||
]">
|
||||
<x-settings.page title="Team" description="Manage who can access Queue, their roles, and which branch each member belongs to.">
|
||||
<div class="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex flex-wrap items-center gap-2 text-sm">
|
||||
<a href="{{ route('qms.settings.edit') }}" class="text-slate-500 hover:text-slate-800">Settings</a>
|
||||
<span class="text-slate-300">/</span>
|
||||
<span class="font-medium text-slate-900">Team</span>
|
||||
@if ($canViewBranches)
|
||||
<a href="{{ route('qms.branches.index') }}" class="ml-3 text-indigo-600 hover:text-indigo-800">Branches</a>
|
||||
@endif
|
||||
</div>
|
||||
@if ($canManageMembers)
|
||||
<x-slot name="actions">
|
||||
<a href="{{ route('qms.members.create') }}" class="btn-primary">Add member</a>
|
||||
</x-slot>
|
||||
<a href="{{ route('qms.members.create') }}" class="btn-primary">Add member</a>
|
||||
@endif
|
||||
</x-qms.page-hero>
|
||||
</div>
|
||||
|
||||
@include('partials.flash')
|
||||
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 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">Member</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Role</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase text-slate-500">Branch</th>
|
||||
<th></th>
|
||||
</tr></thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
@foreach ($members as $member)
|
||||
@php
|
||||
$display = str_contains($member->user_ref, '@')
|
||||
? $member->user_ref
|
||||
: (\App\Models\User::where('public_id', $member->user_ref)->value('email') ?? $member->user_ref);
|
||||
@endphp
|
||||
<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')<x-btn type="submit" variant="danger" size="sm">Remove</x-btn></form>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<x-settings.card title="Team members" description="{{ number_format($heroStats['total']) }} members · {{ number_format($heroStats['operators']) }} operators · {{ number_format($heroStats['branches']) }} branches staffed">
|
||||
@if ($members->isEmpty())
|
||||
<p class="text-sm text-slate-500">No team members yet.</p>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead class="text-left text-xs uppercase text-slate-500">
|
||||
<tr>
|
||||
<th class="pb-2 pr-4">Member</th>
|
||||
<th class="pb-2 pr-4">Role</th>
|
||||
<th class="pb-2 pr-4">Branch</th>
|
||||
<th class="pb-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
@foreach ($members as $member)
|
||||
@php
|
||||
$display = str_contains($member->user_ref, '@')
|
||||
? $member->user_ref
|
||||
: (\App\Models\User::where('public_id', $member->user_ref)->value('email') ?? $member->user_ref);
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="py-3 pr-4">{{ $display }}</td>
|
||||
<td class="py-3 pr-4">{{ $roles[$member->role] ?? $member->role }}</td>
|
||||
<td class="py-3 pr-4">{{ $member->branch?->name ?? 'All branches' }}</td>
|
||||
<td class="py-3 text-right">
|
||||
@if ($canManageMembers && $member->user_ref !== auth()->user()->public_id)
|
||||
<form method="POST" action="{{ route('qms.members.destroy', $member) }}" class="inline">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="text-sm font-medium text-red-600 hover:text-red-800">Remove</button>
|
||||
</form>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</x-settings.card>
|
||||
</x-settings.page>
|
||||
</x-app-layout>
|
||||
|
||||
Reference in New Issue
Block a user