diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor.css b/M02_MasterTemplete/M02_MasterTemplete_Labor.css index 9e31fece..f6791ee2 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor.css +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor.css @@ -12,3 +12,12 @@ .m01-combo .m01-logic__muted { color: var(--color-text-secondary); } + +/* 조합 목록 — 공용 표 모양(줄 선 · 머리 줄) · 줄을 누르면 상세(PLAN 30-5) */ +.m02-labor__list tbody tr { + cursor: pointer; +} + +.m02-labor__list tbody tr:hover { + background: var(--color-mist-violet); +} diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor.ts index 6696c345..fbec69a5 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor.ts @@ -51,6 +51,8 @@ export interface LaborLibrary { /** 켜진 층 단추를 다시 누름 — 조합 목록으로(저장 안 한 고침은 물음 · PLAN 30-4) */ back: () => Promise; refresh: () => Promise; + /** 제목 줄 오른쪽 끝 도구 — 「새 조합」(읽기 층 = null · PLAN 30-5) */ + tools: HTMLElement | null; /** 메인 칸 「저장 안 한 고침」 손잡이 */ bind: (setDirty: (dirty: boolean) => void) => void; } @@ -157,13 +159,14 @@ export function buildLaborLibrary(opt: LaborLibraryOptions): LaborLibrary { editor.showList(); }, refresh: () => tree.refresh(), + tools: editor.newButton, bind: (fn) => (setDirty = fn), }; } export interface LaborSectionOptions { /** 메인 칸에 화면을 띄움 — 고침 표시 손잡이를 돌려받음 */ - show: (label: string, node: HTMLElement) => (dirty: boolean) => void; + show: (label: string, node: HTMLElement, tools?: HTMLElement | null) => (dirty: boolean) => void; /** 저장 안 한 고침을 버릴지 확인 — 취소면 false */ confirmLeave: () => Promise; /** 화면을 열었음 — 좌측 다른 고름 표시를 지움 */ @@ -193,7 +196,7 @@ export function buildLaborSection(opt: LaborSectionOptions): LaborSection { async function open(): Promise { if (opened || !(await opt.confirmLeave())) return; - library.bind(opt.show(tc("Library"), library.root)); + library.bind(opt.show(tc("Library"), library.root, library.tools)); opt.onOpen(); mark(true); await library.refresh(); diff --git a/M02_MasterTemplete/M02_MasterTemplete_Labor_Edit.ts b/M02_MasterTemplete/M02_MasterTemplete_Labor_Edit.ts index efc00c27..23990998 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Labor_Edit.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Labor_Edit.ts @@ -29,6 +29,7 @@ import { tc } from "./M02_MasterTemplete_Labor_Text"; import { matchText } from "./M02_MasterTemplete_TreePanel_Model"; import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css"; import "../M01_MasterData/M01_MasterData_UI_LogicLab_Style.css"; +import "@ui/ui_template_general_layout.css"; import "./M02_MasterTemplete_Labor.css"; const blankCombo = (path: string[] = []): Combo => ({ @@ -75,6 +76,8 @@ export interface LaborEditor { /** 나무 「조합 더하기」 — 그 폴더(구분 · 상세구분)를 채운 빈 조합 */ startNew: (path: string[]) => void; showList: () => void; + /** 「새 조합」 — 제목 줄 오른쪽 끝에 붙임(읽기 층 = null) */ + newButton: HTMLElement | null; isDirty: () => boolean; /** 열린 조합 키 — 목록이면 null · 새 조합이면 빈 글 */ current: () => string | null; @@ -141,21 +144,15 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor { tr.addEventListener("click", () => void open(x.키)); return tr; }); + // 「새 조합」은 제목 줄 오른쪽 끝(`newButton` · PLAN 30-5) · 목록 = 공용 표 모양(줄 선 · 머리 줄) view.replaceChildren( - el("div", { - className: "m01-logic__section-head", - children: [ - ...(edit - ? [createButton({ label: tc("New"), variant: "filled", onClick: () => startNew([]) })] - : [el("span", { className: "m01-logic__muted", text: tc("ReadOnly") })]), - ], - }), + ...(edit ? [] : [el("p", { className: "m01-logic__muted", text: tc("ReadOnly") })]), shown.length ? el("div", { - className: "m01-master__grid-wrap", + className: "ui-general-block__table-wrap ui-general-block__table-wrap--sticky-head", children: [ el("table", { - className: "m01-master__grid", + className: "ui-general-block__table m02-labor__list", children: [ el("thead", { children: [el("tr", { children: heads.map((t) => el("th", { text: t })) })], @@ -387,6 +384,9 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor { open, startNew, showList: drawList, + newButton: edit + ? createButton({ label: tc("New"), variant: "filled", onClick: () => startNew([]) }) + : null, isDirty: dirty, current: () => current?.combo.키 ?? null, setQuery, diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index 82baccef..2108e9fb 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -93,7 +93,11 @@ export interface MainHandle { /** 저장 안 한 고침이 있으면 버릴지 물음 — 취소면 false */ confirmLeave: () => Promise; /** 양식 밖 화면(좌측 자재 조합 상세)을 띄움 — 머리는 제목만 · 고침 표시 손잡이를 돌려줌 */ - showView: (label: string, node: HTMLElement) => (dirty: boolean) => void; + showView: ( + label: string, + node: HTMLElement, + tools?: HTMLElement | null, + ) => (dirty: boolean) => void; /** 구조물집계표를 열고 그 열을 고름 — 이미 표면 다시 안 읽음(`fresh` 면 다시 읽음) */ openTable: (layer: Layer, colId?: string | null, fresh?: boolean) => Promise; /** 나무를 고치기 전 — 저장 안 한 고침이 있으면 저장 · 버림 · 취소를 물음 */ @@ -512,7 +516,12 @@ export function createMain( } } - function showView(label: string, node: HTMLElement): (d: boolean) => void { + /** `tools` = 제목 줄 오른쪽 끝(빈 slot 에 · 다음에 열 때 `drop` 이 비움 · 일위대가 「새 조합」) */ + function showView( + label: string, + node: HTMLElement, + tools?: HTMLElement | null, + ): (d: boolean) => void { ++seq; dirty = false; drop(); @@ -523,6 +532,8 @@ export function createMain( showAside(); title.textContent = label; host.replaceChildren(node); + if (tools) + slot.replaceChildren(el("span", { className: "m02-main__tools", children: [tools] })); return (d) => (dirty = d); } diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css b/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css index 2931748f..8f138a88 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Style.css @@ -56,6 +56,12 @@ min-width: 0; } +/* 제목 줄 오른쪽 끝 도구(일위대가 「새 조합」 · PLAN 30-5) */ +.m02-main__tools { + display: inline-flex; + margin-left: auto; +} + .m02-main__notice { display: flex; align-items: center; @@ -226,4 +232,4 @@ .m02-main__head + .m02-tree__search { margin-bottom: var(--spacing-8); -} \ No newline at end of file +} diff --git a/resources/tester/test_m02_labor_tree.py b/resources/tester/test_m02_labor_tree.py index de58350b..9268309c 100644 --- a/resources/tester/test_m02_labor_tree.py +++ b/resources/tester/test_m02_labor_tree.py @@ -101,3 +101,24 @@ def test_일위대가_나무에_여러_개_복사_붙여넣기_연결(): assert "pasteCombos(clip.from as LaborScope, pick, dest, list.판, to)" in labor # 대상 층 page = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts").read_text("utf-8") assert "canEdit: isAdmin, reveal, copyTo })" in page # 구조물 나무와 같은 「〇〇로 복사」 목록 + + +def test_새_조합_단추는_제목_줄_오른쪽_목록은_공용_표(): + """PLAN 30-5 — 「새 조합」 = 제목 줄 오른쪽 끝(읽기 층 없음) · 목록 = 공용 표(줄 선). + 머리 줄도 공용 표 모양.""" + edit = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor_Edit.ts").read_text("utf-8") + assert ( + "newButton: edit" in edit and 'className: "ui-general-block__table m02-labor__list"' in edit + ) + assert ( + 'tc("New"), variant: "filled", onClick: () => startNew([]) })]' not in edit + ) # 목록 안 옛 자리 + labor = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Labor.ts").read_text("utf-8") + assert 'library.bind(opt.show(tc("Library"), library.root, library.tools))' in labor + main = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts").read_text("utf-8") + assert ( + 'slot.replaceChildren(el("span", { className: "m02-main__tools", children: [tools] }))' + in main + ) + css = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Style.css").read_text("utf-8") + assert ".m02-main__tools {\n display: inline-flex;\n margin-left: auto;" in css