- 단가산출 칸 = 서버 줄 판정(빈 확인) 블록 표: 판정 · 우리 금액 · 엑셀 금액(참고) · 근거 꼬리표 · 만든 SK · [확인] 칩(고를 줄 0 일 때만) · [줄] 펼침 · [보기]
- 줄 표(새 Hopyo_SkLines) = 묶음 · 이름 · 규격 · 매개값 · 상태 칩(엑셀 칠과 한 규칙 unitTone) · 꼬리표 · 방법 · 금액 · 차 · 고름(후보 · 찾기 · 율 기준 · 뺌 + 까닭 · 지움) · 엑셀 보기 뛰기
- 고름 · 확인 = 캐시 m02-hopyo-sk · [확인한 것 옮기기] = 몸 {확인: [{번, 고름}]} · 서버가 다시 셈 · 자동 확인 · 「모두 확인」 없음
- 순수 꼴 = 새 logic/Hopyo_SkPick · 길 = 새 Hopyo_SkApi(Hopyo_Api 안 늘림)
- 마법사 [끝내기] 막힘 글 앞에 「단가산출 확인 안 한 블록 n · 고를 줄 m」(Hopyo_Wizard 한 곳)
- 걷음: 옛 고름 대기 기초 고름 · 줄 비교(Compare.compareView · compareBrief · Api SangeunCompare · CompareLook · CSS) — 쓰는 곳 없음 · 못옮김 창 잇기 만들기(옛 바탕 몸 = 서버가 걷어 422) → 보기만 + 줄 찾기 단추 lineFind 로 다시 씀 · 안 쓰는 문구 39
- 낡은 시험 고침: test_746_2(줄 비교 · 못옮김 요소 단추 시험 걷음 — 기능 걷음) · test_729_3(candLabel · 옛 잇기 몸 기대값 걷음) · test_m02_hopyo_screen(sangeunStep 인자) · 새 test_778_5_sk_pick_screen + helper_778_5_sk_pick.cjs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
164 lines
6.6 KiB
TypeScript
164 lines
6.6 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_SkLines.ts
|
|
* 마법사 단가산출 블록 펼침 = 줄 표(PLAN 778-5 D-5 · 요구 623 · 624) — 서버 줄 판정(D-3) + 캐시 고름 겹침.
|
|
* · 칸 = 묶음 · 이름 · 규격 · 매개값 · 상태 칩(이어짐 · 고를 것 · 못 이음 · 밖 · 뺌 — 엑셀 보기 칠과 한 규칙
|
|
* `SheetMap.unitTone`) + 꼬리표(「기본 — 건설업 작업반장」 · 「내가 고름」) · 방법 글 · 우리 금액 · 엑셀 금액 · 차(참고)
|
|
* · 고름(밖 줄 빼고) = 서버 후보 단추 · [찾기](`Hopyo_Lost.lineFind` = 찾아 넣기 창) · 율 = 기준 고르기 ·
|
|
* 하위 · 값 = 뺌만 · [뺌] + 까닭 · [고름 지움] — 고름은 부르는 쪽 캐시(`onPick`)
|
|
* · [보기] = 엑셀 보기 단가산출 상세 그 행(779-4 뛰기)
|
|
* 금액 · 판정은 서버 — 고른 줄 금액은 [확인한 것 옮기기] 뒤 서버가 다시 셈(그 전 「—」).
|
|
* ========================================================================== */
|
|
|
|
import { createBadge, type BadgeTone } from "@ui/ui_template_badge";
|
|
import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import { createTable, type Cell } from "@ui/ui_template_table";
|
|
import { formatNumber, formatPrice } from "../common_util/common_util_money";
|
|
import type { HopyoScope } from "./M02_MasterTemplete_Hopyo_Api";
|
|
import { lineFind } from "./M02_MasterTemplete_Hopyo_Lost";
|
|
import { unitTone, type Tone } from "./logic/M02_MasterTemplete_Hopyo_SheetMap";
|
|
import {
|
|
BASES,
|
|
GROUP,
|
|
candPick,
|
|
candText,
|
|
dropOnly,
|
|
lineView,
|
|
pickable,
|
|
type SkBlock,
|
|
type SkLine,
|
|
type SkPick,
|
|
} from "./logic/M02_MasterTemplete_Hopyo_SkPick";
|
|
|
|
const BADGE: Record<Tone, BadgeTone> = {
|
|
success: "success",
|
|
warning: "warning",
|
|
info: "info",
|
|
danger: "danger",
|
|
muted: "neutral",
|
|
};
|
|
const FIND = { L: "노임", X: "기계", M: "자재" } as const;
|
|
const won = (v: number | null | undefined): string =>
|
|
v === null || v === undefined ? "—" : formatPrice(v);
|
|
|
|
export interface SkLinesOptions {
|
|
scope: HopyoScope;
|
|
block: SkBlock;
|
|
picks: Record<string, SkPick>;
|
|
/** 고름 넣기 · 지움(null) */
|
|
onPick: (차례: number, p: SkPick | null) => void;
|
|
/** 엑셀 보기 그 행 */
|
|
onSheet?: (행: number) => void;
|
|
}
|
|
|
|
/** 상태 칩 + 꼬리표 */
|
|
function stateCell(line: SkLine, pick: SkPick | undefined): HTMLElement {
|
|
const v = lineView(line, pick);
|
|
const diff = !v.mine && line.차 !== null && line.차 !== undefined && Math.abs(line.차) >= 1;
|
|
const tone = v.상태 === "뺌" ? "muted" : unitTone(v.상태, diff);
|
|
const chip = createBadge(v.상태, BADGE[tone]);
|
|
chip.dataset.skState = v.상태;
|
|
const tags = v.꼬리표.map((t) => {
|
|
const tag = createBadge(t, t === "내가 고름" ? "accent" : "neutral");
|
|
tag.dataset.skTag = t;
|
|
return tag;
|
|
});
|
|
return el("div", { className: "ui-toolbar", children: [chip, ...tags] });
|
|
}
|
|
|
|
/** 고름 칸 — 후보 · 찾기 · 율 기준 · 뺌 + 까닭 · 지움 */
|
|
function pickCell(opt: SkLinesOptions, line: SkLine, pick: SkPick | undefined): HTMLElement {
|
|
const box = el("div", { className: "ui-toolbar", attrs: { "data-sk-pick": String(line.차례) } });
|
|
if (!pickable(line)) return box;
|
|
const put = (p: SkPick | null): void => opt.onPick(line.차례, p);
|
|
if (!dropOnly(line)) {
|
|
for (const c of line.후보.slice(0, 3)) {
|
|
const p = candPick(line.묶음, c);
|
|
if (!p) continue;
|
|
const b = createButton({ label: candText(c), size: 24, variant: "ghost", onClick: () => put(p) }); // prettier-ignore
|
|
b.dataset.skCand = String(line.차례);
|
|
b.title = c.키 ?? c.자재 ?? c.글 ?? "";
|
|
box.append(b);
|
|
}
|
|
const kind = FIND[line.묶음 as keyof typeof FIND];
|
|
if (kind) box.append(lineFind({ scope: opt.scope, 갈래: kind, 이름: line.이름 ?? "", 규격: line.규격, done: put })); // prettier-ignore
|
|
if (line.묶음 === "R") {
|
|
const base = createSelectField({
|
|
options: [{ value: "", text: L("M02_SkBasePick") }].concat(
|
|
BASES.map((b) => ({ value: b, text: b })),
|
|
),
|
|
compact: true,
|
|
size: 24,
|
|
ariaLabel: L("M02_SkBasePick"),
|
|
onChange: (v) => v && put({ 기준: v }),
|
|
});
|
|
box.append(base.root);
|
|
}
|
|
}
|
|
const why = createInputField({ compact: true, size: 24, placeholder: L("M02_SkDropWhy") });
|
|
why.input.dataset.skWhy = String(line.차례);
|
|
const drop = createButton({
|
|
label: L("M02_SkDrop"),
|
|
size: 24,
|
|
variant: "ghost",
|
|
onClick: () => put({ 뺌: why.input.value.trim() || L("M02_SkDropDefault") }),
|
|
});
|
|
drop.dataset.skDrop = String(line.차례);
|
|
box.append(why.root, drop);
|
|
if (pick) {
|
|
const clear = createButton({ label: L("M02_SkClear"), size: 24, variant: "link", onClick: () => put(null) }); // prettier-ignore
|
|
clear.dataset.skClear = String(line.차례);
|
|
box.append(clear);
|
|
}
|
|
return box;
|
|
}
|
|
|
|
/** 블록 줄 표 */
|
|
export function skLineTable(opt: SkLinesOptions): HTMLElement {
|
|
const rows: Cell[][] = opt.block.줄.map((line) => {
|
|
const pick = opt.picks[String(line.차례)];
|
|
const v = lineView(line, pick);
|
|
const param = Object.entries(line.매개값)
|
|
.map(([k, n]) => `${k} ${formatNumber(n)}`)
|
|
.join(" · ");
|
|
const how = [line.방법, v.글].filter(Boolean).join(" · ");
|
|
const sheet =
|
|
line.행 !== null && opt.onSheet
|
|
? createButton({ label: L("M02_SkSheet"), size: 24, variant: "link", onClick: () => opt.onSheet!(line.행!) }) // prettier-ignore
|
|
: "";
|
|
if (sheet) sheet.dataset.skSheet = String(line.행);
|
|
return [
|
|
GROUP[line.묶음] ?? line.묶음,
|
|
line.이름 ?? "",
|
|
line.규격,
|
|
param,
|
|
stateCell(line, pick),
|
|
how,
|
|
v.mine ? "—" : won(line.우리금액),
|
|
won(line.엑셀금액),
|
|
v.mine || line.차 === null || line.차 === undefined ? "—" : won(line.차),
|
|
pickCell(opt, line, pick),
|
|
sheet,
|
|
];
|
|
});
|
|
const table = createTable(
|
|
[
|
|
{ label: L("M02_SkColGroup"), width: "48px" },
|
|
{ label: L("M02_SangeunColName") },
|
|
{ label: L("M02_LeftSpec") },
|
|
{ label: L("M02_SkColParam") },
|
|
{ label: L("M02_SkColState") },
|
|
{ label: L("M02_SkColHow") },
|
|
{ label: L("M02_SkColOurs"), align: "end" },
|
|
{ label: L("M02_SkColExcel"), align: "end" },
|
|
{ label: L("M02_SkColDiff"), align: "end" },
|
|
{ label: L("M02_SkColPick") },
|
|
{ label: L("M02_SkSheet"), width: "52px" },
|
|
],
|
|
rows,
|
|
{ density: "data", empty: L("M02_SkNoLines") },
|
|
);
|
|
table.dataset.skLines = String(opt.block.번);
|
|
return table;
|
|
}
|