import { useCallback, useEffect, useRef } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { apiClient } from './apiClient'; import { migrateScheduleItem, quarterDateBounds } from './hubSchedule'; import { ROUTINE_CATEGORIES } from './routineCategories'; const STORAGE_KEY = 'eene-quarter-hub-config-v1'; const QUERY_KEY = ['hub-config'] as const; export interface HubScheduleItem { id: string; /** YYYY-MM-DD */ date: string; text: string; } export interface HubConfig { sloganTitle: string; sloganLines: string[]; scheduleTitle: string; scheduleItems: HubScheduleItem[]; routineLabels: string[]; } export const DEFAULT_HUB_CONFIG: HubConfig = { sloganTitle: '분기 중점 과제', sloganLines: ['인사 · 육성 · 문화 · 총무', '개선과제', '정상 추진'], scheduleTitle: '분기 주요 일정', scheduleItems: [ { id: '1', date: '2026-04-01', text: '상반기 채용·온보딩' }, { id: '2', date: '2026-05-15', text: '조직문화 진단·리더십 교육' }, { id: '3', date: '2026-06-20', text: '분기 성과 점검·평가' }, ], routineLabels: ['채용 운영', '학습 지원', '직원 소통', '자산·시설', '문서·행정'], }; function migrateRoutineLabels(raw: unknown): string[] { if (!Array.isArray(raw)) return [...ROUTINE_CATEGORIES]; const labels = raw.map(String); if (labels.length === ROUTINE_CATEGORIES.length && labels.every((label, i) => label === ROUTINE_CATEGORIES[i])) { return [...ROUTINE_CATEGORIES]; } const legacyFull = ['채용 운영', '교육 운영', '직원 소통', '자산·시설', '문서·행정']; if (labels.length === legacyFull.length && labels.every((label, i) => label === legacyFull[i])) { return [...ROUTINE_CATEGORIES]; } const legacyShort = ['채용', '교육', '소통', '시설', '자산', '행정']; if (labels.length === legacyShort.length && labels.every((label, i) => label === legacyShort[i])) { return [...ROUTINE_CATEGORIES]; } if (labels.some((label) => label === '교육 운영')) { return labels.map((label) => (label === '교육 운영' ? '학습 지원' : label)); } return labels.length > 0 ? labels : [...ROUTINE_CATEGORIES]; } function migrateConfig(raw: Record): HubConfig { const { year } = quarterDateBounds('2026-Q2'); const scheduleItems = Array.isArray(raw.scheduleItems) ? (raw.scheduleItems as (HubScheduleItem & { month?: string })[]).map((item) => migrateScheduleItem(item, year), ) : DEFAULT_HUB_CONFIG.scheduleItems; return { sloganTitle: (() => { const t = (raw.sloganTitle as string) ?? DEFAULT_HUB_CONFIG.sloganTitle; return t === '분기 슬로건' ? '분기 중점 과제' : t; })(), sloganLines: (raw.sloganLines as string[]) ?? DEFAULT_HUB_CONFIG.sloganLines, scheduleTitle: (raw.scheduleTitle as string) ?? DEFAULT_HUB_CONFIG.scheduleTitle, scheduleItems, routineLabels: migrateRoutineLabels(raw.routineLabels), }; } async function fetchHubConfig(): Promise { const raw = await apiClient.get>('/hub-config').then((r) => r.data); return migrateConfig({ ...DEFAULT_HUB_CONFIG, ...raw }); } async function saveHubConfig(config: HubConfig): Promise { const raw = await apiClient.patch>('/hub-config', config).then((r) => r.data); return migrateConfig({ ...DEFAULT_HUB_CONFIG, ...raw }); } function readLegacyLocalConfig(): HubConfig | null { if (typeof window === 'undefined') return null; try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; return migrateConfig({ ...DEFAULT_HUB_CONFIG, ...JSON.parse(raw) }); } catch { return null; } } export function useHubConfig() { const queryClient = useQueryClient(); const legacyMigrated = useRef(false); const { data: config = DEFAULT_HUB_CONFIG, isLoading } = useQuery({ queryKey: QUERY_KEY, queryFn: fetchHubConfig, staleTime: 30_000, }); const saveMutation = useMutation({ mutationFn: saveHubConfig, onSuccess: (saved) => { queryClient.setQueryData(QUERY_KEY, saved); }, }); useEffect(() => { if (isLoading || legacyMigrated.current) return; const legacy = readLegacyLocalConfig(); if (!legacy) return; legacyMigrated.current = true; localStorage.removeItem(STORAGE_KEY); saveHubConfig(legacy) .then((saved) => { queryClient.setQueryData(QUERY_KEY, saved); }) .catch(() => { /* API 미준비 등 — 기본값 유지 */ }); }, [isLoading, queryClient]); const setConfig = useCallback( (patch: Partial | ((prev: HubConfig) => HubConfig)) => { const prev = queryClient.getQueryData(QUERY_KEY) ?? DEFAULT_HUB_CONFIG; const next = typeof patch === 'function' ? patch(prev) : { ...prev, ...patch }; queryClient.setQueryData(QUERY_KEY, next); saveMutation.mutate(next); }, [queryClient, saveMutation], ); const resetConfig = useCallback(() => { queryClient.setQueryData(QUERY_KEY, DEFAULT_HUB_CONFIG); saveMutation.mutate(DEFAULT_HUB_CONFIG); }, [queryClient, saveMutation]); return { config, setConfig, resetConfig, isLoading }; }