@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.
--}}
{{--
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)
@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)
@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)
@endcan
@endforeach
{{--
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. --}}
@endcan
@endif