2D-карта (станции, заявки) и вкладка «Заявки»
Новая вкладка «Заявки»: показ заявок за интервал на 2D-карте, создание заявки рисованием полигона на карте с формой параметров (Оптика/РСА) и удаление выбранной заявки (createRequest/deleteRequest, RequestCreatePanel/RequestDetailsPanel, polygonToWkt). В Tgu2DMapView добавлен изолированный режим рисования полигона. 2D-карта: панели информации о станции и о заявках, слой станций (stationsApi), поддержка выбора объектов через спатиальный индекс. Список слоёв упрощён — трасса/полоса/маркеры КА убраны из переключателя, по умолчанию включены работы плана, станции и заявки.
This commit is contained in:
@@ -4,6 +4,7 @@ import type { TguPlanDecision, TguPlatform } from "../../model/tguTypes";
|
||||
import type { TguActiveTab, TguPlanUi, TimelineRange } from "../../model/timelineTypes";
|
||||
import { TguEditorTab } from "../tgu-editor/TguEditorTab";
|
||||
import { Tgu2DMapTab } from "../tgu-map-2d/Tgu2DMapTab";
|
||||
import { TguRequestsTab } from "../tgu-requests/TguRequestsTab";
|
||||
import { TguPlanDetails } from "./TguPlanDetails";
|
||||
import { TguPlanningLayout } from "./TguPlanningLayout";
|
||||
import { TguSidebar } from "./TguSidebar";
|
||||
@@ -114,14 +115,6 @@ export function TguPlanningPage() {
|
||||
setDecisionNotice(undefined);
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
if (invalidRange) {
|
||||
setPageError("Некорректный интервал: from должен быть меньше to.");
|
||||
return;
|
||||
}
|
||||
void loadData(appliedRange);
|
||||
};
|
||||
|
||||
const handleDecision = async (planId: string, decision: TguPlanDecision, reason?: string) => {
|
||||
setDecisionInFlight(true);
|
||||
setDecisionError(undefined);
|
||||
@@ -156,7 +149,6 @@ export function TguPlanningPage() {
|
||||
onToChange={setToValue}
|
||||
onApply={applyRange}
|
||||
onQuickRange={quickRange}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@@ -205,6 +197,12 @@ export function TguPlanningPage() {
|
||||
selectedPlan={selectedPlan}
|
||||
platforms={platforms}
|
||||
/>
|
||||
) : activeTab === "requests" ? (
|
||||
<TguRequestsTab
|
||||
range={appliedRange}
|
||||
invalidRange={invalidRange}
|
||||
platforms={platforms}
|
||||
/>
|
||||
) : (
|
||||
<TguEditorTab
|
||||
selectedSpacecraftId={selectedPlan?.spacecraftId ?? selectedSpacecraftId}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TguPlatformUi } from "../../model/timelineTypes";
|
||||
import { filterPlatforms, platformLabel } from "./tguTimelineMapper";
|
||||
import { TguLegend } from "./TguLegend";
|
||||
|
||||
type TguSidebarProps = {
|
||||
platforms: TguPlatformUi[];
|
||||
@@ -69,6 +70,10 @@ export function TguSidebar({
|
||||
})}
|
||||
{filtered.length === 0 && <div className="tgu-empty tgu-empty--compact">Нет КА по текущему поиску.</div>}
|
||||
</div>
|
||||
|
||||
<div className="tgu-sidebar__legend">
|
||||
<TguLegend />
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import type { TimelinePlanSegment, TimelineRange, TimelineRow } from "../../model/timelineTypes";
|
||||
import {
|
||||
buildSequentialLinks,
|
||||
@@ -30,9 +30,11 @@ export function TguTimeline({ rows, range, selectedPlanId, invalidRange, onSelec
|
||||
const height = Math.max(260, timelineHeight(rows.length));
|
||||
const nowMs = Date.now();
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
setContainerWidth(Math.floor(containerRef.current.getBoundingClientRect().width));
|
||||
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
setContainerWidth(Math.floor(entry.contentRect.width));
|
||||
});
|
||||
@@ -55,17 +57,14 @@ export function TguTimeline({ rows, range, selectedPlanId, invalidRange, onSelec
|
||||
? TIMELINE_LABEL_WIDTH + timeToX(nowMs, range, timelineWidth)
|
||||
: undefined;
|
||||
|
||||
if (invalidRange) {
|
||||
return <div className="tgu-empty">Некорректный интервал: from должен быть меньше to.</div>;
|
||||
}
|
||||
|
||||
if (rows.length === 0 || segments.length === 0) {
|
||||
return <div className="tgu-empty">Нет планов в выбранном интервале.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tgu-timeline" ref={containerRef}>
|
||||
<svg className="tgu-timeline__svg" viewBox={`0 0 ${fullWidth} ${height}`} role="img" aria-label="Timeline планов ТГУ">
|
||||
{invalidRange ? (
|
||||
<div className="tgu-empty">Некорректный интервал: from должен быть меньше to.</div>
|
||||
) : rows.length === 0 || segments.length === 0 ? (
|
||||
<div className="tgu-empty">Нет планов в выбранном интервале.</div>
|
||||
) : (
|
||||
<svg className="tgu-timeline__svg" viewBox={`0 0 ${fullWidth} ${height}`} style={{ height }} role="img" aria-label="Timeline планов ТГУ">
|
||||
<defs>
|
||||
<marker id="tgu-arrow-head" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M0 0 L8 4 L0 8 z" fill="#8aa2a6" />
|
||||
@@ -175,6 +174,7 @@ export function TguTimeline({ rows, range, selectedPlanId, invalidRange, onSelec
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { TguLegend } from "./TguLegend";
|
||||
import type { TguActiveTab } from "../../model/timelineTypes";
|
||||
|
||||
type TguToolbarProps = {
|
||||
@@ -12,7 +11,6 @@ type TguToolbarProps = {
|
||||
onToChange: (value: string) => void;
|
||||
onApply: () => void;
|
||||
onQuickRange: (hours: number) => void;
|
||||
onRefresh: () => void;
|
||||
};
|
||||
|
||||
export function TguToolbar({
|
||||
@@ -25,8 +23,7 @@ export function TguToolbar({
|
||||
onFromChange,
|
||||
onToChange,
|
||||
onApply,
|
||||
onQuickRange,
|
||||
onRefresh
|
||||
onQuickRange
|
||||
}: TguToolbarProps) {
|
||||
return (
|
||||
<header className="tgu-toolbar">
|
||||
@@ -56,7 +53,7 @@ export function TguToolbar({
|
||||
aria-selected={activeTab === "map"}
|
||||
onClick={() => onTabChange("map")}
|
||||
>
|
||||
Карта 2D
|
||||
Карта
|
||||
</button>
|
||||
<button
|
||||
className={activeTab === "editor" ? "is-active" : ""}
|
||||
@@ -67,6 +64,15 @@ export function TguToolbar({
|
||||
>
|
||||
Редактор
|
||||
</button>
|
||||
<button
|
||||
className={activeTab === "requests" ? "is-active" : ""}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === "requests"}
|
||||
onClick={() => onTabChange("requests")}
|
||||
>
|
||||
Заявки
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label className="tgu-field">
|
||||
@@ -90,11 +96,7 @@ export function TguToolbar({
|
||||
<button className="tgu-button" type="button" onClick={() => onQuickRange(168)} disabled={loading}>
|
||||
7 суток
|
||||
</button>
|
||||
<button className="tgu-button" type="button" onClick={onRefresh} disabled={loading}>
|
||||
{loading ? "Обновление..." : "Обновить"}
|
||||
</button>
|
||||
</div>
|
||||
<TguLegend />
|
||||
{error && <div className="tgu-alert tgu-alert--danger">{error}</div>}
|
||||
</header>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user