Dev cleaning (#106)

This commit is contained in:
Munawwirul Jamal
2025-10-08 00:03:36 +07:00
committed by GitHub
parent 7fdd5c61f0
commit 3eb9dde21d
892 changed files with 51326 additions and 1 deletions

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
<script setup lang="ts">
const navMenu = ref([])
const teams: {
name: string
logo: string
plan: string
}[] = [
{
name: 'SIMRS - RSSA',
logo: '/rssa-logo.png',
plan: 'Saiful Anwar Hospital',
},
]
const sidebar = {
collapsible: 'offcanvas', // 'offcanvas' | 'icon' | 'none'
side: 'left', // 'left' | 'right'
variant: 'sidebar', // 'sidebar' | 'floating' | 'inset'
}
const navMenuBottom: any[] = [
{
title: 'Help & Support',
icon: 'i-lucide-circle-help',
link: 'https://github.com/simrs/simrs-fe',
},
]
onMounted(async () => {
await setMenu()
})
function resolveNavItemComponent(item: any): any {
if ('children' in item) return resolveComponent('LayoutSidebarNavGroup')
return resolveComponent('LayoutSidebarNavLink')
}
async function setMenu() {
const position_code = 'sys'
const res = await fetch(`/side-menu-items/${position_code}.json`)
const rawMenu = await res.text()
navMenu.value = JSON.parse(rawMenu)
}
</script>
<template>
<Sidebar :collapsible="sidebar.collapsible" :side="sidebar.side" :variant="sidebar.variant">
<SidebarHeader>
<LayoutSidebarNavHeader :teams="teams" />
</SidebarHeader>
<SidebarContent>
<template v-if="navMenu.length > 0">
<SidebarGroup v-for="(nav, indexGroup) in navMenu" :key="indexGroup">
<SidebarGroupLabel v-if="nav.heading">
{{ nav.heading }}
</SidebarGroupLabel>
<component
:is="resolveNavItemComponent(item)"
v-for="(item, index) in nav.items"
:key="index"
:item="item"
class="my-1 mb-1"
/>
</SidebarGroup>
</template>
<template v-else>
<div class="p-5">
<div v-for="n in 10" :key="n" class="my-4 h-8 animate-pulse rounded bg-gray-200 py-2"></div>
</div>
</template>
<SidebarGroup class="mt-auto">
<component
:is="resolveNavItemComponent(item)"
v-for="(item, index) in navMenuBottom"
:key="index"
:item="item"
size="sm"
/>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<LayoutSidebarNavFooter />
</SidebarFooter>
<SidebarRail />
</Sidebar>
</template>
<style scoped></style>
+31
View File
@@ -0,0 +1,31 @@
<script setup lang="ts">
defineProps<{
reverse?: boolean
}>()
</script>
<template>
<div
class="relative flex h-dvh items-center justify-center px-4 lg:max-w-none lg:px-0"
:class="{ 'flex-row-reverse': reverse }"
>
<div class="bg-muted relative hidden h-full flex-1 flex-col p-10 text-white lg:flex dark:border-r">
<div class="absolute inset-0 bg-zinc-900" />
<div class="relative z-20 flex items-center text-lg font-medium">RSSA</div>
<div class="relative z-20 mt-auto">
<blockquote class="space-y-2">
<p class="text-lg">
&ldquo;This library has saved me countless hours of work and helped me deliver stunning designs to my
clients faster than ever before.&rdquo;
</p>
<footer class="text-sm">Sofia Davis</footer>
</blockquote>
</div>
</div>
<div class="mx-auto flex-1 lg:p-8">
<slot />
</div>
</div>
</template>
<style scoped></style>
+62
View File
@@ -0,0 +1,62 @@
<script setup lang="ts">
import ThemeToggle from "./ThemeToggle.vue"
const route = useRoute()
function setLinks() {
if (route.path === '/') {
return [{ title: 'Home', href: '/' }]
}
const segments = route.path.split('/').filter((item) => item !== '')
const breadcrumbs = segments.map((item, index) => {
// Bersihkan query parameters dari segment jika ada
const cleanItem = item.split('?')[0] || item
const str = cleanItem.replace(/-/g, ' ')
const title = str
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')
return {
title,
href: `/${segments.slice(0, index + 1).map(seg => seg.split('?')[0] || seg).join('/')}`,
}
})
return [{ title: 'Home', href: '/' }, ...breadcrumbs]
}
const links = ref<
{
title: string
href: string
}[]
>(setLinks())
watch(
() => route.path,
(val) => {
if (val) {
links.value = setLinks()
}
},
)
</script>
<template>
<header class="h-53px bg-background sticky top-0 z-10 flex items-center gap-4 border-b px-4 md:px-6">
<div class="flex w-full items-center gap-4">
<SidebarTrigger />
<Separator orientation="vertical" class="h-4" />
<PubMyUiBreadcrumb :links="links" />
</div>
<div class="ml-auto flex items-center gap-2">
<slot />
<ThemeToggle />
</div>
</header>
</template>
<style scoped></style>
@@ -0,0 +1,96 @@
<script setup lang="ts">
import { useSidebar } from '~/components/pub/ui/sidebar'
// defineProps<{
// user: {
// name: string
// email: string
// avatar: string
// }
// }>()
const { isMobile } = useSidebar()
const { logout } = useUserStore()
const userStore = useUserStore().user
function handleLogout() {
navigateTo('/auth/login')
logout()
}
const showModalTheme = ref(false)
</script>
<template>
<ClientOnly>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage src="" :alt="userStore?.user_name || 'system'" />
<AvatarFallback class="rounded-lg">
{{
userStore?.user_name
?.split(' ')
.map((n) => n[0])
.join('') || ''
}}
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ userStore?.user_name || '' }}</span>
<span class="truncate text-xs">{{ userStore?.user_email || '' }}</span>
</div>
<Icon name="i-lucide-chevrons-up-down" class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg bg-white"
:side="isMobile ? 'bottom' : 'right'"
align="end"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage src="" :alt="userStore?.user_name || 'system'" />
<AvatarFallback class="rounded-lg">
{{
userStore?.user_name
?.split(' ')
.map((n) => n[0])
.join('') || ''
}}
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ userStore?.user_name || '' }}</span>
<span class="truncate text-xs">{{ userStore?.user_email || '' }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem class="hover:bg-gray-100" @click="showModalTheme = true">
<Icon name="i-lucide-user" />
Profile
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem class="hover:bg-gray-100" @click="handleLogout">
<Icon name="i-lucide-log-out" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</ClientOnly>
</template>
<style scoped></style>
+55
View File
@@ -0,0 +1,55 @@
<script setup lang="ts">
import type { SidebarMenuButtonVariants } from '~/components/pub/ui/sidebar'
import { useSidebar } from '~/components/pub/ui/sidebar'
withDefaults(
defineProps<{
item: any
size?: SidebarMenuButtonVariants['size']
}>(),
{
size: 'default',
},
)
const { setOpenMobile } = useSidebar()
const openCollapsible = ref(false)
</script>
<template>
<SidebarMenu>
<Collapsible :key="item.title" v-model:open="openCollapsible" as-child class="group/collapsible ">
<SidebarMenuItem>
<CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title" :size="size" class="md:!text-xs xl:!text-sm 2xl:!text-base">
<Icon :name="item.icon || ''" mode="svg" />
<span class="mx-2">{{ item.title }}</span>
<Icon
name="i-lucide-chevron-right"
class="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="subItem in item.children" :key="subItem.title">
<SidebarMenuSubButton as-child>
<NuxtLink
:to="subItem.link"
class="mx-4 rounded-lg py-5 text-sm transition-all duration-200 md:!text-xs xl:!text-sm 2xl:!text-base"
active-class="bg-primary text-white"
@click="setOpenMobile(false)"
>
<span>{{ subItem.title }}</span>
</NuxtLink>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</template>
<style scoped></style>
@@ -0,0 +1,40 @@
<script setup lang="ts">
const props = defineProps<{
teams: {
name: string
logo: string
plan: string
}[]
}>()
const activeTeam = ref(props.teams[0])
</script>
<template>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<div
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg"
>
<img :src="activeTeam.logo" class="h-full w-full" />
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">
{{ activeTeam.name }}
</span>
<span class="truncate text-xs">{{ activeTeam.plan }}</span>
</div>
</SidebarMenuButton>
</DropdownMenuTrigger>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</template>
<style scoped></style>
+42
View File
@@ -0,0 +1,42 @@
<script setup lang="ts">
import type { SidebarMenuButtonVariants } from '~/components/pub/ui/sidebar'
import { useSidebar } from '~/components/pub/ui/sidebar'
withDefaults(
defineProps<{
item: any
size?: SidebarMenuButtonVariants['size']
}>(),
{
size: 'default',
},
)
const { setOpenMobile } = useSidebar()
</script>
<template>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton as-child :tooltip="item.title" :size="size" class="">
<NuxtLink
:to="item.link"
class="group flex items-center gap-3 rounded-lg px-2 py-4 text-sm transition-all duration-200 md:!text-xs xl:!text-sm 2xl:!text-base"
active-class="bg-primary text-white"
@click="setOpenMobile(false)"
>
<Icon :name="item.icon || ''" mode="svg" />
<span class="mx-1">{{ item.title }}</span>
<span
v-if="item.new"
class="bg-#adfa1d rounded-md px-1.5 py-0.5 text-xs leading-none text-black no-underline group-hover:no-underline "
>
New
</span>
</NuxtLink>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</template>
<style scoped></style>
+15
View File
@@ -0,0 +1,15 @@
<script setup lang="ts">
import { useTheme } from '~/composables/useTheme'
const { theme, toggleTheme } = useTheme()
</script>
<template>
<button
@click="toggleTheme"
class="ml-2 rounded border px-2 py-1 text-sm"
:title="theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'"
>
<span v-if="theme === 'dark'"><Icon name="i-lucide-moon" class="h-4 w-4 mt-1" /></span>
<span v-else><Icon name="i-lucide-sun" class="h-4 w-4 mt-1" /></span>
</button>
</template>