Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_3

This commit is contained in:
2026-10-11 14:10:07 +09:00
6 changed files with 76 additions and 10 deletions
@@ -217,6 +217,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;
@@ -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,
);
},
@@ -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));
@@ -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
@@ -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_폭_전체_지도_높이_한_값():
@@ -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