goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
@@ -0,0 +1,217 @@
/**
* LiveStrategyPanel
*
* 실시간 전략 체크 — 전역 설정 UI.
* 관심종목(★) 등록 종목 전체가 체크 대상이며, 여기서 전략·실행 방식을 지정합니다.
*/
import React, { useState, useCallback } from 'react';
import type { Theme } from '../types';
import {
saveLiveStrategySettings,
type LiveStrategySettingsDto,
} from '../utils/backendApi';
interface Strategy {
id: number;
name: string;
}
interface LiveStrategyPanelProps {
theme: Theme;
market: string;
strategies: Strategy[];
settings: LiveStrategySettingsDto;
watchlistCount?: number;
monitoredMarkets?: string[];
onClearMarkers?: () => void;
onClose?: () => void;
onSettingsChange?: (settings: LiveStrategySettingsDto) => void;
}
const LiveStrategyPanel: React.FC<LiveStrategyPanelProps> = ({
theme, market, strategies, settings,
watchlistCount = 0, monitoredMarkets = [],
onClearMarkers, onClose, onSettingsChange,
}) => {
const [saving, setSaving] = useState(false);
const persist = useCallback(async (patch: Partial<LiveStrategySettingsDto>) => {
const next: LiveStrategySettingsDto = { ...settings, ...patch, market };
const prev = settings;
onSettingsChange?.(next);
setSaving(true);
try {
const saved = await saveLiveStrategySettings(next);
if (saved) {
onSettingsChange?.(saved);
onClearMarkers?.();
} else {
onSettingsChange?.(prev);
window.alert('실시간 전략 설정 저장에 실패했습니다. 네트워크·서버 상태를 확인해 주세요.');
}
} catch {
onSettingsChange?.(prev);
window.alert('실시간 전략 설정 저장 중 오류가 발생했습니다.');
} finally {
setSaving(false);
}
}, [settings, market, onClearMarkers, onSettingsChange]);
const isOn = settings.isLiveCheck;
const execType = settings.executionType;
const stratId = settings.strategyId;
const monCount = monitoredMarkets.length;
return (
<div className={`lsp-wrap lsp-wrap--${theme}`}>
<div className="lsp-header">
<svg className="lsp-icon" width="13" height="13" viewBox="0 0 14 14"
fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
<circle cx="7" cy="7" r="5.5"/>
<polyline points="5,7 7,9 10,5"/>
</svg>
<span className="lsp-title"> </span>
{saving && <span className="lsp-saving"> </span>}
{onClose && (
<button className="lsp-close-btn" onClick={onClose} title="닫기">
<svg width="11" height="11" viewBox="0 0 11 11" fill="none"
stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
<line x1="1" y1="1" x2="10" y2="10"/>
<line x1="10" y1="1" x2="1" y2="10"/>
</svg>
</button>
)}
</div>
<div className="lsp-monitor-stats">
<span> {watchlistCount}</span>
<span className="lsp-monitor-sep">·</span>
<span className={monCount > 0 ? 'lsp-monitor-on' : ''}>
{monCount}
</span>
</div>
{isOn && stratId && watchlistCount > 0 && (
<p className="lsp-monitor-hint">
.
</p>
)}
{monCount > 0 && (
<div className="lsp-monitor-list" title={monitoredMarkets.join(', ')}>
{monitoredMarkets.slice(0, 8).map(m => (
<span key={m} className="lsp-monitor-chip">{m.replace('KRW-', '')}</span>
))}
{monCount > 8 && <span className="lsp-monitor-chip">+{monCount - 8}</span>}
</div>
)}
{isOn && !stratId && (
<p className="lsp-monitor-hint"> {watchlistCount} .</p>
)}
<div className="lsp-row">
<span className="lsp-label"> </span>
<label className="lsp-toggle">
<input
type="checkbox"
checked={isOn}
onChange={e => persist({ isLiveCheck: e.target.checked })}
/>
<span className="lsp-toggle-slider" />
</label>
</div>
<div className={`lsp-row${!isOn ? ' lsp-row--disabled' : ''}`}>
<span className="lsp-label"> </span>
<select
className="lsp-select"
disabled={!isOn}
value={stratId ?? ''}
onChange={e => persist({ strategyId: e.target.value ? Number(e.target.value) : null })}
>
<option value=""> </option>
{strategies.map(s => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
</select>
</div>
<div className={`lsp-exec-wrap${!isOn ? ' lsp-row--disabled' : ''}`}>
<span className="lsp-label"> </span>
<div className="lsp-radio-group">
<label className="lsp-radio">
<input
type="radio"
name="execType"
value="CANDLE_CLOSE"
checked={execType === 'CANDLE_CLOSE'}
disabled={!isOn}
onChange={() => persist({ executionType: 'CANDLE_CLOSE' })}
/>
<span className="lsp-radio-label">
<span className="lsp-radio-title"> </span>
<span className="lsp-radio-desc"> 1 </span>
</span>
</label>
<label className="lsp-radio">
<input
type="radio"
name="execType"
value="REALTIME_TICK"
checked={execType === 'REALTIME_TICK'}
disabled={!isOn}
onChange={() => persist({ executionType: 'REALTIME_TICK' })}
/>
<span className="lsp-radio-label">
<span className="lsp-radio-title"> (3)</span>
<span className="lsp-radio-desc"> , 3 </span>
</span>
</label>
</div>
</div>
<div className={`lsp-exec-wrap${!isOn ? ' lsp-row--disabled' : ''}`}>
<span className="lsp-label"> </span>
<div className="lsp-radio-group">
<label className="lsp-radio">
<input
type="radio"
name="lsp-posMode"
value="LONG_ONLY"
checked={(settings.positionMode ?? 'LONG_ONLY') === 'LONG_ONLY'}
disabled={!isOn}
onChange={() => persist({ positionMode: 'LONG_ONLY' })}
/>
<span className="lsp-radio-label">
<span className="lsp-radio-title"> </span>
<span className="lsp-radio-desc"> </span>
</span>
</label>
<label className="lsp-radio">
<input
type="radio"
name="lsp-posMode"
value="SIGNAL_ONLY"
checked={settings.positionMode === 'SIGNAL_ONLY'}
disabled={!isOn}
onChange={() => persist({ positionMode: 'SIGNAL_ONLY' })}
/>
<span className="lsp-radio-label">
<span className="lsp-radio-title"> </span>
<span className="lsp-radio-desc"> , </span>
</span>
</label>
</div>
</div>
{isOn && (
<div className="lsp-status">
<span className={`lsp-dot${isOn ? ' lsp-dot--on' : ''}`} />
{stratId
? `${strategies.find(s => s.id === stratId)?.name ?? '전략'} · 관심 ${monCount}종목`
: '전략을 선택하세요'}
</div>
)}
</div>
);
};
export default LiveStrategyPanel;