Deploy Ladill Care / deploy (push) Successful in 1m9s
Keep a short patient-focused empty state and unit links without explaining nurse duty. Co-authored-by: Cursor <cursoragent@cursor.com>
410 lines
27 KiB
PHP
410 lines
27 KiB
PHP
<x-app-layout :title="$patient->fullName()">
|
|
@include('care.partials.patient-header', [
|
|
'patient' => $patient,
|
|
'outstandingMinor' => $patientHeaderMeta['outstanding_minor'] ?? 0,
|
|
'allergies' => $patientHeaderMeta['allergies'] ?? [],
|
|
'insuranceSummary' => $patientHeaderMeta['insurance_summary'] ?? null,
|
|
'emergency' => $patientHeaderMeta['emergency'] ?? false,
|
|
'currency' => $currency ?? 'GHS',
|
|
])
|
|
|
|
<div class="mt-4 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
<div>
|
|
<p class="text-sm text-slate-500">
|
|
@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">
|
|
{{ ($isNurseStation ?? false) && ! ($canConsult ?? false) ? 'Visit & nursing care' : '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
|
|
@if ($visit->careUnit)
|
|
· {{ $visit->careUnit->name }}@if ($visit->bed) / {{ $visit->bed->label }}@endif
|
|
@endif
|
|
</p>
|
|
@empty
|
|
<p class="mt-1 text-slate-400">No visits yet</p>
|
|
@endforelse
|
|
</div>
|
|
|
|
@if (($isNurseStation ?? false) && ! ($canConsult ?? false))
|
|
@if ($activePlacedVisit ?? null)
|
|
<div class="rounded-xl border border-teal-100 bg-teal-50/60 px-3 py-3">
|
|
<p class="text-xs font-semibold uppercase tracking-wide text-teal-800">Patient on unit</p>
|
|
<p class="mt-1 font-medium text-slate-900">
|
|
{{ $activePlacedVisit->careUnit?->name ?? 'Care unit' }}
|
|
@if ($activePlacedVisit->careUnit?->department?->branch)
|
|
· {{ $activePlacedVisit->careUnit->department->branch->name }}
|
|
@endif
|
|
@if ($activePlacedVisit->bed)
|
|
· Bed {{ $activePlacedVisit->bed->label }}
|
|
@endif
|
|
</p>
|
|
@if ($latestVitals ?? null)
|
|
<p class="mt-1 text-xs text-slate-600">
|
|
Latest vitals
|
|
{{ $latestVitals->recorded_at?->format('d M H:i') }}:
|
|
BP {{ $latestVitals->bp_systolic }}/{{ $latestVitals->bp_diastolic }}
|
|
· P {{ $latestVitals->pulse }}
|
|
· SpO₂ {{ $latestVitals->spo2 }}
|
|
</p>
|
|
@else
|
|
<p class="mt-1 text-xs text-slate-500">No vitals recorded on this placement yet.</p>
|
|
@endif
|
|
<p class="mt-3 flex flex-wrap gap-3">
|
|
<a href="{{ route('care.nursing.station', ['unit' => $activePlacedVisit->care_unit_id]) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Open care unit</a>
|
|
@if ($canViewMar ?? false)
|
|
<a href="{{ route('care.visits.mar', $activePlacedVisit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">MAR chart</a>
|
|
@endif
|
|
@if ($activePlacedVisit->careUnit)
|
|
<a href="{{ route('care.care-units.assessments', $activePlacedVisit->careUnit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Vitals & assess</a>
|
|
<a href="{{ route('care.care-units.notes', $activePlacedVisit->careUnit) }}" class="text-sm font-medium text-teal-700 hover:text-teal-900">Nursing notes</a>
|
|
@endif
|
|
</p>
|
|
</div>
|
|
@else
|
|
<div class="rounded-xl border border-dashed border-slate-200 px-3 py-3 text-slate-600">
|
|
<p class="font-medium text-slate-800">This patient is not on a care unit right now.</p>
|
|
<p class="mt-2 flex flex-wrap gap-3">
|
|
@forelse ($nurseDutyUnits ?? [] as $dutyUnit)
|
|
<a href="{{ route('care.nursing.station', ['unit' => $dutyUnit->id]) }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">
|
|
Open {{ $dutyUnit->name }}
|
|
</a>
|
|
@empty
|
|
<a href="{{ route('care.nursing.station') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">My care unit</a>
|
|
@endforelse
|
|
</p>
|
|
</div>
|
|
@endif
|
|
|
|
<div>
|
|
<div class="flex items-center justify-between gap-2">
|
|
<h3 class="font-medium text-slate-800">Nursing assessments</h3>
|
|
@if ($canViewAssessments ?? false)
|
|
<a href="{{ route('care.assessments.index', $patient) }}" class="text-xs text-sky-600 hover:text-sky-700">View all</a>
|
|
@endif
|
|
</div>
|
|
@forelse ($nursingAssessments ?? [] 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 nursing assessments yet — start NEWS2, Braden, Morse, or pain from the unit board.</p>
|
|
@endforelse
|
|
</div>
|
|
|
|
<div>
|
|
<h3 class="font-medium text-slate-800">Medications</h3>
|
|
@forelse ($prescriptions as $rx)
|
|
<p class="mt-1 text-slate-600">
|
|
{{ $rx->created_at->format('d M Y') }}
|
|
· {{ 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 on record</p>
|
|
@endforelse
|
|
</div>
|
|
|
|
<div>
|
|
<h3 class="font-medium text-slate-800">Laboratory</h3>
|
|
@forelse ($laboratory as $lab)
|
|
<p class="mt-1 text-slate-600">
|
|
{{ $lab->investigationType->name }}
|
|
· {{ $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>
|
|
@else
|
|
<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>
|
|
@if ($canViewLab ?? true)
|
|
<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>
|
|
@endif
|
|
@if ($canViewRx ?? true)
|
|
<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>
|
|
@endif
|
|
@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
|
|
<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
|
|
@if ($canViewPathways ?? false)
|
|
<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>
|
|
@endif
|
|
</p>
|
|
</div>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="space-y-6">
|
|
@include('care.partials.patient-timeline', ['events' => $patientTimeline ?? []])
|
|
|
|
@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>
|
|
|
|
@if (($canViewBills ?? false) || ($canConsult ?? false) || ($canManage ?? false))
|
|
@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>
|
|
@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">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>
|