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>
This commit is contained in:
@@ -259,7 +259,7 @@ export function solvedBody(opt: SolvedBodyOptions): {
|
||||
}
|
||||
}
|
||||
|
||||
function candBox(sec: Section, cand: BaseCand): HTMLElement | null {
|
||||
function candBox(cand: BaseCand): HTMLElement | null {
|
||||
const p = picks.find((x) => x.갈래 === "기초" && x.키 === cand.키);
|
||||
if (!p) return null;
|
||||
const got = checked[cand.키];
|
||||
@@ -279,7 +279,7 @@ export function solvedBody(opt: SolvedBodyOptions): {
|
||||
function secBox(sec: Section, i: number): HTMLElement {
|
||||
const box = el("div", { className: "m02-judge__sec", attrs: { "data-judge-sec": String(i) } });
|
||||
const title = `${L("M02_JudgeSection")} ${sec.번 ?? i + 1}. ${sec.제목}`;
|
||||
const chips = sec.후보.map((c) => {
|
||||
const chips: HTMLElement[] = sec.후보.map((c) => {
|
||||
const on = picks.some((x) => x.갈래 === "기초" && x.키 === c.키);
|
||||
const chip = createToggle({ kind: "chip", label: `${c.키} ${c.이름}`, checked: on, disabled: opt.readOnly, onChange: () => put(togglePick(picks, basePick(c, sec))) }); // prettier-ignore
|
||||
chip.root.dataset.judgeBase = c.키;
|
||||
@@ -288,7 +288,7 @@ export function solvedBody(opt: SolvedBodyOptions): {
|
||||
if (!chips.length) chips.push(createText(L("M02_JudgeNoBase"), "warning"));
|
||||
box.append(bar([createText(title, "muted", 14), badge(sec.판정, "judgeSec")]), bar(chips));
|
||||
for (const c of sec.후보) {
|
||||
const cb = candBox(sec, c);
|
||||
const cb = candBox(c);
|
||||
if (cb) box.append(cb);
|
||||
}
|
||||
return box;
|
||||
|
||||
@@ -159,6 +159,9 @@ export interface JudgeBlockBrief {
|
||||
부름: number[];
|
||||
표지: string | null;
|
||||
만든: string | null;
|
||||
/** 블록 풀이 파생 저장본(셈 전 = 없음) */
|
||||
판정?: BlockVerdict | null;
|
||||
고를수?: number | null;
|
||||
}
|
||||
|
||||
export interface JudgeList {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* 오른쪽 = 호표 목록(체크 · 제N호표 [수정] · 찾기 · 제안 n/m — 다 연결이면 녹색 · 뒤 셈 중이면 3 초마다 다시) + [저장] ·
|
||||
* 단가산출표 · 상세 탭이면 단가산출 목록으로 바뀜(780-2 · `Hopyo_SheetSk`).
|
||||
* · 목록 누름 → 시트에서 그 호표 영역(B열 ~ 끝 열) 고름 · 칸 누름 → 그 행 호표를 목록에서 강조 · 머리 행이면 영역 고름
|
||||
* · [수정] = 그 호표 확인 창(`openHopyoCheck` · 줄 누름과 따로 · 785-6 이 줄 판으로 갈음)
|
||||
* · [수정] = 시트 아래 줄 판(785-6 `Hopyo_Rows` · 줄 누름과 따로) · 단가산출 탭 블록 = 같은 자리 블록 판 ·
|
||||
* 오른쪽 판 아래 [옮기기](확인한 줄 · 블록 · 고름 캐시 `createJudgeState` 한 벌)
|
||||
* · [저장] · [모두 고정으로 들여오기] = `Hopyo_SheetMake`
|
||||
* · 격자 = 서버 `/hopyo-grid/{키}` · 옛 파일(격자 없음)은 시트 자리에 까닭 글 · 목록 · 저장은 됨
|
||||
* · `buildHopyoSheet` = 몸(머리 · 띠 · 시트 + 목록) · `openHopyoSheet` = 모달 껍데기([로직 추가] 「실무 호표」 길 — 785-7 이 지움)
|
||||
@@ -29,7 +30,12 @@ import {
|
||||
type HopyoView,
|
||||
type HopyoScope,
|
||||
} from "./M02_MasterTemplete_Hopyo_Api";
|
||||
import { openHopyoCheck, POLL_MS } from "./M02_MasterTemplete_Hopyo_Check";
|
||||
import { POLL_MS } from "./M02_MasterTemplete_Hopyo_Check";
|
||||
import {
|
||||
createJudgeMove,
|
||||
createJudgeRows,
|
||||
createJudgeState,
|
||||
} from "./M02_MasterTemplete_Hopyo_Rows";
|
||||
import * as Nav from "./M02_MasterTemplete_Hopyo_SheetNav";
|
||||
import { createSheetSk, type SheetSk } from "./M02_MasterTemplete_Hopyo_SheetSk";
|
||||
import { note, why, wireMake } from "./M02_MasterTemplete_Hopyo_SheetMake";
|
||||
@@ -154,6 +160,8 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
const checked = new Set<number>();
|
||||
const items = new Map<number, HTMLElement>();
|
||||
const target = opt.target ?? opt.scope;
|
||||
const state = createJudgeState(opt.scope, opt.key); // 판정 고름 캐시(785-6)
|
||||
let panel: { destroy: () => void } | null = null; // 시트 아래 줄 판 몸
|
||||
|
||||
const head = el("div", {
|
||||
className: "ui-toolbar m02-hopyo__bar",
|
||||
@@ -173,6 +181,16 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
attrs: { "data-hopyo-lines": "" },
|
||||
}); // 시트 아래 줄 판 자리
|
||||
lines.hidden = true;
|
||||
const closeLines = (): void => {
|
||||
panel?.destroy();
|
||||
panel = null;
|
||||
lines.hidden = true;
|
||||
};
|
||||
const openLines = (make: (host: HTMLElement) => { destroy: () => void }): void => {
|
||||
panel?.destroy();
|
||||
lines.hidden = false;
|
||||
panel = make(lines);
|
||||
};
|
||||
const col = el("div", { className: "m02-hopyo-sheet__col", children: [left, lines] });
|
||||
const searchField = createInputField({
|
||||
type: "search",
|
||||
@@ -190,6 +208,14 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
fixAll.setAttribute("data-hopyo-all-fixed", "");
|
||||
fixAll.disabled = true; // 엑셀을 다 읽은 뒤 켬
|
||||
const result = el("div", { className: "m02-hopyo-sheet__result" });
|
||||
const move = createJudgeMove({
|
||||
file: opt.scope,
|
||||
key: opt.key,
|
||||
target,
|
||||
state,
|
||||
readOnly: !opt.canWrite,
|
||||
onMoved: () => void refresh(),
|
||||
});
|
||||
const right = el("div", {
|
||||
className: "m02-hopyo-sheet__side",
|
||||
children: [
|
||||
@@ -200,6 +226,7 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
}),
|
||||
list,
|
||||
result,
|
||||
move.root,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -207,6 +234,9 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
opened.delete(id);
|
||||
closeLines();
|
||||
move.destroy();
|
||||
sk?.destroy();
|
||||
nav?.destroy();
|
||||
sheet?.destroy();
|
||||
opt.onClose?.();
|
||||
@@ -261,7 +291,7 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
else if (o.호표 !== undefined) focus(o.호표);
|
||||
});
|
||||
|
||||
/** [수정] — 그 호표 확인 창(줄 누름과 따로) */
|
||||
/** [수정] — 그 호표 자리 + 시트 아래 줄 판(줄 누름과 따로) */
|
||||
const editButton = (h: HopyoBrief): HTMLElement => {
|
||||
const b = createButton({
|
||||
label: "수정",
|
||||
@@ -269,14 +299,20 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
size: "sm",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
openHopyoCheck({
|
||||
scope: opt.scope,
|
||||
key: opt.key,
|
||||
no: h.번,
|
||||
target,
|
||||
canWrite: opt.canWrite,
|
||||
onBuilt: (got) => opt.onImport?.(got, h),
|
||||
});
|
||||
focus(h.번);
|
||||
openLines((host) =>
|
||||
createJudgeRows({
|
||||
host,
|
||||
file: opt.scope,
|
||||
key: opt.key,
|
||||
target,
|
||||
state,
|
||||
no: h.번,
|
||||
readOnly: !opt.canWrite,
|
||||
onClose: closeLines,
|
||||
onSheet: (r, role) => nav?.jumpTo(role === "일위대가표" ? ilwi : detail, r),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
b.setAttribute("data-hopyo-edit", String(h.번));
|
||||
@@ -341,7 +377,7 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
const more = { ...opt, target, grid, last: lastOf, sheet: () => sheet, host: left };
|
||||
nav = ids.일위대가표 ? Nav.createSheetNav(more) : null;
|
||||
if (nav) warn.after(nav.root);
|
||||
sk = nav ? createSheetSk({ ...opt, target, nav, side: right, lines, ...(opt.onSkChange ? { onChange: opt.onSkChange } : {}) }) : null; // prettier-ignore
|
||||
sk = nav ? createSheetSk({ ...opt, target, nav, state, side: right, openLines, closeLines, ...(opt.onSkChange ? { onChange: opt.onSkChange } : {}) }) : null; // prettier-ignore
|
||||
sheet = createSpreadsheet(left, book, {
|
||||
readOnly: true,
|
||||
칠: nav?.fill,
|
||||
@@ -378,6 +414,18 @@ export function buildHopyoSheet(opt: HopyoSheetOptions): HopyoSheetBody {
|
||||
}, POLL_MS);
|
||||
}
|
||||
|
||||
/** 옮긴 뒤 — 목록 · 칠 · 단가산출 목록 다시 읽음 */
|
||||
async function refresh(): Promise<void> {
|
||||
const next = await readHopyo(opt.scope, opt.key).catch(() => null);
|
||||
if (!next || closed) return;
|
||||
briefs = next.호표;
|
||||
paintHead(next);
|
||||
paintList();
|
||||
nav?.marks(briefs);
|
||||
sk?.reload();
|
||||
opt.onSkChange?.();
|
||||
}
|
||||
|
||||
async function load(): Promise<void> {
|
||||
try {
|
||||
const [file, grid] = await Promise.all([
|
||||
|
||||
@@ -1,72 +1,154 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Hopyo_SheetSk.ts
|
||||
* 엑셀 보기 창 오른쪽 판 바꾸기(PLAN 780-2 · 요구 627) — 탭 따라:
|
||||
* 엑셀 보기 창 오른쪽 판 바꾸기(PLAN 780-2 · 785-6 · 요구 627 · 642) — 탭 따라:
|
||||
* · 일위대가표 = 호표 목록(`Hopyo_Sheet` 판 그대로) · 단가산출표 · 단가산출 상세 = 단가산출 목록
|
||||
* (`sangeunStep` 한 벌 · 같은 캐시 · [확인] → [확인한 것 옮기기] · [줄 n] = 시트 아래 줄 판 `lines` — 785-4)
|
||||
* · 목록 블록 줄 누름 → 지금 탭의 그 블록 자리(단가산출표 = 목록 행 · 상세 = 블록 범위 · [◂ 뒤로] 쌓임)
|
||||
* (새 블록 판정 `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";
|
||||
import { sangeunStep, type SkStep } from "./M02_MasterTemplete_Hopyo_WizardSk";
|
||||
|
||||
export interface SheetSkOptions {
|
||||
scope: HopyoScope;
|
||||
key: string;
|
||||
파일: string;
|
||||
target: HopyoScope;
|
||||
canWrite: boolean;
|
||||
nav: SheetNav;
|
||||
state: JudgeState;
|
||||
/** 호표 목록 판 — 단가산출 탭이면 숨김 · 그 뒤에 단가산출 판을 붙임 */
|
||||
side: HTMLElement;
|
||||
/** 시트 아래 줄 판 자리 */
|
||||
lines: 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 root = el("div", { className: "m02-hopyo-sheet__side m02-hopyo-sheet__sk" });
|
||||
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);
|
||||
let step: SkStep | null = null;
|
||||
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 ensure(): SkStep {
|
||||
if (!step) {
|
||||
const readOnly = !opt.canWrite || !sameLayer(opt.scope, opt.target);
|
||||
step = sangeunStep(opt.scope, opt.key, opt.파일, opt.onChange, { lines: opt.lines, onRow, readOnly }); // prettier-ignore
|
||||
root.append(step.root);
|
||||
}
|
||||
return step;
|
||||
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 onRow(no: number): void {
|
||||
/** 목록 — 처음 단가산출 탭에 올 때 한 번 */
|
||||
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: (): ReturnType<SkStep["count"]> => step?.count() ?? null,
|
||||
/** 그 블록으로 뜀 — 단가산출 상세(없으면 목록) 그 자리 · 줄 판 열림(안 이은 줄 창 [단가산출 걸음에서 확인]) */
|
||||
/** 아직 안 옮긴 블록 수 · 그중 일위대가 밖 · 고를 것 합 — 목록 전 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 {
|
||||
const spot = opt.nav.spotOf(번, "단가산출상세") ?? opt.nav.spotOf(번, "단가산출표");
|
||||
if (spot) opt.nav.jumpTo(spot.시트, spot.행);
|
||||
ensure().show(번);
|
||||
ensure();
|
||||
pick(번);
|
||||
},
|
||||
/** 옮긴 뒤 — 목록 다시 읽음(만든 SK) */
|
||||
reload(): void {
|
||||
asked = false;
|
||||
if (!root.hidden) ensure();
|
||||
},
|
||||
/** 시트 고름 · 탭 바꿈 — 판 바꾸기 · 강조 */
|
||||
at(sel: { 시트: string; 칸: string }): void {
|
||||
@@ -77,8 +159,14 @@ export function createSheetSk(opt: SheetSkOptions) {
|
||||
root.hidden = !sk;
|
||||
opt.side.parentElement?.classList.toggle("is-sk", sk);
|
||||
if (!sk) return;
|
||||
ensure().mark(opt.nav.blockAt(sel.시트, Number(/\d+/.exec(sel.칸)?.[0] ?? 0)));
|
||||
ensure();
|
||||
const no = opt.nav.blockAt(sel.시트, Number(/\d+/.exec(sel.칸)?.[0] ?? 0));
|
||||
if (no !== null && no !== current) {
|
||||
current = no;
|
||||
draw();
|
||||
}
|
||||
},
|
||||
destroy: off,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,63 @@ const fakeEl = () => ({
|
||||
this.kids.push(x);
|
||||
},
|
||||
});
|
||||
const list = []; // 그린 목록 줄 벌
|
||||
const reads = []; // 목록 읽음
|
||||
const BRIEFS = [
|
||||
{
|
||||
번: 8,
|
||||
이름: "거푸집",
|
||||
단위: "㎡",
|
||||
부름: [1],
|
||||
표지: null,
|
||||
만든: null,
|
||||
판정: "고를 것",
|
||||
고를수: 2,
|
||||
},
|
||||
{
|
||||
번: 9,
|
||||
이름: "잡석",
|
||||
단위: "㎥",
|
||||
부름: [],
|
||||
표지: "준용",
|
||||
만든: null,
|
||||
판정: "줄 조합",
|
||||
고를수: 0,
|
||||
},
|
||||
{
|
||||
번: 10,
|
||||
이름: "운반",
|
||||
단위: "㎡",
|
||||
부름: [2],
|
||||
표지: null,
|
||||
만든: "SK-000001",
|
||||
판정: "기초로 풂",
|
||||
고를수: 0,
|
||||
},
|
||||
];
|
||||
const STUBS = {
|
||||
"@ui/ui_template_locale": { t: (k) => (k === "M02_JudgeBlock" ? "단산{n}" : k) },
|
||||
"@ui/ui_template_badge": { createBadge: (text, tone) => `${tone}:${text}` },
|
||||
"@ui/ui_template_text": { createText: (text) => text },
|
||||
"@ui/ui_template_list_row": { createListRow: (opts) => ({ opts, dataset: {} }) },
|
||||
"./M02_MasterTemplete_Hopyo_JudgeApi": {
|
||||
readJudge: async (scope, key, to) => {
|
||||
reads.push({ key, to });
|
||||
return { 키: key, 파일: "a.xls", 호표: [], 블록: BRIEFS };
|
||||
},
|
||||
},
|
||||
"./M02_MasterTemplete_Hopyo_Block": { createJudgeBlock: (o) => ({ ...o, destroy() {} }) },
|
||||
"@config/config_frontend": { API_BASE_URL: "/api", ROUTES: {} },
|
||||
"@ui/ui_template_elements": {
|
||||
el: (tag, o = {}) => ({ ...fakeEl(), tag, className: o.className }),
|
||||
el: (tag, o = {}) => ({
|
||||
...fakeEl(),
|
||||
tag,
|
||||
className: o.className,
|
||||
replaceChildren(...kids) {
|
||||
this.kids = kids;
|
||||
if (kids.length && kids[0]?.opts) list.push(kids);
|
||||
},
|
||||
}),
|
||||
},
|
||||
"./M02_MasterTemplete_Hopyo_WizardSk": {
|
||||
sangeunStep: (scope, key, file, onChange, o) => {
|
||||
@@ -49,7 +102,7 @@ global.sessionStorage = { getItem: () => null, setItem: () => {}, removeItem: ()
|
||||
|
||||
(async () => {
|
||||
const input = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
if (input.sheetSk) return process.stdout.write(JSON.stringify(sheetSk(input.sheetSk)));
|
||||
if (input.sheetSk) return process.stdout.write(JSON.stringify(await sheetSk(input.sheetSk)));
|
||||
const { blocks } = input;
|
||||
const calls = [];
|
||||
global.fetch = async (url, init = {}) => {
|
||||
@@ -135,8 +188,8 @@ global.sessionStorage = { getItem: () => null, setItem: () => {}, removeItem: ()
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
/** 780-2 판 바꾸기 — 탭(역할) 차례로 고름 · 판 숨김 · 칸 지음 한 번 · 강조 · 줄 누름 뛰기 */
|
||||
function sheetSk({ same, canWrite, picks }) {
|
||||
/** 780-2 · 785-6 판 바꾸기 — 탭(역할) 차례로 고름 · 판 숨김 · 목록 읽음 한 번 · 강조 · 줄 누름 = 뛰기 + 블록 판 */
|
||||
async function sheetSk({ same, canWrite, picks }) {
|
||||
const M02 = path.join(ROOT, "M02_MasterTemplete");
|
||||
const { createSheetSk } = loadTs(path.join(M02, "M02_MasterTemplete_Hopyo_SheetSk.ts"));
|
||||
const roles = { s1: "일위대가표", s2: "단가산출표", s3: "단가산출상세" };
|
||||
@@ -156,13 +209,51 @@ function sheetSk({ same, canWrite, picks }) {
|
||||
};
|
||||
const scope = { layer: "personal", owner: null };
|
||||
const target = same ? scope : { layer: "company", owner: null };
|
||||
const p = createSheetSk({ scope, key: "HP-000001", 파일: "a.xls", target, canWrite, nav, side });
|
||||
const state = {
|
||||
get: () => ({ 호표: {}, 블록: { 8: { 고른: [], 확인: true } } }),
|
||||
on: () => () => {},
|
||||
};
|
||||
const opened = [];
|
||||
const p = createSheetSk({
|
||||
scope,
|
||||
key: "HP-000001",
|
||||
target,
|
||||
canWrite,
|
||||
nav,
|
||||
side,
|
||||
state,
|
||||
openLines: (make) => opened.push(make("host")),
|
||||
closeLines: () => {},
|
||||
});
|
||||
const seen = [];
|
||||
const rows = () => list.at(-1) ?? [];
|
||||
for (const [sheet, cell] of picks) {
|
||||
p.at({ 시트: sheet, 칸: cell });
|
||||
seen.push({ 호표판숨김: side.hidden, 단가판숨김: p.root.hidden, 지음: made.length });
|
||||
if (sheet === "s2" && made[0]) made[0].onRow(8); // 목록 줄 누름(단가산출표 탭)
|
||||
if (sheet === "s3" && made[0]) made[0].onRow(8); // 상세 탭
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const active = rows().find((r) => r.opts.active)?.opts.label ?? null;
|
||||
seen.push({
|
||||
호표판숨김: side.hidden,
|
||||
단가판숨김: p.root.hidden,
|
||||
읽음: reads.length,
|
||||
강조: active,
|
||||
});
|
||||
if (sheet !== "s1")
|
||||
rows()
|
||||
.find((r) => r.opts.label.startsWith("단산8 "))
|
||||
?.opts.onClick();
|
||||
}
|
||||
return { seen, toggled, made: made.map((m) => ({ args: m.args, marks: m.marks })), jumps };
|
||||
return {
|
||||
seen,
|
||||
toggled,
|
||||
jumps,
|
||||
reads,
|
||||
blocks: opened.map((o) => ({ no: o.no, readOnly: o.readOnly, onSheet: !!o.onSheet })),
|
||||
rows: rows().map((r) => ({
|
||||
label: r.opts.label,
|
||||
sub: r.opts.sub,
|
||||
stripe: r.opts.stripe,
|
||||
tail: r.opts.trailing,
|
||||
})),
|
||||
count: p.count(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,29 +35,40 @@ PICKS = [["s1", "B5"], ["s2", "B12"], ["s1", "C3"], ["s3", "A85"], ["s3", "A2"]]
|
||||
|
||||
def test_판_바꿈_목록_지음_강조_뛰기():
|
||||
got = _node({"sheetSk": {"same": True, "canWrite": True, "picks": PICKS}})
|
||||
assert got["seen"] == [
|
||||
{"호표판숨김": False, "단가판숨김": True, "지음": 0}, # 일위대가표 = 호표 목록 · 칸 안 지음
|
||||
{"호표판숨김": True, "단가판숨김": False, "지음": 1}, # 단가산출표 = 단가산출 목록
|
||||
{"호표판숨김": False, "단가판숨김": True, "지음": 1},
|
||||
{"호표판숨김": True, "단가판숨김": False, "지음": 1}, # 다시 와도 한 번만 지음
|
||||
{"호표판숨김": True, "단가판숨김": False, "지음": 1},
|
||||
# 785-6 — 단가산출 목록 = 새 블록 판정(hopyo-judge 목록) · 처음 단가산출 탭에 올 때 한 번 읽음
|
||||
hide = [(s["호표판숨김"], s["단가판숨김"], s["읽음"]) for s in got["seen"]]
|
||||
assert hide == [
|
||||
(False, True, 0),
|
||||
(True, False, 1),
|
||||
(False, True, 1),
|
||||
(True, False, 1),
|
||||
(True, False, 1),
|
||||
]
|
||||
assert [on for _, on in got["toggled"]] == [False, True, False, True, True]
|
||||
(step,) = got["made"]
|
||||
assert step["args"]["o"] == {
|
||||
"onRow": True,
|
||||
"readOnly": False,
|
||||
} # `lines` 자리는 이 대역에서 없음
|
||||
assert step["args"]["key"] == "HP-000001"
|
||||
assert step["marks"] == [8, 8, 0] # 목록 행 12 → 단산8 · 상세 85 행 → 단산8 · 2 행 → 블록 0
|
||||
# 목록 줄 누름 = 지금 탭의 자리(단가산출표 = 목록 행 · 상세 = 블록 범위)
|
||||
assert got["reads"] == [{"key": "HP-000001", "to": {"layer": "personal", "owner": None}}]
|
||||
# 목록 행 12 → 단산8 강조 · 상세 2 행 → 블록 0(없음) — 목록 줄 누름 뒤 단산8
|
||||
assert [s["강조"] for s in got["seen"]] == [
|
||||
None,
|
||||
"단산8 거푸집",
|
||||
"단산8 거푸집",
|
||||
"단산8 거푸집",
|
||||
None,
|
||||
]
|
||||
# 목록 줄 누름 = 지금 탭의 자리(단가산출표 = 목록 행 · 상세 = 블록 범위) + 시트 아래 블록 판
|
||||
assert got["jumps"] == [["s2", [8, 9]], ["s3", [8, 9]], ["s3", [8, 9]]]
|
||||
assert got["blocks"] == [{"no": 8, "readOnly": False, "onSheet": True}] * 3
|
||||
rows = {r["label"]: r for r in got["rows"]}
|
||||
assert rows["단산8 거푸집"]["sub"] == "㎡ · 고를 것 2 · M02_JudgeCalls 1"
|
||||
assert rows["단산8 거푸집"]["tail"] == "accent:M02_JudgeConfirmed" # 캐시 확인
|
||||
assert rows["단산9 잡석"]["sub"] == "㎥ · 줄 조합 · 준용 · M02_JudgeOutside"
|
||||
assert rows["단산10 운반"]["tail"] == "success:M02_JudgeMade SK-000001"
|
||||
assert got["count"] == {"블록": 2, "밖": 1, "줄": 2} # 만든 SK 뺌
|
||||
|
||||
|
||||
def test_다른_층_권한_없음_보기만():
|
||||
for same, can in ((False, True), (True, False)):
|
||||
got = _node({"sheetSk": {"same": same, "canWrite": can, "picks": [["s2", "B5"]]}})
|
||||
assert got["made"][0]["args"]["o"]["readOnly"] is True
|
||||
assert got["blocks"][0]["readOnly"] is True
|
||||
|
||||
|
||||
def test_거르기_줄0_막음_끝내기_글():
|
||||
@@ -81,7 +92,7 @@ def test_캐시_공유_알림_칠_다시():
|
||||
sheet = _src("Sheet")
|
||||
assert "createSheetSk(" in sheet and "sk?.at(s);" in sheet
|
||||
side = _src("SheetSk")
|
||||
assert "sangeunStep(opt.scope, opt.key" in side # 마법사와 같은 파일 층 · 키
|
||||
assert "readJudge(opt.scope, opt.key, opt.target)" in side # 785-6 새 블록 판정 목록
|
||||
nav = _src("SheetNav")
|
||||
assert "blockAt(sheet: string, row: number)" in nav and "spotOf(no: number, role: Role)" in nav
|
||||
wizard = _src("Import")
|
||||
|
||||
@@ -267,3 +267,14 @@ def test_블록칠_새판정(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "M02_MasterTemplete.M02_Hopyo_Make", Make)
|
||||
monkeypatch.setattr(sys.modules["M02_MasterTemplete"], "M02_Hopyo_Make", Make, raising=False)
|
||||
assert mk._judged(Path("."), "x") == {10: {"판정": "기초로 풂", "고를수": 0}}
|
||||
|
||||
|
||||
def test_엑셀보기_이음_가운데페이지없음():
|
||||
"""줄 판 = 시트 아래 `[data-hopyo-lines]` · [옮기기] = 오른쪽 판 아래 · 단가산출 목록 = 새 블록 판정."""
|
||||
assert not (M02 / "M02_MasterTemplete_Hopyo_Wizard.ts").exists()
|
||||
sheet = (M02 / "M02_MasterTemplete_Hopyo_Sheet.ts").read_text(encoding="utf-8")
|
||||
assert "createJudgeRows({" in sheet and "createJudgeMove({" in sheet
|
||||
right = sheet.split('className: "m02-hopyo-sheet__side"')[1].split("});")[0]
|
||||
assert "move.root" in right
|
||||
sk = (M02 / "M02_MasterTemplete_Hopyo_SheetSk.ts").read_text(encoding="utf-8")
|
||||
assert "createJudgeBlock({" in sk and "sangeunStep" not in sk
|
||||
|
||||
@@ -57,8 +57,8 @@ def test_흠_셋_고침():
|
||||
assert "skLineTable({" in sk.split("function drawLines()")[1]
|
||||
assert "is-sub" not in sk and "o.narrow" not in sk
|
||||
sheet = _src("Sheet")
|
||||
assert 'attrs: { "data-hopyo-lines": "" }' in sheet and "lines," in sheet
|
||||
assert "lines: HTMLElement;" in _src("SheetSk")
|
||||
assert 'attrs: { "data-hopyo-lines": "" }' in sheet and "openLines," in sheet
|
||||
assert "openLines: (make: (host: HTMLElement)" in _src("SheetSk") # 785-6 줄 판 자리 한 손잡이
|
||||
|
||||
|
||||
def test_끝내기_머리_막대_끝냄_뒤_빈_본_칸():
|
||||
|
||||
@@ -328,7 +328,9 @@ def test_옛_즉시_확정과_바로_만들기_없음():
|
||||
) # 하위 먼저 · 함께(777-1)
|
||||
sheet = texts["Hopyo_Sheet"] # 목록 · [수정] = 엑셀 보기 몸
|
||||
edit = sheet.split("const editButton =")[1].split("function paintList(")[0]
|
||||
assert '"수정"' in edit and "e.stopPropagation();" in edit and "openHopyoCheck({" in edit
|
||||
assert (
|
||||
'"수정"' in edit and "e.stopPropagation();" in edit and "createJudgeRows({" in edit
|
||||
) # 785-6 줄 판
|
||||
row = sheet.split("function paintList(")[1]
|
||||
assert row.index("m02-hopyo-sheet__no") < row.index("editButton(h)") < row.index("__name")
|
||||
assert 'linked.classList.toggle("is-linked", allLinked(h));' in row
|
||||
|
||||
@@ -54,4 +54,5 @@ export const ui_locales_m2_judge = {
|
||||
"금액은 참고 — 판정은 품",
|
||||
"Amounts are reference — verdict is by quantity",
|
||||
],
|
||||
M02_JudgeOutside: ["일위대가 밖", "Not called by items"],
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user