Files
Aislo/A00_Common/line_stack_pick.ts
T
eomsangdonandClaude Opus 5.5 0eb7822772 feat(804-3): 줄 쌓기 수정 1차 — 표 값 고르기 · 산출근거 들여쓰기 · 찾아 넣기 창 · 짝 강조 · 기계 이름
- 표 값(요구 678-1): 「표」 꼬리표가 글뿐이라 눌러도 안 열림 → [표 · 고른 줄 · 칸 ▾] 단추 = 품셈 표 창(값 칸 누름 = 그 줄 · 칸)
  옵션에 매인 조건(폼타이규격 등) = 그 줄만 고름 · 다른 줄 막힘 + 「위 옵션에서 고름」 안내 · M02 길 표 값 글 수도 고름
- 산출근거 들여쓰기(678-2): 머리 0 · 항목 1 · 가나다 2 · 줄 = 항목 + 1 · 펼친 블록 = 그 줄 + 1(수준 = stackLevels)
- 찾아 넣기(678-3): [+ 줄] 인력 · 재료 · 기계 · 다른 단가산출 = M02 [항목 추가] 창 그대로(그 종류만) · 고르면 줄 생김(닫으면 없음)
  있는 줄 = [찾아 바꾸기](같은 로직 기계 = 지역 · 보정작업 넘김 그대로) · 옛 찾기 칸 + 고름 목록 · 기종 코드 고르개 걷음
  빈 틀 = [찾아 넣기](돈 아닌 단가산출 결과만 — Find_Pick results)
- 짝 강조(679-1): 오른쪽 줄 누름 = 왼쪽 카드 강조 + 보이는 자리로 · 왼쪽 누름 = 오른쪽 같은 줄 강조
- 기계 이름(679-2): 기계 줄 = 기종 이름 · 규격 · machine-hourly 답에 로직 키 · 원문번호 더함(기존 칸 그대로)
- 모델 common_util_line_stack_pick.ts(boundConds · currentRow · pickCell · addPicked · repick · stackLevels)
- 시험 test_804_2 넓힘 3(표 고름 = 값 + 출처 · 서버 셈 같음 · 찾아 넣기 넷 = 요소 키 · 서버 금액 · 다시 고름 · 수준)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 01:27:20 +09:00

129 lines
5.1 KiB
TypeScript

/* =============================================================================
* line_stack_pick.ts
* 줄 쌓기 고르기 창(PLAN 804-3 · 요구 678-1 · 678-3)
* 찾아 넣기 = M02 [항목 추가] 창 그대로(`openFindPicker` · 탭 M01 전체 · 찾기 칸 · 표) — 줄 갈래마다 그 종류만
* 인력(노임 LB) · 재료(자재 MT) · 기계(EQ → 시간당 경비 로직 + 기계='원문번호') · 다른 단가산출(M01 단가산출 로직)
* M02 단가산출 · 일위대가 탭은 안 보임 — 수식 문서 연결은 M01 키만 셈
* 표 고르기 = 품셈 표(조건 칸 + 값 칸) · 값 칸 누름 = 그 줄 · 칸 · 지금 닿는 칸 강조 ·
* 옵션에 매인 조건 = 그 줄만 고를 수 있음(다른 줄은 막힘 + 「위 옵션에서 고름」 안내)
* ========================================================================== */
import { createButton } from "@ui/ui_template_button";
import { eqText } from "@ui/ui_template_equation";
import { el } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
import { tableRow, tableShell, type Column } from "@ui/ui_template_table";
import { createText } from "@ui/ui_template_text";
import type { DocTable } from "../common_util/common_util_formula_doc_table";
import type { MoneyKind } from "../common_util/common_util_line_stack";
import type { Picked } from "../common_util/common_util_line_stack_pick";
import { openFindPicker, type FindKind } from "../M02_MasterTemplete/M02_MasterTemplete_Find_Pick";
import { lt } from "./line_stack_text";
const KINDS: Partial<Record<MoneyKind, FindKind>> = {
인력: "labor",
재료: "material",
기계: "machine",
단가산출: "logic",
};
/** 찾아 넣기 — 고르면 `done(고른 것)` · 닫으면 아무것도 안 함 */
export function openStackPick(kind: MoneyKind, done: (p: Picked) => void): void {
const want = KINDS[kind];
if (!want) return;
const spec = (e: { 규격: string }) => (e.규격 ? { 규격: e.규격 } : {});
openFindPicker({
layer: "system",
owner: null,
title: `${lt("Find_Title")} — ${lt(`K_${kind}` as never)}`,
kinds: [want],
logic: (x) => done({ 키: x.키, 이름: x.이름, 단위: x.결과단위.replace(/^원\/?/, "") }),
element: (e) => done({ 키: e.코드, 이름: e.이름, 단위: e.단위, ...spec(e) }),
machine: (e) =>
e.로직 && e.원문번호
? done({ 키: e.로직, 이름: e.이름, 입력: { 기계: `'${e.원문번호}'` }, ...spec(e) })
: undefined,
});
}
const isNum = (v: unknown): boolean =>
typeof v === "number" || (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v)));
const cellText = (v: unknown): string =>
Array.isArray(v)
? `${v[0] ?? ""} ~ ${v[1] ?? ""}`
: isNum(v)
? eqText(Number(v))
: String(v ?? "");
export interface TablePick {
table: DocTable;
/** 지금 닿는 줄(-1 = 없음) · 칸 */
now: number;
col: string;
/** 옵션에 매인 조건 — {조건 칸: 옵션 열쇠들} */
bound: Record<string, string[]>;
done: (row: Record<string, unknown>, col: string) => void;
}
/** 표 고르기 창 — 값 칸 누름 = 고름 */
export function openTablePick(o: TablePick): void {
const t = o.table;
const conds = Object.keys(t.조건 ?? {});
const valueCols = [
...new Set([...Object.keys(t.값칸 ?? {}), ...t.줄.flatMap((r) => Object.keys(r))]),
].filter((k) => !conds.includes(k));
const locked = Object.keys(o.bound).length > 0;
const columns: Column[] = [
...conds.map((c) => ({ label: c })),
...valueCols.map((c) => {
const unit = (t.값칸 ?? {})[c];
return { label: unit ? `${c} (${String(unit)})` : c, align: "end" as const };
}),
];
openModal({
title: [t.원문번호, (t as { 이름?: string }).이름].filter(Boolean).join(" "),
closeLabel: lt("Close"),
width: "960px",
mount: (body, close) => {
const { root, tbody } = tableShell(columns, { density: "data" });
t.줄.forEach((row, k) => {
const off = locked && k !== o.now;
const tr = tableRow(columns, [
...conds.map((c) => cellText(row[c])),
...valueCols.map((c) =>
isNum(row[c]) // 수만 고름(글 칸 = 단위 등은 보기만 · M02 길은 수를 글로 줌)
? createButton({
label: cellText(row[c]),
variant: k === o.now && c === o.col ? "filled" : "ghost",
size: 24,
disabled: off,
onClick: () => {
close();
o.done(row, c);
},
})
: cellText(row[c]),
),
]);
tr.dataset.row = String(k);
tbody.append(tr);
});
const note = locked
? [
createText(
`${lt("Tbl_Bound")} — ${[...new Set(Object.values(o.bound).flat())].join(" · ")}`,
),
]
: [];
body.append(
el("div", {
className: "lstk-tpick",
attrs: { "data-table-pick": t.키 ?? "" },
children: [...note, createText(lt("Tbl_Hint")), root],
}),
);
},
});
}