Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Dock.ts
|
||||
* B09 좌측 하단 고정 줄(PLAN 303-1 · 304-1) — 위에서부터
|
||||
* ① [박스보기|호표보기] 묶음 탭 셋 보기(304-1 · 한 값)
|
||||
* ② [하이퍼링크|팝업] 행 누름(303 · 기본 팝업)
|
||||
* ③ `DRAFT.actions` 단추(초안 · 비면 줄 없음)
|
||||
* ④ [?][저장][내역 초기화](도움말 줄 `data-help-row`)
|
||||
*
|
||||
* 스위치 값 = pref global(계정 공용 · [저장] 필요 없음) · 바닥 고정 = 공용 `.ui-sidebar-actions` 한 벌
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
import { readState, writeState } from "../A00_Common/b_page_state";
|
||||
import { DRAFT } from "./B09_Estimation_UI_Tabs";
|
||||
import {
|
||||
chooseGroupView,
|
||||
groupView,
|
||||
viewLabel,
|
||||
type GroupView,
|
||||
} from "./B09_Estimation_UI_Tabs_Groups";
|
||||
|
||||
export type LinkMode = "popup" | "link";
|
||||
|
||||
const LINK_LABEL: Record<LinkMode, string> = { link: "하이퍼링크", popup: "팝업" };
|
||||
|
||||
/** 행 누름 길 — 모르는 값 · 못 읽음 = 팝업 */
|
||||
export function linkMode(): LinkMode {
|
||||
try {
|
||||
return readState<unknown>("b09-link-mode") === "link" ? "link" : "popup";
|
||||
} catch {
|
||||
return "popup";
|
||||
}
|
||||
}
|
||||
|
||||
function setLinkMode(mode: LinkMode): void {
|
||||
try {
|
||||
writeState("b09-link-mode", mode);
|
||||
} catch {
|
||||
// 기억 못 하면 기본(팝업)으로 돌아감
|
||||
}
|
||||
}
|
||||
|
||||
/** 단추 둘 스위치 — 켜진 쪽 filled · aria-pressed */
|
||||
function switchRow<T extends string>(
|
||||
name: string,
|
||||
values: T[],
|
||||
label: (value: T) => string,
|
||||
current: () => T,
|
||||
pick: (value: T) => void,
|
||||
): HTMLElement {
|
||||
const row = document.createElement("div");
|
||||
row.className = "ui-btn-group b09-dock__row";
|
||||
row.dataset.b09Switch = name;
|
||||
const buttons = values.map((value) => {
|
||||
const button = createButton({
|
||||
label: label(value),
|
||||
variant: "ghost",
|
||||
onClick: () => {
|
||||
if (value === current()) return;
|
||||
pick(value);
|
||||
sync();
|
||||
},
|
||||
});
|
||||
button.dataset.value = value;
|
||||
return button;
|
||||
});
|
||||
const sync = (): void =>
|
||||
buttons.forEach((button, k) => {
|
||||
const on = values[k] === current();
|
||||
button.classList.toggle("ui-btn--filled", on);
|
||||
button.classList.toggle("ui-btn--ghost", !on);
|
||||
button.setAttribute("aria-pressed", String(on));
|
||||
});
|
||||
sync();
|
||||
row.append(...buttons);
|
||||
return row;
|
||||
}
|
||||
|
||||
/** 하단 고정 줄 — onView = 보기 바뀜(묶음 탭이면 다시 그림) */
|
||||
export function createDock(buttons: HTMLElement[], onView: () => void): HTMLElement {
|
||||
const views: GroupView[] = ["cards", "table"];
|
||||
const view = switchRow("view", views, viewLabel, groupView, (value) => {
|
||||
chooseGroupView(value);
|
||||
onView();
|
||||
});
|
||||
const link = switchRow<LinkMode>(
|
||||
"link",
|
||||
["link", "popup"],
|
||||
(mode) => LINK_LABEL[mode],
|
||||
linkMode,
|
||||
setLinkMode,
|
||||
);
|
||||
const extra = DRAFT.actions.map((make) => make());
|
||||
const draft = document.createElement("div");
|
||||
draft.className = "b09-dock__row";
|
||||
draft.dataset.b09DraftActions = "";
|
||||
draft.append(...extra);
|
||||
const main = document.createElement("div");
|
||||
main.className = "b09-dock__row";
|
||||
main.dataset.helpRow = "";
|
||||
main.append(...buttons);
|
||||
const dock = document.createElement("div");
|
||||
dock.className = "ui-sidebar-actions b09-dock";
|
||||
dock.append(view, link, ...(extra.length ? [draft] : []), main);
|
||||
return dock;
|
||||
}
|
||||
@@ -248,3 +248,57 @@
|
||||
.b09-estimation [data-b09-table="bill"] tbody tr.is-drag-src td {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
/* 303-1 · 304-1 좌측 하단 고정 줄 — 줄 여럿 세로(보기 · 행 누름 스위치 · 초안 단추 · [?][저장][내역 초기화]) */
|
||||
.b09-dock.ui-sidebar-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.b09-dock.ui-sidebar-actions > * {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.b09-dock .b09-dock__row {
|
||||
display: flex;
|
||||
gap: var(--btn-group-gap);
|
||||
}
|
||||
|
||||
.b09-dock .b09-dock__row > .ui-btn:not(.ui-help-btn) {
|
||||
flex: 1 1 0;
|
||||
min-width: auto;
|
||||
padding-inline: var(--spacing-4);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 303-2 행 고름 팝업 — 누른 줄 곁 표 조각 창(겹쳐 쌓임 · 창 안 b09 표 꼴 그대로 · 빨강 테두리) */
|
||||
.b09-estimation.b09-popup {
|
||||
position: fixed;
|
||||
z-index: var(--z-modal);
|
||||
gap: var(--spacing-4);
|
||||
width: max-content;
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
padding: var(--spacing-8);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-surface-raised);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.b09-popup__head {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.b09-popup__title {
|
||||
margin-right: auto;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.b09-popup .ui-data-table {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Popup.ts
|
||||
* B09 행 고름 팝업(PLAN 303-2) — 링크 있는 줄 · 박스 카드 누름 = 대상 탭 표 조각 창(옮기지 않음).
|
||||
*
|
||||
* 창 = 열 제목(머리 여러 줄 그대로) + 강조 범위 줄 전체 · 표 짜기 = `…_UI_Builders`(화면과 같은 옵션)
|
||||
* 강조 범위 = 설계내역서 그 줄 + 자손 · 묶음 탭 호표 구간 · 그 밖 한 줄(하이퍼링크 강조와 같음)
|
||||
* 자리 = 누른 줄 아래(모자라면 위 · 화면 안) · 너비 = 표 너비(최대 화면 90%) · 넘치면 창 안 스크롤
|
||||
* 창 안 링크 줄 누름 = 이어진 창(겹쳐 위에 · 그 줄 곁) · Esc · × = 맨 위 하나 · 탭 바꿈 · 주소 바뀜 = 모두
|
||||
* 머리 [탭으로] = 하이퍼링크처럼 옮김(모두 닫음) · 창 안 = 보기 전용(고칠 칸 · 메뉴 없음)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createDataTable, type DataTableOptions } from "@ui/ui_template_data_table";
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
import { placeHoverInfo } from "@ui/ui_template_hover_info";
|
||||
import { tableOptionsOf } from "./B09_Estimation_UI_Builders";
|
||||
import { rowIndex, tabName, type Link, type TabId, type TabTable } from "./B09_Estimation_UI_Tabs";
|
||||
import type { BillTable } from "./B09_Estimation_UI_Tabs_Bill_Edit";
|
||||
import { subtreeEnd } from "./B09_Estimation_UI_Tabs_Bill_Level";
|
||||
import { isGroupTab, pickClass, pickRange } from "./B09_Estimation_UI_Tabs_Groups";
|
||||
|
||||
const EDGE = 8;
|
||||
|
||||
/** 강조 범위 [첫 줄, 끝 줄] — 대상 줄 없음 = null */
|
||||
export function popupRange(tab: TabId, spec: TabTable, key: string): [number, number] | null {
|
||||
if (isGroupTab(tab)) return pickRange(spec.rows, key);
|
||||
const at = rowIndex(spec.rows, key);
|
||||
if (at < 0) return null;
|
||||
return (spec as BillTable).menu ? [at, subtreeEnd(spec.rows, at)] : [at, at];
|
||||
}
|
||||
|
||||
/** 창 표 옵션 — 범위 줄만 · 줄 번호 곁값은 원래 줄 번호로 · 빨강 테두리 · 합계 · 고칠 칸 없음 */
|
||||
export function popupOptions(
|
||||
spec: TabTable,
|
||||
tab: TabId,
|
||||
range: [number, number],
|
||||
): DataTableOptions {
|
||||
const [from, to] = range;
|
||||
const at = (i: number): number => i + from;
|
||||
const level = spec.level;
|
||||
return {
|
||||
...tableOptionsOf(spec, tab),
|
||||
rows: spec.rows.slice(from, to + 1),
|
||||
total: null,
|
||||
editable: undefined,
|
||||
onEdit: undefined,
|
||||
rowClass: (i) =>
|
||||
[spec.rowClass?.(at(i)), pickClass(range, at(i))].filter(Boolean).join(" ") || null,
|
||||
cellClass: spec.cellClass && ((i, key) => spec.cellClass?.(at(i), key)),
|
||||
level: level && {
|
||||
...level,
|
||||
of: (i) => level.of(at(i)),
|
||||
group: level.group && ((i) => level.group?.(at(i)) ?? false),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 창 자리 — 누른 줄 아래(모자라면 위) · 화면 밖으로 안 나감 */
|
||||
export function popupPlace(
|
||||
anchor: { left: number; top: number; right: number; bottom: number },
|
||||
size: { width: number; height: number },
|
||||
view: { width: number; height: number },
|
||||
): { x: number; y: number } {
|
||||
const { x, y } = placeHoverInfo(anchor, size, view);
|
||||
return { x, y: Math.max(EDGE, Math.min(y, view.height - EDGE - size.height)) };
|
||||
}
|
||||
|
||||
/** 열린 창(아래 → 위) */
|
||||
const stack: HTMLElement[] = [];
|
||||
let listening = false;
|
||||
|
||||
/** from 번째 창부터 위로 모두 닫음(0 = 모두) */
|
||||
export function closePopups(from = 0): void {
|
||||
for (const box of stack.splice(from)) box.remove();
|
||||
}
|
||||
|
||||
export const popupCount = (): number => stack.length;
|
||||
|
||||
function listen(): void {
|
||||
if (listening) return;
|
||||
listening = true;
|
||||
// Esc = 맨 위 하나(뒤 화면의 Esc 풂보다 먼저 · 창 있을 때만 가로챔)
|
||||
window.addEventListener(
|
||||
"keydown",
|
||||
(event) => {
|
||||
if (event.key !== "Escape" || !stack.length) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
closePopups(stack.length - 1);
|
||||
},
|
||||
true,
|
||||
);
|
||||
window.addEventListener("hashchange", () => closePopups());
|
||||
}
|
||||
|
||||
export interface PopupOpen {
|
||||
link: Link;
|
||||
/** 누른 줄 · 카드 줄(자리 기준) — 없으면 화면 가운데 위 */
|
||||
anchor: Element | null;
|
||||
/** 대상 탭 표(화면과 같은 표 짜기) — 없으면 null */
|
||||
specOf: (tab: TabId) => TabTable | null;
|
||||
/** 하이퍼링크처럼 옮김(대상 줄 없음 · [탭으로]) */
|
||||
go: (link: Link) => void;
|
||||
/** 참 = 창 안 누름(이어진 창) · 거짓 = 화면 누름(있던 창 모두 닫고 새로) */
|
||||
chain?: boolean;
|
||||
}
|
||||
|
||||
export function openRowPopup(o: PopupOpen): void {
|
||||
if (!o.chain) closePopups();
|
||||
const { tab, key } = o.link;
|
||||
const spec = o.specOf(tab);
|
||||
const range = spec && popupRange(tab, spec, key);
|
||||
if (!spec || !range) return o.go(o.link); // 보일 줄 없음 = 옮김
|
||||
listen();
|
||||
const level = stack.length;
|
||||
const box = document.createElement("div");
|
||||
box.className = "b09-estimation b09-popup";
|
||||
box.setAttribute("role", "dialog");
|
||||
box.setAttribute("aria-label", tabName(tab));
|
||||
box.dataset.b09Popup = tab;
|
||||
const title = document.createElement("span");
|
||||
title.className = "b09-popup__title";
|
||||
title.textContent = tabName(tab);
|
||||
const goButton = createButton({
|
||||
label: "탭으로",
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
closePopups();
|
||||
o.go(o.link);
|
||||
},
|
||||
});
|
||||
goButton.dataset.b09PopupGo = "";
|
||||
const closeButton = createButton({
|
||||
label: "×",
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => closePopups(level),
|
||||
});
|
||||
closeButton.dataset.b09PopupClose = "";
|
||||
closeButton.setAttribute("aria-label", "닫기");
|
||||
const head = document.createElement("div");
|
||||
head.className = "b09-popup__head";
|
||||
head.append(title, goButton, closeButton);
|
||||
const from = range[0];
|
||||
const table = createDataTable({
|
||||
...popupOptions(spec, tab, range),
|
||||
// 창 안 링크 줄 = 이어진 창(고칠 칸 · 「단가연결」 칸은 화면처럼 안 엶)
|
||||
onCellClick: (i, cellKey) => {
|
||||
if (spec.editable?.(i + from, cellKey)) return;
|
||||
const next = spec.link?.(i + from, cellKey);
|
||||
if (next)
|
||||
openRowPopup({
|
||||
...o,
|
||||
link: next,
|
||||
anchor: table.root.querySelector(`tr[data-i="${i}"]`),
|
||||
chain: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
table.root.dataset.b09Table = tab;
|
||||
box.append(head, table.root);
|
||||
document.body.append(box);
|
||||
stack.push(box);
|
||||
const view = { width: window.innerWidth, height: window.innerHeight };
|
||||
const size = box.getBoundingClientRect();
|
||||
const anchor = o.anchor?.getBoundingClientRect() ?? {
|
||||
left: (view.width - size.width) / 2,
|
||||
right: (view.width + size.width) / 2,
|
||||
top: view.height / 4,
|
||||
bottom: view.height / 4,
|
||||
};
|
||||
const { x, y } = popupPlace(anchor, size, view);
|
||||
box.style.left = `${x}px`;
|
||||
box.style.top = `${y}px`;
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import { computeTotal } from "./B09_Estimation_Calc_Total";
|
||||
import { createTabBars } from "./B09_Estimation_UI_TabBar";
|
||||
import { createCheckPanel } from "./B09_Estimation_UI_Check";
|
||||
import { tableOptionsOf, tabTableOf } from "./B09_Estimation_UI_Builders";
|
||||
import { createDock, linkMode } from "./B09_Estimation_UI_Dock";
|
||||
import { closePopups, openRowPopup } from "./B09_Estimation_UI_Popup";
|
||||
import {
|
||||
DRAFT,
|
||||
rowIndex,
|
||||
@@ -154,6 +156,7 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
/** 탭 하나 그림 · focus = 이동해 온 줄 키 */
|
||||
const show = (tab: TabId, focus?: string): void => {
|
||||
current = tab;
|
||||
closePopups(); // 303-2 탭 바꿈 · 다시 그림 = 창 모두 닫음
|
||||
bars.draw(tab);
|
||||
bars.setTools([]); // 보기 단추 · 열 수는 묶음 탭만 다시 채움(202-1)
|
||||
if (!projectId) return void panel.replaceChildren(note("프로젝트를 먼저 고르세요."));
|
||||
@@ -162,8 +165,19 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
const spec = tabTableOf(tab, ctx);
|
||||
if (!spec) return void panel.replaceChildren(note(LATER[tab] ?? "다음 단계에서 채움"));
|
||||
if (typeof spec === "string") return void panel.replaceChildren(note(spec));
|
||||
const goto = (link: Link | null): void => {
|
||||
if (link) show(link.tab, link.key);
|
||||
// 303 행 누름 = 팝업(기본 · 누른 줄 곁 표 조각) · 하이퍼링크(그 탭 그 줄로)
|
||||
const goto = (link: Link | null, anchor?: Element | null): void => {
|
||||
if (!link) return;
|
||||
if (linkMode() === "link") return show(link.tab, link.key);
|
||||
openRowPopup({
|
||||
link,
|
||||
anchor: anchor ?? null,
|
||||
specOf: (t) => {
|
||||
const made = tabTableOf(t, ctx);
|
||||
return made && typeof made === "object" ? made : null;
|
||||
},
|
||||
go: (to) => show(to.tab, to.key),
|
||||
});
|
||||
};
|
||||
// extra = 줄 class 곁값(181-2 표 보기 호표 구간)
|
||||
const makeTable = (extra?: (index: number) => string | null): DataTable => {
|
||||
@@ -174,7 +188,8 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
: spec.rowClass,
|
||||
onCellClick: (i, key) => {
|
||||
if (spec.onCell) spec.onCell(i, key);
|
||||
if (spec.link && !spec.editable?.(i, key)) goto(spec.link(i, key));
|
||||
if (spec.link && !spec.editable?.(i, key))
|
||||
goto(spec.link(i, key), made.root.querySelector(`tr[data-i="${i}"]`));
|
||||
},
|
||||
});
|
||||
made.root.dataset.b09Table = tab;
|
||||
@@ -216,9 +231,8 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
return made;
|
||||
};
|
||||
const hint = spec.hint ? lineHint(note(spec.hint)) : null; // 236-1 안내 글 = 한 줄
|
||||
// 171-3 묶음 탭(일위대가표 · 중기사용료 · 단가산출근거) = 묶음 카드 기본 · 머리 단추로 표
|
||||
// 171-3 묶음 탭(일위대가표 · 중기사용료 · 단가산출근거) = 묶음 카드 기본 · 좌측 하단 스위치로 표(304-1)
|
||||
if (isGroupTab(tab)) {
|
||||
const redraw = (): void => show(tab);
|
||||
return void panel.replaceChildren(
|
||||
...renderGroupPanel({
|
||||
tab,
|
||||
@@ -227,7 +241,6 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
focus,
|
||||
goto,
|
||||
makeTable,
|
||||
redraw,
|
||||
setTools: bars.setTools,
|
||||
}),
|
||||
);
|
||||
@@ -321,9 +334,8 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
// 카드 = 좌측 패널 공용 카드(269 · 「구조물 배치」 양식)
|
||||
const section = createPanelCard("데이터 점검");
|
||||
section.append(refresh, check.list);
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "ui-sidebar-actions";
|
||||
actions.append(save, reset);
|
||||
// 303-1 · 304-1 하단 고정 줄 — [박스보기|호표보기] · [하이퍼링크|팝업] · 초안 단추 · [?][저장][내역 초기화]
|
||||
const actions = createDock([save, reset], () => isGroupTab(current) && show(current));
|
||||
const left = document.createElement("div");
|
||||
left.className = "ui-sidebar-panel b09-options";
|
||||
left.append(section, ...DRAFT.panels.map((make) => make(() => user, setUser)), actions);
|
||||
|
||||
@@ -123,12 +123,14 @@ export type TabBuild = (ctx: TabContext) => TabTable | string;
|
||||
|
||||
/** 초안 탭(252 · `draft/*_UI.ts` 가 TABS · TAB_GROUPS 와 함께 넣음 · 폴더째 지우면 빈 채로) —
|
||||
* builders = 탭 짜기 · extend = 브라우저 셈 곁(셈 결과에 더함) ·
|
||||
* panels = 왼쪽 패널 카드(264 · 「데이터 점검」 아래 · 지금 사용자 값 · 고침 → 캐시 · 다시 셈) */
|
||||
* panels = 왼쪽 패널 카드(264 · 「데이터 점검」 아래 · 지금 사용자 값 · 고침 → 캐시 · 다시 셈) ·
|
||||
* actions = 좌측 하단 고정 줄 단추(303-1 · [저장] 줄 위 한 줄 · 비면 줄 없음) */
|
||||
export const DRAFT: {
|
||||
builders: Record<string, TabBuild>;
|
||||
extend: Array<(payload: Row, user: Row, sheets: Row) => void>;
|
||||
panels: Array<(user: () => Row, setUser: (next: Row) => void) => HTMLElement>;
|
||||
} = { builders: {}, extend: [], panels: [] };
|
||||
actions: Array<() => HTMLElement>;
|
||||
} = { builders: {}, extend: [], panels: [], actions: [] };
|
||||
|
||||
/** 201-1 호표 묶음 레벨(보기 전용) — 머리 · 합계(제목 줄) = 1 · 그 밖 = 2 · key 열을 들여씀 */
|
||||
export const blockLevel = (key: string, top: (index: number) => boolean): TabTable["level"] => ({
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Tabs_Groups.ts
|
||||
* B09 묶음 탭 셋(PLAN 171-3) — 일위대가표 · 중기사용료 · 단가산출근거 = 공용 묶음 카드(171-2) 기본 ·
|
||||
* [표로 보기] = 공용 표 그대로(맞은편 단추 = [박스보기]).
|
||||
* 호표보기 = 공용 표 그대로 · 고름 = 좌측 하단 [박스보기|호표보기] 한 벌(304-1 · 셋 탭 함께).
|
||||
*
|
||||
* 카드 = 탭 표 줄(`TabTable.rows`)을 호표(줄 키 앞머리)마다 묶음 — 값 · 풍선 · 누름 이동은 표와 같음
|
||||
* 머리 줄 = 묶음 첫 줄 · 합계 줄 = 키 끝 `|합계` · `|총계` · 그 밖 = 몸 줄(표 줄 번호 그대로)
|
||||
* 목록표에서 온 호표(181-2) = 박스 붉은 외곽선 · 표 보기 = 그 호표 줄 구간 전체 붉은 외곽선
|
||||
* 248-1 빨강 하나(공용 고름 강조 안 붙임 · 스크롤만) · 줄 · 카드 누름 · 방향키 · Esc = 빨강 풂 ·
|
||||
* 누른 줄만 고름(is-focus) · Esc = 고름도 풂
|
||||
* 보기 · 가로 열 수 기억 = `b_page_state`(`b09-group-view` · `b09-group-cols` · 탭마다) · 못 읽으면 카드 · 2
|
||||
* 보기 단추 · 가로 열 수(202-1) = 페이지 보조 탭 줄 오른쪽(`setTools`) · 안내 글 = 표 · 카드 판 맨 아래
|
||||
* 보기 기억 = `b_page_state`(`b09-group-view` · 한 값 · 옛 탭별 값은 첫 읽기에 하나로) · 가로 열 수 =
|
||||
* `b09-group-cols`(탭마다) · 못 읽으면 카드 · 2
|
||||
* 가로 열 수(202-1) = 페이지 보조 탭 줄 오른쪽(`setTools`) · 안내 글 = 표 · 카드 판 맨 아래
|
||||
* 안내 글 길면 한 줄 줄임표 + 공용 호버로 전문(200-1)
|
||||
* 278-2 단가산출근거 박스 = 머리 줄(`|머리n`)을 운반 단가 창 ③ 칸 꼴로(`headSteps` 한 벌 · 몸 위 칸) ·
|
||||
* 출처 = 그 칸 맨 위 · 호표 줄 · 총계 = 그대로 · 묶음 자료(`tabGroups`) · 표는 안 바뀜
|
||||
* ========================================================================== */
|
||||
|
||||
import type { DataRow, DataTable } from "@ui/ui_template_data_table";
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
import { attachHoverInfo } from "@ui/ui_template_hover_info";
|
||||
import {
|
||||
clampColumns,
|
||||
@@ -38,10 +38,8 @@ export const GROUP_TABS: TabId[] = ["dg", "machine_blocks", "sg"];
|
||||
|
||||
export const isGroupTab = (tab: TabId): boolean => GROUP_TABS.includes(tab);
|
||||
|
||||
/** 단추 글 = 바꿀 보기 이름 */
|
||||
export const viewLabel = (view: GroupView): string => (view === "cards" ? "표로 보기" : "박스보기");
|
||||
|
||||
export const flipView = (view: GroupView): GroupView => (view === "cards" ? "table" : "cards");
|
||||
/** 단추 글 = 그 보기 이름(304-1 좌측 스위치) */
|
||||
export const viewLabel = (view: GroupView): string => (view === "cards" ? "박스보기" : "호표보기");
|
||||
|
||||
type Saved = Record<string, unknown> | null;
|
||||
|
||||
@@ -53,19 +51,39 @@ const saved = (name: "b09-group-view" | "b09-group-cols"): Saved => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 고른 보기 — 모르는 값 · 못 읽음 = 카드 */
|
||||
export const readGroupView = (tab: TabId, kept: Saved = saved("b09-group-view")): GroupView =>
|
||||
kept?.[tab] === "table" ? "table" : "cards";
|
||||
/** 고른 보기(셋 탭 한 값) — 옛 탭별 값 = dg · 다른 탭 차례 · 모르는 값 · 못 읽음 = 카드 */
|
||||
export function readGroupView(kept: unknown = saved("b09-group-view")): GroupView {
|
||||
const old = kept && typeof kept === "object" ? (kept as Record<string, unknown>) : null;
|
||||
const value = old ? (old.dg ?? old.machine_blocks ?? old.sg) : kept;
|
||||
return value === "table" ? "table" : "cards";
|
||||
}
|
||||
|
||||
/** 이 화면 안 보기(기억이 안 돼도 고른 보기 유지) */
|
||||
let chosen: GroupView | null = null;
|
||||
|
||||
/** 지금 보기 — 첫 읽기에 옛 탭별 값을 한 값으로 옮겨 적음 */
|
||||
export function groupView(): GroupView {
|
||||
if (chosen) return chosen;
|
||||
const kept = saved("b09-group-view");
|
||||
chosen = readGroupView(kept);
|
||||
if (kept && typeof kept === "object") chooseGroupView(chosen);
|
||||
return chosen;
|
||||
}
|
||||
|
||||
export function chooseGroupView(view: GroupView): void {
|
||||
chosen = view;
|
||||
try {
|
||||
writeState("b09-group-view", view);
|
||||
} catch {
|
||||
// 기억 못 해도 이 화면 안에서는 고른 값 그대로
|
||||
}
|
||||
}
|
||||
|
||||
/** 고른 가로 열 수 — 1~4 · 모르는 값 = 2 */
|
||||
export const readGroupCols = (tab: TabId, kept: Saved = saved("b09-group-cols")): number =>
|
||||
clampColumns(kept?.[tab]);
|
||||
|
||||
export function rememberGroup(
|
||||
name: "b09-group-view" | "b09-group-cols",
|
||||
tab: TabId,
|
||||
value: unknown,
|
||||
): void {
|
||||
export function rememberGroup(name: "b09-group-cols", tab: TabId, value: unknown): void {
|
||||
try {
|
||||
writeState(name, { ...(saved(name) ?? {}), [tab]: value });
|
||||
} catch {
|
||||
@@ -244,33 +262,18 @@ export function unpickOnRelease(root: HTMLElement): void {
|
||||
listenKeys();
|
||||
}
|
||||
|
||||
/** 이 화면 안 보기(기억이 안 돼도 바꾼 보기 유지) */
|
||||
const chosen = new Map<TabId, GroupView>();
|
||||
|
||||
/** 묶음 탭 — 카드 = 카드 판 + 안내 글 · 표 = 표 + 안내 글 · 보기 단추 · 열 수 = `setTools`(보조 탭 줄 오른쪽) */
|
||||
/** 묶음 탭 — 카드 = 카드 판 + 안내 글 · 표 = 표 + 안내 글 · 열 수 = `setTools`(보조 탭 줄 오른쪽) */
|
||||
export function renderGroupPanel(o: {
|
||||
tab: TabId;
|
||||
spec: TabTable;
|
||||
hint: HTMLElement | null;
|
||||
focus?: string;
|
||||
goto: (link: Link | null) => void;
|
||||
goto: (link: Link | null, anchor?: Element | null) => void;
|
||||
makeTable: (rowClass?: (index: number) => string | null) => DataTable;
|
||||
redraw: () => void;
|
||||
setTools: (tools: HTMLElement[]) => void;
|
||||
}): HTMLElement[] {
|
||||
const view = chosen.get(o.tab) ?? readGroupView(o.tab);
|
||||
const view = groupView();
|
||||
const lead = lineHint(o.hint);
|
||||
const toggle = createButton({
|
||||
label: viewLabel(view),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
chosen.set(o.tab, flipView(view));
|
||||
rememberGroup("b09-group-view", o.tab, flipView(view));
|
||||
o.redraw();
|
||||
},
|
||||
});
|
||||
toggle.dataset.b09GroupView = view;
|
||||
const at = o.focus === undefined ? -1 : rowIndex(o.spec.rows, o.focus);
|
||||
if (view === "cards") {
|
||||
const first = o.spec.columns[0]?.key ?? "";
|
||||
@@ -281,19 +284,18 @@ export function renderGroupPanel(o: {
|
||||
: tabGroups(o.tab, o.spec),
|
||||
columns: readGroupCols(o.tab),
|
||||
onColumns: (n) => rememberGroup("b09-group-cols", o.tab, n),
|
||||
onPick: (i) => o.goto(o.spec.link?.(i, first) ?? null),
|
||||
onPick: (i) =>
|
||||
o.goto(o.spec.link?.(i, first) ?? null, cards.querySelector(`[data-at="${i}"]`)),
|
||||
focus: at,
|
||||
money: numberFormat(o.spec, "sum"),
|
||||
qty: numberFormat(o.spec, "qty"),
|
||||
actions: [toggle],
|
||||
placeTools: o.setTools, // 202-1 가로 열 수 · 보기 단추 = 보조 탭 줄 오른쪽
|
||||
placeTools: o.setTools, // 202-1 가로 열 수 = 보조 탭 줄 오른쪽
|
||||
});
|
||||
cards.dataset.b09Table = o.tab;
|
||||
live = { root: cards, unpick: (esc) => unpickCards(cards, esc) };
|
||||
listenKeys();
|
||||
return [cards, ...(lead ? [lead] : [])];
|
||||
}
|
||||
o.setTools([toggle]);
|
||||
let range = pickRange(o.spec.rows, o.focus);
|
||||
let chose = -1; // 누른 줄(다시 그려도 유지)
|
||||
const table = o.makeTable((i) => (i === chose ? "is-focus" : pickClass(range, i)));
|
||||
|
||||
Reference in New Issue
Block a user