Files
goldenChart/frontend/src/hooks/useWorkspacePersist.ts
T
2026-05-23 15:11:48 +09:00

113 lines
3.8 KiB
TypeScript

/**
* 차트 워크스페이스 설정을 DB(백엔드)에 자동 저장·복원하는 훅.
*
* 사용법:
* const { isLoaded, saveNow } = useWorkspacePersist({ layoutId, syncOptions, slots, setLayoutId, setSlots });
*
* - 마운트 시 DB에서 설정을 로드하여 상태를 복원한다.
* - 설정이 변경될 때마다 debounce(2초) 후 자동 저장한다.
* - saveNow() 를 호출하면 즉시 저장한다.
*/
import { useCallback, useEffect, useRef, useState } from 'react';
import {
loadWorkspace,
saveWorkspace,
loadWatchlist,
loadHoldings,
WatchlistItem,
HoldingsItem,
} from '../utils/backendApi';
export interface WorkspaceState {
layoutId: string;
syncOptions: unknown;
slots: unknown[];
}
interface UseWorkspacePersistOptions {
/** 로그인/로그아웃 시 증가 → DB에서 워크스페이스 재로드 */
sessionKey?: number;
/** 현재 워크스페이스 상태 */
state: WorkspaceState;
/** DB 로드 후 상태를 반영하는 콜백 */
onLoad: (data: WorkspaceState) => void;
/** 관심종목 로드 콜백 */
onWatchlistLoad?: (items: WatchlistItem[]) => void;
/** 보유종목 로드 콜백 */
onHoldingsLoad?: (items: HoldingsItem[]) => void;
/** 자동 저장 debounce ms (기본 2000) */
debounceMs?: number;
/** 자동 저장 비활성화 */
noAutoSave?: boolean;
}
export function useWorkspacePersist({
sessionKey = 0,
state,
onLoad,
onWatchlistLoad,
onHoldingsLoad,
debounceMs = 2000,
noAutoSave = false,
}: UseWorkspacePersistOptions) {
const [isLoaded, setIsLoaded] = useState(false);
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isFirstRender = useRef(true);
// ── 초기 로드 (세션 변경 시 재로드) ─────────────────────────────────────────
useEffect(() => {
let cancelled = false;
setIsLoaded(false);
isFirstRender.current = true;
(async () => {
const [ws, watchlist, holdings] = await Promise.all([
loadWorkspace(),
onWatchlistLoad ? loadWatchlist() : Promise.resolve(null),
onHoldingsLoad ? loadHoldings() : Promise.resolve(null),
]);
if (cancelled) return;
if (ws) {
onLoad({
layoutId: ws.layoutId,
syncOptions: ws.syncOptions,
slots: ws.slots,
});
}
if (watchlist && onWatchlistLoad) onWatchlistLoad(watchlist);
if (holdings && onHoldingsLoad) onHoldingsLoad(holdings);
setIsLoaded(true);
})();
return () => { cancelled = true; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionKey]);
// ── 자동 저장 (debounce) ──────────────────────────────────────────────────
useEffect(() => {
if (!isLoaded || noAutoSave) return;
if (isFirstRender.current) { isFirstRender.current = false; return; }
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
saveWorkspace(state as Parameters<typeof saveWorkspace>[0]);
}, debounceMs);
return () => {
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
};
}, [state, isLoaded, noAutoSave, debounceMs]);
// ── 즉시 저장 ──────────────────────────────────────────────────────────────
const saveNow = useCallback(() => {
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
return saveWorkspace(state as Parameters<typeof saveWorkspace>[0]);
}, [state]);
return { isLoaded, saveNow };
}