Files
Aislo/B07_DesignDetail/B07_DesignDetail_UI_Settings_Scale.ts
T
eomsangdonandClaude Opus 5.5 df0e64ffe2 feat(B07): 종단도 장마다 우측 상단 평면 위치도 · 종단 ⚙ 「평면도」 척도 칸 (521-2 · 요구 340-2)
- 새 Engine_Cad_LongPlan — 작도 영역 우상단 5 mm 안쪽 150×100 mm 칸 · 자동 = 전체 노선 맞춤(분모 500 올림) · 척도 정함 = 이 장 중심 확대 · 밖 노선 흐린 파선 · 칸 밖 잘림 · 이 장 굵게 · 시 · 종점 측점 · 방위표 · 「평면도 S=1/n」 · 「n/N 장」 · 좌표 없으면 안 그림
- 장 나눔 — 그래프가 칸을 덮으면 측점 줄여 장 더 나눔 · 장 목록에 number · total
- 축척 — longitudinal.plan 기본 자동 · 범위 1/500~1/50,000 · ⚙ 칸 = 서버 기본값 열쇠 그대로(셋째 칸 + 자동)
- 칸 앞 확정 종단 저장본 — 목록 받을 때 종단만 한 번 모두 풂(DRAWING_FORMAT 그대로)
- 시험 — 칸 자리 · 안 겹침 · 장마다 강조 · 척도 확대 · 좌표 없음 · 한 번 풂 · plan 범위 · ⚙ 평면 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 08:01:02 +09:00

169 lines
6.7 KiB
TypeScript

/* =============================================================================
* B07_DesignDetail_UI_Settings_Scale.ts
* ⚙ 모달 X · Y 축척 칸(485-2 · 요구 280-5) — 종단 · 횡단 · 토적 · 전개도만.
* 종단 = 셋째 칸 「평면도」(521-2 · 우측 상단 평면 위치도) — 칸은 서버 기본값 열쇠 그대로.
*
* 칸 = 분모 정수 · 범위 밖 = 칸 붉게 + [축척 저장] 꺼짐 · 기본이 자동인 칸(토적 X · 전개도) = 「자동」
* [축척 저장] = 그 종류 저장본이 있으면 n 장 물음(취소 = 그대로) → PUT → 화면 다시 세움(도면 다시 그림)
* [기본값] = 칸만 기본으로 채움 — 저장은 [축척 저장]
* ========================================================================== */
import { createCheckbox } from "@ui/ui_template_checkbox";
import {
createButton,
createInputField,
hideLoadingOverlay,
showConfirmDialog,
showLoadingOverlay,
showToast,
} from "@ui/ui_template_elements";
import {
type DrawingScaleAxes,
type DrawingScaleKind,
type DrawingScales,
fetchDrawingScales,
saveDrawingScales,
} from "./B07_DesignDetail_Api_Fetch";
import type { DrawingSettingsDeps } from "./B07_DesignDetail_UI_Settings";
import { readScaleAxis, scaleToSend } from "./logic/B07_DesignDetail_Scale_Check";
const LABEL: Record<DrawingScaleKind, string> = {
longitudinal: "종단도",
cross: "횡단도",
mass_haul: "토적도",
development: "전개도",
};
type Axis = keyof DrawingScaleAxes;
const AXIS_LABEL: Record<Axis, string> = { x: "X(가로) 1/", y: "Y(세로) 1/", plan: "평면도 1/" };
const isScaleKind = (kind: string): kind is DrawingScaleKind => kind in LABEL;
/** ⚙ 모달 몸에 축척 칸을 붙임 — 축척 없는 종류는 아무것도 안 함. */
export function mountDrawingScale(
body: HTMLElement,
kind: string,
deps: DrawingSettingsDeps,
close: () => void,
): void {
if (!isScaleKind(kind) || !deps.plan) return;
const { projectId, onClosed } = deps.plan;
const section = document.createElement("div");
section.className = "b07-scale";
section.textContent = "축척 받는 중…";
body.append(section);
void fetchDrawingScales(projectId)
.then((data) => {
section.textContent = "";
buildScaleFields(section, kind, data, async (next) => {
const saved = data.saved[kind] ?? 0;
const ask = `${LABEL[kind]} 축척을 바꾸면 저장본 ${saved}장이 풀립니다(원본에서 다시 그림). 바꿀까요?`;
if (saved > 0 && !(await showConfirmDialog(ask, "바꿈"))) return;
showLoadingOverlay();
try {
await saveDrawingScales(projectId, { [kind]: next });
close();
showToast(`${LABEL[kind]} 축척을 바꿨습니다.`, "success");
onClosed(true, deps.current()?.id); // 목록 · 도면을 서버에서 다시 받아 다시 그림
} catch (error) {
showToast(
error instanceof Error ? error.message : "축척을 저장하지 못했습니다.",
"error",
);
} finally {
hideLoadingOverlay();
}
});
})
.catch(() => {
section.textContent = "축척을 받지 못했습니다.";
});
}
function buildScaleFields(
section: HTMLElement,
kind: DrawingScaleKind,
data: DrawingScales,
save: (next: DrawingScaleAxes) => Promise<void>,
): void {
const axes = Object.keys(data.defaults[kind]) as Axis[];
const fields = axes.map((axis) => {
const label = AXIS_LABEL[axis];
const fallback = data.defaults[kind][axis] ?? null;
const range = data.ranges[kind][axis] ?? [1, 1];
const [min, max] = range;
const field = createInputField({ label, type: "number", compact: true, min, max });
field.root.classList.add("b07-scale__field");
const auto = fallback === null ? createCheckbox("자동") : undefined;
if (auto) field.root.append(auto.root);
const set = (value: number | null): void => {
field.input.value = value === null ? "" : String(value);
if (auto) auto.input.checked = value === null;
field.input.disabled = value === null && !!auto;
};
set(data.scales[kind][axis] ?? null);
const read = () => readScaleAxis(field.input.value, !!auto?.input.checked, range);
return { axis, field, auto, fallback, set, read };
});
const saveButton = createButton({
label: "축척 저장",
onClick: () => {
const read = fields.map((f) => [f, f.read() as number | null] as const);
const now = data.scales[kind];
if (read.every(([f, value]) => value === (now[f.axis] ?? null))) {
showToast("바뀐 축척이 없습니다.", "info");
return;
}
const next = Object.fromEntries(read.map(([f, v]) => [f.axis, scaleToSend(v, f.fallback)]));
void save(next as DrawingScaleAxes);
},
});
// 칸 밑 오류 글은 숨기고(틀린 칸만 길어짐) 줄 아래 한 줄로 알림
const alertLine = document.createElement("p");
alertLine.className = "b07-scale__error";
alertLine.setAttribute("role", "alert");
alertLine.hidden = true;
const check = (): void => {
const errors = new Map<string, string[]>();
for (const f of fields) {
const result = f.read();
const message = typeof result === "string" ? result : undefined;
f.field.setError(message);
const name = f.axis === "plan" ? "평면도" : f.axis.toUpperCase();
if (message) errors.set(message, [...(errors.get(message) ?? []), name]);
}
alertLine.textContent = [...errors].map(([m, axes]) => `${axes.join(" · ")}: ${m}`).join(" / ");
alertLine.hidden = errors.size === 0;
saveButton.disabled = errors.size > 0;
};
for (const f of fields) {
f.field.input.addEventListener("input", check);
f.auto?.input.addEventListener("change", () => {
if (f.auto!.input.checked) f.set(null);
else f.field.input.disabled = false; // 빈 칸 — 숫자를 적어야 저장
check();
});
}
const reset = createButton({
label: "기본값",
variant: "ghost",
onClick: () => {
fields.forEach((f) => f.set(f.fallback));
check();
},
});
reset.title = "칸을 기본 축척으로 채움 — [축척 저장]으로 씀";
const title = document.createElement("p");
title.className = "b07-settings__note";
title.textContent =
kind === "cross" ? "축척 — 횡단도 장(표준 횡단면도 뺌)" : `축척 — ${LABEL[kind]}`;
const row = document.createElement("div");
row.className = "b07-scale__row";
row.append(...fields.map((f) => f.field.root));
const buttons = document.createElement("div");
buttons.className = "b07-scale__actions";
buttons.append(reset, saveButton);
section.append(title, row, alertLine, buttons);
}