- 새 M02_San_MatPick: 종 열쇠(이름 · 규격 · 단위 · 단서) · 고름 자리 = 개인 층 실무자료/재료고름/<파일 지문>.json([끝내기] 뒤 남음 · 같은 파일 다시 올리면 그대로) · 판(블록마다 재료찾기 판정 → 종 · 첫 판 한 번 셈 → 산근 저장본 칸) · 만들 때 얹기 · 이미 만든 SK 얹기(가져온 그대로인 줄만 · 바꿈 · 따로 고른 줄 그대로 · 풀면 가져온 그대로)
- 만들기: from-hopyo 가 그 파일 고름째 만듦(저장 안 한 판 고름 = 만들 때 저장) · 원본 지문 · 고른 줄 = 이음 + 재료찾기.고름 · 문서 셈 우리 값(엑셀 값 기억 없음) · 별도계상 = 품목만(줄 이음 · 단가 0 그대로)
- 문서 머리 가져온.재료(판정 · 후보 · 단서 · 처음 연결) · 가져온.지문
- 길: GET/POST hopyo-materials/{HP}(판 · [저장] · 만든 것 적용은 물음 예일 때만) · GET/POST material-picks/{지문}(SK 상세 물음)
- 화면: 단가산출 목록 위 「고를 재료」 판(후보 고름 · 찾기 · 고름 풀기 · n 블록에 적용 · 이어진 재료 접힘 · [저장]) · 저장 안 한 고름 = 떠날 때 물음 · 줄 쌓기 재료 줄 판정 배지 · 풍선말(판정 · 단서 · 후보) · 이름 단추 · [이음 고르기] 뒤 「같은 파일 n 줄」 물음
- 별도계상 설명 = 「별도계상 · 품목: 이름 규격」(엔진 이은 품목은 자재표 이름) — test_827_2 기대 글 고침(품목 이음 → 품목 이름)
- 시험 test_827_3 셋(파일 고름 블록 둘 · 다른 파일 0 · 별칭 · 마스터 그대로 · 풀기 · 다시 올림 유지 · 물음 예일 때만 · 따로 바꾼 줄 보존 · 별도계상 품목 + 단가 0)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
6.7 KiB
TypeScript
143 lines
6.7 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_Hopyo_SheetSk.ts
|
|
* 엑셀 보기 창 오른쪽 판 바꾸기(PLAN 780-2 · 809-1 · 요구 627 · 702) — 탭 따라:
|
|
* · 일위대가표 = 호표 목록(`Hopyo_Sheet` 판 그대로) · 단가산출표 · 단가산출 상세 = 단가산출 목록
|
|
* (블록 줄 = 단산 N 이름 · 단위 · 부름 수 / 일위대가 밖 · 만든 가져온 식 SK · 재료 = `hopyo-marks`)
|
|
* · 블록 줄 누름 → 지금 탭의 그 블록 자리 · 줄 끝 [가져온 식으로 열기](만들 층) · [내 단가산출로](개인 층 · 807-1) = `Hopyo_Imported` 모달
|
|
* (경쟁사 로직 그대로 · 엑셀 단가 · 요소 이음 · 소요량 이음)
|
|
* · 시트 칸 고름 → 그 행 블록을 목록에서 강조(`SheetNav.blockAt`)
|
|
* · 목록은 처음 단가산출 탭에 올 때 읽음 · 기초 판정 · 블록 판 · [옮기기] 없음(요구 703)
|
|
* · 머리 = 「고를 재료」 판(827-3 `Hopyo_MatPick`) — 저장 안 한 고름은 블록 만들 때 같이 감
|
|
* ========================================================================== */
|
|
|
|
import { createBadge } from "@ui/ui_template_badge";
|
|
import { createButton, el } from "@ui/ui_template_elements";
|
|
import { t as L } from "@ui/ui_template_locale";
|
|
import { createListRow } from "@ui/ui_template_list_row";
|
|
import { createText } from "@ui/ui_template_text";
|
|
import type { HopyoScope } from "./M02_MasterTemplete_Hopyo_Api";
|
|
import { openImported } from "./M02_MasterTemplete_Hopyo_Imported";
|
|
import { createMatPick } from "./M02_MasterTemplete_Hopyo_MatPick";
|
|
import { readHopyoMarks, type SheetNav } from "./M02_MasterTemplete_Hopyo_SheetNav";
|
|
import type { BlockMark } from "./logic/M02_MasterTemplete_Hopyo_SheetMap";
|
|
|
|
export interface SheetSkOptions {
|
|
scope: HopyoScope;
|
|
key: string;
|
|
target: HopyoScope;
|
|
canWrite: boolean;
|
|
nav: SheetNav;
|
|
/** 호표 목록 판 — 단가산출 탭이면 숨김 · 그 뒤에 단가산출 판을 붙임 */
|
|
side: HTMLElement;
|
|
/** 목록 읽은 뒤(부르는 쪽 끝내기 글) */
|
|
onChange?: () => void;
|
|
}
|
|
|
|
const STRIPE = { done: "var(--color-success)", ready: "var(--color-info)", out: "var(--color-border)" } as const; // prettier-ignore
|
|
|
|
export function createSheetSk(opt: SheetSkOptions) {
|
|
const list = el("div", { className: "m02-hopyo-sheet__list" });
|
|
const mat = createMatPick({ scope: opt.scope, key: opt.key, target: opt.target }); // 827-3
|
|
const root = el("div", { className: "m02-hopyo-sheet__side m02-hopyo-sheet__sk", children: [mat.root, list] }); // prettier-ignore
|
|
root.setAttribute("data-hopyo-sk-side", "");
|
|
root.hidden = true;
|
|
opt.side.after(root);
|
|
let blocks: BlockMark[] | null = null;
|
|
let asked = false;
|
|
let sheet = "";
|
|
let current: number | null = null;
|
|
|
|
const mineAt = { layer: "personal", owner: null } as const; // [내 단가산출로](807-1) = 늘 개인 층
|
|
const open = (no: number, mine: boolean): void =>
|
|
openImported({ file: opt.scope, key: opt.key, no, target: mine ? mineAt : opt.target, canWrite: mine || opt.canWrite, picks: mat.pending(), onMade: mat.reload }); // prettier-ignore
|
|
|
|
function draw(): void {
|
|
if (!blocks) return;
|
|
list.replaceChildren(
|
|
...blocks.map((b) => {
|
|
const calls = b.부름?.length ?? 0;
|
|
const sub = [b.단위 ?? "", calls ? `${L("M02_JudgeCalls")} ${calls}` : L("M02_JudgeOutside")].filter(Boolean).join(" · "); // prettier-ignore
|
|
const go = createButton({ label: L("M02_CheckImported"), size: 24, variant: "ghost", onClick: (e) => { e.stopPropagation(); open(b.번, false); } }); // prettier-ignore
|
|
go.dataset.skOpen = String(b.번);
|
|
go.hidden = opt.target.layer === "personal" && !opt.target.owner; // 만들 층 = 개인이면 [내 단가산출로]와 같은 일
|
|
const mine = createButton({ label: L("M02_ToMySk"), size: 24, variant: "ghost", onClick: (e) => { e.stopPropagation(); open(b.번, true); } }); // prettier-ignore
|
|
mine.dataset.skMine = String(b.번);
|
|
const tail = el("span", { className: "ui-toolbar", children: [...(b.SK ? [createBadge(`${L("M02_JudgeMade")} ${b.SK}`, "success")] : []), go, mine] }); // prettier-ignore
|
|
const row = createListRow({
|
|
label: `${L("M02_JudgeBlock").replace("{n}", String(b.번))} ${b.이름 ?? ""}`,
|
|
sub,
|
|
trailing: tail,
|
|
stripe: b.SK ? STRIPE.done : calls ? STRIPE.ready : STRIPE.out,
|
|
active: b.번 === current,
|
|
size: 36,
|
|
onClick: () => pick(b.번),
|
|
});
|
|
row.dataset.judgeSk = String(b.번);
|
|
return row;
|
|
}),
|
|
);
|
|
}
|
|
|
|
/** 목록 — 처음 단가산출 탭에 올 때 한 번 */
|
|
function ensure(): void {
|
|
if (asked) return;
|
|
asked = true;
|
|
mat.ensure();
|
|
if (!blocks) list.replaceChildren(createText(L("M02_JudgeLoading"), "hint"));
|
|
void readHopyoMarks(opt.scope, opt.key, opt.target)
|
|
.then((got) => {
|
|
blocks = got;
|
|
draw();
|
|
opt.onChange?.();
|
|
})
|
|
.catch((e: Error) => list.replaceChildren(createText(e.message, "error")));
|
|
}
|
|
|
|
/** 블록 고름 — 그 자리 뜀 */
|
|
function pick(no: number): void {
|
|
current = no;
|
|
draw();
|
|
const list = opt.nav.role(sheet) === "단가산출표";
|
|
const spot = list
|
|
? (opt.nav.spotOf(no, "단가산출표") ?? opt.nav.spotOf(no, "단가산출상세"))
|
|
: (opt.nav.spotOf(no, "단가산출상세") ?? opt.nav.spotOf(no, "단가산출표"));
|
|
if (spot) opt.nav.jumpTo(spot.시트, spot.행);
|
|
}
|
|
|
|
return {
|
|
root,
|
|
/** 아직 SK 안 만든 블록 수 · 그중 일위대가 밖 — 목록 전 null */
|
|
count: (): { 블록: number; 밖: number; 줄: number } | null => {
|
|
if (!blocks) return null;
|
|
const left = blocks.filter((b) => !b.SK);
|
|
return { 블록: left.length, 밖: left.filter((b) => !b.부름?.length).length, 줄: 0 };
|
|
},
|
|
/** 그 블록으로 뜀 */
|
|
show(번: number): void {
|
|
ensure();
|
|
pick(번);
|
|
},
|
|
/** 시트 고름 · 탭 바꿈 — 판 바꾸기 · 강조 */
|
|
at(sel: { 시트: string; 칸: string }): void {
|
|
sheet = sel.시트;
|
|
const role = opt.nav.role(sel.시트);
|
|
const sk = role === "단가산출표" || role === "단가산출상세";
|
|
opt.side.hidden = sk;
|
|
root.hidden = !sk;
|
|
opt.side.parentElement?.classList.toggle("is-sk", sk);
|
|
if (!sk) return;
|
|
ensure();
|
|
const no = opt.nav.blockAt(sel.시트, Number(/\d+/.exec(sel.칸)?.[0] ?? 0));
|
|
if (no !== null && no !== current) {
|
|
current = no;
|
|
draw();
|
|
}
|
|
},
|
|
destroy(): void {},
|
|
/** 저장 안 한 재료 고름(827-3) */
|
|
dirty: mat.dirty,
|
|
};
|
|
}
|
|
|
|
export type SheetSk = ReturnType<typeof createSheetSk>;
|