- 새 서버 길 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
103 lines
4.4 KiB
TypeScript
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;
|
|
}
|