refactor(tgu-ui): единый инспектор объектов карты (MapObjectInspector) на вкладках «Карта» и «Комплексный план»

Обобщил ComplexInfoPanel в общий MapObjectInspector (tgu-map-2d): карточки для
всех типов объектов — заявка, включение комплексного плана, ячейка тепловой карты,
маршрут planWorks, станция, маркер КА, линия; список с раскрытием при наложении.

Вкладка «Карта» переведена на enableObjectChooser + onObjectsAtPoint + правый
инспектор — заменила разрозненные Tgu2DMapRequestsPanel/Tgu2DMapStationPanel (удалены)
и объектную часть нижней плашки (Tgu2DMapDetails ужат до контекста плана). Вкладка
«Комплексный план» использует тот же инспектор.

RequestAtPoint переехал в tgu-requests/RequestDetailsPanel (нужен вкладке «Заявки»).
This commit is contained in:
Дмитрий Соловьев
2026-06-10 15:58:55 +03:00
parent bdbc874f41
commit 7202f7cb02
9 changed files with 164 additions and 270 deletions
@@ -6,12 +6,10 @@ import type { TguPlanUi, TguPlatformUi, TimelineRange } from "../../model/timeli
import { platformLabel } from "../tgu-planning/tguTimelineMapper";
import { DEFAULT_TGU_2D_MAP_LAYERS, type Tgu2DMapLayerKey, type Tgu2DMapLayersState } from "./model/mapLayerTypes";
import type { Tgu2DMapSelection } from "./model/mapSelectionTypes";
import type { MapPolygon } from "./model/mapTypes";
import { buildTgu2DMapScene, getLayerAvailability } from "./Tgu2DMapLayers";
import { Tgu2DMapDetails } from "./Tgu2DMapDetails";
import { Tgu2DMapRequestsPanel, type RequestAtPoint } from "./Tgu2DMapRequestsPanel";
import { MapObjectInspector } from "./MapObjectInspector";
import { Tgu2DMapSidebar } from "./Tgu2DMapSidebar";
import { Tgu2DMapStationPanel } from "./Tgu2DMapStationPanel";
import { Tgu2DMapToolbar } from "./Tgu2DMapToolbar";
import { Tgu2DMapView } from "./Tgu2DMapView";
import { getTgu2DMapIntervalState, TGU_2D_MAP_INTERVAL_WARNING } from "./tgu2DMapInterval";
@@ -58,15 +56,13 @@ export function Tgu2DMapTab({
const [layers, setLayers] = useState<Tgu2DMapLayersState>(DEFAULT_TGU_2D_MAP_LAYERS);
const [search, setSearch] = useState("");
const [redrawToken, setRedrawToken] = useState(0);
const [selectedObject, setSelectedObject] = useState<Tgu2DMapSelection>();
const [hiddenIds, setHiddenIds] = useState<ReadonlySet<string>>(new Set());
const [requestsState, setRequestsState] = useState<RequestsState>(() => buildInitialRequestsState(range));
const [stations, setStations] = useState<StationDto[]>([]);
const [stationsFetchFailed, setStationsFetchFailed] = useState(false);
const [requestsAtPoint, setRequestsAtPoint] = useState<RequestAtPoint[]>([]);
const [selectedRequestPolygonId, setSelectedRequestPolygonId] = useState<string | undefined>();
const [hoveredRequestPolygonId, setHoveredRequestPolygonId] = useState<string | undefined>();
const [stationPanel, setStationPanel] = useState<(Tgu2DMapSelection & { type: "marker"; layer: "stations" }) | undefined>();
// Инспектор объекта карты справа (единый для всех вкладок): объекты под кликом и раскрытый.
const [mapHits, setMapHits] = useState<Tgu2DMapSelection[]>([]);
const [activeHitId, setActiveHitId] = useState<string>();
useEffect(() => {
fetchStations()
@@ -183,47 +179,24 @@ export function Tgu2DMapTab({
setLayers((current) => ({ ...current, [key]: !current[key] }));
};
// Клик по карте: 0/1 объект — карточка сразу; ≥2 (наложение) — список под точкой клика.
const handleObjectSelect = useCallback((selection?: Tgu2DMapSelection) => {
if (selection?.type === "marker" && selection.layer === "stations") {
setStationPanel(selection as Tgu2DMapSelection & { type: "marker"; layer: "stations" });
setRequestsAtPoint([]);
setSelectedRequestPolygonId(undefined);
setHoveredRequestPolygonId(undefined);
setSelectedObject(undefined);
} else {
setStationPanel(undefined);
setSelectedObject(selection);
}
setMapHits(selection ? [selection] : []);
setActiveHitId(selection?.id);
}, []);
const handleRequestsAtPoint = useCallback(
(polygons: MapPolygon[]) => {
if (polygons.length === 0) {
setRequestsAtPoint([]);
setSelectedRequestPolygonId(undefined);
setHoveredRequestPolygonId(undefined);
return;
}
const joined = polygons.flatMap((polygon) => {
const item = requestsState.items.find((r) => r.id === polygon.requestId);
return item ? [{ polygon, item }] : [];
});
setRequestsAtPoint(joined);
setSelectedRequestPolygonId(undefined);
setHoveredRequestPolygonId(undefined);
setStationPanel(undefined);
},
[requestsState.items]
);
const closeRequestsPanel = useCallback(() => {
setRequestsAtPoint([]);
setSelectedRequestPolygonId(undefined);
setHoveredRequestPolygonId(undefined);
const handleObjectsAtPoint = useCallback((selections: Tgu2DMapSelection[]) => {
setMapHits(selections);
setActiveHitId(selections[0]?.id);
}, []);
const closeStationPanel = useCallback(() => {
setStationPanel(undefined);
const toggleHit = useCallback((selection: Tgu2DMapSelection) => {
setActiveHitId((current) => (current === selection.id ? undefined : selection.id));
}, []);
const closeInfoPanel = useCallback(() => {
setMapHits([]);
setActiveHitId(undefined);
}, []);
return (
@@ -236,7 +209,7 @@ export function Tgu2DMapTab({
onToggleLayer={toggleLayer}
/>
<div className={`tgu-map-content${(requestsAtPoint.length > 0 || stationPanel != null) ? " has-requests-panel" : ""}`}>
<div className={`tgu-map-content${mapHits.length > 0 ? " has-side-panel" : ""}`}>
<Tgu2DMapSidebar
platforms={platforms}
selectedPlanId={selectedPlan?.planId}
@@ -260,23 +233,22 @@ export function Tgu2DMapTab({
scene={mapInvalidRange ? { polygons: [], lines: [], markers: [] } : scene}
layers={layers}
redrawToken={redrawToken}
enableObjectChooser
onObjectsAtPoint={handleObjectsAtPoint}
onObjectSelect={handleObjectSelect}
onRequestsAtPoint={handleRequestsAtPoint}
hoveredRequestPolygonId={hoveredRequestPolygonId}
selectedRequestPolygonId={selectedRequestPolygonId}
selectedObjectId={activeHitId}
/>
</section>
{requestsAtPoint.length > 0 && (
<Tgu2DMapRequestsPanel
requests={requestsAtPoint}
selectedPolygonId={selectedRequestPolygonId}
onHover={setHoveredRequestPolygonId}
onSelect={setSelectedRequestPolygonId}
onClose={closeRequestsPanel}
/>
)}
{stationPanel != null && (
<Tgu2DMapStationPanel selection={stationPanel} onClose={closeStationPanel} />
{mapHits.length > 0 && (
<aside className="tgu-editor-side-panel">
<MapObjectInspector
hits={mapHits}
activeId={activeHitId}
onToggle={toggleHit}
onClose={closeInfoPanel}
requestItems={requestsState.items}
/>
</aside>
)}
</div>
@@ -290,7 +262,7 @@ export function Tgu2DMapTab({
)}
</div>
<Tgu2DMapDetails selectedPlan={selectedPlan} selectedObject={selectedObject} />
<Tgu2DMapDetails selectedPlan={selectedPlan} />
</main>
);
}