Initial Version
This commit is contained in:
120
core/src/layouts/menus/LayoutSystemMenu.vue
Normal file
120
core/src/layouts/menus/LayoutSystemMenu.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useLayoutStore } from '@KTXC/stores/layoutStore';
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
||||
import Logo from '@KTXC/layouts/logo/LogoDark.vue';
|
||||
import SystemMenuGroupStatic from './LayoutSystemMenuGroupStatic.vue';
|
||||
import SystemMenuGroupDynamic from './LayoutSystemMenuGroupDynamic.vue';
|
||||
import SystemMenuItem from './LayoutSystemMenuItem.vue';
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
const integrationStore = useIntegrationStore();
|
||||
|
||||
// Get all entries based on current menu mode
|
||||
const menuEntries = computed(() => {
|
||||
switch (layoutStore.menuMode) {
|
||||
case 'user-settings':
|
||||
return integrationStore.getPoint('user_settings_menu');
|
||||
case 'admin-settings':
|
||||
return integrationStore.getPoint('admin_settings_menu');
|
||||
case 'apps':
|
||||
default:
|
||||
return integrationStore.getPoint('app_menu');
|
||||
}
|
||||
});
|
||||
|
||||
// Menu mode display info
|
||||
const menuModeInfo = computed(() => {
|
||||
switch (layoutStore.menuMode) {
|
||||
case 'user-settings':
|
||||
return {
|
||||
icon: 'mdi-account-cog',
|
||||
label: 'Personal Settings',
|
||||
toggleLabel: 'Admin',
|
||||
toggleIcon: 'mdi-shield-crown',
|
||||
};
|
||||
case 'admin-settings':
|
||||
return {
|
||||
icon: 'mdi-shield-crown',
|
||||
label: 'Administration',
|
||||
toggleLabel: 'Apps',
|
||||
toggleIcon: 'mdi-view-dashboard',
|
||||
};
|
||||
case 'apps':
|
||||
default:
|
||||
return {
|
||||
icon: 'mdi-view-dashboard',
|
||||
label: 'Applications',
|
||||
toggleLabel: 'Settings',
|
||||
toggleIcon: 'mdi-account-cog',
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
methods: {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
left
|
||||
v-model="layoutStore.sidebarDrawer"
|
||||
elevation="0"
|
||||
rail-width="60"
|
||||
mobile-breakpoint="lg"
|
||||
app
|
||||
class="leftSidebar"
|
||||
:rail="layoutStore.miniSidebar"
|
||||
expand-on-hover
|
||||
>
|
||||
<div class="pa-5">
|
||||
<Logo />
|
||||
</div>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!---Navigation -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<perfect-scrollbar class="scrollnavbar">
|
||||
<v-list aria-busy="true" aria-label="menu list">
|
||||
<!---Menu Loop -->
|
||||
<template v-for="entry in menuEntries" :key="entry.id">
|
||||
<!-- Group with dynamic style (collapsible) -->
|
||||
<SystemMenuGroupDynamic
|
||||
v-if="'items' in entry && entry.style === 'dynamic'"
|
||||
class="leftPadding"
|
||||
:group="entry"
|
||||
/>
|
||||
<!-- Group with static style (subheader) -->
|
||||
<SystemMenuGroupStatic
|
||||
v-else-if="'items' in entry"
|
||||
:group="entry"
|
||||
/>
|
||||
<!-- Single item (no group) -->
|
||||
<SystemMenuItem
|
||||
v-else
|
||||
:item="entry"
|
||||
/>
|
||||
</template>
|
||||
</v-list>
|
||||
</perfect-scrollbar>
|
||||
|
||||
<!-- Menu Mode Toggle -->
|
||||
<template v-slot:append>
|
||||
<v-divider />
|
||||
<v-list density="compact" class="pa-2">
|
||||
<v-list-item
|
||||
rounded
|
||||
color="primary"
|
||||
@click="layoutStore.toggleMenuMode()"
|
||||
:prepend-icon="menuModeInfo.toggleIcon"
|
||||
class="menu-mode-toggle"
|
||||
>
|
||||
<v-list-item-title class="text-body-2">{{ menuModeInfo.toggleLabel }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
42
core/src/layouts/menus/LayoutSystemMenuGroupDynamic.vue
Normal file
42
core/src/layouts/menus/LayoutSystemMenuGroupDynamic.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { IntegrationGroup } from '@KTXC/types/integrationTypes';
|
||||
import NavItem from './LayoutSystemMenuItem.vue';
|
||||
|
||||
const props = defineProps<{ group: IntegrationGroup; level?: number }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!---Item Childern -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<v-list-group no-action>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!---Dropdown -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-list-item v-bind="activatorProps" :value="group.label" rounded class="mb-1" color="primary">
|
||||
<!---Icon -->
|
||||
<template v-slot:prepend>
|
||||
<v-icon v-if="group.icon" :icon="group.icon"></v-icon>
|
||||
</template>
|
||||
<!---Title -->
|
||||
<v-list-item-title class="mr-auto">{{ group.label }}</v-list-item-title>
|
||||
<!---If Caption-->
|
||||
<v-list-item-subtitle v-if="group.caption" class="text-caption mt-n1 hide-menu">
|
||||
{{ group.caption }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!---Sub Item-->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<template v-for="(subitem, i) in group.items" :key="i">
|
||||
<NavItem :item="subitem" :level="(props.level ?? 0) + 1"></NavItem>
|
||||
</template>
|
||||
</v-list-group>
|
||||
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!---End Item Sub Header -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
</template>
|
||||
|
||||
15
core/src/layouts/menus/LayoutSystemMenuGroupStatic.vue
Normal file
15
core/src/layouts/menus/LayoutSystemMenuGroupStatic.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { IntegrationGroup } from '@KTXC/types/integrationTypes';
|
||||
import LayoutSystemMenuItem from './LayoutSystemMenuItem.vue';
|
||||
|
||||
const props = defineProps<{ group: IntegrationGroup }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list-subheader color="lightText" class="smallCap text-subtitle-2">{{ props.group.label }}</v-list-subheader>
|
||||
<LayoutSystemMenuItem
|
||||
v-for="(item, i) in props.group.items"
|
||||
:key="i"
|
||||
:item="item"
|
||||
/>
|
||||
</template>
|
||||
29
core/src/layouts/menus/LayoutSystemMenuItem.vue
Normal file
29
core/src/layouts/menus/LayoutSystemMenuItem.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import type { IntegrationItem } from '@KTXC/types/integrationTypes';
|
||||
|
||||
const props = defineProps<{ item: IntegrationItem; level?: number }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!---Single Item-->
|
||||
<v-list-item
|
||||
:to="item.toType === 'external' ? '' : item.to"
|
||||
:href="item.toType === 'external' ? item.to : ''"
|
||||
rounded
|
||||
class="mb-1"
|
||||
color="primary"
|
||||
:disabled="item.disabled"
|
||||
:target="item.toType === 'external' ? '_blank' : ''"
|
||||
@mouseenter="$emit && $emit('prefetch', item)"
|
||||
>
|
||||
<!---If icon-->
|
||||
<template v-slot:prepend>
|
||||
<v-icon v-if="props.item.icon" :icon="props.item.icon"></v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.label }}</v-list-item-title>
|
||||
<!---If Caption-->
|
||||
<v-list-item-subtitle v-if="item.caption" class="text-caption mt-n1 hide-menu">
|
||||
{{ item.caption }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
110
core/src/layouts/menus/LayoutUserMenu.vue
Normal file
110
core/src/layouts/menus/LayoutUserMenu.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useTheme } from 'vuetify';
|
||||
import { useUserStore } from '@KTXC/stores/userStore';
|
||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
||||
import { useLayoutStore } from '@KTXC/stores/layoutStore';
|
||||
import { useRouter } from 'vue-router';
|
||||
import defaultAvatar from '@KTXC/assets/images/users/avatar-1.png';
|
||||
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const integrationStore = useIntegrationStore();
|
||||
const layoutStore = useLayoutStore();
|
||||
|
||||
const userAuth = computed(() => userStore.auth);
|
||||
const profileMenuItems = computed(() => integrationStore.getItems('profile_menu'));
|
||||
const userAvatar = computed(() => userStore.getProfileField('avatar') || defaultAvatar);
|
||||
const userName = computed(() => {
|
||||
const given = userStore.getProfileField('name_given');
|
||||
const family = userStore.getProfileField('name_family');
|
||||
if (given && family) return `${given} ${family}`;
|
||||
return userAuth.value?.label || 'User';
|
||||
});
|
||||
const userEmail = computed(() => userStore.getProfileField('email') || '');
|
||||
|
||||
// Theme toggle
|
||||
const isDarkMode = computed(() => theme.global.name.value === 'dark');
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme.global.name.value === 'light' ? 'dark' : 'light';
|
||||
theme.global.name.value = newTheme;
|
||||
layoutStore.setTheme(newTheme);
|
||||
};
|
||||
|
||||
// Navigate to settings
|
||||
const goToSettings = () => {
|
||||
layoutStore.setMenuMode('settings');
|
||||
// Navigate to first settings item or a default settings route
|
||||
router.push('/modules'); // TODO: Make this dynamic based on first settings menu item
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- Profile Dropdown -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<div>
|
||||
<!-- User Info Header -->
|
||||
<div class="d-flex align-center pa-5 pb-4">
|
||||
<v-avatar size="40" class="mr-3">
|
||||
<v-img :src="userAvatar" :alt="userName" cover />
|
||||
</v-avatar>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="text-h6 mb-0 font-weight-medium">
|
||||
{{ userName }}
|
||||
</h6>
|
||||
<p class="text-caption mb-0 text-medium-emphasis">{{ userEmail }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<v-divider />
|
||||
|
||||
<perfect-scrollbar style="max-height: 280px">
|
||||
<v-list class="py-0" aria-label="profile menu" aria-busy="true">
|
||||
<!-- Dynamic Profile Menu Items (from modules) -->
|
||||
<v-list-item
|
||||
v-for="item in profileMenuItems"
|
||||
:key="item.id"
|
||||
:to="item.toType === 'external' ? undefined : item.to"
|
||||
:href="item.toType === 'external' ? item.to : undefined"
|
||||
:target="item.toType === 'external' ? '_blank' : undefined"
|
||||
color="primary"
|
||||
rounded="0"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon v-if="item.icon">{{ item.icon }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-h6">{{ item.label }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider v-if="profileMenuItems.length" class="my-2" />
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
<v-list-item @click="toggleTheme" color="primary" rounded="0">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>{{ isDarkMode ? 'mdi-weather-sunny' : 'mdi-weather-night' }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-h6">{{ isDarkMode ? 'Light Mode' : 'Dark Mode' }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<!-- Go to Settings -->
|
||||
<v-list-item @click="goToSettings" color="primary" rounded="0">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-cog-outline</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-h6">Settings</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<!-- Logout -->
|
||||
<v-list-item @click="userStore.logout()" color="secondary" rounded="0">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-logout</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-h6">Logout</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</perfect-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user