Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Hopyo_Side.ts
T
eomsangdonandClaude Sonnet 5.5 a6b236f2b2 feat(837-1): 일위대가 불러오기 층 가르기 걷음 — 누가 하든 자기 개인 층
- 화면: 왼쪽 층 고름(개인·회사·마스터) · 머리 층 배지 · 작업 중 줄의 층 글 걷음(Hopyo_Side · Hopyo_Import)
- 서버: 불러오기 길(Hopyo_Router)은 경로 층 · 주인 · 몸 층을 무시하고 개인 층 고정 · 단산 블록 → 가져온 식(from-hopyo · preview)도 개인 층
- 시험: 새 지킴 시험 test_837_1 · 낡은 시험 고침(785_4 층 고름 → 층 가르기 없음 · dirty 글 / 705_2 남의 층 403 → 경로 층 무시 200)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-15 14:02:33 +09:00

142 lines
6.3 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Hopyo_Side.ts
* 좌측 「일위대가 불러오기」 컨테이너(PLAN 324-4 · 718-1 한시 · 785-4) —
* · [불러오기](구조물 조합 층 단추 꼴 `m02-layers__btn`) = 바로 파일 하나 고르기 창(엑셀 · 경쟁사 .estx · .STC · 814-1) → 올림 → 본 칸 불러오기 보기
* (`M02_MasterTemplete_Hopyo_Import` · 엑셀 + 오른쪽 호표 목록 · 가운데 상세 페이지 없음 · 누름 하나)
* · 층 가르기 없음 — 누가 하든 올린 것 · 만든 것 = 자기 개인 층(요구 855 · 공유는 만든 뒤 본인이)
* · 「작업 중」 줄 = 자기 개인 층 서버 목록(같은 엑셀 작업 중 이어서) · 누르면 본 칸 불러오기 보기
* · 다른 엑셀 [불러오기] · 다른 화면으로 = 지금 보기 떠남(올린 파일 지움 · 743-1) — 고르고 안 만든 확인 캐시가 있으면 물음 한 번
* 옛 [별칭 장부](338-3)는 걷음(815-2) — 별칭은 M01 요소 「별칭」 칸 · M01 별칭 모아 보기.
* ========================================================================== */
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 { t as L } from "@ui/ui_template_locale";
import { listHopyo, uploadHopyo, type HopyoFile } from "./M02_MasterTemplete_Hopyo_Api";
import { buildHopyoImport, MINE, type HopyoImport } from "./M02_MasterTemplete_Hopyo_Import";
export interface HopyoSectionOptions {
/** 저장 안 한 고침을 버릴지 확인 — 취소면 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));
export function buildHopyoSection(opt: HopyoSectionOptions): HopyoSection {
const title = L("M02_HopyoImport");
const input = el("input", {
attrs: { type: "file", accept: ".xls,.xlsx,.estx,.stc", "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 =
"파일 하나 — 경쟁사 엑셀(.xls · .xlsx) 또는 경쟁사 파일(.estx · .STC) → 우리 정보로 다시 만듦(한시 · 끝내거나 떠나면 올린 파일 지움)";
start.dataset.hopyoWizardOpen = "";
const jobs = el("div", { className: "m02-hopyo-side", attrs: { "data-hopyo-jobs": "" } });
const body = el("div", {
className: "m02-hopyo-side",
children: [el("div", { className: "m02-side__row", children: [start, input] }), jobs],
});
let open: { key: string; view: HTMLElement; imp?: HopyoImport } | null = null;
/** 작업 중 줄 — 자기 개인 층 서버 목록 */
async function refresh(): Promise<void> {
const rows = await listHopyo(MINE).catch(() => []);
jobs.replaceChildren(
...(rows.length
? rows.map((f) => {
const row = createListRow({
label: f.파일,
sub: `호표 ${f.호표수} — 이어서`,
size: 28,
active: open?.imp !== undefined && open.key === f.키,
onClick: () => void show(f),
});
row.dataset.hopyoKey = f.키;
return row;
})
: [createEmpty({ text: "작업 중인 불러오기 없음", small: true })]),
);
}
/** [불러오기] — 지금 보기를 떠날 수 있는지 묻고 바로 파일 고르기 창 */
async function choose(): Promise<void> {
if (!(await leaveOk())) return;
input.click();
}
input.addEventListener("change", () => {
const file = input.files?.[0];
input.value = "";
if (!file) return;
start.disabled = true;
uploadHopyo(MINE, file)
.then((got: HopyoFile & { 이어서?: boolean; 원파일?: boolean }) => {
const tool = /\.estx$/i.test(file.name) ? "EST" : "STmate"; // 810-3 원파일 올림 안내
if (got.이어서) showToast(`같은 파일 작업 중 — ${got.키} 이어서`, "info");
else if (got.원파일) showToast(`${tool} 파일 — 단가산출표 · 산출근거`, "success");
else showToast(`올림 — 호표 ${got.호표수}`, got.어긋남 ? "warning" : "success");
if (got.읽기불가) showToast(`가져온 식으로 열 수 없음 — ${got.읽기불가}`, "warning");
return show(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(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({
file,
onEnded: () => {
open = null;
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 };
void refresh();
}
const paint = (): void => {
if (open && !open.view.isConnected) {
open.imp?.leave(); // 다른 화면으로 = 불러오기 떠남
open = null;
void refresh();
}
};
void refresh();
return { title, body, paint };
}