feat(717-2b): M02 단가산출 상세 · 고침 — M01 상세 부품(보기 넷)을 M02 길로 · 품 · 단가 칸 덮개 · 줄 더하기 · 빼기 · 바탕 M01 로직 고름

- 새 Sangeun_Detail(mountSangeunDetail) · Sangeun_Cover(고침 표 · 셈 길 최신 응답만) · logic/M02_Sangeun_Cover_Model(순수 꼴)
- 고친 칸 = 칸 색 + 바탕 값 곁 글 + 「단가산출에서 고침」 · M01 개정 배지(바탕판 · 원값) · 짝잃음 띠 · 별도계상 재료 = 계 밖 표기
- M01 buildLabDetail 덧칸 cover(단가산출 상세 · 인력·재료·경비 덩이 갈음) · 마스터 이동 단추 = 등록 없으면 숨김(hasMasterJump)
- Sangeun_View 자리표 · 옛 글 열쇠(준비 중 · 덮개 · 더한 줄 · 열린 입력) 걷음
- 시험 test_717_2b_sangeun_detail 3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 17:23:02 +09:00
co-authored by Claude Opus 5.5
parent 54ab449a9c
commit 53bca68ea4
10 changed files with 1136 additions and 56 deletions
@@ -1,61 +1,22 @@
/* =============================================================================
* M02_MasterTemplete_Sangeun_View.ts
* 단가산출(SK) 상세 자리(717-2a) — 편집 칸 종류 sangeun 이 여는 곳(UI_Main 갈래 한 줄).
* 지금 = 이름 · 단위 · 바탕 · 고친 줄 요약(덮개 · 더한 줄 · 뺀 줄 · 열린 입력) + 상세 자리표.
* 717-2b(sub2) = 자리표 줄을 `mountSangeunDetail(slot, doc, opt)`(`Sangeun_Detail.ts`)로 갈음 ·
* [저장]은 그 손잡이 `getDoc` · 고침 알림은 `opt.onChanged`. 지금 [저장] = 읽은 문서 그대로.
* 717-2b = 상세 · 고침(`Sangeun_Detail.ts` mountSangeunDetail) · [저장]은 그 손잡이 `getDoc` · 고침 알림 `opt.onChanged`.
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createEmpty } from "@ui/ui_template_empty";
import { createKv } from "@ui/ui_template_kv";
import { t as L } from "@ui/ui_template_locale";
import { tc } from "./M02_MasterTemplete_Labor_Text";
import { mountSangeunDetail } from "./M02_MasterTemplete_Sangeun_Detail";
import type { Selection } from "./M02_MasterTemplete_UI_Main";
/** SK 문서 중 이 자리가 읽는 칸(꼴 = 서버 `M02_Sangeun_Doc`) */
export interface SangeunDoc {
키?: string;
이름?: string;
단위?: string;
바탕?: { 로직?: string; 참조?: { 키?: string } } | null;
덮개?: Record<string, unknown>;
더한줄?: unknown[];
뺀줄?: unknown[];
열린입력?: string[];
}
/** 고름(층 · 주인 · SK 키 = name) + 읽기만 · 고침 알림 */
export type SangeunViewOptions = Pick<Selection, "layer" | "owner" | "name"> & {
readOnly: boolean;
onChanged: (dirty: boolean) => void;
};
/** 바탕 한 줄 — 로직 키 · 참조 SK 키 · 없으면 「바탕 없음」 */
export const baseText = (doc: SangeunDoc): string =>
doc.바탕?.로직 ?? doc.바탕?.참조?.키 ?? L("M02_SangeunNoBase");
export function mountSangeunView(
host: HTMLElement,
doc: unknown,
_opt: SangeunViewOptions,
opt: SangeunViewOptions,
): { getDoc: () => unknown; destroy: () => void } {
const sk = (doc ?? {}) as SangeunDoc;
const count = (v: unknown[] | Record<string, unknown> | undefined): number =>
Array.isArray(v) ? v.length : Object.keys(v ?? {}).length;
const head = createKv([
{ label: L("M02_Name"), value: sk.이름 ?? "" },
{ label: tc("Lib_Unit"), value: sk.단위 ?? "" },
{ label: L("M02_SangeunBase"), value: baseText(sk) },
{ label: L("M02_SangeunCover"), value: count(sk.덮개) },
{ label: L("M02_SangeunAdded"), value: count(sk.더한줄) },
{ label: L("M02_SangeunRemoved"), value: count(sk.뺀줄) },
{ label: L("M02_SangeunOpened"), value: (sk.열린입력 ?? []).join(" · ") || null },
]);
const slot = el("div", {
attrs: { "data-sangeun-detail": sk.키 ?? "" },
children: [createEmpty({ text: L("M02_SangeunDetailSoon") })],
});
host.replaceChildren(el("div", { attrs: { "data-sangeun-view": "" }, children: [head, slot] }));
return { getDoc: () => doc, destroy: () => host.replaceChildren() };
return mountSangeunDetail(host, doc, { ...opt, owner: opt.owner ?? null });
}