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 53867b183..d22b7ecde 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"; @@ -111,7 +112,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, pager], }), grid, 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 {