From f0e56b6e2a697d66325ad8cb2e6cf9d5c30c0fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D1=8C=D0=B5=D0=B2?= Date: Thu, 4 Jun 2026 13:30:12 +0300 Subject: [PATCH] =?UTF-8?q?pcp-tgu:=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=20=E2=80=94=20=D1=81=D1=82=D0=B0=D0=B1=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=B2=D0=B8=D1=82=D0=BA=D0=B8,=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B5=D0=B9=D1=81=D1=85=D0=BE=D0=BB=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D0=BB=D0=B0=D0=BD=D0=B0,?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2=D0=B8=D0=BA=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20=D0=B0=D0=B2=D1=82=D0=BE-=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Вкладка «Планы»: - «Создать черновик на основе плана» больше не перекидывает в редактор: черновик создаётся, выбирается на схеме (тумблер «Черновики» вкл.) и показывается подсказка — оператор сам переходит в «Редактор». Редактор: - Витки больше не пропадают «иногда»: жизненный цикл flightLineData теперь полностью внутри fetch-эффекта на [noradId, mapRange] (сброс + перезагрузка атомарны). Раньше трассу сбрасывал общий reset по ссылке selectedPlan, а перезагружал отдельный эффект — фоновый loadData пересобирал selectedPlan с тем же окном, mapRange по значению не менялся, фетч не перезапускался, витки молча исчезали. - Явный индикатор вместо немой пустоты: «Загрузка трассы…» / «Нет данных трассы на это окно». - После удаления открытого черновика редактор не «всплывал» обратно: guard загрузки включений не тянет режимы по устаревшему draftPlanId, когда план не выбран. - Редактор включений гейтится на наличие плана (план == миссия): без плана — плейсхолдер «Выберите КА и план…». Иначе после удаления черновика вкладка оставалась активной по КА (витки, режим съёмки), хотя редактировать нечего. --- .../src/features/tgu-editor/TguEditorTab.tsx | 49 +++++++++++++++---- .../features/tgu-planning/TguPlanningPage.tsx | 14 ++++-- .../pcp-tgu-ops-ui/src/styles/tgu-editor.css | 14 ++++++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx b/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx index 1426371..b7cddc8 100644 --- a/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx +++ b/services/pcp-tgu-ops-ui/src/features/tgu-editor/TguEditorTab.tsx @@ -127,6 +127,9 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, const [passFrom, setPassFrom] = useState(1); const [passTo, setPassTo] = useState(999); const [flightLineData, setFlightLineData] = useState([]); + // Статус загрузки трассы (витков): отличаем «ещё грузится» от «бэк вернул пусто на это окно», + // чтобы при отсутствии данных не было молчаливой пустоты вместо панели витков. + const [flightLineLoading, setFlightLineLoading] = useState(false); // Команда карте центрироваться/подсветить объект выбранного включения (клик по включению). const [mapFocus, setMapFocus] = useState<{ point?: GeoPoint; objectId?: string; token: number }>({ token: 0 }); const draft = history.draft; @@ -277,15 +280,26 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, setPlanModes([]); setPassFrom(1); setPassTo(999); - setFlightLineData([]); }, [appliedRange, selectedPlan, selectedSpacecraftId]); + // Линия трассы (витки) живёт исключительно по своим входам запроса — noradId и mapRange. + // НЕ сбрасываем её в общем reset-эффекте по ссылке selectedPlan: фоновый loadData (после + // решения/создания черновика/обновления интервала) пересобирает объект selectedPlan с теми же + // временами окна, mapRange по значению не меняется, и фетч бы не перезапустился — витки + // молча пропадали бы до реальной смены окна. Привязка к [noradId, mapRange] делает это + // идемпотентным: сброс и перезагрузка происходят вместе и только когда меняется само окно/КА. useEffect(() => { - if (!noradId) return; + if (!noradId) { + setFlightLineData([]); + setFlightLineLoading(false); + return; + } let cancelled = false; + setFlightLineLoading(true); void fetchFlightLine(noradId, mapRange.fromMs, mapRange.toMs) .then((data) => { if (!cancelled) setFlightLineData(data); }) - .catch(() => { if (!cancelled) setFlightLineData([]); }); + .catch(() => { if (!cancelled) setFlightLineData([]); }) + .finally(() => { if (!cancelled) setFlightLineLoading(false); }); return () => { cancelled = true; }; }, [noradId, mapRange]); @@ -464,8 +478,15 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, // Источник истины для включений — DRAFT, если он уже создан, иначе живой план (read-only превью). useEffect(() => { - const missionId = draftPlanId ?? selectedPlan?.planId; - if (!missionId) return; + // Нет выбранного плана (например, открытый черновик удалили) — чистим включения и НЕ тянем + // режимы по устаревшему draftPlanId. Он обнулится следующим коммитом (эффект выше), но в этом + // ещё указывает на удалённый черновик; без гарда fetchPlanModes(удалённый id) вернул бы его + // включения обратно — редактор «не чистился» бы после удаления. + if (!selectedPlan) { + setPlanModes([]); + return; + } + const missionId = draftPlanId ?? selectedPlan.planId; let cancelled = false; void fetchPlanModes(missionId).then((modes) => { if (!cancelled) setPlanModes(modes); @@ -895,10 +916,13 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, ); }; - if (!selectedSpacecraftId && !selectedPlan) { + // Редактор включений работает только при выбранном плане (план == миссия, в которую кладутся + // включения). Без плана — плейсхолдер: иначе после удаления открытого черновика вкладка + // оставалась бы «активной» по выбранному КА (витки, режим съёмки), хотя редактировать нечего. + if (!selectedPlan) { return (
-
Выберите КА или план, чтобы открыть редактор включений.
+
Выберите КА и план, чтобы открыть редактор включений.
); } @@ -969,7 +993,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange, >
{rightMode === "shoot" &&
Кликните точку съёмки на карте.
} - {orbitPasses.length > 0 && ( + {orbitPasses.length > 0 ? ( - )} + ) : noradId ? ( +
+ Витки + + {flightLineLoading ? "Загрузка трассы…" : "Нет данных трассы на это окно"} + +
+ ) : null} { - // Один открытый черновик на базу: если он уже есть — открываем его, второй не плодим. + // Один открытый черновик на базу: если он уже есть — выбираем его, второй не плодим. const existing = drafts.find((item) => item.basePlanId === planId); if (existing) { - handleEditDraft(existing.planId); + setShowDrafts(true); + setSelectedSpacecraftId(existing.spacecraftId); + setSelectedPlanId(existing.planId); + setDecisionNotice("Черновик на основе этого плана уже создан — выбран на схеме."); return; } setDraftActionInFlight(true); @@ -320,7 +324,7 @@ export function TguPlanningPage() { setShowDrafts(true); if (base) setSelectedSpacecraftId(base.spacecraftId); setSelectedPlanId(draftPlanId); - setActiveTab("editor"); + setDecisionNotice("Черновик создан и выбран на схеме. Откройте «Редактор», чтобы поправить и отправить его."); } catch (error) { setDecisionError(error instanceof Error ? error.message : "Не удалось создать черновик на основе плана."); } finally { diff --git a/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css b/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css index bf875db..f64d496 100644 --- a/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css +++ b/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css @@ -308,6 +308,20 @@ user-select: none; } +.map-pass-selector--empty { + display: flex; + align-items: baseline; + gap: 0.5rem; + padding: 0.4rem 0.65rem; + width: auto; + max-width: 28rem; +} + +.map-pass-selector__empty-note { + color: var(--text-muted, #98a2ac); + font-size: 0.74rem; +} + .map-pass-selector__header { display: flex; align-items: baseline;