Files
invyone/frontend/lib/registry/components/table/internals/pivot/hooks/usePivotState.ts
T
DDD1542 49b4cdf562 feat: 테이블 pivot 모드 본체 통째 흡수 (T3b)
5 viewMode 통합 세번째 단계 — Codex 권고 (Pivot 통째 흡수, 회귀 위험
차단) 그대로 적용. v2-pivot-grid 의 본체 + utils + components + hooks +
보조 타입을 모두 table/ 하위로 이전. 리팩토링 X (V2Date picker 패턴 동일).

table/types.ts 보조 타입 흡수
- PivotResult, PivotHeaderNode, PivotFlatRow, PivotFlatColumn, PivotCellValue,
  PivotCellData, PivotGridState, PivotGridProps
- PivotDataSourceType, PivotFilterCondition, PivotJoinConfig,
  PivotDataSourceConfig, PivotTotalsConfig, PivotFieldChooserConfig,
  PivotChartConfig, PivotConditionalFormatRule, PivotStyleConfig,
  PivotExportConfig
- PivotFieldConfig 에 filterValues / filterType / isCalculated /
  calculateFormula 누락 속성 추가
- TableConfig 에 pivot 보조 키 (pivotTotals / pivotStyle /
  pivotFieldChooser / pivotChart / pivotExportConfig)

table/utils/pivot/ 4개 파일 이전 (1505줄)
- pivotEngine.ts (812) — processPivotData / pathToKey / 헤더 트리 / 매트릭스 /
  10종 displayMode (runningTotal, percentDifferenceFromPrevious 등)
- aggregation.ts (180) — sum/count/avg/min/max/countDistinct + 포맷
- conditionalFormat.ts (311) — colorScale/dataBar/iconSet/cellValue 4 종
- exportExcel.ts (202) — Excel 내보내기 (xlsx)
- 옛 prefix(AggregationType 등) → Pivot prefix 일괄 정리

table/internals/pivot/components/ 7개 파일 이전 (2347줄)
- ContextMenu / DrillDownModal / FieldChooser / FieldPanel / FilterPopup /
  PivotChart / index

table/internals/pivot/hooks/ 3개 파일 이전 (570줄)
- usePivotState / useVirtualScroll / index

table/views/PivotView.tsx 신규 (PivotGridComponent.tsx 1963줄 통째 흡수)
- import 경로 일괄 정정 (../../types → ../types, ./utils → ../utils/pivot,
  ./components → ../internals/pivot/components, ./hooks →
  ../internals/pivot/hooks)
- 컴포넌트 이름 PivotGridComponent → PivotView
- 본체 로직 그대로 (리팩토링 X)

TableComponent.switch
- pivot 분기 placeholder 제거 → PivotView 호출
- DOM filter 에 pivotTotals/pivotStyle/pivotFieldChooser/pivotChart/
  pivotExportConfig 추가

13 files, +5400+ insertions. v2-pivot-grid/ 폴더 자체는 Phase T5 dead code
일괄 삭제에서 정리 예정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:23:39 +09:00

232 lines
6.2 KiB
TypeScript

"use client";
/**
* PivotState 훅
* 피벗 그리드 상태 저장/복원 관리
*/
import { useState, useEffect, useCallback } from "react";
import { PivotFieldConfig, PivotGridState, PivotSortDirection } from "../../../types";
// ==================== 타입 ====================
export interface PivotStateConfig {
enabled: boolean;
storageKey?: string;
storageType?: "localStorage" | "sessionStorage";
}
export interface SavedPivotState {
version: string;
timestamp: number;
fields: PivotFieldConfig[];
expandedRowPaths: string[][];
expandedColumnPaths: string[][];
filterConfig: Record<string, any[]>;
sortConfig: {
field: string;
direction: PivotSortDirection;
} | null;
}
export interface UsePivotStateResult {
// 상태
fields: PivotFieldConfig[];
pivotState: PivotGridState;
// 상태 변경
setFields: (fields: PivotFieldConfig[]) => void;
setPivotState: (state: PivotGridState | ((prev: PivotGridState) => PivotGridState)) => void;
// 저장/복원
saveState: () => void;
loadState: () => boolean;
clearState: () => void;
hasStoredState: () => boolean;
// 상태 정보
lastSaved: Date | null;
isDirty: boolean;
}
// ==================== 상수 ====================
const STATE_VERSION = "1.0.0";
const DEFAULT_STORAGE_KEY = "pivot-grid-state";
// ==================== 훅 ====================
export function usePivotState(
initialFields: PivotFieldConfig[],
config: PivotStateConfig
): UsePivotStateResult {
const {
enabled,
storageKey = DEFAULT_STORAGE_KEY,
storageType = "localStorage",
} = config;
// 상태
const [fields, setFieldsInternal] = useState<PivotFieldConfig[]>(initialFields);
const [pivotState, setPivotStateInternal] = useState<PivotGridState>({
expandedRowPaths: [],
expandedColumnPaths: [],
sortConfig: null,
filterConfig: {},
});
const [lastSaved, setLastSaved] = useState<Date | null>(null);
const [isDirty, setIsDirty] = useState(false);
const [initialStateLoaded, setInitialStateLoaded] = useState(false);
// 스토리지 가져오기
const getStorage = useCallback(() => {
if (typeof window === "undefined") return null;
return storageType === "localStorage" ? localStorage : sessionStorage;
}, [storageType]);
// 저장된 상태 확인
const hasStoredState = useCallback((): boolean => {
const storage = getStorage();
if (!storage) return false;
return storage.getItem(storageKey) !== null;
}, [getStorage, storageKey]);
// 상태 저장
const saveState = useCallback(() => {
if (!enabled) return;
const storage = getStorage();
if (!storage) return;
const stateToSave: SavedPivotState = {
version: STATE_VERSION,
timestamp: Date.now(),
fields,
expandedRowPaths: pivotState.expandedRowPaths,
expandedColumnPaths: pivotState.expandedColumnPaths,
filterConfig: pivotState.filterConfig,
sortConfig: pivotState.sortConfig,
};
try {
storage.setItem(storageKey, JSON.stringify(stateToSave));
setLastSaved(new Date());
setIsDirty(false);
console.log("✅ 피벗 상태 저장됨:", storageKey);
} catch (error) {
console.error("❌ 피벗 상태 저장 실패:", error);
}
}, [enabled, getStorage, storageKey, fields, pivotState]);
// 상태 불러오기
const loadState = useCallback((): boolean => {
if (!enabled) return false;
const storage = getStorage();
if (!storage) return false;
try {
const saved = storage.getItem(storageKey);
if (!saved) return false;
const parsedState: SavedPivotState = JSON.parse(saved);
// 버전 체크
if (parsedState.version !== STATE_VERSION) {
console.warn("⚠️ 저장된 상태 버전이 다름, 무시됨");
return false;
}
// 상태 복원
setFieldsInternal(parsedState.fields);
setPivotStateInternal({
expandedRowPaths: parsedState.expandedRowPaths,
expandedColumnPaths: parsedState.expandedColumnPaths,
sortConfig: parsedState.sortConfig,
filterConfig: parsedState.filterConfig,
});
setLastSaved(new Date(parsedState.timestamp));
setIsDirty(false);
console.log("✅ 피벗 상태 복원됨:", storageKey);
return true;
} catch (error) {
console.error("❌ 피벗 상태 복원 실패:", error);
return false;
}
}, [enabled, getStorage, storageKey]);
// 상태 초기화
const clearState = useCallback(() => {
const storage = getStorage();
if (!storage) return;
try {
storage.removeItem(storageKey);
setLastSaved(null);
console.log("🗑️ 피벗 상태 삭제됨:", storageKey);
} catch (error) {
console.error("❌ 피벗 상태 삭제 실패:", error);
}
}, [getStorage, storageKey]);
// 필드 변경 (dirty 플래그 설정)
const setFields = useCallback((newFields: PivotFieldConfig[]) => {
setFieldsInternal(newFields);
setIsDirty(true);
}, []);
// 피벗 상태 변경 (dirty 플래그 설정)
const setPivotState = useCallback(
(newState: PivotGridState | ((prev: PivotGridState) => PivotGridState)) => {
setPivotStateInternal(newState);
setIsDirty(true);
},
[]
);
// 초기 로드
useEffect(() => {
if (!initialStateLoaded && enabled && hasStoredState()) {
loadState();
setInitialStateLoaded(true);
}
}, [enabled, hasStoredState, loadState, initialStateLoaded]);
// 초기 필드 동기화 (저장된 상태가 없을 때)
useEffect(() => {
if (initialStateLoaded) return;
if (!hasStoredState() && initialFields.length > 0) {
setFieldsInternal(initialFields);
setInitialStateLoaded(true);
}
}, [initialFields, hasStoredState, initialStateLoaded]);
// 자동 저장 (변경 시)
useEffect(() => {
if (!enabled || !isDirty) return;
const timeout = setTimeout(() => {
saveState();
}, 1000); // 1초 디바운스
return () => clearTimeout(timeout);
}, [enabled, isDirty, saveState]);
return {
fields,
pivotState,
setFields,
setPivotState,
saveState,
loadState,
clearState,
hasStoredState,
lastSaved,
isDirty,
};
}
export default usePivotState;