feat(tgu-ops-ui): add spacecraft work editor tab

Port the TGU editor prototype into pcp-tgu-ops-ui as a new editor tab, add draft/conflict/pass logic with tests, extend the 2D map picking API, and include editor styles/theme aliases and task/prototype docs.

Validation: npm run test; npm run build in services/pcp-tgu-ops-ui.
This commit is contained in:
Дмитрий Соловьев
2026-05-30 15:28:34 +03:00
parent c3a1a8b4a1
commit f084775cbb
58 changed files with 6609 additions and 7 deletions
@@ -0,0 +1,82 @@
import type { TguPlanUi, TguPlatformUi } from "../../model/timelineTypes";
import type { Draft, EditorWork } from "./model/editorTypes";
type EditorRailProps = {
selectedSpacecraftId?: string;
selectedPlan?: TguPlanUi;
platforms: TguPlatformUi[];
draft: Draft;
conflictCount: number;
contextSpacecraftIds: Set<string>;
onToggleContext: (spacecraftId: string) => void;
};
export function EditorRail({
selectedSpacecraftId,
selectedPlan,
platforms,
draft,
conflictCount,
contextSpacecraftIds,
onToggleContext
}: EditorRailProps) {
const currentPlatform = platforms.find((platform) => platform.spacecraftId === selectedSpacecraftId);
const contextPlatforms = platforms.filter((platform) => platform.spacecraftId !== selectedSpacecraftId).slice(0, 40);
return (
<aside className="tgu-editor-rail">
<section className="tgu-editor-rail__section">
<div className="tgu-editor-eyebrow">Редактируется</div>
<div className="tgu-editor-rail__title">{currentPlatform?.name ?? selectedSpacecraftId ?? "КА не выбран"}</div>
<div className="tgu-editor-rail__meta">
{selectedPlan ? `${selectedPlan.planId} · КПП ${selectedPlan.kppId}` : "План не выбран"}
</div>
<div className="tgu-editor-stats">
<RailStat label="Включений" value={draft.works.length} />
<RailStat label="Съёмка" value={countWorks(draft.works, "shoot")} color="var(--t-optical)" />
<RailStat label="Сброс" value={countWorks(draft.works, "downlink")} color="var(--accent)" />
<RailStat label="Конфл." value={conflictCount} color={conflictCount > 0 ? "var(--now)" : undefined} />
</div>
</section>
<section className="tgu-editor-rail__section">
<div className="tgu-editor-eyebrow">Контекст других КА</div>
<div className="tgu-editor-context-list">
{contextPlatforms.length === 0 ? (
<div className="tgu-empty tgu-empty--compact">Нет других КА в выбранном интервале.</div>
) : (
contextPlatforms.map((platform) => {
const enabled = contextSpacecraftIds.has(platform.spacecraftId);
return (
<button
className={`tgu-editor-context-item ${enabled ? "is-enabled" : ""}`}
key={platform.spacecraftId}
type="button"
onClick={() => onToggleContext(platform.spacecraftId)}
>
<span className="tgu-editor-context-item__check">{enabled ? "✓" : ""}</span>
<span>{platform.name ?? platform.noradId ?? platform.spacecraftId}</span>
<small>{platform.planCount}</small>
</button>
);
})
)}
</div>
</section>
</aside>
);
}
function RailStat({ label, value, color }: { label: string; value: number; color?: string }) {
return (
<div className="tgu-editor-stat">
<span style={{ color }}>{value}</span>
<small>{label}</small>
</div>
);
}
function countWorks(works: EditorWork[], kind: EditorWork["kind"]): number {
return works.filter((work) => work.kind === kind).length;
}