auto: 2026-08-17 17:09 (ESD_LAPTOP)
This commit is contained in:
@@ -130,31 +130,38 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
memoField.type = "text";
|
||||
memoField.placeholder = "메모(선택)";
|
||||
|
||||
// 측점은 3행 — 한 행이 [라벨][측점번호][잔여거리](2026-08-17 사용자 지시 2).
|
||||
const positionRow = document.createElement("div");
|
||||
positionRow.className = "b05-route__irregular-row";
|
||||
positionRow.className = "b05-structure__position-row";
|
||||
positionRow.append(startFields.wrap, anchorFields.wrap, endFields.wrap);
|
||||
|
||||
// 기본 인터페이스는 2열 격자(2026-08-17 사용자 지시 1).
|
||||
const typeRow = document.createElement("div");
|
||||
typeRow.className = "b05-structure__grid";
|
||||
typeRow.append(field("구조물군", groupSelect), field("종류", typeSelect));
|
||||
|
||||
const sideRow = document.createElement("div");
|
||||
sideRow.className = "b05-route__irregular-row";
|
||||
sideRow.className = "b05-structure__grid";
|
||||
sideRow.append(field("설치측", sideSelect), field("이격 (m)", offsetField));
|
||||
|
||||
// 옵션 칸은 타입마다 다르므로 선택할 때마다 새로 그린다.
|
||||
const optionRow = document.createElement("div");
|
||||
optionRow.className = "b05-route__irregular-row";
|
||||
optionRow.className = "b05-structure__grid";
|
||||
|
||||
// 계곡 통과 시설(배관 등)의 부속 옵션 서브폼 — 일반 구조물의 옵션 칸과 같은
|
||||
// 자리에서, 같은 흐름(종류 선택 → 옵션 → 추가/수정)으로 편집한다(2026-08-17
|
||||
// 사용자 지시 — 자동·수동 배관은 같은 구조물, 별도 UI 금지). 기슭막이 길이를
|
||||
// 넣으면 시작·종료 측점이 기준 − 앞 ~ 기준 + 뒤로 자동 채워진다.
|
||||
const facilityOptions = createFacilityOptionsForm({
|
||||
onSpanSuggest: (frontM, backM) => {
|
||||
const step = interval();
|
||||
const anchor = anchorFields.read(step, false);
|
||||
if (anchor === null) return;
|
||||
startFields.write(Math.max(0, anchor - frontM), step);
|
||||
endFields.write(anchor + backM, step);
|
||||
},
|
||||
});
|
||||
const facilityOptions = createFacilityOptionsForm();
|
||||
|
||||
/** 이 타입이 서브폼(계곡 통과 시설 부속·독립 기슭막이)을 쓰는지 — 쓰면 레지스트리
|
||||
* 옵션 칸 대신 서브폼이 그린다(2026-08-17 사용자 지시 12: 독립 기슭막이도 같은
|
||||
* 인터페이스). */
|
||||
function facilityFormKind(type: StructureType | null): PipeFacility | "revetment" | null {
|
||||
if (!type) return null;
|
||||
if (type.managed_by) return type.type_id as PipeFacility;
|
||||
return type.type_id === "revetment" ? "revetment" : null;
|
||||
}
|
||||
|
||||
const primary = document.createElement("button");
|
||||
primary.type = "button";
|
||||
@@ -176,10 +183,14 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
// 사용법 설명 문단은 두지 않는다 — 공간 대비 의미 없음(2026-08-17 사용자 지시,
|
||||
// 필요 시 별도 설명 페이지로).
|
||||
|
||||
// 측점 그룹과 옵션 나열 사이 구분선(2026-08-17 사용자 지시 2).
|
||||
const positionDivider = document.createElement("hr");
|
||||
positionDivider.className = "b05-structure__divider";
|
||||
|
||||
body.append(
|
||||
field("구조물군", groupSelect),
|
||||
field("종류", typeSelect),
|
||||
typeRow,
|
||||
positionRow,
|
||||
positionDivider,
|
||||
sideRow,
|
||||
optionRow,
|
||||
facilityOptions.root,
|
||||
@@ -215,34 +226,28 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
|
||||
const interval = (): number => callbacks.getInterval();
|
||||
|
||||
/** 배치형태·관리 주체에 맞춰 위치 칸을 바꾼다. 계곡 통과 시설은 기준 측점 필수 +
|
||||
* 시작·종료(범위, 기슭막이 길이로 자동 채움 가능)를 받고, 설치측·이격은 정본
|
||||
* (관 지점) 소관이라 감춘다. 종류 미선택(빈값)이면 입력 칸 전체를 숨긴다 —
|
||||
* 리셋 직후 상태(2026-08-17 사용자 지시). */
|
||||
/** 배치형태·관리 주체에 맞춰 위치 칸을 바꾼다. 계곡 통과 시설(배수관 등)은 점형이라
|
||||
* 기준 측점 하나만 받고(2026-08-17 사용자 지시 2 — 시작·종료 불필요), 설치측·이격도
|
||||
* 정본(관 지점) 소관이라 감춘다. 종류 미선택(빈값)이면 입력 칸 전체를 숨긴다 —
|
||||
* 리셋 직후 상태. */
|
||||
function syncPlacementFields(): void {
|
||||
const type = currentType();
|
||||
const managed = !!type?.managed_by;
|
||||
const isInterval = !!type && (managed || type.placement === "interval");
|
||||
const isInterval = !!type && !managed && type.placement === "interval";
|
||||
positionRow.hidden = !type;
|
||||
positionDivider.hidden = !type;
|
||||
startFields.wrap.hidden = !isInterval;
|
||||
endFields.wrap.hidden = !isInterval;
|
||||
sideRow.hidden = !type || managed;
|
||||
primary.disabled = !type;
|
||||
anchorFields.wrap.querySelector("span")!.textContent =
|
||||
isInterval && !managed ? "기준 측점 (비우면 시작)" : "기준 측점";
|
||||
anchorFields.wrap.querySelector("span")!.textContent = isInterval
|
||||
? "기준 측점 (비우면 시작)"
|
||||
: "기준 측점";
|
||||
}
|
||||
|
||||
/** 종류에 맞춰 부속 옵션 서브폼을 켠다(계곡 통과 시설일 때만, 값은 인자로). */
|
||||
function syncFacilityForm(
|
||||
options: Record<string, string | number> = {},
|
||||
source?: PipeSource,
|
||||
): void {
|
||||
const type = currentType();
|
||||
facilityOptions.setFacility(
|
||||
type?.managed_by ? (type.type_id as PipeFacility) : null,
|
||||
options,
|
||||
source,
|
||||
);
|
||||
/** 종류에 맞춰 부속 옵션 서브폼을 켠다(계곡 통과 시설·독립 기슭막이, 값은 인자로). */
|
||||
function syncFacilityForm(options: Record<string, string | number> = {}): void {
|
||||
facilityOptions.setFacility(facilityFormKind(currentType()), options);
|
||||
}
|
||||
|
||||
/** 타입의 옵션 스키마대로 입력 칸을 다시 그린다 — 상세(detail) 옵션은 B06/B07
|
||||
@@ -251,7 +256,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
const type = currentType();
|
||||
optionInputs = [];
|
||||
optionRow.replaceChildren();
|
||||
const visible = type && !type.managed_by ? type.options.filter(isB05Option) : [];
|
||||
// 서브폼이 담당하는 타입(계곡 통과 시설·독립 기슭막이)은 여기서 그리지 않는다.
|
||||
const visible = type && !facilityFormKind(type) ? type.options.filter(isB05Option) : [];
|
||||
if (!visible.length) {
|
||||
optionRow.hidden = true;
|
||||
return;
|
||||
@@ -261,11 +267,11 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
const preset = values[option.key] ?? option.default ?? "";
|
||||
let input: HTMLInputElement | HTMLSelectElement;
|
||||
if (option.input === "select") {
|
||||
// 필수 선택지는 첫 항목을 슬쩍 고른 것처럼 두지 않는다 — 재료·형식은 설계자가
|
||||
// 고르는 값이라, 안 고른 상태를 빈칸으로 드러낸다(2026-08-16 크로스체크 지적 2).
|
||||
// 빈("선택하세요") 항목은 두지 않는다 — 첫 항목이 곧 기본값이고, 기본값은
|
||||
// 구조물별로 레지스트리에서 지정한다(2026-08-17 사용자 지시 1).
|
||||
const choices = option.choices.map((choice) => [choice, choice] as [string, string]);
|
||||
input = select(option.required ? [["", "선택하세요"], ...choices] : choices);
|
||||
input.value = String(preset || (option.required ? "" : (option.choices[0] ?? "")));
|
||||
input = select(choices);
|
||||
input.value = String(preset || (option.choices[0] ?? ""));
|
||||
} else if (option.input === "number") {
|
||||
input = numberInput("0.1", "0");
|
||||
input.value = String(preset ?? "");
|
||||
@@ -323,12 +329,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
function labelOfPipe(pipe: PipeFacilityItem): string {
|
||||
const type = typeMap().get(pipe.facility);
|
||||
const name = type?.name ?? pipe.facility;
|
||||
const step = interval();
|
||||
const position =
|
||||
pipe.start_m !== undefined && pipe.end_m !== undefined && pipe.end_m > pipe.start_m
|
||||
? `${formatStation(pipe.start_m, step)}~${formatStation(pipe.end_m, step)}`
|
||||
: formatStation(pipe.chainage_m, step);
|
||||
return `${position} · ${name}`;
|
||||
// 계곡 통과 시설은 점형 — 기준 측점 하나로 표기한다(2026-08-17 지시 2).
|
||||
return `${formatStation(pipe.chainage_m, interval())} · ${name}`;
|
||||
}
|
||||
|
||||
function renderList(): void {
|
||||
@@ -407,6 +409,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
offsetField.value = String(target.offset_m ?? 0);
|
||||
memoField.value = target.memo ?? "";
|
||||
renderOptionFields(target.options);
|
||||
syncFacilityForm(target.options);
|
||||
} else {
|
||||
anchorFields.write(null, step);
|
||||
startFields.write(null, step);
|
||||
@@ -414,8 +417,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
offsetField.value = "0";
|
||||
memoField.value = "";
|
||||
renderOptionFields();
|
||||
syncFacilityForm();
|
||||
}
|
||||
syncFacilityForm();
|
||||
[anchorFields, startFields, endFields].forEach((fields) => fields.clearInvalid());
|
||||
syncPlacementFields();
|
||||
syncButtons();
|
||||
@@ -436,10 +439,11 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
syncTypeOptions(pipe.facility);
|
||||
}
|
||||
anchorFields.write(pipe.chainage_m, step);
|
||||
startFields.write(pipe.start_m ?? null, step);
|
||||
endFields.write(pipe.end_m ?? null, step);
|
||||
// 계곡 통과 시설은 점형 — 시작·종료 칸은 감춰지므로 비워 둔다.
|
||||
startFields.write(null, step);
|
||||
endFields.write(null, step);
|
||||
memoField.value = "";
|
||||
syncFacilityForm(pipe.options ?? {}, pipe.source);
|
||||
syncFacilityForm(pipe.options ?? {});
|
||||
[anchorFields, startFields, endFields].forEach((fields) => fields.clearInvalid());
|
||||
syncPlacementFields();
|
||||
syncButtons();
|
||||
@@ -448,6 +452,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
}
|
||||
|
||||
function readOptions(): Record<string, string | number> {
|
||||
// 서브폼이 담당하는 타입(독립 기슭막이)은 서브폼이 값을 읽는다.
|
||||
if (facilityFormKind(currentType())) return facilityOptions.readOptions();
|
||||
const values: Record<string, string | number> = {};
|
||||
optionInputs.forEach((input) => {
|
||||
// 빈 칸은 아예 넣지 않는다 — 빈 숫자를 0으로 저장하면 "0으로 확정"과 구분이 안 된다.
|
||||
@@ -475,13 +481,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
anchorFields.station.focus();
|
||||
return;
|
||||
}
|
||||
// 배수관 등 계곡 통과 시설은 점형 — 기준 측점 하나뿐이다(2026-08-17 지시 2).
|
||||
const attributes: FacilityAttributes = { facility: type.type_id as PipeFacility };
|
||||
const start = startFields.read(step, false);
|
||||
const end = endFields.read(step, false);
|
||||
if (start !== null && end !== null && end > start) {
|
||||
attributes.start_m = start;
|
||||
attributes.end_m = end;
|
||||
}
|
||||
const options = facilityOptions.readOptions();
|
||||
if (Object.keys(options).length) attributes.options = options;
|
||||
if (selectedPipeChainage !== null) {
|
||||
@@ -565,6 +566,10 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu
|
||||
typeSelect.addEventListener("change", () => {
|
||||
syncPlacementFields();
|
||||
renderOptionFields();
|
||||
// 배수관·BOX암거·세월교·독립 기슭막이는 부속 옵션 서브폼이 따라 열려야 한다 —
|
||||
// 수동 추가든 자동 배치 지점 편집이든 같은 옵션을 받는다(2026-08-17 사용자 지시:
|
||||
// 자동은 통수단면으로 자리를 잡아 준 것일 뿐 같은 구조물이다).
|
||||
syncFacilityForm();
|
||||
if (editingId) {
|
||||
editingId = null;
|
||||
syncButtons();
|
||||
|
||||
Reference in New Issue
Block a user