Files
ladill-care/resources/views/care/patients/show.blade.php
T
isaaccladandCursor e0a7a64d38
Deploy Ladill Care / deploy (push) Successful in 1m39s
Add clinical device registry, browser wedge scan, and local agent API.
Branch-scoped Care devices with hashed agent tokens, patient barcode lookup/labels, and provenance-aware vitals from a minimal device agent (Pro for agent hardware; wedge free).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 14:28:46 +00:00

295 lines
19 KiB
PHP

<x-app-layout :title="$patient->fullName()">
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<p class="font-mono text-xs text-sky-600">{{ $patient->patient_number }}</p>
<h1 class="text-2xl font-semibold text-slate-900">{{ $patient->fullName() }}</h1>
<p class="mt-1 text-sm text-slate-500">
{{ $genders[$patient->gender] ?? '—' }}
@if ($patient->date_of_birth)
· {{ $patient->date_of_birth->format('d M Y') }}
({{ $patient->date_of_birth->age }} yrs)
@endif
@if ($patient->phone) · {{ $patient->phone }} @endif
</p>
</div>
@if ($canManage)
<div class="flex gap-2">
<a href="{{ route('care.patients.label', $patient) }}" target="_blank" class="rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Print ID label</a>
<a href="{{ route('care.patients.edit', $patient) }}" class="btn-primary">Edit</a>
<x-confirm-dialog
:name="'archive-patient-'.$patient->id"
title="Archive this patient?"
message="The patient record will be archived and hidden from active lists."
:action="route('care.patients.destroy', $patient)"
method="DELETE"
confirm-label="Archive patient"
>
<x-slot:trigger>
<x-btn type="button" variant="danger">Archive</x-btn>
</x-slot:trigger>
</x-confirm-dialog>
</div>
@else
<div class="flex gap-2">
<a href="{{ route('care.patients.label', $patient) }}" target="_blank" class="rounded-lg border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Print ID label</a>
</div>
@endif
</div>
<div class="mt-6 grid gap-6 lg:grid-cols-3">
<div class="space-y-6 lg:col-span-2">
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Personal information</h2>
<dl class="mt-4 grid gap-3 sm:grid-cols-2 text-sm">
<div><dt class="text-slate-500">National ID</dt><dd class="font-medium">{{ $patient->national_id ?? '—' }}</dd></div>
<div><dt class="text-slate-500">Email</dt><dd class="font-medium">{{ $patient->email ?? '—' }}</dd></div>
<div class="sm:col-span-2"><dt class="text-slate-500">Address</dt><dd class="font-medium">{{ collect([$patient->address, $patient->city, $patient->region])->filter()->implode(', ') ?: '—' }}</dd></div>
<div><dt class="text-slate-500">Branch</dt><dd class="font-medium">{{ $patient->branch?->name ?? '—' }}</dd></div>
</dl>
@if ($patient->notes)
<p class="mt-4 text-sm text-slate-600">{{ $patient->notes }}</p>
@endif
</section>
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Medical history</h2>
<div class="mt-4 space-y-4 text-sm">
<div>
<h3 class="font-medium text-slate-800">Allergies</h3>
@forelse ($patient->allergies as $allergy)
<p class="mt-1 text-slate-600">{{ $allergy->allergen }} <span class="text-slate-400">({{ $allergySeverities[$allergy->severity] ?? $allergy->severity }})</span></p>
@empty
<p class="mt-1 text-slate-400">None recorded</p>
@endforelse
</div>
<div>
<h3 class="font-medium text-slate-800">Conditions</h3>
@forelse ($patient->conditions as $condition)
<p class="mt-1 text-slate-600">{{ $condition->condition }}@if ($condition->is_chronic) <span class="text-amber-600">(chronic)</span>@endif</p>
@empty
<p class="mt-1 text-slate-400">None recorded</p>
@endforelse
</div>
<div>
<h3 class="font-medium text-slate-800">Family history</h3>
@forelse ($patient->familyHistory as $entry)
<p class="mt-1 text-slate-600">{{ $entry->relation }}: {{ $entry->condition }}</p>
@empty
<p class="mt-1 text-slate-400">None recorded</p>
@endforelse
</div>
</div>
</section>
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Visit & clinical history</h2>
<div class="mt-4 space-y-4 text-sm">
<div>
<h3 class="font-medium text-slate-800">Recent visits</h3>
@forelse ($visits as $visit)
<p class="mt-1 text-slate-600">
{{ $visit->checked_in_at?->format('d M Y H:i') ?? '—' }}
· {{ config('care.visit_statuses')[$visit->status] ?? $visit->status }}
@if ($visit->branch) · {{ $visit->branch->name }} @endif
</p>
@empty
<p class="mt-1 text-slate-400">No visits yet</p>
@endforelse
</div>
<div>
<h3 class="font-medium text-slate-800">Consultations</h3>
@forelse ($consultations as $consultation)
<p class="mt-1">
<a href="{{ route('care.consultations.show', $consultation) }}" class="text-sky-600 hover:text-sky-700">
{{ $consultation->started_at?->format('d M Y') ?? '—' }}
@if ($consultation->practitioner) · {{ $consultation->practitioner->name }} @endif
</a>
@if ($consultation->diagnoses->isNotEmpty())
<span class="text-slate-400"> {{ $consultation->diagnoses->first()->description }}</span>
@endif
</p>
@empty
<p class="mt-1 text-slate-400">No consultations yet</p>
@endforelse
</div>
<div>
<h3 class="font-medium text-slate-800">Laboratory</h3>
@forelse ($laboratory as $lab)
<p class="mt-1">
<a href="{{ route('care.lab.requests.show', $lab) }}" class="text-sky-600 hover:text-sky-700">
{{ $lab->investigationType->name }}
</a>
· {{ $lab->completed_at?->format('d M Y') ?? '—' }}
@if ($lab->result?->is_abnormal)
<span class="text-red-600">abnormal</span>
@endif
</p>
@empty
<p class="mt-1 text-slate-400">No lab results yet</p>
@endforelse
</div>
<div>
<h3 class="font-medium text-slate-800">Prescriptions</h3>
@forelse ($prescriptions as $rx)
<p class="mt-1">
<a href="{{ route('care.prescriptions.show', $rx) }}" class="text-sky-600 hover:text-sky-700">
{{ $rx->created_at->format('d M Y') }}
</a>
· {{ config('care.prescription_statuses')[$rx->status] ?? $rx->status }}
· {{ $rx->items->pluck('name')->take(2)->join(', ') }}
</p>
@empty
<p class="mt-1 text-slate-400">No prescriptions yet</p>
@endforelse
</div>
@if ($canViewAssessments ?? false)
<div>
<div class="flex items-center justify-between gap-2">
<h3 class="font-medium text-slate-800">Assessments</h3>
<a href="{{ route('care.assessments.index', $patient) }}" class="text-xs text-sky-600 hover:text-sky-700">View all</a>
</div>
@if ($latestUniversalIntake ?? null)
@php
$ccAnswer = $latestUniversalIntake->answers->first(fn ($a) => $a->question?->code === 'chief_complaint');
@endphp
<div class="mt-2 rounded-lg border border-sky-100 bg-sky-50/50 px-3 py-2">
<p class="text-xs font-medium uppercase tracking-wide text-sky-800">Patient history form</p>
<p class="mt-1">
<a href="{{ route('care.assessments.show', $latestUniversalIntake) }}" class="text-sky-600 hover:text-sky-700">
{{ $assessmentStatuses[$latestUniversalIntake->status] ?? $latestUniversalIntake->status }}
</a>
· {{ $latestUniversalIntake->created_at?->format('d M Y') ?? '—' }}
</p>
@if ($ccAnswer)
<p class="mt-1 text-slate-600">{{ \Illuminate\Support\Str::limit((string) $ccAnswer->authoritativeValue(), 120) }}</p>
@endif
</div>
@endif
@forelse ($recentAssessments as $assessment)
<p class="mt-1">
<a href="{{ route('care.assessments.show', $assessment) }}" class="text-sky-600 hover:text-sky-700">
{{ $assessment->template->name }}
</a>
· {{ ($assessmentStatuses[$assessment->status] ?? $assessment->status) }}
· {{ $assessment->created_at?->format('d M Y') ?? '—' }}
</p>
@empty
<p class="mt-1 text-slate-400">No assessments yet</p>
@endforelse
@if ($canViewAssessments ?? false)
<p class="mt-2 flex flex-wrap gap-3">
@if ($canCaptureAssessment ?? false)
<a href="{{ route('care.assessments.create', $patient) }}" class="text-sm font-medium text-sky-600 hover:text-sky-700">Start assessment</a>
@endif
<a href="{{ route('care.pathways.index', $patient) }}" class="text-sm font-medium text-sky-600 hover:text-sky-700">Condition pathways</a>
<a href="{{ route('care.outcomes.index', $patient) }}" class="text-sm font-medium text-sky-600 hover:text-sky-700">Follow-up scores</a>
</p>
@endif
</div>
@endif
</div>
</section>
</div>
<div class="space-y-6">
@if ($canManage)
<section class="rounded-2xl border border-slate-200 bg-white p-6" x-data="{ tab: 'sms' }">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Message patient</h2>
<p class="mt-1 text-xs text-slate-500">
Uses suite messaging by default (no API keys).
<a href="{{ $messagingSettingsUrl ?? '#' }}" class="text-teal-700 underline" target="_blank" rel="noopener">Account messaging settings</a>
·
<a href="{{ route('care.integrations') }}" class="text-slate-500 underline">Advanced keys</a>
</p>
@if (! ($messagingSmsReady ?? false) || ! ($messagingEmailReady ?? false))
<p class="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
@if (! ($messagingSmsReady ?? false) && ! ($messagingEmailReady ?? false))
Messaging is not available yet. Bind a mailbox under Account Messaging once platform keys are configured, or connect product keys under Integrations.
@elseif (! ($messagingSmsReady ?? false))
SMS is not available (platform SMS key missing and no product SMS credentials).
@else
Email is not available (platform email key missing and no Bird credentials). Set a mailbox under Account Messaging when suite email is live.
@endif
</p>
@endif
<div class="mt-4 flex gap-1 rounded-xl bg-slate-100 p-1 text-sm">
<button type="button" @click="tab = 'sms'" :class="tab === 'sms' ? 'bg-white shadow-sm text-slate-900' : 'text-slate-500'" class="flex-1 rounded-lg px-3 py-1.5 font-medium">SMS</button>
<button type="button" @click="tab = 'email'" :class="tab === 'email' ? 'bg-white shadow-sm text-slate-900' : 'text-slate-500'" class="flex-1 rounded-lg px-3 py-1.5 font-medium">Email</button>
</div>
<form x-show="tab === 'sms'" x-cloak method="POST" action="{{ route('care.patients.sms', $patient) }}" class="mt-4 space-y-3">
@csrf
<p class="text-xs text-slate-500">To: {{ $patient->phone ?: 'no phone on file' }}</p>
<textarea name="message" rows="3" maxlength="480" placeholder="Text message" required class="block w-full rounded-xl border-slate-300 text-sm focus:border-sky-500 focus:ring-sky-500">{{ old('message') }}</textarea>
<div class="text-right">
<button type="submit" class="btn-primary" @if(! $patient->phone || ! ($messagingSmsReady ?? false)) disabled @endif>Send SMS</button>
</div>
</form>
<form x-show="tab === 'email'" x-cloak method="POST" action="{{ route('care.patients.email', $patient) }}" class="mt-4 space-y-3">
@csrf
<p class="text-xs text-slate-500">To: {{ $patient->email ?: 'no email on file' }}</p>
<input type="text" name="subject" value="{{ old('subject') }}" placeholder="Subject" required class="block w-full rounded-xl border-slate-300 text-sm focus:border-sky-500 focus:ring-sky-500">
<textarea name="body" rows="4" placeholder="Message" required class="block w-full rounded-xl border-slate-300 text-sm focus:border-sky-500 focus:ring-sky-500">{{ old('body') }}</textarea>
<div class="text-right">
<button type="submit" class="btn-primary" @if(! $patient->email || ! ($messagingEmailReady ?? false)) disabled @endif>Send email</button>
</div>
</form>
</section>
@endif
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Emergency contacts</h2>
@forelse ($patient->emergencyContacts as $contact)
<div class="mt-3 text-sm">
<p class="font-medium">{{ $contact->name }}@if ($contact->is_primary) <span class="text-sky-600">(primary)</span>@endif</p>
<p class="text-slate-600">{{ $contact->phone }} · {{ $contact->relationship ?? '—' }}</p>
</div>
@empty
<p class="mt-3 text-sm text-slate-400">None recorded</p>
@endforelse
</section>
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Insurance</h2>
@forelse ($patient->insurancePolicies as $policy)
<div class="mt-3 text-sm">
<p class="font-medium">{{ $policy->provider_name }}</p>
<p class="text-slate-600">{{ $policy->policy_number ?? '—' }} · {{ $policy->coverage_type ?? '—' }}</p>
</div>
@empty
<p class="mt-3 text-sm text-slate-400">None recorded</p>
@endforelse
</section>
@php $money = fn ($minor) => config('care.billing.currency').' '.number_format($minor / 100, 2); @endphp
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Billing</h2>
<div class="mt-3 space-y-2 text-sm">
@forelse ($invoices as $invoice)
<div>
<a href="{{ route('care.bills.show', $invoice) }}" class="font-medium text-sky-600 hover:text-sky-700">{{ $invoice->invoice_number }}</a>
<p class="text-slate-600">{{ $invoice->created_at->format('d M Y') }} · {{ $money($invoice->total_minor) }} · {{ config('care.bill_statuses')[$invoice->status] ?? $invoice->status }}</p>
</div>
@empty
<p class="text-slate-400">No invoices yet</p>
@endforelse
</div>
</section>
<section class="rounded-2xl border border-slate-200 bg-white p-6">
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Documents</h2>
<ul class="mt-3 space-y-2 text-sm">
@forelse ($patient->attachments as $attachment)
<li>
<a href="{{ $attachment->url() }}" target="_blank" class="text-sky-600 hover:underline">{{ $attachment->original_name }}</a>
</li>
@empty
<li class="text-slate-400">No documents uploaded</li>
@endforelse
</ul>
</section>
</div>
</div>
</x-app-layout>