Deploy Ladill Care / deploy (push) Successful in 1m22s
Replace internal design jargon (Layer 1, narrative source of truth, pathway instruments) with plain clinical language on consultations, patient charts, settings, and nav.
290 lines
19 KiB
PHP
290 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.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>
|
|
@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>
|