- 새 Labor_QtyModal.ts(로직 줄 수량산출 칸 · 자재 줄 같음 · [확인] = 손 · 고른 칸 · 풀기 · [닫기] = 그대로) · 두 단계 모달([엑셀에서 고르기]) 없앰 - AreaCellPick: 시트 부분을 mountAreaSheet 로 뗌(읽기만 · 실제 값 · 이은 칸 색 · 함께 잇기 · 바꾸기 물음 그대로) · 모달 pickAreaCell 지움 - Labor_Detail: openQty · openMat → openQtyFor · linkCell · Labor_Input: 수량 칸 · openQtyModal 지움(입력값 · 메모만) - 화면 판정 시나리오 resources/tester/ui_checks/43-1_수량산출_모달.js · 시험 test_m02_area_panel 부품 기대값 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
171 lines
6.5 KiB
TypeScript
171 lines
6.5 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Labor_QtyModal.ts
|
|
* 일위대가 수량산출 모달(43-1) — 수량산출 칸 누름(로직 줄 · 자재 줄 같음) = 모달 하나.
|
|
* · 위 = 지금 수량(손 · 칸 · —) · 손 입력 칸 · [연결 풀기]
|
|
* · 아래 = 그 열 면적산출 시트를 모달 안에 바로(읽기만 · 실제 값 · 이은 칸 색 · `mountAreaSheet`) ·
|
|
* 칸 누름 = 그 칸을 고름(함께 잇기 · 바꾸기 물음 그대로)
|
|
* · [확인] = 손 값이 있으면 손 · 아니면 고른 칸으로 잇기 · 풀기 · [닫기] = 그대로
|
|
* 잇기 · 풀기는 화면 문서만(저장은 일위대가 [저장] 때 면적 문서와 함께 · `Labor_Detail`).
|
|
* ========================================================================== */
|
|
|
|
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
|
import type { Layer } from "./M02_MasterTemplete_Api_Fetch";
|
|
import { mountAreaSheet, type AreaCellPick } from "./M02_MasterTemplete_AreaCellPick";
|
|
import type { GoArea } from "./M02_MasterTemplete_Labor_Input";
|
|
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
|
import "./M02_Labor_Values.css";
|
|
import "./M02_MasterTemplete_Labor.css";
|
|
|
|
/** 모달 답 — 손 · 칸 따름(손 지움) · 고른 칸으로 잇기 · 연결 풀기 · 그대로 */
|
|
export type QtyResult =
|
|
| { kind: "hand"; 손: number }
|
|
| { kind: "follow" }
|
|
| { kind: "cell"; pick: AreaCellPick }
|
|
| { kind: "unlink" }
|
|
| { kind: "keep" };
|
|
|
|
export interface QtyModalOptions {
|
|
/** 「GF000219 돌쌓기 메쌓기(인력)」 */
|
|
title: string;
|
|
/** 지금 수량 글(「손 5 ㎥」 · 「B6 = 12.5」 · 「—」) */
|
|
now: string;
|
|
/** 지금 칸에 이어져 있나 — [연결 풀기] */
|
|
linked: boolean;
|
|
/** 지금 손 값(없으면 null) */
|
|
hand: number | null;
|
|
/** 손 입력 단위(분모 · 자재 단위) */
|
|
unit: string;
|
|
readOnly: boolean;
|
|
/** 그 열 면적산출 — `pill` 이 없으면(계산 전) 시트 대신 안내 */
|
|
sheet: { doc: Workbook | null; layer: Layer; column: string; pill: string | null };
|
|
/** [면적산출 상세로] — 없으면 단추 없음 */
|
|
goArea: GoArea | null;
|
|
done: (result: QtyResult) => void;
|
|
}
|
|
|
|
export function openQtyModal(opt: QtyModalOptions): void {
|
|
let pending: QtyResult = { kind: "keep" };
|
|
let sheet: { destroy: () => void } | null = null;
|
|
openModal({
|
|
title: `${tc("Labor_QtyCol")} — ${opt.title}`,
|
|
closeLabel: tc("In_Close"),
|
|
dialogClass: "m02-modal m02-labin m02-labqty",
|
|
beforeClose: () => (sheet?.destroy(), true),
|
|
mount: (body, closeNow) => {
|
|
const finish = (): void => {
|
|
sheet?.destroy();
|
|
closeNow();
|
|
};
|
|
const now = el("span", {
|
|
className: "m02-labqty__now",
|
|
text: opt.now,
|
|
attrs: { "data-labor-qty-now": "" },
|
|
});
|
|
const state = el("small", {
|
|
className: "m02-labin__muted",
|
|
attrs: { "data-labor-qty-state": "" },
|
|
});
|
|
const hand = el("input", {
|
|
className: "ui-input m02-inputval__control",
|
|
attrs: {
|
|
type: "number",
|
|
step: "any",
|
|
"data-labor-qty": "hand",
|
|
"aria-label": tc("In_QtyHand"),
|
|
},
|
|
}) as HTMLInputElement;
|
|
hand.readOnly = opt.readOnly;
|
|
if (opt.hand !== null) hand.value = String(opt.hand);
|
|
const unlink =
|
|
opt.linked && !opt.readOnly
|
|
? createButton({
|
|
label: tc("Labor_Unlink"),
|
|
variant: "ghost",
|
|
onClick: () => {
|
|
pending = { kind: "unlink" };
|
|
hand.value = "";
|
|
state.textContent = tc("Labor_UnlinkPending");
|
|
},
|
|
})
|
|
: null;
|
|
const go = opt.goArea
|
|
? createButton({
|
|
label: tc("In_QtyGoArea"),
|
|
variant: "ghost",
|
|
onClick: () =>
|
|
void opt.goArea?.().then((moved) => {
|
|
if (moved) finish();
|
|
}),
|
|
})
|
|
: null;
|
|
const host = el("div", {
|
|
className: "m02-cellpick__sheet",
|
|
attrs: { "data-labor-qty-sheet": "" },
|
|
});
|
|
if (opt.sheet.pill) {
|
|
sheet = mountAreaSheet(host, {
|
|
doc: opt.sheet.doc,
|
|
layer: opt.sheet.layer,
|
|
column: opt.sheet.column,
|
|
pill: opt.sheet.pill,
|
|
onPick: (pick) => {
|
|
if (opt.readOnly) return;
|
|
pending = { kind: "cell", pick };
|
|
hand.value = ""; // 칸을 고르면 손 값은 지움(칸을 따름)
|
|
state.textContent = tc("Labor_CellPending").replace("{value}", pick.칸);
|
|
},
|
|
});
|
|
} else
|
|
host.replaceChildren(
|
|
el("p", { className: "m02-labin__muted", text: tc("Labor_PickWait") }),
|
|
);
|
|
hand.addEventListener("input", () => {
|
|
if (hand.value.trim() !== "") state.textContent = "";
|
|
});
|
|
const ok = (): void => {
|
|
const raw = hand.value.trim();
|
|
if (raw !== "") {
|
|
const v = Number(raw);
|
|
if (!Number.isFinite(v)) return showToast(tc("In_QtyNeed"), "error");
|
|
opt.done({ kind: "hand", 손: v });
|
|
} else if (pending.kind !== "keep") opt.done(pending);
|
|
else if (opt.hand !== null) opt.done({ kind: "follow" }); // 손 값을 지움 = 칸을 따름
|
|
finish();
|
|
};
|
|
body.append(
|
|
el("div", {
|
|
className: "m02-labqty__head",
|
|
children: [
|
|
el("span", { className: "m02-inputval__name", text: tc("Labor_QtyNow") }),
|
|
now,
|
|
el("label", {
|
|
className: "m02-labin__qty-line",
|
|
children: [
|
|
el("span", { text: tc("In_QtyHand") }),
|
|
hand,
|
|
el("small", { text: opt.unit }),
|
|
],
|
|
}),
|
|
...(unlink ? [unlink] : []),
|
|
...(go ? [go] : []),
|
|
],
|
|
}),
|
|
state,
|
|
el("p", { className: "m02-cellpick__hint", text: tc("Labor_SheetHint") }),
|
|
host,
|
|
el("div", {
|
|
className: "m02-labin__foot",
|
|
children: opt.readOnly
|
|
? [createButton({ label: tc("In_Close"), variant: "ghost", onClick: finish })]
|
|
: [
|
|
createButton({ label: tc("In_Close"), variant: "ghost", onClick: finish }),
|
|
createButton({ label: tc("In_Ok"), variant: "filled", onClick: ok }),
|
|
],
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
}
|