- 고름 대기 칸에 후보(앞 셋)마다 줄 비교 표(줄 · 엑셀 품 · 기초 품 · 꼴 배지) · 후보 고름 칸과 동기 · 칸 글에 같음 · 품 다름 수 · 판정 · 미리 고름 그대로 - 못옮김 잇기 창 · 안 이은 줄 창에 M01 요소 후보 셋 단추(누르면 이음 고름) · 안 이은 줄 창 기초 후보 칸에는 요소 후보가 안 섞이게 가름 - 새 파일 Hopyo_Compare.ts · 새 지킴 시험 test_746_2_hopyo_compare_screen Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
239 lines
10 KiB
TypeScript
239 lines
10 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_WizardSk.ts
|
|
* 일위대가 불러오기 마법사 ② 단가산출 단계(PLAN 729-3 · 요구서 내역19 1장 7 · 2장 3) —
|
|
* · [단가산출 만들기] = 서버 `sangeun`(산근 블록마다 매칭 → 같으면 기초 · 다르면 SK = 기초 + 엑셀 차이 · 맞는 기초 없음 = 고름 대기)
|
|
* · 만든 줄 표 = 블록 · 이름 · 키 · 갈래(기초 · SK 차이 있음) · 이미 만든 블록은 「그대로」 수만
|
|
* · 고름 대기 표 = 블록마다 후보(판정 · 글) 고르기 칸 → [고른 기초로 만들기](바탕 = 고른 기초 · 엑셀 품 견줘 SK) ·
|
|
* 단위 성격 다른 후보(729-4 `계수`) = 계수 칸(가 1 = 나 몇) 함께 → `환산 {계수}`
|
|
* 후보마다 줄 비교 표(746-2 · `Hopyo_Compare` · 서버 `줄비교`) — 고르기 칸 아래 · 후보 단추로 바꿔 봄
|
|
* · 못옮김(729-4) = 만든 줄마다 수 · [잇기] = 못옮김 잇기 창(`Hopyo_Lost`) · 만든 줄은 이 칸이 모아 둠(누를 때마다 더함) ·
|
|
* 이미 만든 SK 의 남은 못옮김(729-5 `남은못옮김`)도 같은 표
|
|
* · 같은 내용(733-1) = 갈래 칸 꼬리표 「같은 것 씀」(이 층 같은 SK 를 씀) · 「위 층에 같은 것 — 연결 권함」(회사 · 마스터 · 알림만) ·
|
|
* 수는 요약 글에
|
|
* · 별칭 제안 = 이 층 별칭 장부에 남음 · 모두에게 쓰이려면 시스템 관리자가 M01 › 별칭 장부에서 승인(링크 · 716 길 그대로)
|
|
* 계산 · 판정은 서버 — 화면은 보이기 · 고름만 · 자동 저장 없음(누를 때만 서버).
|
|
* ========================================================================== */
|
|
|
|
import { ROUTES } from "@config/config_frontend";
|
|
import { createBadge } from "@ui/ui_template_badge";
|
|
import {
|
|
createButton,
|
|
createInputField,
|
|
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";
|
|
import { compareBrief, compareView } from "./M02_MasterTemplete_Hopyo_Compare";
|
|
import { openLost } from "./M02_MasterTemplete_Hopyo_Lost";
|
|
|
|
type Made = SangeunBuilt["만듦"][number];
|
|
|
|
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],
|
|
});
|
|
|
|
const made = new Map<number, Made>(); // 이 칸에서 만든 줄(누를 때마다 더함)
|
|
const reused = new Set<number>(); // 733-1 같은 것 씀 블록
|
|
const above = new Map<number, string[]>(); // 733-1 위 층 같은 것 「층 키」
|
|
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)) +
|
|
(got.같은것씀?.length || got.위층같음?.length
|
|
? L("M02_WizSkSameSum")
|
|
.replace("{same}", String(got.같은것씀?.length ?? 0))
|
|
.replace("{above}", String(new Set(got.위층같음?.map((x) => x.번)).size))
|
|
: "");
|
|
|
|
/** 갈래 칸 — 갈래 + 같은 것 씀 · 위 층 같은 것 꼬리표 */
|
|
const kindCell = (x: Made): HTMLElement => {
|
|
const tags: HTMLElement[] = [kindBadge(x.갈래)];
|
|
if (reused.has(x.번)) {
|
|
const tag = createBadge(L("M02_WizSkSame"), "info");
|
|
tag.title = L("M02_WizSkSameTip");
|
|
tag.dataset.wizSame = String(x.번);
|
|
tags.push(tag);
|
|
}
|
|
const up = above.get(x.번);
|
|
if (up?.length) {
|
|
const tag = createBadge(L("M02_WizSkAbove"), "warning");
|
|
tag.title = up.join(" · ");
|
|
tag.dataset.wizAbove = String(x.번);
|
|
tags.push(tag);
|
|
}
|
|
return el("div", { className: "ui-toolbar", children: tags });
|
|
};
|
|
|
|
/** 못옮김 칸 — 수 + [잇기](남은 것 있을 때) */
|
|
const lostCell = (x: Made, got: SangeunBuilt): HTMLElement => {
|
|
const n = x.못옮김?.length ?? 0;
|
|
if (!n) return el("span", { text: "0" });
|
|
const go = createButton({
|
|
label: `${L("M02_LostOpen")} (${n})`,
|
|
size: 24,
|
|
onClick: () =>
|
|
openLost({ scope, key, 번: x.번, sk: x.키, 이름: x.이름, lost: x.못옮김 ?? [],
|
|
done: (next) => draw({ ...got, 만듦: next.만듦, 별칭: got.별칭 }) }), // prettier-ignore
|
|
});
|
|
go.dataset.wizLost = String(x.번);
|
|
return go;
|
|
};
|
|
|
|
function draw(got: SangeunBuilt): void {
|
|
for (const x of got.남은못옮김 ?? []) if (!made.has(x.번)) made.set(x.번, { ...x, 갈래: "SK" });
|
|
for (const x of got.만듦) made.set(x.번, x);
|
|
for (const x of got.같은것씀 ?? []) reused.add(x.번);
|
|
for (const x of got.위층같음 ?? []) {
|
|
const at = x.층 === "system" ? L("M02_PickerSystem") : L("M02_PickerCompany");
|
|
above.set(x.번, [...(above.get(x.번) ?? []), `${at} ${x.키}`]);
|
|
}
|
|
const table = createTable(
|
|
[
|
|
{ label: L("M02_WizSkBlock"), width: "80px" },
|
|
{ label: L("M02_SangeunColName") },
|
|
{ label: L("M02_WizSkKey"), width: "110px" },
|
|
{ label: L("M02_WizSkKind"), width: "260px" },
|
|
{ label: L("M02_LostCol"), width: "120px" },
|
|
],
|
|
[...made.values()]
|
|
.sort((a, b) => a.번 - b.번)
|
|
.map((x) => [`단산${x.번}`, x.이름, x.키, kindCell(x), lostCell(x, got)]),
|
|
{ density: "data", empty: L("M02_WizSkNoneMade") },
|
|
);
|
|
const selects = new Map<number, HTMLSelectElement>();
|
|
const coefs = new Map<number, HTMLInputElement>();
|
|
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: "62%" },
|
|
],
|
|
got.고름대기.map((x) => {
|
|
const pick = createSelectField({
|
|
options: [{ value: "", text: L("M02_WizSkNoPick") }].concat(
|
|
x.후보.map((c) => ({
|
|
value: c.로직,
|
|
text: candLabel(c) + (c.줄비교 ? ` · ${compareBrief(c.줄비교)}` : ""),
|
|
})),
|
|
),
|
|
compact: true,
|
|
size: 24,
|
|
ariaLabel: `${L("M02_LeftBase")} 단산${x.번}`,
|
|
});
|
|
pick.select.dataset.wizWait = String(x.번);
|
|
selects.set(x.번, pick.select);
|
|
const coef = createInputField({ type: "number", min: 0, compact: true, size: 24,
|
|
placeholder: L("M02_WizSkCoef") }); // prettier-ignore
|
|
coef.input.dataset.wizCoef = String(x.번);
|
|
const need = (): void => {
|
|
const c = x.후보.find((y) => y.로직 === pick.select.value)?.계수;
|
|
coef.root.hidden = !c;
|
|
coef.input.title = c ? `${c.가} 1 = ${c.나} ?` : "";
|
|
};
|
|
const cmp = compareView(x.후보);
|
|
pick.select.addEventListener("change", () => {
|
|
need();
|
|
cmp?.show(pick.select.value);
|
|
});
|
|
need();
|
|
coefs.set(x.번, coef.input);
|
|
return [`단산${x.번}`, x.이름, x.단위 ?? "", x.까닭, el("div", { children: [el("div", { className: "ui-toolbar", children: [pick.root, coef.root] }), ...(cmp ? [cmp.root] : [])] })]; // prettier-ignore
|
|
}),
|
|
{ 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) continue;
|
|
const c = Number(coefs.get(no)?.value);
|
|
const need = got.고름대기.find((x) => x.번 === no)?.후보.find((y) => y.로직 === s.value)?.계수; // prettier-ignore
|
|
if (need && !(c > 0)) return showToast(L("M02_WizSkCoefNeed").replace("{n}", String(no)), "warning"); // prettier-ignore
|
|
picks[no] = { 로직: s.value, ...(need ? { 환산: { 계수: c } } : {}) };
|
|
}
|
|
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),
|
|
table,
|
|
...(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 };
|
|
}
|