Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_5 (선택 입력 카드 sub2 판 · Structure.ts 는 ItemCards 위에 다시 얹음)

This commit is contained in:
2026-09-30 00:00:53 +09:00
11 changed files with 731 additions and 11 deletions
@@ -42,6 +42,7 @@ import {
type SheetKind,
} from "./M02_MasterTemplete_ItemCards";
import { layerSource } from "./M02_MasterTemplete_ItemSource";
import { createInputsCard, inputPills, type InputsCardHandle } from "./M02_MasterTemplete_Inputs";
import "./M02_MasterTemplete_Structure.css";
export type { EditKind, SheetKind } from "./M02_MasterTemplete_ItemCards";
@@ -193,8 +194,12 @@ export async function pillsFor(
kind: SheetKind,
column: string,
): Promise<{ links: LinkOptions; notice?: string }> {
if (kind === "material") return { links: { 항목: [], 모드: "자재", 자재찾기: searchMaterials } };
const none = (notice: string) => ({ links: { 항목: [], 모드: "입력" as const }, notice });
if (kind === "material") {
const own = await inputPills(column, "재료");
return { links: { 항목: own, 모드: "자재", 자재찾기: searchMaterials } };
}
const own = await inputPills(column, "면적"); // 선택 입력 카드가 면적산출로 고른 입력
const none = (notice: string) => ({ links: { 항목: own, 모드: "입력" as const }, notice });
const table = await readTemplate(getActiveLayer(), "table", STRUCTURE_TABLE, null).catch(
() => null,
);
@@ -203,7 +208,7 @@ export async function pillsFor(
if (!combo) return none(TEXT.noPrice);
try {
const got = await fetchComboInputs(combo);
const 항목 = got.물량.map((q) => ({ 키: q.키, 이름: q.이름, 단위: q.단위 }));
const 항목 = [...got.물량.map((q) => ({ 키: q.키, 이름: q.이름, 단위: q.단위 })), ...own];
return 항목.length ? { links: { 항목, 모드: "입력" } } : none(fill(TEXT.noInputs, combo));
} catch (error) {
return none(fill(TEXT.inputsFailed, why(error)));
@@ -218,6 +223,8 @@ export interface StructurePanelOptions {
onEdit: (column: string, label: string, kind: EditKind) => void;
/** 집계표에 저장 안 한 고침이 있으면 버릴지 물음 — 취소면 false */
confirmLeave: () => Promise<boolean>;
/** 「선택 입력」 카드에서 자리를 골랐음 — 저장 안 한 고침 표시 */
onDirty?: () => void;
}
export interface StructurePanelHandle {
@@ -226,6 +233,8 @@ export interface StructurePanelHandle {
numbers: () => ColumnNumbers;
/** 번호 목록 다시 받기 */
reload: () => Promise<void>;
/** 「선택 입력」 카드에서 고른 자리를 서버에 저장 — 집계표 [저장] 뒤에 부름(실패하면 던짐) */
saveInputs: () => Promise<void>;
/** 고른 열이 바뀜 — 같은 열이면 이름만 새로(CAD 를 다시 안 띄움) */
show: (colId: string | null, columns: StructureColumn[]) => void;
/** 보던 열을 다시 읽음 — 집계표 [저장] 뒤(서버가 새 열 파일 · 번호를 만듦) */
@@ -242,6 +251,10 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
let column: string | null = null;
let columnLabel = "";
const inputs: InputsCardHandle = createInputsCard({
isAdmin: opts.isAdmin,
onDirty: () => opts.onDirty?.(),
});
// 카드 셋은 항목 카드 한 벌(`ItemCards`) — 여기서는 지금 층에서 열 하나를 읽어 넘기기만.
const cards = createItemCards({
source: layerSource,
@@ -250,6 +263,7 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
numberOf: (kind, id) => numbers[id]?.[PART_FIELD[kind]],
// 도번 목록에 있는 열인데 못 읽으면 알림 — 없는 열은 그냥 「도면 없음」
warnOnFail: (id) => !!numbers[id],
extraCards: [inputs.root],
});
const root = cards.root;
@@ -257,6 +271,9 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
shown = col;
column = col?.id ?? null;
columnLabel = col ? columnHead(col) : "";
if (!col) void inputs.show(null, null);
// 집계표에 지금 고른 일위대가(저장 전일 수 있음) — 카드가 「먼저 저장」 안내를 고름
else void inputs.show(col.id, (col as { 일위대가?: string | null }).일위대가 ?? null);
return cards.load(col ? { id: col.id, label: columnLabel } : null);
}
@@ -347,7 +364,9 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
numbers: () => numbers,
reload: async () => {
numbers = await fetchStructureNumbers().catch(() => ({}));
inputs.invalidate(); // 새 열 · 바뀐 일위대가를 서버 항목 목록에서 다시 찾게
},
saveInputs: () => inputs.save(),
show,
refresh: (columns) => {
const at = column;
@@ -355,6 +374,9 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
show(at, columns);
},
pick,
destroy: () => cards.destroy(),
destroy: () => {
inputs.destroy();
cards.destroy();
},
};
}