Dev/v1.1 (#2)
Build Docker Image / build-backend (push) Successful in 46s
Build Docker Image / build-frontend (push) Failing after 11s

Co-authored-by: ISMAIL MASSERAN <topaz@ISMAILs-Macbook.local>
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2026-07-02 10:09:08 +08:00
parent 2e2d94f0d6
commit 421775d3ff
41 changed files with 1210 additions and 971 deletions
+72 -22
View File
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { useBreadcrumb } from '@/composables/useBreadcrumb'
import { useNotifications } from '@/modules/notification'
import '@/assets/css/themes/enigma/side-menu.css'
import logo from '@/assets/images/logo-kopkb.svg'
import logo from '@/assets/images/logonew-mykopkb.svg'
import { useSideMenu } from '@/composables/useSideMenu'
import { useAuthStore } from '@/stores/auth'
import { Lucide } from '@/components/ui/lucide'
@@ -40,9 +40,59 @@ const { unreadCount, fetchNotifications } = useNotifications()
const appName = import.meta.env.VITE_APP_NAME
const appVersion = import.meta.env.VITE_APP_VERSION
const notificationsOpen = ref(false)
const profileOpen = ref(false)
const sidebarProfileOpen = ref(false)
const notificationsRef = ref<HTMLElement | null>(null)
const profileRef = ref<HTMLElement | null>(null)
const sidebarProfileRef = ref<HTMLElement | null>(null)
function toggleNotifications() {
profileOpen.value = false
sidebarProfileOpen.value = false
notificationsOpen.value = !notificationsOpen.value
}
function toggleProfile() {
notificationsOpen.value = false
sidebarProfileOpen.value = false
profileOpen.value = !profileOpen.value
}
function closeDropdowns() {
notificationsOpen.value = false
profileOpen.value = false
sidebarProfileOpen.value = false
}
function handleDocumentClick(event: MouseEvent) {
const target = event.target as Node
if (
notificationsRef.value?.contains(target) ||
profileRef.value?.contains(target) ||
sidebarProfileRef.value?.contains(target)
) {
return
}
closeDropdowns()
}
watch(sidebarProfileOpen, (isOpen) => {
if (!isOpen) return
notificationsOpen.value = false
profileOpen.value = false
})
onMounted(() => {
authStore.fetchSession()
fetchNotifications()
document.addEventListener('click', handleDocumentClick)
})
onUnmounted(() => {
document.removeEventListener('click', handleDocumentClick)
})
</script>
@@ -76,6 +126,7 @@ onMounted(() => {
'z-20 relative w-[320px] duration-100 transition-[width] group-[.side-menu--collapsed]:xl:w-[165px] group-[.side-menu--collapsed]:group-hover:xl:w-[320px] h-screen flex flex-col-reverse xl:flex-col',
'before:absolute before:inset-y-0 before:w-px before:mt-23 before:bg-foreground/11 dark:before:bg-foreground/10 before:right-0 before:mr-8 before:hidden xl:before:block',
]">
<!-- Logo -->
<div
class="relative z-10 mt-3 hidden h-[90px] w-[320px] flex-none items-center overflow-hidden pl-8 pr-14 duration-100 xl:flex group-[.side-menu--collapsed]:group-hover:xl:w-[320px] group-[.side-menu--collapsed]:xl:w-[165px]">
<a class="relative flex items-center transition-[margin] duration-100 xl:ml-1.5 group-[.side-menu--collapsed]:group-hover:xl:ml-1.5 group-[.side-menu--collapsed]:xl:ml-8"
@@ -91,13 +142,15 @@ onMounted(() => {
<Lucide icon="ChevronLeft" />
</button>
</div>
<AccountTrigger class="relative transition-[width] xl:mb-2 xl:mr-8"
innerClass="border-foreground/[.11] dark:border-foreground/[.11] border-t py-3 pl-6 pr-5 opacity-90 hover:opacity-100 xl:border-b xl:border-t-0 xl:pb-6 xl:pl-8 xl:pt-2.5"
avatarClass="h-11 w-11 border-background/70 dark:border-foreground/20 shadow-sm"
textClass="text-primary dark:text-foreground w-full">
<AccountDropdown class="absolute top-0 left-full origin-bottom-left"
boxClass="absolute bottom-0 left-[100%] ml-2 xl:bottom-auto xl:top-0" />
</AccountTrigger>
<div ref="sidebarProfileRef">
<AccountTrigger v-model:open="sidebarProfileOpen" class="relative transition-[width] xl:mb-2 xl:mr-8"
innerClass="border-foreground/[.11] dark:border-foreground/[.11] border-t py-3 pl-6 pr-5 opacity-90 hover:opacity-100 xl:border-b xl:border-t-0 xl:pb-6 xl:pl-8 xl:pt-2.5"
avatarClass="h-11 w-11 border-background/70 dark:border-foreground/20 shadow-sm"
textClass="text-primary dark:text-foreground w-full">
<AccountDropdown :open="sidebarProfileOpen" class="absolute top-0 left-full origin-bottom-left"
boxClass="absolute bottom-0 left-[100%] ml-2 xl:bottom-auto xl:top-0" />
</AccountTrigger>
</div>
<ScrollAreaRoot class="flex-1 min-h-0">
<ScrollAreaViewport :class="[
@@ -145,29 +198,26 @@ onMounted(() => {
<Breadcrumb v-if="breadcrumbItems.length"
class="mr-auto hidden xl:flex [&_ol]:text-(--color-nav-foreground)/70 [&_li]:last:text-(--color-nav-foreground)/90 [&_li]:hover:text-(--color-nav-foreground)/90"
:items="breadcrumbItems" />
<div
<div ref="notificationsRef"
class="group/notifications relative flex size-10 flex-none cursor-pointer items-center justify-center rounded-xl border border-(--color-nav-foreground)/30 bg-(--color-nav-foreground)/10 transition-colors hover:bg-(--color-nav-foreground)/20"
>
<Lucide
class="size-[22px] stroke-[1.75] [--color:var(--color-nav-foreground)]"
icon="Bell"
/>
<span
v-if="unreadCount > 0"
class="bg-danger text-background ring-background absolute -right-1.5 -top-1.5 flex min-w-5 items-center justify-center rounded-full px-1 py-0.5 text-[11px] font-semibold leading-none ring-2"
>
@click.stop="toggleNotifications">
<Lucide class="size-[22px] stroke-[1.75] [--color:var(--color-nav-foreground)]" icon="Bell" />
<span v-if="unreadCount > 0"
class="bg-danger text-background ring-background absolute -right-1.5 -top-1.5 flex min-w-5 items-center justify-center rounded-full px-1 py-0.5 text-[11px] font-semibold leading-none ring-2">
{{ unreadCount > 9 ? '9+' : unreadCount }}
</span>
<NotificationDropdown class="absolute right-0 top-full mt-2 origin-top-right"
<NotificationDropdown :open="notificationsOpen"
class="absolute right-0 top-full mt-2 origin-top-right"
boxClass="absolute right-0 top-0 -mr-0.5 -mt-0.5" />
</div>
<div class="group/profile relative size-9 flex-none">
<div ref="profileRef" class="group/profile relative size-9 flex-none cursor-pointer"
@click.stop="toggleProfile">
<AvatarRoot class="ring-(--color)/40 size-full [--color:var(--color-nav-foreground)] rounded-full">
<AvatarFallback>{{ authStore.userName }}</AvatarFallback>
<AvatarImage v-if="authStore.userImageUrl" :src="authStore.userImageUrl"
:alt="authStore.userName" />
</AvatarRoot>
<AccountDropdown class="absolute right-0 top-full mt-2 origin-top-right"
<AccountDropdown :open="profileOpen" class="absolute right-0 top-full mt-2 origin-top-right"
boxClass="absolute right-0 top-0 -mr-0.5 -mt-0.5" />
</div>
</div>