diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts index 1942871d0..49ee0f910 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts @@ -10,7 +10,7 @@ * 열기 = 다이어그램 [요소 추가](155 · 공사지 · 기준점 [+] 없앰) * 분류 기준위치 = 기준점(하나 · 항목 · 목적지 없음 · 있으면 고르기 막음) · 방위 = 소재지 좌표로 자동 자리 * 구간 km · 도로 구분 · 길찾기 = 연결선 모달(`…_UI_Milestone_LegModal`) · [등록] · 소재지 옮김 뒤 그 구간 한 번 - * 시점(공사지) 상자 = 프로젝트 등록 주소 · VWorld 주소(좌표 → 주소 · 서버) · 아래 지도 + * 시점(공사지) 상자 = 중점 측점 km(저장 m · 255-1) · 아래 카카오 지도(시점 · 중점 표지) * 조작 = 캐시 · [저장] 때 서버 * ========================================================================== */ @@ -24,7 +24,6 @@ import { showToast, } from "@ui/ui_template_elements"; import { openModal } from "@ui/ui_template_modal"; -import { buildAddressMap } from "../B01_Dashboard/B01_Dashboard_UI_MapPreview"; import { roadOf, type Dest, @@ -32,7 +31,9 @@ import { type MilestoneRow, type Road, } from "./B08_Quantity_Milestone_Calc"; +import { setLeg } from "./B08_Quantity_Milestone_Layout"; 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 { findSegmentKm, @@ -231,27 +232,26 @@ export interface SiteAddress { mid_address: string | null; } -/** 시점(공사지) 상자 — 프로젝트 등록 주소 · VWorld 주소 · 아래 지도(시점 표지) */ +/** 시점(공사지) 상자 — 중점 측점(km · 저장은 m) · 시점 → 중점 도로 · 아래 카카오 지도(시점 · 중점 표지) */ export function openSiteInfo(projectId: string): void { openModal({ title: "공사지 — 시점 · 중점", closeLabel: "닫기", + dialogClass: "b08-milestone__modal", mount: (body, close, actions) => { - const text = (label: string, value: string): HTMLElement => - el("p", { className: "b08-options__hint", text: `${label} — ${value}` }); - // 중점 측점 · 시점 → 중점 도로(좌측 패널 카드에서 옮김 · 244-6) + // 중점 측점 · 시점 → 중점 도로(좌측 패널 카드에서 옮김 · 244-6) · 보이고 받는 것만 km(255-1) const data = liveMilestone(projectId); const site = siteInfo(); - const km = (m: number | null | undefined): string => - m == null ? "-" : (m / 1000).toLocaleString(undefined, { maximumFractionDigits: 3 }); const mid = createInputField({ - label: "중점 측점 (m)", + label: "중점 측점 (km)", type: "number", min: 0, compact: true, }); - mid.input.placeholder = site?.mid_default_m != null ? String(site.mid_default_m) : ""; - mid.input.value = data.site.mid_station_m == null ? "" : String(data.site.mid_station_m); + mid.input.step = "0.001"; + mid.input.placeholder = site?.mid_default_m != null ? String(site.mid_default_m / 1000) : ""; + mid.input.value = + data.site.mid_station_m == null ? "" : String(data.site.mid_station_m / 1000); mid.input.dataset.b08Milestone = "mid_station_m"; const road = createSelectField({ label: "시점 → 중점 도로", @@ -263,40 +263,39 @@ export function openSiteInfo(projectId: string): void { label: "수정", variant: "filled", onClick: () => { + const text = mid.input.value; + const value = Number(text); + const km = text.trim() && Number.isFinite(value) && value >= 0 ? value : null; const fresh = liveMilestone(projectId); - const value = Number(mid.input.value); - fresh.site.mid_station_m = mid.input.value.trim() && value >= 0 ? value : null; - fresh.site.mid_road = road.select.value as Road; - putMilestone(projectId, fresh); + putMilestone( + projectId, + setLeg(fresh, "start", 0, { road: road.select.value as Road, km }), + ); close(); }, }); save.dataset.b08MilestoneSite = "save"; actions.append(save); - const map = buildAddressMap(); - const info = el("div", { children: [text("주소", "불러오는 중…")] }); - body.append( - line(mid.root, road.root), - text( - "시점 = 노선 첫 점(측점 0)", - `노선 ${km(site?.length_m)} km · 중점 기본 = 노선 ÷ 2(${km(site?.mid_default_m)} km) · 빈칸 = 기본`, - ), - info, - map.root, - ); - request(`/projects/${projectId}/quantity/milestone/site-address`).then( - (s) => { - const ll = (p: [number, number] | null): string => - p ? ` (${p[1].toFixed(6)}, ${p[0].toFixed(6)})` : ""; - info.replaceChildren( - text("프로젝트 등록 주소(사업지역)", s.region || "없음"), - text("시점 브이월드 주소", (s.start_address ?? "못 찾음") + ll(s.start_lonlat)), - text("중점 브이월드 주소", (s.mid_address ?? "못 찾음") + ll(s.mid_lonlat)), - ); - if (s.start_lonlat) map.showPoint(s.start_lonlat[1], s.start_lonlat[0]); - else if (s.region) void map.show(s.region); - }, - (error: unknown) => showToast(`공사지 주소를 못 읽었습니다 — ${String(error)}`, "error"), + const map = editMap(projectId, { hint: "", onMove: () => undefined }); + const pins = (s: { + start_lonlat: [number, number] | null; + mid_lonlat: [number, number] | null; + }): MapPin[] => + ( + [ + ["start", "시점", s.start_lonlat], + ["mid", "중점", s.mid_lonlat], + ] as const + ).flatMap(([key, name, p]) => + p ? [{ key, name, lon: p[0], lat: p[1], movable: false }] : [], + ); + if (site) map.set(pins(site), []); + body.append(line(mid.root, road.root), map.root); + // 지금 중점 측점(캐시)의 중점 좌표 — 오면 표지 다시 + const query = data.site.mid_station_m == null ? "" : `?mid_m=${data.site.mid_station_m}`; + request(`/projects/${projectId}/quantity/milestone/site-address${query}`).then( + (s) => map.set(pins(s), []), + (error: unknown) => showToast(`공사지 좌표를 못 읽었습니다 — ${String(error)}`, "error"), ); }, }); diff --git a/resources/tester/test_244_4_b08_milestone_modal.py b/resources/tester/test_244_4_b08_milestone_modal.py index 613e5d68d..d64ab1ad7 100644 --- a/resources/tester/test_244_4_b08_milestone_modal.py +++ b/resources/tester/test_244_4_b08_milestone_modal.py @@ -147,10 +147,8 @@ def test_화면_모달_팬줌_그림탭(): place = _read("B08_Quantity/B08_Quantity_UI_Milestone_Place.ts") assert 'from "../B01_Dashboard/B01_Dashboard_Api_Fetch"' in place assert "searchAddress(text)" in place - assert 'from "../B01_Dashboard/B01_Dashboard_UI_MapPreview"' in modal # 공사지 모달 지도 - # ② 시점 모달 — 등록 주소 · VWorld 주소 · 지도 - assert "/quantity/milestone/site-address" in modal and "map.showPoint(" in modal - assert "프로젝트 등록 주소" in modal and "시점 브이월드 주소" in modal + # ② 시점 모달 — 지도 = 공용 카카오 지도(255-1 · 주소 줄 지움) + assert "/quantity/milestone/site-address" in modal and "editMap(projectId" in modal # ⑤ 거래처 상자 = 같은 등록 모달(고침 · 삭제 · 244-13) · 구간 km · 도로 구분 = 연결선 모달 for text in ('"수정"', 'label: "삭제"', "rowValues()", "putMilestone(projectId, fresh)"): assert text in add diff --git a/resources/tester/test_244_6_b08_milestone_pan_client.py b/resources/tester/test_244_6_b08_milestone_pan_client.py index 9e609a8ef..fc391dd70 100644 --- a/resources/tester/test_244_6_b08_milestone_pan_client.py +++ b/resources/tester/test_244_6_b08_milestone_pan_client.py @@ -116,8 +116,8 @@ def test_표_폭_다이어그램_폭_좌측_카드_없음(): assert "milestoneCardBody" not in _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts") modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") site = modal[modal.index("export function openSiteInfo") :] - assert 'label: "중점 측점 (m)"' in site and 'label: "시점 → 중점 도로"' in site - assert "fresh.site.mid_station_m =" in site and "putMilestone(projectId, fresh)" in site + assert 'label: "중점 측점 (km)"' in site and 'label: "시점 → 중점 도로"' in site + assert 'setLeg(fresh, "start", 0, {' in site and "putMilestone(" in site # 244-13 — 기준점 = 등록 모달 분류 「기준위치」 · [기준점 +] 없앰 · 삭제 = 기준점 없음 base = modal[ modal.index("export function openPlaceModal") : modal.index("interface SiteAddress") diff --git a/resources/tester/test_255_1_b08_milestone_site_modal.py b/resources/tester/test_255_1_b08_milestone_site_modal.py new file mode 100644 index 000000000..e944a6d05 --- /dev/null +++ b/resources/tester/test_255_1_b08_milestone_site_modal.py @@ -0,0 +1,51 @@ +"""255-1 거리이정표 시점 모달 — 중점 측점 km 로 보이고 받음(저장은 m) · 설명 줄 지움 · +지도 = 공용 카카오 지도(시점 · 중점 표지).""" + +import json +import re +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE) + + +def _read(rel): + return (ROOT / rel).read_text(encoding="utf-8") + + +def _node(expr): + src = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts") + result = subprocess.run( + ["node", "--experimental-strip-types", "--no-warnings", + "--input-type=module-typescript", "-e", + IMPORTS.sub("", src) + f"\nconsole.log(JSON.stringify({expr}));"], + capture_output=True, text=True, encoding="utf-8", timeout=60, + ) # fmt: skip + assert result.returncode == 0, result.stderr + return json.loads(result.stdout) + + +def _site(km): + data = {"site": {"mid_station_m": 999, "mid_road": "비포장"}, "base": {}, "routes": []} + return _node(f'setLeg({json.dumps(data)}, "start", 0, {{ km: {json.dumps(km)} }}).site') + + +def test_중점_km_저장_m(): + assert _site(1.2345)["mid_station_m"] == 1234.5 + assert _site(0.0005)["mid_station_m"] == 0.5 + assert _site(None)["mid_station_m"] is None # 빈칸 = 노선 ÷ 2 + + +def test_시점_모달_km_카카오_지도(): + modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") + site = modal[modal.index("export function openSiteInfo") :] + assert 'label: "중점 측점 (km)"' in site + assert "String(data.site.mid_station_m / 1000)" in site # 보임 = km + assert 'setLeg(fresh, "start", 0, {' in site # 받음 km → 저장 m + # 설명 줄 · 등록 주소 · 브이월드 주소 줄 · B01 타일 지도 없음 + for gone in ("시점 = 노선 첫 점", "프로젝트 등록 주소", "브이월드 주소", "buildAddressMap"): + assert gone not in modal + assert "editMap(projectId" in site + assert '["start", "시점", s.start_lonlat]' in site and '["mid", "중점", s.mid_lonlat]' in site + assert 'dialogClass: "b08-milestone__modal"' in site # 다른 모달과 같은 창 · 지도 높이