feat(b08): 거리이정표 선 연결 = 연결 점 끌기 (250-3)
- 거래처 상자 ● 를 끌어 기준점 = 기준점 거침 · 공사지 = 시점으로 바로 - 바뀌면 그 구간 km 비움 + 길찾기 한 번 · 등록 모달 「기준점 거침」 체크 없앰 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -232,3 +232,28 @@
|
||||
pointer-events: stroke;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 154 연결 점 ● — 거래처 상자 오른쪽 가운데 · 끌어 기준점 · 공사지에 놓으면 연결 */
|
||||
.b08-milestone__connect {
|
||||
fill: var(--color-success);
|
||||
stroke: var(--color-surface);
|
||||
stroke-width: 2;
|
||||
cursor: crosshair;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.b08-milestone__box:hover .b08-milestone__connect {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.b08-milestone__wire {
|
||||
stroke: var(--color-success);
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 6 4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.b08-milestone__box.is-drop-target rect {
|
||||
stroke-width: 3;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
@@ -34,8 +34,9 @@ import {
|
||||
type DiagramBox,
|
||||
} from "./B08_Quantity_Milestone_Layout";
|
||||
import { openLegInfo } from "./B08_Quantity_UI_Milestone_LegModal";
|
||||
import { openPlaceModal, openSiteInfo } from "./B08_Quantity_UI_Milestone_Modal";
|
||||
import { openPlaceModal, openSiteInfo, toastKm } from "./B08_Quantity_UI_Milestone_Modal";
|
||||
import {
|
||||
findSegmentKm,
|
||||
liveMilestone,
|
||||
putMilestone,
|
||||
roadOptions,
|
||||
@@ -218,6 +219,69 @@ export function createDiagram(
|
||||
});
|
||||
};
|
||||
|
||||
/** 154 연결 점 ● — 거래처 상자 오른쪽 가운데 · 끌어 기준점 상자 = 기준점 거침 · 공사지(시점 · 중점) = 시점으로 바로 ·
|
||||
* 빈 곳 · 같은 연결 = 취소 · 바뀌면 그 거래처 구간 새로(km 비움) + 길찾기 한 번(km · 경로 그림) */
|
||||
const connectDot = (box: DiagramBox, shift: SVGGElement): SVGCircleElement => {
|
||||
const cx = box.x + box.w;
|
||||
const cy = box.y + box.h / 2;
|
||||
const dot = svg("circle", { class: "b08-milestone__connect", cx, cy, r: 7 });
|
||||
dot.append(svg("title", {}, "끌어서 기준점 · 공사지에 놓으면 연결"));
|
||||
dot.addEventListener("click", (e) => e.stopPropagation()); // 상자 모달 안 뜸
|
||||
dot.addEventListener("pointerdown", (down) => {
|
||||
if (down.button !== 0) return;
|
||||
down.stopPropagation(); // 상자 끌기 · 팬 안 함
|
||||
down.preventDefault();
|
||||
const toLocal = (e: PointerEvent): [number, number] => {
|
||||
const m = shift.getScreenCTM();
|
||||
if (!m) return [cx, cy];
|
||||
const p = new DOMPoint(e.clientX, e.clientY).matrixTransform(m.inverse());
|
||||
return [p.x, p.y];
|
||||
};
|
||||
const wire = svg("line", { class: "b08-milestone__wire", x1: cx, y1: cy, x2: cx, y2: cy });
|
||||
shift.append(wire);
|
||||
let over: Element | null = null;
|
||||
const targetOf = (e: PointerEvent): "base" | "site" | null => {
|
||||
const hit = document.elementFromPoint(e.clientX, e.clientY);
|
||||
const g = hit?.closest<SVGGElement>("[data-b08-milestone-box]");
|
||||
const id = g?.dataset.b08MilestoneBox;
|
||||
if (id === "base") return "base";
|
||||
return id === "site" || id === "start" || id === "mid" ? "site" : null;
|
||||
};
|
||||
const move = (e: PointerEvent): void => {
|
||||
const [x, y] = toLocal(e);
|
||||
wire.setAttribute("x2", String(x));
|
||||
wire.setAttribute("y2", String(y));
|
||||
const t = targetOf(e);
|
||||
const g = t ? shift.querySelector(`[data-b08-milestone-box="${t}"]`) : null;
|
||||
if (g !== over) {
|
||||
over?.classList.remove("is-drop-target");
|
||||
g?.classList.add("is-drop-target");
|
||||
over = g;
|
||||
}
|
||||
};
|
||||
const up = (e: PointerEvent): void => {
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
wire.remove();
|
||||
over?.classList.remove("is-drop-target");
|
||||
const t = targetOf(e);
|
||||
const fresh = liveMilestone(projectId);
|
||||
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;
|
||||
if (via === now) return; // 같은 연결 = 그대로
|
||||
route.via_base = via;
|
||||
route.legs = [{ road: null, km: null }]; // 옛 구간 km = 다른 길 — 새로 길찾기
|
||||
putMilestone(projectId, fresh);
|
||||
void findSegmentKm(projectId, route.id).then(toastKm);
|
||||
};
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
});
|
||||
return dot;
|
||||
};
|
||||
|
||||
// 153 [자동 정렬] — 끌어 놓은 자리 모두 지우고 방위 자동 자리로
|
||||
const arrange = createButton({
|
||||
label: "자동 정렬",
|
||||
@@ -318,6 +382,7 @@ 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);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* 2열(분류(기준위치 · 거래처) · 항목(손 입력 + 분류 따라 제안 · 244-14) · 거래처 이름 · 목적지) + 소재지(주소 찾기 ·
|
||||
* 다음(카카오) 지도에서 고르기 · `…_UI_Milestone_Place`) · 추가 = [등록] · 상자 = [수정] · [삭제]
|
||||
* 상자 하나 = 항목 하나(149 · [+ 줄] 없음 · 같은 거래처라도 항목마다 따로 등록) · 경로 값은 연결선 모달
|
||||
* 연결(기준점 거침 · 시점으로 바로) = 처음 = [+] 누른 상자 쪽 · 그 뒤 = 다이어그램 연결 점 ● 끌기(154 · 체크 없앰)
|
||||
* 분류 기준위치 = 기준점(하나 · 항목 · 목적지 없음 · 있으면 고르기 막음) · 방위 = 소재지 좌표로 자동 자리
|
||||
* 구간 km · 도로 구분 · 길찾기 = 연결선 모달(`…_UI_Milestone_LegModal`) · [등록] · 소재지 옮김 뒤 그 구간 한 번
|
||||
* 시점(공사지) 상자 = 프로젝트 등록 주소 · VWorld 주소(좌표 → 주소 · 서버) · 아래 지도
|
||||
@@ -13,7 +14,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import "../B01_Dashboard/B01_Dashboard_UI_Style.css";
|
||||
import { createCheckbox } from "@ui/ui_template_checkbox";
|
||||
import {
|
||||
createButton,
|
||||
createInputField,
|
||||
@@ -130,9 +130,6 @@ export function openPlaceModal(
|
||||
const nameLabel = name.root.querySelector(".ui-field__label");
|
||||
// 상자 하나 = 항목 하나(149) — 옛 자료의 둘째 줄부터는 손대지 않고 그대로 둠
|
||||
const first = rowField(route?.rows[0] ?? { item: "", dest: "start", note: "" }, kind);
|
||||
const via = createCheckbox("기준점 거침");
|
||||
via.input.checked = route?.via_base ?? true;
|
||||
via.root.hidden = !route || !data.base.enabled;
|
||||
const old = editBase ? data.base : route;
|
||||
const place = placePicker(
|
||||
projectId,
|
||||
@@ -175,11 +172,10 @@ export function openPlaceModal(
|
||||
if (!r) return close();
|
||||
Object.assign(r, where, {
|
||||
name: text || r.name,
|
||||
via_base: via.input.checked,
|
||||
rows: rowValues(),
|
||||
});
|
||||
putMilestone(projectId, fresh);
|
||||
if (moved(where) || r.via_base !== route.via_base) segment = r.id;
|
||||
if (moved(where)) segment = r.id;
|
||||
} else {
|
||||
if (!text) return name.setError("거래처 이름을 넣으세요.");
|
||||
const viaBase = target.add && target.viaBase;
|
||||
@@ -219,7 +215,6 @@ export function openPlaceModal(
|
||||
className: "b08-milestone__grid",
|
||||
children: [sort.root, first.item.root, name.root, first.dest.root],
|
||||
}),
|
||||
via.root,
|
||||
head("소재지 — 주소 찾기 · 지도에서 고르기(방위는 이 자리로 자동)"),
|
||||
place.root,
|
||||
);
|
||||
|
||||
@@ -104,3 +104,18 @@ def test_153_끌어_놓은_자리_레이아웃():
|
||||
) # fmt: skip
|
||||
assert out.returncode == 0, out.stderr
|
||||
assert sorted(json.loads(out.stdout)) == [["base", 500, -40], ["r", -700, 300]]
|
||||
|
||||
|
||||
def test_154_연결_점_끌어_잇기():
|
||||
ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts")
|
||||
assert 'if (box.kind === "route") g.append(connectDot(box, shift));' in ui
|
||||
assert 'const via = t === "base";' in ui and "route.via_base = via;" in ui
|
||||
assert "route.legs = [{ road: null, km: null }];" in ui # 옛 구간 km 비움
|
||||
assert "void findSegmentKm(projectId, route.id).then(toastKm);" in ui # 새 길찾기 한 번
|
||||
assert "if (via === now) return;" in ui # 같은 연결 = 그대로
|
||||
modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts")
|
||||
assert (
|
||||
'createCheckbox("기준점 거침")' not in modal and "via_base: via.input.checked" not in modal
|
||||
)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user