merge(M02): origin/sub_laptop_6 받음 — UI_Main 충돌 풂(면적 일위대가 패널 살림 · 재료산출 · 조합 패널 길 지움)
- sub3 apanel · 고른 칸 · refresh 살림 · sub6 의 matPanel · matState 지움 · 면적만 남아 죽은 조합 패널(cpanel) · remount 지움 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
@@ -26,21 +26,10 @@ import {
|
||||
type StructureColumn,
|
||||
type StructurePanelHandle,
|
||||
} from "./M02_MasterTemplete_Structure";
|
||||
import { inputPills } from "./M02_MasterTemplete_Inputs";
|
||||
import { askSettle } from "./M02_MasterTemplete_TreePanel_Dialogs";
|
||||
import { buildAreaPanel, type AreaPanel } from "./M02_MasterTemplete_AreaPanel";
|
||||
import { buildComboPanel, type ComboPanel } from "./M02_MasterTemplete_ComboPanel";
|
||||
import {
|
||||
buildMaterialPanel,
|
||||
loadMaterialState,
|
||||
toPills,
|
||||
type MaterialPanel,
|
||||
type MaterialState,
|
||||
} from "./M02_MasterTemplete_Materials";
|
||||
import type { SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links";
|
||||
import { searchMaterials } from "../M01_MasterData/M01_MasterData_Api_Fetch";
|
||||
import type { MaterialItem } from "./M02_MasterTemplete_Api_Fetch";
|
||||
export interface Selection {
|
||||
layer: Layer;
|
||||
projectId: string | null;
|
||||
@@ -67,7 +56,6 @@ type Mods = Record<string, () => Promise<unknown>>;
|
||||
const NUMBER_FIELD: Partial<Record<Kind, string>> = {
|
||||
structure: "도번",
|
||||
area: "면적번",
|
||||
material: "재료번",
|
||||
labor: "일위대가번",
|
||||
};
|
||||
// 표 부품(sub3) · 도면 부품(sub2) — 내보낸 이름으로 찾음
|
||||
@@ -141,7 +129,7 @@ export function createMain(
|
||||
const title = el("h2", { className: "m02-main__title", text: L("M02_PickTemplate") });
|
||||
const reload = createButton({ label: L("M02_Reload"), variant: "ghost" });
|
||||
const save = createButton({ label: L("M02_Save"), variant: "filled" });
|
||||
// 구조물 도면 · 면적산출 · 재료산출 상세 화면 → 구조물집계표(그 열 고른 채)
|
||||
// 구조물 도면 · 면적산출 상세 화면 → 구조물집계표(그 열 고른 채)
|
||||
const back = createButton({ label: L("M02_Back"), variant: "ghost", size: "sm" });
|
||||
const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴
|
||||
const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } });
|
||||
@@ -173,7 +161,7 @@ export function createMain(
|
||||
title.classList.toggle("m02-main__title--fold", combo);
|
||||
title.title = combo ? L("M02_TableFold") : "";
|
||||
if (!combo) {
|
||||
host.hidden = false; // 다른 상세(도면 · 면적 · 재료)는 늘 펼침
|
||||
host.hidden = false; // 다른 상세(도면 · 면적)는 늘 펼침
|
||||
title.classList.remove("is-folded");
|
||||
}
|
||||
};
|
||||
@@ -191,14 +179,11 @@ export function createMain(
|
||||
let sheet: SheetEditorHandle | null = null;
|
||||
let panel: StructurePanelHandle | null = null;
|
||||
let sheetHandle: SpreadsheetHandle | null = null;
|
||||
let matPanel: MaterialPanel | null = null;
|
||||
let matState: MaterialState | null = null;
|
||||
let cpanel: ComboPanel | null = null; // 재료산출 오른쪽 조합 패널(프로젝트 층 아님)
|
||||
let apanel: AreaPanel | null = null; // 면적산출 오른쪽 일위대가 패널(프로젝트 층 아님 · 39-4)
|
||||
let picked = "A1"; // 면적산출 시트에서 지금 고른 칸 — 칸 지정 모달 [지금 고른 칸으로]
|
||||
let seq = 0;
|
||||
let dirty = false;
|
||||
/** 저장 안 한 고침 — 이 화면 + 카드(도면 31-4 · 면적산출 · 재료산출 31-3) */
|
||||
/** 저장 안 한 고침 — 이 화면 + 카드(도면 31-4 · 면적산출 31-3) */
|
||||
const busy = (): boolean => dirty || !!panel?.dirty();
|
||||
|
||||
const guard = (event: BeforeUnloadEvent): void => {
|
||||
@@ -240,10 +225,6 @@ export function createMain(
|
||||
editor = null;
|
||||
sheet = null;
|
||||
sheetHandle = null;
|
||||
matPanel = null;
|
||||
matState = null;
|
||||
cpanel?.destroy();
|
||||
cpanel = null;
|
||||
apanel?.destroy();
|
||||
apanel = null;
|
||||
panel?.destroy();
|
||||
@@ -252,12 +233,11 @@ export function createMain(
|
||||
slot.replaceChildren();
|
||||
};
|
||||
|
||||
/** 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 · 재료산출 카드 */
|
||||
/** 구조물집계표 아래 설계 컨테이너 — 고른 열의 도면 정보 · 면적산출 카드 */
|
||||
function buildPanel(layer: Layer): StructurePanelHandle {
|
||||
const made = createStructurePanel({
|
||||
isAdmin,
|
||||
confirmLeave,
|
||||
onDirty: () => (dirty = true), // 「선택 입력」 카드에서 자리를 고름 — 집계표 [저장]으로 함께 저장
|
||||
keepColumn: (colId) => sheet?.selectColumn(colId), // 카드 고침을 안 버림 — 고른 열 되돌림
|
||||
// 같은 페이지에서 그 열을 CAD·스프레드시트 전체 편집으로 엶
|
||||
onEdit: (column, label, kind) => {
|
||||
@@ -268,38 +248,23 @@ export function createMain(
|
||||
return made;
|
||||
}
|
||||
|
||||
/** 면적산출 · 재료산출 스프레드시트 — 아래 알약 막대 · 면적 = 그 열 일위대가 알약(`pillsFor`) ·
|
||||
* 재료 = 자재DB 찾기 · 프로젝트 층 아니면 면적 = 오른쪽 일위대가 패널(담은 로직 · 자재목록 · 칸 지정) ·
|
||||
* 재료 = 그 열 자재 조합 담은 자재(알약 항목 = 옆 판 표 = 같은 목록) + 옆 판
|
||||
* (`pillItems` = 옆 판에서 고친 목록으로 알약 막대만 다시 만들 때) */
|
||||
/** 면적산출 스프레드시트 — 아래 알약 막대 = 그 열 일위대가 알약(`pillsFor`) ·
|
||||
* 프로젝트 층 아니면 오른쪽 일위대가 패널(담은 로직 · 자재목록 · 칸 지정) */
|
||||
async function mountSheet(
|
||||
doc: unknown,
|
||||
kind: "area" | "material",
|
||||
kind: "area",
|
||||
at: Selection,
|
||||
readOnly: boolean,
|
||||
pillItems?: MaterialItem[],
|
||||
): Promise<void> {
|
||||
const mine = seq;
|
||||
try {
|
||||
const master = at.layer !== "project" && kind === "material";
|
||||
if (master && !matState) matState = await loadMaterialState(at.name);
|
||||
const [{ createSpreadsheet }, pills, own] = await Promise.all([
|
||||
const [{ createSpreadsheet }, pills] = await Promise.all([
|
||||
import("../A00_Common/spreadsheet/spreadsheet"),
|
||||
master ? null : pillsFor(kind, at.name),
|
||||
master ? inputPills(at.name, "재료") : [], // 「선택 입력」 카드가 재료산출로 고른 입력
|
||||
pillsFor(kind, at.name),
|
||||
]);
|
||||
if (mine !== seq) return;
|
||||
if (pills?.notice) showNotice([pills.notice]);
|
||||
const links: LinkOptions =
|
||||
pills?.links ??
|
||||
({
|
||||
항목: [...toPills(pillItems ?? matState?.combo?.담은자재 ?? []), ...own],
|
||||
모드: "자재",
|
||||
자재찾기: searchMaterials,
|
||||
// 알약 막대에서 더하기 · 빼기 — 옆 판 표를 같은 목록으로
|
||||
onItemsChange: (list) =>
|
||||
matPanel?.setItems(list.flatMap((i) => (i.자재 ? [{ ...i.자재 }] : []))),
|
||||
} satisfies LinkOptions);
|
||||
if (pills.notice) showNotice([pills.notice]);
|
||||
const links: LinkOptions = pills.links;
|
||||
const handle: SpreadsheetHandle = createSpreadsheet(
|
||||
host,
|
||||
doc as Parameters<typeof createSpreadsheet>[1],
|
||||
@@ -308,7 +273,6 @@ export function createMain(
|
||||
tabs: false, // 시트 하나 · 탭 줄 없음
|
||||
onChange: () => {
|
||||
dirty = !readOnly;
|
||||
matPanel?.refresh(); // 알약을 이음 · 풂 — 옆 판 「이은 칸」 바로 갱신
|
||||
apanel?.refresh(); // 칸 값 · 연결이 바뀜 — 반영 금액을 바로 다시 곱함
|
||||
},
|
||||
onSelect: (s) => (picked = s.칸),
|
||||
@@ -332,71 +296,11 @@ export function createMain(
|
||||
host.append(apanel.root);
|
||||
return;
|
||||
}
|
||||
// 오른쪽 조합 패널 — 한 번 만들고 시트를 다시 붙여도 그대로(늘 시트 뒤)
|
||||
cpanel ??= buildComboPanel({
|
||||
kind,
|
||||
column: at.name,
|
||||
isAdmin,
|
||||
onUsed: () => remount(kind, at, readOnly, true),
|
||||
});
|
||||
host.append(cpanel.root);
|
||||
if (matPanel || !matState) return;
|
||||
matPanel = buildMaterialPanel({
|
||||
column: at.name,
|
||||
label: at.label ?? at.name,
|
||||
isAdmin,
|
||||
state: matState,
|
||||
onApplied: () => void cpanel?.refresh(),
|
||||
links: () => sheetHandle?.getDoc().시트.flatMap((s) => s.연결 ?? []) ?? [],
|
||||
// 옆 판 표에서 고침 — 알약 막대만 이 목록으로 다시 만듦(저장 안 한 시트 고침은 그대로)
|
||||
onChange: (items) => {
|
||||
const kept = sheetHandle?.getDoc();
|
||||
if (!kept) return;
|
||||
editor?.destroy();
|
||||
editor = null;
|
||||
void mountSheet(kept, kind, at, readOnly, items);
|
||||
},
|
||||
goTo: (code) =>
|
||||
host
|
||||
.querySelector<HTMLElement>(`.ss-link.is-linked[data-key="${CSS.escape(code)}"]`)
|
||||
?.click(),
|
||||
unlink: (codes) => {
|
||||
const book = sheetHandle?.getDoc();
|
||||
if (!book) return;
|
||||
for (const s of book.시트) {
|
||||
const rest = (s.연결 ?? []).filter((l) => !codes.includes(l.자재?.코드 ?? l.키));
|
||||
if (rest.length) s.연결 = rest;
|
||||
else delete s.연결;
|
||||
}
|
||||
sheetHandle?.setDoc(book);
|
||||
dirty = !readOnly;
|
||||
},
|
||||
});
|
||||
cpanel.setMine(matPanel.root);
|
||||
} catch {
|
||||
empty(L("M02_NoSpreadsheet"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 시트만 다시 붙임(알약을 새로 받음) — 저장 안 한 시트 고침은 그대로 옮김 ·
|
||||
* `fresh` = 이 열 조합이 바뀜(재료는 자재 조합 · 담은 자재 표를 새로 받음) */
|
||||
async function remount(
|
||||
kind: "area" | "material",
|
||||
at: Selection,
|
||||
readOnly: boolean,
|
||||
fresh: boolean,
|
||||
): Promise<void> {
|
||||
const kept = sheetHandle?.getDoc();
|
||||
if (sel !== at || !kept) return;
|
||||
editor?.destroy();
|
||||
editor = null;
|
||||
if (fresh && kind === "material") {
|
||||
matPanel = null;
|
||||
matState = null;
|
||||
}
|
||||
await mountSheet(kept, kind, at, readOnly);
|
||||
}
|
||||
|
||||
async function mount(doc: unknown, at: Selection): Promise<void> {
|
||||
const readOnly = !canEdit(at.layer, isAdmin);
|
||||
host.replaceChildren();
|
||||
@@ -406,8 +310,8 @@ export function createMain(
|
||||
"createSheet",
|
||||
);
|
||||
if (!create) return empty(L("M02_NoSheet"));
|
||||
// 구조물집계표(프로젝트 층 아님)만 — 도면 · 면적산출 · 재료산출 줄에 번호 · 칸을 고르면 아래 컨테이너 ·
|
||||
// 고칠 수 있으면 일위대가 · 도면 · 면적산출 · 재료산출 칸은 조회 후 선택
|
||||
// 구조물집계표(프로젝트 층 아님)만 — 도면 · 면적산출 줄에 번호 · 칸을 고르면 아래 컨테이너 ·
|
||||
// 고칠 수 있으면 일위대가 · 도면 · 면적산출 칸은 조회 후 선택
|
||||
const struct =
|
||||
at.layer !== "project" && at.name === STRUCTURE_TABLE ? buildPanel(at.layer) : null;
|
||||
if (struct) await struct.reload();
|
||||
@@ -427,7 +331,6 @@ export function createMain(
|
||||
? {
|
||||
drawingLabel: (colId: string) => struct.numbers()[colId]?.도번 ?? "",
|
||||
areaLabel: (colId: string) => struct.numbers()[colId]?.면적번 ?? "",
|
||||
materialLabel: (colId: string) => struct.numbers()[colId]?.재료번 ?? "",
|
||||
combo: {
|
||||
...(readOnly || !extras.onHeadRename
|
||||
? {}
|
||||
@@ -472,8 +375,8 @@ export function createMain(
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (at.kind === "area" || at.kind === "material") {
|
||||
// 면적산출 · 재료산출 = 구조물집계표 열마다 한 스프레드시트 — 페이지 번들에 안 섞이게 이 화면을
|
||||
if (at.kind === "area") {
|
||||
// 면적산출 = 구조물집계표 열마다 한 스프레드시트 — 페이지 번들에 안 섞이게 이 화면을
|
||||
// 열 때만 `import()` 로 받음(계약 `spreadsheet_types.ts` 머리)
|
||||
return mountSheet(doc, at.kind, at, readOnly);
|
||||
}
|
||||
@@ -529,7 +432,7 @@ export function createMain(
|
||||
)
|
||||
: got.문서;
|
||||
if (mine !== seq) return;
|
||||
// 구조물 도면 · 면적산출 · 재료산출은 열 id 로 열리니 머리 맨 앞에 번호(「SD01010100 돌기슭막이 …」)
|
||||
// 구조물 도면 · 면적산출은 열 id 로 열리니 머리 맨 앞에 번호(「SD01010100 돌기슭막이 …」)
|
||||
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}`;
|
||||
@@ -551,11 +454,11 @@ export function createMain(
|
||||
try {
|
||||
const edited = editor ? await editor.getDoc() : loaded;
|
||||
// 구조물 도면 저장은 `도면` 칸만 갈아 끼움 — 도번은 읽은 그대로.
|
||||
// 면적산출 · 재료산출은 스프레드시트 문서로 통째 갈아 끼우되 번호는 계약 밖(엔진이 모름)이라 옮겨 붙임.
|
||||
// 면적산출은 스프레드시트 문서로 통째 갈아 끼우되 번호는 계약 밖(엔진이 모름)이라 옮겨 붙임.
|
||||
const doc =
|
||||
at.kind === "structure" && loaded
|
||||
? { ...(loaded as StructureDoc), 도면: edited as StructureDoc["도면"] }
|
||||
: (at.kind === "area" || at.kind === "material") && loaded
|
||||
: at.kind === "area" && loaded
|
||||
? { ...(loaded as Record<string, unknown>), ...(edited as Record<string, unknown>) }
|
||||
: edited;
|
||||
const info = await saveTemplate(at.layer, at.kind, at.name, at.projectId, version, doc);
|
||||
@@ -569,7 +472,6 @@ export function createMain(
|
||||
await panel.reload();
|
||||
sheet.recalc();
|
||||
panel.refresh(sheet.getDoc()?.열 ?? []);
|
||||
await panel.saveInputs(); // 「선택 입력」 카드의 자리 고름 — 같은 [저장]에서
|
||||
}
|
||||
showNotice([]);
|
||||
showToast(L("M02_Saved"), "success");
|
||||
|
||||
Reference in New Issue
Block a user