Files
Aislo/M02_MasterTemplete/M02_MasterTemplete_Hopyo_SheetSk.ts
T
eomsangdonandClaude Opus 5.5 dcecc0b326 feat(785-6): 엑셀 보기에 줄 판 · 블록 판 · [옮기기] 이음
- Hopyo_Sheet.ts: [수정] = 시트 아래 줄 판(createJudgeRows · 그 호표 자리 뜀) · 오른쪽 판 아래 [옮기기](createJudgeMove) · 고름 캐시 한 벌 · 옮긴 뒤 목록 · 칠 · 단가산출 목록 다시 읽음
- Hopyo_SheetSk.ts: 단가산출 목록 = 새 블록 판정(hopyo-judge 목록 · 판정 · 고를 것 n · 표지 · 부름 · 만든 SK · 캐시 확인) · 줄 누름 = 그 자리 + 블록 판(createJudgeBlock)
- Hopyo_JudgeApi.ts: 목록 블록 줄 판정 · 고를수 · Block.ts 타입 흠 둘
- 낡은 시험 고침(까닭 = 785-6 이 옛 마법사 단가산출 칸 · 확인 창을 새 판으로 갈음): test_627_sheet_sk(판 바꿈 · 강조 · 뛰기를 새 목록으로 · helper_778_5 sheetSk 대역 새 꼴) · test_785_4 줄 판 자리 손잡이 · test_m02_hopyo_screen [수정] = 줄 판
- test_642_6: 엑셀 보기 이음 · 가운데 페이지 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:16:43 +09:00

174 lines
6.7 KiB
TypeScript

/* =============================================================================
* M02_MasterTemplete_Hopyo_SheetSk.ts
* 엑셀 보기 창 오른쪽 판 바꾸기(PLAN 780-2 · 785-6 · 요구 627 · 642) — 탭 따라:
* · 일위대가표 = 호표 목록(`Hopyo_Sheet` 판 그대로) · 단가산출표 · 단가산출 상세 = 단가산출 목록
* (새 블록 판정 `hopyo-judge` 목록 블록 줄 = 판정 · 고를 것 n · 표지 · 부름 · 만든 SK · 캐시 확인)
* · 목록 블록 줄 누름 → 지금 탭의 그 블록 자리 + 시트 아래 줄 판 = 블록 판(`Hopyo_Block.createJudgeBlock` · [확인])
* · 옮김 = 오른쪽 판 아래 [옮기기](`Hopyo_Rows.createJudgeMove` · 호표 줄과 한 몸)
* · 시트 칸 고름 → 그 행 블록을 목록에서 강조(`SheetNav.blockAt`)
* · 목록은 처음 단가산출 탭에 올 때 읽음 · 만들 층 ≠ 파일 층 · 쓰기 권한 없음 = 보기만
* ========================================================================== */
import { createBadge } from "@ui/ui_template_badge";
import { 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 { createJudgeBlock, type JudgeState } from "./M02_MasterTemplete_Hopyo_Block";
import { readJudge, type JudgeBlockBrief } from "./M02_MasterTemplete_Hopyo_JudgeApi";
import type { SheetNav } from "./M02_MasterTemplete_Hopyo_SheetNav";
export interface SheetSkOptions {
scope: HopyoScope;
key: string;
target: HopyoScope;
canWrite: boolean;
nav: SheetNav;
state: JudgeState;
/** 호표 목록 판 — 단가산출 탭이면 숨김 · 그 뒤에 단가산출 판을 붙임 */
side: HTMLElement;
/** 시트 아래 줄 판 자리 열기 · 닫기(`Hopyo_Sheet` 가 쥠) */
openLines: (make: (host: HTMLElement) => { destroy: () => void }) => void;
closeLines: () => void;
/** 판정 · 옮김 뒤(부르는 쪽 끝내기 글) */
onChange?: () => void;
}
const sameLayer = (a: HopyoScope, b: HopyoScope): boolean =>
a.layer === b.layer && (a.owner ?? null) === (b.owner ?? null);
const STRIPE = {
done: "var(--color-success)",
ready: "var(--color-info)",
pick: "var(--color-warning)",
} as const;
export function createSheetSk(opt: SheetSkOptions) {
const list = el("div", { className: "m02-hopyo-sheet__list" });
const root = el("div", {
className: "m02-hopyo-sheet__side m02-hopyo-sheet__sk",
children: [list],
});
root.setAttribute("data-hopyo-sk-side", "");
root.hidden = true;
opt.side.after(root);
const readOnly = !opt.canWrite || !sameLayer(opt.scope, opt.target);
let blocks: JudgeBlockBrief[] | null = null;
let asked = false;
let sheet = "";
let current: number | null = null;
function draw(): void {
if (!blocks) return;
const d = opt.state.get();
list.replaceChildren(
...blocks.map((b) => {
const ok = !!d.블록[String(b.번)]?.확인;
const sub = [
b.단위,
b.판정 ? `${b.판정}${b.판정 === "고를 것" && b.고를수 ? ` ${b.고를수}` : ""}` : "",
b.표지 ?? "",
b.부름.length ? `${L("M02_JudgeCalls")} ${b.부름.length}` : L("M02_JudgeOutside"),
].filter(Boolean).join(" · "); // prettier-ignore
const tail = b.만든
? createBadge(`${L("M02_JudgeMade")} ${b.만든}`, "success")
: ok
? createBadge(L("M02_JudgeConfirmed"), "accent")
: "";
const row = createListRow({
label: `${L("M02_JudgeBlock").replace("{n}", String(b.번))} ${b.이름}`,
sub,
trailing: tail,
stripe: b.만든 || ok ? STRIPE.done : b.판정 === "고를 것" ? STRIPE.pick : STRIPE.ready,
active: b.번 === current,
size: 36,
onClick: () => pick(b.번),
});
row.dataset.judgeSk = String(b.번);
return row;
}),
);
}
/** 목록 — 처음 단가산출 탭에 올 때 한 번 */
function ensure(): void {
if (asked) return;
asked = true;
list.replaceChildren(createText(L("M02_JudgeLoading"), "hint"));
void readJudge(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.행);
const detail = opt.nav.spotOf(no, "단가산출상세")?.시트;
opt.openLines((host) =>
createJudgeBlock({
host,
file: opt.scope,
key: opt.key,
target: opt.target,
state: opt.state,
no,
readOnly,
onClose: opt.closeLines,
...(detail ? { onSheet: (r: number) => opt.nav.jumpTo(detail, r) } : {}),
}),
);
}
const off = opt.state.on(draw);
return {
root,
/** 아직 안 옮긴 블록 수 · 그중 일위대가 밖 · 고를 것 합 — 목록 전 null */
count: (): { 블록: number; 밖: number; 줄: number } | null => {
if (!blocks) return null;
const left = blocks.filter((b) => !b.만든);
const 밖 = left.filter((b) => !b.부름.length).length;
return { 블록: left.length, 밖, 줄: left.reduce((a, b) => a + (b.고를수 ?? 0), 0) };
},
/** 그 블록으로 뜀 + 블록 판 */
show(번: number): void {
ensure();
pick(번);
},
/** 옮긴 뒤 — 목록 다시 읽음(만든 SK) */
reload(): void {
asked = false;
if (!root.hidden) ensure();
},
/** 시트 고름 · 탭 바꿈 — 판 바꾸기 · 강조 */
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: off,
};
}
export type SheetSk = ReturnType<typeof createSheetSk>;