diff --git a/B01_Dashboard/B01_Dashboard_UI_ProjectDetail.ts b/B01_Dashboard/B01_Dashboard_UI_ProjectDetail.ts index 0ec697d4e..f2c0da38b 100644 --- a/B01_Dashboard/B01_Dashboard_UI_ProjectDetail.ts +++ b/B01_Dashboard/B01_Dashboard_UI_ProjectDetail.ts @@ -27,7 +27,7 @@ import { stageFlow, stageProgress, } from "./B01_Dashboard_UI_ProjectList_Parts"; -import { rowMenuItems } from "./B01_Dashboard_UI_ProjectList_Views"; +import { openMenuAt, rowMenuItems } from "./B01_Dashboard_UI_ProjectList_Views"; /** 인쇄 · 내려받기 화면(B09 초안 길) — 도면 PDF · 수량 · 내역 xlsx 를 받는 곳 */ const PRINT_ROUTE = "b09-print" as RoutePath; @@ -214,9 +214,7 @@ function head(detail: ProjectDetail, ctx: ProjectViewCtx): HTMLElement { more.hidden = !items.length; more.addEventListener("click", (event) => { event.stopPropagation(); - const box = bar.getBoundingClientRect(); - const at = more.getBoundingClientRect(); - menu.open(at.right - box.left - 120, at.bottom - box.top, items); + openMenuAt(menu, bar, more, items); }); const work = createButton({ label: names.length ? `작업 열기 — ${names[current] ?? names[0]} ›` : "작업 열기 ›", diff --git a/B01_Dashboard/B01_Dashboard_UI_ProjectList_Views.ts b/B01_Dashboard/B01_Dashboard_UI_ProjectList_Views.ts index 570a02ce0..70d3fde77 100644 --- a/B01_Dashboard/B01_Dashboard_UI_ProjectList_Views.ts +++ b/B01_Dashboard/B01_Dashboard_UI_ProjectList_Views.ts @@ -3,7 +3,11 @@ * 프로젝트 목록 보기 셋(PLAN 548-1) — 카드 · 표(줄 끝 ⋯) · 칸반(칸 = 서버 `board` · 완료 칸 접힘). * ========================================================================== */ -import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu"; +import { + createMapContextMenu, + type MapContextMenu, + type MapContextMenuItem, +} from "@ui/ui_template_context_menu"; import { el } from "@ui/ui_template_elements_base"; import type { DashboardUser } from "./B01_Dashboard_Api_Fetch"; import type { OverviewProject } from "./B01_Dashboard_Api_Projects"; @@ -82,6 +86,19 @@ export function cardGrid(projects: OverviewProject[], onOpen: Open): HTMLElement }); } +/** ⋯ 메뉴 — 오른쪽 끝을 단추 오른쪽에 맞춤(host = position 기준 · 메뉴 element 를 품은 것) */ +export function openMenuAt( + menu: MapContextMenu, + host: HTMLElement, + button: HTMLElement, + items: MapContextMenuItem[], +): void { + const box = host.getBoundingClientRect(); + const at = button.getBoundingClientRect(); + menu.open(at.right - box.left, at.bottom - box.top, items); + menu.element.style.left = `${at.right - box.left - menu.element.offsetWidth}px`; +} + /** 줄 끝 ⋯ — 지금 있는 동작만(수정 · 삭제) */ export function rowMenuItems(project: OverviewProject, me: DashboardUser): MapContextMenuItem[] { const items: MapContextMenuItem[] = []; @@ -116,9 +133,7 @@ export function projectRows( if (!items.length) more.hidden = true; more.addEventListener("click", (event) => { event.stopPropagation(); - const box = wrap.getBoundingClientRect(); - const at = more.getBoundingClientRect(); - menu.open(at.right - box.left - 120, at.bottom - box.top, items); + openMenuAt(menu, wrap, more, items); }); const row = el("tr", { className: "b01-proj-row", diff --git a/B01_Dashboard/B01_Dashboard_UI_Views.ts b/B01_Dashboard/B01_Dashboard_UI_Views.ts index 3297a73a9..21cf145ea 100644 --- a/B01_Dashboard/B01_Dashboard_UI_Views.ts +++ b/B01_Dashboard/B01_Dashboard_UI_Views.ts @@ -12,6 +12,8 @@ import type { IconName } from "@ui/ui_template_icons"; import type { DashboardUser } from "./B01_Dashboard_Api_Fetch"; import { mountLegacy, type LegacyKey } from "./B01_Dashboard_UI_Legacy"; import { mountChat, mountGuide } from "./B01_Dashboard_UI_Nav"; +import { mountProjectDetail } from "./B01_Dashboard_UI_ProjectDetail"; +import { mountProjectList } from "./B01_Dashboard_UI_ProjectList"; /** 역할 넷 — 산림청 = 세션 is_forest_service 인 일반 사용자(회사 관리자 · 시스템 관리자가 앞섬) */ export type ViewRole = "USER" | "ADMIN" | "FOREST" | "SYSTEM_ADMIN"; @@ -47,8 +49,8 @@ const legacy = export const VIEWS: View[] = [ // 작업 { key: "home", title: "홈", group: "work", roles: ALL, icon: "home", mount: legacy("projects") }, - { key: "projects", title: "프로젝트", group: "work", roles: ALL, icon: "folder", mount: legacy("projects") }, - { key: "project", title: "프로젝트 상세", group: "hidden", roles: ALL, mount: legacy("projects") }, + { key: "projects", title: "프로젝트", group: "work", roles: ALL, icon: "folder", mount: mountProjectList }, + { key: "project", title: "프로젝트 상세", group: "hidden", roles: ALL, mount: mountProjectDetail }, { key: "assigned", title: "받은 예정노선", group: "work", roles: ["USER", "ADMIN", "SYSTEM_ADMIN"], icon: "route", mount: legacy("assigned") }, { key: "temp", title: "임시 보관함", group: "work", roles: ALL, icon: "archive", mount: legacy("temp") }, // 회사 관리 diff --git a/resources/tester/test_546_b01_shell.py b/resources/tester/test_546_b01_shell.py index c59c1f5dd..ee5de7c90 100644 --- a/resources/tester/test_546_b01_shell.py +++ b/resources/tester/test_546_b01_shell.py @@ -24,13 +24,16 @@ def _text(path: Path) -> str: def _run_views(body: str) -> object: - src = re.sub(r"^import[\s\S]*?;\n", "", _text(B01 / "B01_Dashboard_UI_Views.ts"), flags=re.M) - script = ( - "const mountLegacy = () => {}; const mountGuide = () => {}; const mountChat = () => {};\n" - + src - + "\n" - + body - ) + text = _text(B01 / "B01_Dashboard_UI_Views.ts") + src = re.sub(r"^import[\s\S]*?;\n", "", text, flags=re.M) + # 가져온 값(mount 함수 등)은 빈 함수로 — 페이지가 늘어도 시험은 그대로 + names = { + name.strip() + for block in re.findall(r"^import \{([^}]*)\} from", text, flags=re.M) + for name in block.split(",") + if name.strip() and not name.strip().startswith("type ") + } + script = "".join(f"const {name} = () => {{}};\n" for name in sorted(names)) + src + "\n" + body result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", script],