Files
Aislo/B07_DesignDetail/B07_DesignDetail_UI_PlanManage.ts
T

211 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B07_DesignDetail_UI_PlanManage.ts
* [평면도 관리] 모달(PLAN 130-1) — 좌측 패널이 좁아 종류 · 대피소 자리 · 장 보기를 한 창에 모음.
*
* 왼쪽 = 종류 목록(× 지우기) · 종류 추가 · 업데이트 폼 · 대피소 · 사토장 자리 · 평면도 장 목록
* 오른쪽 = 고른 장 미리보기(보기 전용 CAD · 장 바꾸기) · [확정 풀기] · [CAD 에서 열기]
* 고치기는 지금 서버 길 그대로(종류 파일 · 확정 풀기) — 고칠 때마다 모달 안을 다시 받고,
* 닫을 때 `onClosed(changed)` 로 좌측 목록 · 도면을 다시 읽음.
* ========================================================================== */
import { showToast } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
import { createCadHost } from "@ui/cad_host/cad_host";
import {
deletePlanKind,
fetchDesignDrawing,
fetchDesignDrawingList,
fetchPlanKinds,
invalidateDesignDrawing,
type CadDrawing,
type DesignDrawingItem,
type PlanKindsState,
} from "./B07_DesignDetail_Api_Fetch";
import {
groupPlanSheets,
kindForm,
marksForm,
run,
type PlanKindsControls,
} from "./B07_DesignDetail_UI_PlanKinds";
export interface PlanManageControls {
projectId: string;
/** 모달을 닫음 — changed = 종류 · 자리 · 확정을 고쳤나 · openId = [CAD 에서 열기]로 고른 장. */
onClosed: (changed: boolean, openId?: string) => void;
}
function el<K extends keyof HTMLElementTagNameMap>(
tag: K,
className: string,
text = "",
): HTMLElementTagNameMap[K] {
const node = document.createElement(tag);
node.className = className;
if (text) node.textContent = text;
return node;
}
function button(className: string, text: string, onClick: () => void): HTMLButtonElement {
const node = el("button", className, text);
node.type = "button";
node.addEventListener("click", onClick);
return node;
}
export function openPlanManager(controls: PlanManageControls): void {
const { projectId } = controls;
let changed = false;
let closed = false;
/** 미리보기 중인 장 — 늦게 온 응답이 다른 장을 덮지 않게 이것과 맞춰 봄. */
let picked: string | undefined;
const preview = createCadHost<CadDrawing>({ title: "평면도 미리보기" });
const side = el("div", "b07-plan-manage__side");
const head = el("div", "b07-plan-manage__head");
const view = el("div", "b07-plan-manage__view");
view.append(head, preview.element);
// 종류 · 자리 폼은 좌측에 있던 것 그대로 — 고친 뒤 모달 안만 다시 세움(화면은 닫을 때).
const edits: PlanKindsControls = {
projectId,
onChanged: () => {
changed = true;
void refresh();
},
};
const kindList = (state: PlanKindsState, sheets: DesignDrawingItem[]): HTMLElement => {
const list = el("div", "b07-plan-tools__block b07-plan-manage__kinds");
list.append(el("strong", "", "종류 목록"));
for (const kind of state.kinds) {
const row = el("div", "b07-plan-kind__head");
row.dataset.planKind = kind.id;
const count = sheets.filter((item) => item.plan_kind === kind.id).length;
row.append(el("span", "b07-plan-kind__name", `${kind.name} · ${count}장`));
const remove = button("b07-plan-tools__remove", "×", () => {
if (
!window.confirm(`「${kind.name}」 종류를 지웁니다. 다시 쓰려면 「새 종류」로 추가합니다.`)
)
return;
void run(() => deletePlanKind(projectId, kind.id), edits);
});
remove.title = `「${kind.name}」 종류 지우기`;
row.append(remove);
list.append(row);
}
return list;
};
const sheetList = (sheets: DesignDrawingItem[]): HTMLElement => {
const list = el("div", "b07-plan-tools__block b07-plan-manage__sheets");
list.append(el("strong", "", "평면도 장"));
for (const group of groupPlanSheets(sheets)) {
for (const { drawing, label } of group.sheets) {
const item = button("b07-drawing-button", label, () => void show(drawing, label));
item.dataset.drawingId = drawing.id;
item.dataset.confirmed = String(drawing.confirmed);
list.append(item);
}
}
return list;
};
const markActive = (): void =>
side
.querySelectorAll<HTMLElement>(".b07-plan-manage__sheets .b07-drawing-button")
.forEach((item) => (item.dataset.active = String(item.dataset.drawingId === picked)));
/** 장 하나를 미리보기에 — 보기 전용 · 확정이면 [확정 풀기](지금 [수정]과 같은 서버 길). */
const show = async (drawing: DesignDrawingItem, label: string): Promise<void> => {
picked = drawing.id;
markActive();
head.replaceChildren(el("strong", "", label));
preview.beginLoading();
try {
const response = await fetchDesignDrawing(projectId, drawing.id);
if (closed || picked !== drawing.id) return;
const badge = el("span", "b07-plan-manage__badge", response.confirmed ? "확정" : "확정 전");
badge.dataset.confirmed = String(response.confirmed);
head.append(badge);
if (response.confirmed)
head.append(
button("b07-plan-tools__button", "확정 풀기", () => {
void run(() => invalidateDesignDrawing(projectId, drawing.id), edits);
}),
);
head.append(button("b07-plan-tools__button", "CAD 에서 열기", () => finish(drawing.id)));
preview.load(
response.drawing,
null,
false,
{},
{
recoveryScope: "b07-plan-preview",
readOnly: true,
viewOnly: true,
hostTitle: "평면도 미리보기",
ribbonCollapsed: true,
panelsCollapsed: true,
},
);
} catch (error) {
if (closed || picked !== drawing.id) return;
preview.fail(error instanceof Error ? error.message : "평면도를 불러오지 못했습니다.");
}
};
/** 종류 · 장 목록을 서버에서 다시 받아 왼쪽을 세우고 보던 장(없으면 첫 장)을 다시 봄. */
const refresh = async (): Promise<void> => {
try {
const [state, list] = await Promise.all([
fetchPlanKinds(projectId),
fetchDesignDrawingList(projectId),
]);
if (closed) return;
const sheets = list.drawings.filter((item) => item.kind === "plan");
side.replaceChildren(
kindList(state, sheets),
kindForm(state, edits),
marksForm(state, edits),
sheetList(sheets),
);
const all = groupPlanSheets(sheets).flatMap((group) => group.sheets);
const keep = all.find((sheet) => sheet.drawing.id === picked) ?? all[0];
if (keep) void show(keep.drawing, keep.label);
else {
picked = undefined;
head.replaceChildren(el("span", "b07-plan-kind__name", "평면도 장이 없습니다."));
}
} catch (error) {
showToast(error instanceof Error ? error.message : "평면도 목록을 받지 못했습니다.", "error");
}
};
let closeModal = (): void => undefined;
/** 한 번만 — 미리보기 듣기를 멈추고 페이지에 알림. */
const finish = (openId?: string): void => {
if (closed) return;
closed = true;
preview.destroy();
closeModal();
controls.onClosed(changed, openId);
};
openModal({
title: "평면도 관리",
closeLabel: "닫기",
dialogClass: "b07-plan-manage",
beforeClose: () => {
finish();
return true;
},
mount: (body, close) => {
closeModal = close;
const grid = el("div", "b07-plan-manage__grid");
grid.append(side, view);
body.append(grid);
void refresh();
},
});
}