Files
Aislo/B08_Quantity/B08_Quantity_UI_StructureSheet_Library.ts
T
eomsangdonandClaude Opus 5 4f1521eea9 feat(b08): 구조물도 양식 가져오기 — 3단 목록은 고를 때만, 표는 프로젝트에 박힌 양식만 읽음
- 라이브러리 항목을 목록으로(항목마다 AX-ST-<8hex>.json) · 기본 찰쌓기에 코드 부여
- 가져오기: 로그인한 사람의 개인·회사 단 + 프로그램 기본에서 골라 {프로젝트}/B08_Quantity/library 에 박음 · 그 종류의 고친 식은 비움
- 표·원단위·자재총괄·인계는 박힌 양식 → 없으면 프로그램 기본만 읽음(여는 사람마다 값이 안 갈림)
- [확정] 때 아직 안 박힌 기본 양식을 박음 · 장 머리에 「어느 단에서 가져왔나 · 고친 식 N줄」
- 시험 test_b08_structure_library.py 6개 · 전체 1524 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-13 18:21:19 +09:00

135 lines
5.3 KiB
TypeScript

/* =============================================================================
* B08_Quantity_UI_StructureSheet_Library.ts
* 구조물도 양식 가져오기 칸 (PLAN 4장) — 로그인한 사람의 개인·회사 단 + 프로그램 기본에서
* 골라 **프로젝트 작업본에 박음**.
*
* ⛔ 표를 그릴 때는 라이브러리를 안 읽음 — [목록 보기]를 눌렀을 때만 목록을 받음(판정 Ⓑ).
* ⚠ 모양(클래스)은 옆 제원 칸(`b08-spec*`) 것을 그대로 씀 — 그 칸이 스타일을 넣음.
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
const TIER_LABELS: Record<string, string> = { personal: "개인", company: "회사", program: "기본" };
interface LibraryItem {
tier: string;
code: string;
name: string;
}
/** 장 머리의 양식 표시 — **어느 단에서 가져왔나** + **그 뒤 고쳤나**(지금 읽는 단이 아님). */
export function libraryLabel(
item: { name: string; imported_from?: string | null },
editedRows: number,
): string {
const from = item.imported_from
? `${TIER_LABELS[item.imported_from] ?? item.imported_from}에서 가져옴`
: "기본 · 가져오기 전";
return `양식 「${item.name}」(${from})${editedRows ? ` · 고친 식 ${editedRows}줄` : ""}`;
}
function libraryUrl(projectId: string, tail: string): string {
return `${API_BASE_URL}/projects/${encodeURIComponent(projectId)}/quantity/structure-sheets/library${tail}`;
}
async function readJson<T>(response: Response): Promise<T> {
const payload = (await response.json().catch(() => ({}))) as T & { message?: string };
if (!response.ok) throw new Error(payload.message ?? `HTTP ${response.status}`);
return payload;
}
/**
* 가져오기 칸. `confirmTake` 가 거짓이면 안 가져옴 — 새 양식과 줄 차례가 안 맞을 수 있어 서버가
* 그 종류의 고친 식을 비우므로, 고친 식·저장 안 한 식이 있으면 부르는 쪽이 먼저 물음.
*/
export function buildLibraryPanel(
projectId: string,
typeId: string,
currentCode: string | null,
confirmTake: () => boolean,
onImported: (notes: string[]) => Promise<void>,
): HTMLElement {
const panel = document.createElement("div");
panel.className = "b08-spec ui-sidebar-section";
const title = document.createElement("h3");
title.className = "b08-spec__title";
title.textContent = "양식 가져오기";
const scope = document.createElement("p");
scope.className = "b08-spec__scope";
scope.textContent = "가져온 양식은 이 프로젝트에 박혀 누가 열어도 같은 값으로 섭니다.";
const status = document.createElement("p");
status.className = "b08-spec__scope";
const list = document.createElement("select");
list.className = "b08-spec__input";
list.hidden = true;
const load = document.createElement("button");
load.type = "button";
load.className = "b08-quantity__tab";
load.textContent = "목록 보기";
const take = document.createElement("button");
take.type = "button";
take.className = "b08-spec__save";
take.textContent = "가져오기";
take.hidden = true;
load.addEventListener("click", () => {
void (async () => {
load.disabled = true;
status.textContent = "목록 받는 중…";
try {
const { items } = await readJson<{ items: LibraryItem[] }>(
await fetch(libraryUrl(projectId, `?type_id=${encodeURIComponent(typeId)}`), {
credentials: "include",
}),
);
list.replaceChildren(
...items.map((item) => {
const option = document.createElement("option");
option.value = `${item.tier}|${item.code}`;
const now = item.code === currentCode ? " (지금)" : "";
option.textContent = `${TIER_LABELS[item.tier] ?? item.tier} · ${item.name}${now}`;
return option;
}),
);
list.hidden = take.hidden = items.length === 0;
status.textContent = items.length ? "" : "가져올 항목이 없음";
} catch (error) {
status.textContent = error instanceof Error ? error.message : "목록을 받지 못함";
} finally {
load.disabled = false;
}
})();
});
take.addEventListener("click", () => {
const [tier, code] = list.value.split("|");
if (!tier || !code || !confirmTake()) return;
void (async () => {
take.disabled = true;
status.textContent = "가져오는 중…";
try {
const result = await readJson<{ cleared_formulas: number }>(
await fetch(libraryUrl(projectId, "/import"), {
method: "PUT",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type_id: typeId, tier, code }),
}),
);
const cleared = result.cleared_formulas
? ` · 고친 식 ${result.cleared_formulas}줄 비움`
: "";
// 프로젝트 작업본이 바뀌었으니 표를 **다시 받아** 그림.
await onImported([`양식을 가져왔습니다${cleared}.`]);
} catch (error) {
status.textContent = error instanceof Error ? error.message : "가져오지 못함";
take.disabled = false;
}
})();
});
panel.append(title, scope, load, list, take, status);
return panel;
}