- 좌측 [엑셀 올리기] → [파일 업로드](구조물 조합 층 단추 꼴 · 보통 크기 · m02-layers__btn) - 파일 줄 누름 · 올린 직후 = 엑셀 보기 창 · 본 칸 호표 표(Hopyo_View) 지움 · 고름 표시 = 창 열린 동안 - 엑셀 보기 머리 막대 = 시트 · 호표 수 · 검산 · 제안 · 셈 중 · [삭제](옛 본 칸 표 몫) - 호표 목록 제N호표 오른쪽 [수정] = 그 호표 확인 창 · 펼침 표 · [확인 창 열기] 지움 - [고른 N개 확인하고 만들기] → [저장] = 물음 한 번 뒤 체크 줄로 하위 호표부터 생성 · 체크 줄 없음 건너뜀 · 409 안내 - 목록 숫자 다 연결(같고 0 넘음) = 녹색(var(--color-success)) - 별칭 장부 뜻 한 줄(LEDGER_HINT) · 갈래 글 로직없음 · 사용안함 · [변경 취소] · [로직 추가] · [내 층 복사] · [삭제] Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
155 lines
6.0 KiB
TypeScript
155 lines
6.0 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_Side.ts
|
|
* 좌측 「일위대가 실무자료」 컨테이너(PLAN 324-4 · 359-1 · 일위대가 사전조합 아래) — 층 = 구조물 조합 층(화면 층) ·
|
|
* [파일 업로드](구조물 조합 층 단추 꼴 `m02-layers__btn` · 숨은 file 입력 .xls · .xlsx · 쓰기 권한 없으면 꺼짐) +
|
|
* 파일 줄 → 누르면 바로 엑셀 보기 창(329 · `M02_MasterTemplete_Hopyo_Sheet` 늦게 불러옴 · 지우기 · 검산 · 셈 중은 창 머리) ·
|
|
* 고름 표시 = 그 파일 창이 열린 동안 · 올린 직후도 엑셀 보기 창 ·
|
|
* 파일 목록 아래 [별칭 장부](338-3) → 본 칸 장부 화면(`M02_MasterTemplete_Hopyo_Ledger`).
|
|
* ========================================================================== */
|
|
|
|
import { createButton, el, showToast } from "@ui/ui_template_elements";
|
|
import { listHopyo, pageScope, uploadHopyo, type HopyoFile } from "./M02_MasterTemplete_Hopyo_Api";
|
|
import { buildHopyoLedger, LEDGER_HINT, LEDGER_TITLE } from "./M02_MasterTemplete_Hopyo_Ledger";
|
|
|
|
export const HOPYO_TITLE = "일위대가 실무자료";
|
|
|
|
export interface HopyoSectionOptions {
|
|
/** 이 층 쓰기 권한 — 없으면 올리기 꺼짐 · 엑셀 보기 창 보기만 */
|
|
canWrite: boolean;
|
|
/** 저장 안 한 고침을 버릴지 확인 — 취소면 false */
|
|
confirmLeave: () => Promise<boolean>;
|
|
/** 메인 칸에 화면을 띄움 */
|
|
show: (label: string, node: HTMLElement) => (dirty: boolean) => void;
|
|
/** 화면을 열었음 — 좌측 다른 고름 표시를 지움 */
|
|
onOpen: () => void;
|
|
}
|
|
|
|
export interface HopyoSection {
|
|
title: string;
|
|
body: HTMLElement;
|
|
/** 다른 화면이 열렸으면 고름 표시를 지움 */
|
|
paint: () => void;
|
|
refresh: () => Promise<void>;
|
|
}
|
|
|
|
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
|
|
|
|
export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
|
|
const scope = pageScope();
|
|
const list = el("div", { className: "m02-side__items ui-sidebar-list" });
|
|
const input = el("input", {
|
|
attrs: { type: "file", accept: ".xls,.xlsx", "data-hopyo-file": "" },
|
|
}) as HTMLInputElement;
|
|
input.hidden = true;
|
|
// 구조물 조합 층 단추(`TreePanel_Roots` [마스터])와 같은 꼴 — 보통 크기 · 같은 클래스
|
|
const upload = createButton({
|
|
label: "파일 업로드",
|
|
variant: "filled",
|
|
disabled: !opt.canWrite,
|
|
onClick: () => input.click(),
|
|
});
|
|
upload.classList.add("m02-layers__btn");
|
|
upload.title = opt.canWrite ? "설계내역서 엑셀(일위대가 시트)" : "이 층 쓰기 권한 없음";
|
|
const ledger = el("button", {
|
|
className: "m02-side__item ui-sidebar-row",
|
|
text: LEDGER_TITLE,
|
|
attrs: { type: "button", title: LEDGER_HINT, "data-hopyo-ledger-open": "" },
|
|
});
|
|
ledger.addEventListener("click", () => void showLedger());
|
|
const body = el("div", {
|
|
className: "m02-hopyo-side",
|
|
children: [el("div", { className: "m02-side__row", children: [upload, input] }), list, ledger],
|
|
});
|
|
let files: HopyoFile[] = [];
|
|
let open: { key: string; view: HTMLElement } | null = null;
|
|
let sheetKey: string | null = null; // 엑셀 보기 창이 열린 파일
|
|
let seq = 0;
|
|
|
|
input.addEventListener("change", () => {
|
|
const file = input.files?.[0];
|
|
input.value = "";
|
|
if (!file) return;
|
|
upload.disabled = true;
|
|
uploadHopyo(scope, file)
|
|
.then(async (got) => {
|
|
const wrong = got.어긋남 ? ` · 검산 어긋남 ${got.어긋남}` : "";
|
|
showToast(`올림 — 호표 ${got.호표수}${wrong}`, got.어긋남 ? "warning" : "success");
|
|
await refresh();
|
|
await showSheet(got);
|
|
})
|
|
.catch((error) => showToast(why(error), "error"))
|
|
.finally(() => (upload.disabled = !opt.canWrite));
|
|
});
|
|
|
|
function paintList(): void {
|
|
ledger.classList.toggle("is-active", open?.key === LEDGER_TITLE);
|
|
list.replaceChildren(
|
|
...(files.length
|
|
? files.map((f) => {
|
|
const row = el("button", {
|
|
className: `m02-side__item ui-sidebar-row${sheetKey === f.키 ? " is-active" : ""}`,
|
|
text: `${f.파일} · 호표 ${f.호표수}`,
|
|
attrs: { type: "button", title: `${f.키} · ${f.올린때}`, "data-hopyo-key": f.키 },
|
|
});
|
|
row.addEventListener("click", () => void showSheet(f));
|
|
return row;
|
|
})
|
|
: [el("p", { className: "m02-side__empty", text: "올린 실무자료 없음" })]),
|
|
);
|
|
}
|
|
|
|
/** 파일 줄 누름 · 올린 직후 = 바로 엑셀 보기 창(본 칸은 그대로 · 열 때 불러옴) */
|
|
async function showSheet(file: HopyoFile): Promise<void> {
|
|
const sheet = await import("./M02_MasterTemplete_Hopyo_Sheet").catch((error: unknown) => {
|
|
showToast(why(error), "error");
|
|
return null;
|
|
});
|
|
if (!sheet) return;
|
|
sheetKey = file.키;
|
|
paintList();
|
|
sheet.openHopyoSheet({
|
|
scope,
|
|
key: file.키,
|
|
파일: file.파일,
|
|
canWrite: opt.canWrite,
|
|
onClose: () => {
|
|
if (sheetKey === file.키) sheetKey = null;
|
|
paintList();
|
|
},
|
|
onDeleted: () => void refresh(),
|
|
});
|
|
}
|
|
|
|
async function showLedger(): Promise<void> {
|
|
if (open?.key === LEDGER_TITLE && open.view.isConnected) return;
|
|
if (!(await opt.confirmLeave())) return;
|
|
const view = buildHopyoLedger({ scope, canWrite: opt.canWrite });
|
|
opt.show(`${HOPYO_TITLE} — ${LEDGER_TITLE}`, view);
|
|
opt.onOpen();
|
|
open = { key: LEDGER_TITLE, view };
|
|
paintList();
|
|
}
|
|
|
|
async function refresh(): Promise<void> {
|
|
const mine = ++seq;
|
|
try {
|
|
const got = await listHopyo(scope);
|
|
if (mine === seq) files = got;
|
|
} catch (error) {
|
|
if (mine === seq) files = [];
|
|
showToast(why(error), "error");
|
|
}
|
|
if (mine === seq) paintList();
|
|
}
|
|
|
|
const paint = (): void => {
|
|
if (open && !open.view.isConnected) {
|
|
open = null;
|
|
paintList();
|
|
}
|
|
};
|
|
|
|
void refresh();
|
|
return { title: HOPYO_TITLE, body, paint, refresh };
|
|
}
|