From 2d5d357bf3149dd431c685a7080a3b617005b17f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 11 Oct 2026 00:24:53 +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[=EC=9A=94=EC=86=8C=20=EC=B6=94=EA=B0=80]?= =?UTF-8?q?=20=C2=B7=20=EC=84=A0=20=EC=97=86=EB=8A=94=20=EC=83=88=20?= =?UTF-8?q?=EA=B1=B0=EB=9E=98=EC=B2=98=20=C2=B7=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=EC=84=A0=20=EC=88=98=EA=B8=B0=20=EC=9E=85=EB=A0=A5=20(250-4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공사지 · 기준점 [+] 없앰 → [자동 정렬] 옆 [요소 추가] - 등록 = 상자만(linked false) · 연결 점으로 이음 · 안 이은 거래처 = 구간 km 안 셈(파이썬 · TS 짝) - 연결선 모달 [수기 입력] = 카카오 길찾기 연결 끊음(manual) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY --- B08_Quantity/B08_Quantity_Milestone_Calc.py | 6 ++- B08_Quantity/B08_Quantity_Milestone_Calc.ts | 10 ++++- B08_Quantity/B08_Quantity_Milestone_Layout.ts | 6 ++- B08_Quantity/B08_Quantity_Router_Milestone.py | 2 + B08_Quantity/B08_Quantity_UI_Milestone.css | 19 +++----- .../B08_Quantity_UI_Milestone_Diagram.ts | 36 +++++---------- .../B08_Quantity_UI_Milestone_LegModal.ts | 44 ++++++++++++++----- .../B08_Quantity_UI_Milestone_Modal.ts | 13 +++--- .../B08_Quantity_UI_Milestone_State.ts | 2 + .../test_244_13_b08_milestone_place_leg.py | 2 +- .../test_244_2_b08_milestone_diagram.py | 6 +-- .../test_244_9_b08_milestone_route_map.py | 2 +- .../test_250_1_b08_milestone_roads_width.py | 27 ++++++++++++ 13 files changed, 108 insertions(+), 67 deletions(-) diff --git a/B08_Quantity/B08_Quantity_Milestone_Calc.py b/B08_Quantity/B08_Quantity_Milestone_Calc.py index 3030ee999..006196a3d 100644 --- a/B08_Quantity/B08_Quantity_Milestone_Calc.py +++ b/B08_Quantity/B08_Quantity_Milestone_Calc.py @@ -64,8 +64,10 @@ def compute_milestone(data: dict[str, Any], mid_default_m: float | None) -> list for route in data.get("routes") or []: for index, row in enumerate(route.get("rows") or []): sums = {"s2": 0.0, "s1": 0.0, "pv": 0.0, "un": 0.0} - via = bool(base.get("enabled")) and route.get("via_base", True) is not False - add_legs(sums, route.get("legs"), "via" if via else "direct") + linked = route.get("linked", True) is not False # 안 이은 거래처 = 구간 km 없음(155) + via = linked and bool(base.get("enabled")) and route.get("via_base", True) is not False + if linked: + add_legs(sums, route.get("legs"), "via" if via else "direct") if via: add_legs(sums, base.get("legs"), "base") if row.get("dest") == "mid": diff --git a/B08_Quantity/B08_Quantity_Milestone_Calc.ts b/B08_Quantity/B08_Quantity_Milestone_Calc.ts index e518c7ee3..8637bc66e 100644 --- a/B08_Quantity/B08_Quantity_Milestone_Calc.ts +++ b/B08_Quantity/B08_Quantity_Milestone_Calc.ts @@ -83,6 +83,10 @@ export interface MilestoneRoute { lat?: number | null; /** 다이어그램에 끌어 놓은 자리(153 · 왼 위 · 그림 좌표) — 없으면 방위 자동 자리 */ pos?: [number, number] | null; + /** 선 이음(155) — false = 아직 안 이음(요소 추가 뒤 · 연결 점으로 이음 · 구간 km 안 셈) · 없음 = 이음(옛 자료) */ + linked?: boolean; + /** 수기 입력(155) — 카카오 길찾기 안 함 · km 손 입력만 */ + manual?: boolean; /** 업체 → 기준점(거침) · 시점 구간 */ legs: Leg[]; rows: MilestoneRow[]; @@ -98,6 +102,7 @@ export interface MilestoneData { lon?: number | null; lat?: number | null; pos?: [number, number] | null; + manual?: boolean; /** 기준점 → 시점 공통 구간 */ legs: Leg[]; }; @@ -153,8 +158,9 @@ export function computeMilestone( for (const route of data.routes ?? []) { (route.rows ?? []).forEach((row, index) => { const sums = { s2: 0, s1: 0, pv: 0, un: 0 }; - const via = !!base?.enabled && route.via_base !== false; - addLegs(sums, route.legs, via ? "via" : "direct"); + const linked = route.linked !== false; // 안 이은 거래처 = 구간 km 없음(155) + const via = linked && !!base?.enabled && route.via_base !== false; + if (linked) addLegs(sums, route.legs, via ? "via" : "direct"); if (via) addLegs(sums, base.legs, "base"); if (row.dest === "mid") addLegs(sums, [mid], "mid"); const paved = sums.s2 + sums.s1 + sums.pv; diff --git a/B08_Quantity/B08_Quantity_Milestone_Layout.ts b/B08_Quantity/B08_Quantity_Milestone_Layout.ts index fcc802a0c..b6b33a7c1 100644 --- a/B08_Quantity/B08_Quantity_Milestone_Layout.ts +++ b/B08_Quantity/B08_Quantity_Milestone_Layout.ts @@ -220,7 +220,7 @@ export function segmentEnds(data: MilestoneData, from: string, start: LonLat): K ? { key: "base", text: `${baseName} → 시점`, from: pointOf(data.base), to: start ?? null } : null; const route = data.routes.find((r) => r.id === from); - if (!route) return null; + if (!route || route.linked === false) return null; // 155 안 이은 거래처 = 구간 없음 const viaBase = data.base.enabled && route.via_base; return { key: "route", @@ -506,7 +506,9 @@ export function layoutMilestone( }, [ux, uy], ); - line(g.id, g.anchor === "base" ? "base" : "start", g.legs); + // 155 안 이은 거래처 = 선 없음(연결 점으로 이음) + if (data.routes.find((r) => r.id === g.id)?.linked !== false) + line(g.id, g.anchor === "base" ? "base" : "start", g.legs); }); } diff --git a/B08_Quantity/B08_Quantity_Router_Milestone.py b/B08_Quantity/B08_Quantity_Router_Milestone.py index e4f3810de..8e20c819c 100644 --- a/B08_Quantity/B08_Quantity_Router_Milestone.py +++ b/B08_Quantity/B08_Quantity_Router_Milestone.py @@ -94,12 +94,14 @@ class Place(BaseModel): lat: float | None = Field(default=None, ge=-90, le=90) # 153 다이어그램에 끌어 놓은 자리(왼 위 · 그림 좌표) — 없으면 방위 자동 자리 pos: tuple[float, float] | None = None + manual: bool = False # 155 수기 입력 — 카카오 길찾기 안 함 legs: list[Leg] = Field(default_factory=list, max_length=20) class Route(Place): id: str = Field(pattern=ROUTE_ID) via_base: bool = True + linked: bool = True # 155 False = 아직 선 안 이음(요소 추가 뒤) rows: list[Row] = Field(default_factory=list, max_length=50) diff --git a/B08_Quantity/B08_Quantity_UI_Milestone.css b/B08_Quantity/B08_Quantity_UI_Milestone.css index 6592fabb7..0953375dc 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone.css +++ b/B08_Quantity/B08_Quantity_UI_Milestone.css @@ -56,11 +56,13 @@ } /* 244-2 다이어그램 — 상자 · 선 · km 글(테마 변수) · 스크롤 없음(244-8 · 그림 = 틀 안 맞춤) */ -/* 153 [자동 정렬] — 다이어그램 왼쪽 위(끌어 놓은 자리가 있을 때만 · 오른쪽 위 = 진행단계 판 자리) */ -.b08-milestone__arrange { +/* 153 · 155 [자동 정렬](끌어 놓은 자리 있을 때만) · [요소 추가] — 다이어그램 왼쪽 위(오른쪽 위 = 진행단계 판 자리) */ +.b08-milestone__tools { position: absolute; top: 8px; left: 8px; + display: flex; + gap: 6px; } .b08-milestone__diagram { @@ -89,8 +91,7 @@ stroke-width: 1.2; } -.b08-milestone__box text, -.b08-milestone__plus text { +.b08-milestone__box text { fill: var(--color-text); } @@ -127,16 +128,6 @@ stroke-width: 2; } -.b08-milestone__plus { - cursor: pointer; -} - -.b08-milestone__plus circle { - fill: var(--color-surface); - stroke: var(--color-accent); - stroke-width: 1.5; -} - .b08-milestone__km { fill: var(--color-text); font-size: 12px; diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts index 0b755e5cb..37080ea36 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts @@ -161,25 +161,6 @@ export function createDiagram( base: () => openPlaceModal(projectId, { add: false, id: "base" }), }; - const plus = (box: DiagramBox, viaBase: boolean): SVGGElement => { - const g = svg("g", { class: "b08-milestone__plus", role: "button", tabindex: 0 }); - const cx = box.x + box.w - 2; - const cy = box.y + 2; - g.append( - svg("circle", { cx, cy, r: 11 }), - svg("text", { x: cx, y: cy, "text-anchor": "middle", "dominant-baseline": "central" }, "+"), - svg("title", {}, "항목 추가"), - ); - g.dataset.b08MilestonePlus = viaBase ? "base" : "site"; - const go = (e: Event): void => { - e.stopPropagation(); - openPlaceModal(projectId, { add: true, viaBase }, addRoute); - }; - g.addEventListener("click", go); - g.addEventListener("keydown", (e) => e.key === "Enter" && go(e)); - return g; - }; - /** 153 상자 끌어 옮김 — 4px 넘게 움직이면 끌기(누름 = 모달은 그대로) · 놓으면 그 자리 저장(캐시 · [저장] 때 서버) * 기준점을 옮기면 거친 거래처도 따라감(자리 안 정한 것만 · 레이아웃이 기준점 둘레로 놓음) */ const dragBox = (g: SVGGElement, box: DiagramBox, shift: SVGGElement): void => { @@ -269,9 +250,10 @@ export function createDiagram( const route = fresh.routes.find((r) => r.id === box.id); if (!t || !route) return; const via = t === "base"; - const now = fresh.base.enabled && route.via_base !== false; + const now = route.linked === false ? null : fresh.base.enabled && route.via_base !== false; if (via === now) return; // 같은 연결 = 그대로 route.via_base = via; + route.linked = true; // 155 요소 추가 뒤 첫 이음 route.legs = [{ road: null, km: null }]; // 옛 구간 km = 다른 길 — 새로 길찾기 putMilestone(projectId, fresh); void findSegmentKm(projectId, route.id).then(toastKm); @@ -295,8 +277,16 @@ export function createDiagram( putMilestone(projectId, fresh); }, }); - arrange.classList.add("b08-milestone__arrange"); arrange.dataset.b08MilestoneArrange = ""; + // 155 [요소 추가] — 기준점 · 거래처 등록(공사지 · 기준점 [+] 없앰) · 등록 뒤 연결 점으로 선 이음 + const addElem = createButton({ + label: "요소 추가", + size: "sm", + variant: "ghost", + onClick: () => openPlaceModal(projectId, { add: true }, addRoute), + }); + addElem.dataset.b08MilestoneAdd = "open"; + const tools = el("div", { className: "b08-milestone__tools", children: [arrange, addElem] }); const refresh = (): void => { const data = liveMilestone(projectId); @@ -383,8 +373,6 @@ export function createDiagram( g.addEventListener("keydown", (e) => e.key === "Enter" && info()); if (box.kind === "route" || box.kind === "base") dragBox(g, box, shift); if (box.kind === "route") g.append(connectDot(box, shift)); - if (box.kind === "site") g.append(plus(box, false)); - if (box.kind === "base") g.append(plus(box, true)); shift.append(g); if (box.kind === "site") shift.append(lineLayer); } @@ -421,7 +409,7 @@ export function createDiagram( shift.append(text); } arrange.hidden = !data.base.pos && !data.routes.some((r) => r.pos); - host.replaceChildren(root, arrange); + host.replaceChildren(root, tools); attachSvgZoomPan({ svg: root, layer, diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts b/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts index 98d50bffa..988a48bf3 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts @@ -11,6 +11,7 @@ * 자동 그림(244-11) = 거래처 쪽 구간 [길찾기] · 옮김 때만(`findSegmentKm`) * ========================================================================== */ +import { createCheckbox } from "@ui/ui_template_checkbox"; import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements"; import { openModal } from "@ui/ui_template_modal"; import { @@ -240,20 +241,39 @@ export function openLegInfo(projectId: string, from: string): void { }); save.dataset.b08MilestoneLegSave = ""; actions.append(find, save); - body.append( - legs.root, - hint( - from === "base" - ? "기준점 → 시점 = 도로가 안 이어질 수 있음 — 거리를 손으로 넣어도 됨(빈칸 = 길찾기 자동 값)" - : "빈칸 = 길찾기 자동 값 · 넣은 값이 우선", - ), - status, - el("h4", { text: "경로 — 다음(카카오) 지도" }), - map.root, + // 155 [수기 입력] — 카카오 길찾기 연결 끊음(자동 값 지움 · 길찾기 · 지도 숨김 · km 손 입력) · 풀면 다시 길찾기 + const owner = (d = liveMilestone(projectId)) => + from === "base" ? d.base : d.routes.find((r) => r.id === from); + const manual = createCheckbox("수기 입력 — 카카오 길찾기 안 함(거리 손 입력)"); + manual.input.checked = !!owner()?.manual; + manual.input.dataset.b08MilestoneManual = ""; + const auto = hint( + from === "base" + ? "기준점 → 시점 = 도로가 안 이어질 수 있음 — 거리를 손으로 넣어도 됨(빈칸 = 길찾기 자동 값)" + : "빈칸 = 길찾기 자동 값 · 넣은 값이 우선", ); + const mapHead = el("h4", { text: "경로 — 다음(카카오) 지도" }); + const showMode = (): void => { + const off = manual.input.checked; + find.hidden = auto.hidden = mapHead.hidden = map.root.hidden = off; + status.textContent = off ? "수기 입력 — 거리를 손으로 넣으세요" : status.textContent; + }; + manual.input.addEventListener("change", () => { + const fresh = liveMilestone(projectId); + const target = owner(fresh); + if (!target) return; + target.legs = legs.value().map((l) => (manual.input.checked ? { ...l, auto_km: null } : l)); + target.manual = manual.input.checked; + putMilestone(projectId, fresh); + redrawLegs(); + showMode(); + if (!manual.input.checked) void run(); // 다시 카카오 연결 = 길찾기 한 번 + }); + body.append(manual.root, legs.root, auto, status, mapHead, map.root); + showMode(); drawMap(); - // 길 선이 아직 없으면 열 때 한 번(km 같으면 안 씀 · 자동 그림 없음) - if (!segmentPath(from).length && seg.from && seg.to) void run(false); + // 길 선이 아직 없으면 열 때 한 번(km 같으면 안 씀 · 자동 그림 없음 · 수기 입력이면 안 함) + if (!manual.input.checked && !segmentPath(from).length && seg.from && seg.to) void run(false); }, }); } diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts index 9b8bdf6b9..1942871d0 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts @@ -6,7 +6,8 @@ * 2열(분류(기준위치 · 거래처) · 항목(손 입력 + 분류 따라 제안 · 244-14) · 거래처 이름 · 목적지) + 소재지(주소 찾기 · * 다음(카카오) 지도에서 고르기 · `…_UI_Milestone_Place`) · 추가 = [등록] · 상자 = [수정] · [삭제] * 상자 하나 = 항목 하나(149 · [+ 줄] 없음 · 같은 거래처라도 항목마다 따로 등록) · 경로 값은 연결선 모달 - * 연결(기준점 거침 · 시점으로 바로) = 처음 = [+] 누른 상자 쪽 · 그 뒤 = 다이어그램 연결 점 ● 끌기(154 · 체크 없앰) + * 연결(기준점 거침 · 시점으로 바로) = 다이어그램 연결 점 ● 끌기(154 · 155 · 등록 = 상자만 · 선 없음) + * 열기 = 다이어그램 [요소 추가](155 · 공사지 · 기준점 [+] 없앰) * 분류 기준위치 = 기준점(하나 · 항목 · 목적지 없음 · 있으면 고르기 막음) · 방위 = 소재지 좌표로 자동 자리 * 구간 km · 도로 구분 · 길찾기 = 연결선 모달(`…_UI_Milestone_LegModal`) · [등록] · 소재지 옮김 뒤 그 구간 한 번 * 시점(공사지) 상자 = 프로젝트 등록 주소 · VWorld 주소(좌표 → 주소 · 서버) · 아래 지도 @@ -84,7 +85,7 @@ function rowField(r: MilestoneRow, kind: "route" | "base") { return { item, dest, note: r.note, setKind }; } -export type PlaceTarget = { add: true; viaBase: boolean } | { add: false; id: string }; +export type PlaceTarget = { add: true } | { add: false; id: string }; /** 등록 모달 — 추가([+]) · 고침(거래처 · 기준점 상자) 한 벌 · id "base" = 기준점 */ export function openPlaceModal( @@ -97,7 +98,7 @@ export function openPlaceModal( const route = !target.add && !editBase ? data.routes.find((r) => r.id === target.id) : undefined; if (!target.add && !editBase && !route) return; const title = target.add - ? `항목 추가 — ${target.viaBase ? "기준점 거침" : "공사지로 바로"}` + ? "요소 추가 — 기준점 · 거래처" : editBase ? `기준점 — ${data.base.name || "기준점"}` : `항목 정보 — ${route!.name || "거래처"}`; @@ -178,15 +179,15 @@ export function openPlaceModal( if (moved(where)) segment = r.id; } else { if (!text) return name.setError("거래처 이름을 넣으세요."); - const viaBase = target.add && target.viaBase; + // 155 등록 = 상자만 · 선은 다이어그램 연결 점 ● 로 이음(이을 때 길찾기) const id = addRoute?.({ name: text, - via_base: viaBase, + via_base: true, + linked: false, dir: null, rows: rowValues(), ...where, }); - if (id && where.lon != null) segment = id; } close(); if (segment) void findSegmentKm(projectId, segment).then(toastKm); diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_State.ts b/B08_Quantity/B08_Quantity_UI_Milestone_State.ts index b14239be1..9459441fa 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_State.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_State.ts @@ -239,6 +239,8 @@ export async function findSegmentKm( image = true, ): Promise { const data = liveMilestone(projectId); + const owner = from === "base" ? data.base : data.routes.find((r) => r.id === from); + if (owner?.manual) return ""; // 155 수기 입력 = 카카오 길찾기 안 함 const seg = segmentEnds(data, from, siteInfo()?.start_lonlat ?? null); if (!seg) return ""; if (!seg.from || !seg.to) { diff --git a/resources/tester/test_244_13_b08_milestone_place_leg.py b/resources/tester/test_244_13_b08_milestone_place_leg.py index 5234c4374..956d4631d 100644 --- a/resources/tester/test_244_13_b08_milestone_place_leg.py +++ b/resources/tester/test_244_13_b08_milestone_place_leg.py @@ -299,7 +299,7 @@ def test_화면_등록_모달_연결선_모달(): assert "길찾기" not in mid.replace("길찾기 없음", "") and "editMap" not in mid assert 'if (from === "start") return openMidLeg(projectId);' in leg assert "void reverseAddress(projectId, lon, lat)" in leg and "void run();" in leg - assert "if (!segmentPath(from).length && seg.from && seg.to) void run(false);" in leg + assert "if (!manual.input.checked && !segmentPath(from).length && seg.from && seg.to)" in leg for rel in (B08 + "UI_Milestone_Modal.ts", B08 + "UI_Milestone_Place.ts", B08 + "UI_Milestone_LegModal.ts", KMAP, STATE, LAYOUT, CALC, B08 + "UI_Milestone.ts", B08 + "UI_Milestone_Diagram.ts", diff --git a/resources/tester/test_244_2_b08_milestone_diagram.py b/resources/tester/test_244_2_b08_milestone_diagram.py index d07f91343..203e3c163 100644 --- a/resources/tester/test_244_2_b08_milestone_diagram.py +++ b/resources/tester/test_244_2_b08_milestone_diagram.py @@ -145,9 +145,9 @@ def test_화면_상자_누름_더하기_창(): "openTab" not in ui and "route: (id) => openPlaceModal(projectId, { add: false, id })," in ui ) - assert 'if (box.kind === "site") g.append(plus(box, false));' in ui - assert 'if (box.kind === "base") g.append(plus(box, true));' in ui - assert "openPlaceModal(projectId, { add: true, viaBase }, addRoute)" in ui + # 155 공사지 · 기준점 [+] 없앰 → 다이어그램 [요소 추가] + assert "plus(box" not in ui and 'label: "요소 추가"' in ui + assert "openPlaceModal(projectId, { add: true }, addRoute)" in ui modal = (ROOT / "B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts").read_text(encoding="utf-8") assert "openModal({" in modal # 149 상자 하나 = 항목 하나 — [+ 줄] · 같은 이름 합침 없음 diff --git a/resources/tester/test_244_9_b08_milestone_route_map.py b/resources/tester/test_244_9_b08_milestone_route_map.py index 1d25f62a8..7bd1fe72e 100644 --- a/resources/tester/test_244_9_b08_milestone_route_map.py +++ b/resources/tester/test_244_9_b08_milestone_route_map.py @@ -216,7 +216,7 @@ def test_화면_자리_캡처_키_서버만(): assert "sdk.js" in kmap and "?appkey=${encodeURIComponent(key)}&autoload=false" in kmap assert "getDisplayMedia" not in kmap and "[지도 캡처]" not in kmap # 244-11 = 서버 자동 leg = _read("B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts") # 244-13 = 연결선 모달 지도 - assert "map.root," in leg and "map.set(pins()" in leg + assert "mapHead, map.root" in leg and "map.set(pins()" in leg route = _read("B08_Quantity/B08_Quantity_UI_Milestone_Route.ts") assert "routeMapView(projectId, routeId)" in route assert "mapView.draw();" in route 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 e8fa770b8..d368f1cea 100644 --- a/resources/tester/test_250_1_b08_milestone_roads_width.py +++ b/resources/tester/test_250_1_b08_milestone_roads_width.py @@ -119,3 +119,30 @@ def test_154_연결_점_끌어_잇기(): ) css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") assert ".b08-milestone__connect {" in css and ".b08-milestone__box.is-drop-target rect {" in css + + +def test_155_요소_추가_안_이은_거래처_수기_입력(): + leg = lambda road, km, auto=None: {"road": road, "km": km, "auto_km": auto} # noqa: E731 + route = {"id": "r", "name": "a", "address": "", "via_base": True, "dir": None, "linked": False, + "legs": [leg(None, 5.0)], "rows": [{"item": "장비", "dest": "mid", "note": ""}]} # fmt: skip + data = {"site": {"mid_station_m": 700, "mid_road": "비포장"}, + "base": {"enabled": True, "name": "b", "address": "", "dir": None, "legs": [leg(None, 0.6)]}, + "routes": [route]} # fmt: skip + (a,) = compute_milestone(data, None) # 안 이음 = 구간 km 없음 · 중점 구간만 + assert (a["s2"], a["s1"], a["un"], a["total"]) == (0, 0, 0.7, 0.7) + assert _ts(f"computeMilestone({json.dumps(data)}, null)") == [a] + route["linked"] = True + (b,) = compute_milestone(data, None) + assert (b["s2"], b["s1"], b["total"]) == (5.0, 0.6, 6.3) + ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") + assert "route.linked = true;" in ui and 'label: "요소 추가"' in ui and "plus(box" not in ui + modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") + assert "linked: false," in modal and "export type PlaceTarget = { add: true } |" in modal + leg_ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts") + assert ( + 'createCheckbox("수기 입력' in leg_ui and "target.manual = manual.input.checked;" in leg_ui + ) + state = _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts") + assert "if (owner?.manual) return" in state # 수기 입력 = 카카오 길찾기 안 함 + layout = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts") + assert "if (!route || route.linked === false) return null;" in layout