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
@@ -84,6 +84,8 @@ export interface DetailContext {
admin?: boolean;
/** 714-3 텍스트 수식 입력 단추 — 시험 계산 입력 고침 + 다시 셈 */
onInput?: (name: string, value: string) => void;
/** 717-2b M02 단가산출 — 단가산출 상세 · 인력·재료·경비 덩이를 이 칸으로(SK 셈 답 · 고침 표) */
cover?: { ho: HTMLElement; est: HTMLElement };
}
type Group = "labor" | "material" | "cost" | "other";
@@ -526,12 +528,19 @@ export function buildLabDetail(host: HTMLElement, ctx: DetailContext): void {
...infoBox(ctx),
block(section(tl("Calc_Title"), [ctx.calcHost]), "calc"),
block(
section(tl("View_Est"), [buildEst({ text: ctx.text, unit: ctx.row.결과단위, ...fx })]),
section(tl("View_Est"), [
ctx.cover?.est ?? buildEst({ text: ctx.text, unit: ctx.row.결과단위, ...fx }),
]),
"est",
),
...(ctx.flowHost
...(ctx.flowHost && !ctx.cover
? [block(nowInputs(ctx.values), "ho"), ctx.flowHost]
: [hoBox(ctx), block(section(tl("Formula_Title"), [formula]), "formula")]),
: [
ctx.cover
? block(section(tl("Ho_Title"), [nowInputs(ctx.values), ctx.cover.ho]), "ho")
: hoBox(ctx),
block(section(tl("Formula_Title"), [formula]), "formula"),
]),
);
const own = kept?.node.dataset.own; // 기본정보 칸은 새로 만들어지므로 이름표로 다시 찾음
const back = own ? host.querySelector<HTMLInputElement>(`[data-own="${own}"]`) : kept?.node;
@@ -23,7 +23,7 @@ import { createStat, statGrid } from "@ui/ui_template_stat";
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
import { formatMoney, formatPrice } from "./M01_MasterData_UI_Logic_Money";
import { explain, loadTable, type TableRow } from "./M01_MasterData_UI_Logic_Note";
import { jumpToMaster, type MasterJump } from "./M01_MasterData_UI_Side";
import { hasMasterJump, jumpToMaster, type MasterJump } from "./M01_MasterData_UI_Side";
import { fragmentRow, type Fragment } from "./M01_MasterData_UI_LogicLab_Pill";
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
import { createText } from "@ui/ui_template_text";
@@ -180,11 +180,13 @@ const groupOf = (ref: string): string =>
ref.startsWith("LB") ? "인력" : ref.startsWith("M") ? "재료" : ref.startsWith("EQ") ? "기계" : "";
function goButton(target: MasterJump): HTMLElement {
return createButton({
const go = createButton({
label: tl("Pill_Go"),
variant: "ghost",
onClick: () => jumpToMaster(target),
});
go.hidden = !hasMasterJump();
return go;
}
/** 자재품목 값 칸 넷 — 위 가로 줄(관급은 뺌) */
+3 -2
View File
@@ -79,11 +79,12 @@ export function takePendingLogicKey(): string | null {
export type MasterJump =
{ kind: "table"; key: string } | { kind: "element"; group: string; ref: string; file?: string };
let masterJump: (target: MasterJump) => void = () => {};
let masterJump: ((target: MasterJump) => void) | null = null;
export const setMasterJump = (fn: (target: MasterJump) => void): void => {
masterJump = fn;
};
export const jumpToMaster = (target: MasterJump): void => masterJump(target);
export const jumpToMaster = (target: MasterJump): void => masterJump?.(target);
export const hasMasterJump = (): boolean => masterJump !== null; // 등록 없으면(M02) 이동 단추 숨김
/** 요소 화면에서 열 것 — 파일 · 하위 거름(구분·세부분류) · 상세구분 · 제목 */
export interface Pick {