Initial Version
This commit is contained in:
9
core/src/layouts/blank/BlankLayout.vue
Normal file
9
core/src/layouts/blank/BlankLayout.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app>
|
||||
<RouterView />
|
||||
</v-app>
|
||||
</template>
|
||||
29
core/src/layouts/footer/LayoutFooter.vue
Normal file
29
core/src/layouts/footer/LayoutFooter.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { shallowRef } from 'vue';
|
||||
|
||||
const footerLink = shallowRef([
|
||||
{
|
||||
title: 'About us'
|
||||
},
|
||||
{
|
||||
title: 'Privacy'
|
||||
},
|
||||
{
|
||||
title: 'Terms'
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
<template>
|
||||
<v-footer class="px-0 footer">
|
||||
<v-row justify="center" no-gutters>
|
||||
<v-col cols="6">
|
||||
<p class="text-caption mb-0">© All rights reserved</p>
|
||||
</v-col>
|
||||
<v-col class="text-right" cols="6">
|
||||
<a v-for="(item, i) in footerLink" :key="i" class="mx-2 text-caption text-darkText" href="/">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-footer>
|
||||
</template>
|
||||
105
core/src/layouts/header/LayoutHeader.vue
Normal file
105
core/src/layouts/header/LayoutHeader.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useUserStore } from '@KTXC/stores/userStore';
|
||||
import { useLayoutStore } from '@KTXC/stores/layoutStore';
|
||||
import LayoutUserMenu from '@KTXC/layouts/menus/LayoutUserMenu.vue';
|
||||
import NotificationDD from './NotificationDD.vue';
|
||||
import Searchbar from './SearchBarPanel.vue';
|
||||
import defaultAvatar from '@KTXC/assets/images/users/avatar-1.png';
|
||||
|
||||
const layoutStore = useLayoutStore();
|
||||
const userStore = useUserStore();
|
||||
const userAuth = computed(() => userStore.auth);
|
||||
const userAvatar = computed(() => userStore.getProfileField('avatar') || defaultAvatar);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-app-bar elevation="0" height="60">
|
||||
<v-btn
|
||||
class="hidden-md-and-down text-secondary mr-3"
|
||||
color="darkText"
|
||||
icon
|
||||
rounded="sm"
|
||||
variant="text"
|
||||
@click.stop="layoutStore.setMiniSidebar(!layoutStore.miniSidebar)"
|
||||
size="small"
|
||||
>
|
||||
<v-icon>mdi-menu-open</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
class="hidden-lg-and-up text-secondary ms-3"
|
||||
color="darkText"
|
||||
icon
|
||||
rounded="sm"
|
||||
variant="text"
|
||||
@click.stop="layoutStore.toggleSidebarDrawer()"
|
||||
size="small"
|
||||
>
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- search mobile -->
|
||||
<v-menu :close-on-content-click="false" class="hidden-lg-and-up" offset="10, 0">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
class="hidden-lg-and-up text-secondary ml-1"
|
||||
color="lightsecondary"
|
||||
icon
|
||||
rounded="sm"
|
||||
variant="flat"
|
||||
size="small"
|
||||
v-bind="props"
|
||||
>
|
||||
<v-icon>mdi-magnify</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-sheet class="search-sheet v-col-12 pa-0" width="320">
|
||||
<v-text-field persistent-placeholder placeholder="Search here.." color="primary" variant="solo" hide-details>
|
||||
<template v-slot:prepend-inner>
|
||||
<v-icon>mdi-magnify</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-sheet>
|
||||
</v-menu>
|
||||
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- Search part -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<v-sheet class="d-none d-lg-block" width="250">
|
||||
<Searchbar />
|
||||
</v-sheet>
|
||||
|
||||
<!---/Search part -->
|
||||
|
||||
<v-spacer />
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!---right part -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- Notification -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<NotificationDD />
|
||||
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- User Profile -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<v-menu :close-on-content-click="false" offset="8, 0">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="profileBtn" variant="text" rounded="sm" v-bind="props">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-sm-2 mr-0" size="32">
|
||||
<v-img :src="userAvatar" :alt="userAuth?.label || 'User'" cover />
|
||||
</v-avatar>
|
||||
<h6 class="text-subtitle-1 mb-0 d-sm-block d-none">
|
||||
{{ userAuth?.label }}
|
||||
</h6>
|
||||
</div>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-sheet rounded="md" width="290">
|
||||
<LayoutUserMenu />
|
||||
</v-sheet>
|
||||
</v-menu>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
119
core/src/layouts/header/NotificationDD.vue
Normal file
119
core/src/layouts/header/NotificationDD.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const isActive = ref(true);
|
||||
|
||||
function deactivateItem() {
|
||||
isActive.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- notifications DD -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<v-menu :close-on-content-click="false" offset="6, 0">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn icon class="text-secondary ml-sm-2 ml-1" color="darkText" rounded="sm" size="small" v-bind="props">
|
||||
<v-badge :content="isActive ? '2' : '0'" color="primary" offset-x="-4" offset-y="-5">
|
||||
<v-icon>mdi-bell</v-icon>
|
||||
</v-badge>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-sheet rounded="md" width="387" class="notification-dropdown">
|
||||
<div class="pa-4">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<h6 class="text-subtitle-1 mb-0">Notifications</h6>
|
||||
<v-btn
|
||||
variant="text"
|
||||
color="success"
|
||||
icon
|
||||
rounded
|
||||
size="small"
|
||||
@click="deactivateItem()"
|
||||
:class="isActive ? 'd-block' : 'd-none'"
|
||||
>
|
||||
<v-icon>mdi-check-circle</v-icon>
|
||||
<v-tooltip aria-label="tooltip" activator="parent" location="bottom" :content-class="isActive ? 'custom-tooltip' : 'd-none'">
|
||||
<span class="text-caption">Mark as all read</span>
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
<v-divider></v-divider>
|
||||
<perfect-scrollbar style="height: calc(100vh - 300px); max-height: 265px">
|
||||
<v-list class="py-0" lines="two" aria-label="notification list" aria-busy="true">
|
||||
<v-list-item value="1" color="secondary" class="no-spacer py-1" :active="isActive">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar size="36" variant="flat" color="lightsuccess" class="mr-3 py-2 text-success">
|
||||
<v-icon>mdi-gift</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<div class="d-inline-flex justify-space-between w-100">
|
||||
<h6 class="text-subtitle-1 font-weight-regular mb-0">
|
||||
It's <span style="font-weight: 600">Cristina danny's</span> birthday today.
|
||||
</h6>
|
||||
<span class="text-caption">3:00 AM</span>
|
||||
</div>
|
||||
|
||||
<p class="text-caption text-medium-emphasis my-0">2 min ago</p>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item value="2" color="secondary" class="no-spacer">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar size="36" variant="flat" color="lightprimary" class="mr-3 py-2 text-primary">
|
||||
<v-icon>mdi-message</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<div class="d-inline-flex justify-space-between w-100">
|
||||
<h6 class="text-subtitle-1 font-weight-regular mb-0"><span style="font-weight: 600">Aida Burg</span> commented your post.</h6>
|
||||
<span class="text-caption">6:00 PM</span>
|
||||
</div>
|
||||
|
||||
<p class="text-caption text-medium-emphasis my-0">5 August</p>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item value="3" color="secondary" class="no-spacer" :active="isActive">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar size="36" variant="flat" color="lighterror" class="mr-3 py-2 text-error">
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<div class="d-inline-flex justify-space-between w-100">
|
||||
<h6 class="text-subtitle-1 font-weight-regular mb-0">Your Profile is Complete <span style="font-weight: 600">60%</span></h6>
|
||||
<span class="text-caption">2:45 PM</span>
|
||||
</div>
|
||||
|
||||
<p class="text-caption text-medium-emphasis my-0">7 hours ago</p>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item value="4" color="secondary" class="no-spacer">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar size="36" variant="flat" color="lightprimary" class="mr-3 py-2 text-primary"> C </v-avatar>
|
||||
</template>
|
||||
<div class="d-inline-flex justify-space-between w-100">
|
||||
<h6 class="text-subtitle-1 font-weight-regular mb-0">
|
||||
<span style="font-weight: 600">Cristina Danny</span> invited to join <span style="font-weight: 600">Metting.</span>
|
||||
</h6>
|
||||
<span class="text-caption">9:10 PM</span>
|
||||
</div>
|
||||
|
||||
<p class="text-caption text-medium-emphasis my-0">Daily scrum meeting time</p>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</perfect-scrollbar>
|
||||
<v-divider></v-divider>
|
||||
<div class="pa-2 text-center">
|
||||
<v-btn color="primary" variant="text">View All</v-btn>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.v-tooltip {
|
||||
> .v-overlay__content.custom-tooltip {
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
13
core/src/layouts/header/SearchBarPanel.vue
Normal file
13
core/src/layouts/header/SearchBarPanel.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- ---------------------------------------------- -->
|
||||
<!-- searchbar -->
|
||||
<!-- ---------------------------------------------- -->
|
||||
<v-text-field persistent-placeholder placeholder="Ctrl + k" color="primary" variant="outlined" hide-details density="compact" autocomplete="off">
|
||||
<template v-slot:prepend-inner>
|
||||
<v-icon size="small" color="lightText">mdi-magnify</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</template>
|
||||
42
core/src/layouts/logo/LogoDark.vue
Normal file
42
core/src/layouts/logo/LogoDark.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="logo">
|
||||
<RouterLink :to="{ name: 'home' }" aria-label="logo">
|
||||
<svg width="118" height="35" viewBox="0 0 118 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M4.63564 15.8644L6.94797 13.552L6.95038 13.5496H11.3006L9.56969 15.2806L9.12278 15.7275L7.35024 17.5L7.56977 17.7201L17.5 27.6498L27.6498 17.5L25.8766 15.7275L25.7518 15.602L23.6994 13.5496H28.0496L28.052 13.552L29.8644 15.3644L32 17.5L17.5 32L3 17.5L4.63564 15.8644ZM17.5 3L25.8784 11.3784H21.5282L17.5 7.35024L13.4718 11.3784H9.12158L17.5 3Z"
|
||||
:fill="darkprimary"
|
||||
></path>
|
||||
<path
|
||||
d="M7.35025 17.5L9.1228 15.7275L9.5697 15.2805L7.83937 13.5496H6.95039L6.94798 13.552L4.63564 15.8644L6.8551 18.073L7.35025 17.5Z"
|
||||
:fill="darkprimary"
|
||||
></path>
|
||||
<path
|
||||
d="M25.8767 15.7275L27.6498 17.5L27.4743 17.6755L27.4749 17.6761L29.8644 15.3644L28.0521 13.552L28.0497 13.5496H27.8736L25.7518 15.602L25.8767 15.7275Z"
|
||||
:fill="darkprimary"
|
||||
></path>
|
||||
<path d="M6.94549 13.5496L6.9479 13.552L9.12272 15.7275L17.4999 24.1041L28.0544 13.5496H6.94549Z" :fill="primary"></path>
|
||||
<path
|
||||
d="M46.5781 10V26H49.3594V14.9844H49.5078L53.9297 25.9531H56.0078L60.4297 15.0078H60.5781V26H63.3594V10H59.8125L55.0625 21.5937H54.875L50.125 10H46.5781ZM69.8438 26.2422C71.7266 26.2422 72.8516 25.3594 73.3672 24.3516H73.4609V26H76.1797V17.9687C76.1797 14.7969 73.5937 13.8438 71.3047 13.8438C68.7813 13.8438 66.8437 14.9687 66.2188 17.1562L68.8594 17.5312C69.1406 16.7109 69.9375 16.0078 71.3203 16.0078C72.6328 16.0078 73.3516 16.6797 73.3516 17.8594V17.9062C73.3516 18.7188 72.5 18.7578 70.3828 18.9844C68.0547 19.2344 65.8281 19.9297 65.8281 22.6328C65.8281 24.9922 67.5547 26.2422 69.8438 26.2422ZM70.5781 24.1641C69.3984 24.1641 68.5547 23.625 68.5547 22.5859C68.5547 21.5 69.5 21.0469 70.7656 20.8672C71.5078 20.7656 72.9922 20.5781 73.3594 20.2812V21.6953C73.3594 23.0312 72.2813 24.1641 70.5781 24.1641ZM81.8516 18.9687C81.8516 17.2344 82.8984 16.2344 84.3906 16.2344C85.8516 16.2344 86.7266 17.1953 86.7266 18.7969V26H89.5547V18.3594C89.5625 15.4844 87.9219 13.8438 85.4453 13.8438C83.6484 13.8438 82.4141 14.7031 81.8672 16.0391H81.7266V14H79.0234V26H81.8516V18.9687ZM98.4219 14H96.0547V11.125H93.2266V14H91.5234V16.1875H93.2266V22.8594C93.2109 25.1172 94.8516 26.2266 96.9766 26.1641C97.7813 26.1406 98.3359 25.9844 98.6406 25.8828L98.1641 23.6719C98.0078 23.7109 97.6875 23.7812 97.3359 23.7812C96.625 23.7812 96.0547 23.5312 96.0547 22.3906V16.1875H98.4219V14ZM100.787 26H103.615V14H100.787V26ZM102.209 12.2969C103.107 12.2969 103.842 11.6094 103.842 10.7656C103.842 9.91406 103.107 9.22656 102.209 9.22656C101.303 9.22656 100.568 9.91406 100.568 10.7656C100.568 11.6094 101.303 12.2969 102.209 12.2969ZM116.008 17.1719C115.617 15.1406 113.992 13.8438 111.18 13.8438C108.289 13.8438 106.32 15.2656 106.328 17.4844C106.32 19.2344 107.398 20.3906 109.703 20.8672L111.75 21.2969C112.852 21.5391 113.367 21.9844 113.367 22.6641C113.367 23.4844 112.477 24.1016 111.133 24.1016C109.836 24.1016 108.992 23.5391 108.75 22.4609L105.992 22.7266C106.344 24.9297 108.195 26.2344 111.141 26.2344C114.141 26.2344 116.258 24.6797 116.266 22.4062C116.258 20.6953 115.156 19.6484 112.891 19.1562L110.844 18.7188C109.625 18.4453 109.141 18.0234 109.148 17.3281C109.141 16.5156 110.039 15.9531 111.219 15.9531C112.523 15.9531 113.211 16.6641 113.43 17.4531L116.008 17.1719Z"
|
||||
fill="#000"
|
||||
fill-opacity="0.85"
|
||||
></path>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="8.62526" y1="14.0888" x2="5.56709" y2="17.1469" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="darkprimary"></stop>
|
||||
<stop offset="0.9637" stop-color="darkprimary" stop-opacity="0"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="26.2675" y1="14.1279" x2="28.7404" y2="16.938" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="darkprimary"></stop>
|
||||
<stop offset="1" stop-color="darkprimary" stop-opacity="0"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
const primary = ref('rgb(var(--v-theme-primary))');
|
||||
const darkprimary = ref('rgb(var(--v-theme-darkprimary))');
|
||||
</script>
|
||||
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