Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Labor_View.ts
eomsangdonandClaude Opus 5.5 09134bab84 refactor(M02): 띠 알림 · 빈 상태 · 보조 글 · 배지 · 탭 · 표 · 목록 줄 · 도구줄 · 체크를 부품으로 (PLAN 553-5)
- DataFlow: 표 넷 = 데이터표 · 함수 판 알림 · 선/알약 경고 · 끊긴 곳 = 띠 알림 · 알약 · 칸 종류 = 배지 · 고르는 줄 = 목록 줄 · 입력 넷 = 입력 · 도구줄 여섯 · 날 색 → 공통 값
- 구조물 옵션 · 구조물 · 도면 · 면적 · 품목 고르기: 판 띠 = 띠 알림 · 한눈/종류별/부품 = 탭 · 표 둘 = 데이터표 · 체크 둘 = 체크 · 카드 · 번호 = 카드 · 배지 · 도면 도구줄
- 일위대가 · 호표 · 실무자료 · 본 화면 · 서고: 알림 = 띠 알림 · 빈 상태 · 보조 글 · 도구줄 여섯 · 옛 보라 별칭 0
- M01 로직 입력칸 = 입력 부품(작은 칸) · 일위대가 상세 줄 수 고정 시험 693 · 지킴 시험 M02 몫 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:44:19 +09:00

164 lines
6.4 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Labor_View.ts
* 일위대가 보기 갈래(39-3 · 고치기 없음) — 그 열 일위대가의 담은 로직 표 + 자재목록 표(기준 · 반영).
* 면적산출 상세 오른쪽 패널(sub3)이 씀(구조물 조합 카드는 43-4 부터 `Labor_Detail` 카드 갈래). 표 모양은 상세와 한 벌(`Labor_Rows`).
* · `show(층, 열, 수량?)` = 문서 · 로직 입력 목록 · calc 를 받아 그림(최신 응답만)
* · `setQty({알약 키: 칸 값}, {알약 키: 칸 주소}?)` = 면적 칸 값이 바뀌면 서버 없이 짝 셈(`Labor_Calc.settle`)으로
* 바로 곱함 · 칸 주소를 주면 저장 전 연결(setLink · 알약 끌기)도 「수량 B6 = 20」 으로 보임(안 주면 저장본 연결)
* (그 열 면적 알약 값 전부를 줌 — 빠진 키는 「수량 없음」)
* · `onRow` = 줄 누름(알약 키 · 이름 · 단위 — 칸 지정 모달 등) · 없으면 누름 없음
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { readTemplate, type ItemLogic, type Layer } from "./M02_MasterTemplete_Api_Fetch";
import {
areaLinks,
calcLabor,
fetchLogicInputs,
normalizeLabor,
type LaborCalc,
type LaborDoc,
} from "./M02_MasterTemplete_Labor_Api";
import { lineId, settle } from "./M02_MasterTemplete_Labor_Calc";
import { logicMatRows } from "./M02_MasterTemplete_Labor_LogicMats";
import { logicTable, matTable } from "./M02_MasterTemplete_Labor_Rows";
import { tc } from "./M02_MasterTemplete_Labor_Text";
import "./M02_MasterTemplete_Labor.css";
export interface LaborViewRow {
kind: "logic" | "material";
/** 로직 키 · 자재 코드 */
key: string;
/** 면적산출 알약 키(로직 「이름|단위」 · 자재 「자재|MT키」) — calc 전이면 null */
pill: string | null;
name: string;
unit: string;
}
export interface LaborViewHandle {
root: HTMLElement;
/** 그 열 일위대가를 읽어 그림 — `수량` = 저장 안 한 면적 칸 값(없으면 저장된 면적산출) */
show: (layer: Layer, column: string | null, 수량?: Record<string, number>) => Promise<void>;
/** 면적 칸 값이 바뀜(알약 값 전부) — 받은 기준에 바로 곱함(서버 안 부름) ·
* `칸` = 지금 시트 연결 전부(알약 키 → 칸 주소 · 저장 전 연결 포함) — 주면 그 뒤로 이것만 씀 */
setQty: (수량: Record<string, number>, 칸?: Record<string, string>) => void;
/** 지금 문서(없으면 null) */
doc: () => LaborDoc | null;
destroy: () => void;
}
export function createLaborView(onRow?: (row: LaborViewRow) => void): LaborViewHandle {
const root = el("div", { className: "m02-labview" });
const logics = new Map<string, ItemLogic>();
let doc: LaborDoc | null = null;
let calc: LaborCalc | null = null;
let fail: string | null = null;
let cells = new Map<string, string>();
/** 화면이 준 칸 주소(저장 전 연결) — 있으면 저장본 연결보다 앞섬 */
let given: Map<string, string> | null = null;
let seq = 0;
const paint = (): void => {
if (!doc) return;
const d = doc;
const pick = (row: LaborViewRow): (() => void) | undefined =>
onRow ? () => onRow(row) : undefined;
const logicOpen = (i: number): void => {
const row = d.담은로직[i];
const id = lineId(row) ?? "";
const line = calc?.줄.find((l) => lineId(l) === id);
pick({
kind: "logic",
key: id,
pill: line?.알약 ?? null,
name: (row.참조 ? line?.이름 : logics.get(row.로직)?.이름) ?? row.참조?.키 ?? row.로직,
unit: line?.분모 ?? "",
})?.();
};
const matOpen = (i: number): void => {
const mat = d.자재[i];
const line = calc?.자재.find((m) => m.코드 === mat.코드);
pick({
kind: "material",
key: mat.코드,
pill: line?.알약 ?? null,
name: mat.이름 ?? mat.코드,
unit: mat.단위 ?? "",
})?.();
};
root.replaceChildren(
el("h4", { className: "m02-labview__title", text: tc("Logics_Title") }),
logicTable(
{ rows: d.담은로직, logics, calc, fail, cells },
onRow ? { open: logicOpen } : undefined,
),
el("h4", { className: "m02-labview__title", text: tc("Labor_Mats") }),
matTable(
{ mats: d.자재, calc, fail, cells, lead: logicMatRows(d.담은로직, calc, !calc && !fail) },
onRow ? { open: matOpen } : undefined,
),
);
};
const show = async (
layer: Layer,
column: string | null,
수량?: Record<string, number>,
): Promise<void> => {
const mine = ++seq;
given = null; // 열이 바뀌면 화면이 다시 줌(refresh)
doc = null;
calc = null;
fail = null;
if (!column) return void root.replaceChildren();
root.replaceChildren(el("p", { className: "ui-text ui-text--muted", text: tc("Loading") }));
try {
const got = await readTemplate(layer, "labor", column, null);
if (mine !== seq) return;
doc = normalizeLabor(got.문서, column);
const want = doc.담은로직.map((r) => r.로직).filter((k) => k && !logics.has(k));
const [inputs, area] = await Promise.all([
fetchLogicInputs(want),
readTemplate(layer, "area", column, null).then(
(a) => a.문서,
() => null,
),
]);
if (mine !== seq) return;
for (const l of inputs.로직) logics.set(l.로직, l);
cells = given ?? new Map([...areaLinks(area)].map(([k, v]) => [k, v.칸]));
paint(); // 금액 「…」
calc = await calcLabor(layer, column, null, 수량);
} catch (error) {
if (mine !== seq) return;
if (!doc) {
root.replaceChildren(
el("p", {
className: "ui-text ui-text--error",
text: error instanceof Error ? error.message : tc("Load_Failed"),
}),
);
return;
}
fail = error instanceof Error ? error.message : tc("Labor_Calc_Fail");
}
if (mine === seq) paint();
};
return {
root,
show,
setQty: (수량, 칸) => {
if (칸) cells = given = new Map(Object.entries(칸));
if (!doc) return;
if (calc) calc = settle(calc, doc, 수량);
paint();
},
doc: () => doc,
destroy: () => {
seq += 1;
root.remove();
},
};
}