"use client"; /** * useTableSettings — 날코딩 페이지용 테이블 설정 훅 * * TableSettingsModal과 함께 사용하여 컬럼 표시/숨김, 순서, 너비를 관리합니다. * 설정은 localStorage에 자동 저장/복원됩니다. * * @example * const ts = useTableSettings("item-info", TABLE_NAME, GRID_COLUMNS); * * // 툴바 버튼 * * * // 테이블 헤더 — GRID_COLUMNS 대신 ts.visibleColumns 사용 * {ts.visibleColumns.map(col => {col.label})} * * // 모달 (JSX 하단) * */ import React, { useState, useEffect, useCallback, useMemo } from "react"; import { loadTableSettings, type TableSettings, type BaseFilter } from "@/components/common/TableSettingsModal"; export function useTableSettings( settingsId: string, tableName: string, defaultColumns: T[], /** 초기 표시 컬럼 키 (미지정 시 defaultColumns 전체) */ initialVisibleKeys?: string[], ) { const [open, setOpen] = useState(false); const [visibleKeys, setVisibleKeys] = useState>( () => new Set(initialVisibleKeys || defaultColumns.map((c) => c.key)), ); const [columnWidths, setColumnWidths] = useState>({}); const [orderedKeys, setOrderedKeys] = useState( () => initialVisibleKeys || defaultColumns.map((c) => c.key), ); const [baseFilter, setBaseFilter] = useState(); const [groupColumns, setGroupColumns] = useState([]); const [groupSumEnabled, setGroupSumEnabled] = useState(false); // 초기 filterConfig: GRID_COLUMNS에 있는 컬럼만 필터 가능 (전부 비활성) const [filterConfig, setFilterConfig] = useState( () => defaultColumns.map((c) => ({ columnName: c.key, displayName: (c as any).label || c.key, enabled: false, filterType: "text" as const, width: 25, })), ); /** TableSettingsModal onSave에 전달할 콜백 */ const applySettings = useCallback( (settings: TableSettings) => { const visible = new Set(); const widths: Record = {}; const order: string[] = []; for (const cs of settings.columns) { if (cs.visible) { visible.add(cs.columnName); widths[cs.columnName] = cs.width; order.push(cs.columnName); } } // settings에 없는 새 컬럼은 초기 표시 목록에 있을 때만 보이도록 추가 const initKeys = initialVisibleKeys ? new Set(initialVisibleKeys) : new Set(defaultColumns.map((c) => c.key)); for (const col of defaultColumns) { if (!settings.columns.find((c) => c.columnName === col.key) && initKeys.has(col.key)) { visible.add(col.key); order.push(col.key); } } setVisibleKeys(visible); setColumnWidths(widths); setOrderedKeys(order); // 화면에 표시된 컬럼만 필터 가능하도록 제한 setFilterConfig( settings.filters?.filter((f) => visible.has(f.columnName)), ); // 기본 데이터 필터 setBaseFilter(settings.baseFilter); // 그룹 설정 const enabledGroups = (settings.groups || []).filter((g) => g.enabled).map((g) => g.columnName); setGroupColumns(enabledGroups); setGroupSumEnabled(settings.groupSumEnabled || false); }, [defaultColumns, initialVisibleKeys], ); // 마운트 시 저장된 설정 복원 useEffect(() => { const saved = loadTableSettings(settingsId); if (saved) applySettings(saved); }, []); // eslint-disable-line react-hooks/exhaustive-deps /** 설정이 적용된 컬럼 목록 (순서 + 표시 필터 적용) */ const visibleColumns = useMemo((): T[] => { const colMap = new Map(defaultColumns.map((c) => [c.key, c])); const result: T[] = []; // 저장된 순서대로 for (const key of orderedKeys) { if (visibleKeys.has(key)) { const col = colMap.get(key); if (col) result.push(col); } } // orderedKeys에 없는 컬럼 (새로 추가된 것) for (const col of defaultColumns) { if (!orderedKeys.includes(col.key) && visibleKeys.has(col.key)) { result.push(col); } } return result.length > 0 ? result : defaultColumns; }, [defaultColumns, orderedKeys, visibleKeys]); /** 컬럼 표시 여부 확인 */ const isVisible = useCallback((key: string) => visibleKeys.has(key), [visibleKeys]); /** 컬럼 너비 가져오기 (설정값 or undefined) */ const getWidth = useCallback( (key: string): number | undefined => columnWidths[key], [columnWidths], ); /** TableHead/TableCell에 적용할 style 객체 (0 = 자동, 값 있으면 고정) */ const thStyle = useCallback( (key: string): React.CSSProperties | undefined => { const w = columnWidths[key]; if (!w || w <= 0) return undefined; // 0이면 브라우저 자동 return { width: `${w}px`, minWidth: `${w}px`, maxWidth: `${w}px` }; }, [columnWidths], ); /** * 데이터를 그룹핑하고 소계 행을 삽입한 배열을 반환합니다. * groupColumns가 비어있으면 원본 배열을 그대로 반환합니다. * 소계 행은 _isGroupSummary: true, _groupKey, _groupValue 속성을 가집니다. */ const groupData = useCallback( >(rows: R[]): (R & { _isGroupSummary?: boolean; _isGroupHeader?: boolean; _groupKey?: string; _groupValue?: string; _groupCount?: number })[] => { if (groupColumns.length === 0) return rows; // 다중 그룹 컬럼 결합 키 const makeKey = (row: R) => groupColumns.map((col) => String(row[col] ?? "(빈 값)")).join(" / "); const groups = new Map(); for (const row of rows) { const key = makeKey(row); if (!groups.has(key)) groups.set(key, []); groups.get(key)!.push(row); } const result: (R & { _isGroupSummary?: boolean; _isGroupHeader?: boolean; _groupKey?: string; _groupValue?: string; _groupCount?: number })[] = []; for (const [groupValue, groupRows] of groups) { // 그룹 헤더 행 const headerRow: any = { _isGroupHeader: true, _groupKey: groupColumns.join(","), _groupValue: groupValue, _groupCount: groupRows.length }; result.push(headerRow); // 그룹 내 데이터 행 result.push(...groupRows); // 소계 행 (groupSumEnabled일 때만) if (groupSumEnabled) { const summaryRow: any = { _isGroupSummary: true, _groupKey: groupColumns.join(","), _groupValue: groupValue }; for (const col of defaultColumns) { const values = groupRows.map((r) => Number(r[col.key])).filter((v) => !isNaN(v)); if (values.length > 0 && values.some((v) => v !== 0)) { summaryRow[col.key] = values.reduce((a, b) => a + b, 0); } } summaryRow[groupColumns[0]] = `${groupValue} 소계 (${groupRows.length}건)`; result.push(summaryRow); } } return result; }, [groupColumns, groupSumEnabled, defaultColumns], ); return { /** 모달 open 상태 */ open, /** 모달 open 상태 setter */ setOpen, /** web-types API 호출용 테이블명 */ tableName, /** localStorage 키 */ settingsId, /** TableSettingsModal onSave 콜백 */ applySettings, /** 설정 적용된 컬럼 배열 (순서 + 표시 필터) */ visibleColumns, /** 특정 컬럼 표시 여부 */ isVisible, /** 특정 컬럼 너비 (px) */ getWidth, /** TableHead/TableCell style 객체 반환 */ thStyle, /** 필터 설정 */ filterConfig, /** 기본 데이터 필터 (예: division = '판매') */ baseFilter, /** 데이터 그룹핑 + 소계 삽입 함수 */ groupData, /** 그룹 컬럼 목록 */ groupColumns, /** 그룹별 합산 활성 여부 */ groupSumEnabled, /** GRID_COLUMNS 기본 컬럼 키 목록 (TableSettingsModal defaultVisibleKeys용) */ defaultVisibleKeys: initialVisibleKeys || defaultColumns.map((c) => c.key), }; }