diff --git a/A00_Common/app_shell.ts b/A00_Common/app_shell.ts index b79578b37..b1ff0bda9 100644 --- a/A00_Common/app_shell.ts +++ b/A00_Common/app_shell.ts @@ -133,7 +133,7 @@ function buildBrand(): HTMLButtonElement { * 서랍을 열 때 서랍으로 옮기고 닫을 때 제자리(진행 판)로 — 그리기 · 누름 코드는 한 벌. */ let drawerSteps: { node: HTMLElement; home: HTMLElement | null } | null = null; -/** 집(진행 판) 없는 요소 = 서랍이 늘 가짐 · 닫을 때 안 옮김(B01 · M01 · M02 바로가기) */ +/** 집(진행 판) 없는 요소 = 서랍이 늘 가짐 · 닫을 때 안 옮김(M01 · M02 바로가기 · 기본 목록) */ export function setDrawerSteps(node: HTMLElement | null): void { drawerSteps = node ? { node, home: node.parentElement } : null; } @@ -178,11 +178,20 @@ function buildDrawer(header: HTMLElement): HTMLElement[] { const setOpen = (open: boolean): void => { header.classList.toggle("is-drawer-open", open); burger.setAttribute("aria-expanded", String(open)); + if (open && !drawerSteps) void fillDefaultSteps(); if (!drawerSteps) return; - if (open) steps.append(drawerSteps.node); + if (open) + steps.replaceChildren(drawerSteps.node); // 앞서 맡은 기본 목록이 있으면 갈아 끼움 else if (drawerSteps.home && steps.contains(drawerSteps.node)) drawerSteps.home.append(drawerSteps.node); }; + // 진행 판 · 바로가기를 안 맡긴 쪽(772-1) = 기본 목록(🏠 대시보드 + 권한별 M01 · M02) · 늦게 온 쪽 목록이 이김 + const fillDefaultSteps = async (): Promise => { + const bar = await import("./m_settings_nav").then((m) => m.defaultSettingsBar()); + if (!bar || drawerSteps || !steps.isConnected) return; + setDrawerSteps(bar); + if (header.classList.contains("is-drawer-open")) steps.replaceChildren(bar); + }; burger.addEventListener("click", () => setOpen(!header.classList.contains("is-drawer-open"))); scrim.addEventListener("click", () => setOpen(false)); header.addEventListener("keydown", (e) => e.key === "Escape" && setOpen(false)); // ☰ 에 포커스가 있어도 닫힘 @@ -485,7 +494,7 @@ html:is([data-burger="on"], .is-bm-route) .app-burger { display: inline-flex; } .app-drawer__steps .ui-workflow-layout__steps.is-vertical .ui-workflow-layout__step { min-height: var(--spacing-40); margin-inline: 8px; width: auto; padding-left: 7px; } .app-drawer :is(.ui-btn__label, .ui-workflow-layout__step-label) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .app-drawer__steps .ui-workflow-layout__step--home { margin-bottom: 0; } -html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .app-drawer__steps .ui-workflow-layout__step, .app-drawer__row > *) { min-height: 44px; } +html[data-touch="on"] :is(.app-drawer__nav > *, .app-drawer__site > summary, .app-drawer__steps .ui-workflow-layout__steps.is-vertical .ui-workflow-layout__step, .app-drawer__row > *) { min-height: 44px; } .app-drawer__row { display: flex; flex-wrap: wrap; diff --git a/A00_Common/m_settings_nav.ts b/A00_Common/m_settings_nav.ts index 8c7cbeef3..2ce5bbdf3 100644 --- a/A00_Common/m_settings_nav.ts +++ b/A00_Common/m_settings_nav.ts @@ -1,7 +1,8 @@ /* ============================================================================= * m_settings_nav.ts * 시스템 설정 바로가기(334 · 요구 225-2 · 716-2) — 대시보드 설계 기본값 줄(`settingLinks` · 칸마다 한 줄 · - * `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`). + * `?open=`) / M01 · M02 ☰ 메뉴(`pageLinks` · 페이지 둘 · `mountSettingsDrawer`) / + * 진행 판 없는 쪽 ☰ 기본 목록(`defaultSettingsBar` · 772-1). * ========================================================================== */ import { ROUTES, type RoutePath } from "@config/config_frontend"; @@ -112,8 +113,24 @@ export function mountSettingsDrawer( activeKey: string, companyId: number | string | null, ): void { + setDrawerSteps(settingsBar(user, activeKey, companyId)); +} + +/** ☰ 기본 목록(772-1) — 진행 판 · 바로가기를 안 세운 로그인 뒤 쪽(B02 · D01 · E01 등) = 🏠 대시보드 + 권한별 페이지 둘 · + * 로그인 전 = 없음 */ +export async function defaultSettingsBar(): Promise { + const { fetchSessionUser } = await import("../A06_Login/A06_Login_Api_Fetch"); + const user = await fetchSessionUser().catch(() => null); + return user ? settingsBar(user, "", null) : null; +} + +function settingsBar( + user: SettingUser, + activeKey: string, + companyId: number | string | null, +): HTMLElement { const links = pageLinks(user, companyId); - const bar = createStepBar( + return createStepBar( links.map((link) => link.title), links.findIndex((link) => link.key === activeKey), { @@ -124,7 +141,6 @@ export function mountSettingsDrawer( onStepClick: (index) => void go(links[index]), }, ); - setDrawerSteps(bar); } async function go(link: SettingLink): Promise { diff --git a/resources/tester/helper_280_2_step_nav.cjs b/resources/tester/helper_280_2_step_nav.cjs index 50b4c210b..b2234b60f 100644 --- a/resources/tester/helper_280_2_step_nav.cjs +++ b/resources/tester/helper_280_2_step_nav.cjs @@ -156,7 +156,6 @@ const STUBS = { }, }, "../A00_Common/router": { navigateTo: () => undefined }, - "../A00_Common/app_shell": { setDrawerSteps: () => undefined }, "../B01_Dashboard/B01_Dashboard_Api_Fetch": { fetchDashboardMe: async () => ({ role }) }, }; globalThis.__DEV = false; diff --git a/resources/tester/helper_359_2_late_actions.cjs b/resources/tester/helper_359_2_late_actions.cjs index 2d9f78f8a..19291ae5c 100644 --- a/resources/tester/helper_359_2_late_actions.cjs +++ b/resources/tester/helper_359_2_late_actions.cjs @@ -107,6 +107,7 @@ const { loadTs } = makeLoader({ "/b_workflow_nav": { fetchWorkflowState: async () => ({}) }, "/b_page_state": { readByKey: () => null, writeByKey: () => undefined }, "/device_context": { SIZE_BREAKS: { expanded: 840 } }, + "/app_shell": { setDrawerSteps: () => undefined }, }, }); const overlay = loadTs(path.join(ROOT, "ui_template", "ui_template_overlay.ts")); diff --git a/resources/tester/test_772_1_drawer_steps.py b/resources/tester/test_772_1_drawer_steps.py new file mode 100644 index 000000000..83ea56741 --- /dev/null +++ b/resources/tester/test_772_1_drawer_steps.py @@ -0,0 +1,39 @@ +"""772-1 ☰ 서랍 진행 단계 · 바로가기 칸 — 진행 판을 세우는 공용 자리 한 곳에서 맡김 · 안 맡긴 쪽은 기본 목록.""" + +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def _text(rel: str) -> str: + return (ROOT / rel).read_text(encoding="utf-8") + + +def test_진행_판을_세우면_서랍에_맡김(): + overlay = _text("ui_template/ui_template_overlay.ts") + body = overlay.split("export function createWorkflowOverlays")[1] + assert ( + "setDrawerSteps(options.progressContent)" in body.split("if (options.progressContent)")[1] + ) + # 워크플로 판 · B03 은 진행 판(overlay)에만 맡김 — 따로 부르면 두 번 + assert "setDrawerSteps" not in _text("ui_template/ui_template_workflow_layout.ts") + assert "setDrawerSteps" not in _text("B03_FileInput/B03_FileInput_UI_Page.ts") + assert "createWorkflowOverlays" in _text("B03_FileInput/B03_FileInput_UI_Page.ts") + + +def test_안_맡긴_쪽은_기본_목록(): + shell = _text("A00_Common/app_shell.ts") + set_open = shell.split("const setOpen")[1].split("};")[0] + assert "if (open && !drawerSteps) void fillDefaultSteps();" in set_open + assert "steps.replaceChildren(drawerSteps.node)" in set_open + fill = shell.split("const fillDefaultSteps")[1].split("};")[0] + assert ( + "defaultSettingsBar()" in fill + and "if (!bar || drawerSteps || !steps.isConnected) return;" in fill + ) + nav = _text("A00_Common/m_settings_nav.ts") + default = nav.split("export async function defaultSettingsBar")[1].split("\n}")[0] + assert "fetchSessionUser" in default and 'settingsBar(user, "", null)' in default + # M01 · M02 와 같은 한 벌(pageLinks · createStepBar · 🏠) + bar = nav.split("function settingsBar")[1] + assert "pageLinks(user, companyId)" in bar and "homeButton: true" in bar diff --git a/ui_template/ui_template_overlay.ts b/ui_template/ui_template_overlay.ts index 5b43c7c57..f36e27344 100644 --- a/ui_template/ui_template_overlay.ts +++ b/ui_template/ui_template_overlay.ts @@ -22,6 +22,7 @@ import { fetchWorkflowState } from "../A00_Common/b_workflow_nav"; import { currentProjectId } from "../A00_Common/b_project_switch"; import { readByKey, writeByKey } from "../A00_Common/b_page_state"; import { SIZE_BREAKS } from "../A00_Common/device_context"; +import { setDrawerSteps } from "../A00_Common/app_shell"; const TITLE_OVERLAY_STATE_KEY = "frd_workflow_title_overlay_open"; const PROGRESS_OVERLAY_STATE_KEY = "frd_workflow_progress_overlay_open"; @@ -294,6 +295,8 @@ export function createWorkflowOverlays(options: WorkflowOverlayOptions): Workflo if (options.progressContent) { const progressBody = document.createElement("div"); progressBody.append(options.progressContent); + // ☰ 서랍 진행 단계 묶음(6-2 · 772-1) — 진행 판을 세우는 모든 화면(워크플로 판 · B03)이 같은 목록을 서랍에 빌려 줌 + setDrawerSteps(options.progressContent); const progressPanel = createPanel( "progress", t("Workflow_Progress_Title"), diff --git a/ui_template/ui_template_workflow_layout.ts b/ui_template/ui_template_workflow_layout.ts index 0f8832a30..77af0f114 100644 --- a/ui_template/ui_template_workflow_layout.ts +++ b/ui_template/ui_template_workflow_layout.ts @@ -14,7 +14,6 @@ import { t } from "./ui_template_locale"; import { createWorkflowOverlays } from "./ui_template_overlay"; import { showToast } from "./ui_template_elements"; import { navigateTo } from "../A00_Common/router"; -import { setDrawerSteps } from "../A00_Common/app_shell"; import { fetchDashboardMe } from "../B01_Dashboard/B01_Dashboard_Api_Fetch"; let dashboardRolePromise: Promise | undefined; @@ -243,8 +242,6 @@ export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLa }); root.append(body, overlays.root); - // ☰ 서랍 진행 단계 묶음(6-2) — 같은 단계 목록을 서랍이 열릴 때 빌려 감(그리기 · 누름 한 벌) - setDrawerSteps(progressContent); const steps = [ ...progressContent.querySelectorAll( ".ui-workflow-layout__step:not(.ui-workflow-layout__step--home)",