| import { router } from '@inertiajs/react'; |
| import { |
| BrainCircuit, |
| Database, |
| FileSliders, |
| KeyRound, |
| LayoutDashboard, |
| LogOut, |
| MessageSquare, |
| Users, |
| } from 'lucide-react'; |
| import type { CSSProperties, ReactNode } from 'react'; |
| import { useState, useSyncExternalStore } from 'react'; |
|
|
| import { Avatar, AvatarFallback } from '@/components/ui/avatar'; |
| import { Button } from '@/components/ui/button'; |
| import AppearanceToggle from '@/components/appearance-toggle'; |
| import { |
| Dialog, |
| DialogContent, |
| DialogDescription, |
| DialogFooter, |
| DialogHeader, |
| DialogTitle, |
| } from '@/components/ui/dialog'; |
| import { |
| Sidebar, |
| SidebarContent, |
| SidebarFooter, |
| SidebarGroup, |
| SidebarGroupContent, |
| SidebarHeader, |
| SidebarInset, |
| SidebarMenu, |
| SidebarMenuButton, |
| SidebarMenuItem, |
| SidebarProvider, |
| SidebarRail, |
| SidebarTrigger, |
| useSidebar, |
| } from '@/components/ui/sidebar'; |
| import { clearAuthSession, getStoredAuthUser } from '@/lib/auth'; |
| import { |
| dashboard as adminDashboard, |
| llmConfig as adminLlmConfig, |
| login as adminLogin, |
| retrievalConfig as adminRetrievalConfig, |
| vectordbConfig as adminVectorDbConfig, |
| } from '@/routes/admin'; |
| import { index as adminApiKeys } from '@/routes/admin/api-keys'; |
| import { index as adminUsers } from '@/routes/admin/users'; |
|
|
| export type AdminMenu = |
| | 'api-keys' |
| | 'dashboard' |
| | 'llm-config' |
| | 'rag-config' |
| | 'retrieval-config' |
| | 'settings' |
| | 'users' |
| | 'vector-retrieval-config' |
| | 'vectordb-config'; |
|
|
| const adminSidebarStyle = { |
| '--sidebar-width': '260px', |
| '--sidebar-width-icon': '64px', |
| } as CSSProperties; |
|
|
| function getStoredAdminName(): string { |
| return getStoredAuthUser()?.name ?? 'Admin'; |
| } |
|
|
| function subscribeToAuthStorage(onStoreChange: () => void): () => void { |
| if (typeof window === 'undefined') { |
| return () => {}; |
| } |
|
|
| window.addEventListener('storage', onStoreChange); |
|
|
| return () => window.removeEventListener('storage', onStoreChange); |
| } |
|
|
| function useStoredAdminName(): string { |
| return useSyncExternalStore( |
| subscribeToAuthStorage, |
| getStoredAdminName, |
| () => 'Admin', |
| ); |
| } |
|
|
| function getInitials(name: string): string { |
| return name |
| .split(' ') |
| .filter(Boolean) |
| .slice(0, 2) |
| .map((part) => part[0]?.toUpperCase()) |
| .join(''); |
| } |
|
|
| function confirmLogout(): void { |
| clearAuthSession(); |
| router.visit(adminLogin.url()); |
| } |
|
|
| function AdminSidebarBrand() { |
| const { state, toggleSidebar } = useSidebar(); |
|
|
| const handleBrandClick = (): void => { |
| if (state === 'collapsed') { |
| toggleSidebar(); |
| } |
| }; |
|
|
| return ( |
| <button |
| aria-label="Buka sidebar" |
| className="lecturer-brand" |
| onClick={handleBrandClick} |
| type="button" |
| > |
| <span className="lecturer-brand-mark"> |
| <MessageSquare className="size-4" /> |
| </span> |
| <span className="lecturer-brand-copy">RAG Hub</span> |
| </button> |
| ); |
| } |
|
|
| function AdminSidebar({ activeMenu }: { activeMenu: AdminMenu }) { |
| const adminName = useStoredAdminName(); |
| const [isLogoutDialogOpen, setIsLogoutDialogOpen] = useState(false); |
|
|
| return ( |
| <> |
| <Sidebar className="lecturer-sidebar" collapsible="icon"> |
| <SidebarHeader className="lecturer-sidebar-header"> |
| <AdminSidebarBrand /> |
| |
| <SidebarTrigger className="lecturer-sidebar-trigger" /> |
| </SidebarHeader> |
| |
| <SidebarContent className="lecturer-sidebar-content"> |
| <SidebarGroup className="lecturer-nav-group"> |
| <SidebarGroupContent> |
| <SidebarMenu className="lecturer-nav-list"> |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-nav-button" |
| isActive={activeMenu === 'dashboard'} |
| onClick={() => |
| router.visit(adminDashboard()) |
| } |
| tooltip="Dashboard" |
| > |
| <LayoutDashboard className="size-4" /> |
| <span>Dashboard</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-nav-button" |
| isActive={activeMenu === 'llm-config'} |
| onClick={() => |
| router.visit(adminLlmConfig()) |
| } |
| tooltip="LLM Config" |
| > |
| <BrainCircuit className="size-4" /> |
| <span>LLM Config</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-nav-button" |
| isActive={ |
| activeMenu === 'vectordb-config' |
| } |
| onClick={() => |
| router.visit(adminVectorDbConfig()) |
| } |
| tooltip="VectorDB Config" |
| > |
| <Database className="size-4" /> |
| <span>VectorDB Config</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-nav-button" |
| isActive={ |
| activeMenu === 'retrieval-config' |
| } |
| onClick={() => |
| router.visit(adminRetrievalConfig()) |
| } |
| tooltip="Retrieval Config" |
| > |
| <FileSliders className="size-4" /> |
| <span>Retrieval Config</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-nav-button" |
| isActive={activeMenu === 'api-keys'} |
| onClick={() => |
| router.visit(adminApiKeys()) |
| } |
| tooltip="Generate API Key" |
| > |
| <KeyRound className="size-4" /> |
| <span>Generate API Key</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-nav-button" |
| isActive={activeMenu === 'users'} |
| onClick={() => |
| router.visit(adminUsers()) |
| } |
| tooltip="User Management" |
| > |
| <Users className="size-4" /> |
| <span>User Management</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| </SidebarMenu> |
| </SidebarGroupContent> |
| </SidebarGroup> |
| </SidebarContent> |
| |
| <SidebarFooter className="lecturer-sidebar-footer"> |
| <SidebarMenu> |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-user-button" |
| tooltip={adminName} |
| > |
| <Avatar className="lecturer-user-avatar"> |
| <AvatarFallback> |
| {getInitials(adminName) || 'AD'} |
| </AvatarFallback> |
| </Avatar> |
| <span className="lecturer-user-copy"> |
| <span className="lecturer-user-name"> |
| {adminName} |
| </span> |
| <span className="lecturer-user-role"> |
| Admin |
| </span> |
| </span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| |
| <SidebarMenuItem> |
| <SidebarMenuButton |
| className="lecturer-logout-button" |
| onClick={() => setIsLogoutDialogOpen(true)} |
| tooltip="Logout" |
| > |
| <LogOut className="size-4" /> |
| <span>Logout</span> |
| </SidebarMenuButton> |
| </SidebarMenuItem> |
| </SidebarMenu> |
| </SidebarFooter> |
| |
| <SidebarRail /> |
| </Sidebar> |
| |
| <Dialog |
| onOpenChange={setIsLogoutDialogOpen} |
| open={isLogoutDialogOpen} |
| > |
| <DialogContent className="lecturer-confirm-dialog"> |
| <DialogHeader> |
| <DialogTitle>Keluar dari akun admin?</DialogTitle> |
| <DialogDescription> |
| Anda perlu login kembali untuk mengakses dashboard |
| admin. |
| </DialogDescription> |
| </DialogHeader> |
| |
| <DialogFooter className="lecturer-confirm-dialog-footer"> |
| <Button |
| className="lecturer-cancel-button" |
| onClick={() => setIsLogoutDialogOpen(false)} |
| type="button" |
| variant="outline" |
| > |
| Batal |
| </Button> |
| <Button |
| className="lecturer-danger-button" |
| onClick={confirmLogout} |
| type="button" |
| > |
| Logout |
| </Button> |
| </DialogFooter> |
| </DialogContent> |
| </Dialog> |
| </> |
| ); |
| } |
|
|
| export function AdminShell({ |
| activeMenu = 'dashboard', |
| children, |
| }: { |
| activeMenu?: AdminMenu; |
| children: ReactNode; |
| }) { |
| return ( |
| <SidebarProvider |
| className="lecturer-dashboard-shell" |
| defaultOpen |
| style={adminSidebarStyle} |
| > |
| <AdminSidebar activeMenu={activeMenu} /> |
| |
| <SidebarInset className="lecturer-workspace"> |
| <div className="flex items-center justify-between px-3 py-2 md:hidden"> |
| <SidebarTrigger /> |
| <AppearanceToggle className="lecturer-theme-toggle" /> |
| </div> |
| {children} |
| </SidebarInset> |
| </SidebarProvider> |
| ); |
| } |
|
|
|
|
|
|
|
|