import { CalendarClock, Check, Clock3, OctagonAlert, PanelBottomClose, PanelBottomOpen, RotateCcw, TriangleAlert } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useState } from "react"; import { cn } from "@/shared/ui/cn"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/shared/ui/tooltip"; import { clusterOperationalEvents, DEFAULT_OPERATIONAL_EVENT_VISIBILITY, filterOperationalEvents, getOperationalEventSummary, OPERATIONAL_TIMELINE_LAYOUT, type OperationalEvent, type OperationalEventCategory, type OperationalEventCluster, type OperationalEventLane, type OperationalEventVisibility, type OperationalTimelineMode } from "./operational-timeline-model"; type OperationalTimelineProps = { date: string; cursorTime: string; events: OperationalEvent[]; mode: OperationalTimelineMode; selectedEventId: string | null; mobile?: boolean; onModeChange?: (mode: OperationalTimelineMode) => void; onSelectEvent: (event: OperationalEvent) => void; onClearSelection: () => void; onReturnNow: () => void; }; const LANES: Array<{ id: OperationalEventLane; label: string }> = [ { id: "plan", label: "计划" }, { id: "actual", label: "实际" }, { id: "exception", label: "异常" } ]; const LEGEND_ITEMS: Array<{ id: OperationalEventCategory; label: string }> = [ { id: "plan", label: "计划" }, { id: "actual", label: "实际" }, { id: "attention", label: "关注" }, { id: "exception", label: "异常" } ]; export function OperationalTimeline({ date, cursorTime, events, mode, selectedEventId, mobile = false, onModeChange, onSelectEvent, onClearSelection, onReturnNow }: OperationalTimelineProps) { const prefersReducedMotion = useReducedMotion(); const [eventVisibility, setEventVisibility] = useState(() => ({ ...DEFAULT_OPERATIONAL_EVENT_VISIBILITY })); const summary = getOperationalEventSummary(events); const visibleEvents = filterOperationalEvents(events, eventVisibility); const clusters = clusterOperationalEvents(visibleEvents, mobile ? 60 : 10); const selectedEvent = events.find((event) => event.id === selectedEventId) ?? null; const cursorPosition = getDayPosition(cursorTime); const height = mobile ? "100%" : OPERATIONAL_TIMELINE_LAYOUT[mode].height; const sizeTransition = prefersReducedMotion ? { duration: 0 } : { duration: 0.3, ease: [0.22, 1, 0.36, 1] as const }; if (mode === "summary" && !mobile) { return ( ); } return (
{!mobile ? : null} {selectedEvent && !mobile ? ( ) : null}
{mode === "expanded" || mobile ? (
{LANES.map((lane) => ( {lane.label} ))}
{LANES.map((lane) => ( cluster.lane === lane.id)} selectedEventId={selectedEventId} onSelectEvent={onSelectEvent} /> ))}
) : (
{clusters.map((cluster, index) => ( ))}
)} ); } function TimelineLane({ lane, clusters, selectedEventId, onSelectEvent }: { lane: OperationalEventLane; clusters: OperationalEventCluster[]; selectedEventId: string | null; onSelectEvent: (event: OperationalEvent) => void; }) { return (
{clusters.map((cluster, index) => ( ))}
); } function TimelineMarker({ cluster, compact = false, markerIndex, selectedEventId, onSelectEvent }: { cluster: OperationalEventCluster; compact?: boolean; markerIndex: number; selectedEventId: string | null; onSelectEvent: (event: OperationalEvent) => void; }) { const event = pickClusterEvent(cluster.events); const selected = cluster.events.some((item) => item.id === selectedEventId); const position = getDayPosition(event.cursorTime); const exception = event.lane === "exception"; const verticalPosition = compact ? 56 : [24, 50, 76][markerIndex % 3]; return (
{operationalEventNatureLabel(event)}

{event.sourceLabel}

{event.title}

{event.description}

{cluster.events.length > 1 ? (

同一时间窗 {cluster.events.length} 项

) : null}
); } function OperationalEventGlyph({ event }: { event: OperationalEvent }) { if (event.status === "failed") { return