siakad-itm/resources/js/components/app-sidebar.tsx

330 lines
10 KiB
TypeScript

import { usePage } from '@inertiajs/react';
import type { Icon } from '@tabler/icons-react';
import { IconGridDots, IconHelp, IconMessageDots } from '@tabler/icons-react';
import {
BookOpen,
Building2,
Calendar,
CalendarClock,
ClipboardCheck,
ClipboardList,
FileCheck2,
FileText,
GraduationCap,
Mail,
Megaphone,
MessageCircle,
Receipt,
School,
ScrollText,
ShieldCheck,
User,
Users,
} from 'lucide-react';
import * as React from 'react';
import AppLogoIcon from '@/components/app-logo-icon';
import type { NavGroup, NavItem } from '@/components/nav-main';
import { NavMain } from '@/components/nav-main';
import { NavSecondary } from '@/components/nav-secondary';
import {
Sidebar,
SidebarContent,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from '@/components/ui/sidebar';
import { index as assignmentsRoute } from '@/routes/admin/academic-classes/assignments';
import { index as attendancesRoute } from '@/routes/admin/academic-classes/attendances';
import { index as materialsRoute } from '@/routes/admin/academic-classes/materials';
import { index as schedulesRoute } from '@/routes/admin/academic-classes/schedules';
import { index as logsRoute } from '@/routes/admin/developer/logs';
import { index as rolePermissionsRoute } from '@/routes/admin/developer/roles';
import { index as feedbackRoute } from '@/routes/admin/feedback';
import { index as tuitionInvoicesRoute } from '@/routes/admin/finances/tuition-invoices';
import { index as announcementsRoute } from '@/routes/admin/manage/announcements';
import { index as courseClassesRoute } from '@/routes/admin/manage/course-classes';
import { index as courseRegistrationsRoute } from '@/routes/admin/manage/course-registrations';
import { index as academicTerm } from '@/routes/admin/master/academic-terms';
import { index as coursesRoute } from '@/routes/admin/master/courses';
import { index as departmentsRoute } from '@/routes/admin/master/departments';
import { index as academicAdvisingLogsRoute } from '@/routes/admin/services/academic-advising-logs';
import { index as letterRequestsRoute } from '@/routes/admin/services/letter-requests';
import { index as administratorsRoute } from '@/routes/admin/users/administrators';
import { index as lecturersRoute } from '@/routes/admin/users/lecturers';
import { index as studentsRoute } from '@/routes/admin/users/students';
import { index as studentCourseRegistrationsRoute } from '@/routes/student/course-registrations';
import type { Auth } from '@/types/auth';
const STAFF_ROLES = ['developer', 'staff-admin', 'staff-keuangan', 'kaprodi'];
function buildNavMain({
isMahasiswa,
permissions,
}: {
isMahasiswa: boolean;
permissions: string[];
}): (NavGroup | NavItem)[] {
const can = (permission: string) => permissions.includes(permission);
const penggunaItems: NavItem[] = [
...(can('view-administrators')
? [
{
name: 'Administrator',
url: administratorsRoute.url(),
icon: User,
},
]
: []),
...(can('view-lecturers')
? [{ name: 'Dosen', url: lecturersRoute.url(), icon: Users }]
: []),
...(can('view-students')
? [
{
name: 'Mahasiswa',
url: studentsRoute.url(),
icon: GraduationCap,
},
]
: []),
];
const masterItems: NavItem[] = [
...(can('view-academic-terms')
? [
{
name: 'Periode Akademik',
url: academicTerm.url(),
icon: Calendar,
},
]
: []),
...(can('view-departments')
? [
{
name: 'Jurusan',
url: departmentsRoute.url(),
icon: Building2,
},
]
: []),
...(can('view-courses')
? [{ name: 'Mata Kuliah', url: coursesRoute.url(), icon: BookOpen }]
: []),
];
const kelolaItems: NavItem[] = [
...(isMahasiswa || can('view-course-registrations')
? [
{
name: 'Registrasi KRS',
url: isMahasiswa
? studentCourseRegistrationsRoute.url()
: courseRegistrationsRoute.url(),
icon: FileCheck2,
},
]
: []),
...(can('view-course-classes')
? [
{
name: 'Kelas Mata Kuliah',
url: courseClassesRoute.url(),
icon: School,
},
]
: []),
...(can('view-announcements')
? [
{
name: 'Pengumuman',
url: announcementsRoute.url(),
icon: Megaphone,
},
]
: []),
];
const kelasItems: NavItem[] = [
...(can('view-materials')
? [{ name: 'Materi', url: materialsRoute.url(), icon: FileText }]
: []),
...(can('view-assignments')
? [
{
name: 'Tugas',
url: assignmentsRoute.url(),
icon: ClipboardList,
},
]
: []),
...(can('view-schedules')
? [
{
name: 'Jadwal',
url: schedulesRoute.url(),
icon: CalendarClock,
},
]
: []),
...(can('view-attendances')
? [
{
name: 'Kehadiran',
url: attendancesRoute.url(),
icon: ClipboardCheck,
},
]
: []),
];
const keuanganItems: NavItem[] = [
...(can('view-tuition-invoices')
? [
{
name: 'Tagihan',
url: tuitionInvoicesRoute.url(),
icon: Receipt,
},
]
: []),
];
const layananItems: NavItem[] = [
...(can('view-letter-requests')
? [
{
name: 'Surat Permohonan',
url: letterRequestsRoute.url(),
icon: Mail,
},
]
: []),
...(can('view-academic-advising-logs')
? [
{
name: 'Bimbingan Akademik',
url: academicAdvisingLogsRoute.url(),
icon: MessageCircle,
},
]
: []),
];
const pengembangItems: NavItem[] = [
...(can('view-logs')
? [{ name: 'Logs', url: logsRoute.url(), icon: ScrollText }]
: []),
...(can('view-roles')
? [
{
name: 'Role & Permission',
url: rolePermissionsRoute.url(),
icon: ShieldCheck,
},
]
: []),
];
const groups: (NavGroup | NavItem)[] = [
{
name: 'Dasbor',
url: '#',
icon: IconGridDots,
},
];
if (penggunaItems.length > 0) {
groups.push({ label: 'Pengguna', items: penggunaItems });
}
if (masterItems.length > 0) {
groups.push({ label: 'Master', items: masterItems });
}
if (kelolaItems.length > 0) {
groups.push({ label: 'Kelola', items: kelolaItems });
}
if (kelasItems.length > 0) {
groups.push({ label: 'Kelas', items: kelasItems });
}
if (keuanganItems.length > 0) {
groups.push({ label: 'Keuangan', items: keuanganItems });
}
if (layananItems.length > 0) {
groups.push({ label: 'Layanan', items: layananItems });
}
if (pengembangItems.length > 0) {
groups.push({ label: 'Pengembang', items: pengembangItems });
}
return groups;
}
function buildNavSecondary(
pendingFeedbackCount: number,
): { title: string; url: string; icon: Icon; badge?: number }[] {
return [
{
title: 'Kritik dan Saran',
url: feedbackRoute.url(),
icon: IconMessageDots,
badge: pendingFeedbackCount,
},
{
title: 'Bantuan',
url: '#',
icon: IconHelp,
},
];
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { name, auth, pendingFeedbackCount } = usePage<{ auth: Auth }>()
.props;
const roleNames = auth?.user?.roles?.map((role) => role.name) ?? [];
const isStaff = roleNames.some((role) => STAFF_ROLES.includes(role));
const isMahasiswa = !isStaff && roleNames.includes('mahasiswa');
const permissions = auth?.permissions ?? [];
const navMain = buildNavMain({
isMahasiswa,
permissions,
});
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton
asChild
className="data-[slot=sidebar-menu-button]:p-1.5!"
>
<a href="#">
<AppLogoIcon className="size-5! rounded-sm object-cover" />
<span className="text-base font-semibold">
{name}
</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<NavMain items={navMain} />
<NavSecondary
items={buildNavSecondary(pendingFeedbackCount ?? 0)}
className="mt-auto"
/>
</SidebarContent>
</Sidebar>
);
}