Add nursing assessment packs, ward board, and nurse performance KPIs.
Deploy Ladill Care / deploy (push) Successful in 55s

Seeds NEWS2/Braden/Morse/pain packs with visit vitals and a unit dashboard for MAR, assessments, notes, and handover activity.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-20 10:32:52 +00:00
co-authored by Cursor
parent cb6e59e5ed
commit 9eb6c21828
18 changed files with 1511 additions and 16 deletions
@@ -27,16 +27,24 @@
$canMar = $permissions->can($member, 'mar.view');
$canNotes = $permissions->can($member, 'nursing.notes.view');
$canHandover = $permissions->can($member, 'nursing.handover.view');
$canAssess = $permissions->can($member, 'nursing.assessments.view');
$canPerf = $permissions->can($member, 'nursing.performance.view');
@endphp
@if ($canMar)
<a href="{{ route('care.care-units.mar', $unit) }}" class="btn-primary">MAR board</a>
@endif
@if ($canAssess)
<a href="{{ route('care.care-units.assessments', $unit) }}" class="btn-secondary">Assessments</a>
@endif
@if ($canNotes)
<a href="{{ route('care.care-units.notes', $unit) }}" class="btn-secondary">Nursing notes</a>
@endif
@if ($canHandover)
<a href="{{ route('care.care-units.handovers', $unit) }}" class="btn-secondary">Handovers</a>
@endif
@if ($canPerf)
<a href="{{ route('care.care-units.performance', $unit) }}" class="btn-secondary">Performance</a>
@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>
@@ -0,0 +1,106 @@
<x-app-layout title="Assessments · {{ $unit->name }}">
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<p class="text-sm text-slate-500">
<a href="{{ route('care.care-units.show', $unit) }}" class="text-indigo-600 hover:text-indigo-800">{{ $unit->name }}</a>
<span class="text-slate-300">/</span>
Nursing assessments
</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Ward assessment board</h1>
<p class="mt-1 text-sm text-slate-500">NEWS2, Braden, Morse, pain, and visit vitals for placed patients.</p>
</div>
<a href="{{ route('care.care-units.performance', $unit) }}" class="btn-secondary">Performance</a>
</div>
@unless ($engineEnabled)
<div class="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
Assessments engine is disabled for this organization. Enable it under Settings Rollout.
</div>
@endunless
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
<table class="min-w-full text-sm">
<thead class="bg-slate-50 text-left text-xs uppercase text-slate-500">
<tr>
<th class="px-4 py-3">Patient</th>
<th class="px-4 py-3">Latest vitals</th>
@foreach ($templates as $template)
<th class="px-4 py-3">{{ $template->code }}</th>
@endforeach
<th class="px-4 py-3"></th>
</tr>
</thead>
<tbody class="divide-y divide-slate-50">
@forelse ($rows as $row)
@php
$visit = $row['visit'];
$vitals = $row['vitals'];
@endphp
<tr>
<td class="px-4 py-3 font-medium">
{{ $row['patient']?->fullName() }}
<div class="text-xs text-slate-400">{{ $row['bed']?->label ?? 'No bed' }}</div>
</td>
<td class="px-4 py-3 text-xs text-slate-600">
@if ($vitals)
BP {{ $vitals->bp_systolic }}/{{ $vitals->bp_diastolic }}
· P {{ $vitals->pulse }}
· SpO₂ {{ $vitals->spo2 }}
<div class="text-slate-400">{{ $vitals->recorded_at?->format('d M H:i') }}</div>
@else
@endif
</td>
@foreach ($templates as $template)
@php $assessment = $row['assessments']->get($template->code); @endphp
<td class="px-4 py-3">
@if ($assessment)
<a href="{{ route('care.assessments.show', $assessment) }}" class="text-sky-600 hover:text-sky-800">
{{ $assessment->status === 'completed'
? ($assessment->score?->severity_label ?? ($assessment->score?->total_score !== null ? $assessment->score->total_score : 'Done'))
: 'Draft' }}
</a>
@else
<span class="text-slate-400"></span>
@endif
</td>
@endforeach
<td class="px-4 py-3 text-right">
@if ($canCapture && $engineEnabled)
<div class="inline-flex flex-wrap justify-end gap-1">
@foreach ($templates as $template)
<form method="POST" action="{{ route('care.care-units.assessments.start', [$unit, $visit]) }}">
@csrf
<input type="hidden" name="template_code" value="{{ $template->code }}">
<button type="submit" class="rounded border border-slate-200 px-2 py-1 text-xs text-slate-700 hover:bg-slate-50">{{ strtoupper($template->code) }}</button>
</form>
@endforeach
</div>
@endif
</td>
</tr>
@if ($canVitals)
<tr class="bg-slate-50/60">
<td colspan="{{ 3 + $templates->count() }}" class="px-4 py-3">
<form method="POST" action="{{ route('care.care-units.vitals.store', [$unit, $visit]) }}" class="grid gap-2 sm:grid-cols-7">
@csrf
<input type="number" name="bp_systolic" placeholder="Sys" class="rounded-lg border-slate-300 text-sm">
<input type="number" name="bp_diastolic" placeholder="Dia" class="rounded-lg border-slate-300 text-sm">
<input type="number" name="pulse" placeholder="Pulse" class="rounded-lg border-slate-300 text-sm">
<input type="number" step="0.1" name="temperature" placeholder="Temp" class="rounded-lg border-slate-300 text-sm">
<input type="number" name="spo2" placeholder="SpO₂" class="rounded-lg border-slate-300 text-sm">
<input type="number" name="respiratory_rate" placeholder="RR" class="rounded-lg border-slate-300 text-sm">
<button type="submit" class="btn-secondary text-xs">Record vitals · {{ $row['patient']?->first_name }}</button>
</form>
</td>
</tr>
@endif
@empty
<tr><td colspan="{{ 3 + max($templates->count(), 1) }}" class="px-4 py-8 text-center text-slate-500">No patients placed on this unit.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</x-app-layout>
@@ -0,0 +1,97 @@
@php $r = $report; @endphp
<x-app-layout title="Performance · {{ $unit->name }}">
<div class="space-y-6">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<p class="text-sm text-slate-500">
<a href="{{ route('care.care-units.show', $unit) }}" class="text-indigo-600 hover:text-indigo-800">{{ $unit->name }}</a>
<span class="text-slate-300">/</span>
Nurse performance
</p>
<h1 class="mt-1 text-2xl font-semibold text-slate-900">Unit nursing KPIs</h1>
<p class="mt-1 text-sm text-slate-500">MAR compliance, vitals, assessments, notes, and handovers.</p>
</div>
<form method="GET" class="flex flex-wrap items-end gap-2">
<div>
<label class="block text-xs font-medium uppercase text-slate-500">From</label>
<input type="date" name="from" value="{{ $r['from']->toDateString() }}" class="mt-1 rounded-lg border-slate-300 text-sm">
</div>
<div>
<label class="block text-xs font-medium uppercase text-slate-500">To</label>
<input type="date" name="to" value="{{ $r['to']->toDateString() }}" class="mt-1 rounded-lg border-slate-300 text-sm">
</div>
<button type="submit" class="btn-secondary">Apply</button>
</form>
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">MAR given</p>
<p class="mt-2 text-3xl font-semibold text-slate-900">{{ $r['mar']['given'] }}</p>
<p class="mt-1 text-sm text-slate-500">of {{ $r['mar']['total'] }} recorded · compliance {{ $r['mar']['compliance_rate'] ?? '—' }}%</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">On-time MAR</p>
<p class="mt-2 text-3xl font-semibold text-slate-900">{{ $r['mar']['on_time_rate'] ?? '—' }}%</p>
<p class="mt-1 text-sm text-slate-500">{{ $r['mar']['on_time_given'] }} given within window</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Assessments done</p>
<p class="mt-2 text-3xl font-semibold text-slate-900">{{ $r['assessments']['completed'] }}</p>
<p class="mt-1 text-sm text-slate-500">{{ $r['assessments']['completion_rate'] ?? '—' }}% of {{ $r['assessments']['started'] }} started</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Notes / vitals</p>
<p class="mt-2 text-3xl font-semibold text-slate-900">{{ $r['notes']['total'] }} / {{ $r['vitals']['total'] }}</p>
<p class="mt-1 text-sm text-slate-500">{{ $r['handovers']['completed'] }} handovers · {{ $r['placed_patients'] }} placed now</p>
</div>
</div>
<div class="grid gap-6 lg:grid-cols-2">
<div class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-base font-semibold text-slate-900">Nursing packs</h2>
<ul class="mt-4 divide-y divide-slate-100 text-sm">
@forelse ($r['assessments']['by_template'] as $row)
<li class="flex items-center justify-between py-2">
<span>{{ $row['name'] }}</span>
<span class="text-slate-500">{{ $row['completed'] }}/{{ $row['total'] }}</span>
</li>
@empty
<li class="py-2 text-slate-500">No nursing assessments in range.</li>
@endforelse
</ul>
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-base font-semibold text-slate-900">By clinician</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">
<tr>
<th class="pb-2 pr-3">Staff</th>
<th class="pb-2 pr-3">MAR</th>
<th class="pb-2 pr-3">Notes</th>
<th class="pb-2 pr-3">Assess</th>
<th class="pb-2">Handover</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@forelse ($r['by_actor'] as $actor)
<tr>
<td class="py-2 pr-3 font-medium">{{ $actor['label'] }}</td>
<td class="py-2 pr-3">{{ $actor['mar_given'] }}/{{ $actor['mar_total'] }}</td>
<td class="py-2 pr-3">{{ $actor['notes'] }}</td>
<td class="py-2 pr-3">{{ $actor['assessments_completed'] }}</td>
<td class="py-2">{{ $actor['handovers'] }}</td>
</tr>
@empty
<tr><td colspan="5" class="py-3 text-slate-500">No activity attributed yet.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</x-app-layout>