feat(B07,B08): 워크플로 순서 교환 — 상세설계를 수량산출 앞으로
횡단설계(B06) 다음을 상세설계 → 수량산출 → 설계도서 순으로 재배열하고, 폴더 번호가 흐름과 일치하도록 이름을 맞바꾼다. - B08_DesignDetail → B07_DesignDetail, B07_Quantity → B08_Quantity (파일 접두어·식별자·라우트·locale 키 전량 스왑) - STAGE_KEYS 4=DESIGN_DETAIL, 5=QUANTITY 스왑 + 라우터 stage 리터럴 교체 - CAD 마운트 /b08-cad → /b07-cad (main.py·vite proxy·iframe URL), openwebcad Toolbar 라벨 B07로 수정 후 재빌드 - 유지: openwebcad postMessage 프로토콜 aislo:b08:*·패키지명(내부 식별자) - 기존 프로젝트 storage 폴더 rename + project_manifest 갱신, DB project_workflow_stages stage_no 4↔5 행 스왑 완료 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@@ -82,7 +82,7 @@ export interface PendingWorkflowOptions {
|
||||
steps: string[];
|
||||
/** 현재 활성 단계 인덱스 */
|
||||
activeStep: number;
|
||||
/** 본문은 준비 중이지만 좌측 패널만 먼저 붙일 때 (B07 수량 확정 버튼 등) */
|
||||
/** 본문은 준비 중이지만 좌측 패널만 먼저 붙일 때 (B08 수량 확정 버튼 등) */
|
||||
leftPanel?: HTMLElement;
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@ export function workflowSteps(): string[] {
|
||||
L("WF_Step_Surface"),
|
||||
L("WF_Step_Route"),
|
||||
L("WF_Step_ProfileCross"),
|
||||
L("WF_Step_Quantity"),
|
||||
L("WF_Step_DesignDetail"),
|
||||
L("WF_Step_Quantity"),
|
||||
L("WF_Step_Estimation"),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ export const WORKFLOW_STEP_ROUTES: readonly RoutePath[] = [
|
||||
ROUTES.B04_PREPROCESS,
|
||||
ROUTES.B05_PROFILE,
|
||||
ROUTES.B06_SECTION,
|
||||
ROUTES.B07_QUANTITY,
|
||||
ROUTES.B08_DESIGN_DETAIL,
|
||||
ROUTES.B07_DESIGN_DETAIL,
|
||||
ROUTES.B08_QUANTITY,
|
||||
ROUTES.B09_ESTIMATION,
|
||||
];
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ const FOOTERLESS_ROUTES: readonly RoutePath[] = [
|
||||
ROUTES.B04_PREPROCESS,
|
||||
ROUTES.B05_PROFILE,
|
||||
ROUTES.B06_SECTION,
|
||||
ROUTES.B07_QUANTITY,
|
||||
ROUTES.B08_DESIGN_DETAIL,
|
||||
ROUTES.B07_DESIGN_DETAIL,
|
||||
ROUTES.B08_QUANTITY,
|
||||
ROUTES.B09_ESTIMATION,
|
||||
ROUTES.B10_PAYMENT,
|
||||
ROUTES.B11_STATUS,
|
||||
|
||||
@@ -45,12 +45,12 @@ const routeTable: Partial<Record<RoutePath, () => Promise<PageRenderer>>> = {
|
||||
(await import("../B05_Profile/B05_Profile_UI_Page")).renderB05Route,
|
||||
[ROUTES.B06_SECTION]: async () =>
|
||||
(await import("../B06_Section/B06_Section_UI_Page")).renderB06ProfileCross,
|
||||
// B07_QUANTITY: 수량 본문은 재작성 예정(구 구현은 B07_Quantity/0_old_260726_codex.zip 백업).
|
||||
// 지금은 셸 페이지 — 좌측 [확정]으로 stage 4를 닫고 B08 상세설계로 넘어간다.
|
||||
[ROUTES.B07_QUANTITY]: async () =>
|
||||
(await import("../B07_Quantity/B07_Quantity_UI_Page")).renderB07Quantity,
|
||||
[ROUTES.B08_DESIGN_DETAIL]: async () =>
|
||||
(await import("../B08_DesignDetail/B08_DesignDetail_UI_Page")).renderB08DesignDetail,
|
||||
[ROUTES.B07_DESIGN_DETAIL]: async () =>
|
||||
(await import("../B07_DesignDetail/B07_DesignDetail_UI_Page")).renderB07DesignDetail,
|
||||
// B08_QUANTITY: 수량 본문은 재작성 예정(구 구현은 B08_Quantity/0_old_260726_codex.zip 백업).
|
||||
// 지금은 셸 페이지 — 좌측 [확정]으로 stage 5를 닫고 B09 설계도서로 넘어간다.
|
||||
[ROUTES.B08_QUANTITY]: async () =>
|
||||
(await import("../B08_Quantity/B08_Quantity_UI_Page")).renderB08Quantity,
|
||||
[ROUTES.B09_ESTIMATION]: async () =>
|
||||
(await import("../B09_Estimation/B09_Estimation_UI_Page")).renderB09Estimation,
|
||||
[ROUTES.B10_PAYMENT]: async () =>
|
||||
@@ -111,8 +111,8 @@ export async function renderCurrentRoute(outlet: HTMLElement): Promise<void> {
|
||||
ROUTES.B04_PREPROCESS,
|
||||
ROUTES.B05_PROFILE,
|
||||
ROUTES.B06_SECTION,
|
||||
ROUTES.B07_QUANTITY,
|
||||
ROUTES.B08_DESIGN_DETAIL,
|
||||
ROUTES.B07_DESIGN_DETAIL,
|
||||
ROUTES.B08_QUANTITY,
|
||||
ROUTES.B09_ESTIMATION,
|
||||
ROUTES.B10_PAYMENT,
|
||||
ROUTES.B11_STATUS,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
지반유형(토사/리핑암/발파암)과 단면유형(좌절/우절/양절/양성)에 따라 표준횡단
|
||||
설계선을 구성하고, 지반선과의 차이로 절·성토 단면적을 산출한다. B06에서 사용자가
|
||||
버튼을 누를 때 즉시 호출되며, 여기서 나온 값은 잠정치로 저장되고 B08 상세설계에서
|
||||
버튼을 누를 때 즉시 호출되며, 여기서 나온 값은 잠정치로 저장되고 B07 상세설계에서
|
||||
확정치로 대체된다.
|
||||
|
||||
표준단면 기하는 config STANDARD_CROSS_SECTION(단일 진실 원천)을 읽고, B06 설정
|
||||
|
||||
@@ -390,7 +390,7 @@ async def merge_cross_section_design_by_round(
|
||||
) -> bool:
|
||||
"""정수 chainage(m) 측점의 data.design에 patch를 병합 저장한다.
|
||||
|
||||
B08 도면 확정(전체 설계 재계산 결과) 및 확정 해제(status 되돌림)에서 사용한다.
|
||||
B07 도면 확정(전체 설계 재계산 결과) 및 확정 해제(status 되돌림)에서 사용한다.
|
||||
도면 ID가 정수 m라 ROUND로 매칭한다. 갱신 여부를 반환한다.
|
||||
"""
|
||||
async with connection.cursor() as cursor:
|
||||
|
||||
@@ -604,7 +604,7 @@ async def compute_cross_section_design(
|
||||
two_stage_slope=request.two_stage_slope,
|
||||
ditch_enabled=request.ditch_enabled,
|
||||
)
|
||||
# B06 지정 시점은 잠정치. B08 도면 확정 시 동일 엔진으로 재계산해 confirmed로 승격한다.
|
||||
# B06 지정 시점은 잠정치. B07 도면 확정 시 동일 엔진으로 재계산해 confirmed로 승격한다.
|
||||
design["status"] = "provisional"
|
||||
# 법정 근거 문구 표기용 — 사용자가 포장을 바꿔도 제안 여부는 그대로 남긴다.
|
||||
design["pavement_suggested"] = pavement_suggested
|
||||
|
||||
@@ -94,7 +94,7 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
|
||||
const DISPLAY_HALF_WIDTH_DEFAULT_M = 12;
|
||||
|
||||
// 하단 액션 [종단 이동][임시저장][확정] — 종단 이동은 저장 없이 B05로 페이지 이동만,
|
||||
// 확정은 임시저장과 같은 내용 저장 후 stage 2·3을 닫고 B07 수량으로 넘어간다
|
||||
// 확정은 임시저장과 같은 내용 저장 후 stage 2·3을 닫고 B07 상세설계로 넘어간다
|
||||
// (2026-08-08 워크플로우 재정의).
|
||||
const goProfileButton = createButton({
|
||||
label: L("B06_Profile_Btn_GoProfile"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* B08 상세 설계 도면 목록·단건 API 클라이언트. */
|
||||
/* B07 상세 설계 도면 목록·단건 API 클라이언트. */
|
||||
|
||||
import { API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""B08 CAD 도면 조립 엔진 — B06/B05 산출물을 openwebcad 엔티티로 직렬화한다.
|
||||
"""B07 CAD 도면 조립 엔진 — B06/B05 산출물을 openwebcad 엔티티로 직렬화한다.
|
||||
|
||||
납품 도면 양식 복제:
|
||||
- 종단도: 30측점 N분할 + 하단 측점 테이블(곡선/측점/거리/추가거리/지반고/
|
||||
@@ -1,4 +1,4 @@
|
||||
"""B08 종단도 CAD 조립 — 30측점 N분할 + 그래프 축·격자 + 하단 측점 테이블.
|
||||
"""B07 종단도 CAD 조립 — 30측점 N분할 + 그래프 축·격자 + 하단 측점 테이블.
|
||||
|
||||
납품 양식:
|
||||
- 그래프 영역: 좌측 Y축(표고 눈금·라벨), 기준선(X축, 최저 표고에서 5m 이상
|
||||
@@ -15,7 +15,7 @@ import math
|
||||
from typing import Any
|
||||
from uuid import uuid5
|
||||
|
||||
from B08_DesignDetail.B08_DesignDetail_Engine_Cad import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
||||
_ENTITY_NS,
|
||||
DESIGN_COLOR,
|
||||
DESIGN_LAYER_ID,
|
||||
@@ -41,7 +41,7 @@ from B08_DesignDetail.B08_DesignDetail_Engine_Cad import (
|
||||
polyline_entity,
|
||||
station_no_label,
|
||||
)
|
||||
from B08_DesignDetail.B08_DesignDetail_Engine_Template import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
|
||||
entities_bbox,
|
||||
frame_entities,
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
"""B08 도각 템플릿 병합 — openwebcad JSON 템플릿을 도면 콘텐츠 둘레에 배치한다.
|
||||
"""B07 도각 템플릿 병합 — openwebcad JSON 템플릿을 도면 콘텐츠 둘레에 배치한다.
|
||||
|
||||
resources/template_2dDrawing/의 사전 변환 템플릿(A1 도각 등)을 로드해,
|
||||
도면 콘텐츠 bbox에 맞춰 균등 스케일·이동시킨 뒤 잠금 프레임 레이어
|
||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from uuid import uuid5
|
||||
|
||||
from B08_DesignDetail.B08_DesignDetail_Engine_Cad import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
||||
_ENTITY_NS,
|
||||
FRAME_LAYER_ID,
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
"""B06 확정 종·횡단 산출물을 B08 CAD 도면으로 변환하는 라우터."""
|
||||
"""B06 확정 종·횡단 산출물을 B07 CAD 도면으로 변환하는 라우터."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
@@ -20,7 +20,7 @@ from B06_Section.B06_Section_Repository import (
|
||||
get_longitudinal_section,
|
||||
merge_cross_section_design_by_round,
|
||||
)
|
||||
from B08_DesignDetail.B08_DesignDetail_Engine_Cad import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
||||
DRAWING_FORMAT,
|
||||
QUANTITY_VALUE_KEYS,
|
||||
build_cross_drawing,
|
||||
@@ -28,11 +28,11 @@ from B08_DesignDetail.B08_DesignDetail_Engine_Cad import (
|
||||
infer_station_interval,
|
||||
station_no_label,
|
||||
)
|
||||
from B08_DesignDetail.B08_DesignDetail_Engine_Cad_Long import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Long import (
|
||||
build_longitudinal_drawing,
|
||||
longitudinal_chunks,
|
||||
)
|
||||
from B08_DesignDetail.B08_DesignDetail_Schema import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Schema import (
|
||||
DesignDrawingConfirmRequest,
|
||||
DesignDrawingConfirmResponse,
|
||||
DesignDrawingInvalidateResponse,
|
||||
@@ -46,11 +46,11 @@ from common_util.common_util_workflow_state import complete_stage, start_stage
|
||||
from config.config_db import get_db_pool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/api/projects", tags=["B08 Design Detail"])
|
||||
router = APIRouter(prefix="/api/projects", tags=["B07 Design Detail"])
|
||||
|
||||
_CROSS_ID = re.compile(r"^cross_(\d+)m$")
|
||||
_LONG_ID = re.compile(r"^longitudinal(?:_(\d+))?$")
|
||||
_STAGE_DIR = "B08_DesignDetail"
|
||||
_STAGE_DIR = "B07_DesignDetail"
|
||||
|
||||
|
||||
async def _confirmed_source(project_id: UUID) -> tuple[int, Path, Path]:
|
||||
@@ -363,7 +363,7 @@ def _invalidate_drawing(project_root: Path, drawing_id: str) -> None:
|
||||
async def get_design_drawing_list(
|
||||
project_id: UUID,
|
||||
) -> DesignDrawingListResponse | JSONResponse:
|
||||
"""B08 좌측 패널용 도면 메타데이터만 캐시한다."""
|
||||
"""B07 좌측 패널용 도면 메타데이터만 캐시한다."""
|
||||
try:
|
||||
route_id, project_root, longitudinal_path = await _confirmed_source(project_id)
|
||||
drawings = await asyncio.to_thread(_drawing_list, project_root, longitudinal_path)
|
||||
@@ -375,7 +375,7 @@ async def get_design_drawing_list(
|
||||
except PermissionError as exc:
|
||||
return JSONResponse(status_code=409, content={"status": "error", "message": str(exc)})
|
||||
except Exception:
|
||||
logger.exception("B08 도면 목록 조회 실패: project_id=%s", project_id)
|
||||
logger.exception("B07 도면 목록 조회 실패: project_id=%s", project_id)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "상세 설계 도면 목록을 읽지 못했습니다."},
|
||||
@@ -420,7 +420,7 @@ async def get_design_drawing(
|
||||
return JSONResponse(status_code=409, content={"status": "error", "message": str(exc)})
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"B08 단건 도면 조회 실패: project_id=%s drawing_id=%s", project_id, drawing_id
|
||||
"B07 단건 도면 조회 실패: project_id=%s drawing_id=%s", project_id, drawing_id
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
@@ -433,7 +433,7 @@ def _recompute_confirmed_design(
|
||||
) -> dict[str, Any]:
|
||||
"""B06 지정값과 현재 계획고로 절·성토 단면적을 재계산해 확정치(status=confirmed)로 만든다.
|
||||
|
||||
B08 CAD에는 아직 편집 가능한 설계선이 없으므로, 저장된 지정값(지반유형·단면유형·
|
||||
B07 CAD에는 아직 편집 가능한 설계선이 없으므로, 저장된 지정값(지반유형·단면유형·
|
||||
측구위치)과 계획고로 동일 엔진을 재실행해 확정 시점 값을 고정한다.
|
||||
"""
|
||||
longitudinal = _read_json(longitudinal_path)
|
||||
@@ -505,7 +505,7 @@ async def confirm_design_drawing(
|
||||
)
|
||||
except (ValueError, KeyError, FileNotFoundError, OSError):
|
||||
logger.warning(
|
||||
"B08 확정 단면적 재계산 실패 (도면 확정은 유지): drawing_id=%s",
|
||||
"B07 확정 단면적 재계산 실패 (도면 확정은 유지): drawing_id=%s",
|
||||
drawing_id,
|
||||
exc_info=True,
|
||||
)
|
||||
@@ -522,9 +522,9 @@ async def confirm_design_drawing(
|
||||
)
|
||||
async with connection.cursor() as cursor:
|
||||
if all_confirmed:
|
||||
await complete_stage(cursor, str(project_id), 5)
|
||||
await complete_stage(cursor, str(project_id), 4)
|
||||
else:
|
||||
await start_stage(cursor, str(project_id), 5)
|
||||
await start_stage(cursor, str(project_id), 4)
|
||||
await connection.commit()
|
||||
except Exception:
|
||||
await connection.rollback()
|
||||
@@ -543,7 +543,7 @@ async def confirm_design_drawing(
|
||||
except PermissionError as exc:
|
||||
return JSONResponse(status_code=409, content={"status": "error", "message": str(exc)})
|
||||
except Exception:
|
||||
logger.exception("B08 도면 확정 실패: project_id=%s drawing_id=%s", project_id, drawing_id)
|
||||
logger.exception("B07 도면 확정 실패: project_id=%s drawing_id=%s", project_id, drawing_id)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"status": "error", "message": "상세 설계 도면을 확정하지 못했습니다."},
|
||||
@@ -557,7 +557,7 @@ async def confirm_design_drawing(
|
||||
async def invalidate_design_drawing(
|
||||
project_id: UUID, drawing_id: str
|
||||
) -> DesignDrawingInvalidateResponse | JSONResponse:
|
||||
"""확정 도면 편집 시 B08 및 이후 단계를 미확정 상태로 되돌린다."""
|
||||
"""확정 도면 편집 시 B07 및 이후 단계를 미확정 상태로 되돌린다."""
|
||||
try:
|
||||
route_id, project_root, longitudinal_path = await _confirmed_source(project_id)
|
||||
items = await asyncio.to_thread(_drawing_list, project_root, longitudinal_path)
|
||||
@@ -579,7 +579,7 @@ async def invalidate_design_drawing(
|
||||
patch={"status": "provisional"},
|
||||
)
|
||||
async with connection.cursor() as cursor:
|
||||
await start_stage(cursor, str(project_id), 5)
|
||||
await start_stage(cursor, str(project_id), 4)
|
||||
await connection.commit()
|
||||
except Exception:
|
||||
await connection.rollback()
|
||||
@@ -594,7 +594,7 @@ async def invalidate_design_drawing(
|
||||
return JSONResponse(status_code=409, content={"status": "error", "message": str(exc)})
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"B08 도면 확정 해제 실패: project_id=%s drawing_id=%s", project_id, drawing_id
|
||||
"B07 도면 확정 해제 실패: project_id=%s drawing_id=%s", project_id, drawing_id
|
||||
)
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
@@ -1,4 +1,4 @@
|
||||
"""B08 상세 설계 도면 목록·단건 응답 모델."""
|
||||
"""B07 상세 설계 도면 목록·단건 응답 모델."""
|
||||
|
||||
from typing import Any, Literal
|
||||
|
||||
@@ -16,7 +16,7 @@ class DesignDrawingItem(BaseModel):
|
||||
|
||||
|
||||
class DesignDrawingListResponse(BaseModel):
|
||||
"""B08 진입 시 캐시할 경량 도면 목록."""
|
||||
"""B07 진입 시 캐시할 경량 도면 목록."""
|
||||
|
||||
status: str = "success"
|
||||
project_id: str
|
||||
@@ -50,7 +50,7 @@ class DesignDrawingConfirmRequest(BaseModel):
|
||||
|
||||
|
||||
class DesignDrawingConfirmResponse(BaseModel):
|
||||
"""도면별 확정 및 B08 전체 완료 상태."""
|
||||
"""도면별 확정 및 B07 전체 완료 상태."""
|
||||
|
||||
status: str = "success"
|
||||
project_id: str
|
||||
@@ -1,16 +1,16 @@
|
||||
/* =============================================================================
|
||||
* B08_DesignDetail_UI_Page.ts
|
||||
* B07_DesignDetail_UI_Page.ts
|
||||
* 로그인 후 07: 4차 워크플로우 (상세 설계) — 독립형 2D CAD 임베드
|
||||
*
|
||||
* B08_DesignDetail/openwebcad를 프로젝트 소유 B08 CAD 앱으로 빌드하여
|
||||
* /b08-cad 경로로 서빙한다. 업무 도면은 추후 same-origin postMessage로
|
||||
* B07_DesignDetail/openwebcad를 프로젝트 소유 B07 CAD 앱으로 빌드하여
|
||||
* /b07-cad 경로로 서빙한다. 업무 도면은 추후 same-origin postMessage로
|
||||
* JSON만 전달하며 DXF/DWG 파싱은 이 브라우저 앱에서 수행하지 않는다.
|
||||
*
|
||||
* 레이아웃 (사용자 지시): 사이드 패널 빈 상태 유지 + 상세 영역 CAD 화면.
|
||||
* 제약 준수 (frontend.md §2 3단 레이아웃): createWorkflowLayout 재사용.
|
||||
* ========================================================================== */
|
||||
|
||||
import "./B08_DesignDetail_UI_Style.css";
|
||||
import "./B07_DesignDetail_UI_Style.css";
|
||||
import { attachCollapsible } from "@ui/ui_template_collapsible";
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import {
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
type DesignDrawingItem,
|
||||
type DesignDrawingResponse,
|
||||
type QuantityTable,
|
||||
} from "./B08_DesignDetail_Api_Fetch";
|
||||
} from "./B07_DesignDetail_Api_Fetch";
|
||||
|
||||
/** CAD 앱 수량 패널로 넘기는 설계 컨텍스트 (openwebcad DesignMeta와 동일 형식). */
|
||||
interface DesignMeta {
|
||||
@@ -57,8 +57,8 @@ interface SaveResult {
|
||||
quantityTable: QuantityTable | null;
|
||||
}
|
||||
|
||||
/** B08 독립형 CAD 정적 경로 (main.py 마운트, dev는 vite proxy 위임) */
|
||||
const B08_CAD_APP_URL = "/b08-cad/index.html";
|
||||
/** B07 독립형 CAD 정적 경로 (main.py 마운트, dev는 vite proxy 위임) */
|
||||
const B07_CAD_APP_URL = "/b07-cad/index.html";
|
||||
|
||||
/** locale 헬퍼 */
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
@@ -112,9 +112,9 @@ function buildDrawingSidePanel(
|
||||
errorMessage?: string,
|
||||
): HTMLDivElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "b08-drawing-list";
|
||||
panel.className = "b07-drawing-list";
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "b08-drawing-list__heading";
|
||||
heading.className = "b07-drawing-list__heading";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = "설계 도면";
|
||||
const count = document.createElement("span");
|
||||
@@ -124,7 +124,7 @@ function buildDrawingSidePanel(
|
||||
|
||||
if (errorMessage || drawings.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b08-drawing-list__empty";
|
||||
empty.className = "b07-drawing-list__empty";
|
||||
empty.textContent = errorMessage ?? "확정된 종·횡단 도면이 없습니다.";
|
||||
panel.append(empty);
|
||||
return panel;
|
||||
@@ -138,7 +138,7 @@ function buildDrawingSidePanel(
|
||||
if (!items.length) continue;
|
||||
const section = document.createElement("section");
|
||||
// ui-sidebar-section: 사이드 컨테이너 공통 외곽선(B04~B06과 통일, 2026-08-06 사용자 지시).
|
||||
section.className = "b08-drawing-group ui-collapsible ui-sidebar-section";
|
||||
section.className = "b07-drawing-group ui-collapsible ui-sidebar-section";
|
||||
section.dataset.kind = kind;
|
||||
const sectionTitle = document.createElement("h3");
|
||||
sectionTitle.className = "ui-collapsible__title";
|
||||
@@ -147,11 +147,11 @@ function buildDrawingSidePanel(
|
||||
for (const drawing of items) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "b08-drawing-button";
|
||||
button.className = "b07-drawing-button";
|
||||
button.dataset.drawingId = drawing.id;
|
||||
button.dataset.confirmed = String(drawing.confirmed);
|
||||
const name = document.createElement("span");
|
||||
name.className = "b08-drawing-button__name";
|
||||
name.className = "b07-drawing-button__name";
|
||||
name.textContent =
|
||||
drawing.kind === "cross" && typeof drawing.chainage_m === "number"
|
||||
? stationLabel(drawing.chainage_m, stationInterval)
|
||||
@@ -191,67 +191,67 @@ function ditchLabel(design: CrossDesignInfo): string {
|
||||
|
||||
function infoRow(label: string, value: string): HTMLElement {
|
||||
const row = document.createElement("div");
|
||||
row.className = "b08-info__row";
|
||||
row.className = "b07-info__row";
|
||||
const key = document.createElement("span");
|
||||
key.className = "b08-info__key";
|
||||
key.className = "b07-info__key";
|
||||
key.textContent = label;
|
||||
const val = document.createElement("span");
|
||||
val.className = "b08-info__val";
|
||||
val.className = "b07-info__val";
|
||||
val.textContent = value;
|
||||
row.append(key, val);
|
||||
return row;
|
||||
}
|
||||
|
||||
/** 선택 횡단도의 지반정보/계획정보를 2분할로 렌더한다 (잠정치, B08 확정 시 재계산). */
|
||||
/** 선택 횡단도의 지반정보/계획정보를 2분할로 렌더한다 (잠정치, B07 확정 시 재계산). */
|
||||
function buildDesignInfoPanel(title: string, design: CrossDesignInfo | null): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "b08-info";
|
||||
panel.className = "b07-info";
|
||||
const heading = document.createElement("div");
|
||||
heading.className = "b08-info__heading";
|
||||
heading.className = "b07-info__heading";
|
||||
const stationName = document.createElement("strong");
|
||||
stationName.textContent = `${L("B08_Info_Station")} ${title}`;
|
||||
stationName.textContent = `${L("B07_Info_Station")} ${title}`;
|
||||
const confirmed = design?.status === "confirmed";
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `b08-info__badge${confirmed ? " b08-info__badge--confirmed" : ""}`;
|
||||
badge.textContent = confirmed ? L("B08_Info_Confirmed") : L("B08_Info_Provisional");
|
||||
badge.className = `b07-info__badge${confirmed ? " b07-info__badge--confirmed" : ""}`;
|
||||
badge.textContent = confirmed ? L("B07_Info_Confirmed") : L("B07_Info_Provisional");
|
||||
heading.append(stationName, badge);
|
||||
panel.append(heading);
|
||||
|
||||
if (!design) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "b08-info__empty";
|
||||
empty.textContent = L("B08_Info_NoDesign");
|
||||
empty.className = "b07-info__empty";
|
||||
empty.textContent = L("B07_Info_NoDesign");
|
||||
panel.append(empty);
|
||||
return panel;
|
||||
}
|
||||
|
||||
const ground = document.createElement("section");
|
||||
ground.className = "b08-info__block";
|
||||
ground.className = "b07-info__block";
|
||||
const groundTitle = document.createElement("h4");
|
||||
groundTitle.textContent = L("B08_Info_Ground_Title");
|
||||
groundTitle.textContent = L("B07_Info_Ground_Title");
|
||||
ground.append(
|
||||
groundTitle,
|
||||
infoRow(L("B08_Info_GroundType"), L(GROUND_TYPE_LABEL[design.ground_type])),
|
||||
infoRow(L("B08_Info_CutSide"), cutSideLabel(design.section_mode)),
|
||||
infoRow(L("B07_Info_GroundType"), L(GROUND_TYPE_LABEL[design.ground_type])),
|
||||
infoRow(L("B07_Info_CutSide"), cutSideLabel(design.section_mode)),
|
||||
infoRow(
|
||||
L("B08_Info_DitchSide"),
|
||||
L("B07_Info_DitchSide"),
|
||||
design.ditch_side === "left" ? L("B06_Design_Ditch_Left") : L("B06_Design_Ditch_Right"),
|
||||
),
|
||||
);
|
||||
|
||||
const plan = document.createElement("section");
|
||||
plan.className = "b08-info__block";
|
||||
plan.className = "b07-info__block";
|
||||
const planTitle = document.createElement("h4");
|
||||
planTitle.textContent = L("B08_Info_Plan_Title");
|
||||
planTitle.textContent = L("B07_Info_Plan_Title");
|
||||
plan.append(
|
||||
planTitle,
|
||||
infoRow(L("B08_Info_DesignElevation"), `${design.design_elevation_m.toFixed(2)}m`),
|
||||
infoRow(L("B08_Info_CutSlope"), `1:${design.cut_slope_ratio}`),
|
||||
infoRow(L("B08_Info_FillSlope"), `1:${design.fill_slope_ratio}`),
|
||||
infoRow(L("B08_Info_RoadWidth"), `${design.roadbed_width_m.toFixed(2)}m`),
|
||||
infoRow(L("B08_Info_Ditch"), ditchLabel(design)),
|
||||
infoRow(L("B08_Info_CutArea"), `${design.cut_area_m2.toFixed(2)}㎡`),
|
||||
infoRow(L("B08_Info_FillArea"), `${design.fill_area_m2.toFixed(2)}㎡`),
|
||||
infoRow(L("B07_Info_DesignElevation"), `${design.design_elevation_m.toFixed(2)}m`),
|
||||
infoRow(L("B07_Info_CutSlope"), `1:${design.cut_slope_ratio}`),
|
||||
infoRow(L("B07_Info_FillSlope"), `1:${design.fill_slope_ratio}`),
|
||||
infoRow(L("B07_Info_RoadWidth"), `${design.roadbed_width_m.toFixed(2)}m`),
|
||||
infoRow(L("B07_Info_Ditch"), ditchLabel(design)),
|
||||
infoRow(L("B07_Info_CutArea"), `${design.cut_area_m2.toFixed(2)}㎡`),
|
||||
infoRow(L("B07_Info_FillArea"), `${design.fill_area_m2.toFixed(2)}㎡`),
|
||||
);
|
||||
|
||||
panel.append(ground, plan);
|
||||
@@ -261,7 +261,7 @@ function buildDesignInfoPanel(title: string, design: CrossDesignInfo | null): HT
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 페이지 진입점
|
||||
* -------------------------------------------------------------------------- */
|
||||
export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
||||
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
|
||||
let workflowState: WorkflowState | undefined;
|
||||
let drawings: DesignDrawingItem[] = [];
|
||||
@@ -281,14 +281,14 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
}
|
||||
|
||||
const cadHost = document.createElement("div");
|
||||
cadHost.className = "b08-cad-host";
|
||||
cadHost.className = "b07-cad-host";
|
||||
const frame = document.createElement("iframe");
|
||||
frame.className = "b08-cad-frame";
|
||||
frame.src = B08_CAD_APP_URL;
|
||||
frame.title = L("B08_Design_Title");
|
||||
frame.className = "b07-cad-frame";
|
||||
frame.src = B07_CAD_APP_URL;
|
||||
frame.title = L("B07_Design_Title");
|
||||
const license = document.createElement("a");
|
||||
license.className = "b08-cad-license";
|
||||
license.href = "/b08-cad/THIRD_PARTY_LICENSES.txt";
|
||||
license.className = "b07-cad-license";
|
||||
license.href = "/b07-cad/THIRD_PARTY_LICENSES.txt";
|
||||
license.target = "_blank";
|
||||
license.rel = "noreferrer";
|
||||
license.textContent = "Drawing engine based on OpenWebCAD · MIT License";
|
||||
@@ -311,7 +311,7 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
let resolveSave: ((payload: SaveResult) => void) | undefined;
|
||||
let drawingListEl: HTMLElement | undefined;
|
||||
const infoPanelHost = document.createElement("div");
|
||||
infoPanelHost.className = "b08-info-host";
|
||||
infoPanelHost.className = "b07-info-host";
|
||||
|
||||
const updateInfoPanel = (drawing: DesignDrawingItem, response: DesignDrawingResponse): void => {
|
||||
if (drawing.kind !== "cross") {
|
||||
@@ -334,11 +334,11 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
|
||||
const findButton = (drawingId: string) =>
|
||||
drawingListEl?.querySelector<HTMLButtonElement>(
|
||||
`.b08-drawing-button[data-drawing-id="${drawingId}"]`,
|
||||
`.b07-drawing-button[data-drawing-id="${drawingId}"]`,
|
||||
) ?? undefined;
|
||||
|
||||
const highlightActive = (drawingId: string) => {
|
||||
drawingListEl?.querySelectorAll<HTMLButtonElement>(".b08-drawing-button").forEach((item) => {
|
||||
drawingListEl?.querySelectorAll<HTMLButtonElement>(".b07-drawing-button").forEach((item) => {
|
||||
item.dataset.active = String(item.dataset.drawingId === drawingId);
|
||||
});
|
||||
};
|
||||
@@ -442,7 +442,7 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
showToast("현재 도면을 확정하고 저장했습니다.", "success");
|
||||
if (result.all_confirmed) {
|
||||
allDrawingsConfirmed = true;
|
||||
goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[6]);
|
||||
goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[5]);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(
|
||||
@@ -511,16 +511,16 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
drawingListEl = drawingPanel;
|
||||
const confirmActions = document.createElement("div");
|
||||
// 하단 고정은 공용 ui-sidebar-actions로 통일 — 사이드 본문이 [스크롤 영역][액션 줄]로
|
||||
// 쪼개지고 액션 줄은 스크롤 밖에 남는다(2026-08-18 사용자 지시, B04~B08 공통).
|
||||
confirmActions.className = "b08-drawing-actions ui-sidebar-actions";
|
||||
// 쪼개지고 액션 줄은 스크롤 밖에 남는다(2026-08-18 사용자 지시, B04~B07 공통).
|
||||
confirmActions.className = "b07-drawing-actions ui-sidebar-actions";
|
||||
confirmActions.append(confirmButton);
|
||||
|
||||
drawingPanel.append(infoPanelHost, confirmActions);
|
||||
|
||||
const layout = createWorkflowLayout({
|
||||
title: L("B08_Design_Title"),
|
||||
title: L("B07_Design_Title"),
|
||||
steps: workflowSteps(),
|
||||
activeStep: 5,
|
||||
activeStep: 4,
|
||||
leftPanel: drawingPanel,
|
||||
mainContent: cadHost,
|
||||
stages: workflowState?.stages,
|
||||
@@ -535,7 +535,7 @@ export async function renderB08DesignDetail(root: HTMLElement): Promise<void> {
|
||||
goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]);
|
||||
},
|
||||
});
|
||||
layout.root.classList.add("b08-design-layout");
|
||||
layout.root.classList.add("b07-design-layout");
|
||||
root.replaceChildren(layout.root);
|
||||
|
||||
// 페이지 진입 시 첫 도면(종단도)을 자동 선택 — 빈 CAD 화면 방지.
|
||||
@@ -1,23 +1,23 @@
|
||||
/* =============================================================================
|
||||
* B08_DesignDetail_UI_Style.css
|
||||
* B07_DesignDetail_UI_Style.css
|
||||
* 상세 설계(독립형 2D CAD) 화면 스타일 — theme.css 변수만 사용
|
||||
* ========================================================================== */
|
||||
|
||||
/* 워크플로우 레이아웃 높이 (B06 패턴 준수) */
|
||||
.b08-design-layout {
|
||||
.b07-design-layout {
|
||||
height: calc(100vh - var(--spacing-64));
|
||||
height: calc(100dvh - var(--spacing-64));
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.b08-design-layout .ui-workflow-layout__body,
|
||||
.b08-design-layout .ui-workflow-layout__main {
|
||||
.b07-design-layout .ui-workflow-layout__body,
|
||||
.b07-design-layout .ui-workflow-layout__main {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* B06 확정 산출물 도면 목록 */
|
||||
.b08-drawing-list {
|
||||
.b07-drawing-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-12);
|
||||
@@ -26,7 +26,7 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.b08-drawing-list__heading {
|
||||
.b07-drawing-list__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -34,13 +34,13 @@
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.b08-drawing-list__heading span,
|
||||
.b08-drawing-list__empty {
|
||||
.b07-drawing-list__heading span,
|
||||
.b07-drawing-list__empty {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-body-sm);
|
||||
}
|
||||
|
||||
.b08-drawing-group {
|
||||
.b07-drawing-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
@@ -50,13 +50,13 @@
|
||||
}
|
||||
|
||||
/* 횡단도는 2열 배치 */
|
||||
.b08-drawing-group[data-kind="cross"] {
|
||||
.b07-drawing-group[data-kind="cross"] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.b08-drawing-group h3 {
|
||||
.b07-drawing-group h3 {
|
||||
margin: var(--spacing-8) 0 var(--spacing-4);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-caption);
|
||||
@@ -64,11 +64,11 @@
|
||||
}
|
||||
|
||||
/* 그리드 제목은 두 열을 가로지른다 */
|
||||
.b08-drawing-group[data-kind="cross"] h3 {
|
||||
.b07-drawing-group[data-kind="cross"] h3 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.b08-drawing-button {
|
||||
.b07-drawing-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -86,45 +86,45 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.b08-drawing-button__name {
|
||||
.b07-drawing-button__name {
|
||||
overflow: hidden;
|
||||
font-size: var(--text-body-sm);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.b08-drawing-button:hover,
|
||||
.b08-drawing-button[data-active="true"] {
|
||||
.b07-drawing-button:hover,
|
||||
.b07-drawing-button[data-active="true"] {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
.b08-drawing-button[data-active="true"] {
|
||||
.b07-drawing-button[data-active="true"] {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* 확정: 좌측 띠 + 측점 글자색을 함께 성공색으로 반영 */
|
||||
.b08-drawing-button[data-confirmed="true"] {
|
||||
.b07-drawing-button[data-confirmed="true"] {
|
||||
border-color: color-mix(in srgb, var(--color-success) 35%, var(--color-border));
|
||||
border-left-color: var(--color-success);
|
||||
}
|
||||
|
||||
.b08-drawing-button[data-confirmed="true"] .b08-drawing-button__name {
|
||||
.b07-drawing-button[data-confirmed="true"] .b07-drawing-button__name {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
/* 하단 고정·배경·상단 구분선은 공용 ui-sidebar-actions가 맡는다(2026-08-18 통합).
|
||||
여기서는 B08 고유 여백만 남긴다. */
|
||||
.b08-drawing-actions {
|
||||
여기서는 B07 고유 여백만 남긴다. */
|
||||
.b07-drawing-actions {
|
||||
padding-top: var(--spacing-12);
|
||||
}
|
||||
|
||||
.b08-drawing-actions > button {
|
||||
.b07-drawing-actions > button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* CAD 뷰어 호스트 (상세 페이지 영역) */
|
||||
.b08-cad-host {
|
||||
.b07-cad-host {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -134,8 +134,8 @@
|
||||
background-color: var(--color-surface);
|
||||
}
|
||||
|
||||
/* B08 독립형 CAD 앱 임베드 */
|
||||
.b08-cad-frame {
|
||||
/* B07 독립형 CAD 앱 임베드 */
|
||||
.b07-cad-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
@@ -143,7 +143,7 @@
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.b08-cad-license {
|
||||
.b07-cad-license {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 10px;
|
||||
@@ -154,17 +154,17 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.b08-cad-license:hover {
|
||||
.b07-cad-license:hover {
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 선택 횡단도의 지반정보/계획정보 (잠정치) */
|
||||
.b08-info-host:empty {
|
||||
.b07-info-host:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.b08-info {
|
||||
.b07-info {
|
||||
margin-top: var(--spacing-16);
|
||||
padding-top: var(--spacing-16);
|
||||
border-top: 1px solid var(--color-border);
|
||||
@@ -173,14 +173,14 @@
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b08-info__heading {
|
||||
.b07-info__heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b08-info__badge {
|
||||
.b07-info__badge {
|
||||
padding: 1px 6px;
|
||||
font-size: 10px;
|
||||
color: var(--color-primary);
|
||||
@@ -188,40 +188,40 @@
|
||||
border-radius: var(--radius-buttons);
|
||||
}
|
||||
|
||||
.b08-info__badge--confirmed {
|
||||
.b07-info__badge--confirmed {
|
||||
color: var(--color-surface);
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.b08-info__block {
|
||||
.b07-info__block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.b08-info__block h4 {
|
||||
.b07-info__block h4 {
|
||||
margin: var(--spacing-8) 0 2px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.b08-info__row {
|
||||
.b07-info__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-8);
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
|
||||
.b08-info__key {
|
||||
.b07-info__key {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.b08-info__val {
|
||||
.b07-info__val {
|
||||
color: var(--color-text);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.b08-info__empty {
|
||||
.b07-info__empty {
|
||||
font-size: 0.76rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 729 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 3.5 MiB After Width: | Height: | Size: 3.5 MiB |
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
|
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 455 B |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
@@ -225,7 +225,7 @@ export const Toolbar: FC = () => {
|
||||
<header className="cad-titlebar controls">
|
||||
<div className="cad-brand">
|
||||
<strong>Aislo CAD</strong>
|
||||
<span>B08 상세 설계</span>
|
||||
<span>B07 상세 설계</span>
|
||||
</div>
|
||||
<div className="cad-file-state">
|
||||
<span className="cad-file-state__dot" />
|
||||