Ship commercial Dentistry suite with odontogram and treatment plans.
Deploy Ladill Care / deploy (push) Failing after 36s
Deploy Ladill Care / deploy (push) Failing after 36s
Replace placeholder clinical forms with patient-level FDI charting, multi-visit plans, procedure-to-bill linking, imaging, reports, and demo seed data. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Dental chart · {{ $patient->fullName() }}</title>
|
||||
<style>
|
||||
body { font-family: ui-sans-serif, system-ui, sans-serif; color: #0f172a; padding: 24px; }
|
||||
h1 { font-size: 18px; margin: 0 0 4px; }
|
||||
h2 { font-size: 14px; margin: 24px 0 8px; }
|
||||
.meta { color: #64748b; font-size: 12px; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
th, td { border: 1px solid #e2e8f0; padding: 6px 8px; text-align: left; }
|
||||
th { background: #f8fafc; }
|
||||
.teeth { display: flex; flex-wrap: wrap; gap: 4px; margin: 8px 0; }
|
||||
.tooth { width: 36px; text-align: center; border: 1px solid #cbd5e1; border-radius: 6px; padding: 6px 0; font-size: 11px; font-weight: 600; }
|
||||
.tooth.finding { background: #fffbeb; border-color: #fcd34d; }
|
||||
@media print { button { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="window.print()" style="margin-bottom:16px;padding:8px 12px;">Print</button>
|
||||
<h1>{{ $patient->fullName() }}</h1>
|
||||
<p class="meta">
|
||||
{{ $patient->patient_number }}
|
||||
· Visit {{ $visit->uuid }}
|
||||
· Charted {{ $chart->charted_at?->format('d M Y H:i') ?? '—' }}
|
||||
</p>
|
||||
|
||||
<h2>Odontogram (FDI)</h2>
|
||||
@foreach (['upper' => 'Upper', 'lower' => 'Lower'] as $key => $label)
|
||||
<p class="meta">{{ $label }}</p>
|
||||
<div class="teeth">
|
||||
@foreach ($rows[$key] as $code)
|
||||
@php $t = $teethMap[$code] ?? []; $finding = ($t['status'] ?? 'present') !== 'present' || !empty($t['conditions']); @endphp
|
||||
<div class="tooth {{ $finding ? 'finding' : '' }}">{{ $code }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
<h2>Findings</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Tooth</th><th>Status</th><th>Surfaces</th><th>Conditions</th><th>Notes</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($teethMap as $code => $t)
|
||||
@if (($t['status'] ?? 'present') !== 'present' || ! empty($t['conditions']) || ! empty($t['notes']))
|
||||
<tr>
|
||||
<td>{{ $code }}</td>
|
||||
<td>{{ $t['status'] ?? 'present' }}</td>
|
||||
<td>{{ implode('', $t['surfaces'] ?? []) }}</td>
|
||||
<td>
|
||||
@foreach ($t['conditions'] ?? [] as $c)
|
||||
{{ $conditions[$c] ?? $c }}@if(!$loop->last), @endif
|
||||
@endforeach
|
||||
</td>
|
||||
<td>{{ $t['notes'] ?? '' }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if ($plan)
|
||||
<h2>Treatment plan ({{ $plan->status }})</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Procedure</th><th>Tooth</th><th>Status</th><th>Estimate</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($plan->items as $item)
|
||||
<tr>
|
||||
<td>{{ $item->label }}</td>
|
||||
<td>{{ $item->tooth_code ?? '—' }}</td>
|
||||
<td>{{ $item->status }}</td>
|
||||
<td>{{ number_format($item->estimate_minor / 100, 2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,72 @@
|
||||
<x-app-layout title="Dentistry reports">
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-xl font-semibold text-slate-900">Dentistry reports</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Procedures, revenue, unfinished plans, and imaging volume.</p>
|
||||
</div>
|
||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600">← Back to queue</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Unfinished plan items</p>
|
||||
<p class="mt-2 text-2xl font-semibold tabular-nums text-slate-900">{{ number_format($report['unfinished_items']) }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Avg chair time (month)</p>
|
||||
<p class="mt-2 text-2xl font-semibold tabular-nums text-slate-900">
|
||||
{{ $report['avg_chair_minutes'] !== null ? $report['avg_chair_minutes'].' min' : '—' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Imaging studies (month)</p>
|
||||
<p class="mt-2 text-2xl font-semibold tabular-nums text-slate-900">
|
||||
{{ number_format($report['imaging_by_modality']->sum('total')) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Procedures today</h2>
|
||||
<ul class="mt-3 space-y-1 text-sm">
|
||||
@forelse ($report['procedures_today'] as $row)
|
||||
<li class="flex justify-between gap-3">
|
||||
<span>{{ $row->label }} × {{ $row->total }}</span>
|
||||
<span class="tabular-nums">{{ $currency }} {{ number_format($row->amount_minor / 100, 2) }}</span>
|
||||
</li>
|
||||
@empty
|
||||
<li class="text-slate-500">No procedures today.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Procedures this month</h2>
|
||||
<ul class="mt-3 space-y-1 text-sm">
|
||||
@forelse ($report['procedures_month'] as $row)
|
||||
<li class="flex justify-between gap-3">
|
||||
<span>{{ $row->label }} × {{ $row->total }}</span>
|
||||
<span class="tabular-nums">{{ $currency }} {{ number_format($row->amount_minor / 100, 2) }}</span>
|
||||
</li>
|
||||
@empty
|
||||
<li class="text-slate-500">No procedures this month.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Imaging by modality (month)</h2>
|
||||
<ul class="mt-3 space-y-1 text-sm">
|
||||
@forelse ($report['imaging_by_modality'] as $row)
|
||||
<li class="flex justify-between gap-3">
|
||||
<span>{{ $modalities[$row->modality] ?? $row->modality }}</span>
|
||||
<span class="tabular-nums">{{ $row->total }}</span>
|
||||
</li>
|
||||
@empty
|
||||
<li class="text-slate-500">No imaging this month.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,77 @@
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Dental imaging</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Upload PA, bitewing, OPG, or clinical photos linked to teeth.</p>
|
||||
|
||||
<form method="POST" action="{{ route('care.specialty.dentistry.images', $workspaceVisit) }}" enctype="multipart/form-data" class="mt-4 space-y-3">
|
||||
@csrf
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">File</label>
|
||||
<input type="file" name="attachment" required accept=".pdf,.jpg,.jpeg,.png,.webp" class="mt-1 block w-full text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Modality</label>
|
||||
<select name="modality" required class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
@foreach ($dentalModalities as $value => $label)
|
||||
<option value="{{ $value }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-slate-600">Caption</label>
|
||||
<input type="text" name="caption" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<p class="text-xs font-medium text-slate-600">Teeth</p>
|
||||
<div class="mt-2 flex max-h-28 flex-wrap gap-2 overflow-y-auto">
|
||||
@foreach (array_merge($dentalRows['upper'] ?? [], $dentalRows['lower'] ?? []) as $code)
|
||||
<label class="inline-flex items-center gap-1 rounded border border-slate-200 px-1.5 py-0.5 text-xs">
|
||||
<input type="checkbox" name="tooth_codes[]" value="{{ $code }}" class="rounded border-slate-300 text-indigo-600">
|
||||
{{ $code }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-700">
|
||||
<input type="checkbox" name="add_fee" value="1" class="rounded border-slate-300 text-indigo-600" checked>
|
||||
Add imaging fee to bill
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Upload image</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse ($dentalImages as $image)
|
||||
@php
|
||||
$url = $image->attachment?->file_path
|
||||
? \Illuminate\Support\Facades\Storage::disk('public')->url($image->attachment->file_path)
|
||||
: null;
|
||||
@endphp
|
||||
<div class="overflow-hidden rounded-xl border border-slate-100 bg-slate-50">
|
||||
@if ($url && str_starts_with((string) $image->attachment?->mime_type, 'image/'))
|
||||
<a href="{{ $url }}" target="_blank">
|
||||
<img src="{{ $url }}" alt="{{ $image->caption ?: $image->modality }}" class="h-32 w-full object-cover">
|
||||
</a>
|
||||
@elseif ($url)
|
||||
<a href="{{ $url }}" target="_blank" class="flex h-32 items-center justify-center text-sm font-medium text-indigo-600">Open file</a>
|
||||
@endif
|
||||
<div class="px-3 py-2 text-xs">
|
||||
<p class="font-semibold text-slate-800">{{ $dentalModalities[$image->modality] ?? $image->modality }}</p>
|
||||
<p class="text-slate-500">
|
||||
{{ $image->captured_at?->format('d M Y H:i') }}
|
||||
@if (! empty($image->tooth_codes))
|
||||
· {{ implode(', ', $image->tooth_codes) }}
|
||||
@endif
|
||||
</p>
|
||||
@if ($image->caption)
|
||||
<p class="mt-0.5 text-slate-600">{{ $image->caption }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-slate-500 sm:col-span-2">No images yet.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,25 @@
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Dental visit notes</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Short clinical notes for this visit — not a GP SOAP form.</p>
|
||||
|
||||
<form method="POST" action="{{ route('care.specialty.dentistry.notes', $workspaceVisit) }}" class="mt-4 space-y-3">
|
||||
@csrf
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Chief complaint</label>
|
||||
<textarea name="chief_complaint" rows="2" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">{{ old('chief_complaint', $dentalVisitNote?->chief_complaint) }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Soft tissue</label>
|
||||
<textarea name="soft_tissue" rows="2" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">{{ old('soft_tissue', $dentalVisitNote?->soft_tissue) }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Occlusion / bite</label>
|
||||
<textarea name="occlusion" rows="2" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">{{ old('occlusion', $dentalVisitNote?->occlusion) }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Other notes</label>
|
||||
<textarea name="notes" rows="3" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">{{ old('notes', $dentalVisitNote?->notes) }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Save notes</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,101 @@
|
||||
@php
|
||||
$teethMap = $dentalTeethMap ?? [];
|
||||
$selected = old('tooth_code', request('tooth', '16'));
|
||||
$selectedData = $teethMap[$selected] ?? ['status' => 'present', 'surfaces' => [], 'conditions' => [], 'notes' => null];
|
||||
@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">Odontogram (FDI)</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Click a tooth to chart status, surfaces, and conditions. Changes are saved to the patient chart.</p>
|
||||
</div>
|
||||
<a href="{{ route('care.specialty.dentistry.print', $workspaceVisit) }}" target="_blank" class="text-sm font-medium text-indigo-600">Print chart / plan</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-2">
|
||||
@foreach (['upper' => 'Upper', 'lower' => 'Lower'] as $rowKey => $rowLabel)
|
||||
<div>
|
||||
<p class="mb-1 text-[10px] font-semibold uppercase tracking-wide text-slate-400">{{ $rowLabel }}</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach (($dentalRows[$rowKey] ?? []) as $code)
|
||||
@php
|
||||
$t = $teethMap[$code] ?? ['status' => 'present', 'conditions' => []];
|
||||
$hasFinding = ($t['status'] ?? 'present') !== 'present' || ! empty($t['conditions']);
|
||||
@endphp
|
||||
<a
|
||||
href="{{ route('care.specialty.workspace', ['module' => 'dentistry', 'visit' => $workspaceVisit, 'tab' => 'odontogram', 'tooth' => $code]) }}"
|
||||
@class([
|
||||
'inline-flex h-10 w-9 items-center justify-center rounded-lg border text-xs font-semibold tabular-nums transition',
|
||||
'border-indigo-400 bg-indigo-50 text-indigo-900 ring-2 ring-indigo-400' => $code === $selected,
|
||||
'border-amber-300 bg-amber-50 text-amber-900' => $hasFinding && $code !== $selected,
|
||||
'border-slate-200 bg-slate-50 text-slate-700 hover:border-slate-300' => ! $hasFinding && $code !== $selected,
|
||||
])
|
||||
title="Tooth {{ $code }}"
|
||||
>
|
||||
{{ $code }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<form
|
||||
method="POST"
|
||||
action="{{ route('care.specialty.dentistry.tooth', $workspaceVisit) }}"
|
||||
class="mt-6 grid gap-4 border-t border-slate-100 pt-4 sm:grid-cols-2"
|
||||
>
|
||||
@csrf
|
||||
<input type="hidden" name="tooth_code" value="{{ $selected }}">
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Selected tooth</label>
|
||||
<p class="mt-1 text-lg font-semibold tabular-nums text-slate-900">{{ $selected }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Status</label>
|
||||
<select name="status" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
@foreach ($dentalStatuses as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($selectedData['status'] ?? 'present') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-2">
|
||||
<p class="text-xs font-medium text-slate-600">Surfaces</p>
|
||||
<div class="mt-2 flex flex-wrap gap-3">
|
||||
@foreach ($dentalSurfaces as $surface)
|
||||
<label class="inline-flex items-center gap-1.5 text-sm text-slate-700">
|
||||
<input type="checkbox" name="surfaces[]" value="{{ $surface }}" class="rounded border-slate-300 text-indigo-600"
|
||||
@checked(in_array($surface, $selectedData['surfaces'] ?? [], true))>
|
||||
{{ $surface }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-2">
|
||||
<p class="text-xs font-medium text-slate-600">Conditions</p>
|
||||
<div class="mt-2 flex flex-wrap gap-3">
|
||||
@foreach ($dentalConditions as $value => $label)
|
||||
<label class="inline-flex items-center gap-1.5 text-sm text-slate-700">
|
||||
<input type="checkbox" name="conditions[]" value="{{ $value }}" class="rounded border-slate-300 text-indigo-600"
|
||||
@checked(in_array($value, $selectedData['conditions'] ?? [], true))>
|
||||
{{ $label }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-slate-600">Notes</label>
|
||||
<textarea name="notes" rows="2" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">{{ $selectedData['notes'] ?? '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="sm:col-span-2">
|
||||
<button type="submit" class="btn-primary">Save tooth</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,73 @@
|
||||
@php
|
||||
$plan = $dentalPlan;
|
||||
$openItems = $plan?->items?->whereIn('status', ['proposed', 'accepted']) ?? collect();
|
||||
@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">Overview</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Visit summary and open treatment work.</p>
|
||||
</div>
|
||||
<a href="{{ route('care.specialty.dentistry.reports') }}" class="text-sm font-medium text-indigo-600">Dentistry reports</a>
|
||||
</div>
|
||||
|
||||
<dl class="mt-4 grid gap-3 text-sm sm:grid-cols-2">
|
||||
<div>
|
||||
<dt class="text-slate-500">Visit status</dt>
|
||||
<dd class="font-medium text-slate-900">{{ ucfirst(str_replace('_', ' ', $workspaceVisit->status)) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-500">Checked in</dt>
|
||||
<dd class="font-medium text-slate-900">{{ $workspaceVisit->checked_in_at?->format('d M Y H:i') ?? '—' }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-500">Queue</dt>
|
||||
<dd class="font-medium text-slate-900">
|
||||
{{ $workspaceVisit->appointment?->queue_ticket_number ?? '—' }}
|
||||
@if ($workspaceVisit->appointment?->queue_ticket_status)
|
||||
<span class="text-slate-500">({{ $workspaceVisit->appointment->queue_ticket_status }})</span>
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-slate-500">Treatment plan</dt>
|
||||
<dd class="font-medium text-slate-900">
|
||||
@if ($plan)
|
||||
{{ ucfirst(str_replace('_', ' ', $plan->status)) }}
|
||||
· {{ $currency ?? 'GHS' }} {{ number_format($plan->estimate_minor / 100, 2) }}
|
||||
@else
|
||||
None open
|
||||
@endif
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@if ($openItems->isNotEmpty())
|
||||
<div class="mt-4 border-t border-slate-100 pt-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Unfinished plan items</p>
|
||||
<ul class="mt-2 space-y-1 text-sm">
|
||||
@foreach ($openItems->take(8) as $item)
|
||||
<li class="flex justify-between gap-3">
|
||||
<span>
|
||||
{{ $item->label }}
|
||||
@if ($item->tooth_code) <span class="text-slate-400">· {{ $item->tooth_code }}</span> @endif
|
||||
</span>
|
||||
<span class="tabular-nums text-slate-500">{{ number_format($item->estimate_minor / 100, 2) }}</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@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">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>
|
||||
@@ -0,0 +1,92 @@
|
||||
@php
|
||||
$plan = $dentalPlan;
|
||||
@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">Treatment plan</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">
|
||||
@if ($plan)
|
||||
Status: <span class="font-medium">{{ ucfirst(str_replace('_', ' ', $plan->status)) }}</span>
|
||||
· Estimate {{ $currency ?? 'GHS' }} {{ number_format($plan->estimate_minor / 100, 2) }}
|
||||
@else
|
||||
Create a plan by adding the first item.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
@if ($plan && $plan->status === 'draft')
|
||||
<form method="POST" action="{{ route('care.specialty.dentistry.plan.accept', $workspaceVisit) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-1.5 text-sm font-medium text-emerald-800 hover:bg-emerald-100">Accept plan</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<ul class="mt-4 space-y-2 text-sm">
|
||||
@forelse ($plan?->items ?? [] as $item)
|
||||
<li class="flex flex-wrap items-center justify-between gap-2 rounded-xl border border-slate-100 bg-slate-50 px-3 py-2">
|
||||
<div>
|
||||
<p class="font-medium text-slate-800">
|
||||
{{ $item->label }}
|
||||
@if ($item->tooth_code)
|
||||
<span class="text-slate-500">· {{ $item->tooth_code }}</span>
|
||||
@endif
|
||||
@if (! empty($item->surfaces))
|
||||
<span class="text-slate-400">({{ implode('', $item->surfaces) }})</span>
|
||||
@endif
|
||||
</p>
|
||||
<p class="text-xs text-slate-500">Priority {{ $item->priority }} · {{ ucfirst($item->status) }}</p>
|
||||
</div>
|
||||
<span class="tabular-nums text-slate-600">{{ number_format($item->estimate_minor / 100, 2) }}</span>
|
||||
</li>
|
||||
@empty
|
||||
<li class="text-slate-500">No plan items yet.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
|
||||
<form method="POST" action="{{ route('care.specialty.dentistry.plan-items', $workspaceVisit) }}" class="mt-6 space-y-3 border-t border-slate-100 pt-4">
|
||||
@csrf
|
||||
<h4 class="text-sm font-semibold text-slate-900">Add item</h4>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Procedure</label>
|
||||
<select name="procedure_code" required class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">Select…</option>
|
||||
@foreach ($dentalCatalog as $service)
|
||||
<option value="{{ $service['code'] }}">{{ $service['label'] }} — {{ number_format(((int) $service['amount_minor']) / 100, 2) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Tooth (FDI)</label>
|
||||
<select name="tooth_code" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">—</option>
|
||||
@foreach (array_merge($dentalRows['upper'] ?? [], $dentalRows['lower'] ?? []) as $code)
|
||||
<option value="{{ $code }}">{{ $code }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Priority (1 = highest)</label>
|
||||
<input type="number" name="priority" value="50" min="1" max="100" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<p class="text-xs font-medium text-slate-600">Surfaces</p>
|
||||
<div class="mt-2 flex flex-wrap gap-3">
|
||||
@foreach ($dentalSurfaces as $surface)
|
||||
<label class="inline-flex items-center gap-1.5 text-sm">
|
||||
<input type="checkbox" name="surfaces[]" value="{{ $surface }}" class="rounded border-slate-300 text-indigo-600">
|
||||
{{ $surface }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-slate-600">Notes</label>
|
||||
<input type="text" name="notes" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Add to plan</button>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,97 @@
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Treat today</h3>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Record a completed procedure. It posts to the visit bill automatically.</p>
|
||||
|
||||
@if ($dentalPlan?->items)
|
||||
<div class="mt-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">From plan</p>
|
||||
<ul class="mt-2 space-y-2">
|
||||
@foreach ($dentalPlan->items->whereIn('status', ['proposed', 'accepted']) as $item)
|
||||
<li class="rounded-xl border border-slate-100 bg-slate-50 px-3 py-2">
|
||||
<form method="POST" action="{{ route('care.specialty.dentistry.procedures', $workspaceVisit) }}" class="flex flex-wrap items-center justify-between gap-2">
|
||||
@csrf
|
||||
<input type="hidden" name="procedure_code" value="{{ $item->procedure_code }}">
|
||||
<input type="hidden" name="tooth_code" value="{{ $item->tooth_code }}">
|
||||
<input type="hidden" name="plan_item_id" value="{{ $item->id }}">
|
||||
<input type="hidden" name="bill" value="1">
|
||||
@foreach ($item->surfaces ?? [] as $surface)
|
||||
<input type="hidden" name="surfaces[]" value="{{ $surface }}">
|
||||
@endforeach
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-slate-800">{{ $item->label }} @if($item->tooth_code)<span class="text-slate-500">· {{ $item->tooth_code }}</span>@endif</p>
|
||||
<p class="text-xs text-slate-500">{{ number_format($item->estimate_minor / 100, 2) }}</p>
|
||||
</div>
|
||||
<button type="submit" class="rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-indigo-700">Complete & bill</button>
|
||||
</form>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('care.specialty.dentistry.procedures', $workspaceVisit) }}" class="mt-6 space-y-3 border-t border-slate-100 pt-4">
|
||||
@csrf
|
||||
<h4 class="text-sm font-semibold text-slate-900">Ad-hoc procedure</h4>
|
||||
<input type="hidden" name="bill" value="1">
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Procedure</label>
|
||||
<select name="procedure_code" required class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">Select…</option>
|
||||
@foreach ($dentalCatalog as $service)
|
||||
<option value="{{ $service['code'] }}">{{ $service['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Tooth</label>
|
||||
<select name="tooth_code" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">—</option>
|
||||
@foreach (array_merge($dentalRows['upper'] ?? [], $dentalRows['lower'] ?? []) as $code)
|
||||
<option value="{{ $code }}">{{ $code }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600">Anesthesia</label>
|
||||
<select name="anesthesia" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
<option value="">—</option>
|
||||
<option value="None">None</option>
|
||||
<option value="Local">Local</option>
|
||||
<option value="Sedation">Sedation</option>
|
||||
<option value="GA">GA</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<p class="text-xs font-medium text-slate-600">Surfaces</p>
|
||||
<div class="mt-2 flex flex-wrap gap-3">
|
||||
@foreach ($dentalSurfaces as $surface)
|
||||
<label class="inline-flex items-center gap-1.5 text-sm">
|
||||
<input type="checkbox" name="surfaces[]" value="{{ $surface }}" class="rounded border-slate-300 text-indigo-600">
|
||||
{{ $surface }}
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label class="block text-xs font-medium text-slate-600">Notes</label>
|
||||
<input type="text" name="notes" class="mt-1 w-full rounded-xl border border-slate-200 px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Complete & bill</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-6 border-t border-slate-100 pt-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Completed this visit</p>
|
||||
<ul class="mt-2 space-y-1 text-sm">
|
||||
@forelse ($dentalProcedures as $proc)
|
||||
<li class="flex justify-between gap-3">
|
||||
<span>{{ $proc->label }} · {{ $proc->performed_at?->format('H:i') }}</span>
|
||||
<span class="tabular-nums">{{ number_format($proc->amount_minor / 100, 2) }}</span>
|
||||
</li>
|
||||
@empty
|
||||
<li class="text-slate-500">No procedures yet.</li>
|
||||
@endforelse
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@@ -20,10 +20,10 @@
|
||||
$canCompleteConsultation = $consultationInProgress
|
||||
&& $openConsultation
|
||||
&& $openConsultation->status !== \App\Models\Consultation::STATUS_COMPLETED;
|
||||
$chartTab = collect($workspaceTabs ?? [])
|
||||
->keys()
|
||||
->first(fn ($key) => ! in_array($key, ['overview', 'timeline', 'orders', 'billing', 'documents'], true))
|
||||
?: 'clinical_notes';
|
||||
$chartTab = collect($workspaceTabs ?? [])
|
||||
->keys()
|
||||
->first(fn ($key) => ! in_array($key, ['overview', 'timeline', 'plan', 'treat', 'notes', 'imaging', 'orders', 'billing', 'documents'], true))
|
||||
?: 'odontogram';
|
||||
@endphp
|
||||
|
||||
@if ($onWorkspace)
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
<div class="space-y-4">
|
||||
@if ($activeTab === 'timeline')
|
||||
@include('care.partials.patient-timeline', ['events' => $timeline ?? []])
|
||||
@elseif ($moduleKey === 'dentistry' && in_array($activeTab, ['odontogram', 'plan', 'treat', 'notes', 'imaging', 'overview'], true))
|
||||
@include('care.specialty.dentistry.workspace-'.$activeTab)
|
||||
@elseif ($activeTab === 'billing')
|
||||
<section class="rounded-2xl border border-slate-200 bg-white p-5">
|
||||
<h3 class="text-sm font-semibold text-slate-900">Add specialty service</h3>
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<a href="{{ route('care.queue.index') }}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">← Back to queue</a>
|
||||
<div class="flex flex-wrap gap-3 text-sm">
|
||||
@if ($moduleKey === 'dentistry')
|
||||
<a href="{{ route('care.specialty.dentistry.reports') }}" class="text-slate-600 hover:text-slate-900">Reports</a>
|
||||
@endif
|
||||
<a href="{{ route('care.specialty.history', $moduleKey) }}" class="text-slate-600 hover:text-slate-900">History</a>
|
||||
<a href="{{ route('care.specialty.billing', $moduleKey) }}" class="text-slate-600 hover:text-slate-900">Billing</a>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user