feat: add MobileBottomNav component for improved navigation in admin layout
This commit is contained in:
parent
cc77aa92d6
commit
18ea643312
85
resources/js/components/MobileBottomNav.vue
Normal file
85
resources/js/components/MobileBottomNav.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { CalendarDays, CheckCircle, Clock, ShoppingCart } from '@lucide/vue';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import admin from '@/routes/admin';
|
||||
|
||||
const page = usePage();
|
||||
const { can } = useCan();
|
||||
|
||||
interface NavItem {
|
||||
title: string;
|
||||
href: string;
|
||||
icon: any;
|
||||
permission?: string;
|
||||
badgeKey?: 'pendingLeaveRequests' | 'pendingOwnerVerifications' | 'pendingCuttings';
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
title: 'Pesanan',
|
||||
href: admin.manage.orders.index.url(),
|
||||
icon: ShoppingCart,
|
||||
permission: 'orders.view',
|
||||
},
|
||||
{
|
||||
title: 'Verifikasi',
|
||||
href: admin.manage.owner_verifications.index.url(),
|
||||
icon: CheckCircle,
|
||||
permission: 'owner_verifications.view',
|
||||
badgeKey: 'pendingOwnerVerifications',
|
||||
},
|
||||
{
|
||||
title: 'Presensi',
|
||||
href: admin.hr.attendances.index.url(),
|
||||
icon: Clock,
|
||||
permission: 'attendances.view',
|
||||
},
|
||||
{
|
||||
title: 'Cuti',
|
||||
href: admin.hr.leave_requests.index.url(),
|
||||
icon: CalendarDays,
|
||||
permission: 'leave_requests.view',
|
||||
badgeKey: 'pendingLeaveRequests',
|
||||
},
|
||||
];
|
||||
|
||||
function isActive(href: string): boolean {
|
||||
return page.url.startsWith(href);
|
||||
}
|
||||
|
||||
function badgeCount(item: NavItem): number {
|
||||
if (!item.badgeKey) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (page.props as any)[item.badgeKey] ?? 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="pointer-events-none fixed inset-x-0 bottom-0 z-50 flex justify-center pb-4 lg:hidden">
|
||||
<div
|
||||
class="pointer-events-auto flex items-center gap-1 rounded-2xl border bg-background/95 px-2 py-2 shadow-lg backdrop-blur supports-[backdrop-filter]:bg-background/80">
|
||||
<template v-for="item in navItems" :key="item.title">
|
||||
<Link v-if="!item.permission || can(item.permission)" :href="item.href"
|
||||
class="relative flex flex-col items-center gap-0.5 rounded-xl px-3.5 py-1.5 text-[11px] font-medium text-muted-foreground transition-all duration-200"
|
||||
:class="{
|
||||
'text-primary scale-110 drop-shadow-md': isActive(item.href),
|
||||
'hover:text-foreground': !isActive(item.href),
|
||||
}">
|
||||
<div class="relative">
|
||||
<component :is="item.icon" class="h-5 w-5 transition-all duration-200" :class="{
|
||||
'stroke-[2.5px]': isActive(item.href),
|
||||
}" />
|
||||
<span v-if="badgeCount(item) > 0"
|
||||
class="absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-bold text-destructive-foreground">
|
||||
{{ badgeCount(item) }}
|
||||
</span>
|
||||
</div>
|
||||
<span>{{ item.title }}</span>
|
||||
</Link>
|
||||
</template>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
@ -7,6 +7,7 @@ import {
|
||||
SidebarProvider,
|
||||
SidebarTrigger,
|
||||
} from '@/components/ui/sidebar';
|
||||
import MobileBottomNav from '@/components/MobileBottomNav.vue';
|
||||
import UserNav from '@/components/UserNav.vue';
|
||||
|
||||
defineProps<{
|
||||
@ -30,9 +31,10 @@ const page = usePage();
|
||||
<UserNav />
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-4 p-4">
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-4 p-4 pb-24 lg:pb-4">
|
||||
<slot />
|
||||
</div>
|
||||
</SidebarInset>
|
||||
<MobileBottomNav />
|
||||
</SidebarProvider>
|
||||
</template>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user