@extends('layouts.app') @section('title', $order->reference) @php use App\Modules\Laboratory\Enums\OrderStatus; /* | Bootstrap's contextual names to the pill palette. The enums name a tone | for the old badges and are used elsewhere, so they are mapped here rather | than renamed underneath everything that already reads them. */ $pill = static fn (string $bootstrapTone): string => match ($bootstrapTone) { 'success' => 'px-pill--green', 'warning' => 'px-pill--amber', 'danger' => 'px-pill--red', 'info' => 'px-pill--blue', 'primary' => 'px-pill--violet', default => 'px-pill--grey', }; /* | What the bench needs to know before reading a single row: how much of | this order is done, and whether anything on it needs acting on now. | | Counted from the items already loaded — no extra queries, and the numbers | cannot disagree with the list underneath them. */ $current = $order->items->map(fn ($item) => $item->results->firstWhere('superseded_by_id', null)); $tallies = [ [ 'label' => __('laboratory::messages.summary.requested'), 'value' => $order->items->count(), 'icon' => 'bi-list-check', 'tone' => 'violet', ], [ 'label' => __('laboratory::messages.summary.resulted'), 'value' => $current->filter()->count(), 'icon' => 'bi-check2-circle', 'tone' => 'green', ], [ 'label' => __('laboratory::messages.summary.pending'), 'value' => $current->filter(fn ($r) => $r === null)->count(), 'icon' => 'bi-hourglass-split', 'tone' => 'amber', ], [ 'label' => __('laboratory::messages.summary.abnormal'), 'value' => $current->filter(fn ($r) => $r?->flag?->isAbnormal() && ! $r->isCritical())->count(), 'icon' => 'bi-exclamation-triangle', 'tone' => 'amber', ], [ 'label' => __('laboratory::messages.summary.critical'), 'value' => $current->filter(fn ($r) => $r?->isCritical() ?? false)->count(), 'icon' => 'bi-exclamation-octagon', 'tone' => 'pink', ], ]; @endphp @section('content') {{-- ============================ IDENTITY ============================ --}} {{-- Swapped after an inline save: entering a result moves the tallies above as surely as it fills the card below. --}}
{{-- Three tiers, not one. Everything here used to be a single dot-separated row in one colour and one size: the accession number, the patient's hospital number, their age, who asked for the order and when. All of it equally loud, so none of it was findable — and the accession, rendered bold and black, competed with the name it sat under. It is now what the order is, then who it is for, then where it came from — with the state of it beside the name, which is the first place anybody looks. --}}

{{ $patient?->name ?? '—' }}

{{ $order->status->label() }} {{ $order->priority->label() }} @if ($order->fasting) {{ __('laboratory::messages.orders.fasting') }} @endif
{{-- The accession first and set apart: it is what this page is, and it is the number quoted down a telephone. --}}
{{ $order->reference }} @if ($patient) {{ $patient->mrn }} {{ $patient->ageLabel }} @endif
{{-- Provenance, deliberately quietest. It is consulted when something is queried, not while working down the list. --}}
{{ __('laboratory::messages.orders.ordered_by') }}: {{ $order->orderedBy->name }} {{ $order->ordered_at->format('d M Y, H:i') }}
@foreach ($tallies as $tally) {{ $tally['value'] }} {{ $tally['label'] }} @endforeach
@can('print', $order) {{-- Both open in their own tab, as the listing already did. Printing is something done beside the record rather than instead of it: the bench keeps this screen open while the dialog is up, and comes back to it afterwards. --}} {{ __('laboratory::messages.orders.report') }} {{ __('laboratory::messages.orders.request_form') }} @endcan
{{-- The laboratory's own bill, unsettled. Top of the screen and before anything clinical, because it changes what the bench may do next: the collection form below is replaced by an override that asks for a reason. --}} @if (($unpaidMinor ?? 0) > 0)
{{ __('laboratory::messages.specimens.unpaid_title') }} {{ __('laboratory::messages.specimens.unpaid_notice', [ 'amount' => \App\Support\Money\Money::fromMinor($unpaidMinor, $currency)->format(), ]) }} @if (Route::has('invoices.index')) {{ __('laboratory::messages.specimens.go_to_invoice') }} @endif
@endif {{-- Standing notices, not flash messages: px-advice rather than , which is the dismissible banner the layout uses for session feedback. --}} @if ($order->clinical_details)
{{ $order->clinical_details }}
@endif @if ($order->status === \App\Modules\Laboratory\Enums\OrderStatus::Cancelled)
{{ __('laboratory::messages.orders.cancelled_notice', ['reason' => $order->cancellation_reason]) }}
@endif {{-- A critical result that nobody has been recorded as told. This is the failure the whole mechanism exists to prevent — a potassium of 7.2 sitting on a screen nobody opened — so it sits at the top of the page in red, not in a list somewhere. --}} @php $awaiting = $order->items->flatMap->results->filter->awaitsCriticalAcknowledgement(); @endphp @if ($awaiting->isNotEmpty())

{{ __('laboratory::messages.critical.banner') }}

{{ __('laboratory::messages.critical.hint') }}

@foreach ($awaiting as $result) @can('acknowledge', $result)
@csrf
{{ $result->item->test_name }} {{ $result->printableValue($result->item->decimal_places) }} {{ $result->flag->label() }}
@endcan @endforeach
@endif

{{ __('laboratory::messages.results.title') }}

{{ __('laboratory::messages.orders.progress', [ 'done' => $current->filter()->count(), 'total' => $order->items->count(), ]) }}
{{-- One card per analyte, folded shut until wanted. A worklist of twenty tests as a table put an entry form under every row whether or not anybody was working on it, and the thing being typed into was three screens down. Each card now shows what came back at a glance and opens onto its own form.
rather than a script: the CSP forbids inline JavaScript, and native disclosure brings keyboard support and a find-in-page that opens the section it matched. --}} @php /* | Grouped by the panel each test was ordered | under. An order for a full blood count is one | thing a clinician asked for, and thirteen flat | cards make it read as thirteen — with the | analyte somebody is looking for buried among | tests they never chose individually. | | Items ordered on their own keep a null panel, | and are rendered exactly as before. */ $grouped = $order->items->groupBy('panel_id'); @endphp @forelse ($grouped as $panelId => $panelItems) @if ($panelId === null) @foreach ($panelItems as $item) @include('laboratory::orders.partials.test-card', ['item' => $item]) @endforeach @else @php $done = $panelItems->filter( fn ($panelItem) => $panelItem->results->firstWhere('superseded_by_id', null) !== null )->count(); @endphp {{-- Open while there is work in it, shut once every analyte has come back: a finished panel is a heading to scroll past, not a list to read. --}}
count()) open @endif> {{ $panelItems->first()->panel?->name ?? __('laboratory::messages.results.panel') }} {{ $panelItems->first()->panel?->code }} {{ __('laboratory::messages.orders.progress', [ 'done' => $done, 'total' => $panelItems->count(), ]) }}
@foreach ($panelItems as $item) @include('laboratory::orders.partials.test-card', ['item' => $item]) @endforeach
@endif @empty

{{ __('laboratory::messages.items.empty') }}

{{ __('laboratory::messages.items.empty_text') }}

@endforelse @php /* | Entered but not yet released. A scientist reads a panel as | a set — the differential only means anything together — so | releasing thirteen analytes should be one decision, not | thirteen clicks down a column. | | Superseded versions and results marked entered in error | are not waiting for anything, so neither counts. */ $awaitingRelease = $order->items ->map(static fn ($item) => $item->results->firstWhere('superseded_by_id', null)) ->filter(static fn ($result) => $result !== null && ! $result->isVerified() && $result->status !== \App\Modules\Laboratory\Enums\ResultStatus::EnteredInError) ->count(); @endphp @php /* | Cards that are still open for entry — the same condition | the card partial uses to draw its form. This is what | "Save all" would submit, so the button appears only when | there is something for it to do. */ $openForEntry = $order->items ->filter(static function ($item): bool { $current = $item->results->firstWhere('superseded_by_id', null); return ! $item->status->isFinished() && ($current === null || ! $current->isVerified()); }) ->count(); @endphp @if ($openForEntry > 0 || $awaitingRelease > 0)
@if ($awaitingRelease > 0) {{ __('laboratory::messages.results.awaiting_release', ['count' => $awaitingRelease]) }} @endif {{-- One click for a screen full of values. A differential count is thirteen boxes, and pressing Save thirteen times is thirteen round trips for what the technician thinks of as one act of transcription. The per-card Save stays exactly where it was: it is the only thing that works without JavaScript, and it is what a bench uses when a single analyte comes back late. This button is the shortcut, not the mechanism. --}} @can('create', \App\Modules\Laboratory\Models\LabResult::class) @if ($openForEntry > 0) @endif @endcan @if ($awaitingRelease > 0) @can('verifyAny', \App\Modules\Laboratory\Models\LabResult::class) {{-- The question is asked on the button itself. Releasing is what puts a number in front of a clinician, and a confirmation at the top of a long screen is answered far from the thing it is about. --}}
@csrf
@endcan @endif
@endif
@endsection