feat(736-1): 일위대가 [항목 추가] 하나 · 찾아 넣기 창 하나로 — 자재 추가 단추 걷음 · 엑셀 연결 · 별칭 장부 · 견본

- 일위대가표 · 구조물 조합 상세 [로직 추가] → [항목 추가] · [자재 추가] 걷음 · 재료 고름 = 자재 목록(로직 안 자재 · 이미 있는 자재 막힘 그대로)
- 찾아 넣기 창 탭 막기(kinds) · 찾기 칸 미리 넣기(query) · 재료 길(material)
- 불러오기 엑셀 연결 = 「다른 항목 검색…」 한 창(기초 · 재료) · 별칭 장부 = 기초 · 재료 둘
- 부품 모음 단추 견본에 갈래 색(옵션값 · 요소 연결) 칸
- 낡은 시험 고침: 175(머리 단추) · 721(부르는 곳) · 자재 개인(상세 단추) · 호표 화면(장부 단추 글) · 헬퍼 넷(Find_Pick 대역)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 16:50:34 +09:00
co-authored by Claude Sonnet 5.5
parent f82e64ce29
commit f97ea96c08
18 changed files with 152 additions and 79 deletions
@@ -7,9 +7,11 @@
* · M02 단가산출 · 일위대가 = 위 층(마스터 › 회사 › 개인) 서고 목록(`/layers/{층}/templates`) · 고름 = 참조 {층 · 주인 · 키}
* · 인력 · 재료 고름 = `element` · 일위대가 고정 줄 꼴 = `fixedOf`(705-1 이음 노임 LB · 자재 MT · 수량 1 은 부르는 쪽) ·
* `element` 없으면 그 줄 회색 막힘(까닭 = 일위대가 표 개편 뒤 · 723)
* · 부르는 곳 = M02 단가산출 [항목 추가](`Sangeun_Pick`) · 일위대가 [로직 추가]
* · 부르는 곳 = M02 단가산출 [항목 추가](`Sangeun_Pick`) · 일위대가 [항목 추가]
* (`Labor_Pick` 이 `lib.sangeun` 이면 이 창) — B09 · 불러오기는 옛 창(`Labor_Pick`)
* · (727-1) 기초 = 로직마다 한 줄(`logicHeads` · 옵션은 넣은 뒤 고름 · 옛 창도 같이) · 서버 목록 그대로
* · (736-1) `kinds` = 탭 막기(자리마다 고를 수 있는 종류만 · 불러오기 엑셀 연결 · 별칭 장부) · `material` = 재료 고름을
* 일위대가 「자재 목록」 자리로(`element` 대신) · `query` = 찾기 칸 미리 넣기 · 부르는 곳 = 일위대가 [항목 추가]
* · (728-2) 돈 아닌 기초(결과단위가 「원」 아님 · ㎥/시간 등 21 개) = 목록에서 뺌
* ========================================================================== */
@@ -46,10 +48,19 @@ export interface M01Element {
값: number | null;
}
/** 고를 수 있는 종류 — logic = M01 단가산출 · 기초 로직 · labor = 인력 · material = 재료 · sk = M02 단가산출 · pre = 일위대가 */
export type FindKind = "logic" | "labor" | "material" | "sk" | "pre";
export interface FindPickOptions {
layer: Layer;
owner: string | null;
title?: string;
/** 736-1 탭 막기 — 이 종류만 보임(없으면 다 · 탭 하나만 남으면 탭 줄 감춤) */
kinds?: FindKind[];
/** 찾기 칸에 미리 넣을 글 */
query?: string;
/** 재료 고름 — 있으면 재료는 `element` 대신 이쪽(일위대가 「자재 목록」 자리) */
material?: (e: M01Element) => void;
/** M01 단가산출 로직 고름 */
logic: (x: LogicSummary) => void;
/** M02 단가산출 · 일위대가 고름(참조) */
@@ -117,7 +128,17 @@ interface Line {
}
export function openFindPicker(opt: FindPickOptions): void {
let tab: Tab = "m01";
const ok = (k: FindKind): boolean => !opt.kinds || opt.kinds.includes(k);
const allowed = (
[
["m01", ok("logic") || ok("labor") || ok("material")],
["sk", ok("sk")],
["pre", ok("pre")],
] as const
)
.filter(([, on]) => on)
.map(([k]) => k as Tab);
let tab: Tab = allowed[0] ?? "m01";
let close = (): void => {};
let logics: LogicSummary[] = [];
let std: StandardRow[] | null = null;
@@ -182,7 +203,7 @@ export function openFindPicker(opt: FindPickOptions): void {
단위: unitOf(m.단위 ?? ""),
값: Number.isFinite(Number(m.값)) && m.값 !== null ? Number(m.값) : null,
};
const put = opt.element;
const put = kind === "재료" && opt.material ? opt.material : opt.element;
return {
품명: m.이름,
규격: e.규격,
@@ -208,7 +229,11 @@ export function openFindPicker(opt: FindPickOptions): void {
function draw(): void {
const q = search.input.value.trim();
if (tab === "m01") {
shown = [...logicLines(q), ...elementLines("인력", people), ...elementLines("재료", goods)];
shown = [
...(ok("logic") ? logicLines(q) : []),
...(ok("labor") ? elementLines("인력", people) : []),
...(ok("material") ? elementLines("재료", goods) : []),
];
} else {
const words = q.toLowerCase().split(/\s+/).filter(Boolean);
const kind = tab === "sk" ? L("M02_PickSangeun") : L("M02_PrecomboTitle");
@@ -231,7 +256,10 @@ export function openFindPicker(opt: FindPickOptions): void {
`/logic-lib/pick?kind=${kind}&q=${encodeURIComponent(q)}&limit=200`,
).then((d) => d.items);
try {
const [p, g] = await Promise.all([get("job"), q ? get("price") : Promise.resolve([])]);
const [p, g] = await Promise.all([
ok("labor") ? get("job") : Promise.resolve([]),
q && ok("material") ? get("price") : Promise.resolve([]),
]);
if (mine !== seq) return;
[people, goods] = [p, g];
} catch (error) {
@@ -252,7 +280,7 @@ export function openFindPicker(opt: FindPickOptions): void {
{ key: "m01", label: L("M02_FindM01") },
{ key: "sk", label: L("M02_PickSangeun") },
{ key: "pre", label: L("M02_PrecomboTitle") },
],
].filter((x) => allowed.includes(x.key as Tab)),
value: tab,
kind: "line",
onChange: (key) => {
@@ -272,10 +300,11 @@ export function openFindPicker(opt: FindPickOptions): void {
el("div", {
className: "m02-std-pick",
attrs: { "data-find-pick": "" },
children: [tabs.root, search.root, hint, table.root],
children: [...(allowed.length > 1 ? [tabs.root] : []), search.root, hint, table.root],
}),
),
}));
if (opt.query) search.input.value = opt.query;
search.input.focus();
draw();
@@ -292,7 +321,7 @@ export function openFindPicker(opt: FindPickOptions): void {
void upper(opt.layer, opt.owner)
.then((scopes) =>
Promise.all(
(["sk", "pre"] as const).map((t) =>
(["sk", "pre"] as const).filter(ok).map((t) =>
listLayerDocs(scopes, t === "sk" ? SANGEUN_KIND : "precombo").then((rows) => {
lists[t] = rows;
tabs.setBadge(t, rows.length);