Files
Aislo/B08_Quantity/B08_Quantity_UI_Link.ts
T
eomsangdonandClaude Opus 5.5 266d003ce7 feat(B08): 미연결 구조물 바로 연결 — 고르기 모달 → 그 칸만 저장 → 다시 셈(154-2)
- 새 서버 길 PUT /api/projects/{id}/items/{사본 id}/link — 그 시설 조합 칸 하나만 정본에
  (관 지점 = pipe_points.json 노선 지문 그대로 · 박스/관 facility 로 가름 · 구조물 = 판번호 +1) → safe_sync
- 구조물도 모달 맨 위 「조합 연결」 칸(새 B08_Quantity_UI_Link) — 칸마다 연결 여부 · [선택]
- B05/B06 세션 초안(구조물 · 관) 있으면 막고 「B05 · B06 [저장] 먼저」 알림 · 저장 뒤 탭 몸통 새로 그림
- 공용 창구 projectItems().link 추가 · 새 시험 test_154_2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-09 19:11:16 +09:00

103 lines
4.4 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_Link.ts
* 구조물도 모달 맨 위 「조합 연결」 칸(PLAN 154-2) — 그 측점 미연결 시설의 칸 전부 + 연결 여부.
*
* [선택] → 공용 고르기 모달(`openItemPicker` · 누를 때 `import()`) → 바로 저장
* = PUT …/items/{사본 id}/link(그 칸 하나만 정본에 · 서버가 사본 맞춤) → 집계표 다시 셈
* B05/B06 세션 초안(구조물 · 관)이 있으면 막고 알림 — 그쪽 [저장]이 목록 통째로 덮어 연결이 사라짐
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton, showToast } from "@ui/ui_template_elements";
import { projectItems } from "@ui/ui_template_item_picker_project";
import { readPendingStructures } from "../B05_Profile/B05_Profile_Api_Structures";
import { readPendingPipes } from "../B05_Profile/B05_Profile_Api_Pipes_Draft";
import type { SummaryItem } from "../common_util/common_util_summary_item";
import type { UnlinkedFacility, UnlinkedSlot } from "./B08_Quantity_Summary_Data";
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
/** B05/B06 에 저장 안 한 구조물 · 관 조작이 있나(세션 초안). */
export const hasPendingDesign = (projectId: string): boolean =>
readPendingStructures(projectId) !== null || readPendingPipes(projectId) !== null;
/** 칸 하나 고르고 바로 저장 — 저장했으면 true(취소 · 막힘 · 실패 = false). */
async function pickAndLink(
projectId: string,
facility: UnlinkedFacility,
slot: UnlinkedSlot,
): Promise<boolean> {
if (hasPendingDesign(projectId)) {
showToast(L("B08_Quantity_Link_Pending"), "warning", 5000);
return false;
}
const { openItemPicker } = await import("@ui/ui_template_item_picker");
const result = await openItemPicker({
current: slot.item,
canCopy: true,
projectId,
structureId: slot.copy,
});
if (result.action === "cancel") return false;
const item: SummaryItem | null = result.action === "apply" ? result.item : null;
try {
const failed = await projectItems(projectId).link(slot.copy, facility.facility, item);
if (failed.length)
showToast(`${L("B08_Quantity_Link_CopyFailed")} — ${failed.join(", ")}`, "warning");
else showToast(L("B08_Quantity_Link_Saved"), "success");
return true;
} catch (error) {
showToast(`${L("B08_Quantity_Link_Failed")} — ${String(error)}`, "error");
return false;
}
}
/** 「조합 연결」 칸 — 시설마다 이름 + 칸 줄(이름 · 연결 여부 · [선택]). `onLinked` = 저장 뒤(모달 닫고 다시 셈). */
export function linkSection(
projectId: string,
facilities: readonly UnlinkedFacility[],
onLinked: () => void,
): HTMLElement {
const section = document.createElement("section");
section.className = "b08-sheets__combo b08-link";
section.dataset.b08Link = "section";
const title = document.createElement("h4");
title.className = "b08-sheets__title";
title.textContent = L("B08_Quantity_Link_Title");
section.append(title);
for (const facility of facilities) {
const head = document.createElement("p");
head.className = "b08-link__facility";
head.textContent = facility.name;
section.append(head);
for (const slot of facility.slots) {
const row = document.createElement("div");
row.className = "b08-link__slot";
row.dataset.b08LinkSlot = slot.copy;
const name = document.createElement("span");
name.textContent = slot.group ?? L("B08_Quantity_Link_Body");
const state = document.createElement("span");
state.className = slot.linked ? "b08-link__state" : "b08-link__state is-missing";
state.textContent = slot.linked
? `${L("B08_Quantity_Link_Linked")} · ${slot.item?.name ?? ""}`
: L("B08_Quantity_Summary_Unlinked");
const button = createButton({
label: L("B08_Quantity_Link_Pick"),
variant: "ghost",
size: "sm",
onClick: () => {
button.disabled = true;
void pickAndLink(projectId, facility, slot).then((saved) =>
saved ? onLinked() : (button.disabled = false),
);
},
});
row.append(name, state, button);
section.append(row);
}
}
return section;
}