Files
Aislo/B05_Profile/B05_Profile_UI_Drainage_Suggest.ts
T
eomsangdonandClaude Opus 5.5 e5dc007b40 feat(B05 배수유역): 369-2 [자동 제안] 두 방식 · [배관 배치 초기화] = 세류선만 초안 · 제안 [적용] = 관경 · 시설만
- 배수유역 머리 [자동 제안] → 고름 창(⚙ 창 틀) ① 전체 자동(all) ② 지정 포함 추가(add) · 낡은 동안 끔
- [배관 배치 초기화] — 저장분 즉시 지움 없앰 · 관 목록 없이 셈(세류선 교차만) · 초안
- 셋 다 답이 오면 세션 초안 + 사진첩 한 걸음(400 ms 넘은 답도 제 걸음)
- 제안 목록 [적용] — 관 자리 후보는 안 더함(점선 원 · 목록 그대로) · 관경 · 시설 제안 없으면 꺼짐
- computeDetailBasins(points | null, auto) · 글 = 툴팁 두 값 고침 · 새 글 끝에 새 키
- 시험 — 349 흐름에 자동 제안 · 347_2 · 298 · 312 고쳐 씀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
2026-10-17 12:00:43 +09:00

105 lines
4.0 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Drainage_Suggest.ts
* 배수유역 칸 제안 목록(347-3) — 관경 · 시설 종류 · 관 자리 후보를 보여 주고 [적용] 한 번에 관경 · 시설만
* 관 초안으로(관 자리 = [자동 제안] · 369).
* 모으기 · 얹기 규칙은 `logic/B05_Profile_Drainage_Suggest.ts` · 자동 바꿈 없음.
* 관 자리 후보는 배수유역도에도 점선 원으로 그림(`candidates()` → 그리기 판).
* ========================================================================== */
import type {
DetailBasinResponse,
DetailPipeInput,
} from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
import {
applySuggestions,
collectSuggestions,
type Suggestion,
} from "./logic/B05_Profile_Drainage_Suggest";
const FACILITY_NAME: Record<string, string> = {
pipe: "배관",
box_culvert: "BOX암거",
ford_bridge: "세월교",
};
const facilityName = (id: string): string => FACILITY_NAME[id] ?? id;
/** 제안 한 줄 글. */
export function suggestionText(item: Suggestion): string {
const at = `측점 ${Math.round(item.chainageM)} m`;
if (item.kind === "position")
return `${at} — 관 자리 후보(흐름이 모이는 오목 자리 · [자동 제안])`;
if (item.kind === "facility")
return `${at} — ${facilityName(item.from)} → ${facilityName(item.to)} 검토`;
return `${at} — 관경 ${item.from === null ? "미정" : `Ø${item.from}`} → Ø${item.to}`;
}
export interface DrainageSuggest {
root: HTMLElement;
update(response: DetailBasinResponse): void;
/** 노선 · 관이 바뀌어 낡음 — [적용] 끔(옛 노선 기준 제안이 새 노선에 안 들어가게). */
setStale(stale: boolean): void;
/** 관 자리 후보 누가거리 — 배수유역도 점선 원. */
candidates(): number[];
}
export function createDrainageSuggest(
pipeRequest: () => DetailPipeInput[],
applyPoints: (points: DetailPipeInput[]) => void,
): DrainageSuggest {
const root = document.createElement("div");
root.className = "b05-drainage__suggest";
root.hidden = true;
const head = document.createElement("div");
head.className = "b05-drainage__suggest-head";
const title = document.createElement("span");
const apply = document.createElement("button");
apply.type = "button";
apply.className = "b05-drainage__analyze";
apply.textContent = "적용";
const applyTitle =
"관경 · 시설 제안을 관 초안에 한 번에 얹습니다 — [저장] 때 남습니다(관 자리는 [자동 제안]).";
apply.title = applyTitle;
head.append(title, apply);
const list = document.createElement("ul");
list.className = "b05-drainage__suggest-list";
root.append(head, list);
let suggestions: Suggestion[] = [];
let stale = false;
/** [적용] 켜짐 — 낡지 않고 관경 · 시설 제안이 있을 때(관 자리 후보만이면 할 일 없음). */
const paintApply = (): void => {
apply.disabled = stale || !suggestions.some((item) => item.kind !== "position");
apply.title = stale ? "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용" : applyTitle;
};
apply.addEventListener("click", () => {
if (suggestions.length && !apply.disabled)
applyPoints(applySuggestions(pipeRequest(), suggestions));
});
return {
root,
update(response) {
suggestions = response.stale
? []
: collectSuggestions(response.pipe_points, response.basins, response.pipe_candidates ?? []);
root.hidden = suggestions.length === 0;
title.textContent = `제안 ${suggestions.length}건`;
paintApply();
list.replaceChildren(
...suggestions.map((item) => {
const row = document.createElement("li");
row.textContent = suggestionText(item);
return row;
}),
);
},
setStale(next) {
stale = next;
paintApply();
},
candidates: () =>
suggestions.filter((item) => item.kind === "position").map((item) => item.chainageM),
};
}