Files
Aislo/B08_Quantity/B08_Quantity_UI_Link.ts
T
eomsangdonandClaude Opus 5.5 ca98d9799e feat(b08): 조합 연결 [연결 끊기] · 단추 모달 [닫기] 양식(156-1 · 156-3)
- 모달 「조합 연결」 칸 = 그 측점 모든 시설 칸(집계 응답 facilities) · 연결된 칸만 [연결 끊기]
- 끊기 = 확인 창 → link 길에 null(그 칸만 비움 · 판 +1 · 노선 지문 그대로) · 사본 맞춤 안 부름(사본 폴더 그대로)
- B05/B06 초안이면 막고 알림 · 끊은 뒤 다시 셈(「· 미연결」)
- [선택] · [연결 끊기] = ghost 기본 크기(모달 [닫기] 같은 양식)
- 시험 test_156_1_b08_unlink

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

131 lines
5.8 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_Link.ts
* 구조물도 모달 맨 위 「조합 연결」 칸(PLAN 154-2 · 156) — 그 측점 시설의 칸 전부 + 연결 여부.
*
* [선택] → 공용 고르기 모달(`openItemPicker` · 누를 때 `import()`) → 바로 저장
* = PUT …/items/{사본 id}/link(그 칸 하나만 정본에 · 서버가 사본 맞춤) → 집계표 다시 셈
* [연결 끊기](연결된 칸만) → 확인 창 → 같은 길에 null(그 칸만 비움 · 사본 폴더 그대로 · 156-1)
* 단추 = 모달 머리 [닫기]와 같은 양식(ghost · 기본 크기 · 156-3)
* B05/B06 세션 초안(구조물 · 관)이 있으면 막고 알림 — 그쪽 [저장]이 목록 통째로 덮어 연결이 사라짐
* ========================================================================== */
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton, showConfirmDialog, 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;
}
}
/** 칸 하나 연결 끊기 — 끊었으면 true(취소 · 막힘 · 실패 = false · 156-1). */
async function unlink(projectId: string, facility: UnlinkedFacility, slot: UnlinkedSlot) {
if (hasPendingDesign(projectId)) {
showToast(L("B08_Quantity_Link_Pending"), "warning", 5000);
return false;
}
if (!(await showConfirmDialog(L("B08_Quantity_Link_UnlinkAsk"), L("B08_Quantity_Link_Unlink"))))
return false;
try {
await projectItems(projectId).link(slot.copy, facility.facility, null);
showToast(L("B08_Quantity_Link_Unlinked"), "success");
return true;
} catch (error) {
showToast(`${L("B08_Quantity_Link_UnlinkFailed")} — ${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 buttons = document.createElement("span");
buttons.className = "b08-link__buttons";
const act = (label: string, run: typeof pickAndLink): HTMLButtonElement => {
const button = createButton({
label,
variant: "ghost", // 모달 [닫기]와 같은 양식(156-3)
onClick: () => {
buttons.querySelectorAll("button").forEach((b) => (b.disabled = true));
void run(projectId, facility, slot).then((saved) =>
saved
? onLinked()
: buttons.querySelectorAll("button").forEach((b) => (b.disabled = false)),
);
},
});
return button;
};
buttons.append(act(L("B08_Quantity_Link_Pick"), pickAndLink));
if (slot.linked) buttons.append(act(L("B08_Quantity_Link_Unlink"), unlink));
row.append(name, state, buttons);
section.append(row);
}
}
return section;
}