From e535cc3183a406e08490f76c1dd49422cda306cc Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 8 Oct 2026 13:46:59 +0900 Subject: [PATCH] =?UTF-8?q?feat(B08):=20=EC=88=98=EB=A6=AC=20=ED=91=9C=20?= =?UTF-8?q?=EA=B0=92=20=EC=B0=A8=EB=A1=80=20=EA=B8=B0=EB=B3=B8=20<=20?= =?UTF-8?q?=EC=9E=A5=20=EA=B8=B0=EB=B3=B8=20<=20B05=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20<=20=EC=A0=80=EC=9E=A5=20<=20=EC=B4=88=EC=95=88=20=C2=B7=20?= =?UTF-8?q?=EA=B0=99=EC=9D=80=20=EA=B0=92=20=EB=B9=84=EC=9B=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=C2=B7=20B05=20Qd=20=C2=B7=20T=20=EC=B0=B8=EA=B3=A0?= =?UTF-8?q?(96-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 저장본 · 초안의 연결 칸 null 은 건너뜀 · [저장] · 초안에서 연결값과 같은 칸은 null(연동 유지) - 연결된 칸 이름 옆 작은 「B05」(풍선 「B05 분석」) · Qd · T 옆에 B05 값 - 초안 version 2 → 3 · 옛 「B05 와 안 이음」 주석 고침 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ --- A00_Common/b_page_state.ts | 4 +- B08_Quantity/B08_Quantity_UI_Hydro.ts | 30 ++++++++-- B08_Quantity/B08_Quantity_UI_Hydro_Sheet.ts | 62 ++++++++++++++++++++- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/A00_Common/b_page_state.ts b/A00_Common/b_page_state.ts index d1b9b599f..75850c9cb 100644 --- a/A00_Common/b_page_state.ts +++ b/A00_Common/b_page_state.ts @@ -164,8 +164,8 @@ export const STATE_REGISTRY = { * 예전에는 버튼을 누를 때마다 서버가 계산해 **바로 저장**했다. 조작은 캐시에 쌓고 * [저장]·[확정]에서만 정본으로 나가야 한다(사용자 확정). */ crossdesign: { bucket: "draft", scope: "route" }, - /** B08 홍수량 계산서 입력 `{측점: 입력}` — [저장] 때 B08 자기 파일로 · 배수유역 · B05/B06 값과 안 이음(69-6). */ - "b08-hydro": { bucket: "draft", scope: "project", version: 2 }, + /** B08 홍수량 계산서 입력 `{측점: 입력}` — [저장] 때 B08 자기 파일로 · B05 연결값과 같은 칸은 null(연동 · 96). */ + "b08-hydro": { bucket: "draft", scope: "project", version: 3 }, /** 미저장 계획고 편집(▲▼ · 곡선반경) — 설계 가게 `alignmentDraft` 의 칸(계획서 24-2c). * 옛 키는 노선 번호만 붙은 `b05-profile-alignment-draft:{노선}` 이었다. */ "alignment-draft": { diff --git a/B08_Quantity/B08_Quantity_UI_Hydro.ts b/B08_Quantity/B08_Quantity_UI_Hydro.ts index fea648f6d..80512c3b3 100644 --- a/B08_Quantity/B08_Quantity_UI_Hydro.ts +++ b/B08_Quantity/B08_Quantity_UI_Hydro.ts @@ -2,7 +2,8 @@ * B08_Quantity_UI_Hydro.ts * 4탭 수리집수면적표 — 배수지점 측점마다 보조 탭 · 한 장 = 홍수량 계산서(PLAN 62-2 · 69-4~6). * - * 측점 목록만 저장된 관 지점(pipe_points)에서 · 입력은 이 탭 자기 값(B05/B06 값과 안 이음) + * 측점 목록은 저장된 관 지점(pipe_points) · B05 연결값(A · L · H · 관경 · 내공 · 월류 폭 · 96)이 빈칸을 채움 + * 값 차례 = 기본 < 장 기본 < 연결 < 저장 < 초안 · 연결값과 같은 칸은 null 로 저장(연동 유지) * 박스암거 = 사각 단면 장(73-11) · 세월교 · 물넘이포장 = 월류 높이 장(73-12) · 그 밖 = 관 장 * 처음 값 = 표준 값 · 측점 고유 값은 빈칸 · 지역 · R24 = 공사지 자료(서버) * 조작 = 캐시(sessionStorage) · [저장] = B08 자기 파일(PUT …/quantity/hydro) · 뒤 캐시 비움 @@ -23,6 +24,7 @@ import { stationInterval } from "./B08_Quantity_UI_Summary"; import { buildHydroSheet, type HydroInputs, + type HydroLink, type HydroOptions, type HydroResult, } from "./B08_Quantity_UI_Hydro_Sheet"; @@ -34,6 +36,9 @@ interface HydroStation { kind: string; /** 계산서 장 — 시설이 정함(서버 station_rows) */ sheet: HydroInputs["sheet"]; + /** B05 연결값(값이 있는 칸만) · 비교용 B05 Qd · T — 서버 station_rows */ + linked: HydroLink["values"] | null; + b05: HydroLink["b05"]; } type Drafts = Record>; @@ -83,14 +88,26 @@ export async function renderHydro(host: HTMLElement, projectId: string): Promise } let drafts: Drafts = readState("b08-hydro", projectId) ?? {}; + const linkedOf = (station: HydroStation): Record => station.linked ?? {}; + /** 저장본 · 초안 한 겹 — 연결값이 있는 칸의 null 은 건너뜀(연결값을 덮지 않게) */ + const layer = (station: HydroStation, values: Partial = {}) => + Object.fromEntries( + Object.entries(values).filter(([k, v]) => v != null || !(k in linkedOf(station))), + ); const inputsOf = (station: HydroStation): HydroInputs => ({ ...sheet.defaults, ...(station.sheet === "pipe" ? {} : sheet.sheet_defaults[station.sheet]), - ...sheet.saved[keyOf(station)], - ...drafts[keyOf(station)], + ...station.linked, + ...layer(station, sheet.saved[keyOf(station)]), + ...layer(station, drafts[keyOf(station)]), sheet: station.sheet, }) as HydroInputs; + /** 연결값과 같은 칸은 null — 저장 · 초안이 연동을 유지(B05 값이 바뀌면 따라옴) · 다르면 손 값 */ + const unlinked = (station: HydroStation, inputs: HydroInputs): HydroInputs => + Object.fromEntries( + Object.entries(inputs).map(([k, v]) => [k, linkedOf(station)[k] === v ? null : v]), + ) as unknown as HydroInputs; const tabs = document.createElement("div"); tabs.className = "b08-quantity__tabs b08-hydro__tabs"; @@ -134,13 +151,14 @@ export async function renderHydro(host: HTMLElement, projectId: string): Promise inputs, sheet, (next) => { - drafts[keyOf(station)] = next; + drafts[keyOf(station)] = unlinked(station, next); writeState("b08-hydro", drafts, projectId); // 단면 가정 · 관 종류는 그림 · 식이 바뀜 — 장을 다시 그림 · 그 밖은 값만 if (next.section !== inputs.section || next.pipe_kind !== inputs.pipe_kind) draw(next); else void calc(next, view.show); }, station.kind, + { values: station.linked ?? {}, b05: station.b05 }, ); page.replaceChildren(view.root); void calc(inputs, view.show); @@ -166,7 +184,9 @@ export async function renderHydro(host: HTMLElement, projectId: string): Promise /** [저장] — 측점 전부의 지금 입력을 B08 자기 파일로 · 성공하면 캐시를 비우고 저장본으로 다시 엶 */ const saveAll = async (): Promise => { - const stations = Object.fromEntries(sheet.stations.map((s) => [keyOf(s), inputsOf(s)])); + const stations = Object.fromEntries( + sheet.stations.map((s) => [keyOf(s), unlinked(s, inputsOf(s))]), + ); showLoadingOverlay(); try { const body = await request<{ saved: Drafts }>(`/projects/${projectId}/quantity/hydro`, { diff --git a/B08_Quantity/B08_Quantity_UI_Hydro_Sheet.ts b/B08_Quantity/B08_Quantity_UI_Hydro_Sheet.ts index 60adc4a11..04cd6efbf 100644 --- a/B08_Quantity/B08_Quantity_UI_Hydro_Sheet.ts +++ b/B08_Quantity/B08_Quantity_UI_Hydro_Sheet.ts @@ -7,6 +7,7 @@ * 장(sheet) 갈래 — 박스 = 사각 내공 B · H · 수심비(73-11) · 세월교 · 물넘이 = 월류 폭 → 월류 높이(73-12) * 테두리는 입력 · 선택 칸만(공용 ui-input · ui-select) · 계산 칸은 글자만 * 굵게 + 붉은 계열은 중요 결과(Qd · 유출량 · 판정 · 유출율)만 · 빈칸에 기대는 계산 칸은 「-」. + * B05 연결값과 같은 칸은 이름 옆 작은 「B05」(풍선 「B05 분석」) · Qd · T 옆에 B05 값 참고(96-2). * * 입력 칸은 한 번 만들고 계산 결과만 제자리에 바꿈 — 고치는 중 칸 포커스를 잃지 않게. * ========================================================================== */ @@ -77,6 +78,12 @@ export interface HydroOptions { site: HydroSite; } +/** B05 배수유역 연결(96) — 칸 값 · 비교용 설계유량 · 도달시간(분) */ +export interface HydroLink { + values: Partial>; + b05: { design_flow_m3s: number | null; tc_minutes: number | null } | null; +} + export interface HydroSheet { root: HTMLElement; /** 계산값 칸을 바꿈 — null 이면 비움 + 까닭 한 줄 */ @@ -103,6 +110,14 @@ const DASH = "-"; const fmt = (value: number | null | undefined, digits: number, unit = ""): string => value == null || !Number.isFinite(value) ? DASH : `${value.toFixed(digits)}${unit}`; +/** 입력 칸이 놓인 줄의 이름 칸 */ +function sheetLabel(left: HTMLElement, right: HTMLElement, key: string): HTMLElement | null { + const input = [left, right] + .map((card) => card.querySelector(`input[data-b08-hydro="${key}"]`)) + .find(Boolean); + return input?.closest(".b08-hydro__row")?.querySelector(".b08-hydro__label") ?? null; +} + function node( tag: K, className = "", @@ -218,8 +233,15 @@ export function buildHydroSheet( onChange: (next: HydroInputs) => void, /** 시설 이름(박스암거 · 세월교 · 물넘이포장) — 관이면 "" */ kind = "", + link: HydroLink = { values: {}, b05: null }, ): HydroSheet { const site = options.site; + /** 연결된 칸의 「B05」 표 — 값이 연결값과 같을 때만 보임 */ + const marks = new Map(); + const markLinked = (key: Key): void => { + const mark = marks.get(key); + if (mark) mark.hidden = current[key] !== link.values[key]; + }; const out = new Map(); const outCell = (id: string, key = false): HTMLElement => { const cell = node("span", `b08-hydro__out${key ? " is-key" : ""}`, DASH); @@ -229,6 +251,7 @@ export function buildHydroSheet( let current = inputs; const commit = (key: Key, value: unknown): void => { current = { ...current, [key]: value } as HydroInputs; + markLinked(key); onChange(current); }; const numberInput = (key: Key, placeholder = ""): HTMLInputElement => { @@ -238,6 +261,14 @@ export function buildHydroSheet( input.dataset.b08Hydro = key; const value = inputs[key]; input.value = value == null ? "" : String(value); + const linked = link.values[key]; + if (linked != null) { + input.title = `B05 분석 ${linked}`; + const mark = node("span", "b08-hydro__hint", "B05"); + mark.title = "B05 분석"; + mark.dataset.b08HydroLinked = key; + marks.set(key, mark); + } input.addEventListener("change", () => { // 빈칸은 NULLABLE 만 · 최소 도달시간은 0 까지 · 그 밖은 0 보다 커야 함 — 아니면 되돌림 const parsed = input.value === "" ? null : Number(input.value); @@ -283,6 +314,18 @@ export function buildHydroSheet( }; const sub = (text: string): HTMLElement => node("h4", "b08-hydro__h2", text); const hint = (text: string): HTMLElement => node("span", "b08-hydro__hint", text); + /** B05 분석 값 참고(비교만) — 값이 없으면 칸 없음 */ + const b05Hint = ( + key: "design_flow_m3s" | "tc_minutes", + text: (value: number) => string, + ): HTMLElement | undefined => { + const value = link.b05?.[key]; + if (value == null) return undefined; + const el = hint(text(value)); + el.title = "B05 분석"; + el.dataset.b08HydroB05 = key; + return el; + }; // ── 왼쪽 1. 홍수량 계산 ── const factor = select( @@ -332,6 +375,7 @@ export function buildHydroSheet( return label; })(), outCell("design_flow", true), + b05Hint("design_flow_m3s", (v) => `B05 ${fmt(v, 2)}`), ), row("Q : 홍수량 (㎥/s)", outCell("flow")), row("f : 유출계수", numberInput("runoff")), @@ -339,7 +383,16 @@ export function buildHydroSheet( row("A : 유역면적 (㎢)", numberInput("area_km2", "입력"), outCell("area_ha")), sub("1-2. 홍수 도달시간 — 산지부 (Kirpich)"), formula("T = 0.0663 × L^0.77^ × s^-0.385^ (L : ㎞ · T : hr)"), - row("T : 홍수 도달시간 (hr)", outCell("tc_hr"), outCell("tc_note")), + row( + "T : 홍수 도달시간 (hr)", + outCell("tc_hr"), + (() => { + const note = node("span"); + const b05 = b05Hint("tc_minutes", (v) => ` B05 ${fmt(v, 1)}분`); + note.append(outCell("tc_note"), ...(b05 ? [b05] : [])); + return note; + })(), + ), row("L : 산정에서 하천시점까지 거리 (m)", numberInput("length_m", "입력")), row("H : 산정에서 하천시점까지 표고차 (m)", numberInput("height_m", "입력")), row("s : 지표면 경사 (H / L)", outCell("slope_ground")), @@ -443,6 +496,13 @@ export function buildHydroSheet( }); right.classList.add("b08-hydro__card"); + // 「B05」 표는 그 칸 이름 옆에 + for (const [key, mark] of marks) { + const label = sheetLabel(left, right, key); + label?.append(" ", mark); + markLinked(key); + } + const status = node("p", "b08-hydro__status"); const sheet = node("div", "b08-hydro__sheet"); sheet.dataset.b08HydroSheet = stationText;