feat(M02): 47-4 일위대가 상세 오른쪽 「원문」 접는 패널 — 고른 줄 로직의 품셈 원문

- 공용 오른쪽 접는 패널(ui_template_side_panels · 47-3 부품) · 취향 키 m02-labor-side · 카드 갈래엔 없음
- 고른 줄 없음 = 안내 한 줄 · 고르면 그 로직 출처 원문(GET /m01/logic/source)
- 원문 보기를 M01_MasterData_UI_LogicLab_Source 로 떼어 M01 로직 상세와 한 벌로 씀(M01 동작 그대로)
- 입력 목록 길(GET /m02/logic-lib/inputs)에 로직 출처 칸 추가
- 시험: test_m02_labor_values 출처 확인 · 화면 시나리오 ui_checks/47-4_일위대가_원문패널.js

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 13:45:53 +09:00
co-authored by Claude Opus 5.5
parent 5f5da8119b
commit 66cb7afbab
8 changed files with 249 additions and 100 deletions
+2 -1
View File
@@ -41,7 +41,7 @@ def _logics(codes: list[str]) -> tuple[list[dict], list[str]]:
def _listed(logics: list[dict]) -> list[dict[str, Any]]:
"""로직 줄마다 `{로직, 이름, 결과단위, 입력: [{이름, 설명, 단위, 고르기?, 범위?, 자동값}]}`."""
"""로직 줄마다 `{로직, 이름, 결과단위, 출처, 입력: [{이름, 설명, 단위, 고르기?, 범위?, 자동값}]}`."""
whole = m01.loaded()[1]
out = []
for logic in logics:
@@ -57,6 +57,7 @@ def _listed(logics: list[dict]) -> list[dict[str, Any]]:
"로직": logic.get("키"),
"이름": logic.get("이름"),
"결과단위": logic.get("결과단위"),
"출처": logic.get("출처"),
"입력": specs,
}
)
@@ -313,6 +313,8 @@ export interface ItemLogic {
로직: string;
이름: string;
결과단위?: string;
/** 품셈 출처 글(47-4 · 원문 패널) */
출처?: string | null;
입력: ItemInput[];
}
@@ -6,12 +6,15 @@
* [확인] = 화면 캐시만 · 저장은 [저장]
* · 금액 = 서버 calc(기준 ③ · 최신 응답만 · 저장 안 함) · 고칠 때마다 뒤에서 다시 셈
* · 같은 로직 두 번 · 같은 자재 두 번 막음 · 빈 일위대가도 저장 됨
* · 오른쪽 「원문」 접는 패널(47-4 · 상세만 · 공용 `ui_template_side_panels`) = 고른 줄 로직의 출처 원문
* ========================================================================== */
import { createButton, createInputField, el, showToast } from "@ui/ui_template_elements";
import { createSidePanels } from "@ui/ui_template_side_panels";
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 type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
import { recalcWorkbook } from "../A00_Common/spreadsheet/spreadsheet_recalc";
import {
@@ -108,6 +111,26 @@ export function mountLaborDetail(
children: [el("summary", { text: diagramTitle() }), dgHost],
});
/** 원문 패널(47-4 · 카드 갈래엔 없음) — 열려 있을 때만 찾음 · 고른 줄 없으면 안내 한 줄 */
const source = createSourceView();
const sourceHost = el("div", { attrs: { "data-labor-source": "" } });
const side = opt.card
? null
: createSidePanels({
prefKey: "m02-labor-side",
panels: [{ id: "source", title: tc("Labor_Source"), body: sourceHost }],
onChange: () => showSource(),
});
function showSource(): void {
if (side?.current() !== "source") return;
if (!picked)
return void sourceHost.replaceChildren(
el("p", { className: "m01-logic__muted", text: tc("Labor_SourceHint") }),
);
source.show(logics.get(picked)?.출처 ?? "");
if (sourceHost.firstChild !== source.root) sourceHost.replaceChildren(source.root);
}
const lineOf = (key: string) => calc?.줄.find((l) => l.로직 === key);
const matOf = (code: string) => calc?.자재.find((m) => m.코드 === code);
@@ -157,6 +180,7 @@ export function mountLaborDetail(
),
);
drawDg();
showSource();
};
/** 서버 다시 셈 — 늦게 온 답은 버림(최신 응답만) */
@@ -269,11 +293,12 @@ export function mountLaborDetail(
[cur.담은로직[i], cur.담은로직[j]] = [cur.담은로직[j], cur.담은로직[i]];
changed();
}
/** 줄 고름(47-5) — 하이라이트만(표만 · 다이어그램 안 다시 그림) · 화면 캐시 안 바뀜 */
/** 줄 고름(47-5) — 하이라이트(표만 · 다이어그램 안 다시 그림) + 원문 패널(47-4) · 화면 캐시 안 바뀜 */
function pick(i: number): void {
picked = cur.담은로직[i].로직;
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);
showSource();
}
function removeRow(i: number): void {
if (cur.담은로직[i].로직 === picked) picked = null;
@@ -390,26 +415,30 @@ export function mountLaborDetail(
],
});
const editor = (body: HTMLElement): HTMLElement =>
side ? el("div", { className: "ui-side-panels-row", children: [body, side.root] }) : body;
host.replaceChildren(
el("div", {
className: "m01-logic__editor m01-combo m02-labor__edit",
attrs: { "data-labor": opt.column },
children: [
...(opt.card
? []
: [
el("section", {
className: "m01lab__box m01-logic__section",
children: [
el("div", { className: "m01lab__info", children: [unit.root, origin.root] }),
],
}),
]),
section(tc("Logics_Title"), opt.card ? null : addLogic, logicHost),
section(tc("Labor_Mats"), addMat, matHost),
...(opt.card ? [] : [dgBox]),
],
}),
editor(
el("div", {
className: "m01-logic__editor m01-combo m02-labor__edit",
attrs: { "data-labor": opt.column },
children: [
...(opt.card
? []
: [
el("section", {
className: "m01lab__box m01-logic__section",
children: [
el("div", { className: "m01lab__info", children: [unit.root, origin.root] }),
],
}),
]),
section(tc("Logics_Title"), opt.card ? null : addLogic, logicHost),
section(tc("Labor_Mats"), addMat, matHost),
...(opt.card ? [] : [dgBox]),
],
}),
),
);
draw();
// 로직 입력 목록 · 면적산출 이은 칸(없으면 빈 것 — 새 구조물)을 받은 뒤 첫 계산
@@ -35,6 +35,11 @@ const TEXT = {
Down: ["아래로", "Down"],
Remove: ["삭제", "Delete"],
Labor_Option: ["옵션", "Options"],
Labor_Source: ["원문", "Source"],
Labor_SourceHint: [
"단가산출 로직 줄을 고르면 그 원문이 보임",
"Pick a logic row to see its source",
],
AddLogic: ["로직 추가", "Add logic"],
NoLogic: ["단가산출 로직 없음", "No logics yet"],
Open: ["열기", "Open"],