Files
Aislo/M01_MasterData/M01_MasterData_UI_LogicLab_Cover.ts
T
eomsangdonandClaude Sonnet 5.5 39338d9e33 refactor(840-1): 상세 보기에만 있던 기능 넷 판정 · 죽은 코드 걷음 — 수식 보기가 대신하는 것 지움
- 판정 = 자체 로직 편집 · 시험 값 고침 · M02 줄 덮음 · 비목 박스 소계 모두 수식 보기로 됨(살릴 것 없음)
- M01 상세: 숨은 단가산출 상세 덩이 · 자체 로직 시트 편집기 · 시험 고침 · 셈 구동(createDrive) · 보기 이름표(View.ts) · data-view CSS 걷음
- M02 불러오기 모달 머리 [항목 추가] · [율 줄 추가] 단추와 그 창(Rate.ts · openSangeunLinePicker) 걷음
- 글 정리 = Info_Own 새 뜻 · 죽은 글 키 · No_Key · 항목 추가 글 걷음
- 낡은 시험 고침 = 722(시험 고침 · 덮기 셈) · 799-2 · 804-2 · 804-6 · 804-7 · 806-1 · 717-2b · 717-4 · 721 · 728-2 — 걷은 것이 없음 확인으로
- 남김 = M02 수식 문서 없는 줄의 단가산출 상세 몸(cover · buildHo)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-15 15:45:07 +09:00

143 lines
5.5 KiB
TypeScript

/* =============================================================================
* M01_MasterData_UI_LogicLab_Cover.ts
* 단가산출 상세 줄 「그 자리 고침」(PLAN 722-2 · 내역 16 · 17-2) — 줄 모달 안 수량 · 단가 칸.
* 고침 꼴 = M02 단가산출 덮개 칸과 같음(`qty` · `labor` · `material` · `expense` — `M02_Sangeun_Doc._covered` 규칙 중 품 · 단가)
* M02(724) = 고침 칸의 값을 고침 고리(`HoEdit.set`)로 받아 덮개로 보관 — M01 시험 고침은 840-1 에 걷음
* 서버 셈 = 줄 `단가`(손 단가) · 로직 줄 `값묶음`(노 · 재 · 경) · `수량` 글 — `master_pick._priced` · `bundled` 가 읽음
* ========================================================================== */
import { createButton, createInputField, el } from "@ui/ui_template_elements";
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { createText } from "@ui/ui_template_text";
/* ── 순수 셈 ── */
/** 줄 하나 고침 — 품(수량) · 비목 단가(로직 줄은 셋 · 그 밖은 그 줄 비목 하나) */
export interface LineCover {
qty?: number;
labor?: number;
material?: number;
expense?: number;
}
export const COVER_PARTS = { labor: "노무비", material: "재료비", expense: "경비" } as const;
export type CoverPart = keyof typeof COVER_PARTS;
/* ── 순수 셈 끝 ── */
const TEXT = {
Head: ["그 자리 고침 (시험 값)", "Edit here (trial value)"],
Note: [
"정본 · 마스터 값(노임 · 자재)은 안 바뀜 · 로직을 다시 열면 사라짐",
"Source and master prices stay unchanged · cleared when reopened",
],
NoteM02: [
"고친 값은 이 단가산출의 변경 항목으로 저장 — M01 은 안 바뀜",
"Saved as this item's override — M01 stays unchanged",
],
Qty: ["수량(품)", "Quantity"],
Price: ["단가", "Unit price"],
Apply: ["적용", "Apply"],
Undo: ["되돌림", "Undo"],
Bad: ["0 이상 수만", "Numbers ≥ 0 only"],
} as const satisfies Record<string, readonly [string, string]>;
export const tc = (k: keyof typeof TEXT): string =>
TEXT[k][currentLanguageIndex as 0 | 1] ?? TEXT[k][0];
export interface CoverFormOptions {
/** 지금 값(덮기 전 셈 값) — 칸 처음 값 */
qty: number | null;
/** 단가 칸 — 로직 줄 = 셋(1 단위 비목 값) · 그 밖 = 그 줄 비목 하나 */
prices: Partial<Record<CoverPart, number | null>>;
/** 지금 든 고침(있으면 칸 처음 값 · [되돌림]) */
cover?: LineCover;
/** 「m02」 = 덮개 보관 안내 */
scope?: "m01" | "m02";
onApply: (cover: LineCover | null) => void;
/** 모달 머리 단추 자리(닫기 왼쪽 · 748-1) — 주면 [적용] 이 거기로 */
head?: HTMLElement;
}
const num = (raw: string): number | null => {
const v = Number(raw.replaceAll(",", "").trim());
return raw.trim() !== "" && Number.isFinite(v) && v >= 0 ? v : null;
};
/** 고침 칸 — 수량 · 단가(들) · [적용] [되돌림] · 바뀐 칸만 고침으로 넘김 */
export function coverForm(o: CoverFormOptions): HTMLElement {
const parts = Object.keys(o.prices) as CoverPart[];
const field = (label: string, raw: number | null, mine?: number) => {
const now = raw === null ? null : Number(raw.toPrecision(10)); // 칸 글 = 유효 10 자리 · 손대지 않으면 고침 아님
const box = createInputField({
label,
value: String(mine ?? now ?? ""),
compact: true,
type: "text",
});
box.input.inputMode = "decimal";
return { box, now };
};
const qty = field(tc("Qty"), o.qty, o.cover?.qty);
const prices = parts.map((k) => ({
k,
...field(parts.length > 1 ? COVER_PARTS[k] : tc("Price"), o.prices[k] ?? null, o.cover?.[k]),
}));
const error = createText("", "error");
error.hidden = true;
const apply = (): void => {
const next: LineCover = {};
let bad = false;
const take = (f: { box: { input: HTMLInputElement }; now: number | null }, was?: number) => {
const v = num(f.box.input.value);
if (v === null)
return f.box.input.value.trim() === "" ? undefined : ((bad = true), undefined);
return v !== f.now || was !== undefined ? v : undefined; // 셈 값 그대로면 고침 아님
};
const q = take(qty, o.cover?.qty);
if (q !== undefined) next.qty = q;
for (const p of prices) {
const v = take(p, o.cover?.[p.k]);
if (v !== undefined) next[p.k] = v;
}
error.textContent = tc("Bad");
error.hidden = !bad;
if (!bad) o.onApply(Object.keys(next).length ? next : null);
};
const applyButton = createButton({
label: tc("Apply"),
variant: "filled",
size: 32,
onClick: apply,
});
o.head?.append(applyButton);
const actions = el("div", {
className: "m01lab-cover__actions",
children: [
...(o.head ? [] : [applyButton]),
...(o.cover
? [
createButton({
label: tc("Undo"),
variant: "ghost",
size: 32,
onClick: () => o.onApply(null),
}),
]
: []),
],
});
actions.hidden = !actions.childElementCount;
return el("section", {
className: "m01lab-cover",
attrs: { "data-cover-form": "" },
children: [
el("strong", { text: tc("Head") }),
createText(tc(o.scope === "m02" ? "NoteM02" : "Note"), "hint"),
el("div", {
className: "m01lab-cover__fields",
children: [qty.box.root, ...prices.map((p) => p.box.root)],
}),
error,
actions,
],
});
}