refactor(b08): 수량 프로토타입을 B08 폴더로 이관하고 앱 페이지에 싣는다

- B07_DesignDetail_Quantity_Proto.py → B08_Quantity/B08_Quantity_Proto.py (git mv)
- B07 라우터 include 2줄 원복, B08_Quantity_Router.py에 include
- B08_Quantity_UI_Page.ts: 준비 중 셸 → createWorkflowLayout + 보고서 iframe 본문
- 접근: 대시보드 워크플로 칩 또는 #/b08-quantity (수량산출 칩은 stage 가드 적용)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 22:08:59 +09:00
co-authored by Claude Opus 5
parent 76201631ee
commit 425d618b73
4 changed files with 54 additions and 15 deletions
@@ -50,11 +50,6 @@ from config.config_db import get_db_pool
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["B07 Design Detail"])
# 수량 산출 프로토타입 (처분 가능) — 파일 삭제 시 아래 2줄도 함께 지운다.
from B07_DesignDetail.B07_DesignDetail_Quantity_Proto import proto_router # noqa: E402
router.include_router(proto_router)
_CROSS_ID = re.compile(r"^cross_(\d+)m$")
_LONG_ID = re.compile(r"^longitudinal(?:_(\d+))?$")
@@ -1,6 +1,6 @@
"""B07 수량 산출 프로토타입 (처분 가능 — disposable).
"""B08 수량 산출 프로토타입 (처분 가능 — disposable).
사용자 평가용 시안이다. 파일 + `B07_DesignDetail_Router.py` include 2줄이 전부라
사용자 평가용 시안이다. 파일 + `B08_Quantity_Router.py` include 2줄이 전부라
마음에 들면 그대로 지우면 된다. 정본을 읽기만 하고 어디에도 쓰지 않는다.
페이지: GET /api/projects/quantity-proto/page 프로젝트 목록
+5
View File
@@ -17,6 +17,11 @@ from config.config_db import get_db_pool
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["B08 Quantity"])
# 수량 산출 프로토타입 (처분 가능) — B08_Quantity_Proto.py 삭제 시 아래 2줄도 함께 지운다.
from B08_Quantity.B08_Quantity_Proto import proto_router # noqa: E402
router.include_router(proto_router)
@router.post("/{project_id}/quantity/confirm")
async def confirm_quantity(project_id: UUID) -> JSONResponse:
+47 -8
View File
@@ -2,23 +2,29 @@
* B08_Quantity_UI_Page.ts
* 로그인 후 08: 5차 워크플로우 (수량 산출)
*
* ⚠️ 본문 준비 중 — 워크플로우 셸 + 좌측 [확정] 버튼만 구성.
* ⚠️ 프로토타입 단계 — 본문은 서버 렌더 수량 보고서(B08_Quantity_Proto.py)를
* iframe으로 싣는다. 평가 후 폐기/본구현 판단 (처분 가능 구성).
* 확정 = 워크플로 stage 5(QUANTITY) 완료 처리 후 B09 설계도서로 이동.
* 수량 본문(B06 종횡단 기반 산출)은 후속 계획에서 구현한다.
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton, showToast } from "@ui/ui_template_elements";
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
import { API_BASE_URL, CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
import { renderPendingWorkflow, workflowSteps } from "../A00_Common/b_page_scaffold";
import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav";
import { workflowSteps } from "../A00_Common/b_page_scaffold";
import {
fetchWorkflowState,
goToWorkflowStage,
WORKFLOW_STEP_ROUTES,
type WorkflowState,
} from "../A00_Common/b_workflow_nav";
/** locale 헬퍼 */
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
/** stage 5(QUANTITY) 완료 요청 — 본문 미구현 상태의 유일한 백엔드 연동. */
/** stage 5(QUANTITY) 완료 요청. */
async function confirmQuantityStage(projectId: string): Promise<void> {
const response = await fetch(
`${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/confirm`,
@@ -29,14 +35,15 @@ async function confirmQuantityStage(projectId: string): Promise<void> {
}
}
/** 좌측 패널: 준비 중 안내 + 하단 [확정] 액션 행 (다른 워크플로우 페이지와 동일 배치). */
/** 좌측 패널: 프로토 안내 + 하단 [확정] 액션 행 (다른 워크플로우 페이지와 동일 배치). */
function buildQuantitySidePanel(projectId: string | null): HTMLElement {
const panel = document.createElement("div");
panel.className = "b08-quantity__panel";
const note = document.createElement("p");
note.className = "b08-quantity__pending-note";
note.textContent = L("B08_Quantity_Side_Pending");
note.textContent =
"수량 산출 프로토타입 — 정본 일람과 원단위(참조 전용) 산출근거를 표시합니다.";
panel.append(note);
const confirmButton = createButton({
@@ -67,15 +74,47 @@ function buildQuantitySidePanel(projectId: string | null): HTMLElement {
return panel;
}
/** 본문: 서버 렌더 수량 보고서 iframe (프로젝트 미선택 시 목록 페이지). */
function buildQuantityMain(projectId: string | null): HTMLElement {
const wrap = document.createElement("div");
wrap.style.cssText = "height:100%;min-height:480px;display:flex;";
const frame = document.createElement("iframe");
frame.title = "수량 산출 프로토타입";
frame.style.cssText = "flex:1;width:100%;border:0;background:#fff;";
frame.src = projectId
? `${API_BASE_URL}/projects/quantity-proto/page/${encodeURIComponent(projectId)}`
: `${API_BASE_URL}/projects/quantity-proto/page`;
wrap.append(frame);
return wrap;
}
/* -----------------------------------------------------------------------------
* 페이지 진입점
* -------------------------------------------------------------------------- */
export async function renderB08Quantity(root: HTMLElement): Promise<void> {
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
await renderPendingWorkflow(root, {
let workflowState: WorkflowState | undefined;
if (projectId) {
try {
workflowState = await fetchWorkflowState(projectId);
} catch {
/* 조회 실패 시 stages 미전달 → 전체 이동 허용 */
}
}
const layout = createWorkflowLayout({
title: L("B08_Quantity_Title"),
steps: workflowSteps(),
activeStep: 5,
leftPanel: buildQuantitySidePanel(projectId),
mainContent: buildQuantityMain(projectId),
stages: workflowState?.stages,
currentStage: workflowState?.current_stage,
routes: WORKFLOW_STEP_ROUTES,
onStepClick: (stepIndex) => {
if (projectId)
goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]);
},
});
layout.root.classList.add("b-scaffold-wf");
root.append(layout.root);
}