// Partitura — right-side events panel. // Filters events by visible time window AND by selected row key. // Selection key format: // - "agent:" // - "module:" // - "module:/agent:" function EventsPanel({ width, events, // visible-events union from all rows rows, // current rows array (for resolving label/avatar) selectedKey, // string|null setSelectedKey, visRange, // [startMin, endMin] mode, // "agents" | "modules" period, // "day" | "week" | "month" | "year" selectedSeg, // { seg, rowKey } | null onClearSeg, highlightEventKey, agents, // TEAM stickyTop = 96, }) { const listRef = React.useRef(null); const isDay = period === "day"; // Module lookup for an event (resolve via owner segment's module) const evModule = (ev) => { const segs = window.PT_AGENT_SEGMENTS || {}; const owner = (segs[ev.agent] || []).find((s) => s.seq === ev.seq); return owner ? owner.module : null; }; // Parse selection key const sel = React.useMemo(() => { if (!selectedKey) return { kind: "none" }; if (selectedKey.startsWith("agent:")) { return { kind: "agent", agentId: selectedKey.slice("agent:".length) }; } if (selectedKey.startsWith("module:")) { const tail = selectedKey.slice("module:".length); const i = tail.indexOf("/agent:"); if (i >= 0) { return { kind: "module-agent", moduleName: tail.slice(0, i), agentId: tail.slice(i + "/agent:".length) }; } return { kind: "module", moduleName: tail }; } return { kind: "none" }; }, [selectedKey]); // Filter const inWindow = (ev) => !isDay || (ev.t >= visRange[0] && ev.t <= visRange[1]); let filtered = events.filter(inWindow); if (sel.kind === "agent") { filtered = filtered.filter((ev) => ev.agent === sel.agentId); } else if (sel.kind === "module") { filtered = filtered.filter((ev) => evModule(ev) === sel.moduleName); } else if (sel.kind === "module-agent") { filtered = filtered.filter((ev) => ev.agent === sel.agentId && evModule(ev) === sel.moduleName); } filtered.sort((a, b) => a.t - b.t); // Scroll selected event into view React.useEffect(() => { if (!highlightEventKey || !listRef.current) return; const el = listRef.current.querySelector(`[data-ev-key="${highlightEventKey}"]`); if (el) el.scrollIntoView({ behavior: "smooth", block: "center" }); }, [highlightEventKey]); // Resolve display info for the header const headerInfo = React.useMemo(() => { if (sel.kind === "none") return null; if (sel.kind === "agent") { const a = (agents || []).find((x) => x.id === sel.agentId); return a ? { title: a.name, sub: a.roleLabel, avatar: a.avatar, kind: "agent" } : null; } if (sel.kind === "module") { return { title: sel.moduleName, sub: "модуль", color: window.moduleColor(sel.moduleName), kind: "module" }; } if (sel.kind === "module-agent") { const a = (agents || []).find((x) => x.id === sel.agentId); return a ? { title: a.name, sub: `модуль ${sel.moduleName}`, avatar: a.avatar, color: window.moduleColor(sel.moduleName), kind: "module-agent", } : null; } return null; }, [sel, agents]); return ( ); } function PanelBreakdown({ events }) { const counts = {}; events.forEach((e) => { counts[e.kind] = (counts[e.kind] || 0) + 1; }); const order = window.EVENT_KINDS_ORDER || []; return (
{order.filter((k) => counts[k]).map((k) => ( {counts[k]} ))}
); } function SelectedSegBlock({ seg, onClear }) { const style = window.STATE_STYLES[seg.state]; return (
выбран фрагмент
{style.label} PB-{seg.seq} {seg.module}
{seg.title}
{fmtHM(seg.start)} — {fmtHM(seg.end)} · {Math.round(seg.end - seg.start)} мин
); } function PtEventRow({ ev, evKey, isHighlight, showAgent, agents }) { const agent = ev.agent ? (agents || []).find((a) => a.id === ev.agent) : null; return (
{fmtHM(ev.t)} PB-{ev.seq} {showAgent && agent && ( {agent.name} )} {ev.kind === "alert" && ev.sev && ( {ev.sev} )}
{ev.text}
); } function PanelEmpty({ hasSelection }) { return (
{hasSelection ? "Нет событий для выбранного трека в этом окне." : "В видимом окне нет событий."}
); } window.EventsPanel = EventsPanel;