From a961e9cb693918964e94ebcd8770c63cb023e3c8 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 1 Nov 2026 14:43:23 +0900 Subject: [PATCH] =?UTF-8?q?feat(716-1=C2=B7716-2):=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=AC=BC=20=EC=98=B5=EC=85=98=20=EC=A0=95=EC=9D=98=20=EC=9E=85?= =?UTF-8?q?=EA=B5=AC=20M01=20=C2=B7=20=EB=A9=94=EB=89=B4=20=ED=91=9C=20?= =?UTF-8?q?=EB=91=98=20=C2=B7=20M01=20open=20=EC=9D=B8=EC=9E=90=20(?= =?UTF-8?q?=EC=A4=91=EA=B0=84=20=E2=80=94=20=EA=B5=AC=EB=B6=84=EC=84=A0=20?= =?UTF-8?q?=EB=B6=80=ED=92=88=20=C2=B7=20=EC=8B=9C=ED=97=98=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - M01 왼쪽 단가산출 로직 칸 아래 구조물 옵션 정의 칸 · 본 칸 셋째 모드(저장 안 한 고침 물음) - M02 옛 open=옵션 주소 = 시스템 관리자면 M01 로 돌려보냄 - settingLinks = 대시보드 칸별 줄(group · ?open=) · pageLinks = ☰ 페이지 둘 - 새 글 키 덧만: B01_Dashboard_CardLogic · CardStructOptionDef · CardPrecombo Co-Authored-By: Claude Sonnet 5.5 --- A00_Common/m_settings_nav.ts | 57 +++++++++++++----- .../B01_Dashboard_UI_View_SysDefaults.ts | 16 ++--- M01_MasterData/M01_MasterData_UI_Page.ts | 59 +++++++++++++++++-- M01_MasterData/M01_MasterData_UI_Side.ts | 48 +++++++++++++-- .../M02_MasterTemplete_Layer.ts | 8 +++ .../M02_MasterTemplete_UI_Page.ts | 5 +- .../StructOptions/M02_SO_Style.css | 8 +++ ui_template/ui_template_locale_m1.ts | 2 + ui_template/ui_template_locale_m2.ts | 1 + 9 files changed, 166 insertions(+), 38 deletions(-) diff --git a/A00_Common/m_settings_nav.ts b/A00_Common/m_settings_nav.ts index 73b09f8c7..817dcafc0 100644 --- a/A00_Common/m_settings_nav.ts +++ b/A00_Common/m_settings_nav.ts @@ -1,7 +1,7 @@ /* ============================================================================= * m_settings_nav.ts - * 시스템 설정 바로가기(334 · 요구 225-2) — 대시보드 「시스템 설정」 카드 · M01 · M02 ☰ 가 같이 쓰는 - * 역할 규칙 한 벌(`settingLinks`) · ☰ 진행 단계 자리에 🏠 대시보드 + 카드(`mountSettingsDrawer`). + * 시스템 설정 바로가기(334 · 요구 225-2 · 716-2) — 대시보드 설계 기본값 줄(`settingLinks` · 칸마다 한 줄 · + * `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`). * ========================================================================== */ import { ROUTES, type RoutePath } from "@config/config_frontend"; @@ -16,52 +16,63 @@ interface SettingUser { company_id?: number | null; } -/** key = M01 은 「m01」 · 계산 자리 창은 「calcSites」(대시보드 카드만 · 504-2) · M02 는 진입 인자 `open` */ +/** key = 페이지 열쇠(☰ · 「m01」 「m02」) 또는 칸 열쇠(대시보드 줄 · 계산 자리 「calcSites」 = 창 504-2) · + * group = 대시보드 설계 기본값 묶음 제목 */ export interface SettingLink { key: string; title: string; desc: string; route: RoutePath; query?: Record; + group?: string; } function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } -const ICONS: Record = { m01: "🗄️", 구조물조합: "🧱", 옵션: "🎛️", 도면: "📑" }; +const ICONS: Record = { m01: "🗄️", m02: "🧱" }; -/** 시스템 설정 — 역할로 카드 숨김 · M02 카드는 `?open=&root=` 로 진입(27-1d) */ +/** 설계 기본값 줄(대시보드) — 칸마다 한 줄 · 역할로 숨김 · M01 줄 `?open=`(칸 고름) · M02 줄 `?open=&root=`(27-1d). + * 칸을 더할 때는 아래 표에 한 줄(m01 / m02 호출)만 더함. */ export function settingLinks(user: SettingUser, companyId: number | string | null): SettingLink[] { // 시스템 관리자는 고른 회사를 해시에 실음(29-1) const company: Record = user.role === "SYSTEM_ADMIN" && companyId ? { company: String(companyId) } : {}; + const m01 = (key: string, title: string, desc: string, open?: string): SettingLink => ({ + key, + title, + desc, + route: ROUTES.M01_MASTER_DATA, + query: open ? { open } : undefined, + group: "마스터 데이터", + }); const m02 = (title: string, open: string, root: string, desc: string): SettingLink => ({ key: open, title, desc, route: ROUTES.M02_MASTER_TEMPLATE, query: { open, root, ...company }, + group: "마스터 템플릿", }); // 구조물 조합 기본 층 = 개인(123-1 · 누구든) · 개인 층 없음(회사 없음) = 아래 역할 규칙 const comboRoot = (fallback: string): string => (user.company_id ? "personal" : fallback); if (user.role === "SYSTEM_ADMIN") { const desc = L("B01_Dashboard_PermMasterWrite"); + const read = L("B01_Dashboard_PermSourceRead"); return [ - { - key: "m01", - title: L("B01_Dashboard_MasterElements"), - desc: L("B01_Dashboard_PermSourceRead"), - route: ROUTES.M01_MASTER_DATA, - }, + m01("m01", L("M01_Title"), read), + m01("로직", L("B01_Dashboard_CardLogic"), read, "로직"), + m01("옵션", L("B01_Dashboard_CardStructOptionDef"), desc, "옵션"), + m02(L("B01_Dashboard_CardPrecombo"), "일위대가조합", comboRoot("master"), desc), m02(L("B01_Dashboard_CardStructCombo"), "구조물조합", comboRoot("master"), desc), - m02(L("B01_Dashboard_CardStructOption"), "옵션", "master", desc), m02(L("B01_Dashboard_CardDrawing"), "도면", "master", desc), { key: "calcSites", title: L("B01_CalcSites_Title"), desc: L("B01_CalcSites_Desc"), route: ROUTES.B01_ACCOUNT, + group: "계산", }, ]; } @@ -76,14 +87,30 @@ export function settingLinks(user: SettingUser, companyId: number | string | nul return [m02(L("B01_Dashboard_CardStructCombo"), "구조물조합", comboRoot(root), desc)]; } -/** ☰ 진행 단계 자리 — 🏠 대시보드 + 카드 · 지금 칸 강조 · 같은 길(M02 → M02)은 머리 가드가 안 물어 여기서 먼저 */ +/** ☰ 메뉴 — 페이지 둘만(M01 = 시스템 관리자 · M02 = 누구나) · 열쇠 「m01」 「m02」 */ +export function pageLinks(user: SettingUser, companyId: number | string | null): SettingLink[] { + const combo = settingLinks(user, companyId).find((link) => link.key === "구조물조합"); + const links: SettingLink[] = []; + if (user.role === "SYSTEM_ADMIN") + links.push({ + key: "m01", + title: L("B01_Dashboard_MasterElements"), + desc: "", + route: ROUTES.M01_MASTER_DATA, + }); + if (combo) + links.push({ ...combo, key: "m02", title: L("M02_Title"), desc: "", group: undefined }); + return links; +} + +/** ☰ 진행 단계 자리 — 🏠 대시보드 + 페이지 둘 · 지금 페이지 강조(`activeKey` = 「m01」 「m02」) · + * 같은 길(M02 → M02)은 머리 가드가 안 물어 여기서 먼저 */ export function mountSettingsDrawer( user: SettingUser, activeKey: string, companyId: number | string | null, ): void { - // 계산 자리 = 대시보드 창(504-2) — 페이지가 아니라 ☰ 에 안 둠 - const links = settingLinks(user, companyId).filter((link) => link.key !== "calcSites"); + const links = pageLinks(user, companyId); const bar = createStepBar( links.map((link) => link.title), links.findIndex((link) => link.key === activeKey), diff --git a/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts b/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts index df9a8a70e..72060c3df 100644 --- a/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts +++ b/B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts @@ -1,7 +1,8 @@ /* ============================================================================= * B01_Dashboard_UI_View_SysDefaults.ts - * 시스템 관리 › 설계 기본값(549-2) — 마스터 데이터 · 구조물 조합 · 구조물 옵션 · 도면 양식 · 계산 자리. - * 카드 규칙은 `settingLinks` 한 벌(334) — 여기는 모양만(묶음 제목 + 둥근 상자 안 줄) · 계산 자리 = 창(504-2). + * 시스템 관리 › 설계 기본값(549-2 · 716-2) — 마스터 데이터(마스터 요소 · 단가산출 로직 · 구조물 옵션 정의) · + * 마스터 템플릿(일위대가 · 구조물 조합 · 도면 양식) · 계산 자리. 칸마다 한 줄(누르면 그 칸을 고른 채 열림 · `?open=`). + * 줄 · 묶음 규칙은 `settingLinks` 한 벌(334) — 여기는 모양만(묶음 제목 + 둥근 상자 안 줄) · 계산 자리 = 창(504-2). * ========================================================================== */ import { createButton } from "@ui/ui_template_elements"; @@ -12,22 +13,13 @@ import { pageFrame, settingCard, settingRow } from "./B01_Dashboard_UI_PageKit"; import { group } from "./B01_Dashboard_UI_View_Parts"; import type { ViewCtx } from "./B01_Dashboard_UI_Views"; -/** 카드 열쇠 → 묶음 제목 — 모르는 열쇠는 「그 밖」 */ -const GROUP_OF: Record = { - m01: "마스터 데이터", - 구조물조합: "구조물 · 도면", - 옵션: "구조물 · 도면", - 도면: "구조물 · 도면", - calcSites: "계산", -}; - const open = (link: SettingLink): void => link.key === "calcSites" ? void openCalcSitesDialog() : navigateTo(link.route, link.query); export function mountSysDefaults(host: HTMLElement, ctx: ViewCtx): void { const groups = new Map(); for (const link of settingLinks(ctx.me, ctx.companyId)) { - const title = GROUP_OF[link.key] ?? "그 밖"; + const title = link.group ?? "그 밖"; const openBtn = createButton({ label: "열기", variant: "ghost", onClick: () => open(link) }); groups.set(title, [...(groups.get(title) ?? []), settingRow(link.title, link.desc, openBtn)]); } diff --git a/M01_MasterData/M01_MasterData_UI_Page.ts b/M01_MasterData/M01_MasterData_UI_Page.ts index c40020fd3..f4a2ed43b 100644 --- a/M01_MasterData/M01_MasterData_UI_Page.ts +++ b/M01_MasterData/M01_MasterData_UI_Page.ts @@ -1,8 +1,8 @@ /* ============================================================================= * M01_MasterData_UI_Page.ts * 마스터 요소 화면 — 좌측 도킹 패널(컨테이너: 인력·재료·기계·소요량·계수·환율·요율·단가산출 로직) - * · 일위대가 조합은 M02 마스터 템플릿(PLAN 27-2b) - * / 우측 요소 표 · 표 목록 · 로직 편집 + * · 일위대가 조합은 M02 마스터 템플릿(PLAN 27-2b) · 구조물 옵션 정의(716-1 · 화면은 M02 `StructOptions/`) + * / 우측 요소 표 · 표 목록 · 로직 편집 · 옵션 정의 · 주소 `?open=로직|옵션` = 그 칸을 고른 채 열림(716-2) * * 시스템 관리자만. 고친 것은 초안(sessionStorage)에 쌓이고 [저장] 한 번에 서버로 — * 자동저장 없음. @@ -26,7 +26,7 @@ import { t as L } from "@ui/ui_template_locale"; import { createWorkflowOverlays } from "@ui/ui_template_overlay"; import { ROUTES } from "@config/config_frontend"; import { mountSettingsDrawer } from "../A00_Common/m_settings_nav"; -import { navigateTo } from "../A00_Common/router"; +import { currentQuery, navigateTo, setLeaveGuard } 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"; @@ -89,6 +89,7 @@ function buildPage(): HTMLElement { children: [headBar.root, buildHeadFilters([searchField.root])], }); const logicHost = el("div", { className: "m01-master__logic", attrs: { hidden: "" } }); + const optionsHost = el("div", { className: "m02-sod__host", attrs: { hidden: "" } }); const elementView = el("div", { className: "m01-master__elements", children: [ @@ -112,12 +113,50 @@ function buildPage(): HTMLElement { onDraftChange(refreshBar); refreshBar(); - const showMode = (mode: "elements" | "logic"): void => { + const showMode = (mode: "elements" | "logic" | "options"): void => { logicHost.hidden = mode !== "logic"; elementView.hidden = mode !== "elements"; + optionsHost.hidden = mode !== "options"; + if (mode !== "options") side.setOptionsMark(false); + }; + + /* --- 구조물 옵션 정의(716-1) — 저장 안 한 고침은 칸을 바꿀 때 · 페이지를 떠날 때 물음 --- */ + let optionsDirty = false; + const leaveOptions = async (): Promise => { + if (!optionsDirty || optionsHost.hidden) return true; + if (!(await showConfirmDialog(L("M02_DiscardConfirm")))) return false; + optionsDirty = false; + return true; + }; + /** 옵션 정의에 고침이 있으면 물은 뒤에 `run` — 취소면 옵션 줄 표시를 되돌려 머묾 */ + const afterLeave = (run: () => void): boolean => { + if (!optionsDirty || optionsHost.hidden) return true; + void leaveOptions().then((ok) => { + if (ok) return run(); + side.setActive(null); + side.setOptionsMark(true); + }); + return false; + }; + const showOptions = (label: string, node: HTMLElement): ((dirty: boolean) => void) => { + dispose(); + dispose = (): void => {}; + current = null; + optionsDirty = false; + optionsHost.replaceChildren( + el("div", { + className: "m01-master__headbox", + children: [buildHeadBar({ title: label, buttons: [] }).root], + }), + node, + ); + showMode("options"); + setLeaveGuard(leaveOptions); + return (dirty) => (optionsDirty = dirty); }; const openFile = (pick: Pick): void => { + if (!afterLeave(() => openFile(pick))) return; dispose(); showMode("elements"); if (pick.view) { @@ -203,6 +242,7 @@ function buildPage(): HTMLElement { /** 「단가산출 로직」 탭으로 전환 — `key` 가 있으면 그 로직을 바로 엶. */ const openLogicTab = (key?: string): void => { if (key) setPendingLogicKey(key); + if (!afterLeave(() => openLogicTab())) return; dispose(); dispose = (): void => {}; current = null; @@ -259,7 +299,11 @@ function buildPage(): HTMLElement { setMasterJump((target) => void jump(target)); /* --- 좌측: 컨테이너 --- */ - const side = buildSide(openFile, () => openLogicTab()); + const side = buildSide(openFile, () => openLogicTab(), { + confirmLeave: leaveOptions, + show: showOptions, + onOpen: () => side.setActive(null), + }); const layout = el("div", { className: "ui-workflow-layout m01-master" }); const main = el("main", { @@ -267,7 +311,7 @@ function buildPage(): HTMLElement { children: [ el("div", { className: "m01-master__panel", - children: [elementView, logicHost], + children: [elementView, logicHost, optionsHost], }), ], }); @@ -287,5 +331,8 @@ function buildPage(): HTMLElement { ); // 다른 화면(일위대가 조합 다이어그램 등)이 로직 키를 들고 왔으면 로직 탭으로 바로 엶 if (hasPendingLogicKey()) openLogicTab(); + // 주소 `?open=` — 대시보드 설계 기본값 줄 · 옛 M02 `open=옵션` 즐겨찾기가 그 칸을 고른 채 엶 + const want = currentQuery().get("open"); + if (want === "로직" || want === "옵션") side.reveal(want); return layout; } diff --git a/M01_MasterData/M01_MasterData_UI_Side.ts b/M01_MasterData/M01_MasterData_UI_Side.ts index 0f9e752f0..1c7e90b0e 100644 --- a/M01_MasterData/M01_MasterData_UI_Side.ts +++ b/M01_MasterData/M01_MasterData_UI_Side.ts @@ -1,13 +1,18 @@ /* ============================================================================= * M01_MasterData_UI_Side.ts - * 왼쪽 패널 — 공용 접기 컨테이너(`ui-collapsible ui-sidebar-section`) 아홉 개 + * 왼쪽 패널 — 공용 접기 컨테이너(`ui-collapsible ui-sidebar-section`) * 인력 · 재료 · 기계 · 소요량 · 계수 · 환율 · 요율 · 단가산출 로직(일위대가 조합은 M02 · PLAN 27-2b) + * ─ 구분선 ─ 구조물 옵션 정의(716-1 · 화면 코드는 M02 `StructOptions/` 그대로 · 입구만 여기) * 펼치면 하위 목록(거름 · 파일) · 환율/요율은 누르면 바로 표 · 로직은 안에 로직 목록 * ========================================================================== */ import { createSelectField, el, showToast } from "@ui/ui_template_elements"; import { attachCollapsible } from "@ui/ui_template_collapsible"; import { currentLanguageIndex, t as L } from "@ui/ui_template_locale"; +import { + buildOptionsSection, + type OptionsSectionOptions, +} from "../M02_MasterTemplete/StructOptions/M02_SO_Options"; import { fetchFiles, fetchSubsAll, type FileInfo, type SubInfo } from "./M01_MasterData_Api_Fetch"; import { tx } from "./M01_MasterData_UI_Logic_Text"; import { renderTree, type MakeRow } from "./M01_MasterData_UI_Tree"; @@ -103,6 +108,10 @@ export interface SideHandle { setOnPicked: (fn: () => void) => void; /** 「전체」 + 드롭다운 거름 한 벌 — 로직 컨테이너도 씀 */ filter: (args: FilterArgs) => HTMLElement; + /** 주소 `open` — 그 컨테이너를 펼치고 고름(로직 = 로직 목록 · 옵션 = 정의 화면까지) */ + reveal: (target: "로직" | "옵션") => void; + /** 옵션 줄 고름 표시 지움(다른 화면을 열었음) · 켬(떠나기를 취소해 머묾) */ + setOptionsMark: (on: boolean) => void; } export interface FilterArgs { @@ -157,7 +166,11 @@ const LEAF: Group[] = ["환율", "요율"]; const FILTERED: Group[] = ["인력", "기계", "소요량", "계수"]; const LOGIC_ID = "로직|"; -export function buildSide(onOpen: (pick: Pick) => void, onLogic: () => void): SideHandle { +export function buildSide( + onOpen: (pick: Pick) => void, + onLogic: () => void, + optionsOpt: Omit, +): SideHandle { const lists = new Map(); const bodies = new Map(); const buttons = new Map(); @@ -483,9 +496,12 @@ ${first.file}` onLogic(); }); + // 구조물 옵션 정의 — 로직 칸 아래 구분선 + 칸 하나(시스템 관리자 화면이라 고침 가능) + const options = buildOptionsSection({ ...optionsOpt, isAdmin: true }); + const optionsSec = section(options.title, options.body); const root = el("div", { className: "m01-side ui-sidebar-panel", - children: [...groups, logic], + children: [...groups, logic, el("hr", { className: "ui-sidebar-divider" }), optionsSec], }); attachCollapsible(root); void Promise.all(GROUPS.filter((g) => g !== FUEL_GROUP).map(refresh)).catch((error) => @@ -497,5 +513,29 @@ ${first.file}` // 재료는 유가 · 전력 파일까지 다(알약 → 마스터 화면 길이 파일로 찾음) · 유가 · 전력은 그 파일만 const files = (group: string): FileInfo[] => group === FUEL_GROUP ? shown(FUEL_GROUP) : (lists.get(group as Group) ?? []); - return { root, logicHost, refresh, files, setActive, setOnPicked, filter }; + const reveal = (target: "로직" | "옵션"): void => { + if (target === "옵션") { + optionsSec.classList.remove("is-collapsed"); + options.body.querySelector("button")?.click(); + return; + } + logic.classList.remove("is-collapsed"); + setActive(buttons.has(LOGIC_ID) ? LOGIC_ID : null); + onLogic(); + }; + const setOptionsMark = (on: boolean): void => { + if (!on) return options.clear(); + options.body.querySelector("button")?.classList.add("is-active"); + }; + return { + root, + logicHost, + refresh, + files, + setActive, + setOnPicked, + filter, + reveal, + setOptionsMark, + }; } diff --git a/M02_MasterTemplete/M02_MasterTemplete_Layer.ts b/M02_MasterTemplete/M02_MasterTemplete_Layer.ts index ce046f785..242e0fa69 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Layer.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_Layer.ts @@ -51,6 +51,14 @@ export function entryFromHash(user?: SessionUser | null): Entry { return { root, owner, open, company: query.get("company") || null, pre }; } +/** 옛 주소 `open=옵션`(구조물 옵션 정의는 M01 로 옮김 · 716-1) — 시스템 관리자면 M01 그 칸으로 돌려보냄 · + * 돌려보냈으면 참(그 밖 역할은 읽기 화면이 없어져 구조물 조합으로 열림) */ +export function redirectOldOptions(user: SessionUser | null): boolean { + if (user?.role !== "SYSTEM_ADMIN" || currentQuery().get("open") !== "옵션") return false; + navigateTo(ROUTES.M01_MASTER_DATA, { open: "옵션" }); + return true; +} + /** 진입 해시 인자 — `navigateTo(ROUTES.M02_MASTER_TEMPLATE, entryQuery(…))` */ export const entryQuery = (entry: Entry): Record => ({ open: entry.open, diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts index 17cb6cbc6..436262468 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Page.ts @@ -31,6 +31,7 @@ import { entryQuery, layerOfRoot, ownOwner, + redirectOldOptions, viewCompany, type OpenTarget, type Root, @@ -61,6 +62,8 @@ const NEW_ASK = export async function renderM02MasterTemplate(root: HTMLElement): Promise { const user = await fetchSessionUser().catch(() => null); + // 구조물 옵션 정의는 M01 로 옮김(716-1) — 옛 주소는 M01 그 칸으로 + if (redirectOldOptions(user)) return { covers: () => false, show: () => {}, dispose: () => {} }; const entry = entryFromHash(user); // 134-4 — 해시에 층 없음 = 개인(회사 없으면 마스터) const company = viewCompany(entry, user); // 볼 회사(시스템 관리자는 고른 회사) let layer = layerOfRoot(entry.root); @@ -283,7 +286,7 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise false, show: () => {}, dispose: () => setActiveLayer("system") }; diff --git a/M02_MasterTemplete/StructOptions/M02_SO_Style.css b/M02_MasterTemplete/StructOptions/M02_SO_Style.css index 32fcae514..8815da5fa 100644 --- a/M02_MasterTemplete/StructOptions/M02_SO_Style.css +++ b/M02_MasterTemplete/StructOptions/M02_SO_Style.css @@ -404,3 +404,11 @@ table.m02-sod__table--wrap { word-break: keep-all; overflow-wrap: anywhere; } + +/* M01 본 칸(716-1) — 정의 화면을 얹는 틀: 머리 줄 아래 화면 */ +.m02-sod__host { + display: flex; + flex-direction: column; + gap: var(--spacing-12); + min-width: 0; +} diff --git a/ui_template/ui_template_locale_m1.ts b/ui_template/ui_template_locale_m1.ts index 04c28d9a3..6c686efd7 100644 --- a/ui_template/ui_template_locale_m1.ts +++ b/ui_template/ui_template_locale_m1.ts @@ -171,4 +171,6 @@ export const ui_locales_m1 = { B01_CalcSites_Saved: ["계산 자리 저장함", "Calc sites saved"], B01_CalcSites_Leave: ["저장 안 한 계산 자리가 있음", "You have unsaved calc sites"], B01_CalcSites_LoadFailed: ["계산 자리를 못 읽음", "Failed to load calc sites"], + B01_Dashboard_CardLogic: ["단가산출 로직", "Cost-logic library"], + B01_Dashboard_CardStructOptionDef: ["구조물 옵션 정의", "Structure option definition"], } as const; diff --git a/ui_template/ui_template_locale_m2.ts b/ui_template/ui_template_locale_m2.ts index 3a5811a1d..0639ba79f 100644 --- a/ui_template/ui_template_locale_m2.ts +++ b/ui_template/ui_template_locale_m2.ts @@ -188,4 +188,5 @@ export const ui_locales_m2 = { "그 사이 실무문서가 바뀜 — 목록을 다시 받음", "Practice documents changed — reloaded", ], + B01_Dashboard_CardPrecombo: ["일위대가", "Unit-cost items"], } as const;