feat(M01): 시험 계산 입력 2열 · 자재·기계·직종 키를 이름으로
시험 계산 입력 칸을 요소 표(기본정보)가 쓰는 auto-fill 그리드(m01-logic__head)로 감싸 여러 칸이 나란히 놓이게 함 — 짧은 고르기 줄은 2열로 붙고, 이름이 긴 줄만 한 칸을 다 씀(m01-logic__field--wide). 고르기 값이 자재(MT)·기계(EQ)·직종(LB) 키(2글자+6자리)면 /elements 를 키 그대로 물어(부분일치라 그 줄만 걸림) 이름+규격으로 바꿔 보임 — 보내는 값은 키 그대로라 계산은 그대로. 같은 키는 모듈 캐시로 한 번만 물음. 「그릇 폭 280px 고정」은 옛 테스트 컨테이너(UI_Test*, 2-3에서 걷힘)의 죽은 CSS 규칙이라 지금 쓰는 LogicLab 화면엔 안 걸림(실측 730px) — 따로 안 풂. 검증 — GC000031 ORCA: 공기·철선 나란히 2열 · 철선 고르기가 「어닐링철선,서울 #8, 4.0mm」 등 이름으로 뜸 · 계산 15,020.927(고침 전과 같음) · typecheck 통과 · 로직 전체 키형 고르기 179줄이 자재품목에 다 있어 이름 못 바꾸는 자리 0(100건 상한은 키 하나씩 물어 안 걸림). Pick.ts(재료·기계 바꿔 고르기)는 이미 이름으로 보여 안 고침. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
@@ -8,6 +8,7 @@ import { createButton, createSelectField, el, showToast } from "@ui/ui_template_
|
||||
import {
|
||||
runCalc,
|
||||
runText,
|
||||
searchElements,
|
||||
type CalcAnswer,
|
||||
type CalcLine,
|
||||
type TextAnswer,
|
||||
@@ -16,6 +17,28 @@ import {
|
||||
import { formatNumber } from "./M01_MasterData_UI_Logic_Edit";
|
||||
import { tx } from "./M01_MasterData_UI_Logic_Text";
|
||||
|
||||
/** 고르기 값이 마스터 키(자재·기계·직종)면 이름으로 바꿔 보임 — 값 칸은 키 그대로 보냄 */
|
||||
const KEY_RE = /^[A-Z]{2}\d{6}$/;
|
||||
const KEY_GROUP: Record<string, string> = { MT: "재료", EQ: "기계", LB: "인력" };
|
||||
/** 로직마다 다시 그려도 남는 이름 캐시 — 같은 키를 두 번 묻지 않음 */
|
||||
const nameCache = new Map<string, string | null>();
|
||||
|
||||
async function keyName(key: string): Promise<string | null> {
|
||||
if (nameCache.has(key)) return nameCache.get(key)!;
|
||||
const group = KEY_GROUP[key.slice(0, 2)];
|
||||
if (!group) return null;
|
||||
let name: string | null = null;
|
||||
try {
|
||||
const found = await searchElements(group, key); // q = 키 그대로 — 「키」 칸 부분일치라 그 줄만 걸림
|
||||
const hit = found.items.find((it) => it.ref === key);
|
||||
name = hit ? `${hit.이름 ?? ""}${hit.규격 ? ` ${hit.규격}` : ""}`.trim() || null : null;
|
||||
} catch {
|
||||
name = null;
|
||||
}
|
||||
nameCache.set(key, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
export interface CalcContext {
|
||||
/** 저장된 키 — 새 로직은 null */
|
||||
savedKey: string | null;
|
||||
@@ -33,17 +56,31 @@ export interface CalcContext {
|
||||
const SUMS = ["노무비", "재료비", "경비", "계"];
|
||||
|
||||
export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
|
||||
// 그릇 폭 280px 고정 칸은 옛 테스트 컨테이너(UI_Test*, 걷힘)의 죽은 CSS 규칙 —
|
||||
// 지금 쓰는 LogicLab 화면은 그 칸에 안 걸려 폭이 이미 넓음(실측 730px) · 따로 안 풂
|
||||
const optionText = (o: string | number): string =>
|
||||
(typeof o === "string" && nameCache.get(o)) || String(o);
|
||||
const fields = (ctx.row.입력 ?? []).map((spec) => {
|
||||
const name = spec.이름;
|
||||
let control: HTMLElement;
|
||||
if (spec.고르기?.length) {
|
||||
const picks = spec.고르기;
|
||||
if (picks?.length) {
|
||||
const pick = createSelectField({
|
||||
options: ["", ...spec.고르기.map(String)].map((o) => ({ value: o, text: o })),
|
||||
options: ["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })),
|
||||
value: ctx.values[name] ?? "",
|
||||
compact: true,
|
||||
onChange: (v) => (ctx.values[name] = v),
|
||||
});
|
||||
control = pick.root;
|
||||
const keys = picks.filter((o): o is string => typeof o === "string" && KEY_RE.test(o));
|
||||
if (keys.length) {
|
||||
void Promise.all(keys.map(keyName)).then(() => {
|
||||
pick.setOptions(
|
||||
["", ...picks.map(String)].map((o) => ({ value: o, text: optionText(o) })),
|
||||
ctx.values[name] ?? "",
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const box = el("input", {
|
||||
className: "m01-logic__input",
|
||||
@@ -55,8 +92,10 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
|
||||
control = box;
|
||||
}
|
||||
const label = spec.단위 ? `${name} (${spec.단위})` : name;
|
||||
// 긴 이름 줄만 한 칸 다 씀 — 고르기 줄은 짧으면 그대로 2열에 맞춰 나란히
|
||||
const wide = label.length > 10;
|
||||
return el("label", {
|
||||
className: "m01-logic__field",
|
||||
className: `m01-logic__field${wide ? " m01-logic__field--wide" : ""}`,
|
||||
children: [el("span", { text: label }), control],
|
||||
});
|
||||
});
|
||||
@@ -89,7 +128,7 @@ export function buildCalc(host: HTMLElement, ctx: CalcContext): void {
|
||||
host.replaceChildren(
|
||||
el("h3", { text: tx("Calc_Title") }),
|
||||
...(fields.length
|
||||
? fields
|
||||
? [el("div", { className: "m01-logic__head", children: fields })] // 요소 표가 쓰는 auto-fill 그리드 재사용 — 2열
|
||||
: [el("p", { className: "m01-logic__muted", text: tx("Calc_NoInputs") })]),
|
||||
createButton({ label: tx("Calc_Run"), onClick: () => void run() }),
|
||||
out,
|
||||
|
||||
Reference in New Issue
Block a user