pcp-tgu-ops-ui: панель «Параметры включения» в инспекторе маршрута
- WorkInspector показывает аппарат, виток, крен, угол захвата, центр контура (съёмка) и станцию/сбрасываемые съёмки (сброс) - planModesToWorks/addShoot прокидывают planModeId, revolution, rollDeg, captureAngleDeg, centerLat/Lon, surveyIds в модель включения - место под перечень заданий маршрута (привязка — позже) - секция «Станция приёма» рендерится только в режиме правки
This commit is contained in:
@@ -29,6 +29,10 @@ type WorkInspectorProps = {
|
||||
work: EditorWork;
|
||||
modes: EditorMode[];
|
||||
stations: EditorGroundStation[];
|
||||
/** Название аппарата редактора — для блока параметров включения. */
|
||||
spacecraftLabel?: string;
|
||||
/** Метка съёмки по id режима — для перечня сбрасываемых маршрутов у сброса. */
|
||||
surveyModeLabel?: (surveyModeId: number) => string;
|
||||
conflicts: string[];
|
||||
/** Включение существующего плана — показываем только информацию, без правок. */
|
||||
readOnly?: boolean;
|
||||
@@ -118,6 +122,8 @@ export function WorkInspector({
|
||||
work,
|
||||
modes,
|
||||
stations,
|
||||
spacecraftLabel,
|
||||
surveyModeLabel,
|
||||
conflicts,
|
||||
readOnly,
|
||||
onUpdate,
|
||||
@@ -160,6 +166,58 @@ export function WorkInspector({
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="tgu-editor-panel__section">
|
||||
<div className="tgu-editor-eyebrow">Параметры включения</div>
|
||||
<dl className="tgu-map-request-details__grid">
|
||||
<dt>Аппарат</dt>
|
||||
<dd>{spacecraftLabel ?? "—"}</dd>
|
||||
<dt>Виток</dt>
|
||||
<dd className="mono">{work.revolution ?? "—"}</dd>
|
||||
{work.kind === "shoot" && (
|
||||
<>
|
||||
<dt>Крен</dt>
|
||||
<dd className="mono">{work.rollDeg != null ? `${work.rollDeg.toFixed(1)}°` : "—"}</dd>
|
||||
<dt>Угол захвата</dt>
|
||||
<dd className="mono">{work.captureAngleDeg != null ? `${work.captureAngleDeg.toFixed(1)}°` : "—"}</dd>
|
||||
<dt>Центр контура</dt>
|
||||
<dd className="mono">
|
||||
{work.centerLat != null && work.centerLon != null
|
||||
? `${work.centerLat.toFixed(2)}, ${work.centerLon.toFixed(2)}`
|
||||
: "—"}
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
{work.kind === "downlink" && (
|
||||
<>
|
||||
<dt>Станция</dt>
|
||||
<dd>{stations.find((station) => station.id === work.stationId)?.name ?? work.stationId ?? "—"}</dd>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{work.kind === "shoot" && (
|
||||
<section className="tgu-editor-panel__section">
|
||||
<div className="tgu-editor-eyebrow">Задания под маршрутом</div>
|
||||
<div className="tgu-editor-panel__hint">Привязка заданий к маршруту появится позже.</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{work.kind === "downlink" && (
|
||||
<section className="tgu-editor-panel__section">
|
||||
<div className="tgu-editor-eyebrow">Сбрасываемые съёмки</div>
|
||||
{work.surveyIds && work.surveyIds.length > 0 ? (
|
||||
<ul className="tgu-editor-panel__list">
|
||||
{work.surveyIds.map((surveyId) => (
|
||||
<li key={surveyId}>{surveyModeLabel ? surveyModeLabel(surveyId) : `Маршрут #${surveyId}`}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="tgu-editor-panel__hint">Маршруты съёмки к сбросу не привязаны.</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{work.kind === "shoot" && (
|
||||
<section className="tgu-editor-panel__section">
|
||||
<div className="tgu-editor-eyebrow">Режим аппаратуры</div>
|
||||
@@ -183,12 +241,9 @@ export function WorkInspector({
|
||||
</section>
|
||||
)}
|
||||
|
||||
{work.kind === "downlink" && (
|
||||
{work.kind === "downlink" && !readOnly && (
|
||||
<section className="tgu-editor-panel__section">
|
||||
<div className="tgu-editor-eyebrow">Станция приёма</div>
|
||||
{readOnly ? (
|
||||
<div className="tgu-editor-panel__hint">{stations.find((station) => station.id === work.stationId)?.name ?? work.stationId ?? "—"}</div>
|
||||
) : (
|
||||
<select value={work.stationId ?? ""} onChange={(event) => onUpdate(work.id, { stationId: event.target.value })}>
|
||||
{stations.map((station) => (
|
||||
<option key={station.id} value={station.id}>
|
||||
@@ -196,7 +251,6 @@ export function WorkInspector({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
|
||||
@@ -453,7 +453,12 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
endMs,
|
||||
label: mode?.label ?? "Съёмка",
|
||||
modeId,
|
||||
target
|
||||
target,
|
||||
revolution: surveyRoute?.revolution ?? selectedShootPass.revolution,
|
||||
rollDeg: surveyRoute?.roll ?? routeRoll,
|
||||
captureAngleDeg: surveyRoute?.captureAngle ?? DEFAULT_CAPTURE_ANGLE_DEG,
|
||||
centerLat: surveyRoute?.lat,
|
||||
centerLon: surveyRoute?.longitude
|
||||
})
|
||||
);
|
||||
setSelectedWorkId(id);
|
||||
@@ -468,6 +473,17 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
[stationDtos]
|
||||
);
|
||||
|
||||
// Метка съёмки по id режима — для перечня сбрасываемых маршрутов в инспекторе сброса.
|
||||
const surveyModeLabel = useCallback(
|
||||
(surveyModeId: number) => {
|
||||
const mode = planModes.find(
|
||||
(m): m is PlanSurveyMode => m.type === "SURVEY" && m.id === surveyModeId
|
||||
);
|
||||
return mode ? `Съёмка · виток ${mode.revolution}` : `Маршрут #${surveyModeId}`;
|
||||
},
|
||||
[planModes]
|
||||
);
|
||||
|
||||
const addDropWork = async () => {
|
||||
if (!selectedPlan || !selectedZone || selectedDropSurveyIds.size === 0) return;
|
||||
const numericNorad = noradId ? Number(noradId) : NaN;
|
||||
@@ -735,6 +751,8 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
work={selectedWork}
|
||||
modes={modes}
|
||||
stations={stations}
|
||||
spacecraftLabel={activeLabel}
|
||||
surveyModeLabel={surveyModeLabel}
|
||||
conflicts={conflicts[selectedWork.id] ?? []}
|
||||
readOnly={selectedWork.origin === "plan"}
|
||||
onUpdate={onChangeWork}
|
||||
|
||||
@@ -88,6 +88,11 @@ describe("editorDraft", () => {
|
||||
startMs: parseUtc("2026-05-29T01:10:00Z"),
|
||||
endMs: parseUtc("2026-05-29T01:10:00Z") + 180_000,
|
||||
origin: "plan",
|
||||
planModeId: 11,
|
||||
revolution: 4321,
|
||||
rollDeg: 0,
|
||||
centerLat: 55.7,
|
||||
centerLon: 37.6,
|
||||
targetLat: 55.7,
|
||||
targetLon: 37.6
|
||||
},
|
||||
@@ -98,7 +103,10 @@ describe("editorDraft", () => {
|
||||
startMs: parseUtc("2026-05-29T01:40:00Z"),
|
||||
endMs: parseUtc("2026-05-29T01:40:00Z") + 120_000,
|
||||
origin: "plan",
|
||||
stationId: "102"
|
||||
planModeId: 22,
|
||||
revolution: 4322,
|
||||
stationId: "102",
|
||||
surveyIds: [11]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -22,6 +22,12 @@ export type AddShootInput = {
|
||||
label: string;
|
||||
modeId?: string;
|
||||
target: EditorTarget;
|
||||
/** Параметры построенного маршрута (если съёмка строилась с превью). */
|
||||
revolution?: number;
|
||||
rollDeg?: number;
|
||||
captureAngleDeg?: number;
|
||||
centerLat?: number;
|
||||
centerLon?: number;
|
||||
};
|
||||
|
||||
export type AddDownlinkInput = {
|
||||
@@ -66,6 +72,12 @@ export function planModesToWorks(modes: PlanMode[]): EditorWork[] {
|
||||
startMs,
|
||||
endMs,
|
||||
origin: "plan",
|
||||
planModeId: mode.id,
|
||||
revolution: mode.revolution,
|
||||
rollDeg: mode.roll,
|
||||
centerLat: mode.lat,
|
||||
centerLon: mode.longitude,
|
||||
// Центр контура — он же опорная точка для проверки видимости.
|
||||
targetLat: mode.lat,
|
||||
targetLon: mode.longitude
|
||||
}];
|
||||
@@ -78,7 +90,10 @@ export function planModesToWorks(modes: PlanMode[]): EditorWork[] {
|
||||
startMs,
|
||||
endMs,
|
||||
origin: "plan",
|
||||
stationId: mode.station ?? undefined
|
||||
planModeId: mode.id,
|
||||
revolution: mode.revolution,
|
||||
stationId: mode.station ?? undefined,
|
||||
surveyIds: mode.surveys
|
||||
}];
|
||||
});
|
||||
}
|
||||
@@ -136,6 +151,11 @@ export function addShoot(draft: Draft, input: AddShootInput): Draft {
|
||||
targetName: input.target.name,
|
||||
targetLat: input.target.lat,
|
||||
targetLon: input.target.lon,
|
||||
revolution: input.revolution,
|
||||
rollDeg: input.rollDeg,
|
||||
captureAngleDeg: input.captureAngleDeg,
|
||||
centerLat: input.centerLat,
|
||||
centerLon: input.centerLon,
|
||||
origin: "added",
|
||||
isNew: true
|
||||
};
|
||||
|
||||
@@ -20,11 +20,28 @@ export type EditorShootWork = EditorWorkBase & {
|
||||
targetName?: string;
|
||||
targetLat?: number;
|
||||
targetLon?: number;
|
||||
/** Идентификатор сохранённого режима съёмки в БД (есть у включений плана). */
|
||||
planModeId?: number;
|
||||
/** Номер витка. */
|
||||
revolution?: number;
|
||||
/** Крен (gamma), град. */
|
||||
rollDeg?: number;
|
||||
/** Угол захвата (полуширина полосы), град. Есть только у свежепостроенного маршрута. */
|
||||
captureAngleDeg?: number;
|
||||
/** Центр контура съёмки, град. */
|
||||
centerLat?: number;
|
||||
centerLon?: number;
|
||||
};
|
||||
|
||||
export type EditorDownlinkWork = EditorWorkBase & {
|
||||
kind: "downlink";
|
||||
stationId?: string;
|
||||
/** Идентификатор сохранённого режима сброса в БД (есть у включений плана). */
|
||||
planModeId?: number;
|
||||
/** Номер витка зоны радиовидимости. */
|
||||
revolution?: number;
|
||||
/** Идентификаторы режимов съёмки (survey_modes), сбрасываемых в этом сеансе. */
|
||||
surveyIds?: number[];
|
||||
};
|
||||
|
||||
export type EditorWork = EditorShootWork | EditorDownlinkWork;
|
||||
|
||||
@@ -47,6 +47,15 @@
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
|
||||
.tgu-editor-panel__list {
|
||||
margin: 0;
|
||||
padding-left: 1.1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.tgu-editor-toolbar__group {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
|
||||
Reference in New Issue
Block a user