Update various configuration files, components, and assets; enhance notification system and API endpoints; improve documentation and styles across the application.

This commit is contained in:
Haqeem Solehan
2025-10-16 16:05:39 +08:00
commit b124ff8092
336 changed files with 94392 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
<script setup>
import { useLayoutStore } from "~/stores/layout";
import { useWindowSize } from "vue-window-size";
import RSChildItem from "~/components/layouts/sidemenu/ItemChild.vue";
import { useUserStore } from "~/stores/user";
const layoutStore = useLayoutStore();
const mobileWidth = layoutStore.mobileWidth;
const { width } = useWindowSize();
const user = useUserStore();
const route = useRoute();
const props = defineProps({
items: {
type: Array,
default: () => [],
required: true,
},
});
const username = user.username;
const roles = user.roles;
const menuItem = props.items ? props.items : [];
// validate userExist on meta.auth.user
function userExist(item) {
if (item.meta?.auth?.user) {
if (item.meta?.auth?.user.some((e) => e === username)) {
return true;
} else {
return false;
}
}
return true;
}
// validate roleExist on meta.auth.role
function roleExist(item) {
if (item.meta?.auth?.role) {
if (item.meta?.auth?.role.some((e) => roles?.includes(e))) {
return true;
} else {
return false;
}
}
return true;
}
// Toggle show and hide menu content
function openMenu(event) {
const target = event.currentTarget;
try {
target.querySelector("a").classList.toggle("nav-open");
target.querySelector("ul").classList.toggle("hide");
} catch (e) {
// console.log(e);
return;
}
}
// Active menu
function activeMenu(routePath) {
return route.path == routePath
? `bg-[rgb(var(--color-primary))] font-normal text-white active-menu`
: `font-light text-white/90 md:transition-all md:duration-200 hover:md:ml-2`;
}
function toggleMenu() {
document.querySelector(".v-layout").classList.toggle("menu-hide");
document.getElementsByClassName("menu-overlay")[0].classList.toggle("hide");
}
function navigationPage(path, external) {
if (width.value <= mobileWidth) toggleMenu();
navigateTo(path, {
external: external,
});
}
</script>
<template>
<div v-for="(item, index) in menuItem" :key="index">
<div
v-if="
!item.meta || !item.meta?.auth || (userExist(item) && roleExist(item))
"
class="navigation-wrapper"
>
<div
v-if="item.header"
class="text-left font-normal text-xs mx-6 mt-5 mb-2"
>
<span class="uppercase text-gray-400">
{{ item.header ? item.header : "" }}
</span>
<p class="text-gray-400">
{{ item.description ? item.description : "" }}
</p>
</div>
<ul class="navigation-menu">
<li
class="navigation-item"
v-for="(item2, index2) in item.child"
:key="index2"
@click.stop="
item2.child !== undefined ||
(item2.child && item2.child.length !== 0)
? openMenu($event)
: ''
"
>
<div
v-if="
!item2.meta ||
!item2.meta?.auth ||
(userExist(item2) && roleExist(item2))
"
class="navigation-item-wrapper"
>
<NuxtLink
v-if="
item2.child === undefined ||
(item2.child && item2.child.length === 0)
"
class="flex items-center px-6 py-3 cursor-pointer"
@click="navigationPage(item2.path, item2.external)"
:class="activeMenu(item2.path)"
>
<Icon v-if="item2.icon" :name="item2.icon" size="18"></Icon>
<Icon v-else name="mdi:circle-slice-8" size="18"></Icon>
<span class="mx-3 font-normal">{{ item2.title }}</span>
<Icon
v-if="item2.child && item2.child.length > 0"
class="ml-auto side-menu-arrow"
name="material-symbols:chevron-right-rounded"
size="18"
></Icon>
</NuxtLink>
<a
v-else
class="flex items-center px-6 py-3 rounded-lg cursor-pointer"
:class="activeMenu(item2.path)"
>
<Icon v-if="item2.icon" :name="item2.icon" size="18"></Icon>
<Icon v-else name="mdi:circle-slice-8" size="18"></Icon>
<span class="mx-3 font-normal">{{ item2.title }}</span>
<Icon
v-if="item2.child && item2.child.length > 0"
class="ml-auto side-menu-arrow"
name="material-symbols:chevron-right-rounded"
size="18"
></Icon>
</a>
<RSChildItem
v-if="item2.child"
:items="item2.child"
@openMenu="openMenu"
@activeMenu="activeMenu"
></RSChildItem>
</div>
</li>
</ul>
</div>
</div>
</template>

View File

@@ -0,0 +1,145 @@
<script setup>
import { useLayoutStore } from "~/stores/layout";
import { useWindowSize } from "vue-window-size";
import RSChildItem from "~/components/layouts/sidemenu/ItemChild.vue";
import { useUserStore } from "~/stores/user";
const layoutStore = useLayoutStore();
const mobileWidth = layoutStore.mobileWidth;
const { width } = useWindowSize();
const user = useUserStore();
const route = useRoute();
const props = defineProps({
items: {
type: Array,
required: true,
},
indent: {
type: Number,
default: 0.5,
},
});
const emit = defineEmits(["openMenu"]);
const indent = ref(props.indent);
const menuItem = props.items ? props.items : [];
const username = user.username;
const roles = user.roles;
// validate userExist on meta.auth.user
function userExist(item) {
if (item.meta?.auth?.user) {
if (item.meta?.auth?.user.includes(username)) {
return true;
} else {
return false;
}
}
return true;
}
// validate roleExist on meta.auth.role
function roleExist(item) {
if (item.meta?.auth?.role) {
if (item.meta?.auth?.role.some((r) => roles.includes(r))) {
return true;
} else {
return false;
}
}
return true;
}
// Toggle Open/Close menu
function openMenu(event) {
emit("openMenu", event);
}
// Active menu
function activeMenu(routePath) {
return route.path == routePath
? `bg-[rgb(var(--color-primary))] font-normal text-white active-menu`
: `font-light text-white/90 md:transition-all md:duration-200 hover:md:ml-2`;
}
function toggleMenu() {
document.querySelector(".v-layout").classList.toggle("menu-hide");
document.getElementsByClassName("menu-overlay")[0].classList.toggle("hide");
}
function navigationPage(path, external) {
if (width.value <= mobileWidth) toggleMenu();
navigateTo(path, {
external: external,
});
}
const indentStyle = computed(() => {
return { "background-color": `rgba(var(--sidebar-menu), ${indent.value})` };
});
</script>
<template>
<ul
class="menu-content hide transition-all duration-300"
:style="indentStyle"
>
<li
v-for="(item, index) in menuItem"
:key="index"
@click.stop="
item.child !== undefined || (item.child && item.child.length !== 0)
? openMenu($event)
: ''
"
>
<div
v-if="
!item.meta || !item.meta?.auth || (userExist(item) && roleExist(item))
"
class="navigation-item-wrapper"
>
<NuxtLink
v-if="
item.child === undefined || (item.child && item.child.length === 0)
"
class="flex items-center px-6 py-3 cursor-pointer"
@click="navigationPage(item.path, item.external)"
:class="activeMenu(item.path)"
>
<Icon v-if="item.icon" :name="item.icon" size="18"></Icon>
<span class="mx-4 font-normal">{{ item.title }}</span>
<Icon
v-if="item.child && item.child.length > 0"
class="ml-auto side-menu-arrow"
name="material-symbols:chevron-right-rounded"
size="18"
></Icon>
</NuxtLink>
<a
v-else
class="flex items-center px-6 py-3 rounded-lg cursor-pointer"
:class="activeMenu(item.path)"
>
<span class="mx-3 font-normal">{{ item.title }}</span>
<Icon
v-if="item.child && item.child.length > 0"
class="ml-auto side-menu-arrow"
name="material-symbols:chevron-right-rounded"
size="18"
></Icon>
</a>
<RSChildItem
v-if="item.child"
:items="item.child"
:indent="indent + 0.1"
@openMenu="openMenu"
@activeMenu="activeMenu"
></RSChildItem>
</div>
</li>
</ul>
</template>

View File

@@ -0,0 +1,72 @@
<script setup>
import Menu from "~/navigation/index.js";
import RSItem from "~/components/layouts/sidemenu/Item.vue";
// Use site settings composable
const { siteSettings } = useSiteSettings();
// Add computed to ensure logo reactivity
const logoToShow = computed(() => {
// Always try to use the siteLogo from settings first
if (siteSettings.value?.siteLogo && siteSettings.value.siteLogo.trim() !== "") {
return siteSettings.value.siteLogo;
}
// Fallback to default logo if siteLogo is not set
return "/img/logo/corradAF-logo.svg";
});
const siteNameToShow = computed(() => {
return siteSettings.value.siteName || "Jabatan Imigresen Malaysia";
});
// const menuItem = Menu;
const props = defineProps({
menuItem: {
type: Array,
default: () => Menu,
required: false,
},
sidebarToggle: {
type: Boolean,
default: false,
},
});
onMounted(() => {
try {
const el = document.querySelector(".active-menu").closest(".menu-content");
const elParent = el.parentElement.parentElement;
if (elParent) {
elParent.classList.remove("hide");
elParent.querySelector("a").classList.add("nav-open");
}
if (el) el.classList.remove("hide");
} catch (e) {
// console.log(e);
return;
}
});
</script>
<template>
<div class="vertical-menu">
<div class="py-2 px-4 bg-[rgb(var(--header))]">
<nuxt-link to="/">
<div class="flex flex-auto gap-3 justify-center items-center h-[48px]">
<div
class="app-logo text-center h-20 flex justify-center items-center gap-3 px-4"
>
<nuxt-link to="/" class="flex items-center justify-center">
<img src="@/assets/img/logo/lzs-logo.png" class="h-12" alt="logo" />
</nuxt-link>
</div>
</div>
</nuxt-link>
</div>
<NuxtScrollbar class="flex flex-col justify-between my-6" style="max-height: 82dvh">
<RSItem :items="menuItem"></RSItem>
</NuxtScrollbar>
</div>
</template>