diff --git a/app/Services/NavbarService.php b/app/Services/NavbarService.php index 183184b..0030c01 100644 --- a/app/Services/NavbarService.php +++ b/app/Services/NavbarService.php @@ -29,9 +29,13 @@ class NavbarService $allowedIds = $this->mapModel->getNavItemIdsForRoles($roles); if (empty($allowedIds)) return []; - // Load all enabled items, then filter + // Load all enabled items, then filter. + // The displayed sidebar is alphabetized after the tree is built so + // new menu items fall into place without relying on manual sort_order. $rows = $this->navModel->where('is_enabled', 1) ->orderBy('sort_order', 'ASC') + ->orderBy('label', 'ASC') + ->orderBy('id', 'ASC') ->findAll(); // index by id @@ -53,11 +57,40 @@ class NavbarService $tree[] = &$node; } } + unset($node); + + $this->sortTreeAlphabetically($tree); $this->cache->save($cacheKey, $tree, 300); // 5 minutes return $tree; } + private function sortTreeAlphabetically(array &$nodes): void + { + usort($nodes, fn ($a, $b) => strnatcasecmp( + $this->labelSortKey((string) ($a['label'] ?? '')), + $this->labelSortKey((string) ($b['label'] ?? '')) + )); + + foreach ($nodes as &$node) { + if (!empty($node['children']) && is_array($node['children'])) { + $this->sortTreeAlphabetically($node['children']); + } + } + unset($node); + } + + private function labelSortKey(string $label): string + { + $label = trim(preg_replace('/\s+/', ' ', $label) ?? ''); + $label = preg_replace('/^[^[:alnum:]]+/u', '', $label) ?? $label; + $label = preg_replace('/^(the|an|a)\s+/iu', '', $label) ?? $label; + + return function_exists('mb_strtolower') + ? mb_strtolower($label, 'UTF-8') + : strtolower($label); + } + public function clearCache(): void { // simplest: flush; or if you have a tagged cache, clear only keys