Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Hopyo_WizardSk.ts
T
eomsangdonandClaude Opus 5.5 1e6ec26dd0 feat(729-3): 불러오기 마법사 안 이은 줄 고르기 · [끝내기] 막음 · 단가산출 단계 · 일위대가표 띠 · 목록 열
- 새 Hopyo_Left: 안 만든 호표마다 /hopyo-unlinked 남은 줄 표 · 줄마다 고름(고르기 창 · 찾기 = 기초 · 노임 · 자재 · 기계 EQ · % 밑수 · 우리에 없음 · 고름 대기 후보 → SK 만들기) = 확인 창 캐시 → [저장] · 생성 몸 그대로
- 마법사: 단가산출 칸(새 Hopyo_WizardSk — 기초 · SK 차이 있음 갈래 표 · 고름 대기 후보 고름 → 고른 기초로 만들기 · 별칭 제안 + M01 별칭 장부 링크) · 안 이은 줄 띠 · [끝내기] = 다시 셈 → 남으면 막음 + 표
- 확인 창 · 엑셀 보기 창: 만들기 409 {까닭, 남은줄} → 고름 표 · [고르기 (n)] · 대상 꼴 노임 · 기계 · 비율 · % 줄 밑수 고른 것만 체크
- Api_Fetch StaleError.detail(409 몸) · 까닭 글
- 새 Labor_Unlinked: 일위대가표 「M01 연결 없음 n 줄(옛 자료)」 띠 · [연결] = 고정 줄 이음(노임 · 자재 · 기계)만 · 목록 열 「안 이은 줄」
- 시험: 새 test_729_3_hopyo_left_screen(409 → 남은 줄 · 만든 호표 안 물음 · 고름 → 몸 · 0 · 띠 줄 · 소스) · 낡은 시험 고침 — test_m02_hopyo_screen(마법사 /sangeun 부름이 단가산출 칸으로 옮김) · 헬퍼 셋(새 Hopyo_Left 를 대역 — DOM · 찾기 창)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:25:45 +09:00

146 lines
5.8 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Hopyo_WizardSk.ts
* 일위대가 불러오기 마법사 ② 단가산출 단계(PLAN 729-3 · 요구서 내역19 1장 7 · 2장 3) —
* · [단가산출 만들기] = 서버 `sangeun`(산근 블록마다 매칭 → 같으면 기초 · 다르면 SK = 기초 + 엑셀 차이 · 맞는 기초 없음 = 고름 대기)
* · 만든 줄 표 = 블록 · 이름 · 키 · 갈래(기초 · SK 차이 있음) · 이미 만든 블록은 「그대로」 수만
* · 고름 대기 표 = 블록마다 후보(판정 · 글) 고르기 칸 → [고른 기초로 만들기](바탕 = 고른 기초 · 엑셀 품 견줘 SK)
* · 별칭 제안 = 이 층 별칭 장부에 남음 · 모두에게 쓰이려면 시스템 관리자가 M01 › 별칭 장부에서 승인(링크 · 716 길 그대로)
* 계산 · 판정은 서버 — 화면은 보이기 · 고름만 · 자동 저장 없음(누를 때만 서버).
* ========================================================================== */
import { ROUTES } from "@config/config_frontend";
import { createBadge } from "@ui/ui_template_badge";
import { createButton, createSelectField, el, showToast } from "@ui/ui_template_elements";
import { t as L } from "@ui/ui_template_locale";
import { createTable } from "@ui/ui_template_table";
import { createText } from "@ui/ui_template_text";
import {
makeSangeun,
type HopyoScope,
type SangeunBuilt,
type SangeunPick,
} from "./M02_MasterTemplete_Hopyo_Api";
const why = (error: unknown): string => (error instanceof Error ? error.message : String(error));
/** 고름 대기 후보 글 — 「이름 [단위] · 판정 · 글」 */
export const candLabel = (c: { 이름: string; 단위: string; 판정: string; 글: string }): string =>
`${c.이름} [${c.단위}] · ${c.판정}${c.글 && c.글 !== c.판정 ? ` · ${c.글}` : ""}`;
/** 만든 갈래 꼬리표 — 기초 = 초록 · SK = 차이 있음 */
const kindBadge = (kind: string): HTMLElement =>
kind === "SK"
? createBadge(L("M02_WizSkDiff"), "accent")
: createBadge(L("M02_WizSkBase"), "success");
/** ② 단가산출 단계 칸 — `root` 를 마법사 본 칸에 붙임 */
export function sangeunStep(scope: HopyoScope, key: string): { root: HTMLElement } {
const out = el("div", { className: "m02-hopyo", attrs: { "data-wiz-sk": "" } });
const make = createButton({
label: L("M02_WizSkMake"),
variant: "filled",
onClick: () => void run(),
});
make.title = L("M02_WizSkMakeTip");
make.dataset.hopyoSangeunMake = "";
const root = el("div", {
className: "m02-hopyo",
children: [el("div", { className: "ui-toolbar", children: [make] }), out],
});
async function run(picks?: SangeunPick): Promise<void> {
make.disabled = true;
try {
const got = await makeSangeun(scope, key, picks);
draw(got);
showToast(summary(got), got.고름대기.length ? "warning" : "success");
} catch (error) {
showToast(why(error), "error");
} finally {
make.disabled = false;
}
}
const summary = (got: SangeunBuilt): string =>
L("M02_WizSkSummary")
.replace("{b}", String(got.블록))
.replace("{base}", String(got.기초))
.replace("{sk}", String(got.SK))
.replace("{keep}", String(got.그대로))
.replace("{wait}", String(got.고름대기.length));
function draw(got: SangeunBuilt): void {
const made = createTable(
[
{ label: L("M02_WizSkBlock"), width: "80px" },
{ label: L("M02_SangeunColName") },
{ label: L("M02_WizSkKey"), width: "110px" },
{ label: L("M02_WizSkKind"), width: "120px" },
],
got.만듦.map((x) => [`단산${x.번}`, x.이름, x.키, kindBadge(x.갈래)]),
{ density: "data", empty: L("M02_WizSkNoneMade") },
);
const selects = new Map<number, HTMLSelectElement>();
const wait = createTable(
[
{ label: L("M02_WizSkBlock"), width: "80px" },
{ label: L("M02_SangeunColName") },
{ label: L("M02_SangeunColUnit"), width: "64px" },
{ label: L("M02_LeftWhy") },
{ label: L("M02_LeftBase"), width: "40%" },
],
got.고름대기.map((x) => {
const pick = createSelectField({
options: [{ value: "", text: L("M02_WizSkNoPick") }].concat(
x.후보.map((c) => ({ value: c.로직, text: candLabel(c) })),
),
compact: true,
size: 24,
ariaLabel: `${L("M02_LeftBase")} 단산${x.번}`,
});
pick.select.dataset.wizWait = String(x.번);
selects.set(x.번, pick.select);
return [`단산${x.번}`, x.이름, x.단위 ?? "", x.까닭, pick.root];
}),
{ density: "data", empty: L("M02_WizSkNoWait") },
);
const again = createButton({
label: L("M02_WizSkRemake"),
variant: "filled",
size: 28,
onClick: () => {
const picks: SangeunPick = {};
for (const [no, s] of selects) if (s.value) picks[no] = { 로직: s.value };
if (!Object.keys(picks).length) return showToast(L("M02_WizSkPickFirst"), "warning");
void run(picks);
},
});
again.dataset.wizRemake = "";
const ledger = createButton({
label: L("M02_WizSkLedger"),
variant: "link",
onClick: () => void window.open(`#/${ROUTES.M01_MASTER_DATA}`, "_blank"),
});
out.replaceChildren(
createText(summary(got), "muted", 14),
made,
...(got.고름대기.length
? [
createText(L("M02_WizSkWaitHint"), "hint", 14),
wait,
el("div", { className: "ui-toolbar", children: [again] }),
]
: []),
el("div", {
className: "ui-toolbar",
children: [
createText(L("M02_WizSkAlias").replace("{n}", String(got.별칭 ?? 0)), "muted", 14),
ledger,
],
}),
);
}
return { root };
}