React + FastAPI 풀 마이그레이션 — Streamlit 제거
- backend/ — FastAPI + JWT + 모든 REST 엔드포인트 - frontend/ — Next.js 14 + Tailwind + 7페이지 (대시보드/트레이드/거래소/자동매매/설정/내정보/로그인) - core_logic.py — 신호계산/알림 로직 분리 (기존 app_streamlit.py 에서 추출) - users_db.py + bcrypt 인증, exchange_keys.py + Fernet 암호화 - trades_db.py — 진입/청산 lifecycle 추적, signal_events raw 로그 - settings_db.py — 모든 운영 파라미터 DB 영속 저장 (RSI/거래량/펀딩비 임계값 포함) - docker-compose: frontend / backend / postgres + Traefik 라우팅 - assets/logo.svg — JUNGGOMOA 그라디언트 로고 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
import Chart from '@/components/Chart';
|
||||
import { Banner, Card, PageHeader, Select, Toggle } from '@/components/ui';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
|
||||
const SYMBOLS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT'];
|
||||
const INTERVALS = ['1m', '3m', '5m', '15m', '30m', '1h', '4h', '12h', '1d'];
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [symbol, setSymbol] = useState('BTCUSDT');
|
||||
const [interval, setIntervalV] = useState('5m');
|
||||
const [auto, setAuto] = useState(true);
|
||||
const [refresh, setRefresh] = useState(30);
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [tick, setTick] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
api.get(`/api/market/dashboard?symbol=${symbol}&interval=${interval}&limit=200`)
|
||||
.then(d => { if (!cancelled) setData(d); })
|
||||
.catch(e => { if (!cancelled) setError(e.message); })
|
||||
.finally(() => { if (!cancelled) setLoading(false); });
|
||||
return () => { cancelled = true; };
|
||||
}, [symbol, interval, tick]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!auto) return;
|
||||
const id = setInterval(() => setTick(t => t + 1), refresh * 1000);
|
||||
return () => clearInterval(id);
|
||||
}, [auto, refresh]);
|
||||
|
||||
const now = new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title="📊 선물 대시보드"
|
||||
subtitle={`${symbol} · ${interval} · 마지막 갱신 ${now} KST`}
|
||||
right={
|
||||
<button onClick={() => setTick(t => t + 1)} className="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-700 text-white rounded-md shadow-sm">
|
||||
<RefreshCw size={14} /> 새로고침
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card className="mb-4">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 items-end">
|
||||
<Select label="심볼" value={symbol} onChange={(e: any) => setSymbol(e.target.value)}>
|
||||
{SYMBOLS.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</Select>
|
||||
<Select label="시간축" value={interval} onChange={(e: any) => setIntervalV(e.target.value)}>
|
||||
{INTERVALS.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</Select>
|
||||
<div>
|
||||
<span className="block text-xs font-medium text-slate-600 mb-1">갱신(초)</span>
|
||||
<input type="number" value={refresh} min={10} max={300}
|
||||
onChange={(e) => setRefresh(parseInt(e.target.value))}
|
||||
className="w-full px-3 py-2 text-sm rounded-md border border-slate-300 bg-slate-50" />
|
||||
</div>
|
||||
<div className="flex items-center"><Toggle checked={auto} onChange={setAuto} label="자동 갱신" /></div>
|
||||
<div className="text-xs text-slate-500 text-right">
|
||||
{loading ? '⏳ 로딩 중...' : data?.last_price ? `현재가: ${data.last_price.toLocaleString()}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{data?.banner && (
|
||||
<div className="mb-4">
|
||||
<Banner level={data.banner.level}>{data.banner.text}</Banner>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <Banner level="danger">{error}</Banner>}
|
||||
|
||||
<Card className="p-2 md:p-3">
|
||||
<Chart rows={data?.rows || []} lastPrice={data?.last_price} />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user