shak3008's picture
feat: render in-page interactive elements as Links to support right-click open in new tab
46cb663
Raw
History Blame Contribute Delete
4.24 kB
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>
);
}