- 725-3 Sangeun_Detail — 개정 판정(배지 · 띠)은 바탕이 있을 때만(바탕 없는 SK 지문은 이름 · 단위 따라 바뀜) - 725-4 Labor_RowModal — 단가산출로도 저장 성공 뒤 서고 목록 알림(HOPYO_BUILT) · 단가산출 나무 바로 갱신 - 725-4 LogicLab_Modal — prices 에 없는 요소(M02 더한 석공 · 합판)도 키로 찾아 단가 보임(lookup 하나로 합침) - 시험 test_717_2b 에 725 단언 하나 더함 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
252 lines
9.7 KiB
TypeScript
252 lines
9.7 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Labor_RowModal.ts
|
|
* 일위대가표 줄 누름 모달(PLAN 723-2 · 723-3 · 요구서_내역16 2장 4 · 5) — 일위대가표에서 빠진 줄 동작을 모달 안으로.
|
|
* · 로직 줄 · 단가산출(SK) 참조 줄 = 머리(이름 · 「1 ㎡ 당」) · [옵션](메모 · 켜쌓기율 · `Labor_Input`) ·
|
|
* 단가산출 상세 줄 모드(717-2b 보기 넷 · `Labor_RowSk` · 717-3 두 보기 갈음) ·
|
|
* 로직 자재(바꾸기 · 기본 · 세 칸 · `Labor_LogicMats`)
|
|
* · [적용] = 그 줄 덮개(M02 변경 항목) + 옵션(시험 입력 → 그 줄 입력값 · 규격 칸) · [단가산출로도 저장](기본 꺼짐)
|
|
* · 사용자 자재 줄 = 자재 모달(세 칸 · `Labor_MatTable` 한 줄)
|
|
* · 고침 = 그 줄 화면 캐시(저장은 [저장]) · 금액은 표가 다시 셈 → `watch` 로 받아 다시 그림
|
|
* ========================================================================== */
|
|
|
|
import { createCheckbox } from "@ui/ui_template_checkbox";
|
|
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
|
import { askConfirm, openModal } from "@ui/ui_template_modal";
|
|
import { createText } from "@ui/ui_template_text";
|
|
import type { ItemLogic } from "./M02_MasterTemplete_Api_Fetch";
|
|
import { HOPYO_BUILT } from "./M02_MasterTemplete_Hopyo_Check";
|
|
import type { LaborCalc, LaborMat, LaborRow, MatMarkKey } from "./M02_MasterTemplete_Labor_Api";
|
|
import { openLaborInput } from "./M02_MasterTemplete_Labor_Input";
|
|
import { logicMatRows } from "./M02_MasterTemplete_Labor_LogicMats";
|
|
import { setLogicMark, setMark } from "./M02_MasterTemplete_Labor_MatMarks";
|
|
import { matTable } from "./M02_MasterTemplete_Labor_MatTable";
|
|
import { mountRowSk, saveAsSk, type RowSkHandle } from "./M02_MasterTemplete_Labor_RowSk";
|
|
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
|
import type { SkScope } from "./M02_MasterTemplete_Sangeun_Cover";
|
|
import { releaseTied } from "./M02_Labor_LogicMats_Model";
|
|
import { keepKnown, valueLine } from "./M02_Labor_Values_Model";
|
|
|
|
/** 표가 다시 센 답을 받음 — 돌려준 함수 = 그만 받음 */
|
|
export type CalcWatch = (fn: (calc: LaborCalc | null) => void) => () => void;
|
|
|
|
export interface RowModalOptions {
|
|
row: LaborRow;
|
|
logic: ItemLogic | null;
|
|
title: string;
|
|
/** 일위대가 머리(이름 · 「1 ㎡ 당」) */
|
|
head: string;
|
|
/** 줄 분모(SK 단위 기본) */
|
|
unit: string;
|
|
scope: SkScope;
|
|
readOnly: boolean;
|
|
/** 셈 주입(프로젝트 복사본) — 단가산출 상세 없음 */
|
|
inject: boolean;
|
|
/** 사용자 자재 코드 — 로직 자재 [바꾸기]로 같은 품목 막음 */
|
|
userMats: string[];
|
|
watch: CalcWatch;
|
|
/** 고침 뒤 — `again` = 서버 다시 셈(입력 · 고름 · 별도계상) · 아니면 화면만 */
|
|
done: (again: boolean) => void;
|
|
}
|
|
|
|
const TEXT = {
|
|
Apply: ["적용", "Apply"],
|
|
AlsoSk: ["단가산출로도 저장", "Also save as build-up"],
|
|
Saved: ["{k} 저장함(내 개인 층)", "{k} saved (my layer)"],
|
|
Discard: ["적용 안 한 고침이 있음 — 버리고 닫을까?", "Discard unapplied edits?"],
|
|
} as const;
|
|
const t = (k: keyof typeof TEXT): string => TEXT[k][0];
|
|
|
|
export function openRowModal(o: RowModalOptions): void {
|
|
const row = o.row;
|
|
const edit = !o.readOnly;
|
|
const info = el("div", { attrs: { "data-row-head": "" } });
|
|
const mats = el("div", { attrs: { "data-row-mats": "" } });
|
|
let calc: LaborCalc | null = null;
|
|
const line = () => calc?.줄.find((l) => !row.참조 && l.로직 === row.로직);
|
|
|
|
const draw = (): void => {
|
|
const values = valueLine(o.logic?.입력 ?? [], row.입력값);
|
|
info.replaceChildren(
|
|
createText(o.head, "muted", 14),
|
|
...(values.text ? [createText(values.text)] : []),
|
|
...(values.empty
|
|
? [createText(tc("Labor_Empty").replace("{n}", String(values.empty)), "error")]
|
|
: []),
|
|
...(row.켜쌓기율 != null
|
|
? [createText(tc("Labor_Layer").replace("{n}", String(row.켜쌓기율)), "muted")]
|
|
: []),
|
|
...(row.메모 ? [createText(row.메모, "muted")] : []),
|
|
);
|
|
const lead = row.참조
|
|
? []
|
|
: logicMatRows([row], calc, !calc, {
|
|
open: () => undefined,
|
|
...(edit
|
|
? {
|
|
swap: (_r: number, at: number, key: string) => {
|
|
if (o.userMats.includes(key))
|
|
return showToast(tc("Labor_AlreadyMat").replace("{value}", key), "error");
|
|
row.고름 = { ...row.고름, [String(at)]: key };
|
|
o.done(true);
|
|
},
|
|
reset: (_r: number, at: number) => {
|
|
if (!row.고름) return;
|
|
delete row.고름[String(at)];
|
|
if (!Object.keys(row.고름).length) delete row.고름;
|
|
o.done(true);
|
|
},
|
|
mark: (_r: number, at: number, key: MatMarkKey, v: string) => {
|
|
setLogicMark(row, at, key, v);
|
|
o.done(key === "별도계상");
|
|
draw();
|
|
},
|
|
}
|
|
: {}),
|
|
});
|
|
mats.replaceChildren(
|
|
...(lead.length
|
|
? [el("h4", { text: tc("Labor_Mats") }), matTable({ mats: [], calc, lead })]
|
|
: []),
|
|
);
|
|
};
|
|
|
|
const option = createButton({
|
|
label: tc("Labor_Option"),
|
|
variant: "ghost",
|
|
onClick: () =>
|
|
openLaborInput({
|
|
title: o.title,
|
|
logic: o.logic,
|
|
values: row.입력값,
|
|
memo: row.메모,
|
|
rate: row.켜쌓기율 ?? null,
|
|
readOnly: o.readOnly,
|
|
done: (next) => {
|
|
const same = setValues(next.입력값) && next.켜쌓기율 === (row.켜쌓기율 ?? null);
|
|
row.메모 = next.메모;
|
|
if (next.켜쌓기율 === null) delete row.켜쌓기율;
|
|
else row.켜쌓기율 = next.켜쌓기율;
|
|
o.done(!same);
|
|
draw();
|
|
if (!same) skMount(); // 바뀐 옵션으로 상세 다시
|
|
},
|
|
}),
|
|
});
|
|
option.dataset.rowOption = "";
|
|
|
|
/** 옵션 → 그 줄 입력값(로직 줄만 · 아는 입력만) · 같으면 참 */
|
|
function setValues(next: Record<string, string | number>): boolean {
|
|
if (row.참조) return true;
|
|
const values = o.logic ? keepKnown(o.logic.입력, next).kept : next;
|
|
if (JSON.stringify(values) === JSON.stringify(row.입력값)) return true;
|
|
const before = row.입력값;
|
|
row.입력값 = values;
|
|
releaseTied(row, before, line()?.재료 ?? []); // 바뀐 입력에 걸린 고름 = 기본(103-1)
|
|
return false;
|
|
}
|
|
|
|
// 단가산출 상세 줄 모드(723-3) — 셈 주입(프로젝트 복사본)엔 없음
|
|
const skHost = el("div", { attrs: { "data-row-sk-host": "" } });
|
|
let sk: RowSkHandle | null = null;
|
|
const skMount = (): void => {
|
|
if (o.inject) return;
|
|
sk?.destroy();
|
|
sk = mountRowSk(skHost, { row, scope: o.scope, title: o.title, unit: o.unit, readOnly: !edit });
|
|
};
|
|
let close = (): void => {};
|
|
const finish = (): void => {
|
|
stop();
|
|
sk?.destroy();
|
|
};
|
|
const also = createCheckbox(t("AlsoSk"), { checked: false });
|
|
also.input.dataset.rowAlsoSk = "";
|
|
const apply = createButton({
|
|
label: t("Apply"),
|
|
variant: "filled",
|
|
onClick: async () => {
|
|
const got = sk?.take() ?? null;
|
|
if (got) {
|
|
if (got.patch) row.패치 = got.patch;
|
|
else delete row.패치;
|
|
setValues(got.values);
|
|
o.done(true);
|
|
draw();
|
|
}
|
|
if (also.input.checked) {
|
|
try {
|
|
const name = await saveAsSk(row, got?.patch ?? row.패치 ?? null, o.title, o.unit);
|
|
showToast(t("Saved").replace("{k}", name), "success");
|
|
document.dispatchEvent(new Event(HOPYO_BUILT)); // 좌측 단가산출 나무 다시 읽음(725-4)
|
|
} catch (error) {
|
|
return showToast(error instanceof Error ? error.message : String(error), "error");
|
|
}
|
|
}
|
|
finish(); // [적용] = 그 줄에 남기고 닫음(저장은 페이지 [저장]) · 코드 닫기는 beforeClose 안 거침
|
|
close();
|
|
},
|
|
});
|
|
apply.dataset.rowApply = "";
|
|
const stop = o.watch((c) => {
|
|
calc = c;
|
|
draw();
|
|
});
|
|
const tools = edit && !o.inject ? [also.root, apply] : [];
|
|
({ close } = openModal({
|
|
title: o.title,
|
|
closeLabel: tc("In_Close"),
|
|
width: "1100px",
|
|
wide: true,
|
|
actions: [...(row.참조 || !edit ? [] : [option]), ...tools],
|
|
beforeClose: async () => {
|
|
if (sk?.take() && !(await askConfirm({ message: t("Discard"), danger: true }))) return false;
|
|
finish();
|
|
return true;
|
|
},
|
|
mount: (body) => body.append(info, skHost, mats),
|
|
}));
|
|
draw();
|
|
skMount();
|
|
}
|
|
|
|
export interface MatModalOptions {
|
|
mat: LaborMat;
|
|
watch: CalcWatch;
|
|
readOnly: boolean;
|
|
/** 세 칸 고침 뒤 — `money` = 별도계상(금액에 닿음) */
|
|
done: (money: boolean) => void;
|
|
}
|
|
|
|
/** 사용자 자재 줄 — 세 칸(148-4 · 222-1) · 가격 다섯 보기 */
|
|
export function openMatModal(o: MatModalOptions): void {
|
|
const host = el("div", { attrs: { "data-row-mat": o.mat.코드 } });
|
|
let calc: LaborCalc | null = null;
|
|
const draw = (): void =>
|
|
host.replaceChildren(
|
|
matTable(
|
|
{ mats: [o.mat], calc },
|
|
o.readOnly
|
|
? { open: () => undefined }
|
|
: {
|
|
open: () => undefined,
|
|
mark: (_i, key, v) => {
|
|
setMark(o.mat, key, v);
|
|
o.done(key === "별도계상");
|
|
draw();
|
|
},
|
|
},
|
|
),
|
|
);
|
|
const stop = o.watch((c) => {
|
|
calc = c;
|
|
draw();
|
|
});
|
|
openModal({
|
|
title: `${o.mat.코드} ${o.mat.이름 ?? ""}`.trim(),
|
|
closeLabel: tc("In_Close"),
|
|
width: "1100px",
|
|
beforeClose: () => (stop(), true),
|
|
mount: (body) => body.append(host),
|
|
});
|
|
draw();
|
|
}
|