feat(M02): 구조물 관리 화면 — 층 인자(개인 · 회사) · 층별 권한 · 대시보드 카드

- 주소 `?layer=personal|company`(없으면 시스템) — 라우터가 인자를 떼고 `currentQuery` 로 읽음 · 층 고정 풂
- 층 길 접두어 `/layers/{층}` — 구조물집계표 · 조합 · 복사 호출이 화면 층을 따름(시스템은 옛 길 그대로)
- 제목 셋(마스터 템플릿 · 구조물 관리(회사) · 구조물 관리(개인)) · 돌아가기 층 무관 · 도면 양식은 시스템 층만
- 편집 권한 — 시스템 SYSTEM_ADMIN · 회사 ADMIN 또는 is_master · 개인 본인 · 읽기만이면 단추 숨김 + [개인용으로 복사]
- 회사 없음 → 「회사부터 등록」 안내 · 대시보드 카드 「구조물 관리」(USER 개인용 · ADMIN · SYSTEM_ADMIN 회사용 + 개인용)
- 시험 test_m02_layer_screen.py 5 · test_m02_structure_panel.py 갱신

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Kyw9qnkJ94pC1A4PfpDtA
This commit is contained in:
2026-09-29 12:09:31 +09:00
co-authored by Claude Sonnet 5.5
parent b7449f857c
commit 154d693599
14 changed files with 391 additions and 48 deletions
@@ -32,6 +32,7 @@ import {
copyMaterial,
copyStructure,
fetchStructureNumbers,
getActiveLayer,
readTemplate,
type StructureDoc,
type StructureNumbers,
@@ -208,7 +209,9 @@ export async function pillsFor(
): Promise<{ links: LinkOptions; notice?: string }> {
if (kind === "material") return { links: { 항목: [], 모드: "자재", 자재찾기: searchMaterials } };
const none = (notice: string) => ({ links: { 항목: [], 모드: "입력" as const }, notice });
const table = await readTemplate("system", "table", STRUCTURE_TABLE, null).catch(() => null);
const table = await readTemplate(getActiveLayer(), "table", STRUCTURE_TABLE, null).catch(
() => null,
);
const cols = (table?.문서 as { 열?: { id: string; 일위대가?: string | null }[] } | null)?.열;
const combo = cols?.find((c) => c.id === column)?.일위대가;
if (!combo) return none(TEXT.noPrice);
@@ -376,7 +379,7 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
preview = mountDrawingTemplate(previewHost, doc.도면 as unknown as DrawingTemplateDoc, {
readOnly: true,
name: `preview:${column ?? ""}`,
layer: "system",
layer: getActiveLayer(),
panelsCollapsed: true, // 특성 · 도면층 패널은 접힌 채 — › 를 누르면 펼침
preview: true, // 왼쪽 끌기 = 화면 옮기기 · 두 번 누름 = 전체 맞춤
originMarker: true, // 좌표 0,0 기준점 십자
@@ -409,8 +412,8 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
c.host.replaceChildren();
}
const [drawing, ...books] = await Promise.allSettled([
readTemplate("system", "structure", col.id, null),
...sheets.map((c) => readTemplate("system", c.kind, col.id, null)),
readTemplate(getActiveLayer(), "structure", col.id, null),
...sheets.map((c) => readTemplate(getActiveLayer(), c.kind, col.id, null)),
]);
if (mine !== seq) return;
if (drawing.status === "fulfilled") doc = drawing.value.문서 as StructureDoc;