Merge remote-tracking branch 'origin/sub_laptop_3' into sub_laptop_1
충돌 둘(Structures_List · Panel_Types)은 양쪽을 모두 살려 풂 — 측점 글은 공용 stationLabel · 집계 항목 읽기 · isB05Option · SummaryItem 을 한 벌로, 쓰지 않게 된 isB05Option 가져오기는 뺌.
This commit is contained in:
@@ -14,7 +14,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
isB05Option,
|
||||
structureAnchorM,
|
||||
type StructureInstance,
|
||||
type StructuresSectionOptions,
|
||||
@@ -23,7 +22,9 @@ import {
|
||||
} from "./B05_Profile_Api_Structures";
|
||||
import type { PipeFacility } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import { type FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility";
|
||||
import { field, optionControl, suggestButton } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { field } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { renderOptionFields as renderOptionFieldsInto } from "./B05_Profile_UI_Structures_Panel_Options";
|
||||
import { openItemPicker } from "./B05_Profile_UI_Structures_Summary_Picker";
|
||||
import { buildStructuresForm } from "./B05_Profile_UI_Structures_Form";
|
||||
import {
|
||||
bindStructuresEvents,
|
||||
@@ -85,6 +86,7 @@ export function createStructuresSection(
|
||||
removeButton,
|
||||
resetButton,
|
||||
list,
|
||||
summary,
|
||||
} = form;
|
||||
|
||||
/** 이 타입이 서브폼(계곡 통과 시설 부속)을 쓰는지 — 쓰면 레지스트리 옵션 칸 대신
|
||||
@@ -166,6 +168,18 @@ export function createStructuresSection(
|
||||
read: () => string | number;
|
||||
isEmpty: () => boolean;
|
||||
}> = [];
|
||||
const optionContext = {
|
||||
optionInputs,
|
||||
optionRow,
|
||||
includeDetail: sectionOptions.includeDetail === true,
|
||||
currentType: () => currentType(),
|
||||
facilityFormKind: (type: StructureType | null) => facilityFormKind(type),
|
||||
liveCommit: () => liveCommit(),
|
||||
syncOptionLock: () => syncOptionLock(),
|
||||
syncRangeDisplay: () => syncRangeDisplay(),
|
||||
syncSplitInputs: (source: "length" | "before_m" | "after_m", normalizeSource: boolean) =>
|
||||
syncSplitInputs(source, normalizeSource),
|
||||
};
|
||||
|
||||
function typeMap(): Map<string, StructureType> {
|
||||
return new Map(types.map((type) => [type.type_id, type]));
|
||||
@@ -192,6 +206,7 @@ export function createStructuresSection(
|
||||
!!type && !managed && type.placement === "interval" && !hasComputedRange(type);
|
||||
positionRow.hidden = !type;
|
||||
positionDivider.hidden = !type;
|
||||
summary.root.hidden = !type;
|
||||
startFields.wrap.hidden = !isInterval;
|
||||
endFields.wrap.hidden = !isInterval;
|
||||
primary.disabled = !type;
|
||||
@@ -280,85 +295,9 @@ export function createStructuresSection(
|
||||
syncRangeDisplay();
|
||||
}
|
||||
|
||||
/** 타입의 옵션 스키마대로 입력 칸을 다시 그린다. 상세(detail)는 `includeDetail` 인 화면
|
||||
* (B06/B07)에서만 — 안 받으면 뒷길이·돌규격·형식이 비어 **수량이 갈래를 못 고른다**. */
|
||||
/** 타입의 옵션 스키마대로 입력 칸을 다시 그린다 — 그리는 규칙은 `_Structures_Panel_Options`. */
|
||||
function renderOptionFields(values: Record<string, string | number> = {}): void {
|
||||
const type = currentType();
|
||||
// 여기도 배열을 갈아끼우지 않는다 — 조각들이 참조로 받아 두므로 새 배열로
|
||||
// 바꾸면 옛 칸 목록을 읽어 저장값이 어긋난다(2026-09-04).
|
||||
optionInputs.length = 0;
|
||||
optionRow.replaceChildren();
|
||||
// 서브폼이 담당하는 타입(계곡 통과 시설·독립 기슭막이)은 여기서 그리지 않는다.
|
||||
const all = sectionOptions.includeDetail === true;
|
||||
const visible =
|
||||
type && !facilityFormKind(type) ? type.options.filter((o) => all || isB05Option(o)) : [];
|
||||
if (!visible.length) {
|
||||
optionRow.hidden = true;
|
||||
syncOptionLock();
|
||||
return;
|
||||
}
|
||||
optionRow.hidden = false;
|
||||
const suggestions: Array<{ input: HTMLInputElement | HTMLSelectElement; suggested: string }> =
|
||||
[];
|
||||
visible.forEach((option) => {
|
||||
// ⭐ 저장된 값만 칸에 · 등록부 기본값은 제안(회색 글씨·빈 보기 이름)으로만 — 고르기 칸은
|
||||
// 첫 보기를 미리 안 고름(2026-09-14 브레인 판정 ①②, `optionControl` 주석).
|
||||
const input = optionControl(option, values[option.key]);
|
||||
suggestions.push({ input, suggested: String(option.default ?? "") });
|
||||
const label = `${option.unit ? `${option.label} (${option.unit})` : option.label}${option.not_in_table ? " · 표에 안 쓰임" : ""}`;
|
||||
// `enabled:false` 는 칸을 남기고 잠그기만 한다 — 값은 기본값이 그대로 저장된다
|
||||
// (고를 수 없는 칸이라 제안이 아니라 프로그램 값).
|
||||
const locked = option.enabled === false;
|
||||
if (locked && !input.value) input.value = String(option.default ?? "");
|
||||
if (locked) {
|
||||
input.disabled = true;
|
||||
input.classList.add("is-locked");
|
||||
input.title = "지금은 고를 수 없는 항목입니다.";
|
||||
}
|
||||
optionRow.append(field(label, input, option.default_basis));
|
||||
input.addEventListener("change", () => liveCommit());
|
||||
optionInputs.push({
|
||||
key: option.key,
|
||||
required: !locked && !!option.required,
|
||||
input,
|
||||
read: () => (option.input === "number" ? Number(input.value) || 0 : input.value),
|
||||
isEmpty: () => input.value.trim() === "",
|
||||
});
|
||||
// 전면 기울기는 품셈 표준경사 표(0.20~0.50) 밖 값도 받는다 — 실무 도면이 S0.7·S0.8 을
|
||||
// 쓴다(2026-09-09 실무 DWG 확인). **막지 않고 알리기만** 한다: 막으면 실물이 안 들어간다.
|
||||
if (option.key === "face_slope_ratio") {
|
||||
const warn = (): void => {
|
||||
const value = Number(input.value);
|
||||
const outside =
|
||||
input.value.trim() !== "" && Number.isFinite(value) && (value < 0.2 || value > 0.5);
|
||||
input.title = outside
|
||||
? "품셈 표준경사 표 범위(0.20~0.50) 밖 값입니다 — 근거를 적어 두시기 바랍니다."
|
||||
: "비워 두면 품셈 표대로 자동 판정합니다.";
|
||||
input.classList.toggle("is-outside-standard", outside);
|
||||
};
|
||||
warn();
|
||||
input.addEventListener("input", warn);
|
||||
input.addEventListener("change", warn);
|
||||
}
|
||||
// 길이·기준측점 전/후가 바뀌면 서로 연동하고 측점 범위 표시가 즉시 따라온다
|
||||
// (범위 계산 타입, 2026-08-19). 전·후는 **나중에 고친 쪽**이 살아남고 반대쪽이
|
||||
// 길이 − 그 값으로 맞춰진다. 길이를 고치면 나중에 고쳤던 쪽을 지키며 재배분.
|
||||
if (option.key === "length_m" || option.key === "before_m" || option.key === "after_m") {
|
||||
const source = (option.key === "length_m" ? "length" : option.key) as
|
||||
"length" | "before_m" | "after_m";
|
||||
const refresh = (normalize: boolean): void => {
|
||||
input.classList.remove("is-invalid");
|
||||
syncSplitInputs(source, normalize);
|
||||
};
|
||||
input.addEventListener("input", () => refresh(false));
|
||||
input.addEventListener("change", () => refresh(true));
|
||||
}
|
||||
});
|
||||
if (suggestions.some((entry) => entry.suggested)) {
|
||||
optionRow.append(suggestButton(suggestions));
|
||||
}
|
||||
syncOptionLock();
|
||||
syncRangeDisplay();
|
||||
renderOptionFieldsInto(optionContext, values);
|
||||
}
|
||||
|
||||
/** 고른 구조물군의 타입을 종류 목록에 채운다 — A군은 계곡 통과 시설(managed_by)도
|
||||
@@ -527,8 +466,25 @@ export function createStructuresSection(
|
||||
setPositionConfirmed: (value) => {
|
||||
positionConfirmed = value;
|
||||
},
|
||||
summary: () => summary.get(),
|
||||
setSummary: (value) => summary.set(value),
|
||||
};
|
||||
bindStructuresEvents(panelContext);
|
||||
// 집계 항목 — [고르기]는 21-5 모달, 결과는 폼 값으로만 두고 고치는 중이면 곧바로 반영한다
|
||||
// (새 항목은 종전대로 [추가]가 확정). 안 고른 채 닫으면 그대로.
|
||||
summary.bind({
|
||||
pick: () => {
|
||||
void openItemPicker({ current: summary.get() }).then((result) => {
|
||||
if (!result) return;
|
||||
summary.set("item" in result ? result.item : null);
|
||||
liveCommit();
|
||||
});
|
||||
},
|
||||
clear: () => {
|
||||
summary.set(null);
|
||||
liveCommit();
|
||||
},
|
||||
});
|
||||
body.insertBefore(field("메모", memoField), actions);
|
||||
syncButtons();
|
||||
renderList();
|
||||
|
||||
Reference in New Issue
Block a user