mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-18 18:54:07 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user