모의투자 화면수정
This commit is contained in:
@@ -250,15 +250,18 @@ const PaperTradingPage: React.FC<Props> = ({
|
|||||||
rightStorageKey="ptd-right-width"
|
rightStorageKey="ptd-right-width"
|
||||||
rightDefaultWidth={440}
|
rightDefaultWidth={440}
|
||||||
rightCollapsedStorageKey="ptd-right-open"
|
rightCollapsedStorageKey="ptd-right-open"
|
||||||
|
headerCenter={!s?.enabled ? (
|
||||||
|
<span className="bps-header-status-text" role="status">
|
||||||
|
모의투자가 꺼져 있습니다. 설정에서 활성화하세요.
|
||||||
|
</span>
|
||||||
|
) : undefined}
|
||||||
|
headerCenterMode="between"
|
||||||
headerActions={(
|
headerActions={(
|
||||||
<>
|
<>
|
||||||
<button type="button" className="bps-btn bps-btn--ghost" onClick={() => void loadData()}>새로고침</button>
|
<button type="button" className="bps-btn bps-btn--ghost" onClick={() => void loadData()}>새로고침</button>
|
||||||
<button type="button" className="bps-btn bps-btn--danger" onClick={() => void handleReset()}>계좌 초기화</button>
|
<button type="button" className="bps-btn bps-btn--danger" onClick={() => void handleReset()}>계좌 초기화</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
banner={!s?.enabled ? (
|
|
||||||
<div className="bps-banner bps-banner--warn">모의투자가 꺼져 있습니다. 설정에서 활성화하세요.</div>
|
|
||||||
) : undefined}
|
|
||||||
leftTabs={leftTabs}
|
leftTabs={leftTabs}
|
||||||
left={leftTab === 'strategy' ? (
|
left={leftTab === 'strategy' ? (
|
||||||
<PaperLeftStrategyTab
|
<PaperLeftStrategyTab
|
||||||
|
|||||||
@@ -268,9 +268,6 @@ const TradeOrderPanel: React.FC<TradeOrderPanelProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={`top-panel top-panel--upbit top-panel--${side}`}>
|
<div className={`top-panel top-panel--upbit top-panel--${side}`}>
|
||||||
<div className="top-panel-fields">
|
<div className="top-panel-fields">
|
||||||
{paperTradingEnabled && !paperAutoTradeEnabled && showSymbolField && (
|
|
||||||
<p className="top-paper-hint">자동매매 OFF · 수동 주문만 모의 계좌에 체결됩니다.</p>
|
|
||||||
)}
|
|
||||||
{showSymbolField && (
|
{showSymbolField && (
|
||||||
<div className="top-field">
|
<div className="top-field">
|
||||||
<label className="top-label">종목</label>
|
<label className="top-label">종목</label>
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export interface BuilderPageShellProps {
|
|||||||
headerActions?: React.ReactNode;
|
headerActions?: React.ReactNode;
|
||||||
/** 타이틀 우측·중앙 그리드 시작선 정렬 (가상투자 등) */
|
/** 타이틀 우측·중앙 그리드 시작선 정렬 (가상투자 등) */
|
||||||
headerCenter?: React.ReactNode;
|
headerCenter?: React.ReactNode;
|
||||||
|
/** grid: 좌패널폭 기준 3열 / between: 타이틀·버튼 사이 중앙 (모의투자 안내 등) */
|
||||||
|
headerCenterMode?: 'grid' | 'between';
|
||||||
pageClassName?: string;
|
pageClassName?: string;
|
||||||
banner?: React.ReactNode;
|
banner?: React.ReactNode;
|
||||||
leftTitle?: string;
|
leftTitle?: string;
|
||||||
@@ -103,6 +105,7 @@ export default function BuilderPageShell({
|
|||||||
subtitle,
|
subtitle,
|
||||||
headerActions,
|
headerActions,
|
||||||
headerCenter,
|
headerCenter,
|
||||||
|
headerCenterMode = 'grid',
|
||||||
pageClassName,
|
pageClassName,
|
||||||
banner,
|
banner,
|
||||||
leftTitle,
|
leftTitle,
|
||||||
@@ -252,12 +255,24 @@ export default function BuilderPageShell({
|
|||||||
className={`bps-page se-page se-page--${theme}${pageClassName ? ` ${pageClassName}` : ''}`}
|
className={`bps-page se-page se-page--${theme}${pageClassName ? ` ${pageClassName}` : ''}`}
|
||||||
style={pageStyle}
|
style={pageStyle}
|
||||||
>
|
>
|
||||||
<header className={`bps-header${headerCenter ? ' bps-header--vtd' : ''}`}>
|
<header
|
||||||
|
className={[
|
||||||
|
'bps-header',
|
||||||
|
headerCenter && headerCenterMode === 'between' ? 'bps-header--between' : '',
|
||||||
|
headerCenter && headerCenterMode === 'grid' ? 'bps-header--vtd' : '',
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
|
>
|
||||||
<div className="bps-header-left">
|
<div className="bps-header-left">
|
||||||
<h1 className="bps-title">{title}</h1>
|
<h1 className="bps-title">{title}</h1>
|
||||||
{subtitle && <span className="bps-subtitle">{subtitle}</span>}
|
{subtitle && <span className="bps-subtitle">{subtitle}</span>}
|
||||||
</div>
|
</div>
|
||||||
{headerCenter ? (
|
{headerCenter ? (
|
||||||
|
headerCenterMode === 'between' ? (
|
||||||
|
<>
|
||||||
|
<div className="bps-header-center">{headerCenter}</div>
|
||||||
|
{headerActions && <div className="bps-header-actions">{headerActions}</div>}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="bps-header-center">{headerCenter}</div>
|
<div className="bps-header-center">{headerCenter}</div>
|
||||||
{headerActions && (
|
{headerActions && (
|
||||||
@@ -267,6 +282,7 @@ export default function BuilderPageShell({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
headerActions && <div className="bps-header-actions">{headerActions}</div>
|
headerActions && <div className="bps-header-actions">{headerActions}</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,57 +26,101 @@ const PaperAllocationTable: React.FC<Props> = ({
|
|||||||
}, [onChanged]);
|
}, [onChanged]);
|
||||||
|
|
||||||
if (!allocations.length) {
|
if (!allocations.length) {
|
||||||
return <p className="ptd-muted">등록된 종목 한도가 없습니다. 가상투자 대상 추가 시 동기화됩니다.</p>;
|
return (
|
||||||
|
<div className="ptd-item-card-section ptd-alloc-card-section">
|
||||||
|
<div className="vtd-target-list-head">
|
||||||
|
<span>종목별 한도</span>
|
||||||
|
<span className="vtd-target-count">0종목</span>
|
||||||
|
</div>
|
||||||
|
<p className="ptd-muted ptd-item-card-empty">등록된 종목 한도가 없습니다. 가상투자 대상 추가 시 동기화됩니다.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ptd-alloc-table-wrap">
|
<div className="ptd-item-card-section ptd-alloc-card-section">
|
||||||
<table className="ptd-table ptd-alloc-table">
|
<div className="vtd-target-list-head">
|
||||||
<thead>
|
<span>종목별 한도</span>
|
||||||
<tr>
|
<span className="vtd-target-count">{allocations.length}종목</span>
|
||||||
<th>종목</th>
|
</div>
|
||||||
<th>비중</th>
|
<div className="ptd-item-card-scroll">
|
||||||
<th>한도</th>
|
<div className="vtd-target-list ptd-item-card-list">
|
||||||
<th>투입</th>
|
|
||||||
<th>평가</th>
|
|
||||||
<th>수익률</th>
|
|
||||||
<th>활성</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{allocations.map(row => {
|
{allocations.map(row => {
|
||||||
const ret = row.symbolReturnPct ?? 0;
|
const ret = row.symbolReturnPct ?? 0;
|
||||||
const selected = row.symbol === selectedMarket;
|
const selected = row.symbol === selectedMarket;
|
||||||
|
const name = row.koreanName || coinCode(row.symbol);
|
||||||
|
const code = coinCode(row.symbol);
|
||||||
|
const up = ret >= 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<div
|
||||||
key={row.symbol}
|
key={row.symbol}
|
||||||
className={selected ? 'ptd-alloc-row--selected' : ''}
|
className={[
|
||||||
onClick={() => onSelectMarket?.(row.symbol)}
|
'vtd-target-item',
|
||||||
|
'ptd-item-card',
|
||||||
|
'ptd-alloc-card',
|
||||||
|
selected ? 'vtd-target-item--active' : '',
|
||||||
|
row.isActive === false ? 'ptd-alloc-card--inactive' : '',
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={e => { if (e.key === 'Enter') onSelectMarket?.(row.symbol); }}
|
onClick={() => onSelectMarket?.(row.symbol)}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
onSelectMarket?.(row.symbol);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<td>{row.koreanName || coinCode(row.symbol)}</td>
|
<div className="vtd-target-item-main">
|
||||||
<td>{(row.weightPct ?? 0).toFixed(1)}%</td>
|
<div className="vtd-target-names">
|
||||||
<td>{fmtKrw(row.maxInvestKrw)}</td>
|
<span className="vtd-target-ko">{name}</span>
|
||||||
<td>{fmtKrw(row.investedKrw ?? 0)}</td>
|
<span className="vtd-target-en">{code}/KRW</span>
|
||||||
<td>{fmtKrw(row.evalAmount ?? 0)}</td>
|
</div>
|
||||||
<td className={ret >= 0 ? 'up' : 'down'}>{ret >= 0 ? '+' : ''}{ret.toFixed(2)}%</td>
|
<div className="vtd-target-item-actions">
|
||||||
<td onClick={e => e.stopPropagation()}>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`ptd-alloc-toggle${row.isActive !== false ? ' ptd-alloc-toggle--on' : ''}`}
|
className={`ptd-alloc-toggle${row.isActive !== false ? ' ptd-alloc-toggle--on' : ''}`}
|
||||||
onClick={() => void toggleActive(row)}
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
void toggleActive(row);
|
||||||
|
}}
|
||||||
title={row.isActive !== false ? '신규 매수 허용' : '신규 매수 차단'}
|
title={row.isActive !== false ? '신규 매수 허용' : '신규 매수 차단'}
|
||||||
>
|
>
|
||||||
{row.isActive !== false ? 'ON' : 'OFF'}
|
{row.isActive !== false ? 'ON' : 'OFF'}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
|
|
||||||
|
<div className="ptd-item-card-metrics">
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">비중</span>
|
||||||
|
<span className="vtd-trade-meta-value">{(row.weightPct ?? 0).toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">한도</span>
|
||||||
|
<span className="vtd-trade-meta-value">{fmtKrw(row.maxInvestKrw)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">투입</span>
|
||||||
|
<span className="vtd-trade-meta-value">{fmtKrw(row.investedKrw ?? 0)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">평가</span>
|
||||||
|
<span className="vtd-trade-meta-value">{fmtKrw(row.evalAmount ?? 0)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">수익률</span>
|
||||||
|
<span className={`vtd-trade-meta-value ${up ? 'vtd-target-up' : 'vtd-target-dn'}`}>
|
||||||
|
{ret >= 0 ? '+' : ''}{ret.toFixed(2)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ interface Props {
|
|||||||
onOpenSettings?: () => void;
|
onOpenSettings?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SettingsFieldRow: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => (
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">{label}</span>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const PaperLeftSettingsTab: React.FC<Props> = ({ onSettingsSaved, onAccountReset, onOpenSettings }) => {
|
const PaperLeftSettingsTab: React.FC<Props> = ({ onSettingsSaved, onAccountReset, onOpenSettings }) => {
|
||||||
const { defaults, save, isLoaded } = useAppSettings();
|
const { defaults, save, isLoaded } = useAppSettings();
|
||||||
|
|
||||||
@@ -28,101 +35,100 @@ const PaperLeftSettingsTab: React.FC<Props> = ({ onSettingsSaved, onAccountReset
|
|||||||
};
|
};
|
||||||
|
|
||||||
const top = (
|
const top = (
|
||||||
<>
|
<div className="ptd-settings-panel">
|
||||||
{onOpenSettings && (
|
{onOpenSettings && (
|
||||||
<button type="button" className="bps-btn bps-btn--ghost ptd-left-settings-link" onClick={onOpenSettings}>
|
<button type="button" className="bps-btn bps-btn--ghost ptd-settings-link" onClick={onOpenSettings}>
|
||||||
설정 → 모의투자에서 전체 관리
|
설정 → 모의투자에서 전체 관리
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div className="ptd-left-section">
|
<div className="vtd-target-item ptd-item-card ptd-settings-card">
|
||||||
<label className="ptd-left-row">
|
<div className="ptd-item-card-metrics">
|
||||||
<span>모의투자 활성화</span>
|
<SettingsFieldRow label="모의투자 활성화">
|
||||||
|
<label className="vtd-trade-meta-value ptd-settings-value-box ptd-settings-value-box--check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={defaults.paperTradingEnabled}
|
checked={defaults.paperTradingEnabled}
|
||||||
onChange={e => patch({ paperTradingEnabled: e.target.checked })}
|
onChange={e => patch({ paperTradingEnabled: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</SettingsFieldRow>
|
||||||
<div className="ptd-left-section">
|
<SettingsFieldRow label="자동매매">
|
||||||
<label className="ptd-left-row">
|
<label className="vtd-trade-meta-value ptd-settings-value-box ptd-settings-value-box--check">
|
||||||
<span>자동매매</span>
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={defaults.paperAutoTradeEnabled}
|
checked={defaults.paperAutoTradeEnabled}
|
||||||
onChange={e => patch({ paperAutoTradeEnabled: e.target.checked })}
|
onChange={e => patch({ paperAutoTradeEnabled: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="ptd-left-field">
|
</SettingsFieldRow>
|
||||||
<span>매수 예산 (%)</span>
|
<SettingsFieldRow label="매수 예산 (%)">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="ptd-left-input"
|
className="vtd-trade-meta-value ptd-settings-input"
|
||||||
min={1}
|
min={1}
|
||||||
max={100}
|
max={100}
|
||||||
value={defaults.paperAutoTradeBudgetPct}
|
value={defaults.paperAutoTradeBudgetPct}
|
||||||
disabled={!defaults.paperAutoTradeEnabled}
|
disabled={!defaults.paperAutoTradeEnabled}
|
||||||
onChange={e => patch({ paperAutoTradeBudgetPct: Number(e.target.value) })}
|
onChange={e => patch({ paperAutoTradeBudgetPct: Number(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</SettingsFieldRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const bottom = (
|
const bottom = (
|
||||||
<>
|
<div className="ptd-settings-panel ptd-settings-panel--account">
|
||||||
<div className="ptd-left-section">
|
<div className="vtd-target-item ptd-item-card ptd-settings-card">
|
||||||
<label className="ptd-left-field">
|
<div className="ptd-item-card-metrics">
|
||||||
<span>초기 자본 (KRW)</span>
|
<SettingsFieldRow label="초기 자본 (KRW)">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="ptd-left-input"
|
className="vtd-trade-meta-value ptd-settings-input"
|
||||||
min={100000}
|
min={100000}
|
||||||
step={100000}
|
step={100000}
|
||||||
value={defaults.paperInitialCapital}
|
value={defaults.paperInitialCapital}
|
||||||
onChange={e => patch({ paperInitialCapital: Number(e.target.value) })}
|
onChange={e => patch({ paperInitialCapital: Number(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</SettingsFieldRow>
|
||||||
<label className="ptd-left-field">
|
<SettingsFieldRow label="수수료율 (%)">
|
||||||
<span>수수료율 (%)</span>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="ptd-left-input"
|
className="vtd-trade-meta-value ptd-settings-input"
|
||||||
min={0}
|
min={0}
|
||||||
max={1}
|
max={1}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
value={defaults.paperFeeRatePct}
|
value={defaults.paperFeeRatePct}
|
||||||
onChange={e => patch({ paperFeeRatePct: Number(e.target.value) })}
|
onChange={e => patch({ paperFeeRatePct: Number(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</SettingsFieldRow>
|
||||||
<label className="ptd-left-field">
|
<SettingsFieldRow label="슬리피지 (%)">
|
||||||
<span>슬리피지 (%)</span>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="ptd-left-input"
|
className="vtd-trade-meta-value ptd-settings-input"
|
||||||
min={0}
|
min={0}
|
||||||
max={5}
|
max={5}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
value={defaults.paperSlippagePct}
|
value={defaults.paperSlippagePct}
|
||||||
onChange={e => patch({ paperSlippagePct: Number(e.target.value) })}
|
onChange={e => patch({ paperSlippagePct: Number(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</SettingsFieldRow>
|
||||||
<label className="ptd-left-field">
|
<SettingsFieldRow label="최소 주문 (KRW)">
|
||||||
<span>최소 주문 (KRW)</span>
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="ptd-left-input"
|
className="vtd-trade-meta-value ptd-settings-input"
|
||||||
min={1000}
|
min={1000}
|
||||||
step={1000}
|
step={1000}
|
||||||
value={defaults.paperMinOrderKrw}
|
value={defaults.paperMinOrderKrw}
|
||||||
onChange={e => patch({ paperMinOrderKrw: Number(e.target.value) })}
|
onChange={e => patch({ paperMinOrderKrw: Number(e.target.value) })}
|
||||||
/>
|
/>
|
||||||
</label>
|
</SettingsFieldRow>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" className="bps-btn bps-btn--danger ptd-left-reset" onClick={() => void handleReset()}>
|
</div>
|
||||||
|
<button type="button" className="bps-btn bps-btn--danger ptd-settings-reset" onClick={() => void handleReset()}>
|
||||||
계좌 초기화
|
계좌 초기화
|
||||||
</button>
|
</button>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -91,33 +91,90 @@ const PaperLeftStrategyTab: React.FC<Props> = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const positions = summary?.positions ?? [];
|
||||||
|
|
||||||
const bottom = (
|
const bottom = (
|
||||||
<table className="ptd-table ptd-table--compact">
|
<div className="ptd-item-card-section ptd-holdings-card-section">
|
||||||
<thead>
|
<div className="vtd-target-list-head">
|
||||||
<tr><th>자산</th><th>수량</th><th>손익</th></tr>
|
<span>보유 목록</span>
|
||||||
</thead>
|
<span className="vtd-target-count">{positions.length}종목</span>
|
||||||
<tbody>
|
</div>
|
||||||
{(summary?.positions ?? []).length === 0 ? (
|
{positions.length === 0 ? (
|
||||||
<tr><td colSpan={3} className="ptd-muted">보유 없음</td></tr>
|
<p className="ptd-muted ptd-item-card-empty">보유 종목이 없습니다.</p>
|
||||||
) : summary!.positions.map(p => {
|
) : (
|
||||||
|
<div className="ptd-item-card-scroll">
|
||||||
|
<div className="vtd-target-list ptd-item-card-list">
|
||||||
|
{positions.map(p => {
|
||||||
const code = p.symbol.replace(/^KRW-/, '');
|
const code = p.symbol.replace(/^KRW-/, '');
|
||||||
|
const name = p.koreanName || code;
|
||||||
const up = (p.profitLoss ?? 0) >= 0;
|
const up = (p.profitLoss ?? 0) >= 0;
|
||||||
|
const selected = market === p.symbol;
|
||||||
|
const retPct = p.profitLossPct;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<div
|
||||||
key={p.symbol}
|
key={p.symbol}
|
||||||
className={market === p.symbol ? 'ptd-row--sel' : ''}
|
className={[
|
||||||
|
'vtd-target-item',
|
||||||
|
'ptd-item-card',
|
||||||
|
'ptd-holding-card',
|
||||||
|
selected ? 'vtd-target-item--active' : '',
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => onMarketSelect?.(p.symbol)}
|
onClick={() => onMarketSelect?.(p.symbol)}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
onMarketSelect?.(p.symbol);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<td>{code}</td>
|
<div className="vtd-target-item-main">
|
||||||
<td>{p.quantity.toFixed(4)}</td>
|
<div className="vtd-target-names">
|
||||||
<td className={up ? 'up' : 'down'}>
|
<span className="vtd-target-ko">{name}</span>
|
||||||
{p.profitLoss != null ? `${up ? '+' : ''}${fmtKrw(p.profitLoss)}` : '—'}
|
<span className="vtd-target-en">{code}/KRW</span>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
{p.profitLoss != null && (
|
||||||
|
<div className="vtd-target-item-actions">
|
||||||
|
<span className={`vtd-target-price ${up ? 'vtd-target-up' : 'vtd-target-dn'}`}>
|
||||||
|
{up ? '+' : ''}{fmtKrw(p.profitLoss)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ptd-item-card-metrics">
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">보유수량</span>
|
||||||
|
<span className="vtd-trade-meta-value">{p.quantity.toFixed(4)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">평단가</span>
|
||||||
|
<span className="vtd-trade-meta-value">{fmtKrw(p.avgPrice)}</span>
|
||||||
|
</div>
|
||||||
|
{p.evalAmount != null && (
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">평가금액</span>
|
||||||
|
<span className="vtd-trade-meta-value">{fmtKrw(p.evalAmount)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{retPct != null && (
|
||||||
|
<div className="vtd-target-strategy-field">
|
||||||
|
<span className="vtd-target-strategy-label">수익률</span>
|
||||||
|
<span className={`vtd-trade-meta-value ${retPct >= 0 ? 'vtd-target-up' : 'vtd-target-dn'}`}>
|
||||||
|
{retPct >= 0 ? '+' : ''}{retPct.toFixed(2)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,21 +2,42 @@ import React from 'react';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
topTitle?: string;
|
topTitle?: string;
|
||||||
|
/** 타이틀바 우측 (매수·매도 안내 등) */
|
||||||
|
topTitleAside?: React.ReactNode;
|
||||||
bottomTitle?: string;
|
bottomTitle?: string;
|
||||||
|
bottomTitleAside?: React.ReactNode;
|
||||||
top: React.ReactNode;
|
top: React.ReactNode;
|
||||||
bottom: React.ReactNode;
|
bottom: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 좌·우 패널 상·하단 카드 분리 */
|
/** 좌·우 패널 상·하단 카드 분리 */
|
||||||
const PaperSplitPanel: React.FC<Props> = ({ topTitle, bottomTitle, top, bottom, className = '' }) => (
|
const PaperSplitPanel: React.FC<Props> = ({
|
||||||
|
topTitle,
|
||||||
|
topTitleAside,
|
||||||
|
bottomTitle,
|
||||||
|
bottomTitleAside,
|
||||||
|
top,
|
||||||
|
bottom,
|
||||||
|
className = '',
|
||||||
|
}) => (
|
||||||
<div className={`ptd-split-panel${className ? ` ${className}` : ''}`}>
|
<div className={`ptd-split-panel${className ? ` ${className}` : ''}`}>
|
||||||
<section className="ptd-split-card ptd-split-card--top">
|
<section className="ptd-split-card ptd-split-card--top">
|
||||||
{topTitle && <div className="ptd-split-card-head">{topTitle}</div>}
|
{topTitle && (
|
||||||
|
<div className="ptd-split-card-head">
|
||||||
|
<span className="ptd-split-card-head-label">{topTitle}</span>
|
||||||
|
{topTitleAside}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="ptd-split-card-body">{top}</div>
|
<div className="ptd-split-card-body">{top}</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="ptd-split-card ptd-split-card--bottom">
|
<section className="ptd-split-card ptd-split-card--bottom">
|
||||||
{bottomTitle && <div className="ptd-split-card-head">{bottomTitle}</div>}
|
{bottomTitle && (
|
||||||
|
<div className="ptd-split-card-head">
|
||||||
|
<span className="ptd-split-card-head-label">{bottomTitle}</span>
|
||||||
|
{bottomTitleAside}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="ptd-split-card-body">{bottom}</div>
|
<div className="ptd-split-card-body">{bottom}</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -45,11 +45,20 @@ const TradeSplitOrderPanel: React.FC<TradeSplitOrderPanelProps> = ({
|
|||||||
const resolvedBuyFill = fillBuy ?? (fillRequest?.side === 'buy' ? fillRequest : null);
|
const resolvedBuyFill = fillBuy ?? (fillRequest?.side === 'buy' ? fillRequest : null);
|
||||||
const resolvedSellFill = fillSell ?? (fillRequest?.side === 'sell' ? fillRequest : null);
|
const resolvedSellFill = fillSell ?? (fillRequest?.side === 'sell' ? fillRequest : null);
|
||||||
|
|
||||||
|
const paperAutoOffHint =
|
||||||
|
paperTradingEnabled && !paperAutoTradeEnabled ? (
|
||||||
|
<span className="ptd-split-card-head-hint" title="자동매매 OFF · 수동 주문만 모의 계좌에 체결됩니다.">
|
||||||
|
자동매매 OFF · 수동 주문만 모의 계좌에 체결됩니다.
|
||||||
|
</span>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaperSplitPanel
|
<PaperSplitPanel
|
||||||
className={`ptd-split-panel--right${className ? ` ${className}` : ''}`}
|
className={`ptd-split-panel--right${className ? ` ${className}` : ''}`}
|
||||||
topTitle="매수"
|
topTitle="매수"
|
||||||
|
topTitleAside={paperAutoOffHint}
|
||||||
bottomTitle="매도"
|
bottomTitle="매도"
|
||||||
|
bottomTitleAside={paperAutoOffHint}
|
||||||
top={(
|
top={(
|
||||||
<TradeBuyOrderPanel
|
<TradeBuyOrderPanel
|
||||||
market={market}
|
market={market}
|
||||||
|
|||||||
@@ -113,6 +113,42 @@
|
|||||||
color: #fbbf24;
|
color: #fbbf24;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 타이틀·우측 버튼 사이 중앙 안내 (텍스트만, 1줄) */
|
||||||
|
.bps-header--between {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 18px;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-header--between .bps-header-left {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-header--between .bps-header-center {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-header--between .bps-header-actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-header-status-text {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #fbbf24;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.bps-body {
|
.bps-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -138,6 +138,48 @@
|
|||||||
color: var(--text2, var(--se-text-muted));
|
color: var(--text2, var(--se-text-muted));
|
||||||
border-bottom: 1px solid var(--sep, var(--se-border));
|
border-bottom: 1px solid var(--sep, var(--se-border));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 모의투자 좌측(전략·자산 / 보유 종목 등): 우측 매수·매도 타이틀바와 동일 높이·연한 그라데이션 */
|
||||||
|
.ptd-split-panel--left .ptd-split-card--top {
|
||||||
|
border-color: color-mix(in srgb, var(--gc-trade-buy) 35%, var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-split-panel--left .ptd-split-card--bottom {
|
||||||
|
border-color: color-mix(in srgb, var(--gc-trade-sell) 35%, var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-split-panel--left .ptd-split-card-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 12px 14px 11px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-split-panel--left .ptd-split-card--top .ptd-split-card-head {
|
||||||
|
color: var(--gc-trade-buy);
|
||||||
|
border-bottom-color: color-mix(in srgb, var(--gc-trade-buy) 30%, var(--border));
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
color-mix(in srgb, var(--gc-trade-buy) 24%, var(--bg2)) 0%,
|
||||||
|
color-mix(in srgb, var(--gc-trade-buy) 10%, var(--bg2)) 50%,
|
||||||
|
var(--bg2) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-split-panel--left .ptd-split-card--bottom .ptd-split-card-head {
|
||||||
|
color: var(--gc-trade-sell);
|
||||||
|
border-bottom-color: color-mix(in srgb, var(--gc-trade-sell) 30%, var(--border));
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
color-mix(in srgb, var(--gc-trade-sell) 24%, var(--bg2)) 0%,
|
||||||
|
color-mix(in srgb, var(--gc-trade-sell) 10%, var(--bg2)) 50%,
|
||||||
|
var(--bg2) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
.ptd-split-card-body {
|
.ptd-split-card-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -232,26 +274,279 @@
|
|||||||
}
|
}
|
||||||
.ptd-left-hint {
|
.ptd-left-hint {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
.ptd-left-status {
|
.ptd-left-status {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
color: var(--text3);
|
line-height: 1.45;
|
||||||
padding: 6px 8px;
|
color: var(--text2);
|
||||||
border-radius: 8px;
|
padding: 0;
|
||||||
background: var(--bg3);
|
border-radius: 0;
|
||||||
|
background: none;
|
||||||
}
|
}
|
||||||
.ptd-left-status--on {
|
.ptd-left-status--on {
|
||||||
color: #22c55e;
|
color: #22c55e;
|
||||||
background: rgba(34, 197, 94, 0.1);
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-left-hint,
|
||||||
|
.bps-page--ptd .ptd-left-status {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
/* 모의투자 — 보유·투자금 카드 목록 */
|
||||||
|
.ptd-item-card-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-item-card-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-item-card-list {
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-item-card-empty {
|
||||||
|
margin: 0;
|
||||||
|
padding: 12px 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-item-card-metrics {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-item-card-metrics .vtd-target-strategy-field {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-alloc-card-section {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-holdings-card-section {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-split-panel--left .ptd-split-card-body:has(.ptd-holdings-card-section) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-alloc-card--inactive {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-alloc-card--inactive .vtd-target-ko {
|
||||||
|
color: var(--text3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-holding-card .vtd-target-item-actions .vtd-target-price {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 모의투자 목록 — 행·글자 약간 확대 (보유 종목·투자금 표/카드) */
|
||||||
|
.bps-page--ptd .ptd-table {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-table th,
|
||||||
|
.bps-page--ptd .ptd-table td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-table--compact {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-table--compact th,
|
||||||
|
.bps-page--ptd .ptd-table--compact td {
|
||||||
|
padding: 9px 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-split-panel--left .ptd-split-card-body > .ptd-table {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-split-panel--left .ptd-split-card-body > .ptd-table th,
|
||||||
|
.bps-page--ptd .ptd-split-panel--left .ptd-split-card-body > .ptd-table td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-section .vtd-target-list-head {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-section .vtd-target-count {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-list.vtd-target-list {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-list .vtd-target-item {
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-list .vtd-target-ko {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-list .vtd-target-en {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-metrics {
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-metrics .vtd-target-strategy-field {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-metrics .vtd-target-strategy-label,
|
||||||
|
.bps-page--ptd .ptd-item-card-metrics .vtd-trade-meta-value {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-metrics .vtd-trade-meta-value {
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-holding-card .vtd-target-item-actions .vtd-target-price {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-alloc-toggle {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-item-card-empty {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 14px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-invest-card .ptd-alloc-card-section {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ptd-left-reset {
|
.ptd-left-reset {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 설정 탭 — 보유 목록 카드와 동일 라벨·값 박스 행 */
|
||||||
|
.ptd-settings-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-panel--account {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-link {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-card {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-card .ptd-item-card-metrics {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-card .vtd-target-strategy-field {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-input:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-value-box--check {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-value-box--check input[type='checkbox'] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--accent, #3f7ef5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-settings-reset {
|
||||||
|
width: 100%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ptd-split-panel--left .ptd-split-card-body:has(.ptd-settings-panel) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 8px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bps-page--ptd .ptd-settings-card .vtd-target-strategy-label,
|
||||||
|
.bps-page--ptd .ptd-settings-card .ptd-settings-input {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.bps-right-body .ptd-right-body,
|
.bps-right-body .ptd-right-body,
|
||||||
.trade-right-panel .bps-right-body .ptd-right-body {
|
.trade-right-panel .bps-right-body .ptd-right-body {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
|||||||
@@ -95,6 +95,8 @@
|
|||||||
.trade-right-panel .ptd-split-panel--right .ptd-split-card-head {
|
.trade-right-panel .ptd-split-panel--right .ptd-split-card-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
min-height: 42px;
|
min-height: 42px;
|
||||||
padding: 12px 14px 11px;
|
padding: 12px 14px 11px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -103,6 +105,28 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trade-right-panel .ptd-split-panel--right .ptd-split-card-head-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trade-right-panel .ptd-split-panel--right .ptd-split-card-head-hint {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: var(--text3);
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.trade-right-panel .ptd-split-panel--right .ptd-split-card--top .ptd-split-card-head {
|
.trade-right-panel .ptd-split-panel--right .ptd-split-card--top .ptd-split-card-head {
|
||||||
color: var(--gc-trade-buy);
|
color: var(--gc-trade-buy);
|
||||||
border-bottom-color: color-mix(in srgb, var(--gc-trade-buy) 30%, var(--border));
|
border-bottom-color: color-mix(in srgb, var(--gc-trade-buy) 30%, var(--border));
|
||||||
|
|||||||
@@ -2807,7 +2807,17 @@
|
|||||||
}
|
}
|
||||||
.ptd-tabs--center {
|
.ptd-tabs--center {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ptd-tabs--center .ptd-tab {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px 18px;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.ptd-invest-card {
|
.ptd-invest-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2860,40 +2870,49 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.ptd-invest-kpi-cell {
|
.ptd-invest-kpi-cell {
|
||||||
padding: 8px 10px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: var(--gc-surface-2, rgba(255,255,255,0.04));
|
background: var(--gc-surface-2, rgba(255,255,255,0.04));
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.ptd-invest-kpi-cell--up .ptd-invest-kpi-value { color: var(--gc-trade-buy, #ef5350); }
|
.ptd-invest-kpi-cell--up .ptd-invest-kpi-value { color: var(--gc-trade-buy, #ef5350); }
|
||||||
.ptd-invest-kpi-cell--down .ptd-invest-kpi-value { color: var(--gc-trade-sell, #4dabf7); }
|
.ptd-invest-kpi-cell--down .ptd-invest-kpi-value { color: var(--gc-trade-sell, #4dabf7); }
|
||||||
.ptd-invest-kpi-label {
|
.ptd-invest-kpi-label {
|
||||||
display: block;
|
flex-shrink: 0;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
opacity: 0.7;
|
font-weight: 600;
|
||||||
|
opacity: 0.75;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.ptd-invest-kpi-value {
|
.ptd-invest-kpi-value {
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.ptd-alloc-table-wrap {
|
|
||||||
overflow: auto;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-width: 0;
|
||||||
}
|
font-size: 14px;
|
||||||
.ptd-alloc-row--selected {
|
font-weight: 600;
|
||||||
background: rgba(255, 255, 255, 0.06);
|
text-align: right;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
/* 투자금 관리: PaperAllocationTable 카드 목록 — paperDashboard.css .ptd-alloc-card-section */
|
||||||
.ptd-alloc-toggle {
|
.ptd-alloc-toggle {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid rgba(255,255,255,0.15);
|
border: 1px solid color-mix(in srgb, var(--gc-trade-buy, #ef5350) 50%, transparent);
|
||||||
background: transparent;
|
background: color-mix(in srgb, var(--gc-trade-buy, #ef5350) 12%, transparent);
|
||||||
|
color: var(--gc-trade-buy, #ef5350);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.ptd-alloc-toggle--on {
|
.ptd-alloc-toggle--on {
|
||||||
border-color: var(--gc-trade-buy, #ef5350);
|
border-color: color-mix(in srgb, #b8f397 65%, var(--se-border, var(--border)));
|
||||||
color: var(--gc-trade-buy, #ef5350);
|
color: #b8f397;
|
||||||
|
background: color-mix(in srgb, #a3e635 20%, transparent);
|
||||||
}
|
}
|
||||||
.ptd-footer-stack {
|
.ptd-footer-stack {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user