feat(b06): 횡단도 바로 반영 — 새로 놓거나 옮긴 관 · 구조물의 횡단 카드를 [저장] 전에 보임 (273-2)
- 서버: 읽기 전용 미리 뜨기 POST /section/preview-stations — 로그인 · 회사 · 프로젝트 권한 그대로 · 한 번에 60곳 · 파일 · DB 안 씀 · 저장과 같은 길(sample_structure_sections · compute_default_designs crosses) - 화면: 초안이 심는 자리 중 횡단 없는 곳만 묻고 한 번 뜬 자리는 기억(B06_Section_Section_Drafts) · 카드 이름 · 관 그림 얹음 · 늦은 옛 응답 무시 · 배수 재계산이 관경을 정하면 카드 다시 얹음 · B06 관 옮김도 카드 다시 - [저장]은 지금처럼 서버가 같은 계산으로 정본에 씀 — 미리 본 카드와 같은 값 - 알림 글: 횡단도는 바로 보이고 세부 배수유역만 저장 뒤 다시 셈 - 시험: test_273_2 새로 · 177-3 헬퍼에 revealPick 자리 채움 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -18,9 +18,7 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { clearState, readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import {
|
||||
fetchStructures,
|
||||
fetchStructureTypes,
|
||||
readPendingStructures,
|
||||
} from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
@@ -41,11 +39,8 @@ import {
|
||||
pavementSpecsFrom,
|
||||
type PavementStructureInput,
|
||||
} from "@util/common_util_structure_pavement";
|
||||
import {
|
||||
plantedChainages,
|
||||
SAME_STATION_M,
|
||||
setGeneralStations,
|
||||
} from "../common_util/common_util_structure_station";
|
||||
import { setGeneralStations } from "../common_util/common_util_structure_station";
|
||||
import { forgetDraftStations, withDraftStations } from "./B06_Section_Section_Drafts";
|
||||
import { applyStructureAreaRows, structureAreaRows } from "./B06_Section_Structure_Layouts";
|
||||
import type { CrossSectionPatch, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { fetchSectionDetail, saveSections } from "./B06_Section_Api_Fetch";
|
||||
@@ -69,9 +64,10 @@ function keyOf(projectId: string, routeId: number): string {
|
||||
async function withDraftWalls(
|
||||
detail: SectionDetailResponse,
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
): Promise<SectionDetailResponse> {
|
||||
setGeneralStations(detail.longitudinal?.stations); // 구조물 기준 측점이 붙을 일반 측점(234-1)
|
||||
await withDraftStations(detail, projectId);
|
||||
await withDraftStations(detail, projectId, routeId); // 옛 자리 숨김 · 새 자리 미리 뜬 횡단(273-2)
|
||||
const drafts = readPendingStructures(projectId);
|
||||
// ⚠ **빈 목록도 「초안 없음」이다**(2026-09-09). 예전에는 `[]` 가 「초안이 있는데 벽이
|
||||
// 하나도 없다」로 읽혀 **아래에서 서버 저장분을 통째로 지웠다** — 구조물을 놓아도
|
||||
@@ -108,58 +104,6 @@ async function withDraftWalls(
|
||||
return withStructureAreas(detail);
|
||||
}
|
||||
|
||||
/** 저장된 관 지점 — 구조물 초안만 있을 때 심는 자리 셈에 씀. 저장 뒤(`invalidateSectionDetail`)
|
||||
* 다시 받음 · 고칠 때마다 서버에 안 물음. */
|
||||
const savedPipes = new Map<
|
||||
string,
|
||||
Promise<Awaited<ReturnType<typeof fetchDetailPipePoints>>["pipe_points"]>
|
||||
>();
|
||||
|
||||
function savedPipesOf(projectId: string) {
|
||||
let hit = savedPipes.get(projectId);
|
||||
if (!hit) {
|
||||
hit = fetchDetailPipePoints(projectId).then((res) => res.pipe_points);
|
||||
hit.catch(() => savedPipes.delete(projectId));
|
||||
savedPipes.set(projectId, hit);
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
/** 초안이 빼 둔 옛 자리 측점 — 초안이 다시 그 자리를 심거나 초안이 없어지면 돌려 놓음. */
|
||||
const parkedSections = new WeakMap<
|
||||
SectionDetailResponse,
|
||||
SectionDetailResponse["cross_sections"]
|
||||
>();
|
||||
|
||||
/**
|
||||
* 옮긴 관 · 구조물의 옛 자리 측점(서버가 다시 맞추기 전)을 횡단 카드 · 유토곡선에서 뺀다 — 초안이
|
||||
* 심는 자리(`plantedChainages` · 서버와 같은 규칙)에 없는 서버 구조물 측점(272 · 측점 한 벌). 초안이
|
||||
* 없으면 서버 목록이 지금 것이라 다 돌려 놓음. [저장] 뒤는 서버가 맞춘 새 상세라 뺄 것이 없음.
|
||||
*/
|
||||
async function withDraftStations(detail: SectionDetailResponse, projectId: string): Promise<void> {
|
||||
const pipesDraft = readPendingPipes(projectId);
|
||||
const structuresDraft = readPendingStructures(projectId);
|
||||
let planted: number[] | null = null;
|
||||
if (pipesDraft || structuresDraft) {
|
||||
const [pipes, structures, types] = await Promise.all([
|
||||
pipesDraft ?? savedPipesOf(projectId),
|
||||
structuresDraft ?? fetchStructures(projectId).then((res) => res.structures),
|
||||
fetchStructureTypes(true),
|
||||
]).catch(() => [null, null, null] as const);
|
||||
if (pipes && structures && types) planted = plantedChainages(pipes, structures, types);
|
||||
}
|
||||
const all = [...detail.cross_sections, ...(parkedSections.get(detail) ?? [])].sort(
|
||||
(a, b) => a.chainage_m - b.chainage_m,
|
||||
);
|
||||
const stale = (section: (typeof all)[number]): boolean =>
|
||||
planted !== null &&
|
||||
section.kind === "irregular" &&
|
||||
!section.grid &&
|
||||
planted.every((mark) => Math.abs(mark - section.chainage_m) >= SAME_STATION_M);
|
||||
detail.cross_sections.splice(0, detail.cross_sections.length, ...all.filter((s) => !stale(s)));
|
||||
parkedSections.set(detail, all.filter(stale));
|
||||
}
|
||||
|
||||
/** 관 지점 시설 → 횡단 자리 키(파이썬 `_SECTION_KEYS` · 배관 · 독립 기슭막이 = culvert). */
|
||||
const FACILITY_KEYS = ["culvert", "ford", "box", "ford_pavement"] as const;
|
||||
const facilityKey = (facility: string): (typeof FACILITY_KEYS)[number] =>
|
||||
@@ -244,21 +188,21 @@ export async function loadSectionDetail(
|
||||
const key = keyOf(projectId, routeId);
|
||||
if (!options?.force) {
|
||||
const cached = cache.get(key);
|
||||
if (cached) return withDraftWalls(cached, projectId);
|
||||
if (cached) return withDraftWalls(cached, projectId, routeId);
|
||||
const inFlight = pending.get(key);
|
||||
if (inFlight) return inFlight;
|
||||
// 새로고침으로 메모리가 빈 경우 — 세션에 얹어 둔 한 벌로 바로 선다.
|
||||
const stored = readState<SectionDetailResponse>("section-detail", projectId, routeId);
|
||||
if (stored) {
|
||||
cache.set(key, stored);
|
||||
return withDraftWalls(stored, projectId);
|
||||
return withDraftWalls(stored, projectId, routeId);
|
||||
}
|
||||
}
|
||||
const request = fetchSectionDetail(projectId, routeId)
|
||||
.then(async (detail) => {
|
||||
cache.set(key, detail);
|
||||
writeState("section-detail", detail, projectId, routeId);
|
||||
return withDraftWalls(detail, projectId);
|
||||
return withDraftWalls(detail, projectId, routeId);
|
||||
})
|
||||
.finally(() => {
|
||||
pending.delete(key);
|
||||
@@ -283,7 +227,7 @@ export function replaceSectionDetail(
|
||||
* 서버 쪽 정본이 바뀌었는데 새 상세를 손에 못 쥔 조작(계획선 편집 저장 등) 뒤에 쓴다.
|
||||
*/
|
||||
export function invalidateSectionDetail(projectId: string, routeId?: number): void {
|
||||
savedPipes.delete(projectId);
|
||||
forgetDraftStations(projectId);
|
||||
if (routeId !== undefined) {
|
||||
cache.delete(keyOf(projectId, routeId));
|
||||
clearState("section-detail", projectId, routeId);
|
||||
|
||||
Reference in New Issue
Block a user