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..456067d 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,7 +10,13 @@ import { TguPlanningLayout } from "./TguPlanningLayout"; import { TguSidebar } from "./TguSidebar"; import { TguTimeline } from "./TguTimeline"; import { TguToolbar } from "./TguToolbar"; -import { buildPlatformsForInterval, buildTimelineRows, mapPlans } from "./tguTimelineMapper"; +import { + buildPlatformsForInterval, + buildTimelineRows, + isOperationalPlatform, + mapPlans, + operationalSpacecraftIds +} from "./tguTimelineMapper"; const DAY_MS = 24 * 60 * 60 * 1000; @@ -61,19 +67,24 @@ export function TguPlanningPage() { fetchPlans(historyDays) ]); + // На морде работаем только с эксплуатируемыми КА — остальные отсекаем сразу + // на входе, чтобы они не всплывали ни в списке, ни как fallback-строки по планам. + const operationalPlatforms = + platformResult.status === "fulfilled" ? platformResult.value.filter(isOperationalPlatform) : []; + if (platformResult.status === "rejected") { setPlatformLoadFailed(true); } else { setPlatformLoadFailed(false); - setRawPlatforms(platformResult.value); + setRawPlatforms(operationalPlatforms); } if (planResult.status === "rejected") { setPlans([]); setPageError(planResult.reason instanceof Error ? planResult.reason.message : "Не удалось загрузить планы."); } else { - const platforms = platformResult.status === "fulfilled" ? platformResult.value : []; - setPlans(mapPlans(planResult.value, platforms)); + const operationalIds = operationalSpacecraftIds(operationalPlatforms); + setPlans(mapPlans(planResult.value, operationalPlatforms).filter((plan) => operationalIds.has(plan.spacecraftId))); } setLoading(false); diff --git a/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.test.ts b/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.test.ts new file mode 100644 index 0000000..bebb323 --- /dev/null +++ b/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import type { TguPlatform } from "../../model/tguTypes"; +import { isOperationalPlatform, operationalSpacecraftIds } from "./tguTimelineMapper"; + +function platform(overrides: Partial): TguPlatform { + return { id: "x", noradId: 1, status: "OPERATIONAL", ...overrides }; +} + +describe("isOperationalPlatform", () => { + it("принимает только статус OPERATIONAL без учёта регистра и пробелов", () => { + expect(isOperationalPlatform(platform({ status: "OPERATIONAL" }))).toBe(true); + expect(isOperationalPlatform(platform({ status: " operational " }))).toBe(true); + expect(isOperationalPlatform(platform({ status: "STANDBY" }))).toBe(false); + expect(isOperationalPlatform(platform({ status: "LOST" }))).toBe(false); + expect(isOperationalPlatform(platform({ status: null }))).toBe(false); + expect(isOperationalPlatform(platform({ status: undefined }))).toBe(false); + }); +}); + +describe("operationalSpacecraftIds", () => { + it("собирает norad-id в виде строк и пропускает платформы без norad-id", () => { + const ids = operationalSpacecraftIds([ + platform({ noradId: 2 }), + platform({ noradId: 30 }), + platform({ noradId: null }), + platform({ noradId: undefined }) + ]); + expect(ids).toEqual(new Set(["2", "30"])); + }); +}); diff --git a/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.ts b/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.ts index 180b613..f8e7da6 100644 --- a/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.ts +++ b/services/pcp-tgu-ops-ui/src/features/tgu-planning/tguTimelineMapper.ts @@ -3,6 +3,24 @@ import type { TguPlanUi, TguPlatformUi, TimelineRange, TimelineRow } from "../.. const PROBLEM_STATUSES = new Set(["REJECTED", "START_AMBIGUOUS"]); +const OPERATIONAL_STATUS = "OPERATIONAL"; + +/** На морде показываем только эксплуатируемые КА — с остальными мы не работаем. */ +export function isOperationalPlatform(platform: TguPlatform): boolean { + return (platform.status ?? "").trim().toUpperCase() === OPERATIONAL_STATUS; +} + +/** norad-id эксплуатируемых КА; planId.spacecraftId сопоставляется именно с norad-id. */ +export function operationalSpacecraftIds(platforms: TguPlatform[]): Set { + const ids = new Set(); + for (const platform of platforms) { + if (platform.noradId !== null && platform.noradId !== undefined) { + ids.add(String(platform.noradId)); + } + } + return ids; +} + export function mapPlans(plans: TguPlan[], platforms: TguPlatform[]): TguPlanUi[] { const platformByNorad = buildPlatformByNorad(platforms);