- lottoUtils.jsx: 공통 유틸·상수 추출 (Ball, NumberRow, 통계 헬퍼 등) - hooks/useLottoData.js: 핵심 데이터 로드 (최신회차, 통계, 시뮬레이션, 리포트) - hooks/usePurchases.js: 구매 기록 CRUD - hooks/useManualRecommend.js: 수동 추천 + 히스토리 - components/: MetricBlock, FrequencyChart, PerformanceBanner, ConfidenceRing, CombinedRecommendPanel, ReportPanel, PersonalAnalysisPanel, PurchasePanel 분리 - getReport import 누락 버그 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
76 lines
3.3 KiB
JavaScript
76 lines
3.3 KiB
JavaScript
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { deleteHistory, getHistory, recommend } from '../../../api';
|
|
import { buildMetricsFromHistory } from '../lottoUtils';
|
|
|
|
export default function useManualRecommend() {
|
|
const [params, setParams] = useState({
|
|
recent_window: 200, recent_weight: 2.0, avoid_recent_k: 5,
|
|
});
|
|
const presets = useMemo(() => [
|
|
{ name: '기본', recent_window: 200, recent_weight: 2.0, avoid_recent_k: 5 },
|
|
{ name: '최근 가중치↑', recent_window: 100, recent_weight: 3.0, avoid_recent_k: 10 },
|
|
{ name: '안전(분산)', recent_window: 300, recent_weight: 1.6, avoid_recent_k: 8 },
|
|
{ name: '공격(최근)', recent_window: 80, recent_weight: 3.5, avoid_recent_k: 12 },
|
|
], []);
|
|
const [result, setResult] = useState(null);
|
|
const [history, setHistory] = useState([]);
|
|
const [historyExpanded, setHistoryExpanded] = useState(false);
|
|
const historyEndRef = useRef(null);
|
|
const prevHistoryExpandedRef = useRef(false);
|
|
const [loading, setLoading] = useState({ recommend: false, history: false });
|
|
const [error, setError] = useState('');
|
|
|
|
const historyMetrics = useMemo(() => buildMetricsFromHistory(history), [history]);
|
|
const visibleHistory = historyExpanded ? history : history.slice(0, 5);
|
|
|
|
const refreshHistory = useCallback(async () => {
|
|
setLoading((s) => ({ ...s, history: true }));
|
|
setError('');
|
|
try {
|
|
const limit = 100; let offset = 0; const allItems = [];
|
|
while (true) {
|
|
const data = await getHistory(limit, offset);
|
|
const items = data.items ?? [];
|
|
allItems.push(...items);
|
|
if (items.length < limit) break;
|
|
offset += limit;
|
|
}
|
|
setHistory(allItems);
|
|
} catch (e) { setError(e?.message ?? String(e)); }
|
|
finally { setLoading((s) => ({ ...s, history: false })); }
|
|
}, []);
|
|
|
|
const onRecommend = useCallback(async () => {
|
|
setLoading((s) => ({ ...s, recommend: true })); setError('');
|
|
try { const data = await recommend(params); setResult(data); await refreshHistory(); }
|
|
catch (e) { setError(e?.message ?? String(e)); }
|
|
finally { setLoading((s) => ({ ...s, recommend: false })); }
|
|
}, [params, refreshHistory]);
|
|
|
|
const onDelete = useCallback(async (id) => {
|
|
if (!confirm(`히스토리 #${id}를 삭제할까요?`)) return;
|
|
setError('');
|
|
try { await deleteHistory(id); setHistory((prev) => prev.filter((item) => item.id !== id)); }
|
|
catch (e) { setError(e?.message ?? String(e)); }
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (historyExpanded && !prevHistoryExpandedRef.current) {
|
|
requestAnimationFrame(() => {
|
|
historyEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
});
|
|
}
|
|
prevHistoryExpandedRef.current = historyExpanded;
|
|
}, [historyExpanded, visibleHistory.length]);
|
|
|
|
useEffect(() => { refreshHistory(); }, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
return {
|
|
params, setParams, presets,
|
|
result, history, historyExpanded, setHistoryExpanded,
|
|
historyEndRef, loading, error, setError,
|
|
historyMetrics, visibleHistory,
|
|
refreshHistory, onRecommend, onDelete,
|
|
};
|
|
}
|