- 새 Hopyo_Import.ts(머리 막대 층 · 안 이은 줄 배지 · [끝내기] · 떠남 지움) · Hopyo_Wizard.ts 지움 - Hopyo_Side.ts = 올림 · 층 작은 고름(둘 이상일 때만) · 작업 중 줄 · 다른 엑셀 물음 - Hopyo_Sheet.ts = 몸(buildHopyoSheet) + 모달 껍데기 · 만들기 셋 → 새 Hopyo_SheetMake.ts · [삭제] 걷음 · 찾기 · 체크 부품으로 - 흠 셋: 안 이은 줄 0 오표시(빈 호표 번) · 맞춤 셈 중 멈춤(3 초 저절로 다시) · 좁은 판 줄 짓눌림(시트 아래 줄 판 자리) - 시험: 785-4 지킴 시험 새로 · 낡은 마법사 · 모달 [삭제] · 좁은 꼴 인자 대조를 새 파일 · 새 길로 고침(Wizard 삭제 · SheetMake 이동 · narrow 걷음)
213 lines
8.4 KiB
TypeScript
213 lines
8.4 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_Side.ts
|
|
* 좌측 「일위대가 불러오기」 컨테이너(PLAN 324-4 · 718-1 한시 · 785-4) —
|
|
* · [불러오기](구조물 조합 층 단추 꼴 `m02-layers__btn`) = 바로 엑셀 고르기 창 → 올림 → 본 칸 불러오기 보기
|
|
* (`M02_MasterTemplete_Hopyo_Import` · 엑셀 + 오른쪽 호표 목록 · 가운데 상세 페이지 없음 · 누름 하나)
|
|
* · 결과 층 고름 = 작은 고름(쓸 수 있는 층 둘 이상일 때만 · 일반 = 개인 하나라 안 보임)
|
|
* · 「작업 중」 줄 = 층마다 서버 목록(같은 엑셀 작업 중 이어서) · 누르면 본 칸 불러오기 보기
|
|
* · 다른 엑셀 [불러오기] · 다른 화면으로 = 지금 보기 떠남(올린 파일 지움 · 743-1) — 고르고 안 만든 확인 캐시가 있으면 물음 한 번
|
|
* 아래 [별칭 장부](338-3) → 본 칸 장부 화면(`M02_MasterTemplete_Hopyo_Ledger` · 층 = 구조물 조합 층).
|
|
* ========================================================================== */
|
|
|
|
import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
|
|
import { createEmpty } from "@ui/ui_template_empty";
|
|
import { createListRow } from "@ui/ui_template_list_row";
|
|
import { createSegment } from "@ui/ui_template_segment";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import {
|
|
listHopyo,
|
|
pageScope,
|
|
uploadHopyo,
|
|
type HopyoFile,
|
|
type HopyoScope,
|
|
} from "./M02_MasterTemplete_Hopyo_Api";
|
|
import { buildHopyoLedger, LEDGER_HINT, LEDGER_TITLE } from "./M02_MasterTemplete_Hopyo_Ledger";
|
|
import {
|
|
buildHopyoImport,
|
|
LAYERS,
|
|
layerText,
|
|
writableLayers,
|
|
type HopyoImport,
|
|
} from "./M02_MasterTemplete_Hopyo_Import";
|
|
import type { ManageLayer } from "./M02_MasterTemplete_Layer";
|
|
|
|
export interface HopyoSectionOptions {
|
|
/** 이 층 쓰기 권한 — 별칭 장부 고침(구조물 조합 층이 제자리에서 바뀌어 열 때 읽음 · 720-4) */
|
|
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;
|
|
}
|
|
|
|
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
|
|
const scopeOf = (layer: ManageLayer): HopyoScope => ({ layer, owner: null });
|
|
|
|
export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
|
|
const title = L("M02_HopyoImport");
|
|
let layers: ManageLayer[] = [];
|
|
let pick: ManageLayer = "personal";
|
|
const input = el("input", {
|
|
attrs: { type: "file", accept: ".xls,.xlsx", "data-hopyo-file": "" },
|
|
}) as HTMLInputElement;
|
|
input.hidden = true;
|
|
const start = createButton({
|
|
label: "불러오기",
|
|
variant: "filled",
|
|
onClick: () => void choose(),
|
|
});
|
|
start.classList.add("m02-layers__btn");
|
|
start.title =
|
|
"다른 프로그램 일위대가 엑셀 → 우리 일위대가(한시 · 끝내거나 떠나면 올린 파일 지움)";
|
|
start.dataset.hopyoWizardOpen = "";
|
|
const seg = el("div", { className: "m02-side__row" });
|
|
seg.hidden = true;
|
|
const jobs = el("div", { className: "m02-hopyo-side", attrs: { "data-hopyo-jobs": "" } });
|
|
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: [start, input] }),
|
|
seg,
|
|
jobs,
|
|
ledger,
|
|
],
|
|
});
|
|
let open: { key: string; view: HTMLElement; imp?: HopyoImport } | null = null;
|
|
|
|
/** 층 고름 · 작업 중 줄 — 쓸 수 있는 층을 처음 한 번 읽음 */
|
|
async function refresh(): Promise<void> {
|
|
if (!layers.length) {
|
|
layers = await writableLayers();
|
|
if (!layers.includes(pick)) pick = layers[0] ?? "personal";
|
|
if (layers.length > 1) {
|
|
const s = createSegment({
|
|
options: LAYERS.filter((x) => layers.includes(x.layer)).map((x) => ({ value: x.layer, label: x.text })), // prettier-ignore
|
|
value: pick,
|
|
size: 24,
|
|
onChange: (v) => (pick = v as ManageLayer),
|
|
});
|
|
s.root.dataset.hopyoLayer = "";
|
|
seg.replaceChildren(s.root);
|
|
seg.hidden = false;
|
|
}
|
|
}
|
|
const rows = (
|
|
await Promise.all(
|
|
layers.map((layer) =>
|
|
listHopyo(scopeOf(layer))
|
|
.then((list) => list.map((f) => ({ layer, f })))
|
|
.catch(() => []),
|
|
),
|
|
)
|
|
).flat();
|
|
jobs.replaceChildren(
|
|
...(rows.length
|
|
? rows.map(({ layer, f }) => {
|
|
const row = createListRow({
|
|
label: f.파일,
|
|
sub: `호표 ${f.호표수} · ${layerText(layer)} — 이어서`,
|
|
size: 28,
|
|
active: open?.imp !== undefined && open.key === f.키,
|
|
onClick: () => void show(layer, f),
|
|
});
|
|
row.dataset.hopyoKey = f.키;
|
|
return row;
|
|
})
|
|
: [createEmpty({ text: "작업 중인 불러오기 없음", small: true })]),
|
|
);
|
|
}
|
|
|
|
/** [불러오기] — 지금 보기를 떠날 수 있는지 묻고 바로 파일 고르기 창 */
|
|
async function choose(): Promise<void> {
|
|
if (!layers.length) await refresh().catch(() => {});
|
|
if (!layers.length) return void showToast("쓸 수 있는 층 없음", "warning");
|
|
if (!(await leaveOk())) return;
|
|
input.click();
|
|
}
|
|
input.addEventListener("change", () => {
|
|
const file = input.files?.[0];
|
|
input.value = "";
|
|
if (!file) return;
|
|
start.disabled = true;
|
|
uploadHopyo(scopeOf(pick), file)
|
|
.then((got: HopyoFile & { 이어서?: boolean }) => {
|
|
if (got.이어서) showToast(`같은 엑셀 작업 중 — ${got.키} 이어서`, "info");
|
|
else showToast(`올림 — 호표 ${got.호표수}`, got.어긋남 ? "warning" : "success");
|
|
return show(pick, got, true);
|
|
})
|
|
.catch((error) => showToast(why(error), "error"))
|
|
.finally(() => (start.disabled = false));
|
|
});
|
|
|
|
/** 지금 보기를 떠나도 되는지 — 저장 안 한 고침 · 고르고 안 만든 확인 캐시 */
|
|
async function leaveOk(): Promise<boolean> {
|
|
if (!(await opt.confirmLeave())) return false;
|
|
if (!open?.imp?.dirty()) return true;
|
|
return showConfirmDialog(
|
|
"지금 불러오기를 떠남 — 올린 파일을 지움 · 고르고 안 만든 호표는 빠짐(만든 것은 남음)",
|
|
"떠나기",
|
|
);
|
|
}
|
|
|
|
/** 본 칸에 불러오기 보기 — 같은 파일이면 그대로 */
|
|
async function show(layer: ManageLayer, file: HopyoFile, checked = false): Promise<void> {
|
|
if (open?.imp && open.key === file.키 && open.view.isConnected) return;
|
|
if (!checked && !(await leaveOk())) return;
|
|
open?.imp?.leave(); // 불러오기 떠남 = 올린 파일 지움(743-1)
|
|
const imp = buildHopyoImport({
|
|
layer,
|
|
file,
|
|
onEnded: () => {
|
|
open = null;
|
|
ledger.classList.remove("is-active");
|
|
opt.show(title, el("div", { className: "m02-hopyo" })); // 끝냄 — 빈 본 칸
|
|
void refresh();
|
|
},
|
|
});
|
|
opt.show(`${title} — ${file.파일}`, imp.root);
|
|
opt.onOpen();
|
|
open = { key: file.키, view: imp.root, imp };
|
|
ledger.classList.remove("is-active");
|
|
void refresh();
|
|
}
|
|
|
|
async function showLedger(): Promise<void> {
|
|
if (open?.key === LEDGER_TITLE && open.view.isConnected) return;
|
|
if (!(await leaveOk())) return;
|
|
open?.imp?.leave();
|
|
const view = buildHopyoLedger({ scope: pageScope(), canWrite: opt.canWrite() });
|
|
opt.show(`${title} — ${LEDGER_TITLE}`, view);
|
|
opt.onOpen();
|
|
open = { key: LEDGER_TITLE, view };
|
|
ledger.classList.add("is-active");
|
|
void refresh();
|
|
}
|
|
|
|
const paint = (): void => {
|
|
if (open && !open.view.isConnected) {
|
|
open.imp?.leave(); // 다른 화면으로 = 불러오기 떠남
|
|
open = null;
|
|
ledger.classList.remove("is-active");
|
|
void refresh();
|
|
}
|
|
};
|
|
|
|
void refresh();
|
|
return { title, body, paint };
|
|
}
|