first commit

This commit is contained in:
2025-11-26 07:49:54 +00:00
commit d8685ccf10
468 changed files with 41346 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import {appsLink} from '~/_mockApis/headerData';
import { Icon } from '@iconify/vue';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- apps link -->
<!-- ---------------------------------------------- -->
<v-row>
<v-col cols="12" md="6" v-for="(item, i) in appsLink" :key="i">
<router-link :to="item.href" class="text-decoration-none custom-text-primary">
<div class="d-flex align-center">
<v-avatar size="45" :color=" 'light' + item.color" rounded="md">
<Icon :icon="'solar:' + item.avatar" height="24" :class="'text-' + item.color"/>
</v-avatar>
<div class="ml-3">
<h6 class="text-subtitle-1 mb-1 heading custom-title">{{ item.title }}</h6>
<p class="text-subtitle-2 textSecondary">{{ item.subtext }}</p>
</div>
</div>
</router-link>
</v-col>
</v-row>
</template>

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { ref } from 'vue';
import { languageDD } from '@/_mockApis/headerData';
import flag1 from '~/assets/images/flag/icon-flag-en.svg';
import flag2 from '~/assets/images/flag/icon-flag-ro.svg';
import flag3 from '~/assets/images/flag/icon-flag-zh.svg';
import flag4 from '~/assets/images/flag/icon-flag-fr.svg';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- language DD -->
<!-- ---------------------------------------------- -->
<v-menu open-on-hover open-on-click>
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<v-avatar size="20">
<img v-if="$i18n.locale === 'en'" :src="flag1" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
<img v-if="$i18n.locale === 'fr'" :src="flag4" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
<img v-if="$i18n.locale === 'ro'" :src="flag2" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
<img v-if="$i18n.locale === 'zh'" :src="flag3" :alt="$i18n.locale" width="22" height="22" class="obj-cover" />
</v-avatar>
</v-btn>
</template>
<v-sheet rounded="md" width="200" elevation="10">
<v-list class="theme-list">
<v-list-item
v-for="(item, index) in languageDD"
:key="index"
color="primary"
:active="$i18n.locale == item.value"
class="d-flex align-center"
@click="() => ($i18n.locale = item.value)"
>
<template v-slot:prepend>
<v-avatar size="22">
<img :src="item.avatar" :alt="item.avatar" width="22" height="22" class="obj-cover" />
</v-avatar>
</template>
<v-list-item-title class="text-subtitle-1 font-weight-regular">
{{ item.title }}
<span class="text-disabled text-subtitle-1 pl-2">({{ item.subtext }})</span>
</v-list-item-title>
</v-list-item>
</v-list>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- mega menu DD -->
<!-- ---------------------------------------------- -->
<v-menu open-on-hover open-on-click >
<template v-slot:activator="{ props }">
<v-btn class="hidden-sm-and-down custom-hover-primary" size="small" variant="text" icon color="primary" v-bind="props"> <Icon icon="solar:widget-3-line-duotone" height="20" /> </v-btn>
</template>
<v-sheet width="900" height="300" elevation="10" rounded="md" class="pa-0 overflow-hidden">
<div>
<v-row>
<v-col cols="12" md="8" class="d-flex">
<div class="pa-6">
<LazyLayoutFullVerticalHeaderAppsLink />
</div>
</v-col>
<v-col cols="12" md="4" class="pa-0">
<img src="@/assets/images/backgrounds/mega-dd-bg.jpg" alt="matdash-img" height="320" class="w-100" />
</v-col>
</v-row>
</div>
</v-sheet>
</v-menu>
</template>
s

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import {notifications} from '~/_mockApis/headerData';
import { Icon } from '@iconify/vue';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- notifications DD -->
<!-- ---------------------------------------------- -->
<v-menu open-on-hover open-on-click >
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<Icon icon="solar:bell-bing-line-duotone" height="22" />
</v-btn>
</template>
<v-sheet rounded="md" width="360" elevation="10">
<div class="px-6 pt-6 pb-4">
<div class="d-flex align-center justify-space-between">
<h6 class="text-h5">Notifications</h6>
<v-chip color="primary" variant="flat" size="small" class="text-white rounded-sm">5 New</v-chip>
</div>
</div>
<perfect-scrollbar style="height: 350px">
<v-list class="py-0 theme-list" lines="two">
<v-list-item v-for="item in notifications" :key="item.title" :value="item" color="primary" class="py-3 px-6">
<template v-slot:prepend>
<v-avatar size="45" :color=" 'light' + item.color" rounded="circle">
<Icon :icon="'solar:' + item.avatar" height="20" :class="'text-' + item.color"/>
</v-avatar>
</template>
<div class="d-flex justify-space-between">
<h6 class="text-subtitle-1 heading mb-1">{{ item.title }}</h6>
<span class="text-subtitle-2 textSecondary">{{ item.time }}</span>
</div>
<p class="text-subtitle-2 font-weight-regular textSecondary">{{ item.subtitle }}</p>
</v-list-item>
</v-list>
</perfect-scrollbar>
<div class="py-4 px-6 text-center">
<v-btn color="primary" variant="flat" size="large" block>See all Notifications</v-btn>
</div>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,227 @@
<!-- components/layout/ProfileDD.vue -->
<script setup lang="ts">
import { MailIcon } from "vue-tabler-icons";
import { profileDD } from "~/_mockApis/headerData";
import { PerfectScrollbar } from "vue3-perfect-scrollbar";
import { useUserInfo } from "~/composables/useUserInfo";
import { computed } from "vue";
const userInfo = useUserInfo();
// Enhanced logout with proper error handling
const logout = async () => {
try {
// Use the updated logout method from useUserInfo that handles Keycloak logout and session clearing
await userInfo.logout({
reason: "idle",
confirmDialog: false // Show confirmation dialog
});
} catch (error) {
console.error("Logout from profile failed:", error);
}
};
// **TAMBAHAN: Full logout function dengan konfirmasi**
const fullLogout = async () => {
try {
// Tampilkan konfirmasi sebelum full logout
const confirmed = confirm(
"Apakah Anda yakin ingin keluar dari semua sesi? Ini akan menghapus semua data lokal dan sesi Keycloak."
);
if (!confirmed) return;
console.log("Initiating full logout from ProfileDD...");
// Gunakan fullLogout dari useUserInfo composable
await userInfo.fullLogout();
} catch (error) {
console.error("Full logout from profile failed:", error);
// Fallback jika fullLogout gagal
try {
console.log("Attempting fallback logout...");
await userInfo.logout({
reason: "manual",
clearStorage: true
});
} catch (fallbackError) {
console.error("Fallback logout also failed:", fallbackError);
// Last resort - force redirect
if (process.client) {
localStorage.clear();
sessionStorage.clear();
window.location.href = "/auth/login?reason=force";
}
}
}
};
// **TAMBAHAN: Logout dengan konfirmasi untuk UX yang lebih baik**
const logoutWithConfirmation = async () => {
try {
const confirmed = confirm("Apakah Anda yakin ingin keluar?");
if (!confirmed) return;
await logout();
} catch (error) {
console.error("Logout with confirmation failed:", error);
}
};
// Get user display info from session
const getUserDisplayInfo = () => {
if (!userInfo.user.value && !userInfo.data.value?.user)
return {
name: "Guest User",
email: "guest@example.com",
role: "Guest"
};
const user = userInfo.user.value || userInfo.data.value?.user || {};
return {
name: user.name || user.given_name || user.preferred_username || "User",
email: user.email || "No email",
role: userInfo.userRoles.value[0] || "User"
};
};
const displayInfo = computed(() => getUserDisplayInfo());
// Computed properties for decodedToken and clientScopes
// const decodedToken = computed(() => userInfo.decodedToken.value);
// const clientScopes = computed(() => userInfo.clientScopes.value);
// **TAMBAHAN: Computed property untuk menampilkan status session**
const sessionInfo = computed(() => {
return {
isAuthenticated: userInfo.isAuthenticated.value,
sessionExpires: userInfo.sessionExpires.value,
hasValidToken: !!userInfo.idToken.value
};
});
</script>
<template>
<v-menu :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-btn class="custom-hover-primary" variant="text" v-bind="props" icon>
<v-avatar size="35">
<img
src="~/assets/images/profile/user-1.jpg"
width="35"
alt="User Avatar"
/>
</v-avatar>
</v-btn>
</template>
<v-sheet rounded="md" width="360" elevation="10">
<div class="px-8 pt-6">
<h6 class="text-h5 font-weight-medium">User Profile</h6>
<div class="d-flex align-center mt-4 pb-6">
<v-avatar size="80">
<img src="~/assets/images/profile/user-1.jpg" width="80" />
</v-avatar>
<div class="ml-3">
<h6 class="text-h6 mb-n1">{{ displayInfo.name }}</h6>
<span class="text-subtitle-1 font-weight-regular textSecondary">
{{ displayInfo.role }}
</span>
<div class="d-flex align-center mt-1">
<MailIcon size="18" stroke-width="1.5" />
<span
class="text-subtitle-1 font-weight-regular textSecondary ml-2"
>
{{ displayInfo.email }}
</span>
</div>
<!-- **TAMBAHAN: Tampilkan status session** -->
<div class="mt-2">
<v-chip
:color="sessionInfo.isAuthenticated ? 'success' : 'error'"
size="small"
variant="outlined"
>
{{ sessionInfo.isAuthenticated ? "Active" : "Inactive" }}
</v-chip>
</div>
</div>
</div>
<v-divider></v-divider>
</div>
<PerfectScrollbar style="height: calc(100vh - 240px); max-height: 240px">
<v-list class="py-0 theme-list" lines="two">
<v-list-item
v-for="item in profileDD"
:key="item.title"
class="py-4 px-8 custom-text-primary"
:to="item.href"
>
<template v-slot:prepend>
<v-avatar
size="48"
color="lightprimary"
class="mr-3"
rounded="md"
>
<img
:src="item.avatar"
width="24"
height="24"
:alt="item.avatar"
/>
</v-avatar>
</template>
<div>
<h6 class="text-subtitle-1 font-weight-bold mb-2 custom-title">
{{ item.title }}
</h6>
</div>
<p class="text-subtitle-1 font-weight-regular textSecondary">
{{ item.subtitle }}
</p>
</v-list-item>
</v-list>
</PerfectScrollbar>
<!-- **DIPERBAIKI: Logout buttons section dengan multiple options** -->
<div class="pt-4 pb-6 px-8">
<!-- Regular Logout Button -->
<v-btn
color="primary"
variant="outlined"
block
@click="logoutWithConfirmation"
prepend-icon="mdi-logout"
class="mb-2"
>
Logout
</v-btn>
<!-- **TAMBAHAN: Full Logout Button** -->
<v-btn
color="error"
variant="outlined"
block
@click="fullLogout"
prepend-icon="mdi-logout-variant"
class="mb-2"
>
Full Logout
</v-btn>
<!-- **TAMBAHAN: Quick info text** -->
<div class="text-center mt-2">
<span class="text-caption textSecondary">
Full logout akan menghapus semua sesi dan data lokal
</span>
</div>
</div>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
</script>
<template>
<v-menu open-on-hover open-on-click >
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<Icon icon="solar:widget-line-duotone" height="22" />
</v-btn>
</template>
<v-sheet rounded="md" width="360" elevation="10">
<perfect-scrollbar style="height: 370px">
<div class="pa-6">
<LazyLayoutFullVerticalHeaderAppsLink />
</div>
</perfect-scrollbar>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,39 @@
<script setup>
import { Icon } from '@iconify/vue';
import { searchSugg } from '~/_mockApis/headerData';
</script>
<template>
<!-- ---------------------------------------------- -->
<!-- search1 -->
<!-- ------------------------------------------------>
<v-menu :close-on-content-click="false">
<template v-slot:activator="{ props }">
<v-btn icon class="custom-hover-primary" size="small" variant="text" color="primary" v-bind="props">
<Icon icon="solar:magnifer-linear" height="20" />
</v-btn>
</template>
<v-sheet width="360" elevation="10" rounded="md">
<div class="d-flex align-center justify-space-between pa-5">
<v-text-field placeholder="Search" color="primary" density="compact" variant="outlined" hide-details></v-text-field>
</div>
<v-divider></v-divider>
<h5 class="text-h5 mt-4 px-5 pb-4">Quick Page Links</h5>
<perfect-scrollbar style="height: 380px">
<v-list class="pt-0 pb-5" lines="two">
<v-list-item
:value="item"
v-for="(item, index) in searchSugg"
:key="index"
:to="item.href"
color="primary"
class="px-5 py-2"
>
<h6 class="text-subtitle-1 heading mb-1">{{ item.title }}</h6>
<p class="text-subtitle-2 textSecondary">{{ item.href }}</p>
</v-list-item>
</v-list>
</perfect-scrollbar>
</v-sheet>
</v-menu>
</template>

View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useTheme } from 'vuetify';
import { useCustomizerStore } from '~/store/customizer';
import { Icon } from '@iconify/vue';
const theme = useTheme();
const customizer = useCustomizerStore();
// template skin color options
const themeColors = ref([
{
name: 'BLUE_THEME',
bg: 'togglethemeBlue'
},
{
name: 'DARK_BLUE_THEME',
bg: 'togglethemeDarkBlue'
}
]);
</script>
<template>
<div class="position-relative">
<v-item-group mandatory v-model="customizer.actTheme" class="d-flex">
<div v-for="theme in themeColors" :key="theme.name">
<v-item v-slot="{ toggle }" :value="theme.name">
<v-sheet rounded="circle" class="cursor-pointer text-center hover-btns" elevation="0" @click="toggle">
<v-btn icon :class="theme.bg" class="custom-hover-primary" size="small" variant="text" color="primary">
<SunIcon v-if="theme.bg == 'togglethemeBlue'" :class="theme.bg" height="22" />
<MoonIcon v-if="theme.bg == 'togglethemeDarkBlue'" :class="theme.bg" height="22" />
</v-btn>
</v-sheet>
</v-item>
</div>
</v-item-group>
</div>
</template>

View File

@@ -0,0 +1,120 @@
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { useCustomizerStore } from '~/store/customizer';
// import { useEcomStore } from '@/stores/apps/eCommerce';
// import LanguageDD from './LanguageDD.vue';
import NotificationDD from './NotificationDD.vue';
import ProfileDD from './ProfileDD.vue';
import Searchbar from './Searchbar.vue';
import RightMobileSidebar from './RightMobileSidebar.vue';
import Navigations from './Navigations.vue';
import { Icon } from '@iconify/vue';
import Logo from '../logo/Logo.vue';
import ThemeToggler from './ThemeToggler.vue';
const customizer = useCustomizerStore();
const showSearch = ref(false);
const priority = ref(customizer.setHorizontalLayout ? 0 : 0);
function searchbox() {
showSearch.value = !showSearch.value;
}
watch(priority, (newPriority) => {
priority.value = newPriority;
});
// count items
// const store = useEcomStore();
// const getCart = computed(() => {
// return store.cart;
// });
</script>
<template>
<v-app-bar elevation="0" :priority="priority" height="70" id="top" class="main-head">
<v-btn
class="hidden-lg-and-up custom-hover-primary"
size="small"
variant="text"
color="primary"
icon
@click.stop="customizer.SET_SIDEBAR_DRAWER"
>
<Icon icon="solar:hamburger-menu-line-duotone" height="22" />
</v-btn>
<!-- ---------------------------------------------- -->
<!-- Search part -->
<!-- ---------------------------------------------- -->
<Searchbar />
<!-- ---------------------------------------------- -->
<!-- Mega menu -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<Navigations />
</div>
<v-spacer class="hidden-sm-and-down" />
<!-- ---------------------------------------------- -->
<!-- Mobile Logo -->
<!-- ---------------------------------------------- -->
<div class="hidden-md-and-up">
<Logo />
</div>
<ThemeToggler />
<!-- ---------------------------------------------- -->
<!-- translate -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<LanguageDD />
</div>
<!-- ---------------------------------------------- -->
<!-- ShoppingCart -->
<!-- ---------------------------------------------- -->
<!-- <v-btn icon class="custom-hover-primary hidden-sm-and-down" size="small" variant="text" color="primary" to="/ecommerce/checkout">
<v-badge color="error" :content="getCart?.length">
<Icon icon="solar:cart-large-2-outline" height="22" />
</v-badge>
</v-btn> -->
<!-- ---------------------------------------------- -->
<!-- Notification -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<NotificationDD />
</div>
<!-- ---------------------------------------------- -->
<!-- User Profile -->
<!-- ---------------------------------------------- -->
<div class="hidden-sm-and-down">
<ProfileDD />
</div>
<!----Mobile ----->
<v-menu :close-on-content-click="true" class="mobile_popup">
<template v-slot:activator="{ props }">
<v-btn icon class="hidden-md-and-up custom-hover-primary" color="primary" variant="text" v-bind="props" size="small">
<Icon icon="solar:menu-dots-bold-duotone" height="22" />
</v-btn>
</template>
<v-sheet rounded="lg" elevation="10" class="mt-4 dropdown-box px-4 py-3">
<div class="d-flex justify-space-between align-center">
<RightMobileSidebar />
<!-- <LanguageDD /> -->
<!-- <v-btn icon variant="text" class="mr-sm-3 mr-2 custom-hover-primary" to="/ecommerce/checkout" size="small">
<v-badge color="primary" :content="getCart?.length" offset-x="-4" offset-y="-6">
<Icon icon="solar:cart-large-2-outline" height="22" />
</v-badge>
</v-btn> -->
<NotificationDD />
<ProfileDD />
</div>
</v-sheet>
</v-menu>
</v-app-bar>
</template>