feat(721-2): M02 찾아 넣기 창 — 탭 M01 전체(로직 · 인력 · 재료) · 단가산출 · 일위대가 · 찾기 칸 하나 · 거름 값 = 구분 · 출처 열

- 새 파일 M02_MasterTemplete_Find_Pick.ts — 있는 길 넷 이음(기준 목록 · 로직 · 층 서고 목록 · /logic-lib/pick 인력 job · 재료 price 서버 찾기) · 기계 · 실무 호표 뺌
- 부르는 곳 셋: 단가산출 [기본 로직 선택](로직 줄만) · [항목 추가](더한줄 인력 · 재료 = 요소 키 LB · MT 수량 1) · 일위대가 [로직 추가](Labor_Pick 이 lib.sangeun 이면 새 창)
- 일위대가 인력 · 재료 = 고정 줄 이음 꼴 fixedOf · lib.fixed 받는 칸은 723(sub1) — 그 전엔 회색 막힘
- 옛 창(B09 둘 · 불러오기 둘 · 프로젝트 층) 그대로 · 옛 창의 단가산출 탭 코드 걷음(부르는 곳 없음)
- 부품 상자: 데이터 표 흐린 줄 is-dim 더함
- 낡은 시험 test_717_4_sangeun_pick: 옛 창 단가산출 탭 · 재료 숨은 한 줄 글 대조 → 새 창 꼴로 고침
- 새 지킴 시험 test_721_find_pick(부르는 곳 · 옛 창 그대로 · 인력 · 재료 줄이 서버 셈에서 노임 · 자재 값)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 21:45:11 +09:00
co-authored by Claude Opus 5.5
parent f7ebf1831f
commit 0e24c7a2aa
9 changed files with 506 additions and 139 deletions
@@ -9,8 +9,9 @@
* 704-1 단가산출 줄 = 기준 목록 표(품명 · 규격 · 단위 · 금액 · 출처 · 대표 규격 펼침 · 공종 거름 ·
* 실무 낱말 = 서버가 별칭에 덧붙임) · 금액 = 서버 M01 엔진(`/logic-lib/standard`) · 오기 전엔 로직 줄만 ·
* 줄 누름 = 계산서 보기(704-2 · `Labor_Sheet`) → [담기] = 지금 담기 동작(`done(로직)`)
* 717-4 `lib.sangeun` = 탭 [기준 목록 | 단가산출] · 단가산출 = 위 층 SK(마스터 › 회사 › 개인 · `Sangeun_Pick`)를
* 같은 표 꼴(출처 = 단가산출 · 층) · 줄 누름 = `lib.pick` 참조(SK 키)
* 721-2 `lib.sangeun`(M02 일위대가 [로직 추가] · `withHopyo`) = 찾아 넣기 창(`Find_Pick` · 탭 M01 전체 · 단가산출 ·
* 일위대가 · 실무 호표 줄 없음) · M01 인력 · 재료 = `lib.fixed`(일위대가 고정 줄 · 없으면 회색 막힘) ·
* B09 · 불러오기는 이 창 그대로
* ========================================================================== */
import { createButton, createSelectField, el, showToast } from "@ui/ui_template_elements";
@@ -19,7 +20,6 @@ import { createFindList } from "@ui/ui_template_find_list";
import { aliasNote, matchLogic } from "@ui/ui_template_logic_find";
import { t as L } from "@ui/ui_template_locale";
import type { LogicSummary, SubBrief } from "../M01_MasterData/M01_MasterData_UI_Logic_Api";
import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch";
import type { Layer } from "./M02_MasterTemplete_Api_Fetch";
import {
fetchLibLogics as fetchLogics,
@@ -30,8 +30,7 @@ import {
type StandardRow,
} from "./M02_MasterTemplete_Labor_Api";
import { openStandardSheet } from "./M02_MasterTemplete_Labor_Sheet";
import type { LaborRef } from "./M02_MasterTemplete_Labor_Calc";
import { ownOwner } from "./M02_MasterTemplete_Layer";
import type { LaborFixed, LaborRef } from "./M02_MasterTemplete_Labor_Calc";
import { tc } from "./M02_MasterTemplete_Labor_Text";
import { precomboClasses } from "./M02_MasterTemplete_UI_Side_Precombo";
import { fetchStructureDef } from "./StructOptions/M02_SO_Api";
@@ -39,8 +38,7 @@ import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css"; // 창 겉틀
import "./M02_MasterTemplete_Labor.css";
import { openModal } from "@ui/ui_template_modal";
import { createText } from "@ui/ui_template_text";
import { createTabs } from "@ui/ui_template_tabs";
import { listSangeun, type SangeunRow } from "./M02_MasterTemplete_Sangeun_Pick";
import { fixedOf, openFindPicker, upper } from "./M02_MasterTemplete_Find_Pick";
export { withHopyo } from "./M02_MasterTemplete_Hopyo_Pick";
@@ -75,22 +73,12 @@ export interface LibraryPick {
more?: () => Promise<PickExtra[]>;
/** 종류 고르기 칸(251 · 값 logic · precombo · 덧붙인 줄 kind) */
kindFilter?: Array<{ value: string; text: string }>;
/** 717-4 「단가산출」 탭(M02 SK · 위 층 모두) — 고르면 `pick` 참조 */
/** 721-2 찾아 넣기 창(`Find_Pick`)으로 — M02 일위대가 [로직 추가] */
sangeun?: boolean;
/** 721-2 M01 인력 · 재료 → 일위대가 고정 줄(705-1 이음 꼴 · 담은로직 `{로직: "", 고정, 수량}`) */
fixed?: (fx: LaborFixed, name: string) => void;
}
/** 고를 수 있는 층 — 위 층만(마스터 → 마스터 · 회사 → 그 회사 · 마스터 · 개인 → 그 개인 · 그 회사 · 마스터) */
async function upper(layer: Layer, owner: string | null): Promise<Scope[]> {
if (layer === "project") return [];
const own =
layer === "system"
? null
: (owner ?? ownOwner(layer, await fetchSessionUser().catch(() => null)));
const out: Scope[] = [{ 층: "system", 주인: null }];
if (own) out.push({ 층: "company", 주인: own.split("/")[0] });
if (own && layer === "personal") out.push({ 층: "personal", 주인: own });
return out;
}
const LAYER_TEXT = {
system: "Lib_System",
company: "Lib_Company",
@@ -103,6 +91,17 @@ export function openLogicPicker(
lib?: LibraryPick,
pre?: { query?: string },
): void {
if (lib?.sangeun && lib.layer !== "project") {
const fixed = lib.fixed;
return openFindPicker({
layer: lib.layer,
owner: lib.owner,
title: lib.title,
logic: done,
ref: lib.pick,
element: fixed && ((e) => fixed(fixedOf(e), e.이름)),
});
}
const subs: SubBrief[] = [];
let classes: ReturnType<typeof precomboClasses> = []; // 사전조합 새 분류(149-2) — 서고 줄이 있을 때만
let all: LogicSummary[] = [];
@@ -114,8 +113,6 @@ export function openLogicPicker(
detail.select.disabled = true;
let close = (): void => {};
let rowPick: Array<() => void> = []; // 표 줄 누름 — 탭마다 다시 셈
let tab: "std" | "sk" = "std";
let skRows: SangeunRow[] | null = null; // 717-4 — 오기 전 = null
/* 한 목록 — 구분(단가산출 · 일위조합) 칸 · 층 단추는 서고 줄(사전조합)의 층 */
const layerBar = el("div", { className: "ui-btn-group", attrs: { "data-pick-layers": "" } });
@@ -171,7 +168,6 @@ export function openLogicPicker(
const find = (): void => {
const q = finder.search.value.trim().toLowerCase();
if (tab === "sk") return findSangeun(q);
const rows = std ?? all.map(plain);
const found = rows.filter(
(x) =>
@@ -250,32 +246,6 @@ export function openLogicPicker(
})),
]);
};
/** 717-4 단가산출 탭 — 같은 표 꼴 · 찾기 = 이름 · 제목 · 구분 */
const findSangeun = (q: string): void => {
const rows = (skRows ?? []).filter(
(x) => !q || [x.이름, x.제목, x.구분, x.상세구분].join(" ").toLowerCase().includes(q),
);
finder.hint.textContent = !skRows
? tc("Loading")
: rows.length
? `${rows.length} / ${skRows.length}`
: tc("Pick_None");
rowPick = rows.map((x) => () => {
close();
lib?.pick(x.ref, x.제목 || x.이름);
});
table.root.hidden = false;
table.setRows(
rows.map((x) => ({
품명: x.제목 || x.이름,
규격: [x.구분, x.상세구분].filter(Boolean).join(" "),
단위: x.단위 ?? "",
금액: x.기준금액,
출처: `${L("M02_PickSangeun")} · ${tc(LAYER_TEXT[x.ref.층 as keyof typeof LAYER_TEXT])}`,
})),
);
finder.setRows([]);
};
const loadLib = async (): Promise<void> => {
if (!lib || !scope) return;
const mine = ++seq;
@@ -340,45 +310,12 @@ export function openLogicPicker(
layerBar.style.display = "none";
const hint = createText(tc("Std_Hint"));
/** 탭 바꿈 — 단가산출 탭은 찾기 칸만(거름 · 층 단추 · 계산서 안내 숨김) */
const tabs = lib?.sangeun
? createTabs({
tabs: [
{ key: "std", label: L("M02_PickStd") },
{ key: "sk", label: L("M02_PickSangeun") },
],
value: tab,
kind: "line",
onChange: (key) => {
tab = key === "sk" ? "sk" : "std";
const std = tab === "std";
for (const f of [kindSel?.root, work.root, sub.root, detail.root])
if (f) f.style.display = std ? "" : "none";
layerBar.style.display = std && scopes.length ? "" : "none";
hint.hidden = !std;
find();
},
})
: null;
if (lib)
void upper(lib.layer, lib.owner).then((got) => {
scopes = got;
if (tabs)
listSangeun(got).then(
(rows) => {
skRows = rows;
tabs.setBadge("sk", rows.length);
find();
},
(error) => {
skRows = [];
find();
showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error");
},
);
scope = got[got.length - 1] ?? null; // 기본 = 가장 가까운 층
if (!got.length) return;
layerBar.style.display = tab === "std" ? "" : "none";
layerBar.style.display = "";
drawLayers();
void loadLib();
void fetchStructureDef().then(
@@ -401,7 +338,7 @@ export function openLogicPicker(
body.append(
el("div", {
className: "m02-std-pick",
children: [...(tabs ? [tabs.root] : []), finder.root, hint, table.root],
children: [finder.root, hint, table.root],
}),
),
}));