fix(M01): 잡힌 품목 이름 · 모달 검색 칸 괄호 앞 낱말 · 상세 재료 줄 모달 서버 품목 · 단위경고 딱지 상세 줄에도 (PLAN 8-5)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-24 22:07:11 +09:00
co-authored by Claude Sonnet 5
parent 3f7d2b386f
commit 8759bd7929
5 changed files with 59 additions and 20 deletions
@@ -10,7 +10,6 @@
import { createButton, el } from "@ui/ui_template_elements";
import {
fetchMaterials,
searchElements,
type ElementBrief,
type NamedFormula,
@@ -46,7 +45,18 @@ export interface PickCond {
export const condText = (cond: PickCond): string =>
["구분", "상세구분", "이름", "규격", "대표", "지역", "계약종별"]
.map((k) => String(cond[k] ?? ""))
.join("|");
.join("|") + (cond["검색어"] ? `|${cond["검색어"]}` : "");
/** 재료 모달 검색 칸 글 — 검색어 첫 낱말의 괄호 앞(「합판(내수) 12t」 → 「합판」) */
export const headWord = (text: unknown): string =>
String(text ?? "")
.trim()
.split(/\s+/)[0]
.replace(/[((].*$/, "");
/** 고르기 줄 후보 찾기 글 — 검색어 → 이름 → 상세구분 → 구분 차례 첫 낱말 */
export const seedOf = (cond: PickCond): string =>
[cond["검색어"], cond.이름, cond.상세구분, cond.구분].map(headWord).find(Boolean) ?? "";
export interface ModalTarget {
title: string;
@@ -227,19 +237,12 @@ const muted = (text: string): HTMLElement => el("p", { className: "m01-logic__mu
/** 재료 고르기 줄 후보 목록 — 잡힌 품목(`pickedRef`)을 노란 줄로 · 후보가 100건을 넘으면 앞 100건만 옴 */
function candidateView(
cond: PickCond,
unit: string,
pickedRef: string,
onPicked: (item: ElementBrief | null) => void,
): HTMLElement {
const box = el("div", { className: "m01lab__data", attrs: { "data-candidates": "1" } });
box.append(muted("…"));
void fetchMaterials({
sub: String(cond["구분"] ?? ""),
detail: String(cond["상세구분"] ?? ""),
spec: String(cond["규격"] ?? ""),
region: "",
unit,
})
void searchElements("재료", seedOf(cond))
.then((got) => {
onPicked(got.items.find((it) => it.ref === pickedRef) ?? null);
const rows = got.items.map((it) =>
@@ -301,12 +304,17 @@ export function openMaterialModal(target: ModalTarget): void {
const element = target.element ?? "";
const base = element.split(".")[0];
// 고르기 줄 = 서버가 조건 글을 열쇠로 준 줄(대표가 안 정해졌으면 없음)
const priced = cond ? target.prices[condText(cond)] : undefined;
const known = cond
? target.prices[condText(cond)]
? target.unitRef && priced?.ref !== target.unitRef
? undefined // 서버가 잡은 품목이 다르면 그 품목으로
: priced
: element
? (target.prices[element] ?? target.prices[base])
: undefined;
const priceRef = cond ? pickedOf(cond, target.values, known?.ref ?? "") : element;
const priceRef = cond
? target.unitRef || pickedOf(cond, target.values, known?.ref ?? "")
: element;
const logicKey = /^로직\((\w+)/.exec(element)?.[1] ?? "";
const sum = el("p", { className: "m01lab__sum" });
const setSum = (price: ElementBrief | null | undefined): void => {
@@ -366,7 +374,7 @@ export function openMaterialModal(target: ModalTarget): void {
rate,
...(cond
? [
candidateView(cond, target.unit ?? "", priceRef, (item) => {
candidateView(cond, priceRef, (item) => {
if (known || !priceRef) return;
const show = (brief: ElementBrief | null): void => {
rate.replaceChildren(