- 로직 삭제 = 서버 정본에서 바로(로직 줄 + 수식 문서 · `/logic/remove`) → 로직 목록 · 부르는 곳(다른 수식 문서 · 일위대가 조합 · M02 단가산출 바탕)이 있으면 확인 창 전에 막고 까닭(`/logic/users`) - 머리 단추 다섯 — [신규 로직] · [로직 복사/생성] · [로직 삭제] · [되돌림] · [저장] · [저장] 한 번 = 기본정보 + 수식 · [되돌림] = 둘 다 저장 뒤 상태 · 줄 쌓기 [고침 버림] · [수식 저장]은 M01 에서 걷음(M02 사본은 그대로) - 합계 상자 화면 아래 고정 · 원문 손잡이 브라우저 오른쪽 끝 - 왼쪽: 그룹 머리 한 줄(접어도 [+] · X 자리 그대로) · 「비우면 번호 없음」 · 가나다 체크 걷음 · 금액 줄 한 줄 배치 · 이름 단추 = 그 이름 찾은 찾기 창 · 수량 기호 = 값 + 출처 꼬리표 · [+] 메뉴(단가산출 · 율 · 식 · 근거 글 · 틀 찾기 걷음) · 식 글 눌러 그 자리 고침([식 고치기] 걷음) · 단위 = 공용 단위 고르기(친 글도 씀) · 만약 안 탄 갈래 숨김 - 오른쪽 기계 줄: 비목 수 링크 걷음 · 제목 줄 누름 = 시간당 경비 펼침 · 펼친 제목 보통 글 - 요소 키가 기호 이름으로 샌 것(`k_EQ0006`) — 변환기가 자원 이름으로(`k_하베스터`) · 수식 문서 다시 빌드(10 파일 · 78 기호 · 금액 같음) - 시험 페이지 `tools/line-stack-test` · main.py 마운트 · vite 프록시 · M01 왼쪽 「시험: 줄 쌓기」 걷음 - 단위 표준 표 읽기 = 로그인만(M02 일반 사용자 단위 목록) - 시험: test_804_7 새로 · 804_2 · 804_4 이음 글 고침(createStackScreen 줄 나뉨) · 552_3 여는 단추 기본 ⋯ 꼴로 고침(부르는 쪽이 [+] 로 바꿀 수 있음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
6.5 KiB
TypeScript
158 lines
6.5 KiB
TypeScript
/* =============================================================================
|
|
* line_stack_pick.ts
|
|
* 줄 쌓기 고르기 창(PLAN 804-3 · 804-4 · 요구 678-1 · 678-3 · 680-1 · 681)
|
|
* 찾아 넣기 = M02 [항목 추가] 창 그대로(`openFindPicker` · 탭 M01 전체 · 찾기 칸 · 표) — 줄 갈래마다 그 종류만
|
|
* 인력(노임 LB) · 재료(자재 MT) · 기계(EQ → 시간당 경비 로직 + 기계='원문번호') · 다른 단가산출(M01 단가산출 로직)
|
|
* M01 단가산출 기초 줄 쌓기 = 「M01 전체」 탭만 — M01 은 원천 기준값이라 M02 단가산출 · 일위대가를 부르지 않음(요구 680-1)
|
|
* M02 단가산출 줄 쌓기(`take` 있음) · 다른 단가산출 = 탭 [M01 전체 | M02 단가산출 | 일위대가](위 층 · 자기 SK 뺌) —
|
|
* 고름 = 복사해 펼침(`take(src)` · src = M01 키 | `참조|층|주인|키`) · 참조 줄 없음(요구 681)
|
|
* 표 고르기 = 품셈 표(조건 칸 + 값 칸) · 값 칸 누름 = 그 줄 · 칸 · 지금 닿는 칸 강조 ·
|
|
* 옵션에 매인 조건 = 그 줄만 고를 수 있음(다른 줄은 막힘 + 「위 옵션에서 고름」 안내)
|
|
* ========================================================================== */
|
|
|
|
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 { showToast } from "@ui/ui_template_elements";
|
|
import type { Layer } from "../M02_MasterTemplete/M02_MasterTemplete_Api_Fetch";
|
|
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",
|
|
};
|
|
|
|
/** M02 단가산출 복사 길(804-4) — 그 SK 층 · 키 · 복사해 넣기 */
|
|
export interface StackTake {
|
|
layer: Layer;
|
|
owner: string | null;
|
|
me: string;
|
|
take: (src: string) => void;
|
|
}
|
|
|
|
/** 찾아 넣기 — 고르면 `done(고른 것)` · 닫으면 아무것도 안 함 · `m02` 있고 다른 단가산출 = 복사해 펼침 ·
|
|
* `query` = 찾기 칸에 넣고 연 결과(이름 단추 · 요구 687-9) */
|
|
export function openStackPick(
|
|
kind: MoneyKind,
|
|
done: (p: Picked) => void,
|
|
m02?: StackTake,
|
|
query = "",
|
|
): void {
|
|
const want = KINDS[kind];
|
|
if (!want) return;
|
|
const spec = (e: { 규격: string }) => (e.규격 ? { 규격: e.규격 } : {});
|
|
const copy = kind === "단가산출" && m02 ? m02 : null;
|
|
openFindPicker({
|
|
layer: copy?.layer ?? "system",
|
|
owner: copy?.owner ?? null,
|
|
title: `${lt("Find_Title")} — ${lt(copy ? "K_복사" : (`K_${kind}` as never))}`,
|
|
kinds: copy ? ["logic", "sk", "pre"] : [want], // M01 = M01 전체만(680-1)
|
|
query: query.trim() || undefined,
|
|
ref: copy
|
|
? (r) =>
|
|
r.키 === copy.me && r.층 === copy.layer && (!copy.owner || r.주인 === copy.owner)
|
|
? showToast(lt("Self_Take"), "warning")
|
|
: copy.take(`참조|${r.층}|${r.주인 ?? ""}|${r.키}`)
|
|
: undefined,
|
|
logic: (x) =>
|
|
copy
|
|
? copy.take(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],
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
}
|