import { Layout, Menu, Card, Tag, Button, Space, message } from "antd" import { UserOutlined, SettingOutlined, HistoryOutlined, UnorderedListOutlined, HomeOutlined, SyncOutlined, FileTextOutlined, CloudOutlined, UploadOutlined, MenuFoldOutlined, MenuUnfoldOutlined, } from "@ant-design/icons" import { useState, useEffect } from "react" import { useTranslation } from "react-i18next" import appLogo from "../assets/logoHD.svg" const { Sider } = Layout interface SidebarProps { activeMenu: string permission: "admin" | "points" | "view" onMenuChange: (value: string) => void } interface DbStatus { type: "sqlite" | "postgresql" connected: boolean error?: string } export function Sidebar({ activeMenu, permission, onMenuChange }: SidebarProps): React.JSX.Element { const { t } = useTranslation() const [collapsed, setCollapsed] = useState(false) const [dbStatus, setDbStatus] = useState({ type: "sqlite", connected: true }) const [syncLoading, setSyncLoading] = useState(false) const [messageApi, contextHolder] = message.useMessage() useEffect(() => { loadDbStatus() const handleStatusChange = () => { loadDbStatus() } const api = (window as any).api if (!api) return let disposed = false let unlisten: (() => void) | null = null api .onSettingChanged((change: { key: string; value: any }) => { if (change.key === "pg_connection_status") { handleStatusChange() } }) .then((fn: () => void) => { if (disposed) { fn() return } unlisten = fn }) .catch(() => void 0) return () => { disposed = true if (unlisten) unlisten() } }, []) const loadDbStatus = async () => { if (!(window as any).api) return try { const res = await (window as any).api.dbGetStatus() if (res.success && res.data) { setDbStatus(res.data) } } catch (e) { console.error("Failed to load database status:", e) } } const handleSync = async () => { if (!(window as any).api) return setSyncLoading(true) try { await loadDbStatus() } catch (e) { console.error("Failed to sync database status:", e) } finally { setSyncLoading(false) } } const [forceSyncLoading, setForceSyncLoading] = useState(false) const handleForceSync = async () => { if (!(window as any).api) return const statusRes = await (window as any).api.dbGetStatus() if (!statusRes.success || !statusRes.data) { messageApi.error(t("sidebar.getDbStatusFailed")) return } if (statusRes.data.type !== "postgresql") { messageApi.error(t("sidebar.notRemoteMode")) return } if (!statusRes.data.connected) { messageApi.error(t("sidebar.dbNotConnected")) return } setForceSyncLoading(true) try { const res = await (window as any).api.dbSync() if (res.success && res.data?.success) { messageApi.success(t("sidebar.syncSuccess")) } else { messageApi.error(res.data?.message || res.message || t("sidebar.syncFailed")) } } catch (e: any) { messageApi.error(e?.message || t("sidebar.syncFailed")) } finally { setForceSyncLoading(false) } } const menuItems = [ { key: "home", icon: , label: t("sidebar.home"), }, { key: "students", icon: , label: t("sidebar.students"), disabled: permission !== "admin", }, { key: "score", icon: , label: t("sidebar.score"), }, { key: "auto-score", icon: , label: t("sidebar.autoScore"), }, { key: "leaderboard", icon: , label: t("sidebar.leaderboard"), }, { key: "settlements", icon: , label: t("sidebar.settlements"), }, { key: "reasons", icon: , label: t("sidebar.reasons"), disabled: permission !== "admin", }, { key: "settings", icon: , label: t("sidebar.settings"), disabled: permission !== "admin", }, ] return (
onMenuChange(key)} style={{ width: "100%", border: "none", backgroundColor: "transparent", }} items={menuItems} />
{!collapsed && dbStatus.type === "postgresql" && ( {contextHolder}
{t("sidebar.remoteDb")} {dbStatus.connected ? t("settings.database.connected") : t("settings.database.disconnected")}
)}
) }