diff --git a/app/Http/Controllers/Care/SpecialtyModuleController.php b/app/Http/Controllers/Care/SpecialtyModuleController.php index 307dfb4..9aecfb9 100644 --- a/app/Http/Controllers/Care/SpecialtyModuleController.php +++ b/app/Http/Controllers/Care/SpecialtyModuleController.php @@ -963,6 +963,8 @@ class SpecialtyModuleController extends Controller 'visitHistory' => $visitHistory, 'kpis' => $kpis, 'stages' => $shell->stages($module), + 'specialtyStageFlow' => $shell->stageFlow($module), + 'specialtyStageRoute' => $shell->stageAdvanceRoute($module), 'services' => $services, 'workspaceTabs' => $shell->workspaceTabs($module), 'actions' => $shell->actions($module), diff --git a/app/Services/Care/SpecialtyShellService.php b/app/Services/Care/SpecialtyShellService.php index e6124e5..7165989 100644 --- a/app/Services/Care/SpecialtyShellService.php +++ b/app/Services/Care/SpecialtyShellService.php @@ -9,6 +9,9 @@ use App\Models\Member; use App\Models\Organization; use App\Models\Patient; use App\Models\Visit; +use App\Services\Care\BloodBank\BloodBankWorkflowService; +use App\Services\Care\Emergency\EmergencyWorkflowService; +use App\Services\Care\Ophthalmology\OphthalmologyWorkflowService; use Illuminate\Support\Collection; /** @@ -43,6 +46,79 @@ class SpecialtyShellService return is_array($stages) ? array_values($stages) : []; } + /** + * Named route for advancing/setting specialty visit stage, if the module has one. + */ + public function stageAdvanceRoute(string $moduleKey): ?string + { + return match ($moduleKey) { + 'dentistry' => 'care.specialty.dentistry.stage', + 'emergency' => 'care.specialty.emergency.stage', + 'blood_bank' => 'care.specialty.blood-bank.stage', + 'ophthalmology' => 'care.specialty.ophthalmology.stage', + default => null, + }; + } + + /** + * Default stage progression for Move CTAs and action menus. + * + * @return array + */ + public function stageFlow(string $moduleKey): array + { + return match ($moduleKey) { + 'emergency' => app(EmergencyWorkflowService::class)->stageFlow(), + 'blood_bank' => app(BloodBankWorkflowService::class)->stageFlow(), + 'ophthalmology' => app(OphthalmologyWorkflowService::class)->stageFlow(), + 'dentistry' => [ + 'waiting' => ['next' => 'chair', 'label' => 'Seat at chair'], + 'chair' => ['next' => 'procedure', 'label' => 'Start procedure'], + 'procedure' => ['next' => 'recovery', 'label' => 'Move to recovery'], + 'recovery' => ['next' => 'completed', 'label' => 'Complete visit'], + ], + default => $this->sequentialStageFlow($moduleKey), + }; + } + + /** + * Build next-stage map from configured stage order when no suite workflow exists. + * + * @return array + */ + protected function sequentialStageFlow(string $moduleKey): array + { + $stages = $this->stages($moduleKey); + $flow = []; + + for ($i = 0, $count = count($stages); $i < $count; $i++) { + $code = (string) ($stages[$i]['code'] ?? ''); + if ($code === '') { + continue; + } + + $next = $stages[$i + 1] ?? null; + if ($next === null) { + $flow[$code] = ['next' => $code, 'label' => 'Completed']; + + continue; + } + + $nextCode = (string) ($next['code'] ?? ''); + if ($nextCode === '') { + continue; + } + + $nextLabel = (string) ($next['label'] ?? $nextCode); + $flow[$code] = [ + 'next' => $nextCode, + 'label' => 'Move to '.mb_strtolower($nextLabel), + ]; + } + + return $flow; + } + /** * Catalog from config (authoritative template). * diff --git a/resources/views/care/specialty/blood-bank/workspace-overview.blade.php b/resources/views/care/specialty/blood-bank/workspace-overview.blade.php index 788b09d..b933159 100644 --- a/resources/views/care/specialty/blood-bank/workspace-overview.blade.php +++ b/resources/views/care/specialty/blood-bank/workspace-overview.blade.php @@ -3,9 +3,6 @@ $payload = $requestRecord?->payload ?? []; $issuePayload = $bloodBankIssue?->payload ?? []; $inventoryPayload = $bloodBankInventory?->payload ?? []; - $currentStage = $workspaceVisit->specialty_stage; - $stageFlow = $bloodBankStageFlow ?? []; - $canManage = $canAdvanceStage ?? $canConsult ?? false; $urgency = (string) ($payload['urgency'] ?? ''); $isHighUrgency = str_contains(strtolower($urgency), 'urgent') || str_contains(strtolower($urgency), 'emergency') @@ -16,7 +13,7 @@

Blood Bank overview

-

Open request, urgency, cross-match status, and visit stage.

+

Open request, urgency, cross-match status, and issue summary.

Print summary @@ -55,51 +52,6 @@
-
-

Visit stage

-

- {{ $currentStage ? ucfirst(str_replace('_', ' ', $currentStage)) : 'Not set' }} -

-
- @foreach (($bloodBankStageCodes ?: ['request', 'crossmatch', 'issue', 'transfusion', 'completed']) as $stageCode) - @if ($canManage) -
- @csrf - - -
- @else - $currentStage === $stageCode, - 'border border-slate-200 bg-white text-slate-400' => $currentStage !== $stageCode, - ]) - @if ($currentStage !== $stageCode) aria-disabled="true" title="View-only access" @endif - > - {{ ucfirst(str_replace('_', ' ', $stageCode)) }} - - @endif - @endforeach - @if ($canManage && $currentStage && isset($stageFlow[$currentStage]) && $stageFlow[$currentStage]['next'] !== $currentStage) -
- @csrf - - -
- @endif -
-
-
Indication
diff --git a/resources/views/care/specialty/dentistry/workspace-overview.blade.php b/resources/views/care/specialty/dentistry/workspace-overview.blade.php index 3659c0f..1dd74ca 100644 --- a/resources/views/care/specialty/dentistry/workspace-overview.blade.php +++ b/resources/views/care/specialty/dentistry/workspace-overview.blade.php @@ -1,70 +1,17 @@ @php $plan = $dentalPlan; $openItems = $plan?->items?->whereIn('status', ['proposed', 'accepted']) ?? collect(); - $currentStage = $workspaceVisit->specialty_stage; - $canManage = $canConsult ?? false; - $stageFlow = [ - 'waiting' => ['next' => 'chair', 'label' => 'Seat at chair'], - 'chair' => ['next' => 'procedure', 'label' => 'Start procedure'], - 'procedure' => ['next' => 'recovery', 'label' => 'Move to recovery'], - 'recovery' => ['next' => 'completed', 'label' => 'Complete visit'], - ]; @endphp

Overview

-

Visit stage, summary, and open treatment work.

+

Visit summary and open treatment work.

Dentistry reports
-
-

Visit stage

-

- {{ $currentStage ? ucfirst(str_replace('_', ' ', $currentStage)) : 'Not set' }} -

-
- @foreach (['waiting', 'chair', 'procedure', 'recovery', 'completed'] as $stageCode) - @if ($canManage) -
- @csrf - - -
- @else - $currentStage === $stageCode, - 'border border-slate-200 bg-white text-slate-400' => $currentStage !== $stageCode, - ]) - @if ($currentStage !== $stageCode) aria-disabled="true" title="View-only access" @endif - > - {{ ucfirst($stageCode) }} - - @endif - @endforeach - @if ($canManage && $currentStage && isset($stageFlow[$currentStage])) -
- @csrf - - -
- @endif -
-
-
Visit status
diff --git a/resources/views/care/specialty/emergency/workspace-overview.blade.php b/resources/views/care/specialty/emergency/workspace-overview.blade.php index dc939f8..65609c2 100644 --- a/resources/views/care/specialty/emergency/workspace-overview.blade.php +++ b/resources/views/care/specialty/emergency/workspace-overview.blade.php @@ -1,17 +1,14 @@ @php $triage = $emergencyTriage; $payload = $triage?->payload ?? []; - $currentStage = $workspaceVisit->specialty_stage; - $stageFlow = $emergencyStageFlow ?? []; $latest = $emergencyLatestVitals; - $canManage = $canConsult ?? false; @endphp

Emergency overview

-

Acuity, stage, ABC flags, and latest vitals for this visit.

+

Acuity, ABC flags, and latest vitals for this visit.

Print summary @@ -49,51 +46,6 @@
-
-

Visit stage

-

- {{ $currentStage ? ucfirst(str_replace('_', ' ', $currentStage)) : 'Not set' }} -

-
- @foreach (($emergencyStageCodes ?: ['arrival', 'resus', 'treatment', 'observation', 'disposition']) as $stageCode) - @if ($canManage) -
- @csrf - - -
- @else - $currentStage === $stageCode, - 'border border-slate-200 bg-white text-slate-400' => $currentStage !== $stageCode, - ]) - @if ($currentStage !== $stageCode) aria-disabled="true" title="View-only access" @endif - > - {{ ucfirst(str_replace('_', ' ', $stageCode)) }} - - @endif - @endforeach - @if ($canManage && $currentStage && isset($stageFlow[$currentStage]) && $stageFlow[$currentStage]['next'] !== $currentStage) -
- @csrf - - -
- @endif -
-
-
Mode of arrival
diff --git a/resources/views/care/specialty/ophthalmology/workspace-overview.blade.php b/resources/views/care/specialty/ophthalmology/workspace-overview.blade.php index 65d13ce..de91fff 100644 --- a/resources/views/care/specialty/ophthalmology/workspace-overview.blade.php +++ b/resources/views/care/specialty/ophthalmology/workspace-overview.blade.php @@ -2,10 +2,6 @@ $examPayload = $ophthalmologyExam?->payload ?? []; $refractionPayload = $ophthalmologyRefraction?->payload ?? []; $planPayload = $ophthalmologyPlan?->payload ?? []; - $currentStage = $workspaceVisit->specialty_stage; - $stageFlow = $ophthalmologyStageFlow ?? []; - $stageLabels = collect($stages ?? [])->pluck('label', 'code'); - $canManage = $canAdvanceStage ?? $canConsult ?? false; $vaOd = $refractionPayload['va_od'] ?? ($examPayload['va_od'] ?? null); $vaOs = $refractionPayload['va_os'] ?? ($examPayload['va_os'] ?? null); @endphp @@ -14,7 +10,7 @@

Eye care overview

-

Visual acuity, IOP, diagnosis, and visit stage for this episode.

+

Visual acuity, IOP, diagnosis, and visit summary for this episode.

Print summary @@ -44,51 +40,6 @@
-
-

Visit stage

-

- {{ $currentStage ? ($stageLabels[$currentStage] ?? ucfirst(str_replace('_', ' ', $currentStage))) : 'Not set' }} -

-
- @foreach (($ophthalmologyStageCodes ?: ['check_in', 'history', 'refraction', 'exam', 'investigation', 'plan', 'treatment', 'completed']) as $stageCode) - @if ($canManage) -
- @csrf - - -
- @else - $currentStage === $stageCode, - 'border border-slate-200 bg-white text-slate-400' => $currentStage !== $stageCode, - ]) - @if ($currentStage !== $stageCode) aria-disabled="true" title="View-only access" @endif - > - {{ $stageLabels[$stageCode] ?? ucfirst(str_replace('_', ' ', $stageCode)) }} - - @endif - @endforeach - @if ($canManage && $currentStage && isset($stageFlow[$currentStage]) && $stageFlow[$currentStage]['next'] !== $currentStage) -
- @csrf - - -
- @endif -
-
-
Key findings
diff --git a/resources/views/care/specialty/partials/actions-menu.blade.php b/resources/views/care/specialty/partials/actions-menu.blade.php index 2644be5..85a17e0 100644 --- a/resources/views/care/specialty/partials/actions-menu.blade.php +++ b/resources/views/care/specialty/partials/actions-menu.blade.php @@ -36,43 +36,20 @@ 'blood_bank' => 'requests', default => 'odontogram', }; - $stageRoute = match ($moduleKey) { - 'dentistry' => 'care.specialty.dentistry.stage', - 'emergency' => 'care.specialty.emergency.stage', - 'blood_bank' => 'care.specialty.blood-bank.stage', - default => null, - }; - $stageFlow = match ($moduleKey) { - 'emergency' => ($emergencyStageFlow ?? [ - 'arrival' => ['next' => 'treatment', 'label' => 'Move to treatment'], - 'resus' => ['next' => 'treatment', 'label' => 'Move to treatment bay'], - 'treatment' => ['next' => 'observation', 'label' => 'Move to observation'], - 'observation' => ['next' => 'disposition', 'label' => 'Ready for disposition'], - ]), - 'blood_bank' => ($bloodBankStageFlow ?? [ - 'request' => ['next' => 'crossmatch', 'label' => 'Start cross-match'], - 'crossmatch' => ['next' => 'issue', 'label' => 'Ready to issue'], - 'issue' => ['next' => 'transfusion', 'label' => 'Start transfusion'], - 'transfusion' => ['next' => 'completed', 'label' => 'Complete visit'], - ]), - 'dentistry' => [ - 'waiting' => ['next' => 'chair', 'label' => 'Seat at chair'], - 'chair' => ['next' => 'procedure', 'label' => 'Start procedure'], - 'procedure' => ['next' => 'recovery', 'label' => 'Move to recovery'], - 'recovery' => ['next' => 'completed', 'label' => 'Complete visit'], - ], - default => [], - }; + $stageRoute = $specialtyStageRoute ?? null; + $stageFlow = $specialtyStageFlow ?? []; $defaultStartStage = match ($moduleKey) { 'emergency' => 'arrival', 'blood_bank' => 'request', 'dentistry' => 'chair', - default => null, + 'ophthalmology' => 'check_in', + default => collect($stages ?? [])->pluck('code')->first(), }; $defaultStartLabel = match ($moduleKey) { 'emergency' => 'Start triage', 'blood_bank' => 'Start request', 'dentistry' => 'Seat at chair', + 'ophthalmology' => 'Start check-in', default => 'Start', }; $currentStage = $workspaceVisit?->specialty_stage; diff --git a/resources/views/care/specialty/sections/workspace.blade.php b/resources/views/care/specialty/sections/workspace.blade.php index ece464f..8b8985e 100644 --- a/resources/views/care/specialty/sections/workspace.blade.php +++ b/resources/views/care/specialty/sections/workspace.blade.php @@ -29,6 +29,15 @@ @endif
@else + + {{-- Module tabs --}}
@foreach ($workspaceTabs as $tabKey => $tabLabel) diff --git a/resources/views/components/care/specialty-stage-bar.blade.php b/resources/views/components/care/specialty-stage-bar.blade.php new file mode 100644 index 0000000..57fcadf --- /dev/null +++ b/resources/views/components/care/specialty-stage-bar.blade.php @@ -0,0 +1,76 @@ +@props([ + 'stages' => [], + 'currentStage' => null, + 'stageFlow' => [], + 'stageRoute' => null, + 'visit' => null, + 'canAdvance' => false, +]) + +@php + $stageList = collect($stages) + ->filter(fn ($stage) => is_array($stage) && ($stage['code'] ?? '') !== '') + ->values(); + $current = $currentStage ? (string) $currentStage : null; + $canMutate = (bool) $canAdvance && is_string($stageRoute) && $stageRoute !== '' && $visit; + $nextStep = ($current && isset($stageFlow[$current])) ? $stageFlow[$current] : null; + $canMoveNext = $canMutate + && is_array($nextStep) + && ($nextStep['next'] ?? null) + && ($nextStep['next'] ?? null) !== $current; +@endphp + +@if ($visit && $stageList->isNotEmpty()) + +@endif diff --git a/tests/Feature/CareBloodBankSuiteTest.php b/tests/Feature/CareBloodBankSuiteTest.php index ab31ada..f23dc6a 100644 --- a/tests/Feature/CareBloodBankSuiteTest.php +++ b/tests/Feature/CareBloodBankSuiteTest.php @@ -127,7 +127,9 @@ class CareBloodBankSuiteTest extends TestCase ])) ->assertOk() ->assertSee('Blood Bank overview') - ->assertSee('Visit stage') + ->assertSee('data-care-stage-bar', false) + ->assertSee('Request received') + ->assertSee('Start cross-match') ->assertSee('BB reports'); $this->actingAs($this->owner) diff --git a/tests/Feature/CareDentistrySuiteTest.php b/tests/Feature/CareDentistrySuiteTest.php index fbe4d73..52c0825 100644 --- a/tests/Feature/CareDentistrySuiteTest.php +++ b/tests/Feature/CareDentistrySuiteTest.php @@ -132,6 +132,9 @@ class CareDentistrySuiteTest extends TestCase 'tab' => 'odontogram', ])) ->assertOk() + ->assertSee('data-care-stage-bar', false) + ->assertSee('Waiting room') + ->assertSee('Seat at chair') ->assertSee('Odontogram (FDI)') ->assertSee('18') ->assertSee('Save tooth') diff --git a/tests/Feature/CareEmergencySuiteTest.php b/tests/Feature/CareEmergencySuiteTest.php index de98910..5f2e185 100644 --- a/tests/Feature/CareEmergencySuiteTest.php +++ b/tests/Feature/CareEmergencySuiteTest.php @@ -128,7 +128,9 @@ class CareEmergencySuiteTest extends TestCase ])) ->assertOk() ->assertSee('Emergency overview') - ->assertSee('Visit stage') + ->assertSee('data-care-stage-bar', false) + ->assertSee('Arrival / triage') + ->assertSee('Move to treatment') ->assertSee('ED reports'); $this->actingAs($this->owner) diff --git a/tests/Feature/CareOphthalmologySuiteTest.php b/tests/Feature/CareOphthalmologySuiteTest.php index 63736a9..9ffca50 100644 --- a/tests/Feature/CareOphthalmologySuiteTest.php +++ b/tests/Feature/CareOphthalmologySuiteTest.php @@ -127,7 +127,10 @@ class CareOphthalmologySuiteTest extends TestCase ])) ->assertOk() ->assertSee('Eye care overview') - ->assertSee('Visit stage') + ->assertSee('data-care-stage-bar', false) + ->assertSee('Check-in') + ->assertSee('Diagnosis & plan') + ->assertSee('Start history') ->assertSee('Eye care reports'); $this->actingAs($this->owner)