Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Labor_Input.ts
T
eomsangdonandClaude Opus 5.5 47cbbd17bd feat(M02): 51-14 일위대가 순서 칸 2행 — 위 [위로] [아래로] / 아래 [옵션]·[수정] [삭제] · 단가승격 [옵션] · 일위조합 [수정] 서고 원본 모달
- 순서 칸 = 단추 묶음 두 줄(공용 ui-btn-group 간격) · 카드 갈래는 아래 줄만
- 단가승격 줄 [옵션] = 승격 문서 넣을 값 모달 · [저장] = 서고 저장 길 · 머리 한 줄 알림(쓰는 모든 구조물에 따라감)
- 일위조합 줄 [수정] = 사전조합 상세 모달(서고 모드 표 · 옵션 · 수량 · 순서 · 삭제 · 추가) · [저장] = 서고 원본 · 취소 = 버림 물음
- 자기 층 서고만 고침(위 층 = 읽기만) · 닫으면 구조물 일위대가 다시 셈
- 확인 시나리오 resources/tester/ui_checks/51-14_순서칸_2행_옵션수정.js

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
2026-10-05 21:05:31 +09:00

146 lines
5.4 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Labor_Input.ts
* 일위대가 입력 모달(39-3) — 단가산출 로직 줄을 누르면 그 로직 입력 전부(빈 칸 = 자동값) · 메모.
* 수량은 이 모달에 없음(43-2) — 수량산출 칸 누름 = `Labor_QtyModal`(면적산출 시트를 모달 안에).
* [확인] = 줄 값만 바꿈(화면 캐시) · 저장은 상세 [저장]. 값 칸 규칙 = `M02_Labor_Values`(한 벌).
* ========================================================================== */
import { createButton, el, showToast } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
import type { ItemInput, ItemLogic } from "./M02_MasterTemplete_Api_Fetch";
import { tc } from "./M02_MasterTemplete_Labor_Text";
import { createValueControl } from "./M02_Labor_Values";
import { keepKnown, valueHint, type LaborValues } from "./M02_Labor_Values_Model";
import "./M02_Labor_Values.css";
import "./M02_MasterTemplete_Labor.css";
const fill = (text: string, value: string): string => text.replace("{value}", value);
/** 면적산출 상세로 감 — 옮겨 갔으면 참(물음에서 취소 = 거짓) */
export type GoArea = () => Promise<boolean>;
function footer(
readOnly: boolean,
ok: () => void,
close: () => void,
okLabel = tc("In_Ok"),
): HTMLElement {
return el("div", {
className: "m02-labin__foot",
children: readOnly
? [createButton({ label: tc("In_Close"), variant: "ghost", onClick: close })]
: [
createButton({ label: tc("In_Cancel"), variant: "ghost", onClick: close }),
createButton({ label: okLabel, variant: "filled", onClick: ok }),
],
});
}
export interface LaborInputResult {
입력값: LaborValues;
메모: string | null;
}
export interface LaborInputOptions {
/** 「GF000219 메쌓기」 */
title: string;
/** 로직 입력 목록(없으면 = 못 읽음 · 메모만) */
logic: ItemLogic | null;
values: LaborValues;
memo: string | null;
readOnly: boolean;
done: (next: LaborInputResult) => void;
/** 머리 한 줄 알림(51-14 · 서고 원본 고침) */
notice?: string;
/** 확인 단추 글(기본 「확인」) */
okLabel?: string;
}
/** 담은 로직 줄 입력 모달 */
export function openLaborInput(opt: LaborInputOptions): void {
const inputs: ItemInput[] = opt.logic?.입력 ?? [];
const { kept, lost } = keepKnown(inputs, opt.values);
const draft: LaborValues = { ...kept };
const bad = new Set<string>();
openModal({
title: fill(tc("In_Title"), opt.title),
closeLabel: tc("In_Close"),
dialogClass: "m02-modal m02-labin",
mount: (body, close) => {
const rows = inputs.map((input) => {
const field = createValueControl(input, draft[input.이름] ?? null, (value, isBad) => {
if (isBad) bad.add(input.이름);
else bad.delete(input.이름);
if (value === null) delete draft[input.이름];
// 고르기 · 범위 없는 칸도 수로 읽히면 수로 둠(M01 넣을 값과 같게)
else
draft[input.이름] =
typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))
? Number(value)
: value;
});
field.control.disabled = opt.readOnly;
field.control.setAttribute("data-labor-input", input.이름);
const hint = valueHint(input);
return el("div", {
className: "m02-inputval__row",
children: [
el("span", {
className: "m02-inputval__name",
text: input.단위 ? `${input.이름} (${input.단위})` : input.이름,
attrs: { title: [input.설명, hint].filter(Boolean).join(" · ") },
}),
el("div", { className: "m02-inputval__cell", children: [field.control, field.error] }),
],
});
});
const memo = el("input", {
className: "ui-input m02-inputval__control",
attrs: { type: "text", maxlength: "200", "data-labor-memo": "" },
}) as HTMLInputElement;
memo.value = opt.memo ?? "";
memo.readOnly = opt.readOnly;
const ok = (): void => {
if (bad.size) return showToast(tc("In_Bad"), "error");
opt.done({ 입력값: { ...draft }, 메모: memo.value.trim() || null });
close();
};
body.append(
el("div", {
className: "m02-inputval",
children: [
...(opt.notice
? [
el("p", {
className: "m02-labin__muted",
text: opt.notice,
attrs: { "data-labor-notice": "" },
}),
]
: []),
...(lost.length
? [
el("p", {
className: "m02-labin__lost",
text: fill(tc("Labor_Lost"), lost.join(", ")),
}),
]
: []),
...(rows.length
? rows
: [el("p", { className: "m02-labin__muted", text: tc("In_None") })]),
el("div", {
className: "m02-inputval__row",
children: [
el("span", { className: "m02-inputval__name", text: tc("In_Memo") }),
memo,
],
}),
],
}),
footer(opt.readOnly, ok, close, opt.okLabel),
);
},
});
}