feat(M02): 50-4 일위대가 줄 누르면 표 아래 상세 · 구분 열

- 서버 calc 답 줄마다 그 로직 재료 · 노무 · 경비 「줄」 실음(버리던 것)
- 상세 · 카드 표 줄 누름 = 표 아래 M01 시험계산 표(answerView 다시 씀) + 반영 합 · 한 줄만 · 같은 줄 다시 누름 = 닫힘
- 이름 왼쪽 「구분」 열 — 키 머리에서 읽음(SP- 단가승격 · SC- 일위조합 · 그 밖 단가산출 · 저장 안 함)
- 시험: 줄 실림 · 합 = 기준 · ui_checks 50-4 시나리오

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
2026-10-05 18:41:23 +09:00
co-authored by Claude Opus 5.5
parent 3e572ef210
commit 7b28cb42d3
9 changed files with 235 additions and 9 deletions
@@ -2,7 +2,8 @@
* M02_MasterTemplete_Labor_Detail.ts
* 일위대가 상세(39-3) — 구조물 열마다 하나(`labor/<열>.json`) · UI_Main 종류 `labor` 가 이 부품을 붙임
* (양식 길 읽기 · [저장] · [다시 읽기]는 UI_Main). 차례 = 정보(단위 · 본뜬) → 담은 로직 → 자재목록 → 다이어그램.
* · 담은 로직 줄 누름 = 고름(하이라이트 · 다시 그려도 유지 · 47-5) · [옵션] = 입력 모달(`Labor_Input`) ·
* · 담은 로직 줄 누름 = 고름(하이라이트 · 다시 그려도 유지 · 47-5) + 표 아래 그 줄 상세(50-4 · M01 시험계산 표) ·
* 같은 줄 다시 누르면 닫힘 · [옵션] = 입력 모달(`Labor_Input`) ·
* [확인] = 화면 캐시만 · 저장은 [저장]
* · 금액 = 서버 calc(기준 ③ · 최신 응답만 · 저장 안 함) · 고칠 때마다 뒤에서 다시 셈
* · 같은 로직 두 번 · 같은 자재 두 번 막음 · 빈 일위대가도 저장 됨
@@ -15,6 +16,9 @@ import { ROUTES } from "@config/config_frontend";
import { navigateTo } from "../A00_Common/router";
import { setPendingLogicKey } from "../M01_MasterData/M01_MasterData_UI_Side";
import { createSourceView } from "../M01_MasterData/M01_MasterData_UI_LogicLab_Source";
import { answerView } from "../M01_MasterData/M01_MasterData_UI_Logic_Calc";
import type { CalcAnswer } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
import { formatMoney } from "../M01_MasterData/M01_MasterData_UI_Logic_Money";
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
import { recalcWorkbook } from "../A00_Common/spreadsheet/spreadsheet_recalc";
import {
@@ -24,7 +28,7 @@ import {
type Layer,
} from "./M02_MasterTemplete_Api_Fetch";
import { linkAreaCell } from "./M02_MasterTemplete_AreaCellPick";
import { settle } from "./M02_MasterTemplete_Labor_Calc";
import { MONEY_KEYS, settle } from "./M02_MasterTemplete_Labor_Calc";
import {
areaLinks,
calcLabor,
@@ -134,6 +138,46 @@ export function mountLaborDetail(
const lineOf = (key: string) => calc?.줄.find((l) => l.로직 === key);
const matOf = (code: string) => calc?.자재.find((m) => m.코드 === code);
/** 고른 줄 상세(50-4) — M01 시험계산 표(재료 · 노무 · 경비 줄 + 기준 합) + 반영 합 · 고른 줄 없으면 비움 */
const lineDet = el("div", {
className: "m02-labor__linedet",
attrs: { "data-labor-linedet": "" },
});
function showLine(): void {
if (!picked) return void lineDet.replaceChildren();
const logic = logics.get(picked);
const head = el("h4", {
text: tc("Labor_LineDetail").replace("{value}", `${picked} ${logic?.이름 ?? ""}`.trim()),
});
const line = lineOf(picked);
if (!line)
return void lineDet.replaceChildren(
head,
el("p", { className: "m01-logic__muted", text: fail ?? "…" }),
);
const answer: CalcAnswer = line.까닭
? { ok: false, reason: line.까닭 }
: { ok: true, lines: line.줄, sums: line.기준 ?? undefined, result: line.결과, middle: {} };
// 반영 = 기준 × 수량 ÷ 배(표 아래 줄과 같음) · 수량 없으면 「—」 · 돈 아닌 로직은 없음
const applied = line.기준
? [
el("h4", { text: tc("Labor_LineApplied") }),
el("dl", {
className: "m01-logic__sums",
children: MONEY_KEYS.flatMap((k) => [
el("dt", { text: k === "계" ? tc("Prev_Sum") : k }),
el("dd", {
className: "m01-logic__money",
text: line.반영 ? formatMoney(line.반영[k]) : "—",
}),
]),
}),
]
: [];
const money = (logic?.결과단위 ?? "").startsWith("원");
lineDet.replaceChildren(head, ...answerView(answer, false, money), ...applied);
}
const drawDg = (): void => {
if (opt.card || !dgBox.open) return; // 카드 갈래엔 다이어그램 없음
const data = buildLaborDiagram({
@@ -172,7 +216,9 @@ export function mountLaborDetail(
}
: { open: pick, option: openRow },
),
lineDet,
);
showLine();
matHost.replaceChildren(
matTable(
{ mats: cur.자재, calc, fail, cells: cells() },
@@ -293,11 +339,14 @@ export function mountLaborDetail(
[cur.담은로직[i], cur.담은로직[j]] = [cur.담은로직[j], cur.담은로직[i]];
changed();
}
/** 줄 고름(47-5) — 하이라이트(표만 · 다이어그램 안 다시 그림) + 원문 패널(47-4) · 화면 캐시 안 바뀜 */
/** 줄 고름(47-5) — 하이라이트(표만 · 다이어그램 안 다시 그림) + 줄 상세(50-4) + 원문 패널(47-4) ·
* 고른 줄 다시 누름 = 닫음 · 화면 캐시 안 바뀜 */
function pick(i: number): void {
picked = cur.담은로직[i].로직;
const key = cur.담은로직[i].로직;
picked = picked === key ? null : key;
for (const tr of logicHost.querySelectorAll<HTMLElement>("[data-labor-logic],[data-labor-opt]"))
tr.classList.toggle("is-selected", (tr.dataset.laborLogic ?? tr.dataset.laborOpt) === picked);
showLine();
showSource();
}
function removeRow(i: number): void {