Replace employee user_ref text field with Ladill user select.
Deploy Ladill Frontdesk / deploy (push) Successful in 40s

Populate the dropdown from team members and linked hosts, show name and email labels, and block linking a user already tied to another employee.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-28 22:18:59 +00:00
co-authored by Cursor
parent 49dc5cfccf
commit 5cd93a84fc
4 changed files with 114 additions and 13 deletions
@@ -8,11 +8,14 @@ use App\Models\Branch;
use App\Models\Employee;
use App\Models\EmployeePresenceEvent;
use App\Models\Host;
use App\Models\Member;
use App\Models\User;
use App\Services\Frontdesk\EmployeePresenceService;
use App\Services\Frontdesk\QrCodeService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
@@ -44,6 +47,7 @@ class EmployeeController extends Controller
'organization' => $organization,
'branches' => $this->branches($request, $organization->id),
'hosts' => $this->hosts($request, $organization->id),
'linkableUsers' => $this->linkableUsers($request, $organization->id),
]);
}
@@ -52,7 +56,7 @@ class EmployeeController extends Controller
$this->authorizeAbility($request, 'employees.manage');
$organization = $this->organization($request);
$validated = $this->validatedEmployee($request);
$validated = $this->validatedEmployee($request, $organization->id);
$pin = $validated['pin'];
unset($validated['pin']);
@@ -77,6 +81,7 @@ class EmployeeController extends Controller
'employee' => $employee->load('presence'),
'branches' => $this->branches($request, $employee->organization_id),
'hosts' => $this->hosts($request, $employee->organization_id),
'linkableUsers' => $this->linkableUsers($request, $employee->organization_id, $employee),
]);
}
@@ -85,7 +90,7 @@ class EmployeeController extends Controller
$this->authorizeAbility($request, 'employees.manage');
$this->authorizeOwner($request, $employee);
$validated = $this->validatedEmployee($request, updating: true);
$validated = $this->validatedEmployee($request, $employee->organization_id, updating: true, employee: $employee);
$payload = $this->employeePayload($validated);
if (! empty($validated['pin'])) {
@@ -277,12 +282,17 @@ class EmployeeController extends Controller
}
/** @return array<string, mixed> */
protected function validatedEmployee(Request $request, bool $updating = false): array
protected function validatedEmployee(Request $request, int $organizationId, bool $updating = false, ?Employee $employee = null): array
{
$pinRules = $updating
? ['nullable', 'string', 'regex:/^\d{4,6}$/']
: ['required', 'string', 'regex:/^\d{4,6}$/'];
$linkableRefs = collect($this->linkableUsers($request, $organizationId, $employee))
->reject(fn (array $user) => $user['taken'])
->pluck('user_ref')
->all();
return $request->validate([
'employee_code' => ['required', 'string', 'max:32'],
'full_name' => ['required', 'string', 'max:255'],
@@ -292,10 +302,82 @@ class EmployeeController extends Controller
'pin' => $pinRules,
'branch_id' => ['nullable', 'integer'],
'host_id' => ['nullable', 'integer'],
'user_ref' => ['nullable', 'string', 'max:64'],
'user_ref' => [
'nullable',
'string',
'max:64',
Rule::when(
filled($request->input('user_ref')),
[Rule::in($linkableRefs)]
),
],
]);
}
/**
* Ladill users that can be linked to an employee (team members and linked hosts).
*
* @return list<array{user_ref: string, label: string, taken: bool}>
*/
protected function linkableUsers(Request $request, int $organizationId, ?Employee $employee = null): array
{
$ownerRef = $this->ownerRef($request);
$refs = collect()
->merge(
Member::owned($ownerRef)
->where('organization_id', $organizationId)
->pluck('user_ref')
)
->merge(
Host::owned($ownerRef)
->where('organization_id', $organizationId)
->whereNotNull('user_ref')
->pluck('user_ref')
)
->merge(
Employee::owned($ownerRef)
->where('organization_id', $organizationId)
->whereNotNull('user_ref')
->pluck('user_ref')
);
if ($employee?->user_ref) {
$refs->push($employee->user_ref);
}
$refs = $refs->filter()->unique()->values();
$users = User::query()
->whereIn('public_id', $refs)
->get(['public_id', 'name', 'email'])
->keyBy('public_id');
$takenRefs = Employee::owned($ownerRef)
->where('organization_id', $organizationId)
->whereNotNull('user_ref')
->when($employee, fn ($query) => $query->where('id', '!=', $employee->id))
->pluck('user_ref')
->flip();
return $refs
->map(function (string $ref) use ($users, $takenRefs, $employee) {
$user = $users->get($ref);
$label = $user
? trim($user->name.' · '.$user->email)
: $ref;
return [
'user_ref' => $ref,
'label' => $label,
'taken' => $takenRefs->has($ref) && $employee?->user_ref !== $ref,
];
})
->sortBy('label', SORT_NATURAL | SORT_FLAG_CASE)
->values()
->all();
}
protected function employeePayload(array $validated): array
{
$payload = $validated;
@@ -51,10 +51,7 @@
</select>
<p class="mt-1 text-xs text-slate-500">When stepped out, linked hosts show as unavailable to visitors.</p>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Linked Ladill user (optional)</label>
<input type="text" name="user_ref" value="{{ old('user_ref') }}" placeholder="User public ID" class="mt-1 w-full rounded-lg border-slate-300 font-mono text-sm">
</div>
@include('frontdesk.employees.partials.user-ref-select', ['selectedUserRef' => old('user_ref')])
<button type="submit" class="btn-primary w-full">Save employee</button>
</form>
</div>
@@ -49,11 +49,7 @@
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Linked Ladill user (for mobile step-out)</label>
<input type="text" name="user_ref" value="{{ old('user_ref', $employee->user_ref) }}" placeholder="User public ID" class="mt-1 w-full rounded-lg border-slate-300 font-mono text-sm">
<p class="mt-1 text-xs text-slate-400">Enables the My presence portal for this employee.</p>
</div>
@include('frontdesk.employees.partials.user-ref-select', ['selectedUserRef' => old('user_ref', $employee->user_ref)])
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" name="active" value="1" @checked(old('active', $employee->active)) class="rounded border-slate-300">
Active (can use kiosk)
@@ -0,0 +1,26 @@
@php
$selectedUserRef = old('user_ref', $selectedUserRef ?? '');
@endphp
<div>
<label class="block text-sm font-medium text-slate-700">Linked Ladill user (optional)</label>
<select name="user_ref" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
<option value="">None</option>
@foreach ($linkableUsers as $linkableUser)
<option value="{{ $linkableUser['user_ref'] }}"
@selected($selectedUserRef === $linkableUser['user_ref'])
@disabled($linkableUser['taken'] && $selectedUserRef !== $linkableUser['user_ref'])>
{{ $linkableUser['label'] }}@if ($linkableUser['taken']) (linked to another employee) @endif
</option>
@endforeach
</select>
@if (count($linkableUsers) === 0)
<p class="mt-1 text-xs text-amber-700">
No Ladill users available yet.
<a href="{{ route('frontdesk.members.create') }}" class="font-medium underline">Add a team member</a>
so they can be linked here.
</p>
@else
<p class="mt-1 text-xs text-slate-500">Enables the My presence portal when this person signs in with the selected Ladill account.</p>
@endif
</div>