From baeaa26702b38d93018dda7467de3a2d96273770 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 14 Sep 2026 02:01:18 +0900 Subject: [PATCH] =?UTF-8?q?refactor(b09):=20=EC=98=9B=20=ED=83=AD=20?= =?UTF-8?q?=EC=98=AE=EA=B8=B0=EA=B8=B0=20=E2=91=A1=20=EC=82=B0=EC=B6=9C?= =?UTF-8?q?=EA=B8=B0=EC=B4=88=20=E2=80=94=20=EC=83=88=20=ED=8B=80=20?= =?UTF-8?q?=ED=83=AD=20=ED=8C=8C=EC=9D=BC=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 화면 확인: 옮기기 전후 같음 — 표 3 · 줄 192 · 판 12 · 근거 공종 92 · 못 채운 자리 88 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq --- B09_Estimation/B09_Estimation_UI_Shell.ts | 3 +- .../B09_Estimation_UI_Tab_BasisSheet.ts | 120 ++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts diff --git a/B09_Estimation/B09_Estimation_UI_Shell.ts b/B09_Estimation/B09_Estimation_UI_Shell.ts index 41858b0f..f64ddf0d 100644 --- a/B09_Estimation/B09_Estimation_UI_Shell.ts +++ b/B09_Estimation/B09_Estimation_UI_Shell.ts @@ -24,6 +24,7 @@ import { priceCompareTab } from "./B09_Estimation_UI_Tab_PriceCompare"; import { summaryTab } from "./B09_Estimation_UI_Tab_Summary"; import { listsTab } from "./B09_Estimation_UI_Tab_Lists"; import { designDocTab } from "./B09_Estimation_UI_Tab_DesignDoc"; +import { basisSheetTab } from "./B09_Estimation_UI_Tab_BasisSheet"; /** 탭 등록 — 한 줄에 탭 하나. 옛 탭(`legacyTab`)은 새 탭 파일이 서면 그 줄만 바꿈. */ const TABS: B09Tab[] = [ @@ -39,7 +40,7 @@ const TABS: B09Tab[] = [ legacyTab("supply", "B09_Estimation_Tab_Supply"), legacyTab("base_data", "B09_Estimation_Tab_BaseData"), designDocTab, - legacyTab("basis_sheet", "B09_Estimation_Tab_BasisSheet"), + basisSheetTab, ]; export async function renderB09Estimation(root: HTMLElement): Promise { diff --git a/B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts b/B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts new file mode 100644 index 00000000..3b8a1c0d --- /dev/null +++ b/B09_Estimation/B09_Estimation_UI_Tab_BasisSheet.ts @@ -0,0 +1,120 @@ +/* ============================================================================= + * B09_Estimation_UI_Tab_BasisSheet.ts + * B09 산출기초 탭 — 줄에 달린 근거를 한 장으로 모은 장(별표2 (5)(가) 열셋째) + * + * - 옛 `B09_Estimation_UI_BaseData.ts` 의 산출기초를 그대로 옮김(PLAN 12장 옛 탭 옮기기). + * - ⚠ 여기서 값을 다시 계산하지 않음 — 모으기만 함. 네 구획: 판 · 고른 값 · 공종별 근거 · 못 채운 자리. + * ========================================================================== */ + +import { API_BASE_URL } from "@config/config_frontend"; +import type { ProvenancePayload } from "@ui/ui_template_provenance"; +import type { B09Tab } from "./B09_Estimation_UI_Shell_Types"; +import { L, hint } from "./B09_Estimation_UI_Sheet"; +import { head, infoTable, note } from "./B09_Estimation_UI_Table"; + +interface BasisSheetDto { + provenance?: ProvenancePayload; + note: string; + summary: string; + dataset_versions: Array<{ + dataset_id: string; + file: string; + effective_date: string; + sha256: string; + }>; + chosen_conditions: Array<{ item: string; value: string }>; + work_items: Array<{ code: string; name: string; unit: string; notes: string[] }>; + gaps: Array<{ kind: string; code: string; reason: string }>; +} + +function draw(body: HTMLElement, data: BasisSheetDto): void { + const sheets = data.provenance?.sheets; + body.append(head("산출기초")); + body.append(note(data.summary)); + body.append(note(data.note)); + + body.append(head(`① 어느 판으로 계산했나 (${data.dataset_versions.length})`)); + body.append( + infoTable( + ["자료", "파일", "기준일", "지문(앞 12)"], + data.dataset_versions.map((row) => [ + row.dataset_id, + row.file, + row.effective_date, + row.sha256 || "—", + ]), + [0, 1, 2, 3], + ["dataset_id", "file", "effective_date", "sha256"], + sheets?.basis_versions, + ), + ); + + body.append(head(`② 무엇을 골랐나 (${data.chosen_conditions.length})`)); + if (data.chosen_conditions.length === 0) { + body.append(note("고른 값이 없습니다 — 전부 확정 기본값으로 돌고 있습니다.")); + } else { + body.append( + infoTable( + ["항 목", "고른 값"], + data.chosen_conditions.map((row) => [row.item, row.value]), + [0, 1], + ["item", "value"], + sheets?.basis_chosen, + ), + ); + } + + body.append(head(`③ 공종마다 무엇을 근거로 했나 (${data.work_items.length})`)); + body.append( + infoTable( + ["코드", "공 종", "단위", "근 거"], + data.work_items.map((row) => [row.code, row.name, row.unit, row.notes.join(" · ")]), + [0, 1, 2, 3], + ["code", "name", "unit", "notes"], + sheets?.basis_items, + ), + ); + + body.append(head(`④ 못 채운 자리 (${data.gaps.length})`)); + if (data.gaps.length === 0) { + body.append(note("못 채운 자리가 없습니다.")); + return; + } + body.append( + infoTable( + ["갈 래", "코드", "사 유"], + data.gaps.map((row) => [row.kind, row.code, row.reason]), + [0, 1, 2], + ["kind", "code", "reason"], + sheets?.basis_gaps, + ), + ); + body.append(note("⚠ 여기 있는 것은 0 으로 때우지 않고 남겨 둔 자리입니다.")); +} + +export const basisSheetTab: B09Tab = { + key: "basis_sheet", + label: () => L("B09_Estimation_Tab_BasisSheet"), + render(ctx) { + if (!ctx.projectId) { + ctx.body.append(hint(L("B09_Sheet_NoProject"))); + return; + } + // 고른 값이 바뀌면 근거도 바뀜 — 고를 때마다 새로 받음. + ctx.body.append(hint(L("B09_Sheet_Loading"))); + fetch(`${API_BASE_URL}/projects/${encodeURIComponent(ctx.projectId)}/estimation/basis-sheet`, { + credentials: "include", + }) + .then((response) => { + if (!response.ok) throw new Error(String(response.status)); + return response.json() as Promise; + }) + .then((data) => { + ctx.body.replaceChildren(); + draw(ctx.body, data); + }) + .catch((error: Error) => { + ctx.body.replaceChildren(hint(`${L("B09_Sheet_LoadFailed")} ${error.message}`, true)); + }); + }, +};