feat(B05): 구조물 컨테이너 병합 2단계 — 측점 표기·기준점 입력·계곡 통과 시설 UI

PLAN 2026-08-17 「B05 구조물 컨테이너 병합」 프론트엔드.

- B05_Profile_Util_Station.ts 신설: 측점번호+잔여거리("3+18.0") ↔ 누가거리
  변환·서식·해석. 누가거리 직접 입력도 허용, 부동소수 올림 보정.
- 구조물 패널: 위치 입력을 측점 표기 텍스트로 재편. 점형 = 기준 측점 1칸,
  구간형 = 기준점(비우면 시작)+시작+종료 3칸, 시작≤기준≤종료 검증과 잘못된
  입력 붉힘. 목록 표기도 측점식. 구간형 마크 드래그는 기준점 기준으로 시작·
  종료가 따라온다. 상세(detail) 옵션은 폼에서 숨긴다 — B05는 유무·종류·위치
  단계(2026-08-17 사용자 확정).
- 우클릭 메뉴 필터 완화: managed_by와 b05 phase 필수만 제외 — 상세 필수였던
  타입(옹벽·돌쌓기 등)도 우클릭 한 번으로 추가된다.
- B05_Profile_UI_Drainage_Facility.ts 신설(패널 700줄 제한 대응 분리): 관 마커
  선택 시 시설 종류(배관/BOX암거/물넘이/세월교)·구간·세월교 관 종류/크기/수량
  폼. FacilityStore가 시설 확장 정보를 보관하고 세부유역 재계산·저장 요청에
  되붙인다(관 이동 후에도 최근접 승계 — 백엔드 carry와 같은 기준). 응답이
  정본이라 apply()에서 전량 재구성.
- B04_PreProcess_Api_Fetch.ts: PipeFacility·DetailPipeInput 타입, 요청·응답에
  시설 필드 반영.

npm run typecheck·npm run build 통과. 통합 서클마크 표시와 구 비정규 측점
이관·폐기(3단계)는 배관 측점선 체계 이전과 묶어 다음 작업 — PLAN.md 체크리스트
에 미완 사유 기록.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 09:06:19 +09:00
co-authored by Claude Opus 5
parent 1d07633260
commit deaf51778f
9 changed files with 502 additions and 72 deletions
+23 -2
View File
@@ -437,12 +437,33 @@ export async function fetchRoadInflow(
/** 관이 그 자리에 있는 이유. 백엔드 `common_util_drainage_pipes`가 정의처다. */
export type PipeSource = "stream" | "spacing" | "user";
/** 계곡 통과 시설 종류(2026-08-17 컨테이너 병합). 같은 계곡 교차점에서 유량·지형에
* 따라 택일한다 — 정의처는 백엔드 `common_util_drainage_pipes`. 교량은 임도용이 아니다. */
export type PipeFacility = "pipe" | "box_culvert" | "ford_pavement" | "ford_bridge";
export const PIPE_FACILITY_LABELS: ReadonlyArray<[PipeFacility, string]> = [
["pipe", "배관"],
["box_culvert", "BOX암거"],
["ford_pavement", "물넘이포장"],
["ford_bridge", "세월교"],
];
export interface DetailPipePoint {
chainage_m: number;
lonlat: [number, number];
source: PipeSource;
/** 시설 종류 — 응답에서 생략되면 기본 배관. */
facility?: PipeFacility;
/** 기준점 앞뒤 구간(유입·유출 부속 폭). 없으면 폭 0 — 사용자가 필요할 때 벌린다. */
start_m?: number;
end_m?: number;
/** 유무·종류 수준의 시설 옵션(예: 세월교 관 종류/크기/수량). 상세 치수는 B06/B07. */
options?: Record<string, string | number>;
}
/** 편집·저장 요청에 싣는 관 1건 — 좌표(lonlat)는 서버가 다시 계산하므로 뺀다. */
export type DetailPipeInput = Omit<DetailPipePoint, "lonlat">;
export interface DetailBasin {
index: number;
chainage_m: number;
@@ -504,7 +525,7 @@ export async function fetchDetailPipePoints(projectId: string): Promise<DetailBa
* `points`를 비우면 저장분을 무시하고 기본 관 + 자동 보충으로 되돌린다. */
export async function computeDetailBasins(
projectId: string,
points: Array<{ chainage_m: number; source: PipeSource }>,
points: DetailPipeInput[],
): Promise<DetailBasinResponse> {
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/detail-basins`, {
method: "POST",
@@ -524,7 +545,7 @@ export async function resetDetailPipePoints(projectId: string): Promise<DetailBa
/** 관 매설 지점을 정본으로 확정하고 세부유역 산출물까지 남긴다(모델 확정 시점). */
export async function saveDetailPipePoints(
projectId: string,
points: Array<{ chainage_m: number; source: PipeSource }>,
points: DetailPipeInput[],
): Promise<DetailBasinResponse> {
return requestJson<DetailBasinResponse>(`/projects/${projectId}/drainage/pipe-points`, {
method: "PUT",