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

This commit is contained in:
2026-10-11 16:34:03 +09:00
6 changed files with 95 additions and 20 deletions
@@ -204,6 +204,19 @@
white-space: nowrap;
}
/* 262-2 고정 시점 표지 — 둥근 붉은 알약(고르는 표지 = 기본 핀 + 흰 이름표와 구별) */
.b08-milestone__map-start {
padding: 3px 10px;
border: 2px solid #fff;
border-radius: 999px;
background: #e60046;
color: #fff;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
pointer-events: none;
}
/* 등록 · 연결선 모달(244-13) — 더 큰 창 · 2열 칸 */
.ui-modal.b08-milestone__modal {
width: min(960px, 94vw);
@@ -98,6 +98,8 @@ export interface MapPin {
lon: number;
lat: number;
movable: boolean;
/** 262-2 고정 시점 표지 — 끌기 · 맞춤 · 보기 이동에 안 쓰임 · 고르는 표지와 모양이 다름 */
fixed?: boolean;
}
export interface EditMap {
@@ -127,8 +129,9 @@ export function editMap(
loadKakaoMaps(projectId).then(
(maps) =>
requestAnimationFrame(() => {
const moving = pins.filter((p) => !p.fixed);
const points = [
...pins.map((p) => new maps.LatLng(p.lat, p.lon)),
...moving.map((p) => new maps.LatLng(p.lat, p.lon)),
...lines.flat().map(([lat, lon]) => new maps.LatLng(lat, lon)),
];
const c = opts.center?.();
@@ -156,6 +159,17 @@ export function editMap(
),
...pins.flatMap((p) => {
const at = new maps.LatLng(p.lat, p.lon);
if (p.fixed)
return [
new maps.CustomOverlay({
position: at,
content: el("span", {
className: "b08-milestone__map-start",
text: p.name,
}),
yAnchor: 0.5,
}),
];
const marker = new maps.Marker({ position: at, title: p.name, draggable: p.movable });
if (p.movable)
maps.event.addListener(marker, "dragend", () => {
@@ -176,7 +190,7 @@ export function editMap(
if (fitted) {
// 보기 밖 표지만 그쪽으로 옮김(줌 그대로)
const view = map.getBounds();
const out = pins.find((p) => !view.contain(new maps.LatLng(p.lat, p.lon)));
const out = moving.find((p) => !view.contain(new maps.LatLng(p.lat, p.lon)));
if (out) map.panTo(new maps.LatLng(out.lat, out.lon));
} else if (points.length > 1) {
const bounds = new maps.LatLngBounds();
@@ -106,8 +106,8 @@ export function openPlaceModal(
const title = target.add
? "요소 추가 — 기준점 · 거래처"
: base
? `기준점 — ${base.name || "기준점"}`
: `거래처 — ${route!.name || "거래처"}`; // 254-3 표 머리 글(항목 정보) 없음
? base.name || "기준점"
: route!.name || "거래처"; // 262-1 「—」 앞 글자 없음
openModal({
title,
closeLabel: "닫기",
@@ -259,7 +259,7 @@ export interface SiteAddress {
/** 시점(공사지) 상자 — 중점 측점(km · 저장은 m) · 시점 → 중점 도로 · 아래 카카오 지도(시점 · 중점 표지) */
export function openSiteInfo(projectId: string): void {
openModal({
title: "공사지 — 시점 · 중점",
title: "시점 · 중점",
closeLabel: "닫기",
dialogClass: "b08-milestone__modal",
mount: (body, close, actions) => {
+21 -13
View File
@@ -12,7 +12,7 @@ import { createButton, createInputField, el, showToast } from "@ui/ui_template_e
import { searchAddress, type AddressCandidate } from "../B01_Dashboard/B01_Dashboard_Api_Fetch";
import { request } from "./B08_Quantity_UI_Hydro";
import { editMap } from "./B08_Quantity_UI_Milestone_KakaoMap";
import { siteInfo } from "./B08_Quantity_UI_Milestone_State";
import { liveMilestone, startPoint } from "./B08_Quantity_UI_Milestone_State";
export interface Place {
address: string;
@@ -52,27 +52,35 @@ export function placePicker(projectId: string, initial: Place, name: () => strin
list.hidden = true;
const where = el("p", { className: "b08-milestone__place" });
where.dataset.b08MilestonePlace = "value";
// 262-2 시점 = 거리이정표가 쓰는 시점(끌어 옮긴 덮개 · 없으면 노선 시점)
const start = (): [number, number] | null => startPoint(liveMilestone(projectId));
const map = editMap(projectId, {
hint: "지도 누름 = 그 자리를 소재지로 · 표지 끌어 옮김",
onClick: (lon, lat) => void moveTo(lon, lat),
onMove: (_key, lon, lat) => void moveTo(lon, lat),
center: () => siteInfo()?.start_lonlat ?? null,
center: start,
});
const draw = (): void => {
where.textContent = `소재지 — ${place.address || "주소 찾기 · 지도에서 고르세요"}`;
const s = start();
map.set(
place.lon != null && place.lat != null
? [
{
key: "place",
name: name() || "소재지",
lon: place.lon,
lat: place.lat,
movable: true,
},
]
: [],
[
...(s
? [{ key: "start", name: "시점", lon: s[0], lat: s[1], movable: false, fixed: true }]
: []),
...(place.lon != null && place.lat != null
? [
{
key: "place",
name: name() || "소재지",
lon: place.lon,
lat: place.lat,
movable: true,
},
]
: []),
],
[],
);
};
@@ -1,6 +1,6 @@
"""254-3 B08 거리이정표 상자 모달 제목 · 장소 그림(사용자 162-6 · 8).
① 상자 누름 모달 제목 = 「거래처 — 이름」 · 「기준점 — 이름」(표 머리 글 「항목 정보」 없음)
① 상자 누름 모달 제목 = 이름만(262-1 · 「—」 앞 글자 없음 · 표 머리 글 「항목 정보」 없음)
② 장소 그림(256-1 `place_images`) = 상자 안 글 아래 작게 · 그림 없으면 자리 없음(상자 높이 그대로)
"""
@@ -67,7 +67,7 @@ def test_장소_그림_상자_안_글_아래():
def test_모달_제목_화면_그림():
modal = _read(B08 + "UI_Milestone_Modal.ts")
assert "`거래처 — ${route!.name" in modal and "`기준점 — ${base.name" in modal
assert 'route!.name || "거래처"' in modal and 'base.name || "기준점"' in modal # 262-1
assert "항목 정보 —" not in modal
ui = _read(B08 + "UI_Milestone_Diagram.ts")
assert "placeImageBlobUrl(projectId, id)" in ui and "placeImageKey(id)" in ui
@@ -0,0 +1,40 @@
"""262 B08 거리이정표 상자 창 제목 · 소재지 지도 시점 표지(사용자 164-1).
262-1 거래처 · 기준점 창 제목 = 이름만(빈 이름 = 「거래처」 · 「기준점」) · 시점 창 = 「시점 · 중점」 · 요소 추가 창 그대로
262-2 소재지 지도 = 시점 표지 고정(끌기 없음 · 맞춤에서 빠짐) · 시점 없으면 표지 없음 · 소재지 없으면 시점 중심
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
B08 = "B08_Quantity/B08_Quantity_UI_Milestone"
def _read(name):
return (ROOT / f"{B08}{name}").read_text(encoding="utf-8")
def test_제목_이름만():
modal = _read("_Modal.ts")
title = modal[modal.index("const title = target.add") : modal.index("openModal({")]
assert '? base.name || "기준점"' in title and ': route!.name || "거래처"' in title
assert '"요소 추가 — 기준점 · 거래처"' in title
assert 'title: "시점 · 중점",' in modal and "공사지 —" not in modal
assert re.search(r"title: `?[^\n]*(거래처|기준점|공사지) —", modal) is None
def test_시점_표지_고정():
place = _read("_Place.ts")
assert "startPoint(liveMilestone(projectId))" in place # 거리이정표가 쓰는 시점
assert "center: start" in place # 소재지 없음 = 시점 중심
assert "movable: false, fixed: true" in place and 'name: "시점"' in place
assert "...(s" in place and " : [])," in place # 시점 없음 = 표지 없음
kakao = _read("_KakaoMap.ts")
assert "fixed?: boolean;" in kakao
assert "const moving = pins.filter((p) => !p.fixed);" in kakao
assert "...moving.map(" in kakao # 처음 보기 = 소재지 중심(시점은 맞춤에서 빠짐)
assert "moving.find(" in kakao # 보기 이동에서도 빠짐
assert "if (p.fixed)" in kakao and "b08-milestone__map-start" in kakao
css = _read(".css")
assert ".b08-milestone__map-start {" in css