feat(724-1): M02 단가산출 상세 · 일위대가 줄 모달 = 722 공용 상세(LogicLab_Ho) — 줄 모달 그 자리 고침 → M02 덮개 · 옛 고침 표 둘 · 이름 같은 모델 파일 걷음

- SK 셈 답 → 공용 상세 줄(logic/M02_Sangeun_Cover_Model.hoView · 정체 차례 · 바탕 줄 그대로 · 더한줄) · 고침 고리 HoEdit → setLineCover(덮개 · 더한줄 · 열린입력 = 배율)
- HoEdit 덧칸 more(줄 빼기 · 별도계상) · M02 = 덧줄 율 고침 · 꼬리표 「변경 항목」 · 뺀 줄 띠 = Sangeun_Cover.removedStrip
- 시험 계산 · 텍스트 수식 = 바탕 로직 + 덮개 품 · 단가 사본(applyCovers) · 뺀 바탕 줄 글 거름(dropText — 같은 이름 더한줄이 안 받음)
- 717-3 openRowSangeun = 줄 모드 상세(mountRowSk) 얇은 모달(Labor_Detail 부름 그대로) · skCoverTable · coverTable · 뿌리 M02_Sangeun_Cover_Model.ts 지움(Labor_Api import 한 줄)
- 낡은 시험: test_717_2b(setQty · setPrice → setLineCover · hoView · dropText) · test_717_3(모델 경로 · setCover · priceKey · sheetGroups 걷힘 → cleanPatch · 화면 이음 새 꼴)
- 안내 글(M02_SangeunBaseViews) = 덮개 사본 반영

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 23:15:16 +09:00
co-authored by Claude Opus 5.5
parent c42c454c00
commit 82e6e8584b
11 changed files with 530 additions and 779 deletions
+33 -20
View File
@@ -111,6 +111,7 @@ const TEXT = {
Fold: ["누르면 펼침", "Click to expand"],
Per: ["{n} — {v} 원/{u}", "{n} — {v} KRW/{u}"],
Edited: ["시험 고침", "Trial edit"],
EditedM02: ["변경 항목", "Override"],
} as const satisfies Record<string, readonly [string, string]>;
const th = (k: keyof typeof TEXT): string => TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0];
const boxName = (b: string): string => (b in TEXT ? th(b as keyof typeof TEXT) : b);
@@ -120,8 +121,14 @@ export interface HoEdit {
covers: Record<number, LineCover>;
set: (idx: number, cover: LineCover | null) => void;
scope?: "m01" | "m02";
/** (M02) 모달 고침 칸 아래 덧 동작(줄 빼기 · 별도계상 등) */
more?: (idx: number, close: () => void) => HTMLElement[];
}
/** 고친 줄 꼬리표 — M01 = 시험 고침 · M02 = 변경 항목 · 고침 없음 = "" */
const editTag = (ctx: HoContext, idx: number): string =>
ctx.edit?.covers[idx] ? th(ctx.edit.scope === "m02" ? "EditedM02" : "Edited") : "";
export interface HoContext {
row: LogicRow;
prices: Record<string, ElementBrief | null>;
@@ -266,22 +273,28 @@ function modalOf(e: Entry, ctx: HoContext, extra: { cond?: PickCond; item?: HoLi
...(item?.종류 === "재료" && ctx.calcHost
? { onPick: (it) => pickMaterial(ctx.calcHost!, ctx.values, e.idx, it) }
: {}),
...(edit && item && !e.extra
// 덧줄(%) = M02 덮개만 율 고침(M01 시험 값은 호표 줄만)
...(edit && (e.extra ? edit.scope === "m02" : item)
? {
edit: (close: () => void) =>
coverForm({
qty: line ? line.수량 : null,
prices: e.logic
? {
labor: perUnit("노무비"),
material: perUnit("재료비"),
expense: perUnit("경비"),
}
: { [e.part]: line ? line.단가 : null },
edit: (close: () => void) => {
const form = coverForm({
qty: line ? pctView(line).qty : null,
prices: e.extra
? {}
: e.logic
? {
labor: perUnit("노무비"),
material: perUnit("재료비"),
expense: perUnit("경비"),
}
: { [e.part]: line ? line.단가 : null },
cover: edit.covers[e.idx],
scope: edit.scope,
onApply: (c) => (close(), edit.set(e.idx, c)),
}),
});
const more = edit.more?.(e.idx, close) ?? [];
return more.length ? el("div", { children: [form, ...more] }) : form;
},
}
: {}),
});
@@ -429,23 +442,23 @@ function formulaCell(e: Entry, ctx: HoContext, text = ""): HTMLElement {
return cell;
}
function nameCell(e: Entry, label: string, covered: boolean): HTMLElement {
function nameCell(e: Entry, label: string, tag: string): HTMLElement {
return el("div", {
children: [
el("div", { text: label }),
...(e.share && label === e.name ? [createBadge(`${e.share} ${tl("Share")}`, "accent")] : []),
...(e.spec ? [el("div", { className: "ui-text ui-text--muted", text: e.spec })] : []),
...(e.warn ? [createBadge(`${tx("Calc_UnitWarn")} · ${e.warn}`, "warning")] : []),
...(covered ? [createBadge(th("Edited"), "accent")] : []),
...(tag ? [createBadge(tag, "accent")] : []),
],
});
}
/** 보통 줄 · 몫 줄 — 누르면 그 줄 모달 */
function lineRow(e: Entry, ctx: HoContext, label = e.name): HTMLTableRowElement {
const covered = !!ctx.edit?.covers[e.idx];
const tag = editTag(ctx, e.idx);
const tr = tableRow(COLS, [
nameCell(e, label, covered && !e.share),
nameCell(e, label, e.share ? "" : tag),
e.qtyValue,
e.unit,
formulaCell(e, ctx),
@@ -454,7 +467,7 @@ function lineRow(e: Entry, ctx: HoContext, label = e.name): HTMLTableRowElement
]);
tr.classList.add("m01lab__line");
if (e.extra) tr.classList.add("m01lab__line--extra");
if (covered) tr.classList.add("is-changed");
if (tag) tr.classList.add("is-changed");
tr.tabIndex = 0;
tr.dataset.line = e.name;
tr.addEventListener("click", () => e.open(e));
@@ -486,9 +499,9 @@ function mergedRows(m: HoMerged<Entry>, ctx: HoContext): HTMLTableRowElement[] {
.replace("{a}", share("노무비"))
.replace("{b}", share("재료비"))
.replace("{c}", share("경비"));
const covered = !!ctx.edit?.covers[m.idx];
const tag = editTag(ctx, m.idx);
const head = tableRow(COLS, [
nameCell(first, `▸ ${first.name}`, covered),
nameCell(first, `▸ ${first.name}`, tag),
first.qtyValue,
first.unit,
formulaCell(first, ctx, short),
@@ -496,7 +509,7 @@ function mergedRows(m: HoMerged<Entry>, ctx: HoContext): HTMLTableRowElement[] {
money(m.금액),
]);
head.classList.add("m01lab__line");
if (covered) head.classList.add("is-changed");
if (tag) head.classList.add("is-changed");
head.dataset.equip = first.name;
head.title = th("Fold");
head.tabIndex = 0;