feat(813-2): M01 「기계」 · 「계수」 아래 부품 문서 · 줄 쌓기 [+] 메뉴 「식」
- M01 기계 › 상세구분(기종) = 「시간당 기계경비 계산식」(그 기종 기계가 쓰는 방식 · 기계 수) · 「작업량 공식 틀」 · 계수 = 「계수식」 넷 - 이름 누름 = 줄 쌓기 읽기 창(고침 없음) · [단가산출 기초에서 열기] = 지금 수식 탭에서 고침(목록 밖 부품 키도 키로 읽어 엶) - 줄 쌓기 [+] 메뉴 「식 — 공식 틀」 → 「식」 - 할 일 4 ② 공식 묶음 [찾아 넣기] 걷기 · ③ 기계 넣기 + 작업량 틀 = 요구 739 · 740 로 뺌(지금 동작 그대로) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ const TEXT = {
|
||||
Add_Item: ["+ 그룹", "+ Group"],
|
||||
Add_Line: ["줄 더함", "Add line"],
|
||||
Add_율: ["율 — 그룹 소계의 %", "Rate — % of subtotal"],
|
||||
Add_식: ["식 — 공식 틀", "Formula"],
|
||||
Add_식: ["식", "Formula"],
|
||||
Del_Item: ["그룹 지움", "Delete group"],
|
||||
Del: ["지움", "Delete"],
|
||||
Item: ["그룹", "Group"],
|
||||
|
||||
@@ -571,7 +571,13 @@ export async function mountM01LogicLab(
|
||||
showLoadingOverlay();
|
||||
const openKeyOn = async (key: string): Promise<void> => {
|
||||
if (!(await leave())) return;
|
||||
const found = allLogics.find((x) => x.키 === key);
|
||||
// 목록에 없는 부품 문서(813-2) = 키로 읽어 엶
|
||||
const found =
|
||||
allLogics.find((x) => x.키 === key) ??
|
||||
(await fetchLogic(key).then(
|
||||
(o) => o.logic as unknown as { 구분: string; 키: string },
|
||||
() => null,
|
||||
));
|
||||
if (found) await open(logicId(found.구분, found.키), found.구분, found.키);
|
||||
else showToast(tx("Load_Failed"), "error");
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
} from "./M01_MasterData_UI_Side";
|
||||
import { loadTable } from "./M01_MasterData_UI_Logic_Note";
|
||||
import { tl } from "./M01_MasterData_UI_LogicLab_Text";
|
||||
import { renderParts } from "./M01_MasterData_UI_Parts";
|
||||
import { renderTables } from "./M01_MasterData_UI_Tables";
|
||||
import type { LogicHandle } from "./M01_MasterData_UI_LogicLab";
|
||||
import "./M01_MasterData_UI_Style.css";
|
||||
@@ -84,6 +85,7 @@ function buildPage(): HTMLElement {
|
||||
attrs: { hidden: "" },
|
||||
});
|
||||
const body = el("div", { className: "m01-master__body" });
|
||||
const partsHost = el("div"); // 기계 · 계수 표 아래 부품 문서(813-2)
|
||||
// 제목 줄 · 찾기 줄 = 「단가산출 로직」 화면과 같은 공용 조각
|
||||
const headBar = buildHeadBar({ title, status: [summary], buttons: [drop, save] });
|
||||
const head = el("div", {
|
||||
@@ -99,6 +101,7 @@ function buildPage(): HTMLElement {
|
||||
notice,
|
||||
el("p", { className: "m01-master__hint", text: L("M01_WideHint") }),
|
||||
body,
|
||||
partsHost,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -173,6 +176,7 @@ function buildPage(): HTMLElement {
|
||||
: pick.group === "요율"
|
||||
? renderRateRows(body, pick, query)
|
||||
: renderRows(body, pick, query);
|
||||
renderParts(partsHost, pick, (key) => openLogicTab(key));
|
||||
showNotice(isStale(pick.file.file, pick.file.version) ? [L("M01_FileStale")] : []);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/* =============================================================================
|
||||
* M01_MasterData_UI_Parts.ts
|
||||
* M01 「기계」 · 「계수」 표 아래 부품 문서(PLAN 813-2 · 요구 722-3 · 724~732) — 단가산출 기초 목록에서 뺀 보조 문서 자리
|
||||
* 기계 · 상세구분(기종) 고름 = 「시간당 기계경비 계산식」(그 기종 기계가 쓰는 방식 · 기계 수) · 「작업량 공식 틀」(그 기종 틀)
|
||||
* 계수 = 「계수식」(기계 무관 · 돈 아닌 식 넷)
|
||||
* 이름 누름 = 줄 쌓기 읽기 창(`createStackScreen` · 고침 없음) · [단가산출 기초에서 열기] = 지금 수식 탭 그대로 고침
|
||||
* 서버 = `GET /api/m01/parts?group&detail`(`M01_MasterData_Parts.of_kind`)
|
||||
* ========================================================================== */
|
||||
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import { createButton } from "@ui/ui_template_button";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { createStackScreen } from "../A00_Common/line_stack_screen";
|
||||
import type { Pick } from "./M01_MasterData_UI_Side";
|
||||
|
||||
interface Part {
|
||||
키: string;
|
||||
이름: string;
|
||||
결과단위: string;
|
||||
기계수?: number;
|
||||
}
|
||||
type Parts = Partial<Record<"기계경비식" | "작업량 틀" | "계수식", Part[]>>;
|
||||
|
||||
const T = {
|
||||
기계경비식: ["시간당 기계경비 계산식", "Hourly machine cost formula"],
|
||||
"작업량 틀": ["작업량 공식 틀", "Output formula templates"],
|
||||
계수식: ["계수식", "Coefficient formulas"],
|
||||
Open: ["단가산출 기초에서 열기", "Open in unit price basis"],
|
||||
Machines: ["기계", "machines"],
|
||||
None: ["없음", "None"],
|
||||
Close: ["닫기", "Close"],
|
||||
} as const;
|
||||
const tx = (k: keyof typeof T): string => T[k][currentLanguageIndex === 1 ? 1 : 0];
|
||||
const KINDS = ["기계경비식", "작업량 틀", "계수식"] as const;
|
||||
|
||||
/** 표 아래 부품 칸 — 기계(상세구분 있음) · 계수만 · 그 밖 = 비움 */
|
||||
export function renderParts(host: HTMLElement, pick: Pick, openLogic: (key: string) => void): void {
|
||||
host.replaceChildren();
|
||||
const group = pick.group === "계수" ? "계수식" : pick.group === "기계" ? "기계" : "";
|
||||
if (!group || (group === "기계" && !pick.detail)) return;
|
||||
let stack: ReturnType<typeof createStackScreen> | null = null;
|
||||
/** 줄 쌓기 읽기 창 — 고침 없음 · [단가산출 기초에서 열기] = 수식 탭에서 고침 */
|
||||
const show = (p: Part): void => {
|
||||
stack ??= createStackScreen({ base: "/m01/logic/doc", admin: false, cache: "m01_parts_view" });
|
||||
const view = stack;
|
||||
openModal({
|
||||
title: `${p.키} · ${p.이름}`,
|
||||
closeLabel: tx("Close"),
|
||||
width: "1100px",
|
||||
wide: true,
|
||||
mount: (body, close, actions) => {
|
||||
actions.append(
|
||||
createButton({
|
||||
label: tx("Open"),
|
||||
variant: "ghost",
|
||||
size: 28,
|
||||
onClick: () => {
|
||||
close();
|
||||
openLogic(p.키);
|
||||
},
|
||||
}),
|
||||
);
|
||||
body.append(view.root);
|
||||
view.draw(p.키, true);
|
||||
},
|
||||
});
|
||||
};
|
||||
const q = new URLSearchParams({ group, detail: pick.detail ?? "" });
|
||||
const mine = (host.dataset.partsAsk = q.toString()); // 늦게 온 옛 답은 버림
|
||||
void fetch(`${API_BASE_URL}/m01/parts?${q}`, { credentials: "include" })
|
||||
.then((r): Promise<Parts> | Parts => (r.ok ? (r.json() as Promise<Parts>) : {}))
|
||||
.catch((): Parts => ({}))
|
||||
.then((got) => {
|
||||
if (host.dataset.partsAsk !== mine) return;
|
||||
const blocks = KINDS.filter((k) => k in got).map((k) => {
|
||||
const list = got[k] ?? [];
|
||||
const items = list.map((p) => {
|
||||
const b = createButton({
|
||||
label: `${p.이름}${p.기계수 ? ` · ${tx("Machines")} ${p.기계수}` : ""}`,
|
||||
variant: "ghost",
|
||||
tone: "link",
|
||||
size: 28,
|
||||
onClick: () => show(p),
|
||||
});
|
||||
b.dataset.part = p.키;
|
||||
return b;
|
||||
});
|
||||
return el("section", {
|
||||
className: "m01-parts__block",
|
||||
attrs: { "data-parts": k },
|
||||
children: [
|
||||
el("h3", { className: "m01-parts__title", text: tx(k) }),
|
||||
items.length
|
||||
? el("div", { className: "m01-parts__list", children: items })
|
||||
: createText(tx("None")),
|
||||
],
|
||||
});
|
||||
});
|
||||
if (blocks.length)
|
||||
host.replaceChildren(el("div", { className: "m01-parts", children: blocks }));
|
||||
});
|
||||
}
|
||||
@@ -170,3 +170,21 @@ table.m01-master__grid {
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 부품 문서(813-2) — 기계 · 계수 표 아래 */
|
||||
.m01-parts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-12);
|
||||
margin-top: var(--spacing-16);
|
||||
}
|
||||
.m01-parts__title {
|
||||
margin: 0 0 var(--spacing-4);
|
||||
font-size: var(--text-body);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.m01-parts__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user