feat(b08): 거리이정표 단가연결 램프 · 우클릭 단가연결 · 다이어그램 틀 높이 맞춤 (251-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
This commit is contained in:
@@ -268,3 +268,19 @@
|
||||
.b08-milestone__route-addr {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 단가연결 램프(251-4) — 항목 칸 앞 · 노랑 = 없음 · 초록 = 연결 */
|
||||
[data-b08-milestone="table"] td.b08-milestone__lamp::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-warning);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[data-b08-milestone="table"] td.b08-milestone__lamp.is-linked::before {
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,16 @@
|
||||
* 입력 = 더블클릭(243) · 거래처 이름을 고치면 그 경로 이름(하위 탭)도
|
||||
* 위쪽 다이어그램 = `…_UI_Milestone_Diagram`(상자 누름 = 모달 · [+] = 항목 추가 모달 · 팬 · 줌)
|
||||
* 경로 탭 = `…_UI_Milestone_Route`(길찾기 지도 그림만) · 데이터 · [저장] = `…_UI_Milestone_State`
|
||||
* 단가연결(251-4) = 항목 칸 앞 램프(초록 = 연결 · 노랑 = 없음 · 호버 = 무엇) · 줄 우클릭 [단가연결] ·
|
||||
* [단가연결 풀기] — 창 = B09 설계내역서와 같은 `openPriceLink` · 경로 `ref` · B09 운반공 줄이 받음
|
||||
* ========================================================================== */
|
||||
|
||||
import "./B08_Quantity_UI_Milestone.css";
|
||||
import { createDataTable, type DataColumn } from "@ui/ui_template_data_table";
|
||||
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
||||
import { attachHoverInfo } from "@ui/ui_template_hover_info";
|
||||
import type { Row } from "../B09_Estimation/B09_Estimation_Calc_Base";
|
||||
import { openPriceLink } from "../B09_Estimation/B09_Estimation_UI_Tabs_Bill_Link";
|
||||
import { computeMilestone, type MilestoneRoute } from "./B08_Quantity_Milestone_Calc";
|
||||
import { createDiagram } from "./B08_Quantity_UI_Milestone_Diagram";
|
||||
import {
|
||||
@@ -35,6 +40,18 @@ import {
|
||||
const KM = { kind: "number", decimals: 3, trim: true, width: 72 } as const;
|
||||
const ROAD = ["도로 구분 (km)"];
|
||||
const SUM = ["계 (km)"];
|
||||
/** 단가연결 램프 칸 class(`B08_Quantity_UI_Milestone.css`) */
|
||||
const LAMP = "b08-milestone__lamp";
|
||||
|
||||
/** 램프 호버 글 — 연결된 것 */
|
||||
export function refText(ref: Record<string, unknown> | null | undefined): string {
|
||||
if (!ref) return "단가연결 없음 — 줄 우클릭 [단가연결]";
|
||||
const what =
|
||||
ref.tab === "logic"
|
||||
? `단가산출 ${String(ref.code ?? "")} ${String(ref.name ?? "")}`
|
||||
: `일위대가 ${String(ref.name ?? ref.key ?? "")}`;
|
||||
return `단가연결 — ${what.trim()}`;
|
||||
}
|
||||
|
||||
/** 표 열(244-13) — 2차포장 열 = 기준점 있을 때만 · 기준점 없음 = 포장(2차 + 1차 + 포장) · 비포장 · 합계 ·
|
||||
* 기준점 있음 = 거침 안 한 거래처 「포장」 값이 있으면 포장 열도 */
|
||||
@@ -53,7 +70,7 @@ export function milestoneColumns(withBase: boolean, anyPaved: boolean): DataColu
|
||||
{ key: "un", label: "비포장", group: ROAD, ...KM },
|
||||
];
|
||||
return [
|
||||
{ key: "item", label: "항목", width: 120 },
|
||||
{ key: "item", label: "항목", width: 134 },
|
||||
{ key: "company", label: "거래처 이름", width: 120 },
|
||||
{ key: "dest", label: "목적지", width: 72 },
|
||||
...roads,
|
||||
@@ -84,6 +101,8 @@ export function addRoute(projectId: string, init: Partial<MilestoneRoute> = {}):
|
||||
function tableView(projectId: string): MilestoneView {
|
||||
const diagram = createDiagram(projectId, (init) => addRoute(projectId, init));
|
||||
let keys: Array<{ route: string; index: number }> = [];
|
||||
const routeAt = (i: number) =>
|
||||
liveMilestone(projectId).routes.find((r) => r.id === keys[i]?.route);
|
||||
const makeTable = (columns: DataColumn[]) => {
|
||||
const t = createDataTable({
|
||||
columns,
|
||||
@@ -91,6 +110,7 @@ function tableView(projectId: string): MilestoneView {
|
||||
caption: "거리이정표",
|
||||
empty: "",
|
||||
editOnDouble: true,
|
||||
cellClass: (i, key) => (key !== "item" ? null : routeAt(i)?.ref ? `${LAMP} is-linked` : LAMP),
|
||||
editable: (_i, key) => key === "item" || key === "company" || key === "note",
|
||||
onEdit: (i, key, text) => {
|
||||
const at = keys[i];
|
||||
@@ -116,6 +136,26 @@ function tableView(projectId: string): MilestoneView {
|
||||
});
|
||||
trackCells(t.root);
|
||||
t.root.dataset.b08Milestone = "table";
|
||||
attachHoverInfo(
|
||||
t.root,
|
||||
(cell) => refText(routeAt(Number(cell.closest("tr")?.dataset.i))?.ref),
|
||||
`td.${LAMP}`,
|
||||
);
|
||||
t.root.addEventListener("contextmenu", (event) => {
|
||||
const tr = (event.target as Element).closest<HTMLElement>("tr[data-i]");
|
||||
const route = tr ? routeAt(Number(tr.dataset.i)) : undefined;
|
||||
if (!route) return;
|
||||
event.preventDefault();
|
||||
const link = (ref: Row | null, from?: Row): void =>
|
||||
editRoute(projectId, route.id, (_, r) => {
|
||||
// 호버 글 이름(사전조합 · 구조물 조합 = 들여온 대가 이름) — B09 는 tab · key 만 읽음
|
||||
r.ref = ref && { ...ref, name: ref.name ?? from?.name ?? "" };
|
||||
});
|
||||
pickAtCell([
|
||||
["단가연결", () => void openPriceLink({ apply: link })],
|
||||
...(route.ref ? [["단가연결 풀기", () => link(null)] as [string, () => void]] : []),
|
||||
]);
|
||||
});
|
||||
return t;
|
||||
};
|
||||
let mode = "false:false";
|
||||
@@ -177,7 +217,7 @@ export function renderMilestone(host: HTMLElement, projectId: string): void {
|
||||
};
|
||||
tabs.replaceChildren(
|
||||
tab("table", "거리이정표"),
|
||||
...routes.map((r, i) => tab(r.id, r.name.trim() || `거래처 ${i + 1}`)),
|
||||
...routes.map((r, i) => tab(r.id, r.name.trim() || r.rows[0]?.item || `거래처 ${i + 1}`)),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -421,12 +421,15 @@ export function createDiagram(
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
// 틀 크기가 바뀌면(창 · 패널 접기) 그 비율로 다시 그림
|
||||
// 틀 크기가 바뀌면(창 · 패널 접기 · 높이 = 58vh) 그 비율로 다시 그림 — 그림이 틀보다 크면 아래가 잘림(251)
|
||||
let lastW = 0;
|
||||
let lastH = 0;
|
||||
new ResizeObserver(() => {
|
||||
const w = host.clientWidth;
|
||||
if (w && Math.abs(w - lastW) > 1) {
|
||||
const h = host.clientHeight;
|
||||
if (w && (Math.abs(w - lastW) > 1 || Math.abs(h - lastH) > 1)) {
|
||||
lastW = w;
|
||||
lastH = h;
|
||||
refresh();
|
||||
}
|
||||
}).observe(host);
|
||||
|
||||
@@ -34,7 +34,8 @@ let lastCell: Element | null = null;
|
||||
|
||||
/** 표 칸 누름 자리 기억 — 「▾」 메뉴를 그 칸 아래에 엶 */
|
||||
export function trackCells(root: HTMLElement): void {
|
||||
root.addEventListener("click", (e) => (lastCell = (e.target as Element).closest("td")), true);
|
||||
for (const type of ["click", "contextmenu"])
|
||||
root.addEventListener(type, (e) => (lastCell = (e.target as Element).closest("td")), true);
|
||||
}
|
||||
|
||||
export function pickAtCell(items: MapContextMenuItem[]): void {
|
||||
|
||||
@@ -107,3 +107,38 @@ def test_251_5_자동_줄_단가_줄_키_짝():
|
||||
ts = (ROOT / "B09_Estimation" / "B09_Estimation_Calc_Bill.ts").read_text(encoding="utf-8")
|
||||
assert 'ref_price(item.get("ref"), books, hopyo, item["key"])' in py
|
||||
assert "refPrice(item.ref ?? null, book, hopyo, item.key)" in ts
|
||||
|
||||
|
||||
def test_251_4_거리이정표_램프_우클릭_단가연결():
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
src = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Milestone.ts").read_text(encoding="utf-8")
|
||||
fn = (
|
||||
"export function refText"
|
||||
+ src.split("export function refText")[1].split("\n}\n")[0]
|
||||
+ "\n}\n"
|
||||
)
|
||||
js = (
|
||||
fn
|
||||
+ """
|
||||
console.log(JSON.stringify([refText(null),
|
||||
refText({ tab: "logic", code: "GF000134", name: "골재운반 모래" }),
|
||||
refText({ tab: "dg", key: "DG:x", name: "회사 조합" })]));
|
||||
"""
|
||||
)
|
||||
run = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"],
|
||||
input=js, capture_output=True, text=True, encoding="utf-8", timeout=60,
|
||||
) # fmt: skip
|
||||
assert run.returncode == 0, run.stderr
|
||||
assert json.loads(run.stdout) == [
|
||||
"단가연결 없음 — 줄 우클릭 [단가연결]",
|
||||
"단가연결 — 단가산출 GF000134 골재운반 모래",
|
||||
"단가연결 — 일위대가 회사 조합",
|
||||
]
|
||||
# 램프 = 항목 칸 · 우클릭 = [단가연결] · 연결 있으면 [단가연결 풀기] · 창 = B09 와 같은 것
|
||||
assert 'key !== "item" ? null : routeAt(i)?.ref ? `${LAMP} is-linked` : LAMP' in src
|
||||
assert "openPriceLink({ apply: link })" in src and '"단가연결 풀기"' in src
|
||||
diagram = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Milestone_Diagram.ts").read_text("utf-8")
|
||||
assert "Math.abs(h - lastH) > 1" in diagram # 틀 높이 바뀜 = 다시 그림(아래 잘림 없음)
|
||||
|
||||
Reference in New Issue
Block a user