From 53a7b14f94cb4828e83293459a2f1b28c5dfbf4b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 11 Oct 2026 14:08:59 +0900 Subject: [PATCH] =?UTF-8?q?feat(b08):=20=EA=B1=B0=EB=A6=AC=EC=9D=B4?= =?UTF-8?q?=EC=A0=95=ED=91=9C=20=EA=B1=B0=EB=9E=98=EC=B2=98=20=C2=B7=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=A0=90=20=EB=AA=A8=EB=8B=AC=20=E2=80=94=20?= =?UTF-8?q?=EC=B9=B8=20=EC=9D=B4=EB=A6=84=20=3D=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EA=B8=80=20=ED=81=AC=EA=B8=B0=20=C2=B7=20=EC=86=8C=EC=9E=AC?= =?UTF-8?q?=EC=A7=80=20=EB=A8=B8=EB=A6=AC=20=EC=A4=84=20=EC=A7=80=EC=9B=80?= =?UTF-8?q?=20=C2=B7=20=EA=B8=B0=EC=A4=80=EC=A0=90=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=ED=95=9C=20=EC=A4=84=20=C2=B7=20=EC=86=8C=EC=9E=AC=EC=A7=80=20?= =?UTF-8?q?=EC=9C=84=20=EA=B0=80=EB=A1=9C=EC=84=A0=20=C2=B7=20=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=20=EA=B7=B8=EB=A6=BC=20=EC=BA=A1=EC=B2=98=20=EB=B6=80?= =?UTF-8?q?=EB=A6=84=20(255-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G --- B08_Quantity/B08_Quantity_UI_Milestone.css | 12 ++++++ .../B08_Quantity_UI_Milestone_Modal.ts | 23 ++++++---- .../B08_Quantity_UI_Milestone_Route.ts | 3 ++ .../test_244_5_b08_milestone_base_dir.py | 4 +- .../test_250_1_b08_milestone_roads_width.py | 2 +- .../test_255_3_b08_milestone_place_modal.py | 42 +++++++++++++++++++ 6 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 resources/tester/test_255_3_b08_milestone_place_modal.py diff --git a/B08_Quantity/B08_Quantity_UI_Milestone.css b/B08_Quantity/B08_Quantity_UI_Milestone.css index fe859ca05..e57efe263 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone.css +++ b/B08_Quantity/B08_Quantity_UI_Milestone.css @@ -216,6 +216,18 @@ font-weight: 600; } +/* 255-3 칸 이름 = 내용 글 크기 이상 · 한 줄 · 소재지(주소 · 지도) 위 가로선 */ +.b08-milestone__modal .ui-field__label { + font-size: var(--text-body-sm); + white-space: nowrap; +} + +.b08-milestone__place-field { + margin-top: var(--spacing-8); + padding-top: var(--spacing-16); + border-top: 1px solid var(--color-border); +} + /* 연결선 누름 자리 — 투명한 굵은 선(244-13) */ .b08-milestone__line-hit { stroke: transparent; diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts index 476ede05d..ddc0dbd91 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts @@ -37,6 +37,7 @@ import { request } from "./B08_Quantity_UI_Hydro"; import { editMap, type MapPin } from "./B08_Quantity_UI_Milestone_KakaoMap"; import { placePicker } from "./B08_Quantity_UI_Milestone_Place"; import { + capturePlace, findSegmentKm, liveMilestone, putMilestone, @@ -58,8 +59,6 @@ export const ITEMS: Record<"route" | "base", string[]> = { const line = (...children: HTMLElement[]): HTMLElement => el("div", { className: "b08-milestone__line-edit", children }); -const head = (text: string): HTMLElement => el("h4", { text }); - /** 길찾기 결과 한 줄 → 알림(까닭 줄 = 경고) */ export function toastKm(text: string): void { if (text) showToast(`길찾기 — ${text}`, text.includes(" — ") ? "warning" : "success"); @@ -79,7 +78,8 @@ function rowField(r: MilestoneRow, kind: "route" | "base") { list.replaceChildren(...ITEMS[k].map((t) => el("option", { attrs: { value: t } }))); item.input.placeholder = `예 ${ITEMS[k][0]}`; // 기준점 = 거쳐 가는 자리 — 항목 · 목적지 없음(149) - item.root.style.visibility = dest.root.style.visibility = k === "base" ? "hidden" : ""; + // 255-3 자리도 비움 — 기준점 이름이 분류 옆 첫 줄(두 줄로 안 꺾임) + item.root.style.display = dest.root.style.display = k === "base" ? "none" : ""; }; setKind(kind); dest.select.value = r.dest; @@ -160,16 +160,18 @@ export function openPlaceModal( const where = place.value(); const fresh = liveMilestone(projectId); let segment: string | undefined; // [등록] · 소재지 옮김 뒤 그 구간 길찾기 한 번 + let placed: string | undefined; // 256-1 등록 · 좌표 바뀜 = 장소 그림 캡처(뒤에서) if (kind === "base" && base) { const b = fresh.bases.find((x) => x.id === base.id); if (!b) return close(); Object.assign(b, where, { name: text, dir: null }); putMilestone(projectId, fresh); - if (moved(where)) segment = b.id; + if (moved(where)) segment = placed = b.id; } else if (kind === "base") { // 254 새 기준점 = 상자만 · 선은 연결 점 ● 로 이음(이을 때 길찾기) + placed = `b${Date.now().toString(36)}${Math.floor(Math.random() * 1296).toString(36)}`; fresh.bases.push({ - id: `b${Date.now().toString(36)}${Math.floor(Math.random() * 1296).toString(36)}`, + id: placed, name: text, dir: null, legs: [{ road: null, km: null }], @@ -186,11 +188,11 @@ export function openPlaceModal( rows: rowValues(), }); putMilestone(projectId, fresh); - if (moved(where)) segment = r.id; + if (moved(where)) segment = placed = r.id; } else { if (!text) return name.setError("거래처 이름을 넣으세요."); // 155 등록 = 상자만 · 선은 다이어그램 연결 점 ● 로 이음(이을 때 길찾기) - addRoute?.({ + placed = addRoute?.({ name: text, to: "site", linked: false, @@ -201,6 +203,12 @@ export function openPlaceModal( } close(); if (segment) void findSegmentKm(projectId, segment).then(toastKm); + if (placed && where.lon != null && where.lat != null) + void capturePlace(projectId, placed, { + name: text || (kind === "base" ? "기준점" : "거래처"), + lon: where.lon, + lat: where.lat, + }); }, }); ok.dataset.b08MilestoneInfo = "save"; @@ -231,7 +239,6 @@ export function openPlaceModal( className: "b08-milestone__grid", children: [sort.root, first.item.root, name.root, first.dest.root], }), - head("소재지 — 주소 찾기 · 지도에서 고르기(방위는 이 자리로 자동)"), place.root, ); }, diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Route.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Route.ts index 8c632231c..f7ea25721 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Route.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Route.ts @@ -20,6 +20,7 @@ import { chainOf, ownerOf } from "./B08_Quantity_Milestone_Layout"; import { editMap, type MapPin } from "./B08_Quantity_UI_Milestone_KakaoMap"; import { reverseAddress } from "./B08_Quantity_UI_Milestone_Place"; import { + capturePlace, deleteImage, findSegmentKm, hasUserImage, @@ -165,6 +166,8 @@ export function routeView(projectId: string, routeId: string): MilestoneView { const target = ownerOf(fresh, key); if (!target) return; Object.assign(target, { lon, lat, address }); + const label = fresh.bases.some((b) => b.id === key) ? "기준점" : "거래처"; + void capturePlace(projectId, key, { name: target.name || label, lon, lat }); // 256-1 장소 그림 } putMilestone(projectId, fresh); await refind(touchingSegments(fresh, key)); diff --git a/resources/tester/test_244_5_b08_milestone_base_dir.py b/resources/tester/test_244_5_b08_milestone_base_dir.py index 216b0aa41..d3fa2290a 100644 --- a/resources/tester/test_244_5_b08_milestone_base_dir.py +++ b/resources/tester/test_244_5_b08_milestone_base_dir.py @@ -65,7 +65,9 @@ def test_좌측_패널_방위_칸_없음_모달_안내(): base = modal[ modal.index("export function openPlaceModal") : modal.index("interface SiteAddress") ] - assert "방위는 이 자리로 자동" in base # 244-13 — 기준점 = 등록 모달 분류 기준위치 + assert ( + "placePicker(" in base and "방위는 이 자리로 자동" not in base + ) # 255-3 소재지 머리 줄 지움 layout = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts") assert "placeDeg({ dir: null, lon: b.lon, lat: b.lat }, anchorLL) ??" in layout assert '(anchor === "site" && !used.has(keyOf("site", 270)) ? 270 : free(anchor))' in layout diff --git a/resources/tester/test_250_1_b08_milestone_roads_width.py b/resources/tester/test_250_1_b08_milestone_roads_width.py index 93982a914..51e5f93f5 100644 --- a/resources/tester/test_250_1_b08_milestone_roads_width.py +++ b/resources/tester/test_250_1_b08_milestone_roads_width.py @@ -65,7 +65,7 @@ def test_모달_상자_하나_항목_하나_수정_글(): leg = _read("B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts") assert '"+ 줄"' not in modal and "same.rows" not in modal assert 'label: "고침"' not in modal + leg and modal.count('"수정"') >= 2 - assert 'item.root.style.visibility = dest.root.style.visibility = k === "base"' in modal + assert 'item.root.style.display = dest.root.style.display = k === "base"' in modal # 255-3 def test_폭_전체_지도_높이_한_값(): diff --git a/resources/tester/test_255_3_b08_milestone_place_modal.py b/resources/tester/test_255_3_b08_milestone_place_modal.py new file mode 100644 index 000000000..244906939 --- /dev/null +++ b/resources/tester/test_255_3_b08_milestone_place_modal.py @@ -0,0 +1,42 @@ +"""255-3 거리이정표 거래처 · 기준점 모달 — 칸 이름 ≥ 내용 글 · 「소재지 — 주소 찾기 · 지도에서 고르기」 +줄 지움 · 기준점 이름 한 줄(빈 칸 자리 비움) · 주소 · 지도 위 가로선 · +256-1 장소 그림 = 등록 · 좌표 바뀜 · 경로 탭 끌기 뒤 capturePlace.""" + +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + + +def _read(rel): + return (ROOT / rel).read_text(encoding="utf-8") + + +def _rule(css, selector): + m = re.search(re.escape(selector) + r" \{([^}]*)\}", css) + assert m, selector + return m.group(1) + + +def test_모달_꼴(): + modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") + assert "주소 찾기 · 지도에서 고르기" not in modal and 'el("h4"' not in modal + assert ( + 'item.root.style.display = dest.root.style.display = k === "base" ? "none" : "";' in modal + ) + css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") + label = _rule(css, ".b08-milestone__modal .ui-field__label") + assert "font-size: var(--text-body-sm);" in label # 입력 글(text-body-sm)과 같음 + assert "white-space: nowrap;" in label + assert "border-top: 1px solid var(--color-border);" in _rule(css, ".b08-milestone__place-field") + place = _read("B08_Quantity/B08_Quantity_UI_Milestone_Place.ts") + assert 'className: "b08-milestone__place-field"' in place + + +def test_장소_그림_캡처_부름(): + modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") + assert modal.count("segment = placed = ") == 2 # 기준점 · 거래처 좌표 바뀜 + assert "placed = addRoute?.({" in modal and "id: placed," in modal # 새 거래처 · 새 기준점 + assert "void capturePlace(projectId, placed, {" in modal + route = _read("B08_Quantity/B08_Quantity_UI_Milestone_Route.ts") + assert "void capturePlace(projectId, key, { name: target.name || label, lon, lat });" in route