pcp-tgu-ops-ui: fork-forward в редакторе — выбор будущей ЗРВ для перевыдачи невозвратного плана
Селектор «Целевая ЗРВ» в EditorToolbar: текущий слот (replace, по умолчанию)
либо будущая ЗРВ из проекции pcp-tgu-service (fetchSpacecraftPlans →
GET /api/plans/{spacecraftId}, фильтр futureZrvWindows: PLANNED строго в
будущем, кроме базы — та же проекция, что валидирует бэк). Выбор окна
переводит обзор редактора и заставляет ensureDraft слать mode=fork-forward
(naive-UTC окно + kpp); селектор блокируется после создания DRAFT.
This commit is contained in:
@@ -13,7 +13,8 @@ import {
|
||||
validateMission,
|
||||
type SurveyRoute
|
||||
} from "../../api/missionPlanningApi";
|
||||
import { abandonDraft, createDraft, promoteDraft } from "../../api/tguPlanApi";
|
||||
import { abandonDraft, createDraft, fetchSpacecraftPlans, promoteDraft } from "../../api/tguPlanApi";
|
||||
import { futureZrvWindows, toCreateDraftRequest, type ZrvWindow } from "./editorForkForward";
|
||||
import { parseUtc } from "../../utils/utcTime";
|
||||
import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
|
||||
import { parseWktPolygon } from "../tgu-map-2d/geometry/wktParser";
|
||||
@@ -121,6 +122,10 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
// Пока null — редактор показывает живой план read-only; после создания все правки и валидация
|
||||
// адресуются в этот DRAFT, а «Применить» делает его promote в живую цепочку.
|
||||
const [draftPlanId, setDraftPlanId] = useState<string>();
|
||||
// Будущие ЗРВ КА (проекция PLANNED) и выбранная цель fork-forward. Пусто/undefined — правка
|
||||
// текущего слота (replace); выбор окна заставляет ensureDraft создать черновик на следующей ЗРВ.
|
||||
const [futureWindows, setFutureWindows] = useState<ZrvWindow[]>([]);
|
||||
const [targetWindowId, setTargetWindowId] = useState<string>();
|
||||
const [passFrom, setPassFrom] = useState(1);
|
||||
const [passTo, setPassTo] = useState(999);
|
||||
const [flightLineData, setFlightLineData] = useState<FlightLinePoint[]>([]);
|
||||
@@ -223,6 +228,10 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
// «Применить» доступно, когда на сервере есть черновик (создаётся на первую сохранённую правку);
|
||||
// локальные несохранённые твики без DRAFT применять нечего.
|
||||
const dirty = Boolean(draftPlanId);
|
||||
const targetWindow = useMemo(
|
||||
() => futureWindows.find((window) => window.planId === targetWindowId),
|
||||
[futureWindows, targetWindowId]
|
||||
);
|
||||
const addedCount = draft.works.filter((work) => work.isNew).length;
|
||||
// Реальный состав плана берём из включений (plan modes), а не из черновика:
|
||||
// черновик сидируется заглушкой, когда у плана нет встроенных works.
|
||||
@@ -247,11 +256,33 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
setNotice(undefined);
|
||||
setApplyError(undefined);
|
||||
setPlanModes([]);
|
||||
setTargetWindowId(undefined);
|
||||
setPassFrom(1);
|
||||
setPassTo(999);
|
||||
setFlightLineData([]);
|
||||
}, [appliedRange, selectedPlan, selectedSpacecraftId]);
|
||||
|
||||
// Будущие ЗРВ-кандидаты для fork-forward: проекция КА из pcp-tgu-service (та же, с которой бэк
|
||||
// сверяет выбранное окно). Грузим при выборе плана; невозвратный план оператор перевыдаёт на одну
|
||||
// из этих ЗРВ. Сбой непрозрачен — список просто остаётся пустым (доступен только replace).
|
||||
useEffect(() => {
|
||||
if (!selectedPlan || !selectedSpacecraftId) {
|
||||
setFutureWindows([]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void fetchSpacecraftPlans(selectedSpacecraftId)
|
||||
.then((plans) => {
|
||||
if (!cancelled) setFutureWindows(futureZrvWindows(plans, selectedPlan.planId, Date.now()));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setFutureWindows([]);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [selectedPlan, selectedSpacecraftId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!noradId) return;
|
||||
let cancelled = false;
|
||||
@@ -453,15 +484,31 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
changeDraft((current) => updateWork(current, workId, patch));
|
||||
};
|
||||
|
||||
// Гарантирует наличие черновика: при первой правке клонирует базовый план в DRAFT (replace)
|
||||
// и возвращает его id. Дальше все правки/валидация/promote адресуются именно сюда.
|
||||
// Гарантирует наличие черновика: при первой правке клонирует базовый план в DRAFT и возвращает его id.
|
||||
// Без выбранной целевой ЗРВ — replace (тот же слот); с выбранной — fork-forward на следующую ЗРВ
|
||||
// (для невозвратного плана). Дальше все правки/валидация/promote адресуются именно сюда.
|
||||
const ensureDraft = useCallback(async (): Promise<string> => {
|
||||
if (draftPlanId) return draftPlanId;
|
||||
if (!selectedPlan) throw new Error("План не выбран — нечего редактировать.");
|
||||
const created = await createDraft(selectedPlan.planId, { mode: "replace" });
|
||||
const created = await createDraft(selectedPlan.planId, toCreateDraftRequest(targetWindow));
|
||||
setDraftPlanId(created.draftPlanId);
|
||||
return created.draftPlanId;
|
||||
}, [draftPlanId, selectedPlan]);
|
||||
}, [draftPlanId, selectedPlan, targetWindow]);
|
||||
|
||||
// Выбор целевой ЗРВ для fork-forward (до создания черновика): фиксирует окно и переводит
|
||||
// обзор редактора на него, чтобы оператор строил включения там, где будет жить новый план.
|
||||
const selectTargetWindow = useCallback(
|
||||
(planId: string | undefined) => {
|
||||
setTargetWindowId(planId);
|
||||
const window = planId ? futureWindows.find((item) => item.planId === planId) : undefined;
|
||||
if (window) {
|
||||
setWindow({ fromMs: window.startMs - 3 * HOUR_MS, toMs: window.endMs + 3 * HOUR_MS });
|
||||
} else if (selectedPlan) {
|
||||
setWindow(defaultEditorWindow(selectedPlan, appliedRange));
|
||||
}
|
||||
},
|
||||
[futureWindows, selectedPlan, appliedRange]
|
||||
);
|
||||
|
||||
// Бэкенд-валидация черновика (Ф4): авторитетные конфликты по режимам → карта по workId.
|
||||
// MVP-заглушка возвращает пусто; ошибки валидации не эскалируем в основной поток правок.
|
||||
@@ -781,6 +828,10 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
dirty={dirty}
|
||||
applying={applying}
|
||||
canceling={canceling}
|
||||
futureWindows={futureWindows}
|
||||
targetWindowId={targetWindowId}
|
||||
targetLocked={dirty}
|
||||
onSelectTargetWindow={selectTargetWindow}
|
||||
windowLabel={formatDuration(window.toMs - window.fromMs)}
|
||||
rightMode={rightMode}
|
||||
onSetRightMode={(mode) => {
|
||||
|
||||
Reference in New Issue
Block a user