Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1

This commit is contained in:
2026-09-30 20:03:35 +09:00
9 changed files with 233 additions and 6 deletions
@@ -10,6 +10,7 @@
import { createButton, el, showToast } from "@ui/ui_template_elements";
import { attachCollapsible } from "@ui/ui_template_collapsible";
import { showLoading } from "@ui/ui_template_loading";
import type { SpreadsheetHandle, Workbook } from "../A00_Common/spreadsheet/spreadsheet";
import type { StructureDoc, StructureNumbers } from "./M02_MasterTemplete_Api_Fetch";
import {
@@ -254,10 +255,13 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
c.host.replaceChildren();
}
const source = opts.source();
// 0.3초 넘으면 카드마다 칸 안 「불러오는 중」(PLAN 30-6) · 못 읽은 것은 아래 그대로(알림 · 「없음」)
const stops = [previewHost, ...sheets.map((c) => c.host)].map((host) => showLoading(host));
const [drawing, ...books] = await Promise.allSettled([
source.read("structure", col.id),
...sheets.map((c) => source.read(c.kind, col.id)),
]);
stops.forEach((stop) => stop());
if (mine !== seq) return;
if (drawing.status === "fulfilled") doc = drawing.value.문서 as StructureDoc;
// 도번 목록에 있는 열인데 못 읽으면 알림 — 없는 열은 그냥 「도면 없음」
@@ -158,7 +158,14 @@ export function buildLaborLibrary(opt: LaborLibraryOptions): LaborLibrary {
if (editor.isDirty() && !(await showConfirmDialog(tc("BackAsk")))) return;
editor.showList();
},
refresh: () => tree.refresh(),
refresh: async () => {
const stop = editor.loading(); // 조합 목록 칸에도 「불러오는 중」(나무 칸은 나무 부품이 · PLAN 30-6)
try {
await tree.refresh();
} finally {
stop();
}
},
tools: editor.newButton,
bind: (fn) => (setDirty = fn),
};
@@ -27,6 +27,7 @@ import { openLogicPicker } from "./M02_MasterTemplete_Labor_Pick";
import { buildPreview } from "./M02_MasterTemplete_Labor_Preview";
import { tc } from "./M02_MasterTemplete_Labor_Text";
import { matchText } from "./M02_MasterTemplete_TreePanel_Model";
import { loadingError, showLoading } from "@ui/ui_template_loading";
import "../M01_MasterData/M01_MasterData_UI_Logic_Style.css";
import "../M01_MasterData/M01_MasterData_UI_LogicLab_Style.css";
import "@ui/ui_template_general_layout.css";
@@ -83,6 +84,8 @@ export interface LaborEditor {
current: () => string | null;
/** 라이브러리 찾기 칸 글 — 목록을 같은 규칙(`matchText`)으로 거름 */
setQuery: (text: string) => void;
/** 조합 목록을 새로 받는 동안 칸 안 「불러오는 중」(목록 화면일 때만 · 돌려받은 함수 = 끝) */
loading: () => () => void;
}
export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
@@ -167,17 +170,26 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
};
const open = async (key: string): Promise<void> => {
const my = ++nav;
const stop = showLoading(view); // 0.3초 넘으면 상세 칸 안에 「불러오는 중」(PLAN 30-6)
try {
const my = ++nav;
const one = await fetchCombo(key);
await logicsReady;
if (my !== nav) return;
stop();
draw(one.combo, one.version);
} catch (error) {
showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error");
if (my !== nav) return;
stop();
view.replaceChildren(loadingError(reason(error), () => void open(key)));
} finally {
stop();
}
};
/** 조합 목록을 새로 받는 동안 — 목록 화면일 때만 칸 안 표시(돌려받은 함수 = 끝) */
const loading = (): (() => void) => (current ? () => {} : showLoading(view));
const startNew = (path: string[]): void => draw(blankCombo(path), "");
/* --- 상세 --- */
@@ -390,5 +402,6 @@ export function createLaborEditor(opt: LaborEditOptions): LaborEditor {
isDirty: dirty,
current: () => current?.combo.키 ?? null,
setQuery,
loading,
};
}
@@ -11,6 +11,7 @@
import "./M02_MasterTemplete_TreePanel.css";
import { createInputField, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
import { loadingError, showLoading } from "@ui/ui_template_loading";
import { t as L } from "@ui/ui_template_locale";
import {
createTreeRow,
@@ -393,6 +394,7 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
async function refresh(): Promise<void> {
const mine = ++seq;
const stop = showLoading(body); // 0.3초 넘으면 나무 칸 안에 「불러오는 중」(PLAN 30-6)
try {
const got = await api.fetch(opt.layer, owner);
if (mine !== seq) return;
@@ -400,8 +402,11 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
} catch (error) {
if (mine !== seq) return;
view = null;
body.replaceChildren(hint(L("M02_PickerLoadFailed").replace("{value}", why(error))));
stop();
body.replaceChildren(loadingError(why(error), () => void refresh()));
return;
} finally {
stop(); // 늦게 온 옛 요청의 표시도 걷음
}
paint();
}
@@ -6,6 +6,7 @@
import { setLeaveGuard } from "../A00_Common/router";
import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
import { loadingError, showLoading } from "@ui/ui_template_loading";
import { t as L } from "@ui/ui_template_locale";
import {
readFilled,
@@ -463,6 +464,7 @@ export function createMain(
return empty("");
}
title.textContent = isCombo(next) ? L("M02_KindTable") : (next.label ?? next.name); // 26-7a
const stop = showLoading(host); // 0.3초 넘으면 상세 칸 안에 「불러오는 중」(PLAN 30-6)
try {
const got = await readTemplate(next.layer, next.kind, next.name, next.projectId);
if (mine !== seq) return;
@@ -481,12 +483,15 @@ export function createMain(
const field = NUMBER_FIELD[next.kind];
const number = field && (got.문서 as Record<string, unknown> | null)?.[field];
if (number) title.textContent = `${String(number)} ${next.label ?? next.name}`;
stop();
await mount(shown, next);
} catch (error) {
if (mine !== seq) return;
empty("");
stop();
const why = error instanceof Error ? error.message : "";
showToast(L("M02_LoadFailed").replace("{value}", why), "error");
host.replaceChildren(loadingError(why, () => void open(next))); // 그 자리에 까닭 + 다시 불러오기
} finally {
stop();
}
}