wepee's picture
fix: admin dashboard endpoint
45e7f4a
Raw
History Blame Contribute Delete
12.8 kB
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>
);
}