Files
Aislo/A00_Common/formula_doc_options.ts
T
eomsangdonandClaude Opus 5.5 badc24c0cc fix(845-2): 단가산출 단위 표시 규칙 한 벌 · 기계 산출근거 겹분수 중간 줄로 나눔
- 단위 규칙 한 벌(common_util_line_stack_unit) — 왼쪽 값 칸 · 식 줄 · 옵션 · 오른쪽 산출근거 · 기계 제어 구역 · 기계 보기가 같이 씀
  · 메타 단위(구분 따름 · 수(…) · 원문 단위 없음) 안 그림 · %(설명) = % · 10^-7 = 10⁻⁷ · 기계 칸 연결 = 기계 표 칸 단위 · 코드 값 쉼표 없음
- 비목 · 금액 줄 = 「단가 원/단위 × (수량 식) 단위 = 금액 원」 · 소계 · 합계 · 계 = 원 · 단가 · 수량 한 덩이(줄 바뀜에 단위 안 떨어짐)
- 기계경비식 겹분수 → 중간 줄(상여 · 휴지 · 손료계수 · common_util_line_stack_frac) · 한 줄 분수 한 층
- 기계 제어 구역 단위 = 칸 밖 바로 뒤 · 기계 보기 오른쪽 = 박기 전 기계 칸 단위
- 휴대폰 폭(560 px 밑) 산출근거 금액 칸 넷 걷음(가로 넘침 0) · 분수 줄 위아래 여유
- 전수 1,520 표시 탓 43,218 → 0 · 기계 겹분수 2 층 넘는 줄 1,040 → 0 · 금액 · 식 · 문서 자료 불변(그리기만)
- 시험 test_845_2_unit_show(차린 줄 글 전수 지킴) · shownUnit 걷음(규칙 한 벌로)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 22:44:36 +09:00

119 lines
4.9 KiB
TypeScript

/* =============================================================================
* formula_doc_options.ts
* 수식 문서 옵션 가로(PLAN 802-3 · 802-4b-2 · 요구 655 「옵션 선택은 상단에 수평」) — 선택 · 입력 기호마다 칸 하나
* 값 = 화면 값(`given`) › 문서 값 · 고침은 부른 쪽 `set`(M01 = 화면 값만 · M02 사본 = 문서 값) · `lock` = 읽기만
* 목록이 모두 기계 원문번호(`0101-0007`)면 글 = 「이름 규격 (원문번호)」(요구 692-2 · 값은 그대로 · 이름은 한 번 받아 둠)
* `view` 있음 + 기계 목록 = 곁에 [조회](827-1 · 기계 창 읽기 전용 · 고른 기계)
* ========================================================================== */
import { createButton } from "@ui/ui_template_button";
import { createInputField, createSelectField } from "@ui/ui_template_input";
import { varId, type DocVar, type FormulaDoc } from "../common_util/common_util_formula_doc";
import { varUnit } from "../common_util/common_util_line_stack_unit";
import { cfMachineNames, cfMachineOff } from "../M02_MasterTemplete/DataFlow/M02_DF_Api";
import { mt } from "../M02_MasterTemplete/M02_MyMachine_Text";
import { ft } from "./formula_doc_text";
const MACHINE = /^\d{4}-\d{4}$/;
let names: Record<string, string> = {};
let off = new Set<string>(); // 842-2 끈 기계 원문번호
const machineInfo = (): Promise<Record<string, string>> =>
Promise.all([cfMachineNames(), cfMachineOff()]).then(
([got, gone]) => ((off = new Set(gone)), got),
);
/** 고르기 글 — 기계 원문번호는 이름 · 규격을 앞에 · 끈 기계는 「· 비활성 기계」(842-2) */
export const optionText = (x: string, known: Record<string, string> = names): string =>
known[x] ? `${known[x]} (${x})${off.has(x) ? ` · ${ft("Machine_Off")}` : ""}` : x;
/** 고르기 목록 — 끈 기계 뺌(지금 고른 값은 남김 · 842-2) */
export const liveOptions = (list: string[], now: string): string[] =>
list.filter((x) => x === now || !off.has(x));
/** 목록이 모두 원문번호(빈 칸 빼고)면 이름을 받아 `redraw`(글만 다시) — 수식 탭 옵션 · 단가산출 상세 옵션 */
export function nameMachines(list: string[], redraw: () => void): void {
const codes = list.filter(Boolean);
if (codes.length && codes.every((x) => MACHINE.test(x)) && codes.some((x) => !(x in names)))
void machineInfo().then((got) => {
names = got;
redraw();
});
}
let asked = false;
/** 글 값이 기계 원문번호면 「이름 규격 (원문번호)」(838-3 · 요구 859) — 이름이 아직 없으면 한 번 받아 `redraw` · 아니면 null */
export function machineText(x: unknown, redraw: () => void): string | null {
if (typeof x !== "string" || !MACHINE.test(x)) return null;
if (!(x in names) && !asked) {
asked = true;
void machineInfo().then((got) => {
names = { ...names, ...got };
redraw();
});
}
return optionText(x);
}
export function buildOptions(
doc: FormulaDoc | null,
given: Record<string, unknown>,
lock: boolean,
set: (key: string, v: DocVar, value: unknown) => void,
view?: (code: string) => void,
): HTMLElement[] {
const out: HTMLElement[] = [];
for (const v of doc?.변수 ?? []) {
const kind = v.연결?.갈래;
const k = v.열쇠;
if (!k || (kind !== "선택" && kind !== "입력")) continue;
const label = v.이름 || varId(v);
const now = k in given ? given[k] : v.연결?.값;
if (kind === "선택") {
const list = (v.연결?.목록 ?? []).map(String);
const items = () =>
liveOptions(list, String(now ?? "")).map((x) => ({ value: x, text: optionText(x) }));
const f = createSelectField({
label,
options: items(),
value: String(now ?? ""),
compact: true,
size: 28,
disabled: lock,
onChange: (x) => set(k, v, x),
});
f.root.dataset.option = k;
nameMachines(list, () => f.setOptions(items(), f.select.value));
out.push(f.root);
const codes = list.filter(Boolean);
if (view && codes.length && codes.every((x) => MACHINE.test(x))) {
const b = createButton({
label: mt("Win_View"),
variant: "ghost",
size: 28,
iconStart: "search",
onClick: () => view(f.select.value),
});
b.dataset.optionView = k;
out.push(b);
}
} else {
const f = createInputField({
label,
value: now === undefined || now === null ? "" : String(now),
compact: true,
size: 28,
unit: varUnit(v) || undefined, // 845-2 단위 규칙 한 벌
});
f.input.disabled = lock;
f.input.addEventListener("change", () => {
const t = f.input.value.trim();
const n = Number(t.replace(/,/g, ""));
set(k, v, t !== "" && Number.isFinite(n) ? n : t);
});
f.root.dataset.option = k;
out.push(f.root);
}
}
return out;
}