diff --git a/A00_Common/design/design_structure_form.ts b/A00_Common/design/design_structure_form.ts index 8ced4037d..b46ea9d03 100644 --- a/A00_Common/design/design_structure_form.ts +++ b/A00_Common/design/design_structure_form.ts @@ -52,8 +52,6 @@ export interface SpecLinks { export interface SpecFormHooks { /** 자동 항목 값 글(빈 글 = 「—」). */ auto?: (c: SpecContainer, item: SpecItem, state: SpecState) => string; - /** 숫자 칸 빈 칸 안내. */ - hint?: (c: SpecContainer, item: SpecItem, state: SpecState) => string; onChange?: (c: SpecContainer, item: SpecItem) => void; /** 선택 컨테이너 개수를 밖에서 정함(54-1 배관 흙막이 = 추가 기슭막이(단)) — 수면 그만큼 늘 보임 · * [추가] · [빼기] 없음 · 줄면 끝에서부터 가림(값은 남음 · 54-10) · null = 모름(저장된 것 그대로) · @@ -61,7 +59,7 @@ export interface SpecFormHooks { count?: (c: SpecContainer) => number | null | undefined; /** 컨테이너 칸 끝에 덧붙이는 칸(54-27 배관 유출 「추가 흙막이(단)」 · 값은 저장 옵션 밖). */ fields?: (c: SpecContainer, values: Record) => HTMLElement[]; - /** 그림이 쓰는 값(54-13) — 고르기 칸이 비었거나 자동 규칙일 때 그 값 · 꼬리(「기본」 · 「자동」) · + /** 그림이 쓰는 값(54-13) — 자동 규칙일 때 그 값 · 꼬리(「자동」) · * null = 없음. 보여 주기만(값 · 저장은 사용자가 고를 때만). `values` = 보이는 값(위 칸 반영). */ shown?: ( c: SpecContainer, @@ -79,6 +77,8 @@ export interface SpecForm { options: Record, names?: SpecNames, groupNames?: Record, + /** 정의 기본값(저장 키 → 값) — 필수 상자 빈 고르기 칸이 받는 값(54-43). */ + defaults?: Record, ) => void; read: () => Record; state: () => SpecState; @@ -100,6 +100,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = { let state: SpecState = {}; let names: SpecNames = {}; let groupNames: Record = {}; + let defaults: Record = {}; const recent = new Map(); const slots = new Map(); const rows: Array<{ key: string; set: (item: SummaryItem | null) => void }> = []; @@ -128,9 +129,10 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = { const drawn = choices.find((ch) => ch.value === now); // 그림이 쓰는 값은 빈 보기 자리에 꼬리를 달아 보임 — 고른 값과 구별(54-13) const name = drawn ? choiceLabel(c, item, drawn, names) : now; - const blank = !tag ? "선택 안 함" : name ? `${name} (${tag})` : tag; + const blank = !tag ? "" : name ? `${name} (${tag})` : tag; + // 필수 상자 = 빈 보기 없음(54-43) · 선택 상자 빈 보기 = 빈 글 select.replaceChildren( - new Option(blank, ""), + ...(c.required && !tag ? [] : [new Option(blank, "")]), ...choices.map((ch) => new Option(choiceLabel(c, item, ch, names), ch.value)), ); // 선택지 밖 옛 값은 그 값만 남겨 보임(지운 값을 몰래 바꾸지 않음) @@ -145,8 +147,8 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = { input.value = values[item.id] ?? ""; return input; } - // 기본값은 새 상자 칸에 실제 값으로만(54-39) — 빈 칸 「(기본)」 안내 없음 - const input = numberInput(String(item.step ?? 0.1), "0", hooks.hint?.(c, item, state) ?? ""); + // 빈 칸 = 그냥 빈 칸(54-43 · 자리표시 글 없음) + const input = numberInput(String(item.step ?? 0.1), "0"); if (item.kind === "auto") { // 보여 주는 칸(54-1) — 손 입력 칸 아님 const shown = document.createElement("output"); @@ -203,6 +205,16 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = { const box = group(c.required ? title : `${title} ${index + 1}`); box.root.dataset.specContainer = c.id; box.root.dataset.specIndex = String(index); // 칸을 고친 뒤 다시 그려도 몇째 상자인지(54-30) + // 필수 상자 빈 고르기 칸 = 정의 기본값(없으면 첫 보기)을 실제 값으로 — 저장도 그 값(54-43) + if (c.required) + for (const item of c.items) { + if (item.kind !== "select" || values[item.id] || (item.by && !branchOf(item, values))) + continue; + const choices = choicesOf(item, values); + const key = branchOf(item, values)?.key; + const wanted = String((key ? defaults[key] : undefined) ?? item.default ?? ""); + values[item.id] = (choices.find((ch) => ch.value === wanted) ?? choices[0])?.value ?? ""; + } // 보이는 값 = 고른 값 + 그림이 쓰는 값(54-13) · 위 칸이 다른 갈래를 보이면 아래 칸은 빈 채로 const view: Instance = { ...values }; const tags = new Map(); @@ -320,8 +332,9 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = { return { root, - write(next, options, nextNames = {}, nextGroups = {}) { + write(next, options, nextNames = {}, nextGroups = {}, nextDefaults = {}) { spec = next; + defaults = nextDefaults; names = nextNames; groupNames = nextGroups; state = readState(next, options); diff --git a/B05_Profile/B05_Profile_UI_Drainage_Facility.ts b/B05_Profile/B05_Profile_UI_Drainage_Facility.ts index 31a987769..9c7acea7b 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Facility.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Facility.ts @@ -41,13 +41,13 @@ import { type InletStructureKind, labeled, numberInput, - optionalSelect, OUTLET_REVET_KEYS, stepper, WING_IN_KEYS, WING_OUT_KEYS, } from "./B05_Profile_UI_Drainage_Facility_Fields"; import { revetChoiceRow } from "./B05_Profile_UI_Drainage_Revet_Choice"; +import { optionDefaults } from "./B05_Profile_UI_Structures_Fields"; import { createSpecForm, type SpecLinks } from "../A00_Common/design/design_structure_form"; import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe"; import { @@ -57,7 +57,7 @@ import { boxOptionsIn, } from "../A00_Common/design/design_structure_box"; import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford"; -import { fordHeightHint, fordHeightM } from "../A00_Common/design/design_structure_ford_calc"; +import { fordHeightM } from "../A00_Common/design/design_structure_ford_calc"; import { pipeDrawn, pipeExtraCount, @@ -216,8 +216,8 @@ export interface FacilityOptionsForm { definition?: { spec_names?: Record; groups?: Array<{ id: string; name: string }>; - /** 옵션 정의 기본값 — 빈 고르기 칸이 그림 값을 보임(54-13). */ - options?: Array<{ key: string; default: unknown }>; + /** 옵션 정의 기본값 — 필수 상자 빈 고르기 칸이 받는 값(54-43). */ + options?: Array<{ key: string; default: string | number | null }>; } | null, ) => void; /** 배관 명세 컨테이너 「구조물 조합」 잇기 자리 — 폼이 패널 그룹 값에 이음(PLAN 52-1). */ @@ -289,7 +289,7 @@ export function createFacilityOptionsForm( return drawnMemo; }; // 유출 「추가 흙막이(단)」(54-27) — 값 = 횡단 조정 단 수(같은 저장 자리) · 흙막이 상자 수가 따라감 - const tiersInput = numberInput("1", "0", "횡단 설계 뒤"); + const tiersInput = numberInput("1", "0"); tiersInput.max = "9"; const tiersRow = labeled("추가 흙막이(단)", stepper(tiersInput, 1, 0)); tiersRow.dataset.specItem = "outlet.tiers"; @@ -378,10 +378,6 @@ export function createFacilityOptionsForm( const fordNeed = (): number | null => fordHeightM(fordFlow, Number(fordForm.state().ford?.[0]?.width ?? "")); const fordForm = createSpecForm(specLinks, { - hint: (c, item, state) => - c.id === "ford" && item.id === "height" - ? fordHeightHint(fordFlow, Number(state.ford?.[0]?.width ?? "")) - : "", onChange: (c, item) => { const need = c.id === "ford" ? fordNeed() : null; const height = fordForm.state().ford?.[0]?.height ?? ""; @@ -406,7 +402,9 @@ export function createFacilityOptionsForm( // 나눈다(2026-08-30 사용자). 설치 측이 한쪽이면 그 칸만 뜬다. // 좌·우가 저장 채널(유입/유출) 중 어느 쪽인지는 측점 지형이 정하므로 이름표만 // 바꿔 단다(`setRevetSideLabels`) — 값은 언제나 그 채널 키로 오간다. - const revetSide = optionalSelect("— 안 정함 —", ["양쪽", "좌", "우"]); + // 설치 측 = 필수 칸 — 빈 보기 없음(54-43 · 빈 옛 값 = 양쪽) + const revetSide = document.createElement("select"); + revetSide.replaceChildren(...["양쪽", "좌", "우"].map((v) => new Option(v, v))); const revetTopRow = grid(labeled("설치 측", revetSide, "side")); const revetInlet = createRevetSideGroup(INLET_REVET_KEYS); const revetOutlet = createRevetSideGroup(OUTLET_REVET_KEYS); @@ -582,6 +580,8 @@ export function createFacilityOptionsForm( // 받은 값만 칸에 — 없으면 「안 정함」(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7). const groups = Object.fromEntries((definition?.groups ?? []).map((g) => [g.id, g.name])); const names = definition?.spec_names ?? {}; + // 필수 상자 빈 고르기 칸이 받는 정의 기본값(54-43) — 옛 키는 열 때와 같은 길로 바꿈 + const defaults = optionDefaults(definition?.options ?? []); if (facility === "pipe") { // 정의 파일의 옛 이름 「흙막이 추가」 — 명세 이름 「흙막이」가 앞섬(54-1) const { soil_add: _old, ...pipeNames } = names; @@ -591,21 +591,31 @@ export function createFacilityOptionsForm( !["revet", "I", "L", "U"].includes(String(kind ?? "")) ? { structure: kind } : null; - pipeForm.write(PIPE_SPEC, options, pipeNames, groups); + pipeForm.write(PIPE_SPEC, options, pipeNames, groups, defaults); } if (facility === "box_culvert") { // 본체 규격은 저장 안 함 — 폭 · 높이로 되찾음 · 옛 설치 「있음」 = 날개벽 - boxForm.write(BOX_SPEC, boxOptionsIn(options), names, groups); + boxForm.write(BOX_SPEC, boxOptionsIn(options), names, groups, boxOptionsIn(defaults)); for (const [id, value] of Object.entries(bodyOf(options))) boxForm.setValue("body", id, value); } if (facility === "ford_bridge") - fordForm.write(FORD_SPEC, withLegacyGuards(options), names, groups); - if (facility === "ford_pavement") paveForm.write(FORD_PAVEMENT_SPEC, options, names, groups); + fordForm.write( + FORD_SPEC, + withLegacyGuards(options), + names, + groups, + withLegacyGuards(defaults), + ); + if (facility === "ford_pavement") + paveForm.write(FORD_PAVEMENT_SPEC, options, names, groups, defaults); wingInFields.write(options); wingOutFields.write(options); - revetSide.value = text("side"); + revetSide.value = text("side") || "양쪽"; const spread = legacyRevetOptions(options); + // 빈 형태 = 정의 기본값(54-43 · 필수 칸) + for (const key of [INLET_REVET_KEYS.form, OUTLET_REVET_KEYS.form]) + if (!spread[key] && defaults.form) spread[key] = defaults.form; revetInlet.fields.write(spread); revetOutlet.fields.write(spread); syncVisibility(); diff --git a/B05_Profile/B05_Profile_UI_Drainage_Facility_Fields.ts b/B05_Profile/B05_Profile_UI_Drainage_Facility_Fields.ts index 42654c90d..0a326b5b7 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Facility_Fields.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Facility_Fields.ts @@ -159,16 +159,13 @@ export function putNumber(target: Record, key: string, if (Number.isFinite(value) && value > 0) target[key] = Number(value.toFixed(1)); } -/** 선택지 셀렉트 — **맨 앞에 빈 보기(「안 정함」)** 를 둠. 첫 보기를 미리 고르지 않음 +/** 선택지 셀렉트 — **맨 앞에 빈 보기(빈 글 · 54-43)** 를 둠. 첫 보기를 미리 고르지 않음 * (2026-09-14 브레인 판정 · 옛 2026-08-17 「빈 항목 두지 않음」 걷음) — 고른 적 없는 첫 값이 저장돼 * 확정으로 굳지 않게. 기본값이 있는 칸은 부르는 쪽이 값을 넣음(그것은 따로 차례). */ -export function optionalSelect( - blankLabel: string, - choices: ReadonlyArray, -): HTMLSelectElement { +export function optionalSelect(choices: ReadonlyArray): HTMLSelectElement { const element = document.createElement("select"); element.replaceChildren( - new Option(blankLabel, ""), + new Option("", ""), ...choices.map((choice) => new Option(choice, choice)), ); return element; @@ -211,13 +208,6 @@ export interface RevetmentKeys { after?: string; } -/** 높이 칸 안내 — 관 벽은 비우면 관경 기준 최소 높이(엔진 `pipe_wall_min_height_m` · 함수로 받아 - * 볼 때마다 셈) · 빈 글이면 「설계자 입력」. 형태 · 길이 · 전후 기본값은 등록부가 시설이 생길 때 - * 칸에 실제 값으로 넣음(PLAN 46-7 · 제안값 단추 없음). */ -export interface RevetmentDefaults { - height: string | (() => string); -} - export const INLET_REVET_KEYS: RevetmentKeys = { form: "inlet_revet_form", length: "inlet_revet_length_m", @@ -249,7 +239,7 @@ export function createRevetSideGroup(keys: RevetmentKeys): RevetSideGroup { const kind = document.createElement("select"); kind.replaceChildren(new Option("기슭막이", "기슭막이")); // 높이는 기본값 없음 — 설계자 입력(계획홍수위 + 여유고 · 2026-09-14 브레인 판정). - const fields = createRevetmentFields(keys, { height: "" }); + const fields = createRevetmentFields(keys); // 치수 근거 글(사방기술교본)은 [?] 설명서 `구조물_기슭막이.md` 로 옮김(54-2) const slot = document.createElement("div"); slot.className = "b05-structure__adjust-slot"; @@ -282,24 +272,13 @@ export interface RevetmentFields { * (2026-08-17 사용자 지시 9·11). 재질과 표면처리는 "형태" 한 축(지시 1). 바닥 * 보호공(돌붙임) 한 벌은 뺐다 — 기슭막이는 사면 공정이라 그 아래 돌붙임이 붙을 * 자리가 아니다(2026-08-19 사용자 지시 1). */ -export function createRevetmentFields( - keys: RevetmentKeys, - defaults: RevetmentDefaults, -): RevetmentFields { - const wallFloor = (): string => - typeof defaults.height === "function" ? defaults.height() : defaults.height; +export function createRevetmentFields(keys: RevetmentKeys): RevetmentFields { + // 형태 = 필수 칸 — 빈 보기 없음(54-43 · 값은 부르는 쪽이 정의 기본값으로 채움) const form = document.createElement("select"); - form.replaceChildren( - new Option("— 안 정함 —", ""), - ...REVET_FORMS.map((value) => new Option(value, value)), - ); + form.replaceChildren(...REVET_FORMS.map((value) => new Option(value, value))); const length = keys.length ? numberInput("0.1", "0") : null; - // 관 벽은 비우면 관경 기준 최소 높이로 섬(엔진) · 독립 기슭막이 높이는 「설계자 입력」. - const heightHint = (): string => - wallFloor() ? `비우면 관경 기준 ${wallFloor()}` : "설계자 입력"; - const height = numberInput("0.1", "0", heightHint()); - // 관 벽 기준 높이는 관경을 따라감 — 칸을 볼 때마다 다시 셈. - height.addEventListener("focus", () => (height.placeholder = heightHint())); + // 빈 칸 = 그냥 빈 칸(54-43 · 자리표시 글 없음) + const height = numberInput("0.1", "0"); // 기준측점 전/후(2026-08-23 사용자) — 구조물 패널(D군)과 같은 연동: // 전 + 후 = 길이, 나중에 고친 쪽이 살아남고 반대쪽이 재배분된다. const before = keys.before && length ? numberInput("0.1", "0") : null; @@ -342,9 +321,9 @@ export function createRevetmentFields( const text = (key: string): string => options[key] !== undefined ? String(options[key]) : ""; form.value = text(keys.form); + if (form.selectedIndex < 0) form.selectedIndex = 0; if (length && keys.length) length.value = text(keys.length); height.value = text(keys.height); - height.placeholder = heightHint(); if (before && keys.before) before.value = text(keys.before); if (after && keys.after) after.value = text(keys.after); syncSplit("length"); @@ -476,7 +455,7 @@ interface WingFields { * 설치 "없음"이면 제원 칸을 접는다(기슭막이 보호공 치수와 같은 패턴). * 기본 제원(있음·1·2·45, 사용자 확정)은 등록부 기본값 — 시설이 생길 때 칸에 실제 값(PLAN 46-7). */ export function createWingFields(title: string, keys: WingKeys): WingFields { - const install = optionalSelect("— 안 정함 —", ["있음", "없음"]); + const install = optionalSelect(["있음", "없음"]); const height = numberInput("0.1", "0"); const length = numberInput("0.1", "0"); const angle = numberInput("1", "0"); diff --git a/B05_Profile/B05_Profile_UI_Structures_Fields.ts b/B05_Profile/B05_Profile_UI_Structures_Fields.ts index 5e4c93d17..262ea9815 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Fields.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Fields.ts @@ -54,7 +54,8 @@ interface OptionShape { /** * 등록부 옵션 입력 한 칸 — **받은 값만 칸에 넣음**. 새 구조물은 부르는 쪽이 등록부 기본값을 값으로 * 실어 줌(PLAN 46-7 · 제안값 단추 없음) · 옛 기록의 빈 칸은 빈 칸 그대로(미확정). - * 고르기 칸은 늘 빈 보기를 둠 — 첫 보기를 미리 고르지 않음. + * 필수 · 기본값 있는 고르기 칸 = 빈 보기 없음(54-43 · 빈 옛 값은 기본값 · 없으면 첫 보기) · + * 그 밖 고르기 칸 = 빈 글 빈 보기. */ export function optionControl( option: OptionShape, @@ -66,15 +67,16 @@ export function optionControl( // 툴팁 — 표가 아직 안 읽는 칸이면 그 사유가 먼저(넣어도 안 바뀜을 알아야 함), 아니면 비울 때의 뜻. const tip = option.not_in_table ?? option.empty_means ?? ""; if (option.input === "select") { - // 빈 보기 = 값 없음(54-39) — 기본값은 새 구조물 칸에 실제 값으로만(「(기본)」 꼬리 없음) - const blank = option.required ? "— 선택 —" : "선택 안 함"; + const fallback = option.default === null || option.default === "" ? "" : String(option.default); + const must = !!option.required || fallback !== ""; // 정의에서 뺀 선택지(48)를 고른 옛 값 — 그 값만 남겨 보임(값을 잃지 않음) const kept = value && !option.choices.includes(value) ? [value] : []; const element = select([ - ["", blank], + ...(must ? [] : [["", ""] as [string, string]]), ...[...option.choices, ...kept].map((c) => [c, label(c)] as [string, string]), ]); - element.value = value; + element.value = value || fallback; + if (element.selectedIndex < 0) element.selectedIndex = 0; if (tip) element.title = tip; return element; } @@ -82,7 +84,6 @@ export function optionControl( option.input === "number" ? numberInput("0.1", "0") : document.createElement("input"); if (option.input !== "number") element.type = "text"; element.value = value; - element.placeholder = option.required ? "필수 입력" : ""; if (tip) element.title = tip; const limit = option.warn_above; if (typeof limit === "number") { diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel.ts b/B05_Profile/B05_Profile_UI_Structures_Panel.ts index 9fe5b52d9..6501870dd 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel.ts @@ -239,9 +239,7 @@ export function createStructuresSection( root.dataset.helpTopic = type ? `구조물_${type.name}` : ""; // 정의가 기준 측점을 끈 종류(48-3) — 기준 측점이 곧 위치인 종류(점형 · 범위 계산)는 늘 보임 anchorFields.wrap.hidden = isInterval && !showsAnchor(type!); - anchorFields.wrap.querySelector("span")!.textContent = isInterval - ? "기준 측점 (비우면 시작)" - : "기준 측점"; + anchorFields.wrap.querySelector("span")!.textContent = "기준 측점"; // 빈 칸 안내 없음(54-43) syncRangeDisplay(); } diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel_Options.ts b/B05_Profile/B05_Profile_UI_Structures_Panel_Options.ts index 94eefbd05..f10f307d8 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel_Options.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel_Options.ts @@ -250,14 +250,16 @@ function syncChoices( choices: string[], label: (choice: string) => string, ): void { - const now = [...element.children].slice(1).map((o) => (o as HTMLOptionElement).value); + // 빈 보기는 선택 칸에만(54-43 · 필수 칸은 없음) + const blank = [...element.options].filter((o) => o.value === "").slice(0, 1); + const now = [...element.options].filter((o) => o.value !== "").map((o) => o.value); const keep = element.value && !choices.includes(element.value) ? [element.value] : []; const want = [...choices, ...keep]; if (now.length === want.length && now.every((v, i) => v === want[i])) return; const value = element.value; - const blank = element.children[0]; - element.replaceChildren(blank, ...want.map((c) => new Option(label(c), c))); + element.replaceChildren(...blank, ...want.map((c) => new Option(label(c), c))); element.value = value; + if (element.selectedIndex < 0) element.selectedIndex = 0; } /** 「지난 정의 값」 상자(48-3) — 지금 정의에 없는 키의 값을 읽기만 칸으로 보임(오류 없음 · 저장은 diff --git a/B05_Profile/B05_Profile_UI_Structures_Spec.ts b/B05_Profile/B05_Profile_UI_Structures_Spec.ts index d63ecab14..e03819c49 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Spec.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Spec.ts @@ -16,6 +16,7 @@ import { } from "../A00_Common/design/design_structure_form"; import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe"; import { specKeys, type StructureSpec } from "../A00_Common/design/design_structure_spec"; +import { optionDefaults } from "./B05_Profile_UI_Structures_Fields"; const forms = new WeakMap(); @@ -63,7 +64,7 @@ export function paintStructureSpec( form.root.hidden = !spec; if (!spec) return; const groups = Object.fromEntries((type?.groups ?? []).map((g) => [g.id, g.name])); - form.write(spec, values, type?.spec_names ?? {}, groups); + form.write(spec, values, type?.spec_names ?? {}, groups, optionDefaults(type?.options ?? [])); } /** 명세 폼 값(저장 키 그대로) — 명세 없는 종류면 빈 묶음. */ diff --git a/docs/raw/사용자요구/2026-10-05_수정요구.md b/docs/raw/사용자요구/2026-10-05_수정요구.md index 32abe1f92..794ffbed8 100644 --- a/docs/raw/사용자요구/2026-10-05_수정요구.md +++ b/docs/raw/사용자요구/2026-10-05_수정요구.md @@ -289,3 +289,9 @@ B05에서 3D 영역의 상단 버튼들 중 원근으로, 직교로 버튼의 (10-6 사용자) 빈칸은 그냥 빈칸이야. 별도의 안내는 필요없어. 기본 값 XX 라 흐린 글씨로 작성하지 말고 기본값을 사용자가 넣은 것 처럼. 기본값을 입력하면 된다는 말이야. 그리고 필수로 집수정이낭 흙막이처럼 필수로 필요한 정보에는 선택안함 선택지 자체가 없어야해. + +(10-6 사용자) +기존 프로젝트에서 존재하는 예전 데이터 때문에 검증이 어려운것같은데 쉽게 데이터를 초기화하고 신규로직으로 적용되게 하는 방법은 없어? +B 방향이 맞겠네. 새 프로젝트를 만드는건 문제없어보여. 이때 개별 워크트리들이 검증할수 있는 프로젝트를 각각지정해주는게 좋겠어. 그리고 추가로 엄상준과 박동철 프로젝트도 같이 만들어줘. 같은회사 검증자 들이야. 이 사람들도 본인의 프로젝트로 검증할거야. +이전 프로젝트는 그냥 삭제해 +전부 다 손대도 상관없음 프로젝트 전역 초기 diff --git a/resources/tester/test_b05_revetment_sabang_basis.py b/resources/tester/test_b05_revetment_sabang_basis.py index e48c6ad13..b2a886960 100644 --- a/resources/tester/test_b05_revetment_sabang_basis.py +++ b/resources/tester/test_b05_revetment_sabang_basis.py @@ -42,6 +42,12 @@ def test_TS_가_쓰는_칸이_파일에_있다() -> None: def test_B05_높이_칸은_제안_없이_설계자_입력() -> None: - # 독립 기슭막이 높이 = 기본값 없음 · 「설계자 입력」 안내(제안값 단추 걷음 · PLAN 46-7) - assert 'createRevetmentFields(keys, { height: "" })' in FIELDS and '"설계자 입력"' in FIELDS + # 독립 기슭막이 높이 = 기본값 없음 · 빈 칸 그대로(54-43 · 자리표시 글 없음) + assert ( + "createRevetmentFields(keys)" in FIELDS + and "placeholder" + not in FIELDS.split("createRevetmentFields(keys: RevetmentKeys)")[1].split( + "createBasinSplitFields" + )[0] + ) assert "기슭막이 높이 없음 — 근입만 그림" in CHROME diff --git a/resources/tester/ui_checks/54-43_빈칸_필수칸.js b/resources/tester/ui_checks/54-43_빈칸_필수칸.js new file mode 100644 index 000000000..990881bec --- /dev/null +++ b/resources/tester/ui_checks/54-43_빈칸_필수칸.js @@ -0,0 +1,48 @@ +/* 54-43 — 구조물 배치 상자: 빈 칸 = 그냥 빈 칸 · 필수 칸 빈 보기 없음 · 빈 옛 값 = 기본값 + * 시작 화면: B05 종단설계(#/b05-profile) 또는 B06 횡단설계(#/b06-section) · 검증 프로젝트(sub5_검증) · 로그인 + * 부르는 법: 한 번 — 결과 `sessionStorage.getItem("__5443_out")` + * 목록의 구조물 전부를 차례로 열어 「구조물 배치」 상자 글 · 보기 글 · 자리표시 글을 셈 + * 쓰는 것: 없음(열기만 · 저장 없음) + * 기대 수치: words = 0 · requiredBlank = 0 · blankText = 0 · requiredEmpty = 0 · optionalBlank > 0(선택 칸 빈 보기 그대로) + */ +(async () => { + const w = (ms) => new Promise((r) => setTimeout(r, ms)); + const txt = (el) => el.innerText.replace(/\s+/g, " ").trim(); + const WORDS = /선택 안 함|안 정함|비우면|기본/; + const OPTIONAL = new Set(["soil_add", "chute_add", "chute"]); + const out = { rows: 0, words: [], requiredBlank: [], blankText: [], requiredEmpty: [], optionalBlank: 0, seen: {} }; + for (let i = 0; i < 50 && !document.querySelector(".b05-route__irregular-list li"); i++) await w(200); + const rows = [...document.querySelectorAll(".b05-route__irregular-list li")]; + for (const row of rows) { + const head = txt(row).slice(0, 24); + row.click(); + await w(1200); + const panel = document.querySelector(".b05-structure-section"); + if (!panel) continue; + out.rows += 1; + const shown = (el) => !!el.offsetParent; + // 보이는 글 · 고르기 보기 글 · 자리표시 글 + const texts = [ + txt(panel), + ...[...panel.querySelectorAll("select")].filter(shown).flatMap((s) => [...s.options].map((o) => o.text)), + ...[...panel.querySelectorAll("input")].filter(shown).map((i) => i.placeholder), + ]; + for (const t of texts) { + const hit = t.match(WORDS); + if (hit) out.words.push(`${head}: …${t.slice(Math.max(0, hit.index - 12), hit.index + 14)}…`); + } + for (const sel of [...panel.querySelectorAll("select")].filter(shown)) { + const wrap = sel.closest("[data-spec-item]"); + const box = sel.closest("[data-spec-container]"); + const blank = [...sel.options].find((o) => o.value === ""); + const name = wrap?.dataset.specItem ?? sel.closest("[data-option-keys]")?.dataset.optionKeys ?? "?"; + out.seen[name] = (out.seen[name] ?? 0) + 1; + if (blank && blank.text !== "" && !wrap?.dataset.specShown) out.blankText.push(`${head}: ${name} 「${blank.text}」`); + if (box && !OPTIONAL.has(box.dataset.specContainer)) { + if (blank && !wrap?.dataset.specShown) out.requiredBlank.push(`${head}: ${name}`); + if (!sel.value && !wrap?.dataset.specShown) out.requiredEmpty.push(`${head}: ${name}`); + } else if (blank) out.optionalBlank += 1; + } + } + sessionStorage.setItem("__5443_out", JSON.stringify(out)); +})();