- 공용 모달 바깥 누르기 = 누름 · 뗌 둘 다 배경일 때만 닫음(숫자 칸 끌기로 안 닫힘 · 평면도 ⚙ 포함 모든 openModal) - ⚙ 설정 모달(종단 · 횡단 · 토적 · 구조물 · 전개도) 폭 720 → 360 px · 그룹 상자(legend) · 맨 아래 단추 줄 하나([안내선 숨기기] [기본값] [취소] [확인]) - [확인] = 바뀐 것만 저장(축척 → 배치) 뒤 다시 그림 · 붉은 칸이면 꺼짐 · 횡단 [이 장] [모든 장] [배율 적용] 단추 → 「적용」 고르기 + [확인] - 축척 「자동」 체크 걷음 — 기본 = 지정값(종단 평면도 1/5,000 · 토적 X 1/2,000 · 전개도 X · Y 1/50) · 옛 null = 기본으로 읽음 - 낡은 시험 고침: 153-1(지운 buildCrossLayoutControls 대신 ⚙ 횡단 모달) · 212-3 · 180-3(안내선 단추 자리) · 280-5(자동 체크 · 단추 판정) · 485 축척 · 토적 · 종단 평면 칸(옛 자동 축척 값 → 지정 기본값) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
179 lines
6.7 KiB
TypeScript
179 lines
6.7 KiB
TypeScript
/* =============================================================================
|
|
* B07_DesignDetail_UI_Settings_Scale.ts
|
|
* ⚙ 모달 X · Y 축척 칸(485-2 · 요구 280-5) — 종단 · 횡단 · 토적 · 전개도만.
|
|
* 종단 = 셋째 칸 「평면도」(521-2 · 우측 상단 평면 위치도) — 칸은 서버 기본값 열쇠 그대로.
|
|
*
|
|
* 칸 = 분모 정수(늘 지정값 · 자동 없음 523-1) · 범위 밖 = 칸 붉게 + 모달 [확인] 꺼짐
|
|
* 저장 = 모달 [확인](`_UI_Settings`)이 `save` 를 부름 — 그 종류 저장본이 있으면 n 장 물음(취소 = 그대로) → PUT
|
|
* [기본값] = 칸만 기본으로 채움 — 저장은 [확인]
|
|
* `settingsGroup` = 모달 안 그룹 상자(523-1) — 횡단 배치 · 표도 같은 상자
|
|
* ========================================================================== */
|
|
|
|
import {
|
|
createInputField,
|
|
hideLoadingOverlay,
|
|
showConfirmDialog,
|
|
showLoadingOverlay,
|
|
showToast,
|
|
} from "@ui/ui_template_elements";
|
|
import {
|
|
type DrawingScaleAxes,
|
|
type DrawingScaleKind,
|
|
type DrawingScales,
|
|
type DrawingScaleSend,
|
|
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;
|
|
|
|
/** 모달 안 그룹 상자 — 제목(legend) + 칸. */
|
|
export function settingsGroup(legend: string, ...children: HTMLElement[]): HTMLFieldSetElement {
|
|
const box = document.createElement("fieldset");
|
|
box.className = "b07-settings__group";
|
|
const title = document.createElement("legend");
|
|
title.textContent = legend;
|
|
box.append(title, ...children);
|
|
return box;
|
|
}
|
|
|
|
/** 축척 칸 한 벌 — 모달 [확인] · [기본값] 이 씀. */
|
|
export interface ScaleFields {
|
|
/** null = 그대로(못 받음 포함) · "invalid" = 붉은 칸 있음 · 그 밖 = 보낼 값 */
|
|
read: () => DrawingScaleSend | null | "invalid";
|
|
reset: () => void;
|
|
/** 저장본 물음 → PUT — 취소 · 실패면 거짓(모달 머묾) */
|
|
save: (next: DrawingScaleSend) => Promise<boolean>;
|
|
}
|
|
|
|
/** ⚙ 모달 몸에 축척 그룹을 붙임 — 축척 없는 종류는 undefined · `onCheck` = 칸 바뀜(모달 [확인] 켜고 끔). */
|
|
export function mountDrawingScale(
|
|
body: HTMLElement,
|
|
kind: string,
|
|
deps: DrawingSettingsDeps,
|
|
onCheck: () => void,
|
|
): ScaleFields | undefined {
|
|
if (!isScaleKind(kind) || !deps.plan) return undefined;
|
|
const { projectId } = deps.plan;
|
|
const status = document.createElement("p");
|
|
status.className = "b07-settings__note";
|
|
status.textContent = "축척 받는 중…";
|
|
const section = settingsGroup(
|
|
kind === "cross" ? "축척 — 횡단도 장(표준 횡단면도 뺌)" : `축척 — ${LABEL[kind]}`,
|
|
status,
|
|
);
|
|
section.classList.add("b07-scale");
|
|
body.append(section);
|
|
let built: Omit<ScaleFields, "save"> | undefined;
|
|
let data: DrawingScales | undefined;
|
|
void fetchDrawingScales(projectId)
|
|
.then((got) => {
|
|
data = got;
|
|
status.remove();
|
|
built = buildScaleFields(section, kind, got, onCheck);
|
|
onCheck();
|
|
})
|
|
.catch(() => {
|
|
status.textContent = "축척을 받지 못했습니다.";
|
|
});
|
|
return {
|
|
read: () => built?.read() ?? null,
|
|
reset: () => built?.reset(),
|
|
save: async (next) => {
|
|
const saved = data?.saved[kind] ?? 0;
|
|
const ask = `${LABEL[kind]} 축척을 바꾸면 저장본 ${saved}장이 풀립니다(원본에서 다시 그림). 바꿀까요?`;
|
|
if (saved > 0 && !(await showConfirmDialog(ask, "바꿈"))) return false;
|
|
showLoadingOverlay();
|
|
try {
|
|
await saveDrawingScales(projectId, { [kind]: next });
|
|
showToast(`${LABEL[kind]} 축척을 바꿨습니다.`, "success");
|
|
return true;
|
|
} catch (error) {
|
|
showToast(error instanceof Error ? error.message : "축척을 저장하지 못했습니다.", "error");
|
|
return false;
|
|
} finally {
|
|
hideLoadingOverlay();
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildScaleFields(
|
|
section: HTMLElement,
|
|
kind: DrawingScaleKind,
|
|
data: DrawingScales,
|
|
onCheck: () => void,
|
|
): Omit<ScaleFields, "save"> {
|
|
const axes = Object.keys(data.defaults[kind]) as Axis[];
|
|
const fields = axes.map((axis) => {
|
|
const fallback = data.defaults[kind][axis] as number;
|
|
const range = data.ranges[kind][axis] ?? [1, 1];
|
|
const [min, max] = range;
|
|
const field = createInputField({
|
|
label: AXIS_LABEL[axis],
|
|
type: "number",
|
|
compact: true,
|
|
min,
|
|
max,
|
|
});
|
|
field.root.classList.add("b07-scale__field");
|
|
const set = (value: number): void => {
|
|
field.input.value = String(value);
|
|
};
|
|
set(data.scales[kind][axis] ?? fallback);
|
|
const read = () => readScaleAxis(field.input.value, range);
|
|
return { axis, field, fallback, set, read };
|
|
});
|
|
|
|
// 칸 밑 오류 글은 숨기고(틀린 칸만 길어짐) 줄 아래 한 줄로 알림
|
|
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;
|
|
onCheck();
|
|
};
|
|
for (const f of fields) f.field.input.addEventListener("input", check);
|
|
|
|
const row = document.createElement("div");
|
|
row.className = "b07-scale__row";
|
|
row.append(...fields.map((f) => f.field.root));
|
|
section.append(row, alertLine);
|
|
return {
|
|
read: () => {
|
|
const read = fields.map((f) => [f, f.read()] as const);
|
|
if (read.some(([, value]) => typeof value === "string")) return "invalid";
|
|
const now = data.scales[kind];
|
|
if (read.every(([f, value]) => value === (now[f.axis] ?? f.fallback))) return null;
|
|
return Object.fromEntries(
|
|
read.map(([f, v]) => [f.axis, scaleToSend(v as number, f.fallback)]),
|
|
) as DrawingScaleSend;
|
|
},
|
|
reset: () => {
|
|
fields.forEach((f) => f.set(f.fallback));
|
|
check();
|
|
},
|
|
};
|
|
}
|