같은 데이터를 두 화면에 보여 주는 기능인데 세 곳이 갈려 값이 달랐다(실측: 절토(자연) B06 3,704.6㎥ ↔ B05 4,526.8㎥). ① 보는 노선 — B05는 최신 경로, B06은 최신 **확정** 경로를 열어 노선을 다시 탐색한 프로젝트에서 서로 다른 노선을 봤다(route 126 DRAFT ↔ 125 CONFIRMED, 같은 20m 측점 성토 4.82㎡ ↔ 85.9㎡). `get_workflow_route_context()`(최신 경로) 신설해 B06 화면 context가 그것을 쓴다. 납품 도면(B07)이 쓰는 확정 전용 창구는 그대로 둔다. ② 횡단 재계산 — 호출이 두 벌이라 인자가 갈렸다(B06만 표준 단면값·암 경계 오프셋 전달, 보존하는 사용자 부속값도 2개 ↔ 7개). `B06_Section_Cross_Refresh.refreshCrossDesigns()` 한 창구로 모으고 세션 편집값은 저장소에서 직접 읽어 패널 없는 B05도 같은 값을 보낸다. ③ 낡음 판정 — 「옛 암 2단계 필드 누락」 조건이 B06 페이지에만 있어 B05는 재계산을 건너뛰었다. 공용 `staleDesignChainages()` 안으로 옮겨 두 화면이 같은 시점에 같은 조치를 한다. 검증 — 공용 브라우저 실측: 두 화면 모두 route 126, 요약줄 문자열 완전 일치 (`절토(자연) 4,526.8㎥ · 성토 14,881.8㎥ · 토취 10,213.3㎥ · 최종 누가토량 −10,213.3㎥`). pytest 370 passed·17 skipped(일원화 검사 4건 신설), typecheck·prettier·ruff 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71 lines
2.9 KiB
TypeScript
71 lines
2.9 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_Profile_Preview.ts
|
|
* 계획선 편집 → 횡단 설계 프리뷰 반영 (패널 본체에서 분리, 2026-09-02 · 700줄 한계).
|
|
*
|
|
* 계획고가 바뀌면 측점별 횡단 단면적도 바뀐다 — 서버에 한 번 물어 전 측점을 다시 계산해
|
|
* **공유 캐시가 들고 있는 같은 객체**를 제자리 갱신한다. 그래야 횡단 기준 유토곡선과
|
|
* 3D 예상형상이 같은 값으로 따라온다(2026-08-03·08-23 사용자 보고).
|
|
*
|
|
* 끌기 중에는 계속 호출되므로 디바운스하고, 늦게 온 응답은 seq 비교로 버린다.
|
|
* ========================================================================== */
|
|
|
|
import type { AlignmentEdits } from "./B05_Profile_UI_Profile_Alignment";
|
|
import { refreshCrossDesigns } from "../B06_Section/B06_Section_Cross_Refresh";
|
|
import type { SectionDetailResponse } from "../B06_Section/B06_Section_Api_Fetch";
|
|
|
|
export interface CrossPreviewContext {
|
|
projectId: string;
|
|
detail: () => SectionDetailResponse | null;
|
|
routeId: () => number | null;
|
|
edits: () => AlignmentEdits;
|
|
debounceMs: number;
|
|
/** 반영이 끝난 뒤 다시 그린다. */
|
|
onApplied: () => void;
|
|
}
|
|
|
|
export interface CrossPreview {
|
|
/** 편집이 있을 때마다 부른다 — 마지막 값만 서버로 나간다. */
|
|
schedule: () => void;
|
|
/** 패널을 걷을 때 대기 중인 요청 타이머를 끈다. */
|
|
dispose: () => void;
|
|
}
|
|
|
|
export function createCrossPreview(ctx: CrossPreviewContext): CrossPreview {
|
|
let timer = 0;
|
|
let seq = 0;
|
|
|
|
return {
|
|
schedule() {
|
|
if (!ctx.detail() || ctx.routeId() === null) return;
|
|
window.clearTimeout(timer);
|
|
timer = window.setTimeout(() => {
|
|
const detail = ctx.detail();
|
|
const routeId = ctx.routeId();
|
|
if (!detail || routeId === null) return;
|
|
const current = (seq += 1);
|
|
// 재계산은 B06과 **같은 창구**를 쓴다 — 표준 단면값·암 경계 오프셋이 빠지면 서버가
|
|
// 다른 설계를 그려 같은 데이터가 두 화면에서 다른 값이 된다(2026-09-03 일원화).
|
|
const isCurrent = (): boolean =>
|
|
current === seq && ctx.detail() === detail && ctx.routeId() === routeId;
|
|
void refreshCrossDesigns({
|
|
projectId: ctx.projectId,
|
|
routeId,
|
|
detail,
|
|
edits: ctx.edits(),
|
|
// 늦게 온 옛 응답이 새 설계를 덮지 않게 반영 직전에 한 번 더 확인한다.
|
|
shouldApply: isCurrent,
|
|
})
|
|
.then((updated) => {
|
|
if (updated.length) ctx.onApplied();
|
|
})
|
|
.catch(() => {
|
|
/* 프리뷰 실패는 무시 — 화면의 계획선은 그대로 두고 다음 편집에서 다시 시도한다. */
|
|
});
|
|
}, ctx.debounceMs);
|
|
},
|
|
dispose() {
|
|
window.clearTimeout(timer);
|
|
},
|
|
};
|
|
}
|