구조적인 부분 수정

This commit is contained in:
2026-02-24 11:38:35 +09:00
parent 6d9c9cb010
commit 883f87753c
15 changed files with 789 additions and 672 deletions

View File

@@ -10,7 +10,7 @@ import './Dashboard.css'
const formatTime = (isoStr?: string) => {
if (!isoStr) return null;
const d = new Date(isoStr);
return d.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
return d.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZone: 'Asia/Seoul' });
};
// 마켓 운영 시간 체크 (한국장 + 미국장)
@@ -70,10 +70,11 @@ const Dashboard = () => {
const { data: mcpStatus } = useQuery({
queryKey: ['mcp-status'],
queryFn: async () => (await api.get('/mcp/status')).data,
refetchInterval: isMarketOpen ? 5000 : false,
refetchInterval: isMarketOpen ? 30000 : false, // 5초 → 30초 (MCP 상태는 빈번한 폴링 불필요)
staleTime: 20000,
})
const { data: ruleBasedRecommendations } = useQuery({
const { data: ruleBasedRecommendations, refetch: refetchRecommendations, isError: isRecommendationsError } = useQuery({
queryKey: ['rule-based-recommendations'],
queryFn: async () => {
const response = await api.get('/recommendations/rule-based', {
@@ -95,8 +96,9 @@ const Dashboard = () => {
return data;
},
refetchInterval: isMarketOpen ? 600000 : false, // 10분마다 배치 실행
// 장외에도 초기 로드는 실행 (DB 조회는 빠름)
refetchInterval: isMarketOpen ? 120000 : 300000, // 장중 2분, 장외 5분
staleTime: isMarketOpen ? 110000 : 290000, // refetchInterval보다 약간 짧게 → 재진입 시 즉시 표시
refetchOnWindowFocus: false, // 탭 전환 시 불필요한 재요청 방지 (자동매매 중복 트리거 차단)
})
const { data: chartData, isLoading: chartLoading } = useQuery({
@@ -201,13 +203,11 @@ const Dashboard = () => {
}
}, [wsMessages, queryClient])
// 보유종목 + 분석종목 전체 코드 수집 (현재가 일괄 조회용)
// 보유종목 코드만 수집 (현재가 일괄 실시간 조회용)
// 분석종목은 stock_analysis.current_price (DB값)를 폴백으로 사용하므로 실시간 조회 불필요
const allStockCodes = useMemo(() => {
const codes = new Set<string>()
;(portfolio || []).forEach((s: any) => codes.add(s.stock_code))
;(ruleBasedRecommendations?.analyzed_stock_list || []).forEach((s: any) => codes.add(s.stock_code))
return Array.from(codes)
}, [portfolio, ruleBasedRecommendations])
return (portfolio || []).map((s: any) => s.stock_code)
}, [portfolio])
// 현재가 일괄 실시간 조회 (보유종목·분석종목 동기화)
const { data: livePrices } = useQuery({
@@ -698,10 +698,23 @@ const Dashboard = () => {
<span className="text-xs bg-purple-500/20 text-purple-300 px-2 py-0.5 rounded-full">
{ruleBasedRecommendations?.analyzed_stocks || 0}
</span>
<button
onClick={() => refetchRecommendations()}
className="p-1 hover:bg-gray-700/50 rounded transition-colors"
title="분석 종목 새로고침"
>
<RefreshCw className="w-3 h-3 text-gray-400 hover:text-purple-400" />
</button>
</div>
</div>
<div className="divide-y divide-gray-700/50 overflow-y-auto custom-scrollbar flex-1">
{ruleBasedRecommendations === undefined ? (
{isRecommendationsError ? (
<div className="h-full flex flex-col items-center justify-center text-red-500 text-xs p-4 text-center">
<p>⚠️ API 오류</p>
<p className="text-[10px] text-gray-500 mt-1">백엔드 로그 확인 필요</p>
<button onClick={() => refetchRecommendations()} className="mt-2 px-2 py-1 bg-red-500/20 text-red-400 rounded text-[10px]">재시도</button>
</div>
) : ruleBasedRecommendations === undefined ? (
<div className="h-full flex flex-col items-center justify-center text-gray-500 text-xs p-4">
<RefreshCw className="w-6 h-6 animate-spin mb-2 text-purple-400" />
<p>분석 종목 로딩 중...</p>
@@ -790,8 +803,8 @@ const Dashboard = () => {
{journals && journals.length > 0 ? (
journals.map((journal: any) => {
const journalDate = new Date(journal.journal_date || journal.created_at);
const dateStr = journalDate.toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit' });
const timeStr = journalDate.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', hour12: false });
const dateStr = journalDate.toLocaleDateString('ko-KR', { month: '2-digit', day: '2-digit', timeZone: 'Asia/Seoul' });
const timeStr = journalDate.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Asia/Seoul' });
const isGoodMood = journal.mood === '좋음' || journal.mood === 'good' || journal.mood === 'positive';
const isBadMood = journal.mood === '나쁨' || journal.mood === 'bad' || journal.mood === 'negative';
const tags: string[] = Array.isArray(journal.tags) ? journal.tags : [];