@extends('layouts.app') @section('title', 'Visit ' . $visit->visit_number) @section('styles') @include('partials._encounter-styles') @endsection @section('content') @php $readOnly = in_array($visit->status, ['completed','cancelled','discharged']); $user = auth()->user(); $isAdmin = $user->hasAnyRole('Super Admin','Hospital Admin'); $isClinical = $user->hasAnyRole('Clinician','Nurse'); $isTriageNurse = $user->hasRole('Triage Nurse'); $isReception = $user->hasAnyRole('Receptionist', 'Triage Nurse'); $isPharmacy = $user->hasRole('Pharmacy User'); $canEditTriage = !$readOnly && ($isAdmin || $isClinical || $isTriageNurse || $isReception); $canEditConsultation = !$readOnly && ($isAdmin || $isClinical); $canViewConsultation = $canEditConsultation || $isReception || $isTriageNurse; $canEditDiagnosis = !$readOnly && ($isAdmin || $isClinical); $canEditPrescriptions = !$readOnly && ($isAdmin || $isClinical); $canDeletePrescription = $isAdmin || $isClinical || $isReception || $isPharmacy; $canEditLab = !$readOnly && ($isAdmin || $isClinical || $isReception); $canEditImaging = !$readOnly && ($isAdmin || $isClinical || $isReception); $canEditBilling = !$readOnly && ($isAdmin || $isClinical || $isReception); // Direct lab/imaging encounter flags. These visits skip triage and // clinical consultation; the encounter UI hides those sections entirely. // For a direct lab encounter, only the Lab section is visible (plus // billing/payments). For a direct imaging encounter, only Imaging. $isDirectLab = $visit->visit_type === 'direct_lab'; $isDirectRadiology = $visit->visit_type === 'direct_radiology'; $isDirect = $isDirectLab || $isDirectRadiology; @endphp
{{-- Flash warning --}} @if(session('warning'))

{{ session('warning') }}

@endif {{-- ═══ DIRECT ENCOUNTER BANNER ═══ Shown on direct_lab and direct_radiology encounters. Reminds staff this is a walk-in test request — no consultation, vitals, or notes. If clinical care is needed after the test, the patient closes this visit and a new OPD visit is opened. --}} @if($isDirect)

Direct {{ $isDirectLab ? 'Lab' : 'Imaging' }} Encounter — No Consultation

Walk-in {{ $isDirectLab ? 'lab test' : 'imaging' }} request. Vitals and clinical notes don't apply here. Order the {{ $isDirectLab ? 'tests' : 'imaging' }}, bill the patient, and complete the visit when results are out. If the patient needs treatment after the {{ $isDirectLab ? 'lab' : 'imaging' }}, close this visit and start a new OPD visit.

@endif {{-- ═══ ADMIN: REOPEN BANNER (for completed/cancelled/discharged visits) ═══ --}} @if($readOnly && $isAdmin)

This visit is {{ $visit->status }}

As an administrator, you can reopen it to modify tests, drugs, services, or billing. Reopening will set status back to in consultation and unlock all forms.

@csrf
@endif {{-- ═══ TITLE + PATIENT BANNER ═══ --}} @include('partials._encounter-patient-header', [ 'visit' => $visit, 'title' => 'Update Visit', 'readOnly' => $readOnly, 'showPatientActions' => true, ]) {{-- Inline error banner — surfaces validation, stock, payment-block and safety errors thrown by Dispense (and any other form on this page). --}} @if($errors->any())
Action could not be completed:
@endif {{-- ═══ VISIT META ═══ --}} @php // Is this a dental visit? Used to hide free-text exam/treatment fields // (dentists work from Products/Services + Prescriptions, not narrative // notes) and to surface dental clinicians at the top of the picker. $isDental = ($visit->department->code ?? null) === 'DEN'; // Clinician pool now lives in resources/views/partials/ // _clinician-options.blade.php — included directly in the dropdown // below. The partial restricts the pool to clinical roles // (Clinician / Nurse / Triage Nurse / Dental User) and supports a // preferDentalTop flag for dental encounters. $_canAssignClinician = !$readOnly && ($isAdmin || $isClinical || $isReception || $isTriageNurse); @endphp
Patient Name
{{ strtoupper($visit->patient->full_name ?? '') }}
Payment Mode @php // Same permission set as the clinician assignment editor. $_canEditPaymentMode = !$readOnly && ($isAdmin || $isClinical || $isReception || $isTriageNurse); $_paymentModes = ['cash' => 'Cash', 'mpesa' => 'M-Pesa', 'insurance' => 'Insurance', 'corporate' => 'Corporate']; $_currentLabel = $_paymentModes[$visit->payment_mode] ?? ucfirst(str_replace('_', ' ', $visit->payment_mode ?? '-')); if ($visit->payment_mode === 'insurance' && $visit->insuranceCompany) { $_currentLabel .= ' · ' . $visit->insuranceCompany->name; } @endphp @if($_canEditPaymentMode) {{-- Default view: read-only label + small pencil icon to enter edit mode. Dropdowns and Save button stay hidden until the pencil is clicked — keeps the page clean for the receptionist's normal workflow but still gives them a fix path when the wrong mode was recorded at intake. --}}
{{ $_currentLabel }}
@else
{{ $_currentLabel }}
@endif
{{-- Department: dropdown when editable on an OPD visit. We deliberately keep the department READ-ONLY for direct_lab and direct_radiology encounters — those visits' department (Laboratory / Radiology) is what makes them a direct encounter in the first place, so letting the user "correct" it would create an inconsistent record (visit_type=direct_lab but department=Outpatient). If reception genuinely picked the wrong encounter type, the visit should be cancelled and re-created from the Queue page's Start New Visit modal. For everyone else (outpatient visits) we surface a dropdown so reception can fix a wrong department (e.g. visit was started as Outpatient but the patient actually went to the Dental clinic). Lab and Radiology are excluded from the dropdown because picking them would not actually convert this consultation into a direct encounter — it would just mislabel it. --}}
Department @if($_canAssignClinician && !$isDirectLab && !$isDirectRadiology)
@csrf
@else
{{ $visit->department->name ?? '-' }}
@endif
{{-- Clinician: dropdown when editable, read-only when locked. Pool comes from the standard _clinician-options partial: active users with Clinician / Nurse / Triage Nurse / Dental User roles. Dental encounters float Dental Users to the top so the dentist is the obvious first pick. --}}
Clinician @if($_canAssignClinician)
@csrf
@else
{{ $visit->clinician->name ?? '— Unassigned —' }}
@endif
Patient Type
Outpatient (OPD)
Date
{{ $visit->check_in_at?->format('Y-m-d H:i') ?? $visit->created_at->format('Y-m-d H:i') }}
Status
{{ ucfirst(str_replace('_',' ', $visit->status)) }}
Visit #
{{ $visit->visit_number }}
{{-- ═══ 1. VITALS (multi-triage support) ═══ --}} @unless($isDirect) @php $_triages = $visit->triages; // hasMany, newest first (per Visit model) $_triageCount = $_triages->count(); $_showAddForm = $canEditTriage && ($_triageCount === 0); // auto-open when none exist yet @endphp

1 Vitals @if($_triageCount > 1)({{ $_triageCount }} readings)@endif

@if($_triageCount > 0) Done @else Pending @endif @if($canEditTriage) @endif
{{-- History table: one row per triage, newest first --}} @if($_triageCount > 0) @if($canEditTriage)@endif @foreach($_triages as $t) {{-- Display row --}} @if($canEditTriage) @endif {{-- Notes row (only if notes exist) --}} @if($t->notes) @endif {{-- Inline edit row (hidden by default) --}} @if($canEditTriage) @endif @endforeach
Date / Time Height Weight BMI Temp BP Pulse RR SPO² Pain Priority Recorded ByActions
{{ $t->created_at->format('d M H:i') }} {{ $t->height ?? '—' }} {{ $t->weight ?? '—' }} {{ $t->bmi ?? '—' }} {{ $t->temperature ?? '—' }} {{ ($t->blood_pressure_systolic ?? '—').' / '.($t->blood_pressure_diastolic ?? '—') }} {{ $t->pulse ?? '—' }} {{ $t->respiratory_rate ?? '—' }} {{ $t->oxygen_saturation ?? '—' }} {{ $t->pain_score ?? '—' }} @php $pr = $t->priority ?? 'normal'; @endphp {{ ucfirst($pr) }} {{ \App\Models\User::find($t->recorded_by)?->name ?? '—' }} @if($t->triage_source === 'triage_nurse') · triage@endif
@csrf @method('DELETE')
Notes: {{ $t->notes }}
@endif {{-- Add-new-triage form (toggled open by "+ Add Triage" button) --}} @if($canEditTriage)
@csrf
+ New Triage Reading
Height Weight Temp BP Sys BP Dia Pulse RR SPO² Pain Priority
@if($_triageCount > 0) @endif
@elseif($_triageCount === 0)

Triage not recorded for this visit.

@endif
@endunless {{-- ═══ 2. CLINICAL DETAILS ═══ --}} @unless($isDirect)

2 Clinical Details

View Patient History @if($visit->patient->allergies && count($visit->patient->allergies)) ⚠ {{ count($visit->patient->allergies) }} allergies @else View Allergies (0) @endif
@if(($readOnly || (!$canEditConsultation && $canViewConsultation)) && $visit->consultation) {{-- READ-ONLY VIEW --}}
Chief Complaint
{{ $visit->consultation->presenting_complaints ?? '-' }}
Clinical Notes
{{ $visit->consultation->history_of_presenting_illness ?? '-' }}
@unless($isDental)
Examination Findings
{{ $visit->consultation->examination_findings ?? '-' }}
Treatment Plan
{{ $visit->consultation->treatment_plan ?? '-' }}
@endunless
Diagnosis
@php $diags = $visit->consultation->diagnoses ?? collect(); @endphp @if($diags->isNotEmpty())
@foreach($diags as $d) @if($d->is_primary)PRIMARY@endif {{ $d->icd10Code->code ?? '' }} {{ $d->icd10Code->description ?? '' }} @endforeach
@else
{{ is_array($visit->consultation->diagnosis) ? implode(', ', $visit->consultation->diagnosis) : ($visit->consultation->diagnosis ?? '—') }}
@endif
@elseif($canEditConsultation) {{-- EDIT FORM --}}
@csrf @if($visit->consultation) @method('PUT') @endif {{-- Chief Complaint: single-line, compact --}}
{{-- Clinical Notes / History: full-width, taller --}}
{{-- Examination + Treatment: side-by-side. Hidden on dental visits — dentists work from Products/Services and Prescriptions, not narrative exam notes. --}} @unless($isDental)
@endunless {{-- Diagnosis --}}
Diagnosis (ICD-10) *
@include('partials._icd10_picker', ['consultation' => $visit->consultation ?? null])
@else

No consultation recorded.

@endif
@endunless {{-- ═══ 3. LABORATORY TESTS ═══ --}} @unless($isDirectRadiology)
@php // Auto-expand when there's already lab data on this visit so the // clinician doesn't have to click to see results that exist. Empty // visits start collapsed — the "+ Add" button reveals the search UI. $_labHasData = $visit->labOrders->count() > 0; @endphp

@unless($isDirect)3 @endunless Laboratory Tests

@if($visit->labOrders->count()){{ $visit->labOrders->count() }} orders@endif @if(!$readOnly) @endif
@if($canEditLab)@endif @forelse($visit->labOrders as $lo) @if($canEditLab) @endif @empty @endforelse {{-- Search row (like Banda Image 4 "Search Test e.g. Full Hemogram") --}} @if($canEditLab) @endif
Test Result Reference Range Notes Urgency StatusDelete
{{ $lo->test_name }} @if($lo->result && is_array($lo->result->results)) @php $resData = $lo->result->results; @endphp @if(isset($resData['generic'])) {{ $resData['generic']['value'] ?? '-' }} {{ $resData['generic']['unit'] ?? '' }} @else @foreach($resData as $param) @if(is_array($param) && isset($param['name']))
{{ $param['short_code'] ?? $param['name'] }}: {{ $param['value'] ?? '-' }} {{ $param['unit'] ?? '' }}
@endif @endforeach @endif @else — @endif
@if($lo->result && is_array($lo->result->results)) @foreach($lo->result->results as $param) @if(is_array($param) && isset($param['reference_range']))
{{ $param['reference_range'] }}
@endif @endforeach @else — @endif
{{ $lo->clinical_notes ?? '—' }} {{ $lo->urgency }} {{ ucfirst($lo->status) }} @if(!in_array($lo->status, ['validated','cancelled','resulted']))
@csrf @method('DELETE')
@endif
No lab orders yet.
@csrf

@endunless {{-- ═══ 4. RADIOLOGY / IMAGING ═══ --}} @unless($isDirectLab)
@php $_imgHasData = $visit->imagingRequests->count() > 0; @endphp

@unless($isDirect)4 @endunless Radiology / Imaging

@if($visit->imagingRequests->count()){{ $visit->imagingRequests->count() }} orders@endif @if(!$readOnly) @endif
@if($canEditImaging)@endif @forelse($visit->imagingRequests as $ir) @if($canEditImaging) @endif @empty @endforelse @if($canEditImaging) @endif
Imaging Type Clinical Indication Findings StatusDelete
{{ $ir->imaging_type }} {{ $ir->clinical_indication ?? '—' }} {{ $ir->report ? Str::limit($ir->report->findings, 80) : '—' }} {{ ucfirst($ir->status) }} @if(!in_array($ir->status, ['reported','cancelled','verified']))
@csrf @method('DELETE')
@endif
No imaging orders yet.
@csrf

@endunless {{-- ═══ 5. PRESCRIPTIONS ═══ --}} {{-- Hidden on direct lab / direct radiology encounters: those are walk-in test requests, prescriptions belong to a consultation visit. --}} @unless($isDirect)

5 Prescriptions

@if($visit->prescriptions->count()){{ $visit->prescriptions->count() }} items@endif
@if($canEditPrescriptions)@endif @forelse($visit->prescriptions as $rx) @php $rxLocked = in_array($rx->status, ['dispensed','cancelled']); $rxCanEdit = $canEditPrescriptions && !$rxLocked; // How much of this prescription has already been dispensed // (used to: (a) show progress, (b) compute the remainder // the one-tap Dispense button will give out, (c) decide // whether the Dispense button should appear at all). // whereNull('reversed_at') so reversed dispenses correctly // free the prescription up for redispensing. $rxDispensedQty = \App\Models\PharmacyDispense::where('prescription_id', $rx->id) ->whereNull('reversed_at') ->sum('quantity_dispensed'); $rxRemainingQty = max(0, ((int) $rx->quantity) - (int) $rxDispensedQty); // Dispense button appears for clinical roles on prescriptions that // are not yet fully dispensed/cancelled and that have a real drug // item linked (a free-text drug has no stock to dispense from). $rxCanDispense = $canEditPrescriptions && !$rxLocked && $rx->drug_item_id && $rxRemainingQty > 0; @endphp {{-- Display row --}} @if($canEditPrescriptions) @endif {{-- Inline edit row (hidden by default) --}} @if($rxCanEdit) @php $rxDispensed = $rxDispensedQty; @endphp @endif @empty @endforelse @if($canEditPrescriptions) @endif
Drug Dosage Frequency Route Duration Qty StatusActions
{{ $rx->drug_name }} {{ $rx->dosage ?? '—' }} {{ $rx->frequency ?? '—' }} {{ $rx->route ?? '—' }} {{ $rx->duration ?? '—' }} {{ $rx->quantity }} {{ ucfirst(str_replace('_',' ',$rx->status)) }} @if($rxDispensedQty > 0 && !$rxLocked)
{{ $rxDispensedQty }} of {{ $rx->quantity }} dispensed
@endif
@if($rxCanDispense)
@csrf
@endif @if($rxCanEdit) @endif {{-- Reverse dispense — Admin, Clinician or Nurse. Shows only when something has actually been dispensed (otherwise there's nothing to reverse). Sends the prescription back to the pharmacy queue; the invoice line stays so redispense just works. To also drop the bill charge, click Delete after reversing. --}} @if(($isAdmin || $isClinical) && $rxDispensedQty > 0 && $rx->status !== 'cancelled')
@csrf
@endif @if($canDeletePrescription && !$rxLocked)
@csrf @method('DELETE')
@endif
No prescriptions yet.
@csrf

@endunless {{-- ═══ 6. PRODUCTS / SERVICES (BILLING) ═══ --}}

@unless($isDirect)6 @endunless Products / Services

@if($canEditBilling)@endif
@if($canEditBilling)@endif @if($visit->invoice && $visit->invoice?->items->count()) @foreach($visit->invoice?->items as $item) @php $isEditable = $canEditBilling && $item->source_type !== 'prescription'; @endphp @if($canEditBilling) @endif @endforeach @endif @if($canEditBilling)@endif
Product or Service Instructions Existing Qty Qty Unit Selling Price TotalDelete
{{ $item->description }} {{ $item->notes ?? '—' }} @if($isEditable) @else {{ $item->quantity }} @endif @if($isEditable) @else {{ number_format($item->unit_price) }} @endif {{ number_format($item->total) }} @if($item->source_type === 'prescription') {{-- Pharmacy items are managed from the Prescriptions section to keep both sides in sync --}} 🔒 @elseif(auth()->user()->hasAnyRole('Receptionist','Triage Nurse','Clinician','Nurse','Hospital Admin','Super Admin'))
@csrf @method('DELETE')
@endif
Total {{ number_format($visit->invoice?->total_amount ?? 0) }}
{{-- ═══ 7. PAYMENTS ═══ --}}

@unless($isDirect)7 @endunless Payments

@include('partials._payment-section', ['visit' => $visit])
{{-- /.enc-page --}} {{-- ═══ STICKY BOTTOM ACTION BAR (preserved exactly as before) ═══ --}} @if(!in_array($visit->status, ['completed', 'cancelled', 'discharged'])) @php if ($visit->invoice) { $opdFreshTotal = $visit->invoice->items()->sum(\DB::raw('quantity * unit_price')); $opdFreshPaid = $visit->invoice->payments()->where('status', 'completed')->sum('amount'); $opdBalance = max(0, $opdFreshTotal - $opdFreshPaid); } else { $opdBalance = 0; } @endphp
@if(auth()->user()->hasAnyRole('Super Admin', 'Hospital Admin'))
@csrf
@endif @if($opdBalance > 0) @else
@csrf
@endif
@if($opdBalance > 0)@endif
@if($opdBalance > 0) @else @php $pendingLabCount = $visit->labOrders->whereNotIn('status',['validated','cancelled','rejected'])->count(); $pendingRxCount = $visit->prescriptions->whereNotIn('status',['dispensed','cancelled'])->count(); $hasPendingItems = ($pendingLabCount + $pendingRxCount) > 0; @endphp @if($hasPendingItems)

⚠ Pending items before completion:

@if($pendingLabCount)

• {{ $pendingLabCount }} lab order(s) not yet validated

@endif @if($pendingRxCount)

• {{ $pendingRxCount }} prescription(s) not yet dispensed

@endif
@endif
@if($hasPendingItems) @endif @endif
{{-- Appointment Modal --}} @endif @stack('scripts') @endsection {{-- ═══════════════════════════════════════════════════════════════════════ SCRIPTS — preserved exactly from original visits/show.blade.php (only minor selector changes for billing inputs) ═══════════════════════════════════════════════════════════════════════ --}} @section('scripts') @endsection