import { useEffect, useState, type ReactNode } from "react"; import { Link, useNavigate } from "react-router-dom"; import { useAuth } from "../App"; import { api } from "../lib/client"; import type { Workspace } from "../lib/types"; import { Book, ChevronDown, Columns, Inbox, Logo, Logout, TicketIcon, Users, X, } from "./icons"; export type Section = "tasks" | "board" | "tickets" | "knowledge" | "members"; const NAV: { key: Section; label: string; to: (id: string) => string; icon: ReactNode }[] = [ { key: "tasks", label: "任务", to: (id) => `/w/${id}`, icon: }, { key: "board", label: "看板", to: (id) => `/w/${id}/board`, icon: }, { key: "tickets", label: "工单", to: (id) => `/w/${id}/tickets`, icon: }, { key: "knowledge", label: "知识", to: (id) => `/w/${id}/knowledge`, icon: }, { key: "members", label: "成员", to: (id) => `/w/${id}/members`, icon: }, ]; function initial(name: string) { return name.trim().slice(0, 1).toUpperCase() || "W"; } const ROLE_TEXT: Record = { owner: "所有者", admin: "管理员", member: "成员", guest: "访客", }; export function AppShell({ wsId, active, children, }: { wsId: string; active: Section; children: ReactNode; }) { const { user, workspaces, signOut } = useAuth(); const navigate = useNavigate(); const [fetched, setFetched] = useState(null); const [unread, setUnread] = useState(0); const [switcher, setSwitcher] = useState(false); // 深链直接进来时 workspaces 还没加载好,单独取一次工作区信息 const ws = workspaces.find((w) => w.id === wsId) ?? fetched; useEffect(() => { if (ws || !wsId) return; let alive = true; api .get(`/workspaces/${wsId}`) .then((w) => { if (alive) setFetched(w); }) .catch(() => undefined); return () => { alive = false; }; }, [ws, wsId]); useEffect(() => { if (!wsId) return; let alive = true; const load = async () => { try { const r = await api.get<{ count: number }>(`/workspaces/${wsId}/notifications/unread-count`); if (alive) setUnread(r.count); } catch { /* 未读数失败不该影响主流程 */ } }; void load(); const timer = window.setInterval(load, 30_000); return () => { alive = false; window.clearInterval(timer); }; }, [wsId]); if (!ws) { return ( 正在打开工作区… ); } const phoneMasked = user?.phone ? user.phone.replace(/^(\d{3})\d{4}(\d{4})$/, "$1 **** $2") : ""; return ( {/* —— 侧栏(桌面)—— */}