Add detectOS function to helper.ts and update style.css with new utility classes

This commit is contained in:
Dede Fuji Abdul
2024-04-06 22:17:19 +07:00
parent b27972b0b5
commit dd6da0f25a
6 changed files with 506 additions and 311 deletions

View File

@ -2206,6 +2206,12 @@ body {
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-1 > :not([hidden]) ~ :not([hidden]) { .space-y-1 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@ -2224,12 +2230,6 @@ body {
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
} }
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.divide-y > :not([hidden]) ~ :not([hidden]) { .divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0; --tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
@ -2475,6 +2475,10 @@ body {
border-bottom-width: 0px; border-bottom-width: 0px;
} }
.border-b-2 {
border-bottom-width: 2px;
}
.border-l-4 { .border-l-4 {
border-left-width: 4px; border-left-width: 4px;
} }
@ -2491,10 +2495,6 @@ body {
border-top-width: 0px; border-top-width: 0px;
} }
.border-b-2 {
border-bottom-width: 2px;
}
.border-solid { .border-solid {
border-style: solid; border-style: solid;
} }
@ -2582,6 +2582,11 @@ body {
border-color: rgb(255 51 51 / var(--tw-border-opacity)); border-color: rgb(255 51 51 / var(--tw-border-opacity));
} }
.border-secondary-500 {
--tw-border-opacity: 1;
border-color: rgb(0 162 185 / var(--tw-border-opacity));
}
.border-transparent { .border-transparent {
border-color: transparent; border-color: transparent;
} }
@ -2596,11 +2601,6 @@ body {
border-color: rgb(255 255 51 / var(--tw-border-opacity)); border-color: rgb(255 255 51 / var(--tw-border-opacity));
} }
.border-secondary-500 {
--tw-border-opacity: 1;
border-color: rgb(0 162 185 / var(--tw-border-opacity));
}
.bg-black { .bg-black {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity)); background-color: rgb(0 0 0 / var(--tw-bg-opacity));
@ -3715,6 +3715,16 @@ body {
color: rgb(102 102 0 / var(--tw-text-opacity)); color: rgb(102 102 0 / var(--tw-text-opacity));
} }
.text-slate-300 {
--tw-text-opacity: 1;
color: rgb(203 213 225 / var(--tw-text-opacity));
}
.text-slate-500 {
--tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity));
}
.text-opacity-40 { .text-opacity-40 {
--tw-text-opacity: 0.4; --tw-text-opacity: 0.4;
} }
@ -3723,6 +3733,10 @@ body {
text-decoration-line: underline; text-decoration-line: underline;
} }
.no-underline {
text-decoration-line: none;
}
.placeholder-gray-500::-moz-placeholder { .placeholder-gray-500::-moz-placeholder {
--tw-placeholder-opacity: 1; --tw-placeholder-opacity: 1;
color: rgb(107 114 128 / var(--tw-placeholder-opacity)); color: rgb(107 114 128 / var(--tw-placeholder-opacity));
@ -5252,6 +5266,11 @@ body {
color: rgb(51 51 0 / 0.9); color: rgb(51 51 0 / 0.9);
} }
:is(.dark .dark\:text-slate-500) {
--tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity));
}
:is(.dark .dark\:text-opacity-70) { :is(.dark .dark\:text-opacity-70) {
--tw-text-opacity: 0.7; --tw-text-opacity: 0.7;
} }

View File

@ -1,33 +1,54 @@
<template> <template>
<TransitionRoot :show="command.open" @after-leave="onQueryChange('')" appear> <TransitionRoot :show="command.open" @after-leave="onQueryChange('')" appear>
<Dialog as="div" class="relative z-10" @close="command.closeCommand"> <Dialog as="div" class="relative z-10" @close="command.closeCommand">
<TransitionChild enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" <TransitionChild
leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0"> enter="ease-out duration-300"
enter-from="opacity-0"
enter-to="opacity-100"
leave="ease-in duration-200"
leave-from="opacity-100"
leave-to="opacity-0"
>
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-25 backdrop-blur" /> <div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-25 backdrop-blur" />
</TransitionChild> </TransitionChild>
<div class="fixed inset-0 z-10 p-4 overflow-y-auto sm:p-6 md:p-20"> <div class="fixed inset-0 z-10 p-4 overflow-y-auto sm:p-6 md:p-20">
<TransitionChild enter="ease-out duration-300" enter-from="opacity-0 scale-95" <TransitionChild
enter-to="opacity-100 scale-100" leave="ease-in duration-200" leave-from="opacity-100 scale-100" enter="ease-out duration-300"
leave-to="opacity-0 scale-95"> enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="ease-in duration-200"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<!-- max-w-2xl mx-auto overflow-hidden transition-all transform bg-white bg-opacity-80 divide-y divide-gray-500 shadow-2xl divide-opacity-10 rounded-xl ring-0 ring-black ring-opacity-5 --> <!-- max-w-2xl mx-auto overflow-hidden transition-all transform bg-white bg-opacity-80 divide-y divide-gray-500 shadow-2xl divide-opacity-10 rounded-xl ring-0 ring-black ring-opacity-5 -->
<DialogPanel <DialogPanel
class="max-w-2xl mx-auto overflow-hidden transition-all transform bg-white bg-opacity-100 divide-y divide-gray-500 shadow-2xl divide-opacity-10 rounded-xl ring-0 ring-black ring-opacity-5"> class="max-w-2xl mx-auto overflow-hidden transition-all transform bg-white bg-opacity-100 divide-y divide-gray-500 shadow-2xl divide-opacity-10 rounded-xl ring-0 ring-black ring-opacity-5"
>
<Combobox> <Combobox>
<div class="relative"> <div class="relative">
<MagnifyingGlassIcon <MagnifyingGlassIcon
class="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40" class="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-900 text-opacity-40"
aria-hidden="true" /> aria-hidden="true"
<ComboboxInput :autofocus="false" />
<ComboboxInput
:autofocus="false"
class="w-full h-12 pr-4 text-gray-900 placeholder-gray-500 bg-white border-0 pl-11 focus:ring-0 sm:text-sm" class="w-full h-12 pr-4 text-gray-900 placeholder-gray-500 bg-white border-0 pl-11 focus:ring-0 sm:text-sm"
placeholder="Cari menu..." @change="onQueryChange($event.target.value)" /> placeholder="Cari menu..."
@change="onQueryChange($event.target.value)"
/>
</div> </div>
<ComboboxOptions v-if="query === '' || filteredMenus.length > 0" static <ComboboxOptions
class="overflow-y-auto divide-y divide-gray-500 scroll-py-2 divide-opacity-10"> v-if="query === '' || filteredMenus.length > 0"
static
class="overflow-y-auto divide-y divide-gray-500 scroll-py-2 divide-opacity-10"
>
<li class="p-2" v-if="filteredMenus.length > 0 || recent.length > 0"> <li class="p-2" v-if="filteredMenus.length > 0 || recent.length > 0">
<h2 v-if="query === '' && recent.length > 0" <h2
class="px-3 mt-4 mb-2 text-xs font-semibold text-gray-900"> v-if="query === '' && recent.length > 0"
class="px-3 mt-4 mb-2 text-xs font-semibold text-gray-900"
>
Terakhir Diakses Terakhir Diakses
</h2> </h2>
<!-- <ul class="text-sm text-gray-700"> <!-- <ul class="text-sm text-gray-700">
@ -78,65 +99,104 @@
</ComboboxOption> </ComboboxOption>
</ul> --> </ul> -->
<ul class="text-sm text-gray-700"> <ul class="text-sm text-gray-700">
<ComboboxOption as="template" v-for="menu in query === '' ? recent : filteredMenus" <ComboboxOption
:key="menu.path" v-slot="{ active }"> as="template"
v-for="menu in query === '' ? recent : filteredMenus"
:key="menu.path"
v-slot="{ active }"
>
<li @click="command.openMenu(menu)" class="group"> <li @click="command.openMenu(menu)" class="group">
<div class="flex flex-row items-center justify-between px-3 py-2 rounded-md cursor-pointer select-none lex group-hover:bg-primary-500 group-hover:text-white group-hover:bg-opacity-80"> <div
<component v-if="menu.path.includes('/gangguan/')" :is="navigationIcon[0]" class="flex flex-row items-center justify-between px-3 py-2 rounded-md cursor-pointer select-none lex group-hover:bg-primary-500 group-hover:text-white group-hover:bg-opacity-80"
>
<component
v-if="menu.path.includes('/gangguan/')"
:is="navigationIcon[0]"
alt="icon" alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
<component v-else-if="menu.path.includes('/keluhan/')" />
:is="navigationIcon[1]" alt="icon" <component
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> v-else-if="menu.path.includes('/keluhan/')"
<component v-else-if="menu.path.includes('/monalisa/')" :is="navigationIcon[1]"
:is="navigationIcon[2]" alt="icon" alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
<component v-else-if="menu.path.includes('/check-in-out/')" />
:is="navigationIcon[3]" alt="icon" <component
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> v-else-if="menu.path.includes('/monalisa/')"
<component v-else-if="menu.path.includes('/anomali-pengaduan/')" :is="navigationIcon[2]"
:is="navigationIcon[4]" alt="icon" alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
<component v-else-if="menu.path.includes('/ctt-kwh-periksa/')" />
:is="navigationIcon[5]" alt="icon" <component
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> v-else-if="menu.path.includes('/check-in-out/')"
<component v-else-if="menu.path.includes('/material/')" :is="navigationIcon[3]"
:is="navigationIcon[6]" alt="icon" alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
<component v-else-if="menu.path.includes('/transaksi/')" />
:is="navigationIcon[7]" alt="icon" <component
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> v-else-if="menu.path.includes('/anomali-pengaduan/')"
<component v-else :is="navigationIcon[8]" alt="icon" :is="navigationIcon[4]"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/ctt-kwh-periksa/')"
:is="navigationIcon[5]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/material/')"
:is="navigationIcon[6]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/transaksi/')"
:is="navigationIcon[7]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else
:is="navigationIcon[8]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<div class="flex flex-col flex-1 ml-3 space-y-1"> <div class="flex flex-col flex-1 ml-3 space-y-1">
<span <span
class="w-full text-sm font-medium text-gray-800 text-start group-hover:text-white line-clamp-1"> class="w-full text-sm font-medium text-gray-800 text-start group-hover:text-white line-clamp-1"
>
{{ menu.name }} {{ menu.name }}
</span> </span>
<span <span
class="w-full text-xs text-gray-500 text-start group-hover:text-white line-clamp-1"> class="w-full text-xs text-gray-500 text-start group-hover:text-white line-clamp-1"
>
{{ menu.path.replace('/home/', '') }} {{ menu.path.replace('/home/', '') }}
</span> </span>
</div> </div>
<span class="hidden ml-3 text-sm text-gray-500 group-hover:block group-hover:text-white"> <span
class="hidden ml-3 text-sm text-gray-500 group-hover:block group-hover:text-white"
>
Buka Buka
</span> </span>
</div> </div>
</li> </li>
</ComboboxOption> </ComboboxOption>
</ul> </ul>
</li> </li>
</ComboboxOptions> </ComboboxOptions>
<div v-if="query !== '' && filteredMenus.length === 0" class="px-6 text-center py-14 sm:px-14"> <div
v-if="query !== '' && filteredMenus.length === 0"
class="px-6 text-center py-14 sm:px-14"
>
<h2 class="font-semibold text-slate-900">Tidak ada hasil</h2> <h2 class="font-semibold text-slate-900">Tidak ada hasil</h2>
<p class="mt-2 text-sm leading-6 text-slate-600"> <p class="mt-2 text-sm leading-6 text-slate-600">
Kami tidak dapat menemukan menu apa pun dengan istilah itu saat ini, silahkan gunakan Kami tidak dapat menemukan menu apa pun dengan istilah itu saat ini, silahkan
kata kunci lainnya. gunakan kata kunci lainnya.
</p> </p>
</div> </div>
</Combobox> </Combobox>
@ -148,7 +208,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { MagnifyingGlassIcon } from '@heroicons/vue/20/solid' import { MagnifyingGlassIcon } from '@heroicons/vue/20/solid'
import { import {
Combobox, Combobox,
@ -158,25 +218,23 @@ import {
Dialog, Dialog,
DialogPanel, DialogPanel,
TransitionChild, TransitionChild,
TransitionRoot, TransitionRoot
} from '@headlessui/vue' } from '@headlessui/vue'
import { routes } from '@/router' import { routes } from '@/router'
import { useCommandPalattesStore } from '@/stores/command' import { useCommandPalattesStore } from '@/stores/command'
import { navigationIcon } from '@/utils/route' import { navigationIcon } from '@/utils/route'
const command = useCommandPalattesStore() const command = useCommandPalattesStore()
const recent = computed(() => command.open ? command.readRecent() : []) const recent = computed(() => (command.open ? command.readRecent() : []))
const query = ref('') const query = ref('')
const filteredMenus = computed(() => const filteredMenus = computed(() =>
query.value === '' query.value === '' ? [] : command.searchRoutesByName(routes, query.value)
? []
: command.searchRoutesByName(routes, query.value)
) )
let debounceTimeout: ReturnType<typeof setTimeout> | null = null; let debounceTimeout: ReturnType<typeof setTimeout> | null = null
const onQueryChange = (value: string) => { const onQueryChange = (value: string) => {
if (debounceTimeout) { if (debounceTimeout) {
clearTimeout(debounceTimeout); clearTimeout(debounceTimeout)
} }
debounceTimeout = setTimeout(() => { debounceTimeout = setTimeout(() => {
// check if value is empty or only spaces // check if value is empty or only spaces
@ -185,7 +243,10 @@ const onQueryChange = (value: string) => {
return return
} }
query.value = value query.value = value
}, 300); }, 300)
} }
onMounted(() => {
window.addEventListener('keydown', (e) => command.onKeyPressed(e))
})
</script> </script>

View File

@ -9,12 +9,15 @@ import PictureInitial from '@/components/PictureInitial.vue'
import { useDialogStore } from '@/stores/dialog' import { useDialogStore } from '@/stores/dialog'
import { IconApp, IconBars3 } from '@/utils/icons' import { IconApp, IconBars3 } from '@/utils/icons'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { ref } from 'vue'
import { detectOS } from '@/utils/helper'
const auth = useAuthStore() const auth = useAuthStore()
const user = useUserStore() const user = useUserStore()
const command = useCommandPalattesStore() const command = useCommandPalattesStore()
const menu = useMenuStore() const menu = useMenuStore()
const dialog = useDialogStore() const dialog = useDialogStore()
const os = ref(detectOS())
const openSideBar = () => menu.toggleSidebar() const openSideBar = () => menu.toggleSidebar()
@ -35,7 +38,11 @@ const showDialogLogout = () => {
<template> <template>
<div class="sticky top-0 z-10 flex flex-shrink-0 h-16 bg-primary-50 lg:ml-80"> <div class="sticky top-0 z-10 flex flex-shrink-0 h-16 bg-primary-50 lg:ml-80">
<button type="button" class="px-4 text-gray-500 focus:outline-none focus:ring-0 lg:hidden" @click="openSideBar"> <button
type="button"
class="px-4 text-gray-500 focus:outline-none focus:ring-0 lg:hidden"
@click="openSideBar"
>
<span class="sr-only">Open sidebar</span> <span class="sr-only">Open sidebar</span>
<IconBars3 class="w-6 h-6 fill-gray-600" /> <IconBars3 class="w-6 h-6 fill-gray-600" />
</button> </button>
@ -44,36 +51,57 @@ const showDialogLogout = () => {
</RouterLink> </RouterLink>
<div class="flex justify-end w-full px-4"> <div class="flex justify-end w-full px-4">
<div class="flex items-center ml-4 md:ml-6"> <div class="flex items-center ml-4 md:ml-6">
<button @click="command.showCommandPalettes" type="button" <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"> @click="command.showCommandPalettes"
type="button"
class="justify-between 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> <span class="sr-only">Search</span>
<MagnifyingGlassIcon class="w-6 h-6 text-primary-500" aria-hidden="true" /> <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> <span
class="flex-1 hidden px-3 text-sm font-medium text-left text-gray-500 md:block text-md"
>Cari menu</span
>
<button class="hidden mx-2 md:block" v-if="os == 'macOS'"> M</button>
</button> </button>
<!-- Profile dropdown --> <!-- Profile dropdown -->
<Menu as="div" class="relative ml-1"> <Menu as="div" class="relative ml-1">
<div> <div>
<MenuButton <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"> 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> <span class="sr-only">Open user menu</span>
<PictureInitial size-class="w-8 h-8" background-class="bg-secondary-100" <PictureInitial
font-class="text-xs font-bold text-primary-500" :name="user.user_name" /> 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"> <span class="hidden px-3 text-xs font-medium md:block text-primary-50 line-clamp-1">
{{ user.user_name }} {{ user.user_name }}
</span> </span>
</MenuButton> </MenuButton>
</div> </div>
<transition enter-active-class="transition duration-100 ease-out" <transition
enter-from-class="transform scale-95 opacity-0" enter-to-class="transform scale-100 opacity-100" enter-active-class="transition duration-100 ease-out"
leave-active-class="transition duration-75 ease-in" leave-from-class="transform scale-100 opacity-100" enter-from-class="transform scale-95 opacity-0"
leave-to-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="container-menu-item"> <MenuItems class="container-menu-item">
<div class="container-menu-profile group"> <div class="container-menu-profile group">
<div class="flex items-center"> <div class="flex items-center">
<div> <div>
<PictureInitial class-name="inline-block" size-class="w-9 h-9" background-class="bg-secondary-100" <PictureInitial
font-class="text-xs font-normal font-semibold text-primary-500" :name="user.user_name" /> class-name="inline-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>
<div class="ml-3 space-y-1"> <div class="ml-3 space-y-1">
<p class="text-sm font-medium text-primary-50"> <p class="text-sm font-medium text-primary-50">
@ -89,7 +117,10 @@ const showDialogLogout = () => {
</div> </div>
</div> </div>
<MenuItem v-slot="{ active }"> <MenuItem v-slot="{ active }">
<button @click="showDialogLogout" :class="[active ? 'menu-item-active' : 'menu-item']"> <button
@click="showDialogLogout"
:class="[active ? 'menu-item-active' : 'menu-item']"
>
Log out Log out
</button> </button>
</MenuItem> </MenuItem>

View File

@ -1,12 +1,20 @@
<template> <template>
<div class="flex flex-col items-center justify-between px-4 text-center whitespace-pre-wrap h-[calc(90vh-24px)]"> <div
class="flex flex-col items-center justify-between px-4 text-center whitespace-pre-wrap h-[calc(90vh-24px)]"
>
<div class="w-full flex-1 md:w-[440px] justify-center py-4 md:py-0 flex flex-col items-center"> <div class="w-full flex-1 md:w-[440px] justify-center py-4 md:py-0 flex flex-col items-center">
<img :src="IconApp" alt="pln" class="h-[66px] object-contain mx-auto mb-4"> <img :src="IconApp" alt="pln" class="h-[66px] object-contain mx-auto mb-4" />
<button @click="command.showCommandPalettes" type="button" <button
class="flex flex-row items-center w-full p-2 text-gray-400 rounded-full bg-gray-50 hover:text-primary-500 focus:outline-none focus:ring-0"> @click="command.showCommandPalettes"
type="button"
class="flex flex-row items-center justify-between w-full p-2 text-gray-400 rounded-full bg-gray-50 hover:text-primary-500 focus:outline-none focus:ring-0"
>
<span class="sr-only">Search</span> <span class="sr-only">Search</span>
<MagnifyingGlassIcon class="w-6 h-6 text-primary-500" aria-hidden="true" /> <MagnifyingGlassIcon class="w-6 h-6 text-primary-500" aria-hidden="true" />
<span class="px-3 text-sm font-medium text-gray-500 text-md">Cari menu</span> <span class="flex-1 px-3 text-sm font-medium text-left text-gray-500 text-md">
Cari menu</span
>
<button class="mx-2" v-if="os == 'macOS'"> M</button>
</button> </button>
<div class="flex mt-3.5" v-if="filteredMenus.length === 0 && recent.length === 0"> <div class="flex mt-3.5" v-if="filteredMenus.length === 0 && recent.length === 0">
@ -15,51 +23,103 @@
</h1> </h1>
</div> </div>
<div v-if="query === '' || filteredMenus.length > 0" static class="w-full mt-6 md:mt-8 lg:mt-12"> <div
<div class="flex flex-col items-start w-full p-2" v-if="filteredMenus.length > 0 || recent.length > 0"> v-if="query === '' || filteredMenus.length > 0"
<h2 v-if="query === '' && recent.length > 0" class="mb-2 text-xs font-semibold text-gray-900 "> static
class="w-full mt-6 md:mt-8 lg:mt-12"
>
<div
class="flex flex-col items-start w-full p-2"
v-if="filteredMenus.length > 0 || recent.length > 0"
>
<h2
v-if="query === '' && recent.length > 0"
class="mb-2 text-xs font-semibold text-gray-900"
>
Terakhir Diakses Terakhir Diakses
</h2> </h2>
<div class="flex flex-col w-full space-y-3" v-for="menu in query === '' ? recent : filteredMenus" <div
:key="menu.path"> class="flex flex-col w-full space-y-3"
v-for="menu in query === '' ? recent : filteredMenus"
:key="menu.path"
>
<div @click="command.openMenu(menu)" class="group"> <div @click="command.openMenu(menu)" class="group">
<div <div
class="flex flex-row items-center justify-between px-3 py-2 rounded-md cursor-pointer select-none group-hover:bg-primary-500 group-hover:text-white group-hover:bg-opacity-80"> class="flex flex-row items-center justify-between px-3 py-2 rounded-md cursor-pointer select-none group-hover:bg-primary-500 group-hover:text-white group-hover:bg-opacity-80"
<component v-if="menu.path.includes('/gangguan/')" :is="navigationIcon[0]" alt="icon" >
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> <component
<component v-else-if="menu.path.includes('/keluhan/')" :is="navigationIcon[1]" alt="icon" v-if="menu.path.includes('/gangguan/')"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :is="navigationIcon[0]"
<component v-else-if="menu.path.includes('/monalisa/')" :is="navigationIcon[2]" alt="icon" alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
<component v-else-if="menu.path.includes('/check-in-out/')" :is="navigationIcon[3]" />
alt="icon" :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> <component
<component v-else-if="menu.path.includes('/anomali-pengaduan/')" :is="navigationIcon[4]" v-else-if="menu.path.includes('/keluhan/')"
alt="icon" :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :is="navigationIcon[1]"
<component v-else-if="menu.path.includes('/ctt-kwh-periksa/')" :is="navigationIcon[5]" alt="icon"
alt="icon" :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
<component v-else-if="menu.path.includes('/material/')" :is="navigationIcon[6]" alt="icon" />
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> <component
<component v-else-if="menu.path.includes('/transaksi/')" :is="navigationIcon[7]" alt="icon" v-else-if="menu.path.includes('/monalisa/')"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :is="navigationIcon[2]"
<component v-else :is="navigationIcon[8]" alt="icon" alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']" /> :class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/check-in-out/')"
:is="navigationIcon[3]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/anomali-pengaduan/')"
:is="navigationIcon[4]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/ctt-kwh-periksa/')"
:is="navigationIcon[5]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/material/')"
:is="navigationIcon[6]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else-if="menu.path.includes('/transaksi/')"
:is="navigationIcon[7]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<component
v-else
:is="navigationIcon[8]"
alt="icon"
:class="['w-8 h-8 fill-gray-600 group-hover:fill-white flex']"
/>
<div class="flex flex-col items-start flex-1 w-full pl-3 space-y-1"> <div class="flex flex-col items-start flex-1 w-full pl-3 space-y-1">
<span <span
class="w-full text-sm font-medium text-gray-800 text-start group-hover:text-white line-clamp-1"> class="w-full text-sm font-medium text-gray-800 text-start group-hover:text-white line-clamp-1"
>
{{ menu.name }} {{ menu.name }}
</span> </span>
<span <span
class="w-full text-xs text-gray-500 text-start group-hover:text-white line-clamp-1"> class="w-full text-xs text-gray-500 text-start group-hover:text-white line-clamp-1"
>
{{ menu.path.replace('/home/', '') }} {{ menu.path.replace('/home/', '') }}
</span> </span>
</div> </div>
<span class="hidden ml-3 text-sm text-gray-500 group-hover:block group-hover:text-white"> <span
class="hidden ml-3 text-sm text-gray-500 group-hover:block group-hover:text-white"
>
Buka Buka
</span> </span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@ -74,27 +134,30 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { MagnifyingGlassIcon } from '@heroicons/vue/20/solid'; import { MagnifyingGlassIcon } from '@heroicons/vue/20/solid'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useCommandPalattesStore } from '@/stores/command' import { useCommandPalattesStore } from '@/stores/command'
import { IconApp } from '@/utils/icons' import { IconApp } from '@/utils/icons'
import { type RouteRecordRaw } from 'vue-router' import { type RouteRecordRaw } from 'vue-router'
import { routes, extractLeafRoutes } from '@/router' import { routes, extractLeafRoutes } from '@/router'
import { navigationIcon } from '@/utils/route'; import { navigationIcon } from '@/utils/route'
import { detectOS } from '@/utils/helper'
const os = ref(detectOS())
const command = useCommandPalattesStore() const command = useCommandPalattesStore()
const recent = computed(() => query.value === '' ? command.readRecent() : []) const recent = computed(() => (query.value === '' ? command.readRecent() : []))
const query = ref('') const query = ref('')
const searchRoutesByName = (routes: RouteRecordRaw[], query: string): RouteRecordRaw[] => { const searchRoutesByName = (routes: RouteRecordRaw[], query: string): RouteRecordRaw[] => {
const matchingRoutes = extractLeafRoutes(routes, '').filter((item: RouteRecordRaw) => item.path.includes('home/') && item.name?.toString().toLocaleLowerCase().includes(query.toLocaleLowerCase())) const matchingRoutes = extractLeafRoutes(routes, '').filter(
(item: RouteRecordRaw) =>
item.path.includes('home/') &&
item.name?.toString().toLocaleLowerCase().includes(query.toLocaleLowerCase())
)
return matchingRoutes return matchingRoutes
} }
const filteredMenus = computed(() => const filteredMenus = computed(() =>
query.value === '' query.value === '' ? [] : searchRoutesByName(routes, query.value)
? []
: searchRoutesByName(routes, query.value)
) )
</script> </script>

View File

@ -17,29 +17,30 @@ export const useCommandPalattesStore = defineStore('command_palettes', () => {
} }
const onKeyPressed = (event: KeyboardEvent) => { const onKeyPressed = (event: KeyboardEvent) => {
if (event.key === 'Control') { if ((event.metaKey || event.ctrlKey) && event.key === 'm') {
console.log('control pressed');
controlStatus.value = true
}
if (event.key === 'f') {
console.log('f pressed');
keyFStatus.value = true
}
if (controlStatus.value && keyFStatus.value) {
showCommandPalettes() showCommandPalettes()
// console.log('control pressed')
// controlStatus.value = true
} }
// if (event.key === 'f') {
// console.log('f pressed');
// keyFStatus.value = true
// }
// if (controlStatus.value && keyFStatus.value) {
// showCommandPalettes()
// }
} }
const onKeyUp = (event: KeyboardEvent) => { const onKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Control') { if (event.key === 'Control') {
console.log('control released'); console.log('control released')
controlStatus.value = false controlStatus.value = false
} }
if (event.key === 'f') { if (event.key === 'f') {
console.log('f released'); console.log('f released')
keyFStatus.value = false keyFStatus.value = false
} }
} }
@ -71,12 +72,18 @@ export const useCommandPalattesStore = defineStore('command_palettes', () => {
} }
const searchRoutesByName = (routes: RouteRecordRaw[], query: string): RouteRecordRaw[] => { const searchRoutesByName = (routes: RouteRecordRaw[], query: string): RouteRecordRaw[] => {
const matchingRoutes = extractLeafRoutes(routes, '').filter((item: RouteRecordRaw) => item.path.includes('home/') && item.name?.toString().toLocaleLowerCase().includes(query.toLocaleLowerCase())) const matchingRoutes = extractLeafRoutes(routes, '').filter(
(item: RouteRecordRaw) =>
item.path.includes('home/') &&
item.name?.toString().toLocaleLowerCase().includes(query.toLocaleLowerCase())
)
return matchingRoutes return matchingRoutes
} }
const searchRoutesPath = (routes: RouteRecordRaw[], query: string): RouteRecordRaw[] => { const searchRoutesPath = (routes: RouteRecordRaw[], query: string): RouteRecordRaw[] => {
const matchingRoutes = extractLeafRoutes(routes, '').filter((item: RouteRecordRaw) => item.path.includes('home/') && item.path === query) const matchingRoutes = extractLeafRoutes(routes, '').filter(
(item: RouteRecordRaw) => item.path.includes('home/') && item.path === query
)
return matchingRoutes return matchingRoutes
} }
@ -86,7 +93,7 @@ export const useCommandPalattesStore = defineStore('command_palettes', () => {
closeCommand() closeCommand()
setTimeout(() => { setTimeout(() => {
menu.expandCurrentActiveMenu() menu.expandCurrentActiveMenu()
}, 200); }, 200)
} }
const closeCommand = () => { const closeCommand = () => {

View File

@ -37,3 +37,17 @@ export const getDataRowGroup = (data: any): any => {
} }
} }
} }
export const detectOS = () => {
const platform = navigator.userAgent.toLowerCase()
if (platform.includes('mac')) {
return 'macOS'
} else if (platform.includes('win')) {
return 'Windows'
} else if (platform.includes('linux')) {
return 'Linux'
} else {
return 'Unknown'
}
}