feat(modules): dynamic sidebar group rendering active modules

The sidebar gains a new section that lists each currently-activated module
as a direct shortcut to its settings page. This is the always-visible
companion to the company-context Active Modules index — both surface the
same set of modules, but the index is the catalog landing page and the
sidebar group is the per-module quick access.

- BootstrapController returns module_menu populated from
  \InvoiceShelf\Modules\Registry::allMenu(), but only on the company-context
  branch — not on the super-admin branch (lines 53-69), since super admins
  don't see the dynamic group. Because nwidart only boots service providers
  for currently-activated modules, the registry naturally contains only
  active modules at request time, no extra filtering needed.

- bootstrap.service.ts BootstrapResponse type extended with
  module_menu?: ModuleMenuItem[]; new ModuleMenuItem interface
  (title/link/icon) — shaped distinctly from MenuItem because module entries
  use namespaced i18n keys and don't carry group/ability metadata.

- global.store.ts exposes a moduleMenu ref + a hasActiveModules computed.

- SiteSidebar.vue appends a new "Modules" section after the existing
  menuGroups output, in both the mobile (Dialog) and desktop branches. The
  section is hidden when hasActiveModules is false. Uses the
  modules.sidebar.section_title i18n key added in the previous commit.
This commit is contained in:
Darko Gjorgjijoski
2026-04-09 00:29:56 +02:00
parent e6eeacb6d4
commit 7743c2e126
4 changed files with 95 additions and 1 deletions

View File

@@ -99,6 +99,34 @@
{{ $t(item.title) }}
</router-link>
</nav>
<!-- Dynamic Modules section (one entry per active module's registered settings link) -->
<nav v-if="globalStore.hasActiveModules" class="mt-5 space-y-1">
<div class="px-4 mt-6 mb-2 text-xs font-semibold text-subtle uppercase tracking-wider">
{{ $t('modules.sidebar.section_title') }}
</div>
<router-link
v-for="(item, idx) in globalStore.moduleMenu"
:key="`module-${idx}`"
:to="item.link"
:class="[
hasActiveUrl(item.link)
? 'text-primary-600 bg-primary-50 font-semibold'
: 'text-body hover:bg-hover',
'cursor-pointer mx-3 px-3 py-2.5 flex items-center rounded-lg text-sm not-italic font-medium transition-colors',
]"
@click="globalStore.setSidebarVisibility(false)"
>
<BaseIcon
:name="item.icon"
:class="[
hasActiveUrl(item.link) ? 'text-primary-500' : 'text-subtle',
'mr-3 shrink-0 h-5 w-5',
]"
/>
{{ $t(item.title) }}
</router-link>
</nav>
</div>
</div>
</TransitionChild>
@@ -168,6 +196,49 @@
</router-link>
</div>
<!-- Dynamic Modules section: one shortcut per active module's registered
settings link. Hidden when no modules are active. -->
<div
v-if="globalStore.hasActiveModules"
class="p-0 m-0 mt-4 list-none"
>
<div
v-if="!globalStore.isSidebarCollapsed"
class="px-6 mt-6 mb-2 text-xs font-semibold text-subtle uppercase tracking-wider whitespace-nowrap"
>
{{ $t('modules.sidebar.section_title') }}
</div>
<div
v-else
class="mx-3 my-2 border-t border-line-light"
/>
<router-link
v-for="(item, idx) in globalStore.moduleMenu"
:key="`module-desktop-${idx}`"
:to="item.link"
v-tooltip="globalStore.isSidebarCollapsed ? { content: $t(item.title), placement: 'right' } : null"
:class="[
hasActiveUrl(item.link)
? 'text-primary-600 bg-primary-50 font-semibold'
: 'text-body hover:bg-hover',
globalStore.isSidebarCollapsed
? 'cursor-pointer mx-2 px-0 py-2.5 group flex items-center justify-center rounded-lg text-sm font-medium transition-colors'
: 'cursor-pointer mx-3 px-3 py-2.5 group flex items-center rounded-lg text-sm not-italic font-medium transition-colors',
]"
>
<BaseIcon
:name="item.icon"
:class="[
hasActiveUrl(item.link) ? 'text-primary-500' : 'text-subtle group-hover:text-body',
globalStore.isSidebarCollapsed ? 'shrink-0 h-6 w-6' : 'mr-3 shrink-0 h-5 w-5',
]"
/>
<span v-if="!globalStore.isSidebarCollapsed" class="whitespace-nowrap">
{{ $t(item.title) }}
</span>
</router-link>
</div>
<!-- Bottom toolbar -->
<div class="mt-auto sticky bottom-0 border-t border-white/10 bg-surface/80 backdrop-blur-xl p-2 flex flex-col items-center gap-1">
<button