fix(M02): 가져오기 목록 — 그 부분 문서가 없는 줄은 흐리게 · 누르면 까닭 한 줄 · 확인 창 · 복사 안 감

39-8 H3 — 옮기기 전 층(일위대가 labor 없음 등)에서 고르면 「없는 양식」 으로 실패하던 것 · missingPart(도면 · 면적산출 · 일위대가 · 항목 통째는 막지 않음).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
2026-10-04 21:30:36 +09:00
co-authored by Claude Opus 5.5
parent 46f8b7611d
commit 31d9a099be
5 changed files with 51 additions and 4 deletions
@@ -7,7 +7,7 @@
* 찾기 = 보이는 칸 글 전부에서 띄어쓰기 AND(나무 찾기와 같은 `matchText`).
* ========================================================================== */
import { createInputField, el } from "@ui/ui_template_elements";
import { createInputField, el, showToast } from "@ui/ui_template_elements";
import { buildPager } from "../M01_MasterData/M01_MasterData_UI_Cells";
import { buildHeadFilters } from "../M01_MasterData/M01_MasterData_UI_Head";
import "../M01_MasterData/M01_MasterData_UI_Style.css";
@@ -89,6 +89,16 @@ export const ITEM_COLS: Record<ListKind, Col<ItemRow>[]> = {
],
};
const PART_NAME = { structure: "도면", area: "면적산출", labor: "일위대가" } as const;
/** 그 부분 문서가 없는 줄의 까닭(항목 하나 복사 · 39-8 H3) — 옮기기 전 층의 일위대가처럼 파일이 없으면
* 가져올 것이 없음 · 항목 통째(복사 생성)는 없는 문서를 빈 문서로 세워 막지 않음 · 있으면 null */
export function missingPart(kind: ListKind, row: ItemRow): string | null {
if (kind === "item") return null;
const has = kind === "labor" ? !!row.labor : !!row.수정일?.[kind];
return has ? null : `이 항목에는 ${PART_NAME[kind]} 문서가 없음`;
}
/** 찾기 · 쪽 — 보일 줄 · 맞춘 쪽 · 맞는 줄 수(화면 밖 · 시험이 부름) */
export function pageOf<R>(
rows: R[],
@@ -131,7 +141,12 @@ export function buildAllItems(
export interface ItemListHandle {
root: HTMLElement;
/** 줄 · 칸 · 누를 일을 바꿔 그림(찾는 글은 그대로 · 쪽은 1) */
show: <R>(rows: R[], cols: Col<R>[], onPick: (row: R) => void) => void;
show: <R>(
rows: R[],
cols: Col<R>[],
onPick: (row: R) => void,
blocked?: (row: R) => string | null,
) => void;
/** 표 자리에 글 한 줄(불러오는 중 · 못 받음) */
message: (text?: string) => void;
}
@@ -147,7 +162,12 @@ export function buildItemList(): ItemListHandle {
grid.replaceChildren(el("p", { className: "m01-master__empty", text }));
};
const show = <R>(rows: R[], cols: Col<R>[], onPick: (row: R) => void): void => {
const show = <R>(
rows: R[],
cols: Col<R>[],
onPick: (row: R) => void,
blocked?: (row: R) => string | null,
): void => {
page = 1;
draw = (): void => {
const got = pageOf(rows, cols, search.value, page);
@@ -167,7 +187,13 @@ export function buildItemList(): ItemListHandle {
return el("td", { text, attrs: { title: text } });
}),
});
tr.addEventListener("click", () => onPick(row));
// 고를 수 없는 줄 — 흐리게 · 누르면 까닭 한 줄(고르기 안 함)
const why = blocked?.(row) ?? null;
if (why) {
tr.classList.add("is-blocked");
tr.title = why;
}
tr.addEventListener("click", () => (why ? showToast(why, "info") : onPick(row)));
return tr;
});
grid.replaceChildren(
@@ -33,3 +33,9 @@
flex-wrap: wrap;
gap: var(--spacing-8);
}
/* 그 부분 문서가 없는 줄(39-8 H3) — 흐리게 · 누르면 까닭만 */
.m02-itemlist .m01-master__table-row.is-blocked {
opacity: 0.45;
cursor: not-allowed;
}
@@ -15,6 +15,7 @@ import {
buildItemList,
ITEM_COLS,
loadItems,
missingPart,
type Col,
type ItemRow,
type ListKind,
@@ -141,6 +142,7 @@ export function buildPicker(opt: PickerOptions): { root: HTMLElement } {
rows.filter((row) => !opt.skip?.(from, row)),
ITEM_COLS[opt.kind],
(row) => opt.onPick({ from, row }),
(row) => missingPart(opt.kind, row), // 그 부분 문서 없는 줄 = 흐림 · 까닭(39-8 H3)
);
} catch (error) {
if (mine === seq) list.message(TEXT.failed.replace("{value}", why(error)));
@@ -61,5 +61,9 @@ process.stdout.write(
),
pageOver: m.pageOf(many, m.ITEM_COLS.structure, "", 9).page,
pageEmpty: m.pageOf([], m.ITEM_COLS.structure, "", 2).page,
// 그 부분 문서 없는 줄(39-8 H3) — c1 = 일위대가 있음 · 면적 수정일 없음 · c3 = 수정일 · 일위대가 없음
missing: ["structure", "area", "labor", "item"].map((k) =>
rows.map((r) => m.missingPart(k, r)),
),
}),
);
+9
View File
@@ -67,6 +67,15 @@ def test_찾기_쪽(ts):
assert ts["pageOver"] == 3 and ts["pageEmpty"] == 1
def test_부분_문서_없는_줄_까닭(ts):
"""39-8 H3 — 그 부분 문서가 없으면 고르기 막음(흐림 · 까닭) · 항목 통째는 막지 않음."""
structure, area, labor, item = ts["missing"]
assert structure == [None, None, "이 항목에는 도면 문서가 없음"]
assert area == ["이 항목에는 면적산출 문서가 없음", None, "이 항목에는 면적산출 문서가 없음"]
assert labor == [None, None, "이 항목에는 일위대가 문서가 없음"]
assert item == [None, None, None]
def test_서버_목록_칸_수정일_labor(world):
t = Tree(_as(world), "personal")
t.do(op="add-item", 경로=["배수"], 이름="측구", 단위="m")