feat(M02): 일위대가 「새 조합」 단추를 제목 줄 오른쪽 끝으로 · 조합 목록 공용 표 모양(PLAN 30-5 일위대가)

- UI_Main showView 에 tools(빈 slot · 다음 열기 때 drop 이 비움) · .m02-main__tools 오른쪽 끝
- 읽기 층은 단추 없음 · 목록 안 옛 단추 뺌
- 조합 목록 = ui-general-block__table(줄 선 · 머리 줄 고정) · 줄 누름 모양
- 시험 test_m02_labor_tree 한 건 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BU9gbNE3aWFUvE61pS2WGt
This commit is contained in:
2026-09-30 19:25:53 +09:00
co-authored by Claude Opus 5.5
parent 4fa49e307a
commit 3ec501ab47
6 changed files with 65 additions and 15 deletions
@@ -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);
}
@@ -51,6 +51,8 @@ export interface LaborLibrary {
/** 켜진 층 단추를 다시 누름 — 조합 목록으로(저장 안 한 고침은 물음 · PLAN 30-4) */
back: () => Promise<void>;
refresh: () => Promise<void>;
/** 제목 줄 오른쪽 끝 도구 — 「새 조합」(읽기 층 = 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<boolean>;
/** 화면을 열었음 — 좌측 다른 고름 표시를 지움 */
@@ -193,7 +196,7 @@ export function buildLaborSection(opt: LaborSectionOptions): LaborSection {
async function open(): Promise<void> {
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();
@@ -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,
@@ -93,7 +93,11 @@ export interface MainHandle {
/** 저장 안 한 고침이 있으면 버릴지 물음 — 취소면 false */
confirmLeave: () => Promise<boolean>;
/** 양식 밖 화면(좌측 자재 조합 상세)을 띄움 — 머리는 제목만 · 고침 표시 손잡이를 돌려줌 */
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<void>;
/** 나무를 고치기 전 — 저장 안 한 고침이 있으면 저장 · 버림 · 취소를 물음 */
@@ -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);
}
@@ -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);
}
}
+21
View File
@@ -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