From 65c2de2e9c5ee7f137d3d780219c3cf4e535d980 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 2 Oct 2026 09:42:34 +0900 Subject: [PATCH] =?UTF-8?q?feat(m01):=20=EB=A7=88=EC=8A=A4=ED=84=B0=20?= =?UTF-8?q?=EC=9A=94=EC=86=8C=20=EC=83=81=EC=84=B8=20=EC=9D=BC=EA=B3=B1=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=A8=B8=EB=A6=AC=EB=A5=BC=20=EB=8B=A8?= =?UTF-8?q?=EA=B0=80=EC=82=B0=EC=B6=9C=20=EB=A1=9C=EC=A7=81=EA=B3=BC=20?= =?UTF-8?q?=EA=B0=99=EC=9D=80=20=EA=B3=B5=EC=9A=A9=20=EC=A1=B0=EA=B0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C(=EC=A0=9C=EB=AA=A9=2018px=20=C2=B7=20?= =?UTF-8?q?=EB=8B=A8=EC=B6=94=20=EC=A4=84=20=C2=B7=20=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=EC=A4=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr --- M01_MasterData/M01_MasterData_UI_Head.css | 18 ++++++++++ M01_MasterData/M01_MasterData_UI_Head.ts | 36 +++++++++++++++++++ M01_MasterData/M01_MasterData_UI_LogicLab.ts | 15 ++++---- .../M01_MasterData_UI_Logic_List.ts | 3 +- M01_MasterData/M01_MasterData_UI_Page.ts | 9 +++-- M01_MasterData/M01_MasterData_UI_Style.css | 11 +++--- 6 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 M01_MasterData/M01_MasterData_UI_Head.css create mode 100644 M01_MasterData/M01_MasterData_UI_Head.ts diff --git a/M01_MasterData/M01_MasterData_UI_Head.css b/M01_MasterData/M01_MasterData_UI_Head.css new file mode 100644 index 000000000..66c1e41c3 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_Head.css @@ -0,0 +1,18 @@ +.m01-head { + display: flex; + align-items: center; + gap: var(--spacing-8); + flex-wrap: wrap; +} + +.m01-head__title { + margin: 0 auto 0 0; + color: var(--color-text); + font-size: 18px; +} + +.m01-head__filters { + display: flex; + flex-direction: column; + gap: var(--spacing-4); +} diff --git a/M01_MasterData/M01_MasterData_UI_Head.ts b/M01_MasterData/M01_MasterData_UI_Head.ts new file mode 100644 index 000000000..9587effa7 --- /dev/null +++ b/M01_MasterData/M01_MasterData_UI_Head.ts @@ -0,0 +1,36 @@ +/* ============================================================================= + * M01_MasterData_UI_Head.ts + * 마스터 화면 머리 공용 조각 — 제목 줄(제목 왼쪽 · 단추 오른쪽) + 찾기 줄. + * 인력 · 재료 · 유가·전력 · 기계 · 소요량 · 계수 · 환율 · 요율 상세와 「단가산출 로직」 화면이 같은 조각을 써 + * 제목 크기 · 자리 · 단추 차례 · 공용 단추 크기 · 찾기 칸 꼴이 한 벌로 같음. + * ========================================================================== */ + +import { el } from "@ui/ui_template_elements"; +import "./M01_MasterData_UI_Head.css"; + +export interface HeadOptions { + /** 제목 앞에 놓을 것(예: ← 목록 단추) */ + lead?: HTMLElement[]; + /** 제목 — 글이면 h2 를 만듦 */ + title: HTMLElement | string; + /** 제목 바로 뒤 · 단추 앞 알림(고친 개수 등) */ + status?: HTMLElement[]; + /** 오른쪽 단추 — 차례 그대로(공용 단추 · 크기를 덮지 않음) */ + buttons: HTMLElement[]; +} + +/** 제목 줄 — 제목은 18px · 왼쪽 · 남는 자리를 차지해 단추를 오른쪽으로 밂 */ +export function buildHeadBar(opts: HeadOptions): { root: HTMLElement; title: HTMLElement } { + const title = typeof opts.title === "string" ? el("h2", { text: opts.title }) : opts.title; + title.classList.add("m01-head__title"); + const root = el("div", { + className: "m01-head", + children: [...(opts.lead ?? []), title, ...(opts.status ?? []), ...opts.buttons], + }); + return { root, title }; +} + +/** 찾기 줄 — 찾기 칸 · 거름 · 쪽 넘기기를 위에서 아래로 쌓음 */ +export function buildHeadFilters(children: HTMLElement[]): HTMLElement { + return el("div", { className: "m01-head__filters", children }); +} diff --git a/M01_MasterData/M01_MasterData_UI_LogicLab.ts b/M01_MasterData/M01_MasterData_UI_LogicLab.ts index 3f29328c9..8f20784f8 100644 --- a/M01_MasterData/M01_MasterData_UI_LogicLab.ts +++ b/M01_MasterData/M01_MasterData_UI_LogicLab.ts @@ -52,6 +52,7 @@ import { type LogicFlow, } from "./M01_MasterData_UI_LogicLab_Flow"; import { askBasicInfo } from "./M01_MasterData_UI_LogicLab_Basic"; +import { buildHeadBar } from "./M01_MasterData_UI_Head"; import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import type { CfLogic } from "../M02_MasterTemplete/DataFlow/M02_DF_Model"; import "./M01_MasterData_UI_Logic_Style.css"; @@ -539,19 +540,19 @@ export async function mountM01LogicLab( }) : showToast(tl("Copy_NeedOpen"), "info"), }); - const bar = el("div", { - className: "m01-logic__bar", - children: [ - back, - el("h2", { text: tx("Title") }), - pending, + // 제목 줄 = 요소 화면(인력 · 재료 …)과 같은 공용 조각 + const bar = buildHeadBar({ + lead: [back], + title: tx("Title"), + status: [pending], + buttons: [ createButton({ label: tf("New"), variant: "filled", onClick: () => void onNew() }), copyButton, createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }), discardButton, saveButton, ], - }); + }).root; host.replaceChildren( el("div", { className: "m01-logic", diff --git a/M01_MasterData/M01_MasterData_UI_Logic_List.ts b/M01_MasterData/M01_MasterData_UI_Logic_List.ts index 334592c0e..468d052c5 100644 --- a/M01_MasterData/M01_MasterData_UI_Logic_List.ts +++ b/M01_MasterData/M01_MasterData_UI_Logic_List.ts @@ -8,6 +8,7 @@ import { createInputField, el } from "@ui/ui_template_elements"; import { buildPager } from "./M01_MasterData_UI_Cells"; import type { LogicSummary } from "./M01_MasterData_UI_Logic_Api"; import { tx } from "./M01_MasterData_UI_Logic_Text"; +import "./M01_MasterData_UI_Head.css"; export type ListMark = "edited" | "new" | "deleted"; @@ -114,7 +115,7 @@ export function buildList(onOpen: (item: ListItem) => void): ListHandle { className: "m01-logic__listview", children: [ el("div", { - className: "m01-logic__filters", + className: "m01-head__filters", children: [ searchField.root, el("label", { diff --git a/M01_MasterData/M01_MasterData_UI_Page.ts b/M01_MasterData/M01_MasterData_UI_Page.ts index 43ccda072..6b0b3a9e6 100644 --- a/M01_MasterData/M01_MasterData_UI_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Page.ts @@ -23,6 +23,7 @@ import { navigateTo } from "../A00_Common/router"; import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { saveFiles, type FileInfo } from "./M01_MasterData_Api_Fetch"; import { discard, isStale, onDraftChange, payload, totalCount } from "./M01_MasterData_Draft"; +import { buildHeadBar, buildHeadFilters } from "./M01_MasterData_UI_Head"; import { renderRows } from "./M01_MasterData_UI_Rows"; import { buildSide, @@ -62,7 +63,7 @@ function buildPage(): HTMLElement { let logicMounted = false; /* --- 우측: 머리(제목·찾기·저장) + 알림 + 본문 --- */ - const title = el("h2", { className: "m01-master__title", text: L("M01_PickFile") }); + const title = el("h2", { text: L("M01_PickFile") }); const searchField = createInputField({ type: "search", placeholder: L("M01_Search") }); const search = searchField.input; const summary = el("span", { className: "m01-master__summary" }); @@ -70,9 +71,11 @@ function buildPage(): HTMLElement { const drop = createButton({ label: L("M01_Discard"), variant: "ghost" }); const notice = el("div", { className: "m01-master__notice", attrs: { hidden: "" } }); const body = el("div", { className: "m01-master__body" }); + // 제목 줄 · 찾기 줄 = 「단가산출 로직」 화면과 같은 공용 조각 + const headBar = buildHeadBar({ title, status: [summary], buttons: [drop, save] }); const head = el("div", { - className: "m01-master__head", - children: [title, searchField.root, summary, drop, save], + className: "m01-master__headbox", + children: [headBar.root, buildHeadFilters([searchField.root])], }); const logicHost = el("div", { className: "m01-master__logic", attrs: { hidden: "" } }); const elementView = el("div", { diff --git a/M01_MasterData/M01_MasterData_UI_Style.css b/M01_MasterData/M01_MasterData_UI_Style.css index f2bd7a239..20950050a 100644 --- a/M01_MasterData/M01_MasterData_UI_Style.css +++ b/M01_MasterData/M01_MasterData_UI_Style.css @@ -32,7 +32,6 @@ padding: var(--spacing-16) var(--spacing-24); } -.m01-master__head, .m01-master__bar, .m01-master__filter { display: flex; @@ -41,11 +40,11 @@ gap: var(--spacing-12); } -.m01-master__title { - flex: 1 1 auto; - margin: 0; - color: var(--color-text); - font-size: var(--text-body); +/* 머리 상자 — 제목 줄(공용 Head) 아래 찾기 줄 */ +.m01-master__headbox { + display: flex; + flex-direction: column; + gap: var(--spacing-8); } .m01-master__summary {