File size: 4,243 Bytes
dd92235 46cb663 dd92235 033de88 dd92235 7acdf04 9308228 046481b dd92235 033de88 dd92235 8f548b0 dd92235 046481b 9308228 046481b 46cb663 7acdf04 046481b 46cb663 dd92235 046481b 9308228 046481b dd92235 9308228 046481b dd92235 9308228 033de88 9308228 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | import { useState, useEffect, useCallback } from "react";
import { NavLink, Link, useNavigate } from "react-router-dom";
import { useWorkspaceContext } from "../../context/WorkspaceContext";
import { useAuth } from "../../context/AuthContext";
import { getDashboardStats } from "../../api/dashboard";
import { DocWeaveLogo } from "../DocWeaveLogo";
import "./Sidebar.css";
export function Sidebar() {
const [collapsed, setCollapsed] = useState(false);
const navigate = useNavigate();
const { workspace } = useWorkspaceContext();
const { user, logout } = useAuth();
const [counts, setCounts] = useState({});
const loadCounts = useCallback(async () => {
if (!workspace) return;
try {
const s = await getDashboardStats(workspace.id);
if (s && !s.detail) setCounts(s);
} catch { /* silent */ }
}, [workspace]);
useEffect(() => { loadCounts(); }, [loadCounts]);
// Refresh counts every 30s
useEffect(() => {
const id = setInterval(loadCounts, 30000);
return () => clearInterval(id);
}, [loadCounts]);
const NAV_ITEMS = [
{ to: "/", label: "Dashboard", icon: "π ", end: true },
{ to: "/documents", label: "Documents", icon: "π", count: counts.total_documents },
{ to: "/knowledge", label: "Knowledge", icon: "π§ ", count: counts.knowledge_items },
{ to: "/search", label: "Search", icon: "π" },
{ to: "/workflows", label: "Workflows", icon: "β‘", count: counts.workflows_waiting_for_review, countTone: "warning" },
{ to: "/activity", label: "Activity", icon: "π" },
{ to: "/settings", label: "Settings", icon: "β" },
];
return (
<aside className={`dw-sidebar ${collapsed ? "dw-sidebar--collapsed" : ""}`}>
<div className="dw-sidebar__top">
<Link to="/" className="dw-sidebar__brand">
<DocWeaveLogo size={collapsed ? 36 : 42} className="dw-sidebar__logo" />
{!collapsed && <span className="dw-sidebar__wordmark">DocWeave</span>}
</Link>
<button className="dw-sidebar__collapse-btn" onClick={() => setCollapsed((v) => !v)} title={collapsed ? "Expand" : "Collapse"}>
{collapsed ? "βΈ" : "β"}
</button>
</div>
<nav className="dw-sidebar__nav">
{NAV_ITEMS.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={item.end}
title={collapsed ? item.label : undefined}
className={({ isActive }) => `dw-sidebar__link ${isActive ? "dw-sidebar__link--active" : ""}`}
>
<span className="dw-sidebar__link-icon">{item.icon}</span>
{!collapsed && (
<>
<span className="dw-sidebar__link-label">{item.label}</span>
{item.count != null && item.count > 0 && (
<span className={`dw-sidebar__badge ${item.countTone === "warning" ? "dw-sidebar__badge--warning" : ""}`}>
{item.count}
</span>
)}
</>
)}
</NavLink>
))}
</nav>
<div className="dw-sidebar__footer">
<div className="dw-sidebar__user">
<span className="dw-sidebar__user-avatar">{(user?.username || "U")[0].toUpperCase()}</span>
{!collapsed && (
<span className="dw-sidebar__user-name">{user?.username || "User"}</span>
)}
</div>
<button
className="dw-sidebar__logout-btn"
title="Log out"
onClick={() => { logout(); navigate("/login"); }}
>
{collapsed ? "βͺ" : "Log out"}
</button>
</div>
</aside>
);
}
|