update component structure
This commit is contained in:
117
src/components/Navigation/Aside/Aside.vue
Normal file
117
src/components/Navigation/Aside/Aside.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import { watch } from 'vue'
|
||||
import { useRoute, RouterLink } from 'vue-router'
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
} from '@headlessui/vue'
|
||||
import { XMarkIcon } from '@heroicons/vue/24/outline'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
import { IconApp } from '@/utils/icons'
|
||||
import AsideMenuSingle from '@/components/Navigation/Aside/AsideMenuSingle.vue'
|
||||
import AsideMenuMultiple from '@/components/Navigation/Aside/AsideMenuMultiple.vue'
|
||||
import { Bars3BottomLeftIcon } from '@heroicons/vue/20/solid'
|
||||
|
||||
const menu = useMenuStore()
|
||||
const route = useRoute()
|
||||
|
||||
watch(route, (to, _) => {
|
||||
menu.menuSelected = to.fullPath
|
||||
closeSideBar()
|
||||
})
|
||||
|
||||
const isMenu = (name: string) => {
|
||||
return menu.menuSelected === name
|
||||
}
|
||||
|
||||
const closeSideBar = () => menu.toggleSidebar()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="menu.sidebarOpen">
|
||||
<Dialog as="div" class="relative z-40 md:hidden" @close="closeSideBar">
|
||||
<TransitionChild as="template" enter="transition-opacity ease-linear duration-300" enter-from="opacity-0"
|
||||
enter-to="opacity-100" leave="transition-opacity ease-linear duration-300" leave-from="opacity-100"
|
||||
leave-to="opacity-0">
|
||||
<div class="fixed inset-0 bg-gray-600 bg-opacity-75" />
|
||||
</TransitionChild>
|
||||
|
||||
<div class="fixed inset-0 z-40 flex">
|
||||
<TransitionChild as="template" enter="transition ease-in-out duration-300 transform"
|
||||
enter-from="-translate-x-full" enter-to="translate-x-0"
|
||||
leave="transition ease-in-out duration-300 transform" leave-from="translate-x-0"
|
||||
leave-to="-translate-x-full">
|
||||
<DialogPanel class="relative flex flex-col flex-1 w-full max-w-xs pt-5 pb-4 bg-primary-50">
|
||||
<TransitionChild as="template" enter="ease-in-out duration-300" enter-from="opacity-0"
|
||||
enter-to="opacity-100" leave="ease-in-out duration-300" leave-from="opacity-100"
|
||||
leave-to="opacity-0">
|
||||
<div class="absolute top-0 right-0 pt-2 -mr-12">
|
||||
<button type="button"
|
||||
class="flex items-center justify-center w-10 h-10 ml-1 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
|
||||
@click="closeSideBar">
|
||||
<span class="sr-only">Close sidebar</span>
|
||||
<XMarkIcon class="w-6 h-6 text-white" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
<div class="flex items-center flex-shrink-0 px-4">
|
||||
<RouterLink to="/">
|
||||
<img class="w-auto h-16" :src="IconApp" alt="PLN" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="flex-1 h-0 mt-5 overflow-y-auto">
|
||||
<nav class="px-2 space-y-1">
|
||||
<template v-for="item in menu.navigation" :key="item.name">
|
||||
<!-- Single-level item -->
|
||||
<AsideMenuSingle v-if="item.children.length === 0" :item="item"
|
||||
:selected="isMenu(item.href)" />
|
||||
|
||||
<!-- Nested item with children -->
|
||||
<AsideMenuMultiple v-else :item="item" :selected="isMenu(item.href)" />
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
<div class="flex-shrink-0 w-14" aria-hidden="true">
|
||||
<!-- Dummy element to force sidebar to shrink to fit close icon -->
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
<!-- Static sidebar for desktop -->
|
||||
<div class="z-10 hidden md:fixed md:inset-y-0 md:flex md:w-80 md:flex-col">
|
||||
<!-- Sidebar component, swap this element with another sidebar if you like -->
|
||||
<div class="flex flex-row items-center flex-shrink-0 h-16 px-2 bg-primary-50">
|
||||
<button type="button"
|
||||
class="px-4 py-4 text-gray-500 rounded-full hover:bg-primary-100 focus:outline-none focus:ring-0"
|
||||
@click="menu.toggleSidebarDesktop()">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<Bars3BottomLeftIcon class="w-6 h-6" aria-hidden="true" />
|
||||
</button>
|
||||
<RouterLink to="/">
|
||||
<img class="w-auto h-11" :src="IconApp" alt="PLN" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
<span v-if="menu.sidebarShowed" class="px-4 mt-4 text-sm font-semibold text-primary-800">
|
||||
Menu
|
||||
</span>
|
||||
<div :class="[menu.sidebarShowed ? 'flex flex-col flex-grow overflow-y-auto' : 'hidden', '']">
|
||||
<div class="flex flex-col flex-grow mt-3">
|
||||
<nav class="flex-1 px-2 pb-4 space-y-1">
|
||||
<template v-for="item in menu.navigation" :key="item.name">
|
||||
<!-- Single-level item -->
|
||||
<AsideMenuSingle v-if="item.children.length === 0" :item="item" :selected="isMenu(item.href)" />
|
||||
|
||||
<!-- Nested item with children -->
|
||||
<AsideMenuMultiple v-else :item="item" :selected="isMenu(item.href)" />
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
74
src/components/Navigation/Aside/AsideMenuMultiple.vue
Normal file
74
src/components/Navigation/Aside/AsideMenuMultiple.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps } from 'vue'
|
||||
import type { MenuItemModel } from '@/utils/interfaces'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
import { DotOutline } from '@/utils/icons'
|
||||
import AsideMenuSingle from '@/components/Navigation/Aside/AsideMenuSingle.vue'
|
||||
const menu = useMenuStore()
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as () => MenuItemModel,
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isChildren: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const isMenu = (name: string) => {
|
||||
return menu.menuSelected === name
|
||||
}
|
||||
|
||||
const isMenuSelected = computed(() => {
|
||||
return props.item.children.find((d) => d.href === menu.menuSelected) || props.item.children.find((d) => d.children.find((e) => e.href === menu.menuSelected))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[isChildren ? 'ml-2 mt-1 bg-primary-100 rounded-xl' : '']">
|
||||
<Disclosure v-bind:default-open="isMenuSelected ? true : false" as="dev">
|
||||
<!-- Nested item with children -->
|
||||
<DisclosureButton @click="menu.toggleSidebarMenu(item.href, !item.expanded)"
|
||||
:class="[(isMenuSelected || item.expanded || isChildren || item.expanded) ? 'bg-primary-100 text-primary-500 font-bold' : 'text-gray-600 font-semibold text-aside hover:bg-primary-100 hover:text-primary-500', isChildren ? 'pl-1 pr-2' : 'px-2', 'group w-full flex items-center py-2 text-left text-xs rounded-lg focus:outline-none focus:ring-0']">
|
||||
<embed :src="isChildren ? DotOutline : item.icon"
|
||||
:class="[isMenuSelected ? 'fill-primary-500' : 'fill-gray-400 group-hover:fill-gray-500', isChildren ? '' : 'mr-2', 'flex-shrink-0 w-6 h-6']"
|
||||
type="image/svg+xml" />
|
||||
<span class="flex-1">{{ item.name }}</span>
|
||||
|
||||
<svg :class="[isMenuSelected ? 'text-primary-500' : 'text-gray-300 group-hover:text-gray-500', item.expanded ? 'rotate-180' : '', 'ml-3 flex-shrink-0 transform transition-colors duration-150 ease-in-out ']"
|
||||
width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M4.44002 8.9399C4.72127 8.659 5.10252 8.50122 5.50002 8.50122C5.89752 8.50122 6.27877 8.659 6.56002 8.9399L12 14.3799L17.44 8.9399C17.7244 8.67494 18.1005 8.53069 18.4891 8.53755C18.8777 8.54441 19.2484 8.70183 19.5233 8.97666C19.7981 9.25148 19.9555 9.62225 19.9624 10.0109C19.9692 10.3995 19.825 10.7756 19.56 11.0599L13.06 17.5599C12.7788 17.8408 12.3975 17.9986 12 17.9986C11.6025 17.9986 11.2213 17.8408 10.94 17.5599L4.44002 11.0599C4.15912 10.7787 4.00134 10.3974 4.00134 9.9999C4.00134 9.6024 4.15912 9.22115 4.44002 8.9399Z"
|
||||
:fill="isMenuSelected ? '#14A2BA' : '#647375'" />
|
||||
</svg>
|
||||
</DisclosureButton>
|
||||
<transition enter-active-class="overflow-hidden transition-all duration-300"
|
||||
enter-from-class="transform scale-95 max-h-0" enter-to-class="transform scale-100 max-h-[1000px]"
|
||||
leave-active-class="overflow-hidden transition-all duration-300"
|
||||
leave-from-class="transform scale-100 max-h-[1000px]" leave-to-class="transform scale-95 max-h-0">
|
||||
<div v-show="item.expanded">
|
||||
<DisclosurePanel :class="['bg-primary-100 rounded-xl ml-4', 'space-y-1']" static>
|
||||
<!-- Nested children -->
|
||||
<template v-for="(subItem, index) in item.children" :key="subItem.href">
|
||||
<!-- Single-level child -->
|
||||
<AsideMenuSingle v-if="subItem.children.length === 0" :item="subItem" :is-children="true"
|
||||
:selected="isMenu(subItem.href)" />
|
||||
<!-- Multiple-level child -->
|
||||
<AsideMenuMultiple v-else :item="subItem" :selected="subItem.href === menu.menuSelected"
|
||||
:is-children="true" />
|
||||
</template>
|
||||
</DisclosurePanel>
|
||||
</div>
|
||||
|
||||
</transition>
|
||||
|
||||
</Disclosure>
|
||||
</div>
|
||||
</template>
|
43
src/components/Navigation/Aside/AsideMenuSingle.vue
Normal file
43
src/components/Navigation/Aside/AsideMenuSingle.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuItemModel } from '@/utils/interfaces'
|
||||
|
||||
defineProps({
|
||||
item: {
|
||||
type: Object as () => MenuItemModel,
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isChildren: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="group">
|
||||
<RouterLink :to="item.href"
|
||||
:class="[selected ? 'bg-primary-500 text-white font-bold' : 'font-semibold text-aside group-hover:bg-primary-100 text-gray-600 group-hover:text-white group-hover:bg-primary-500', 'w-full flex items-center pl-2 pr-4 py-2 text-xs rounded-xl', isChildren ? 'mt-1' : 'mt-0']">
|
||||
|
||||
<svg v-if="isChildren" :class="[selected ? 'stroke-white' : 'text-aside group-hover:stroke-white', 'h-6 w-6']"
|
||||
width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="DotOutline">
|
||||
<path id="Vector" opacity="0.2"
|
||||
d="M9.5 8C9.5 8.29667 9.41203 8.58668 9.24721 8.83336C9.08238 9.08003 8.84812 9.27229 8.57403 9.38582C8.29994 9.49935 7.99834 9.52906 7.70737 9.47118C7.41639 9.4133 7.14912 9.27044 6.93934 9.06066C6.72956 8.85088 6.5867 8.58361 6.52882 8.29264C6.47094 8.00166 6.50065 7.70006 6.61418 7.42597C6.72771 7.15189 6.91997 6.91762 7.16665 6.7528C7.41332 6.58797 7.70333 6.5 8 6.5C8.39783 6.5 8.77936 6.65804 9.06066 6.93934C9.34197 7.22064 9.5 7.60218 9.5 8Z"
|
||||
fill="#4B5563" />
|
||||
<path id="Vector_2"
|
||||
d="M8 6C7.60444 6 7.21776 6.1173 6.88886 6.33706C6.55996 6.55682 6.30362 6.86918 6.15224 7.23463C6.00087 7.60009 5.96126 8.00222 6.03843 8.39018C6.1156 8.77814 6.30608 9.13451 6.58579 9.41421C6.86549 9.69392 7.22186 9.8844 7.60982 9.96157C7.99778 10.0387 8.39992 9.99913 8.76537 9.84776C9.13082 9.69638 9.44318 9.44004 9.66294 9.11114C9.8827 8.78224 10 8.39556 10 8C10 7.46957 9.78929 6.96086 9.41421 6.58579C9.03914 6.21071 8.53043 6 8 6ZM8 9C7.80222 9 7.60888 8.94135 7.44443 8.83147C7.27998 8.72159 7.15181 8.56541 7.07612 8.38268C7.00043 8.19996 6.98063 7.99889 7.01922 7.80491C7.0578 7.61093 7.15304 7.43275 7.29289 7.29289C7.43275 7.15304 7.61093 7.0578 7.80491 7.01921C7.99889 6.98063 8.19996 7.00043 8.38268 7.07612C8.56541 7.15181 8.72159 7.27998 8.83147 7.44443C8.94135 7.60888 9 7.80222 9 8C9 8.26522 8.89464 8.51957 8.70711 8.70711C8.51957 8.89464 8.26522 9 8 9Z"
|
||||
fill="#4B5563" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<embed v-else :src="item.icon" type="image/svg+xml"
|
||||
:class="[selected ? 'text-white fill-white' : 'text-aside group-hover:text-white group-hover:fill-white', 'mr-2 flex-shrink-0 h-6 w-6']">
|
||||
|
||||
{{ item.name }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
137
src/components/Navigation/Header.vue
Normal file
137
src/components/Navigation/Header.vue
Normal file
@ -0,0 +1,137 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useCommandPalattesStore } from '@/stores/command'
|
||||
import { useMenuStore } from '@/stores/menu'
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuItems,
|
||||
} from '@headlessui/vue'
|
||||
import {
|
||||
Bars3BottomLeftIcon
|
||||
} from '@heroicons/vue/24/outline'
|
||||
import { MagnifyingGlassIcon } from '@heroicons/vue/24/solid'
|
||||
import PictureInitial from '@/components/PictureInitial.vue'
|
||||
import { useDialogStore } from '@/stores/dialog'
|
||||
import { IconApp } from '@/utils/icons'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const user = useUserStore()
|
||||
const command = useCommandPalattesStore()
|
||||
const menu = useMenuStore()
|
||||
const dialog = useDialogStore()
|
||||
|
||||
const openSideBar = () => menu.toggleSidebar()
|
||||
|
||||
const showDialogLogout = () => {
|
||||
dialog.type = 'error'
|
||||
dialog.title = 'Logout dari akun?'
|
||||
dialog.content = 'Apakah Anda sudah yakin akan logout dari akun ini?'
|
||||
dialog.cancelText = 'Batalkan'
|
||||
dialog.confirmText = 'Ya, logout'
|
||||
dialog.showCancelButton = true
|
||||
dialog.onConfirm = () => {
|
||||
auth.logout()
|
||||
window.location.href = '/login'
|
||||
}
|
||||
dialog.open = true
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="sticky top-0 z-10 flex flex-shrink-0 h-16 bg-primary-50 md:ml-80">
|
||||
<button type="button" class="px-4 text-gray-500 focus:outline-none focus:ring-0 md:hidden" @click="openSideBar">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<Bars3BottomLeftIcon class="w-6 h-6" aria-hidden="true" />
|
||||
</button>
|
||||
<div class="flex items-center flex-shrink-0 my-auto ml-2 md:hidden">
|
||||
<img class="w-auto h-11" :src="IconApp" alt="PLN" />
|
||||
</div>
|
||||
<div class="flex justify-end w-full px-4">
|
||||
<!-- <div class="flex flex-1">
|
||||
<div class="flex w-full md:ml-0">
|
||||
<button @click="command.showCommandPalettes"
|
||||
class="relative w-full text-gray-400 border-0 border-transparent rounded-xl hover:text-primary-500 ring-0 ring-transparent focus:border-transparent focus:border-0 focus:ring-0">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
|
||||
<MagnifyingGlassIcon class="w-5 h-5" aria-hidden="true" />
|
||||
<span class="hidden ml-2 sm:text-sm md:block">Cari menu...</span>
|
||||
</div>
|
||||
</button>
|
||||
<img :src="Icon" alt="pln" class="md:hidden">
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="flex items-center ml-4 md:ml-6">
|
||||
<!-- <button type="button"
|
||||
class="p-1 mr-2 text-gray-400 bg-white rounded-full hover:text-primary-500 focus:outline-none focus:ring-0">
|
||||
<span class="sr-only">FAQ</span>
|
||||
<QuestionMarkCircleIcon class="w-6 h-6" aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button"
|
||||
class="p-1 mr-2 text-gray-400 bg-white rounded-full hover:text-primary-500 focus:outline-none focus:ring-0">
|
||||
<span class="sr-only">Manual Book</span>
|
||||
<BookOpenIcon class="w-6 h-6" aria-hidden="true" />
|
||||
</button -->
|
||||
<button @click="command.showCommandPalettes" type="button"
|
||||
class="flex flex-row items-center md:w-[300px] p-2 mr-2 text-gray-400 bg-white rounded-full hover:text-primary-500 focus:outline-none focus:ring-0">
|
||||
<span class="sr-only">Search</span>
|
||||
<MagnifyingGlassIcon class="w-6 h-6 text-primary-500" aria-hidden="true" />
|
||||
<span class="hidden px-3 text-sm font-medium text-gray-500 md:block text-md">Cari menu</span>
|
||||
</button>
|
||||
|
||||
<!-- Profile dropdown -->
|
||||
<Menu as="div" class="relative ml-1">
|
||||
<div>
|
||||
<MenuButton
|
||||
class="flex items-center max-w-xs px-1 py-1 text-sm rounded-full bg-secondary-100 md:bg-primary-500 focus:outline-none focus:ring-0 line-clamp-1">
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<PictureInitial size-class="w-8 h-8" background-class="bg-secondary-100"
|
||||
font-class="text-xs font-bold text-primary-500" :name="user.user_name" />
|
||||
<span class="hidden px-3 text-xs font-medium md:block text-primary-50 line-clamp-1">
|
||||
{{ user.user_name }}
|
||||
</span>
|
||||
</MenuButton>
|
||||
</div>
|
||||
<transition enter-active-class="transition duration-100 ease-out"
|
||||
enter-from-class="transform scale-95 opacity-0" enter-to-class="transform scale-100 opacity-100"
|
||||
leave-active-class="transition duration-75 ease-in"
|
||||
leave-from-class="transform scale-100 opacity-100" leave-to-class="transform scale-95 opacity-0">
|
||||
|
||||
<MenuItems
|
||||
class="absolute right-0 z-10 w-48 mt-2 overflow-hidden origin-top-right bg-white rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div class="flex-shrink-0 block px-4 py-2 border-b bg-primary-500 border-gray-50 group">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<!-- <img class="inline-block rounded-full h-9 w-9" :src="user.user_image" alt="" /> -->
|
||||
<PictureInitial class-name="nline-block" size-class="w-9 h-9"
|
||||
background-class="bg-secondary-100"
|
||||
font-class="text-xs font-normal font-semibold text-primary-500"
|
||||
:name="user.user_name" />
|
||||
</div>
|
||||
<div class="ml-3 space-y-1">
|
||||
<p class="text-sm font-medium text-primary-50 ">
|
||||
{{ user.user_name }}
|
||||
</p>
|
||||
<p class="text-xs font-normal text-primary-50">
|
||||
{{ user.user_access }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MenuItem v-slot="{ active }">
|
||||
<button @click="showDialogLogout"
|
||||
:class="[active ? 'bg-gray-100' : '', 'w-full text-left block px-4 py-3 text-base text-gray-800']">
|
||||
Log out
|
||||
</button>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
9
src/components/Navigation/Navigation.vue
Normal file
9
src/components/Navigation/Navigation.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import Aside from '@/components/Navigation/Aside/Aside.vue'
|
||||
import Header from '@/components/Navigation/Header.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<Aside />
|
||||
</template>
|
Reference in New Issue
Block a user