Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_5' into sub_laptop_6

This commit is contained in:
2026-10-12 22:14:16 +09:00
12 changed files with 329 additions and 21 deletions
@@ -30,6 +30,7 @@ import {
} from "../B05_Profile/B05_Profile_Api_Structures";
import { readPendingPipes, writePendingPipes } from "../B05_Profile/B05_Profile_Api_Pipes_Draft";
import { mergeFacilityOptions } from "../B05_Profile/B05_Profile_UI_Drainage_Facility_Merge";
import { fordWaitsOf, waitsFordHeight } from "../B05_Profile/B05_Profile_UI_Drainage_Ford_Fill";
import { fetchDetailPipePoints } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
import {
linkFields,
@@ -61,15 +62,14 @@ import { withSpecDefaults } from "./B06_Section_UI_Page_Structures_Spec";
const PIPE_ADD_GUIDE = "계곡 통과 시설을 넣고 빼려면 프로젝트를 먼저 여세요.";
const PIPE_MOVE_GUIDE = "기준 측점 이동은 배수유역을 다시 나눠야 해 B05(종단) 화면에서 합니다.";
/** B05·B06 은 한 페이지라 넣고 빼는 자리도 같아야 한다(2026-09-12 사용자). 재분할은 저장 뒤. */
/** B05·B06 은 한 페이지라 넣고 빼는 자리도 같아야 한다(2026-09-12 사용자). 재분할은 뒤에서 바로(298-1). */
const PIPE_ADD_NOTICE =
"계곡 통과 시설을 넣었습니다 — 횡단도는 바로 보이고, 세부 배수유역은 [저장]·[확정] 뒤에 다시 계산됩니다.";
const PIPE_REMOVE_NOTICE =
"계곡 통과 시설을 뺐습니다 — 세부 배수유역은 [저장]·[확정] 뒤에 다시 계산됩니다.";
"계곡 통과 시설을 넣었습니다 — 횡단도는 바로 보이고, 세부 배수유역은 뒤에서 다시 나눕니다.";
const PIPE_REMOVE_NOTICE = "계곡 통과 시설을 뺐습니다 — 세부 배수유역은 뒤에서 다시 나눕니다.";
/** 고른 것 없이 폼만 만졌을 때 — 값이 어디로도 가지 않으므로 이유를 알린다. */
const PIPE_PICK_GUIDE = "먼저 횡단도나 목록에서 구조물을 고르세요 — 고른 것에만 값이 반영됩니다.";
const PIPE_MOVE_NOTICE =
"기준 측점을 옮겼습니다 — 횡단도는 바로 보이고, 세부 배수유역은 [저장]·[확정] 뒤에 다시 계산됩니다.";
"기준 측점을 옮겼습니다 — 횡단도는 바로 보이고, 세부 배수유역은 뒤에서 다시 나눕니다.";
export interface B06StructuresPanelDeps {
projectId: string | null;
@@ -132,6 +132,8 @@ export interface B06StructuresPanel {
selectStructure: (structureId: string) => void;
/** 측점 없는 관(물넘이포장) 하나를 폼에 올림 — 카드 안 고름 · 스크롤만 · 알약 · 고름 값에 알약 id(요구 178). */
selectPipeAt: (chainageM: number) => void;
/** 배수유역도가 관 초안에 채운 빈 월류 높이(126-1)를 목록 · 폼에 받는다(298-1). */
adoptFordHeights: () => void;
/** 지금 목록의 구조물 정본(미저장분 포함) — 카드 찾기에 쓴다. */
getStructures: () => StructureInstance[];
/** 지금 폼에 올라온 계곡 통과 시설의 누가거리(없으면 null). */
@@ -259,6 +261,9 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
});
pipeFacilities.sort((left, right) => left.chainage_m - right.chainage_m);
currentChainageM = at;
// 빈 월류 높이 = 다시 나눈 유량으로 채움(126-1) — 배수유역도 판이 이 표를 보고 채움(298-1)
if (waitsFordHeight(attributes.facility, attributes.options))
fordWaitsOf(deps.projectId).add(at);
persistPipes();
section.setPipeFacilities(pipeFacilities);
pushMarks();
@@ -266,7 +271,7 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
showToast(PIPE_ADD_NOTICE, "success");
},
// 값 수정·기준점 이동 모두 B06 에서 받는다 — 이동은 종단 끌기와 **같은 함수**를 탄다
// (2026-09-12 일원화). 재분할은 [저장]·[확정] 뒤 서버 몫이다.
// (2026-09-12 일원화). 세부유역은 관 초안을 쓰면 배수유역도가 뒤에서 다시 나눈다(298-1).
onPipeUpdate: (fromChainageM, toChainageM, attributes) => {
// 값만 고치면 폼이 옛 측점을 그대로 돌려줌(안 고친 칸) — 0.005m 넘으면 옮긴 것(80.17 → 80 도 · 234-1)
if (Math.abs(fromChainageM - toChainageM) > 0.005) movePipeTo(fromChainageM, toChainageM);
@@ -548,6 +553,23 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
section.selectPipeByChainage(hit.chainage_m);
},
getStructures: () => structures,
adoptFordHeights: () => {
// 배수유역도가 초안에 채운 월류 높이(126-1)를 목록 · 폼에 받음 — 빈 칸만(298-1)
const draft = deps.projectId ? readPendingPipes(deps.projectId) : null;
const filled = pipeFacilities.filter((entry) => {
if (entry.options?.ford_height_m !== undefined) return false;
const height = draft?.find(
(pipe) => Math.abs(pipe.chainage_m - entry.chainage_m) < PIPE_MATCH_M,
)?.options?.ford_height_m;
if (height === undefined) return false;
entry.options = { ...entry.options, ford_height_m: height };
return true;
});
if (!filled.length) return;
section.setPipeFacilities(pipeFacilities);
const open = filled.find((entry) => entry.chainage_m === currentChainageM);
if (open) section.selectPipeByChainage(open.chainage_m);
},
releasePipe: () => {
currentChainageM = null;
section.selectPipeByChainage(null);
@@ -220,9 +220,14 @@ export function createSectionProfile(
onPipesChanged: (pipes) => {
if (shared.view() === "cross" && shared.profile?.side)
paintMapPick(shared.profile.side, readSelection(shared.projectId));
// 관 종류 · 관경이 바뀜(새 관 관경은 배수 재계산이 정함) — 카드 관 그림도 다시(273-2)
// 관 종류 · 관경 · 채운 월류 높이가 바뀜(새 관 관경 · 빈 높이는 배수 재계산이 정함) — 카드 관 그림도 다시(273-2 · 298-1)
const signature = JSON.stringify(
pipes.map((p) => [p.chainage_m, p.facility, p.options?.pipe_diameter_mm ?? null]),
pipes.map((p) => [
p.chainage_m,
p.facility,
p.options?.pipe_diameter_mm ?? null,
p.options?.ford_height_m ?? null,
]),
);
const changed = pipeSignature !== null && signature !== pipeSignature;
pipeSignature = signature;