feat(structure): 구조물 목록 줄마다 잇기 램프 — 측점 왼쪽 고정 폭 칸 · 3행 × 열 · 초록 이음 · 빨강 필수 미지정 · 노랑 선택 미지정 · 툴팁 상자 · 항목(54-15)
- 램프 자리 = 폼 잇기 줄 규칙(명세 컨테이너 · 인스턴스 · 배관 흙막이 지형 단 수 · 정의 그룹 · 종류 단위 · B군 없음) - 단 수 바뀜 · 횡단 자료 도착 때 목록 다시 그림 - 지정 줄 왼쪽 선 = 안쪽 그림자(글 시작 자리 같게) - ui_checks/54-15_잇기_램프.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
This commit is contained in:
@@ -15,7 +15,17 @@ import {
|
||||
} from "./B05_Profile_Api_Structures";
|
||||
import type { PipeFacilityItem } from "./B05_Profile_UI_Structures_Panel";
|
||||
import { stationLabel as formatStation } from "../common_util/common_util_station";
|
||||
import { readSummaryItem, summaryTooltip } from "../common_util/common_util_summary_item";
|
||||
import {
|
||||
readGroupItems,
|
||||
readSummaryItem,
|
||||
summaryLabel,
|
||||
summaryTooltip,
|
||||
type SummaryItem,
|
||||
} from "../common_util/common_util_summary_item";
|
||||
import { STRUCTURE_SPECS } from "../A00_Common/design/design_structure_pipe";
|
||||
import { pipeExtraCount } from "../A00_Common/design/design_structure_pipe_calc";
|
||||
import { containerTitle, linkOf, readState } from "../A00_Common/design/design_structure_spec";
|
||||
import { asRoad } from "./B05_Profile_UI_Structures_Define";
|
||||
|
||||
export interface StructureListParams {
|
||||
/** 목록을 담을 <ul>. 그릴 때마다 통째로 갈아 끼운다. */
|
||||
@@ -62,6 +72,72 @@ export function etcName(structure: StructureInstance): string | null {
|
||||
return name || null;
|
||||
}
|
||||
|
||||
/** 잇기 자리 하나 — 상자 이름 · 이은 항목(없으면 null) · 필수 여부. */
|
||||
interface LinkLamp {
|
||||
title: string;
|
||||
item: SummaryItem | null;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
/** 줄의 잇기 자리(54-15) — 폼이 잇기 줄을 세우는 규칙 그대로: 명세 컨테이너마다(선택은 인스턴스마다 ·
|
||||
* 배관 흙막이는 지형이 세운 단까지) · 정의 그룹마다(옛 종류 단위 값 = 첫 그룹 · 54-6) · 그 밖은
|
||||
* 종류 단위 하나 · 종단배수(B군)는 없음. */
|
||||
export function linkLamps(
|
||||
type: StructureType | undefined,
|
||||
source: {
|
||||
type_id: string;
|
||||
options?: Record<string, string | number>;
|
||||
summary_item?: unknown;
|
||||
group_items?: unknown;
|
||||
},
|
||||
chainageM: number | null,
|
||||
): LinkLamp[] {
|
||||
const links = readGroupItems(source.group_items);
|
||||
const summary = readSummaryItem(source.summary_item);
|
||||
const spec = STRUCTURE_SPECS[source.type_id];
|
||||
if (spec?.containers.length) {
|
||||
const groupNames = Object.fromEntries((type?.groups ?? []).map((g) => [g.id, g.name]));
|
||||
// 배관 흙막이 = 명세 이름 「흙막이」가 정의 옛 이름 「흙막이 추가」보다 앞섬(54-1 · 시설 폼과 같음)
|
||||
const { soil_add: _old, ...pipeNames } = type?.spec_names ?? {};
|
||||
const names = source.type_id === "pipe" ? pipeNames : (type?.spec_names ?? {});
|
||||
const state = readState(spec, source.options ?? {});
|
||||
return spec.containers.flatMap((c): LinkLamp[] => {
|
||||
const title = containerTitle(c, names, groupNames[c.link]);
|
||||
if (c.required) return [{ title, item: links[c.link] ?? null, required: true }];
|
||||
const n = (c.id === "soil_add" ? pipeExtraCount(chainageM) : null) ?? state[c.id].length;
|
||||
return Array.from({ length: n }, (_, i) => ({
|
||||
title: `${title} ${i + 1}`,
|
||||
item: links[linkOf(c, i + 1)] ?? null,
|
||||
required: false,
|
||||
}));
|
||||
});
|
||||
}
|
||||
if (type?.groups?.length) {
|
||||
const first = type.groups[0].id;
|
||||
return type.groups.map((g) => ({
|
||||
title: g.name,
|
||||
item: links[g.id] ?? (g.id === first ? summary : null),
|
||||
required: true,
|
||||
}));
|
||||
}
|
||||
if (!type || type.group === "B") return [];
|
||||
return [{ title: "구조물 조합", item: summary, required: true }];
|
||||
}
|
||||
|
||||
/** 램프 칸 — 3행 × 열(위 → 아래 → 다음 열) · 색 = 이음 · 필수 미지정 · 선택 미지정 · 툴팁 = 상자 · 항목. */
|
||||
function lampCell(lamps: LinkLamp[]): HTMLElement {
|
||||
const cell = document.createElement("div");
|
||||
cell.className = "b05-route__lamps";
|
||||
for (const lamp of lamps) {
|
||||
const dot = document.createElement("i");
|
||||
dot.className = lamp.item ? "is-linked" : lamp.required ? "is-required" : "is-optional";
|
||||
cell.append(dot);
|
||||
}
|
||||
if (lamps.length)
|
||||
cell.title = lamps.map((lamp) => `${lamp.title} · ${summaryLabel(lamp.item)}`).join("\n");
|
||||
return cell;
|
||||
}
|
||||
|
||||
export function renderStructureList(params: StructureListParams): void {
|
||||
const { list, typeMap, intervalM, editingId, selectedPipeChainage } = params;
|
||||
list.replaceChildren();
|
||||
@@ -101,6 +177,20 @@ export function renderStructureList(params: StructureListParams): void {
|
||||
: (typeMap.get(row.pipe.facility)?.name ?? row.pipe.facility),
|
||||
),
|
||||
);
|
||||
const lamps = rows.map((row) =>
|
||||
row.kind === "structure"
|
||||
? linkLamps(typeMap.get(asRoad(row.structure).type_id), asRoad(row.structure), null)
|
||||
: linkLamps(
|
||||
typeMap.get(row.pipe.facility),
|
||||
{ ...row.pipe, type_id: row.pipe.facility },
|
||||
row.pipe.chainage_m,
|
||||
),
|
||||
);
|
||||
// 램프 칸 폭 = 목록에서 가장 많은 열 — 줄마다 글 시작 자리가 같게(54-15)
|
||||
list.style.setProperty(
|
||||
"--lamp-cols",
|
||||
String(Math.max(0, ...lamps.map((l) => Math.ceil(l.length / 3)))),
|
||||
);
|
||||
rows.forEach((row, index) => {
|
||||
const item = document.createElement("li");
|
||||
item.className = "b05-route__irregular-item";
|
||||
@@ -132,7 +222,7 @@ export function renderStructureList(params: StructureListParams): void {
|
||||
// 사용자). 패널이 막 펼쳐진 참이라 자리가 잡힌 다음 프레임에 민다.
|
||||
if (picked) requestAnimationFrame(() => item.scrollIntoView({ block: "nearest" }));
|
||||
}
|
||||
item.append(station, name);
|
||||
item.append(lampCell(lamps[index]), station, name);
|
||||
list.append(item);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -540,7 +540,7 @@ export function createStructuresSection(
|
||||
|
||||
return {
|
||||
root,
|
||||
facility: facilityOptions,
|
||||
facility: { ...facilityOptions, recount: () => (facilityOptions.recount(), renderList()) }, // 단 수 → 목록 램프(54-15)
|
||||
listRoot: list,
|
||||
hasSelection: () => editingId !== null || selectedPipeChainage !== null,
|
||||
setTypes(next, pick = () => true) {
|
||||
|
||||
@@ -479,7 +479,35 @@
|
||||
|
||||
/* 목록 줄 — 집계 항목이 지정된 구조물 · 관은 왼쪽 선 + 이름 색으로(밝음 · 어두움 같은 토큰). */
|
||||
.b05-route__irregular-item.is-assigned {
|
||||
border-left: 3px solid var(--color-success);
|
||||
/* 테두리 두께 대신 안쪽 그림자 — 글 시작 자리가 다른 줄과 같게(54-15) */
|
||||
border-left-color: var(--color-success);
|
||||
box-shadow: inset 2px 0 0 var(--color-success);
|
||||
}
|
||||
|
||||
/* 목록 줄 잇기 램프(54-15) — 측점 왼쪽 고정 폭 칸 · 3행 × 열(위 → 아래 → 다음 열) ·
|
||||
폭 = 목록에서 가장 많은 열(`--lamp-cols`) · 줄 높이를 늘리지 않음 */
|
||||
.b05-route__lamps {
|
||||
display: grid;
|
||||
flex: none;
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: repeat(3, 5px);
|
||||
grid-auto-columns: 5px;
|
||||
gap: 2px;
|
||||
align-self: center;
|
||||
width: max(0px, calc(var(--lamp-cols, 0) * 7px - 2px));
|
||||
}
|
||||
|
||||
.b05-route__lamps i {
|
||||
border-radius: 50%;
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.b05-route__lamps i.is-required {
|
||||
background: var(--color-danger);
|
||||
}
|
||||
|
||||
.b05-route__lamps i.is-optional {
|
||||
background: var(--color-warning);
|
||||
}
|
||||
|
||||
.b05-route__irregular-item.is-assigned span {
|
||||
|
||||
Reference in New Issue
Block a user