Files
Aislo/B06_Section/B06_Section_Cross_Design_Session.ts
eomsangdonandClaude Opus 5 5907de85c6 feat(B06): 측구 선택과 결과를 가른 칸 ditch_choice 추가
- 사용자가 누른 선택(`ditch_choice`)과 자동 판정 결과(`ditch_enabled`)를 다른 칸으로 나눔.
- 캐시(`CrossDesignChoice`)에 칸이 없어 토글이 화면에서 안 돌던 자리 이음 —
  선택이 있으면 그것을 따르고, 없으면 저장분 결과를 자동값과 다를 때만 선택으로 살림.
- 서버 patch 스키마에도 같은 칸을 둠. 최상위 `None` 은 병합에서 걷히므로
  「자동」으로 되돌리는 길은 아직 없음(카드 토글은 켬/끔 둘뿐이라 지금은 손해 없음).

화면 실측(카드에서 껐다 켜기)은 PLAN.md 3-16 에 미체크로 남아 있음.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 20:32:50 +09:00

71 lines
2.9 KiB
TypeScript

/* =============================================================================
* B06_Section_Cross_Design_Session.ts
* 횡단 카드 버튼(지반유형·단면유형·측구·포장·2단 비탈) 선택의 **세션 보관소**.
*
* 왜 생겼나(2026-09-06 사용자 확정) — 예전에는 버튼을 누를 때마다
* `POST …/sections/{route}/cross-design` 이 나가 서버가 계산하고 **바로 저장**했다.
* 조작은 캐시에 쌓이고 [저장]·[확정]에서만 정본으로 나가야 한다는 규칙에 어긋난다.
* 계산은 브라우저가 하고(`common_util_cross_design.ts`), 선택값은 여기 담긴다.
*
* 담는 것은 **사용자가 고른 것만**이다 — 계산 결과(면적·설계선)는 담지 않는다.
* ========================================================================== */
import { readState, writeState } from "../A00_Common/b_page_state";
/** 카드에서 고를 수 있는 값 한 벌 — 서버 `CrossDesignRequest` 와 같은 이름을 쓴다. */
export interface CrossDesignChoice {
ground_type: string;
section_mode: string;
ditch_side?: string | null;
ditch_type?: string | null;
paved?: boolean;
two_stage_slope?: boolean;
/** 측구를 둘지 **사용자가 정한 선택**(`null` = 자동 판정). 결과(`ditch_enabled`)가 아니다.
* ⚠ 이 칸이 없으면 측구 토글이 눌려도 캐시에 안 담겨 **화면에서 안 돈다**(2026-09-09). */
ditch_choice?: boolean | null;
}
type ChoiceMap = Record<string, CrossDesignChoice>;
/** 측점 키 — 암 경계선 저장소와 같은 규칙(0.01m 단위). */
const keyOf = (chainageM: number): string => chainageM.toFixed(2);
function readAll(projectId: string, routeId: number): ChoiceMap {
return readState<ChoiceMap>("crossdesign", projectId, routeId) ?? {};
}
/** 이 측점의 선택값(없으면 null). */
export function readCrossDesignChoice(
projectId: string,
routeId: number,
chainageM: number,
): CrossDesignChoice | null {
return readAll(projectId, routeId)[keyOf(chainageM)] ?? null;
}
/** 선택값을 세션에 담는다. 같은 측점의 앞선 값은 덮어쓴다. */
export function writeCrossDesignChoice(
projectId: string,
routeId: number,
chainageM: number,
choice: CrossDesignChoice,
): void {
const all = readAll(projectId, routeId);
all[keyOf(chainageM)] = choice;
writeState("crossdesign", all, projectId, routeId);
}
/** [저장]·[확정]이 patch 로 실을 목록 — 누가거리(숫자) → 선택값. */
export function crossDesignChoices(
projectId: string | null,
routeId: number | null,
): Map<number, CrossDesignChoice> {
const choices = new Map<number, CrossDesignChoice>();
if (!projectId || routeId === null) return choices;
for (const [key, value] of Object.entries(readAll(projectId, routeId))) {
const chainage = Number(key);
if (Number.isFinite(chainage) && value) choices.set(chainage, value);
}
return choices;
}