Separate unit placements from shift duty assignments.
Deploy Ladill Care / deploy (push) Successful in 30s

Roster no longer writes temporary staff assignments; unit assignment UI drops shift fields and labels clarify the two workflows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-20 11:49:18 +00:00
co-authored by Cursor
parent 5125a6fe27
commit b7aca6ee2b
15 changed files with 68 additions and 101 deletions
@@ -35,7 +35,7 @@
<a href="{{ route('care.care-units.mar', $unit) }}" class="btn-primary">MAR board</a>
@endif
@if ($canRoster)
<a href="{{ route('care.care-units.roster', $unit) }}" class="btn-secondary">Duty roster</a>
<a href="{{ route('care.care-units.roster', $unit) }}" class="btn-secondary">Shift assignments</a>
@endif
@if ($canAssess)
<a href="{{ route('care.care-units.assessments', $unit) }}" class="btn-secondary">Assessments</a>
@@ -51,7 +51,7 @@
@endif
@if ($canManageUnit)
<a href="{{ route('care.care-units.edit', $unit) }}" class="btn-secondary">Edit unit</a>
<a href="{{ route('care.staff-assignments.create', ['care_unit_id' => $unit->id]) }}" class="btn-secondary">Assign staff</a>
<a href="{{ route('care.staff-assignments.create', ['care_unit_id' => $unit->id]) }}" class="btn-secondary">Unit assignment</a>
@endif
</div>
</div>
@@ -2,10 +2,10 @@
$assignment = $assignment ?? null;
@endphp
<x-app-layout title="{{ $assignment ? 'Edit assignment' : 'Add assignment' }}">
<x-app-layout title="{{ $assignment ? 'Edit unit assignment' : 'Add unit assignment' }}">
<div class="mx-auto max-w-xl">
<h1 class="text-xl font-semibold text-slate-900">{{ $assignment ? 'Edit staff assignment' : 'Add staff assignment' }}</h1>
<p class="mt-1 text-sm text-slate-500">RBAC role stays on the team member. This record is their unit placement over a date range.</p>
<h1 class="text-xl font-semibold text-slate-900">{{ $assignment ? 'Edit unit assignment' : 'Add unit assignment' }}</h1>
<p class="mt-1 text-sm text-slate-500">Home or float placement on a unit. Day/evening/night duty is scheduled separately on the shift roster.</p>
<form method="POST"
action="{{ $assignment ? route('care.staff-assignments.update', $assignment) : route('care.staff-assignments.store') }}"
@@ -63,25 +63,14 @@
@error('care_unit_id')<p class="mt-1 text-xs text-red-600">{{ $message }}</p>@enderror
</div>
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label class="block text-sm font-medium">Post / grade</label>
<select name="assignment_role" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
<option value=""></option>
@foreach ($assignmentRoles as $value => $label)
<option value="{{ $value }}" @selected(old('assignment_role', $assignment?->assignment_role) === $value)>{{ $label }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium">Shift</label>
<select name="shift_code" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
<option value=""></option>
@foreach ($shiftCodes as $value => $label)
<option value="{{ $value }}" @selected(old('shift_code', $assignment?->shift_code) === $value)>{{ $label }}</option>
@endforeach
</select>
</div>
<div>
<label class="block text-sm font-medium">Post / grade</label>
<select name="assignment_role" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
<option value=""></option>
@foreach ($assignmentRoles as $value => $label)
<option value="{{ $value }}" @selected(old('assignment_role', $assignment?->assignment_role) === $value)>{{ $label }}</option>
@endforeach
</select>
</div>
<div class="grid gap-4 sm:grid-cols-2">
@@ -109,13 +98,13 @@
<textarea name="notes" rows="2" class="mt-1 w-full rounded-lg border-slate-300 text-sm">{{ old('notes', $assignment?->notes) }}</textarea>
</div>
<button type="submit" class="btn-primary w-full">{{ $assignment ? 'Save changes' : 'Create assignment' }}</button>
<button type="submit" class="btn-primary w-full">{{ $assignment ? 'Save changes' : 'Create unit assignment' }}</button>
</form>
@if ($assignment)
<form method="POST" action="{{ route('care.staff-assignments.destroy', $assignment) }}" class="mt-4" onsubmit="return confirm('Remove this assignment?')">
<form method="POST" action="{{ route('care.staff-assignments.destroy', $assignment) }}" class="mt-4" onsubmit="return confirm('Remove this unit assignment?')">
@csrf @method('DELETE')
<button type="submit" class="text-sm font-medium text-red-600 hover:text-red-800">Remove assignment</button>
<button type="submit" class="text-sm font-medium text-red-600 hover:text-red-800">Remove unit assignment</button>
</form>
@endif
</div>
@@ -1,9 +1,9 @@
<x-app-layout title="Staff assignments">
<x-app-layout title="Unit assignments">
<div class="space-y-6">
<x-care.page-hero
badge="Primary · Temporary · Specialty"
title="Staff assignments"
description="Dated employment placements — department, care unit, post, and shift. Temporary assignments cover float nurses without making ward ownership permanent."
title="Unit assignments"
description="Where staff belong — department, care unit, and post over a date range. Use the duty roster for day/evening/night shift assignments."
:stats="[
['value' => number_format($heroStats['total']), 'label' => 'Assignments'],
['value' => number_format($heroStats['active']), 'label' => 'Effective today'],
@@ -11,7 +11,7 @@
['value' => number_format($heroStats['temporary']), 'label' => 'Temporary'],
]">
<x-slot name="actions">
<a href="{{ route('care.staff-assignments.create') }}" class="btn-primary">Add assignment</a>
<a href="{{ route('care.staff-assignments.create') }}" class="btn-primary">Add unit assignment</a>
</x-slot>
</x-care.page-hero>
@@ -23,7 +23,6 @@
<th class="px-4 py-3">Kind</th>
<th class="px-4 py-3">Post</th>
<th class="px-4 py-3">Unit / Dept</th>
<th class="px-4 py-3">Shift</th>
<th class="px-4 py-3">Dates</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3"></th>
@@ -41,7 +40,6 @@
<div class="text-xs text-slate-400">{{ $assignment->department->name }}</div>
@endif
</td>
<td class="px-4 py-3">{{ $shiftCodes[$assignment->shift_code] ?? ($assignment->shift_code ?: '—') }}</td>
<td class="px-4 py-3 whitespace-nowrap">
{{ $assignment->starts_on->format('Y-m-d') }}
@@ -58,7 +56,7 @@
</td>
</tr>
@empty
<tr><td colspan="8" class="px-4 py-6 text-center text-slate-500">No staff assignments yet.</td></tr>
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">No unit assignments yet.</td></tr>
@endforelse
</tbody>
</table>
@@ -7,7 +7,7 @@
<span class="text-slate-300">/</span>
Duty roster
</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Week roster</h1>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Shift assignments</h1>
<p class="mt-1 text-sm text-slate-500">
{{ $weekStart->format('d M Y') }} {{ $weekStart->copy()->addDays(6)->format('d M Y') }}
· {{ $unit->department?->name }}
@@ -80,8 +80,8 @@
@if ($canManage)
<div class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-base font-semibold text-slate-900">Add to roster</h2>
<p class="mt-1 text-sm text-slate-500">Creates a temporary staff assignment for that duty date.</p>
<h2 class="text-base font-semibold text-slate-900">Add shift assignment</h2>
<p class="mt-1 text-sm text-slate-500">Schedules who works this unit on a specific date and shift. Does not change their unit placement.</p>
<form method="POST" action="{{ route('care.care-units.roster.store', $unit) }}" class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
@csrf
<div>
@@ -15,7 +15,7 @@
<a href="{{ route('care.shifts.index') }}" class="btn-secondary">Shift templates</a>
@endif
<a href="{{ route('care.care-units.index') }}" class="btn-secondary">Care units</a>
<a href="{{ route('care.staff-assignments.index') }}" class="btn-secondary">Staff assignments</a>
<a href="{{ route('care.staff-assignments.index') }}" class="btn-secondary">Unit assignments</a>
</div>
</div>
@@ -35,7 +35,7 @@
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-5 lg:col-span-2">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Todays roster</h2>
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Todays shift assignments</h2>
<div class="mt-4 overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-left text-xs uppercase text-slate-500">
@@ -64,7 +64,7 @@
<td class="py-2.5">{{ $nurseLabels[$entry->member_id] ?? ($entry->member?->user_ref ?? '—') }}</td>
</tr>
@empty
<tr><td colspan="3" class="py-6 text-slate-500">No duty roster entries for today.</td></tr>
<tr><td colspan="3" class="py-6 text-slate-500">No shift assignments for today.</td></tr>
@endforelse
</tbody>
</table>
@@ -73,8 +73,8 @@
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Active allocations</h2>
<p class="mt-1 text-sm text-slate-500">Dated staff assignments effective today (primary, temporary, specialty).</p>
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Unit assignments</h2>
<p class="mt-1 text-sm text-slate-500">Home and float placements effective today (primary, temporary, specialty) separate from shift duty.</p>
<div class="mt-4 overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="text-left text-xs uppercase text-slate-500">
@@ -102,7 +102,7 @@
</td>
</tr>
@empty
<tr><td colspan="4" class="py-6 text-slate-500">No active nursing allocations today.</td></tr>
<tr><td colspan="4" class="py-6 text-slate-500">No active unit assignments today.</td></tr>
@endforelse
</tbody>
</table>
@@ -118,7 +118,7 @@
<p class="mt-0.5 text-xs text-slate-500">{{ $unit->department?->name }} · {{ str_replace('_', ' ', $unit->kind) }}</p>
<div class="mt-3 flex flex-wrap gap-2 text-xs">
@if ($canViewRoster)
<a href="{{ route('care.care-units.roster', $unit) }}" class="text-indigo-600 hover:text-indigo-800">Roster</a>
<a href="{{ route('care.care-units.roster', $unit) }}" class="text-indigo-600 hover:text-indigo-800">Shifts</a>
@endif
<a href="{{ route('care.care-units.mar', $unit) }}" class="text-slate-600 hover:text-slate-900">MAR</a>
<a href="{{ route('care.care-units.notes', $unit) }}" class="text-slate-600 hover:text-slate-900">Notes</a>
+1 -1
View File
@@ -138,7 +138,7 @@
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z" />'];
}
if ($permissions->can($member, 'admin.members.view')) {
$adminNav[] = ['name' => 'Staff assignments', 'route' => route('care.staff-assignments.index'), 'active' => request()->routeIs('care.staff-assignments.*'),
$adminNav[] = ['name' => 'Unit assignments', 'route' => route('care.staff-assignments.index'), 'active' => request()->routeIs('care.staff-assignments.*'),
'icon' => '<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />'];
}
if ($permissions->can($member, 'admin.practitioners.view')) {