import { Activity, ArrowRightLeft, Bell, Bot, CheckCircle2, ChevronDown, Columns2, FileText, Keyboard, LogOut, Map, RefreshCw, ShieldCheck, X, UserRound, type LucideIcon } from "lucide-react"; import { useRef, useState, type ReactNode } from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { ProjectSwitchDialog } from "./project-switch-dialog"; import { MAP_COMPACT_RADIUS_CLASS_NAME, MAP_ICON_CELL_RADIUS_CLASS_NAME, MAP_MAJOR_PANEL_RADIUS_CLASS_NAME, MAP_READABLE_RADIUS_CLASS_NAME } from "@/features/map/core"; import { cn } from "@/shared/ui/cn"; import { StatusBadge, StatusDot, StatusIcon as SemanticStatusIcon, type StatusTone } from "@/shared/ui/status"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/shared/ui/dropdown-menu"; import type { WorkbenchProject } from "../api/project-client"; import type { WorkbenchAlert, WorkbenchUser } from "../types"; import { compactHeaderControlButtonClassName, compactHeaderTextClassName, headerControlButtonClassName, headerControlIconClassName } from "./workbench-top-bar-styles"; const menuSurfaceClassName = cn( MAP_MAJOR_PANEL_RADIUS_CLASS_NAME, "acrylic-panel border p-2 text-slate-900" ); const menuReadableRowClassName = "surface-well focus:bg-[var(--action-soft)] focus:text-slate-950"; const alertActionClassName = cn( MAP_COMPACT_RADIUS_CLASS_NAME, "h-10 w-10 shrink-0 justify-center border border-transparent bg-transparent p-0 text-[var(--ink-muted)] transition-colors hover:border-[var(--action-blue)] hover:bg-[var(--action-blue)] hover:text-white focus:border-[var(--action-blue)] focus:bg-[var(--action-blue)] focus:text-white data-[highlighted]:border-[var(--action-blue)] data-[highlighted]:bg-[var(--action-blue)] data-[highlighted]:text-white active:scale-95" ); export function AnalysisViewMenu({ open, onOpenChange, title, subtitle, sourceLabel, splitAvailable, hasSpatialView, onShowResult, onShowMapSplit, onShowMapOnly, onClose }: { open: boolean; onOpenChange: (open: boolean) => void; title: string; subtitle: string; sourceLabel: string; splitAvailable: boolean; hasSpatialView: boolean; onShowResult: () => void; onShowMapSplit: () => void; onShowMapOnly: () => void; onClose: () => void; }) { return ( 查看方式 {splitAvailable ? ( ) : null} {hasSpatialView ? ( ) : null} ); } export function AlertMenu({ open, onOpenChange, alerts, sourceLabel, compact = false, conditionFeedVisible, onSelectAlert, onRemoveAlert, onToggleConditionFeed, onAnalyzeAlertsWithAgent }: { open: boolean; onOpenChange: (open: boolean) => void; alerts: WorkbenchAlert[]; sourceLabel: string; compact?: boolean; conditionFeedVisible: boolean; onSelectAlert: (alert: WorkbenchAlert) => void; onRemoveAlert?: (alert: WorkbenchAlert) => void; onToggleConditionFeed: () => void; onAnalyzeAlertsWithAgent: () => void | Promise; }) { const reduceMotion = useReducedMotion(); const keyboardInteraction = useRef(false); const skipRemovalMotion = reduceMotion === true || keyboardInteraction.current; const hasAlerts = alerts.length > 0; const hasCriticalAlerts = alerts.some((alert) => alert.severity !== "warning"); const alertSummaryTone = hasCriticalAlerts ? "danger" : hasAlerts ? "warning" : "neutral"; const latestAlertTime = alerts[0]?.time; return ( { keyboardInteraction.current = true; }} onPointerDownCapture={() => { keyboardInteraction.current = false; }} > 待处理事项
{hasAlerts ? alerts.map((alert) => { const tone = alert.severity === "warning" ? "warning" : "danger"; return ( ({ opacity: 0, x: skip ? 0 : 12, pointerEvents: "none", transition: { duration: skip ? 0 : 0.18, ease: [0.4, 0, 1, 1] } }) }} transition={{ duration: skipRemovalMotion ? 0 : 0.18, ease: [0.4, 0, 1, 1] }} className={cn( tone === "warning" ? "status-tone-warning" : "status-tone-danger", "relative my-1 min-h-[76px] border border-transparent px-3 py-2 transition-colors hover:border-[var(--action-border-hover)] hover:bg-[var(--action-selection-soft)] focus-within:border-[var(--action-border-hover)] focus-within:bg-[var(--action-selection-soft)]", MAP_COMPACT_RADIUS_CLASS_NAME, menuReadableRowClassName )} > onSelectAlert(alert)} >
{ onToggleConditionFeed(); onOpenChange(false); }} /> { void onAnalyzeAlertsWithAgent(); onOpenChange(false); }} />
); } export type WorkbenchProjectSelection = { projects: WorkbenchProject[]; selectedProjectId: string; pendingProjectId: string | null; error: string | null; onSelectProject: (projectId: string) => void; onOpen?: () => void; onRefresh?: () => void; refreshing?: boolean; }; export function UserMenu({ open, onOpenChange, user, projectSelection, onRefreshTiles, onShowDataStatus, onShowShortcuts, onLogout }: { open: boolean; onOpenChange: (open: boolean) => void; user: WorkbenchUser; projectSelection?: WorkbenchProjectSelection; onRefreshTiles: () => void; onShowDataStatus: () => void; onShowShortcuts: () => void; onLogout?: () => Promise; }) { const [projectDialogOpen, setProjectDialogOpen] = useState(false); const [projectDialogKeyboard, setProjectDialogKeyboard] = useState(false); const triggerRef = useRef(null); return ( <> setProjectDialogKeyboard(true)} onPointerDownCapture={() => setProjectDialogKeyboard(false)} onCloseAutoFocus={(event) => { if (projectDialogOpen) event.preventDefault(); }}> {projectSelection ? ( <> { onOpenChange(false); setProjectDialogOpen(true); projectSelection.onOpen?.(); }} /> ) : null} 会话控制 void onLogout() : undefined} className={cn( "px-2 py-2", !onLogout && "text-[var(--ink-muted)]", MAP_COMPACT_RADIUS_CLASS_NAME )} > {projectDialogOpen && projectSelection ? setProjectDialogOpen(false)} onRestoreFocus={() => triggerRef.current?.focus()} /> : null} ); } function AlertQueueSummary({ count, latestAlertTime, conditionFeedVisible, sourceLabel, tone }: { count: number; latestAlertTime?: string; conditionFeedVisible: boolean; sourceLabel: string; tone: StatusTone; }) { const hasAlerts = count > 0; return (
{hasAlerts ? : }

待办与告警

{hasAlerts ? `${count} 条待处理${latestAlertTime ? ` · 最近 ${latestAlertTime}` : ""}` : "当前无待处理事项"}

业务告警来源:{sourceLabel}

{hasAlerts ? "待处理" : "暂无待办"}
{conditionFeedVisible ? "工况面板已显示,可查看详情。" : "点击条目可打开工况任务。"}
); } function MenuPlainButton({ label, description, disabled = false, onClick }: { label: string; description: string; disabled?: boolean; onClick: () => void; }) { return ( ); } function MenuPrimaryButton({ icon: Icon, label, disabled = false, onClick }: { icon: LucideIcon; label: string; disabled?: boolean; onClick: () => void | Promise; }) { return ( ); } function MenuHeader({ title, meta, tone = "default", icon: Icon }: { title: string; meta: string; tone?: "default" | "warning"; icon?: LucideIcon; }) { return (
{Icon ?

{meta}

{tone === "warning" ? : null}
); } function MenuSectionLabel({ children }: { children: ReactNode }) { return {children}; } function MenuSeparator() { return ; } function MenuAction({ icon: Icon, label, description, tone = "default", onSelect }: { icon: LucideIcon; label: string; description: string; tone?: "default" | "primary" | "warning"; onSelect: () => void; }) { return ( {label} {description} ); }