From f96907fb8a40ed3ed171784779a9b32677f13aff Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 3 Nov 2026 20:05:47 +0900 Subject: [PATCH] =?UTF-8?q?fix(772-1):=20=E2=98=B0=20=EC=84=9C=EB=9E=8D=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=EB=8B=A8=EA=B3=84=20=C2=B7=20=EB=B0=94?= =?UTF-8?q?=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EC=B9=B8=20=EB=B9=A0=EC=A7=90=20?= =?UTF-8?q?=E2=80=94=20=EC=A7=84=ED=96=89=20=ED=8C=90=20=EA=B3=B5=EC=9A=A9?= =?UTF-8?q?=20=EC=9E=90=EB=A6=AC=EC=97=90=EC=84=9C=20=EB=A7=A1=EA=B9=80=20?= =?UTF-8?q?=C2=B7=20=EC=95=88=20=EB=A7=A1=EA=B8=B4=20=EC=AA=BD=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EB=AA=A9=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 까닭: B03 파일입력 = 진행 판을 직접 조립(createWorkflowOverlays)해 서랍 맡김(setDrawerSteps)이 빠짐 · B02 · B10 · B11 · D01 · E01 = 일반 판이라 맡기는 쪽이 없음 · B01 = 10-26 새 틀(e505f4014)에서 옛 ☰ 구역 바로가기를 걷음 - 진행 판(createWorkflowOverlays)이 progressContent 를 받으면 서랍에 맡김 — 워크플로 판 · B03 한 자리 - ☰ 를 열 때 맡긴 목록이 없으면 기본 목록 = 🏠 대시보드 + 권한별 M01 · M02(mountSettingsDrawer 와 같은 한 벌) · 늦게 온 쪽 목록이 이김 - 터치 켬 서랍 진행 단계 줄 40 → 44(옆 규칙이 더 세어 덮던 것) - 시험: helper_280_2 의 app_shell 대역은 workflow_layout 이 더는 안 불러 지움 · helper_359_2 에 overlay 용 대역 더함 · test_772_1 새로 Co-Authored-By: Claude Opus 5.5 --- A00_Common/app_shell.ts | 15 +++++-- A00_Common/m_settings_nav.ts | 22 +++++++++-- resources/tester/helper_280_2_step_nav.cjs | 1 - .../tester/helper_359_2_late_actions.cjs | 1 + resources/tester/test_772_1_drawer_steps.py | 39 +++++++++++++++++++ ui_template/ui_template_overlay.ts | 3 ++ ui_template/ui_template_workflow_layout.ts | 3 -- 7 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 resources/tester/test_772_1_drawer_steps.py 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)",