From 1e00c31b8b2ae502e3f5841591fd4e79dc0bef87 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Sat, 18 Jul 2026 18:30:14 +0000 Subject: [PATCH] Give each specialty module a distinct Heroicon-style icon. Icon names live on the specialty catalog; SVG paths resolve through a shared map and render in sidebar, dashboard, settings, and the specialty shell. Co-authored-by: Cursor --- app/Services/Care/SpecialtyModuleService.php | 27 +++++++++++++ app/Support/helpers.php | 8 ++++ config/care.php | 6 +++ config/care_specialty_icons.php | 34 ++++++++++++++++ config/care_specialty_modules.php | 22 +++++++++++ resources/views/care/dashboard.blade.php | 15 ++++--- .../views/care/settings/module-show.blade.php | 9 +++-- .../views/care/settings/modules.blade.php | 17 +++++--- .../views/care/specialty/shell.blade.php | 27 +++++++++---- .../components/care/specialty-icon.blade.php | 16 ++++++++ resources/views/partials/sidebar.blade.php | 3 +- tests/Feature/CareSpecialtyModulesTest.php | 39 +++++++++++++++++++ 12 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 config/care_specialty_icons.php create mode 100644 resources/views/components/care/specialty-icon.blade.php diff --git a/app/Services/Care/SpecialtyModuleService.php b/app/Services/Care/SpecialtyModuleService.php index 5d4ad7e..9650f51 100644 --- a/app/Services/Care/SpecialtyModuleService.php +++ b/app/Services/Care/SpecialtyModuleService.php @@ -40,6 +40,33 @@ class SpecialtyModuleService return $catalog[$key] ?? null; } + /** + * Heroicon-style icon identifier from the specialty catalog (e.g. bolt, heart). + */ + public function iconName(string $key): string + { + $name = (string) ($this->definition($key)['icon'] ?? 'squares-2x2'); + + return $name !== '' ? $name : 'squares-2x2'; + } + + /** + * Inline SVG path markup for sidebar / card icons (24×24 outline). + */ + public function iconSvgPath(string $key): string + { + $icons = config('care.specialty_icons', []); + $name = $this->iconName($key); + + if (is_array($icons) && isset($icons[$name]) && is_string($icons[$name]) && $icons[$name] !== '') { + return $icons[$name]; + } + + return is_array($icons) && isset($icons['squares-2x2']) && is_string($icons['squares-2x2']) + ? $icons['squares-2x2'] + : ''; + } + public function isDefaultOnPaidPlans(string $key): bool { return (bool) ($this->definition($key)['default_on_paid_plans'] ?? false); diff --git a/app/Support/helpers.php b/app/Support/helpers.php index 6ae16b2..139dfba 100644 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -46,6 +46,14 @@ if (! function_exists('care_money')) { } } +if (! function_exists('care_specialty_icon_path')) { + /** Inline SVG path markup for a specialty module key (Heroicons-style outline). */ + function care_specialty_icon_path(string $moduleKey): string + { + return app(\App\Services\Care\SpecialtyModuleService::class)->iconSvgPath($moduleKey); + } +} + if (! function_exists('crm_money')) { /** @deprecated Use care_money() */ function crm_money(?int $minor, ?string $currency = null): string diff --git a/config/care.php b/config/care.php index 0e05208..4ef4c6c 100644 --- a/config/care.php +++ b/config/care.php @@ -86,6 +86,12 @@ return [ */ 'specialty_modules' => require __DIR__.'/care_specialty_modules.php', + /* + | SVG path markup for specialty module icons (Heroicons-style outline). + | Referenced by care.specialty_modules.*.icon identifiers. + */ + 'specialty_icons' => require __DIR__.'/care_specialty_icons.php', + 'audit_actions' => [ 'organization.created' => 'Organization created', 'organization.updated' => 'Organization updated', diff --git a/config/care_specialty_icons.php b/config/care_specialty_icons.php new file mode 100644 index 0000000..f001423 --- /dev/null +++ b/config/care_specialty_icons.php @@ -0,0 +1,34 @@ + + */ +return [ + 'bolt' => '', + 'droplet' => '', + 'face-smile' => '', + 'eye' => '', + 'arrow-path' => '', + 'home' => '', + 'camera' => '', + 'heart' => '', + 'chat-bubble-left-right' => '', + 'user-group' => '', + 'cube' => '', + 'speaker-wave' => '', + 'shield-exclamation' => '', + 'circle-stack' => '', + 'wrench-screwdriver' => '', + 'shield-check' => '', + 'document-magnifying-glass' => '', + 'beaker' => '', + 'swatch' => '', + 'finger-print' => '', + 'sparkles' => '', + 'gift' => '', + 'squares-2x2' => '', +]; diff --git a/config/care_specialty_modules.php b/config/care_specialty_modules.php index cf8afc1..c984411 100644 --- a/config/care_specialty_modules.php +++ b/config/care_specialty_modules.php @@ -23,6 +23,7 @@ return [ 'queue_name' => 'Emergency', 'queue_prefix' => 'ER', 'nav_label' => 'Emergency', + 'icon' => 'bolt', 'queue_keywords' => ['emerg', 'casualty', 'trauma', 'er', 'a&e'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'lab_technician', 'pharmacist', 'receptionist'], @@ -36,6 +37,7 @@ return [ 'queue_name' => 'Blood Bank', 'queue_prefix' => 'BB', 'nav_label' => 'Blood Bank', + 'icon' => 'droplet', 'queue_keywords' => ['blood', 'transfusion', 'crossmatch', 'donor'], 'access' => 'general', 'roles' => ['lab_technician', 'nurse', 'doctor', 'receptionist'], @@ -49,6 +51,7 @@ return [ 'queue_name' => 'Dentistry', 'queue_prefix' => 'DEN', 'nav_label' => 'Dentistry', + 'icon' => 'face-smile', 'queue_keywords' => ['dent', 'dental', 'oral'], 'access' => 'limited', 'roles' => ['doctor', 'nurse', 'receptionist'], @@ -62,6 +65,7 @@ return [ 'queue_name' => 'Eye care', 'queue_prefix' => 'EYE', 'nav_label' => 'Eye care', + 'icon' => 'eye', 'queue_keywords' => ['eye', 'ophthal', 'optom'], 'access' => 'limited', 'roles' => ['doctor', 'nurse', 'receptionist'], @@ -75,6 +79,7 @@ return [ 'queue_name' => 'Physiotherapy', 'queue_prefix' => 'PHY', 'nav_label' => 'Physiotherapy', + 'icon' => 'arrow-path', 'queue_keywords' => ['physio', 'rehab', 'therapy'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'receptionist'], @@ -87,6 +92,7 @@ return [ 'queue_name' => 'Maternity', 'queue_prefix' => 'MAT', 'nav_label' => 'Maternity', + 'icon' => 'home', 'queue_keywords' => ['matern', 'antenatal', 'obstetric'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'pharmacist', 'receptionist'], @@ -99,6 +105,7 @@ return [ 'queue_name' => 'Radiology', 'queue_prefix' => 'RAD', 'nav_label' => 'Radiology', + 'icon' => 'camera', 'queue_keywords' => ['radio', 'imaging', 'x-ray', 'xray', 'ultrasound', 'ct', 'mri'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'lab_technician', 'receptionist'], @@ -111,6 +118,7 @@ return [ 'queue_name' => 'Cardiology', 'queue_prefix' => 'CAR', 'nav_label' => 'Cardiology', + 'icon' => 'heart', 'queue_keywords' => ['cardio', 'heart', 'ecg', 'echo'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -127,6 +135,7 @@ return [ 'queue_name' => 'Psychiatry', 'queue_prefix' => 'PSY', 'nav_label' => 'Psychiatry', + 'icon' => 'chat-bubble-left-right', 'queue_keywords' => ['psych', 'mental', 'behaviour'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -143,6 +152,7 @@ return [ 'queue_name' => 'Pediatrics', 'queue_prefix' => 'PED', 'nav_label' => 'Pediatrics', + 'icon' => 'user-group', 'queue_keywords' => ['pedia', 'paedia', 'child', 'infant'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'receptionist'], @@ -155,6 +165,7 @@ return [ 'queue_name' => 'Orthopedics', 'queue_prefix' => 'ORT', 'nav_label' => 'Orthopedics', + 'icon' => 'cube', 'queue_keywords' => ['ortho', 'fracture', 'bone', 'joint'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -171,6 +182,7 @@ return [ 'queue_name' => 'ENT', 'queue_prefix' => 'ENT', 'nav_label' => 'ENT', + 'icon' => 'speaker-wave', 'queue_keywords' => ['ent', 'ear', 'nose', 'throat', 'otolaryng'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -187,6 +199,7 @@ return [ 'queue_name' => 'Oncology', 'queue_prefix' => 'ONC', 'nav_label' => 'Oncology', + 'icon' => 'shield-exclamation', 'queue_keywords' => ['oncol', 'cancer', 'chemo'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -203,6 +216,7 @@ return [ 'queue_name' => 'Renal Care', 'queue_prefix' => 'REN', 'nav_label' => 'Renal Care', + 'icon' => 'circle-stack', 'queue_keywords' => ['renal', 'dialysis', 'nephro', 'kidney'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -219,6 +233,7 @@ return [ 'queue_name' => 'Surgery', 'queue_prefix' => 'SUR', 'nav_label' => 'Surgery', + 'icon' => 'wrench-screwdriver', 'queue_keywords' => ['surg', 'theatre', 'operat', 'pre-op'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -235,6 +250,7 @@ return [ 'queue_name' => 'Vaccination', 'queue_prefix' => 'VAX', 'nav_label' => 'Vaccination', + 'icon' => 'shield-check', 'queue_keywords' => ['vaccin', 'immun', 'epi', 'injection'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'receptionist'], @@ -247,6 +263,7 @@ return [ 'queue_name' => 'Pathology', 'queue_prefix' => 'PATH', 'nav_label' => 'Pathology', + 'icon' => 'document-magnifying-glass', 'queue_keywords' => ['pathol', 'histo', 'cytol', 'biopsy'], 'access' => 'general', 'roles' => ['lab_technician', 'doctor', 'nurse', 'receptionist'], @@ -260,6 +277,7 @@ return [ 'queue_name' => 'Infusion', 'queue_prefix' => 'INF', 'nav_label' => 'Infusion', + 'icon' => 'beaker', 'queue_keywords' => ['infusion', 'iv', 'drip', 'day care'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'pharmacist', 'receptionist'], @@ -272,6 +290,7 @@ return [ 'queue_name' => 'Dermatology', 'queue_prefix' => 'DER', 'nav_label' => 'Dermatology', + 'icon' => 'swatch', 'queue_keywords' => ['derma', 'skin', 'rash'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -288,6 +307,7 @@ return [ 'queue_name' => 'Podiatry', 'queue_prefix' => 'POD', 'nav_label' => 'Podiatry', + 'icon' => 'finger-print', 'queue_keywords' => ['podiat', 'foot', 'diabetic foot'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -304,6 +324,7 @@ return [ 'queue_name' => 'Fertility', 'queue_prefix' => 'FER', 'nav_label' => 'Fertility', + 'icon' => 'sparkles', 'queue_keywords' => ['fertil', 'ivf', 'reproduct'], 'access' => 'restricted', 'roles' => ['doctor'], @@ -320,6 +341,7 @@ return [ 'queue_name' => 'Child Welfare', 'queue_prefix' => 'CWC', 'nav_label' => 'Child Welfare', + 'icon' => 'gift', 'queue_keywords' => ['cwc', 'child welfare', 'well baby', 'growth'], 'access' => 'general', 'roles' => ['doctor', 'nurse', 'receptionist'], diff --git a/resources/views/care/dashboard.blade.php b/resources/views/care/dashboard.blade.php index ab73823..42a4427 100644 --- a/resources/views/care/dashboard.blade.php +++ b/resources/views/care/dashboard.blade.php @@ -206,17 +206,22 @@
-

{{ $item['definition']['nav_label'] ?? $item['definition']['label'] }}

+
+ +
+

{{ $item['definition']['nav_label'] ?? $item['definition']['label'] }}

+

{{ $item['definition']['description'] ?? '' }}

+ @if ($toSettings) +

Settings & pricing →

+ @endif +
+
@if (! empty($item['access_level']) && $item['access_level'] !== 'manage' && ! $toSettings) {{ $item['access_level'] === 'refer' ? 'View + refer' : 'View' }} @endif
-

{{ $item['definition']['description'] ?? '' }}

- @if ($toSettings) -

Settings & pricing →

- @endif
@endforeach diff --git a/resources/views/care/settings/module-show.blade.php b/resources/views/care/settings/module-show.blade.php index 09ab64c..9912602 100644 --- a/resources/views/care/settings/module-show.blade.php +++ b/resources/views/care/settings/module-show.blade.php @@ -3,9 +3,12 @@ title="{{ $definition['label'] ?? ucfirst($moduleKey) }}" description="{{ $definition['description'] ?? 'Module settings and service prices for '.$organization->name.'.' }}" > -

- ← Specialty modules -

+ @if (session('success'))

{{ session('success') }}

diff --git a/resources/views/care/settings/modules.blade.php b/resources/views/care/settings/modules.blade.php index 2c180b2..2cb7306 100644 --- a/resources/views/care/settings/modules.blade.php +++ b/resources/views/care/settings/modules.blade.php @@ -29,12 +29,17 @@
-

{{ $module['label'] }}

-

{{ $module['description'] }}

- @if ($isDefault) -

Included on Pro / Enterprise

- @endif -

Settings & pricing →

+
+ +
+

{{ $module['label'] }}

+

{{ $module['description'] }}

+ @if ($isDefault) +

Included on Pro / Enterprise

+ @endif +

Settings & pricing →

+
+