Add specialty clinical records on the shared shell (Phase 3).
Deploy Ladill Care / deploy (push) Failing after 1m2s
Deploy Ladill Care / deploy (push) Failing after 1m2s
Visit-scoped structured forms for Emergency triage, Blood Bank requests, and Dentistry odontogram with derived alerts, document upload, and KPI counts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
'allergies' => [],
|
||||
'insuranceSummary' => null,
|
||||
'emergency' => false,
|
||||
'clinicalAlerts' => [],
|
||||
'currency' => 'GHS',
|
||||
])
|
||||
|
||||
@@ -74,4 +75,17 @@
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (! empty($clinicalAlerts))
|
||||
<div class="mt-3 space-y-1.5 border-t border-slate-100 pt-3">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-amber-700">Alerts</span>
|
||||
@foreach ($clinicalAlerts as $alert)
|
||||
<p @class([
|
||||
'text-xs font-medium',
|
||||
'text-rose-700' => ($alert['severity'] ?? '') === 'critical',
|
||||
'text-amber-800' => ($alert['severity'] ?? '') !== 'critical',
|
||||
])>{{ $alert['message'] ?? '' }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
@php
|
||||
$values = old('payload', $clinicalRecord?->payload ?? []);
|
||||
@endphp
|
||||
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">{{ $workspaceTabs[$activeTab] ?? 'Clinical' }}</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Structured specialty record on this visit episode.</p>
|
||||
</div>
|
||||
@if ($clinicalRecord)
|
||||
<span class="rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-slate-600">
|
||||
{{ $clinicalRecord->status }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if (! empty($clinicalAlerts))
|
||||
<div class="mt-3 space-y-1.5">
|
||||
@foreach ($clinicalAlerts as $alert)
|
||||
<div @class([
|
||||
'rounded-xl px-3 py-2 text-xs font-medium',
|
||||
'bg-rose-50 text-rose-800 border border-rose-100' => ($alert['severity'] ?? '') === 'critical',
|
||||
'bg-amber-50 text-amber-900 border border-amber-100' => ($alert['severity'] ?? '') !== 'critical',
|
||||
])>
|
||||
{{ $alert['message'] ?? '' }}
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('care.specialty.clinical.save', ['module' => $moduleKey, 'visit' => $workspaceVisit]) }}" class="mt-4 space-y-3">
|
||||
@csrf
|
||||
<input type="hidden" name="tab" value="{{ $activeTab }}">
|
||||
<input type="hidden" name="status" value="active">
|
||||
|
||||
@if (! empty($stages))
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Current stage</label>
|
||||
<select name="stage_code" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">—</option>
|
||||
@foreach ($stages as $stage)
|
||||
<option value="{{ $stage['code'] }}" @selected(old('stage_code', $clinicalRecord?->stage_code) === ($stage['code'] ?? ''))>
|
||||
{{ $stage['label'] ?? $stage['code'] }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@foreach ($clinicalFields as $field)
|
||||
@php
|
||||
$name = $field['name'];
|
||||
$value = $values[$name] ?? '';
|
||||
$id = 'field_'.$name;
|
||||
@endphp
|
||||
<div>
|
||||
<label for="{{ $id }}" class="block text-xs font-medium text-slate-600">
|
||||
{{ $field['label'] }}
|
||||
@if (! empty($field['required'])) <span class="text-rose-500">*</span> @endif
|
||||
</label>
|
||||
|
||||
@if (($field['type'] ?? '') === 'textarea')
|
||||
<textarea id="{{ $id }}" name="payload[{{ $name }}]" rows="{{ $field['rows'] ?? 3 }}"
|
||||
@if (! empty($field['required'])) required @endif
|
||||
class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">{{ $value }}</textarea>
|
||||
@elseif (($field['type'] ?? '') === 'select')
|
||||
<select id="{{ $id }}" name="payload[{{ $name }}]"
|
||||
@if (! empty($field['required'])) required @endif
|
||||
class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">Select…</option>
|
||||
@foreach ($field['options'] ?? [] as $option)
|
||||
<option value="{{ $option }}" @selected((string) $value === (string) $option)>{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@elseif (($field['type'] ?? '') === 'boolean')
|
||||
<label class="mt-2 flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="hidden" name="payload[{{ $name }}]" value="0">
|
||||
<input id="{{ $id }}" type="checkbox" name="payload[{{ $name }}]" value="1" @checked(filter_var($value, FILTER_VALIDATE_BOOLEAN))
|
||||
class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||
Yes
|
||||
</label>
|
||||
@elseif (($field['type'] ?? '') === 'number')
|
||||
<input id="{{ $id }}" type="number" step="any" name="payload[{{ $name }}]" value="{{ $value }}"
|
||||
@if (! empty($field['required'])) required @endif
|
||||
class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
@else
|
||||
<input id="{{ $id }}" type="text" name="payload[{{ $name }}]" value="{{ $value }}"
|
||||
@if (! empty($field['required'])) required @endif
|
||||
class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
@endif
|
||||
|
||||
@error('payload.'.$name)
|
||||
<p class="mt-1 text-xs text-rose-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<div class="flex flex-wrap gap-2 pt-2">
|
||||
<button type="submit" class="btn-primary">Save record</button>
|
||||
@if ($workspaceVisit->appointment)
|
||||
<a href="{{ route('care.appointments.show', $workspaceVisit->appointment) }}" class="rounded-xl border border-slate-200 px-3 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Appointment</a>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -1,8 +1,8 @@
|
||||
{{-- KPI strip --}}
|
||||
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
||||
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-6">
|
||||
@foreach ([
|
||||
['Waiting', $kpis['waiting'] ?? 0, 'text-amber-700'],
|
||||
['In care', $kpis['in_progress'] ?? 0, 'text-sky-700'],
|
||||
['Clinical open', $kpis['clinical_open'] ?? 0, 'text-rose-700'],
|
||||
['Completed today', $kpis['completed_today'] ?? 0, 'text-emerald-700'],
|
||||
['Open visits', $kpis['open_visits'] ?? 0, 'text-indigo-700'],
|
||||
['Paid today', ($currency ?? 'GHS').' '.number_format(($kpis['revenue_today_minor'] ?? 0) / 100, 2), 'text-slate-800'],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
'allergies' => $patientHeader['allergies'] ?? [],
|
||||
'insuranceSummary' => $patientHeader['insurance_summary'] ?? null,
|
||||
'emergency' => $patientHeader['emergency'] ?? false,
|
||||
'clinicalAlerts' => $patientHeader['clinical_alerts'] ?? ($clinicalAlerts ?? []),
|
||||
'currency' => $currency ?? 'GHS',
|
||||
])
|
||||
@endif
|
||||
@@ -67,28 +68,48 @@
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
@elseif (in_array($activeTab, ['clinical_notes', 'triage', 'odontogram', 'requests', 'inventory'], true))
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">{{ $workspaceTabs[$activeTab] ?? 'Clinical' }}</h3>
|
||||
<p class="mt-2 text-sm text-slate-500">
|
||||
Structured clinical forms for this tab ship in Phase 3. Use the patient chart and appointment for notes today.
|
||||
</p>
|
||||
@if ($workspaceVisit->appointment)
|
||||
<a href="{{ route('care.appointments.show', $workspaceVisit->appointment) }}" class="mt-3 inline-flex text-sm font-medium text-indigo-600">Open appointment</a>
|
||||
@endif
|
||||
</section>
|
||||
@elseif (! empty($clinicalFields))
|
||||
@include('care.specialty.partials.clinical-form')
|
||||
@elseif ($activeTab === 'orders')
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Orders</h3>
|
||||
<p class="mt-2 text-sm text-slate-500">Order lab / imaging from the consultation flow. Specialty-specific order sets arrive in Phase 3.</p>
|
||||
<a href="{{ route('care.lab.requests.index') }}" class="mt-3 inline-flex text-sm font-medium text-indigo-600">Lab requests</a>
|
||||
<p class="mt-2 text-sm text-slate-500">Order lab / imaging from the consultation flow. Specialty order sets can extend this later.</p>
|
||||
<div class="mt-3 flex flex-wrap gap-3">
|
||||
<a href="{{ route('care.lab.requests.index') }}" class="text-sm font-medium text-indigo-600">Lab requests</a>
|
||||
@if ($workspaceVisit->appointment)
|
||||
<a href="{{ route('care.appointments.show', $workspaceVisit->appointment) }}" class="text-sm font-medium text-indigo-600">Appointment</a>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
@elseif ($activeTab === 'documents')
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Documents</h3>
|
||||
<p class="mt-2 text-sm text-slate-500">Consent forms and specialty documents will attach here. Patient attachments remain on the chart.</p>
|
||||
<p class="mt-1 text-xs text-slate-500">Specialty documents attach to the patient chart for this episode.</p>
|
||||
|
||||
<form method="POST" action="{{ route('care.specialty.documents.upload', ['module' => $moduleKey, 'visit' => $workspaceVisit]) }}" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
@csrf
|
||||
<input type="file" name="attachment" required accept=".pdf,.jpg,.jpeg,.png,.webp" class="block w-full text-sm">
|
||||
<button type="submit" class="btn-primary">Upload document</button>
|
||||
</form>
|
||||
|
||||
<ul class="mt-4 space-y-2 text-sm">
|
||||
@forelse ($visitDocuments ?? [] as $doc)
|
||||
<li class="flex items-center justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-3 py-2">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate font-medium text-slate-800">{{ $doc->original_name }}</p>
|
||||
<p class="text-xs text-slate-500">{{ $doc->document_type }} · {{ $doc->created_at?->format('d M Y H:i') }}</p>
|
||||
</div>
|
||||
@if ($doc->file_path)
|
||||
<a href="{{ \Illuminate\Support\Facades\Storage::disk('public')->url($doc->file_path) }}" target="_blank" class="shrink-0 text-xs font-medium text-sky-600">Open</a>
|
||||
@endif
|
||||
</li>
|
||||
@empty
|
||||
<li class="text-slate-500">No documents yet.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
|
||||
@if ($workspaceVisit->patient)
|
||||
<a href="{{ route('care.patients.show', $workspaceVisit->patient) }}" class="mt-3 inline-flex text-sm font-medium text-indigo-600">Patient attachments</a>
|
||||
<a href="{{ route('care.patients.show', $workspaceVisit->patient) }}" class="mt-3 inline-flex text-sm font-medium text-indigo-600">Patient chart</a>
|
||||
@endif
|
||||
</section>
|
||||
@else
|
||||
@@ -117,6 +138,17 @@
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@if (! empty($clinicalAlerts))
|
||||
<div class="mt-4 space-y-1.5 border-t border-slate-100 pt-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Clinical alerts</p>
|
||||
@foreach ($clinicalAlerts as $alert)
|
||||
<p class="text-sm {{ ($alert['severity'] ?? '') === 'critical' ? 'text-rose-700' : 'text-amber-800' }}">
|
||||
{{ $alert['message'] ?? '' }}
|
||||
</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user