feat(B08): 거리이정표 등록 모달 항목 = 손 입력 + 분류별 제안 목록(244-14)
- 항목 칸 = 손 입력 + datalist 제안(거래처 = 견본 다섯 · 기준위치 = 기준점) · 목록 밖 글도 받음 - 분류 바꿈 = 제안만 바뀜 · 쓴 글 그대로 · 줄마다 제안 목록 따로 - 시험 test_244_14 (실제 rowField 를 node 로) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* 거리이정표 다이어그램 모달(PLAN 244-4 · 244-13 · 사용자 147번 5 · 8) — 페이지 이동 없이 창에서 보고 고침.
|
||||
*
|
||||
* 등록 모달(244-13) = 공사지 · 기준점 [+] · 거래처 · 기준점 상자 누름 같은 한 벌(`openPlaceModal`) — 더 큰 창 ·
|
||||
* 2열(분류(기준위치 · 거래처) · 항목(분류 따라 고르기 목록) · 거래처 이름 · 목적지) + 소재지(주소 찾기 ·
|
||||
* 2열(분류(기준위치 · 거래처) · 항목(손 입력 + 분류 따라 제안 · 244-14) · 거래처 이름 · 목적지) + 소재지(주소 찾기 ·
|
||||
* 다음(카카오) 지도에서 고르기 · `…_UI_Milestone_Place`) · 추가 = [등록] · 상자 = [고침] · [삭제]
|
||||
* 분류 기준위치 = 기준점(하나 · 있으면 고르기 막음 · 다이어그램 [기준점 +] 없앰) · 목적지 = 시점 고정
|
||||
* 같은 거래처 이름 = 그 경로에 표 줄만 · 방위 = 소재지 좌표로 자동 자리
|
||||
@@ -56,23 +56,24 @@ export function toastKm(text: string): void {
|
||||
if (text) showToast(`길찾기 — ${text}`, text.includes(" — ") ? "warning" : "success");
|
||||
}
|
||||
|
||||
/** 항목 ▾ · 목적지 ▾ 한 줄 — 목록 밖 옛 항목 글도 보임 */
|
||||
let listSeq = 0;
|
||||
|
||||
/** 항목(손 입력 + 제안 목록 · 244-14) · 목적지 ▾ 한 줄 — 목록 밖 글도 받음 · 분류 바꾸면 제안만 바뀜 */
|
||||
function rowField(r: MilestoneRow, kind: "route" | "base") {
|
||||
const item = createSelectField({ label: "항목", compact: true, options: [] });
|
||||
const item = createInputField({ label: "항목", compact: true });
|
||||
const list = el("datalist", { attrs: { id: `b08-milestone-items-${++listSeq}` } });
|
||||
item.input.setAttribute("list", list.id);
|
||||
item.input.value = r.item;
|
||||
item.root.append(list);
|
||||
const dest = createSelectField({ label: "목적지", compact: true, options: DEST_OPTIONS });
|
||||
const setKind = (k: "route" | "base"): void => {
|
||||
const list = [...ITEMS[k]];
|
||||
if (r.item && !list.includes(r.item)) list.unshift(r.item);
|
||||
const keep = list.includes(item.select.value) ? item.select.value : r.item || list[0];
|
||||
item.setOptions(
|
||||
list.map((t) => ({ value: t, text: t })),
|
||||
keep,
|
||||
);
|
||||
list.replaceChildren(...ITEMS[k].map((t) => el("option", { attrs: { value: t } })));
|
||||
item.input.placeholder = `예 ${ITEMS[k][0]}`;
|
||||
dest.root.style.visibility = k === "base" ? "hidden" : ""; // 기준점 → 시점 고정
|
||||
};
|
||||
setKind(kind);
|
||||
dest.select.value = r.dest;
|
||||
item.select.dataset.b08MilestoneAdd = "item";
|
||||
item.input.dataset.b08MilestoneAdd = "item";
|
||||
return { item, dest, note: r.note, setKind };
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ export function openPlaceModal(
|
||||
setKind(kind);
|
||||
const rowValues = (): MilestoneRow[] =>
|
||||
rows.map((r) => ({
|
||||
item: r.item.select.value,
|
||||
item: r.item.input.value.trim(),
|
||||
dest: r.dest.select.value as Dest,
|
||||
note: r.note,
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"""244-14 B08 거리이정표 [+] 모달 「항목」 = 손 입력 + 제안 목록(사용자 120-답 ③ 「항목: 수기 입력」).
|
||||
|
||||
거래처 제안 = 견본 다섯 · 기준위치 제안 = 기준점 · 목록 밖 글도 받음 · 분류 바꾸면 제안만 바뀜(쓴 글 그대로)
|
||||
— node 로 실제 rowField 를 돌림(가짜 DOM).
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MODAL = ROOT / "B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts"
|
||||
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
|
||||
|
||||
STUBS = """
|
||||
type N = any;
|
||||
const el = (tag: string, o: any = {}): N => {
|
||||
const n: N = { tag, children: [] as N[], attrs: { ...(o.attrs ?? {}) }, dataset: {}, style: {},
|
||||
value: "", placeholder: "",
|
||||
get id() { return n.attrs.id; },
|
||||
setAttribute(k: string, v: string) { n.attrs[k] = v; },
|
||||
append(...c: N[]) { n.children.push(...c); },
|
||||
replaceChildren(...c: N[]) { n.children = c; } };
|
||||
return n;
|
||||
};
|
||||
const createInputField = (o: any) => {
|
||||
const input = el("input");
|
||||
return { root: el("div", { children: [] }), input, label: o.label, setError: () => undefined };
|
||||
};
|
||||
const createSelectField = (o: any) => {
|
||||
const select = el("select");
|
||||
return { root: el("div"), select, setOptions: () => undefined };
|
||||
};
|
||||
"""
|
||||
|
||||
SCRIPT = """
|
||||
const sug = (f: any) => f.item.root.children.find((c: N) => c.tag === "datalist")
|
||||
.children.map((c: N) => c.attrs.value);
|
||||
const a = rowField({ item: "", dest: "start", note: "" }, "route");
|
||||
const empty = { value: a.item.input.value, list: sug(a), ph: a.item.input.placeholder,
|
||||
linked: a.item.input.attrs.list === a.item.root.children[0].id };
|
||||
a.item.input.value = " 레미콘(25-21-150) "; // 목록 밖 손 글
|
||||
a.setKind("base");
|
||||
const base = { value: a.item.input.value, list: sug(a), ph: a.item.input.placeholder };
|
||||
a.setKind("route");
|
||||
const old = rowField({ item: "옛 항목", dest: "mid", note: "" }, "route");
|
||||
const b = rowField({ item: "", dest: "start", note: "" }, "route");
|
||||
console.log(JSON.stringify({ empty, base, back: a.item.input.value, list: sug(a),
|
||||
old: [old.item.input.value, old.dest.select.value, sug(old)],
|
||||
ids: new Set([a, old, b].map((f) => f.item.input.attrs.list)).size,
|
||||
tag: a.item.input.dataset.b08MilestoneAdd }));
|
||||
"""
|
||||
|
||||
|
||||
def test_항목_손_입력_제안_분류_바꿔도_글_유지():
|
||||
src = STUBS + IMPORTS.sub("", MODAL.read_text(encoding="utf-8"))
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"],
|
||||
input=src + "\n" + SCRIPT, capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
got = json.loads(result.stdout)
|
||||
five = ["장비(20톤)", "장비(8톤)", "혼합골재", "전석운반", "철근"]
|
||||
# 빈 줄 = 빈 손 입력 칸 + 거래처 제안 다섯(datalist 이어짐)
|
||||
assert got["empty"] == {"value": "", "list": five, "ph": "예 장비(20톤)", "linked": True}
|
||||
# 분류 → 기준위치 = 제안만 기준점 · 쓴 글 그대로
|
||||
assert got["base"] == {
|
||||
"value": " 레미콘(25-21-150) ",
|
||||
"list": ["기준점"],
|
||||
"ph": "예 기준점",
|
||||
}
|
||||
assert got["back"] == " 레미콘(25-21-150) " and got["list"] == five
|
||||
# 옛 항목 글 = 그대로 칸에 · 목적지 그대로
|
||||
assert got["old"] == ["옛 항목", "mid", five]
|
||||
assert got["ids"] == 3 and got["tag"] == "item" # 줄마다 제안 목록 따로
|
||||
|
||||
|
||||
def test_저장_값_손_글_그대로():
|
||||
modal = MODAL.read_text(encoding="utf-8")
|
||||
assert "item: r.item.input.value.trim()," in modal # 목록 밖 글도 받음(앞뒤 빈칸만 뺌)
|
||||
assert 'createSelectField({ label: "항목"' not in modal
|
||||
assert len(modal.splitlines()) <= 700
|
||||
Reference in New Issue
Block a user