diff --git a/services/pcp-tgu-ops-ui/src/features/tgu-planning/TguPlanningPage.tsx b/services/pcp-tgu-ops-ui/src/features/tgu-planning/TguPlanningPage.tsx index cd446e0..6712f23 100644 --- a/services/pcp-tgu-ops-ui/src/features/tgu-planning/TguPlanningPage.tsx +++ b/services/pcp-tgu-ops-ui/src/features/tgu-planning/TguPlanningPage.tsx @@ -10,6 +10,7 @@ import { TguPlanningLayout } from "./TguPlanningLayout"; import { TguSidebar } from "./TguSidebar"; import { TguTimeline } from "./TguTimeline"; import { TguToolbar } from "./TguToolbar"; +import { isActivePlanStatus } from "./tguStatus"; import { buildPlatformsForInterval, buildTimelineRows, mapPlans } from "./tguTimelineMapper"; const DAY_MS = 24 * 60 * 60 * 1000; @@ -28,6 +29,9 @@ export function TguPlanningPage() { const [selectedSpacecraftId, setSelectedSpacecraftId] = useState(); const [selectedPlanId, setSelectedPlanId] = useState(); const [activeTab, setActiveTab] = useState("timeline"); + // По умолчанию на таймлайне видна только актуальная цепочка; история версий + // (SUPERSEDED/EXPIRED/REJECTED и т.п.) скрыта, чтобы не было визуальной каши. + const [showHistory, setShowHistory] = useState(false); // Вкладки рендерятся в режиме keep-alive: каждую монтируем при первом открытии // и дальше держим смонтированной (скрытой через display:none), чтобы её // внутреннее состояние — загруженные заявки, выбор, слои, скролл — не сбрасывалось @@ -98,9 +102,14 @@ export function TguPlanningPage() { () => buildPlatformsForInterval(rawPlatforms, plans, appliedRange), [rawPlatforms, plans, appliedRange] ); + // Планы, показываемые на таймлайне: по умолчанию только актуальная цепочка. + const timelinePlans = useMemo( + () => (showHistory ? plans : plans.filter((plan) => isActivePlanStatus(plan.status))), + [plans, showHistory] + ); const rows = useMemo( - () => buildTimelineRows(plans, platforms, appliedRange, selectedSpacecraftId), - [plans, platforms, appliedRange, selectedSpacecraftId] + () => buildTimelineRows(timelinePlans, platforms, appliedRange, selectedSpacecraftId), + [timelinePlans, platforms, appliedRange, selectedSpacecraftId] ); const selectedPlan = useMemo(() => { const plan = plans.find((item) => item.planId === selectedPlanId); @@ -181,6 +190,16 @@ export function TguPlanningPage() { }} />
+
+ +
[style.status, style])); +/** Статусы актуальной цепочки: их показываем на таймлайне по умолчанию. */ +const ACTIVE_PLAN_STATUSES = new Set([ + "PLANNED", + "ISSUING", + "WAITING_DECISION", + "ACCEPTED" +]); + +/** Проверяет, относится ли план к актуальной цепочке (а не к истории версий). */ +export function isActivePlanStatus(status: TguPlanStatus): boolean { + return ACTIVE_PLAN_STATUSES.has(status); +} + export function statusStyle(status: TguPlanStatus): TimelineStatusStyle { return ( STATUS_BY_NAME.get(status) ?? { diff --git a/services/pcp-tgu-ops-ui/src/styles/tgu-planning.css b/services/pcp-tgu-ops-ui/src/styles/tgu-planning.css index 1152dd2..37e6639 100644 --- a/services/pcp-tgu-ops-ui/src/styles/tgu-planning.css +++ b/services/pcp-tgu-ops-ui/src/styles/tgu-planning.css @@ -346,6 +346,27 @@ background: #121516; } +.tgu-timeline-controls { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.6rem 0.85rem 0; +} + +.tgu-timeline-controls__toggle { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-size: 0.8rem; + color: var(--muted, #9aa4ad); + cursor: pointer; + user-select: none; +} + +.tgu-timeline-controls__toggle input { + cursor: pointer; +} + .tgu-timeline { min-width: 48rem; min-height: 100%;