- 사전조합 상세 머리 [추가] = 고르기 창 서고 줄만(단가승격 · 사전조합 · 위 층만 · 구분 거름 없음) - 새 줄 수량 기본 손 1 · 수량 창은 손 입력만(면적산출 시트 없음) · 위로 · 아래로 · 삭제 · [저장] - 서버: 사전조합이 자기 자신을 담으면 422 「순환 — 자기 자신」 · 단가승격 상세는 한 줄 고정 그대로 - 시험 한 개 · 화면 시나리오 51-6(가짜 응답 · 서고 쓰기 없음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
176 lines
6.7 KiB
TypeScript
176 lines
6.7 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;
|
|
/** 손 입력만(서고 사전조합 줄 · 51-6) — 면적산출 시트 없음 */
|
|
handOnly?: boolean;
|
|
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.handOnly) {
|
|
// 시트 없음
|
|
} else 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,
|
|
...(opt.handOnly
|
|
? []
|
|
: [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 }),
|
|
],
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
}
|