diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemList.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemList.ts index 4d015ca6b..c52f0345c 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemList.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemList.ts @@ -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[]> = { ], }; +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( rows: R[], @@ -131,7 +141,12 @@ export function buildAllItems( export interface ItemListHandle { root: HTMLElement; /** 줄 · 칸 · 누를 일을 바꿔 그림(찾는 글은 그대로 · 쪽은 1) */ - show: (rows: R[], cols: Col[], onPick: (row: R) => void) => void; + show: ( + rows: R[], + cols: Col[], + 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 = (rows: R[], cols: Col[], onPick: (row: R) => void): void => { + const show = ( + rows: R[], + cols: Col[], + 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( diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css index a6713f59b..de02c066e 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.css @@ -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; +} diff --git a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts index 4fd1b1215..7d010b5f8 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_ItemPick.ts @@ -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))); diff --git a/resources/tester/helper_m02_item_list.cjs b/resources/tester/helper_m02_item_list.cjs index 875074b62..dcf2777a7 100644 --- a/resources/tester/helper_m02_item_list.cjs +++ b/resources/tester/helper_m02_item_list.cjs @@ -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)), + ), }), ); diff --git a/resources/tester/test_m02_item_list.py b/resources/tester/test_m02_item_list.py index ccdfa1f68..935b8576c 100644 --- a/resources/tester/test_m02_item_list.py +++ b/resources/tester/test_m02_item_list.py @@ -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")