refactor(portfolio): 예수금 패널 CashPanel 컴포넌트 추출
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHXzpsZQxKG9hQmNRfZjRS
This commit is contained in:
@@ -5,6 +5,7 @@ import PriceSessionBadge from './portfolio/PriceSessionBadge';
|
||||
import AddHoldingForm from './portfolio/AddHoldingForm';
|
||||
import PortfolioSummary from './portfolio/PortfolioSummary';
|
||||
import AssetHistoryChart from './portfolio/AssetHistoryChart';
|
||||
import CashPanel from './portfolio/CashPanel';
|
||||
|
||||
const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
||||
<>
|
||||
@@ -53,131 +54,7 @@ const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
||||
</section>
|
||||
|
||||
{/* 예수금 패널 */}
|
||||
<section className="stock-panel stock-panel--wide">
|
||||
<div className="stock-panel__head">
|
||||
<div>
|
||||
<p className="stock-panel__eyebrow">예수금 관리</p>
|
||||
<h3>증권사별 예수금</h3>
|
||||
<p className="stock-panel__sub">
|
||||
증권사별 예수금을 입력하면 총 자산에 자동 반영됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{pf.cashList.length > 0 && (
|
||||
<div className="pf-cash-table">
|
||||
{pf.cashList.map((item) => {
|
||||
const isEditing = pf.cashEditingBroker === item.broker;
|
||||
return (
|
||||
<div key={item.id ?? item.broker} className="pf-cash-row">
|
||||
<span className="pf-cash-broker">{item.broker}</span>
|
||||
{isEditing ? (
|
||||
<input
|
||||
className="pf-cash-edit-input"
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={pf.cashEditingValue}
|
||||
onChange={(e) => pf.setCashEditingValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') pf.handleCashInlineSave(item.broker);
|
||||
if (e.key === 'Escape') pf.handleCashInlineCancel();
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<strong className="pf-cash-amount">
|
||||
{formatNumber(item.cash)}원
|
||||
</strong>
|
||||
)}
|
||||
<span className="pf-cash-date">
|
||||
{item.updated_at
|
||||
? new Date(item.updated_at).toLocaleDateString('ko-KR')
|
||||
: ''}
|
||||
</span>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<button
|
||||
className="button primary small"
|
||||
onClick={() => pf.handleCashInlineSave(item.broker)}
|
||||
disabled={pf.cashEditSaving}
|
||||
>
|
||||
{pf.cashEditSaving ? '저장 중' : '저장'}
|
||||
</button>
|
||||
<button
|
||||
className="button ghost small"
|
||||
onClick={pf.handleCashInlineCancel}
|
||||
disabled={pf.cashEditSaving}
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="button ghost small"
|
||||
onClick={() => pf.handleCashInlineEdit(item)}
|
||||
title="수정"
|
||||
>
|
||||
✏️
|
||||
</button>
|
||||
<button
|
||||
className="button ghost small pf-btn-danger"
|
||||
onClick={() => pf.handleCashDelete(item.broker)}
|
||||
title="삭제"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{pf.cashList.length === 0 && (
|
||||
<p className="stock-empty" style={{ fontSize: 13 }}>
|
||||
등록된 예수금이 없습니다.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<form className="pf-cash-form" onSubmit={pf.handleCashSave}>
|
||||
<label>
|
||||
증권사명
|
||||
<input
|
||||
type="text"
|
||||
value={pf.cashForm.broker}
|
||||
onChange={(e) =>
|
||||
pf.setCashForm((p) => ({ ...p, broker: e.target.value }))
|
||||
}
|
||||
placeholder="KB증권"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
예수금 (원)
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={pf.cashForm.cash}
|
||||
onChange={(e) =>
|
||||
pf.setCashForm((p) => ({ ...p, cash: e.target.value }))
|
||||
}
|
||||
placeholder="1500000"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
className="button primary"
|
||||
type="submit"
|
||||
disabled={pf.cashSaving}
|
||||
>
|
||||
{pf.cashSaving ? '저장 중...' : '저장'}
|
||||
</button>
|
||||
{pf.cashError && <p className="stock-error">{pf.cashError}</p>}
|
||||
</form>
|
||||
</section>
|
||||
<CashPanel pf={pf} />
|
||||
|
||||
{/* Broker cards stacked */}
|
||||
{pf.brokerGroups.map(([broker, items]) => {
|
||||
|
||||
132
src/pages/stock/components/portfolio/CashPanel.jsx
Normal file
132
src/pages/stock/components/portfolio/CashPanel.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import { formatNumber } from '../../stockUtils';
|
||||
|
||||
const CashPanel = ({ pf }) => (
|
||||
<section className="stock-panel stock-panel--wide">
|
||||
<div className="stock-panel__head">
|
||||
<div>
|
||||
<p className="stock-panel__eyebrow">예수금 관리</p>
|
||||
<h3>증권사별 예수금</h3>
|
||||
<p className="stock-panel__sub">
|
||||
증권사별 예수금을 입력하면 총 자산에 자동 반영됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{pf.cashList.length > 0 && (
|
||||
<div className="pf-cash-table">
|
||||
{pf.cashList.map((item) => {
|
||||
const isEditing = pf.cashEditingBroker === item.broker;
|
||||
return (
|
||||
<div key={item.id ?? item.broker} className="pf-cash-row">
|
||||
<span className="pf-cash-broker">{item.broker}</span>
|
||||
{isEditing ? (
|
||||
<input
|
||||
className="pf-cash-edit-input"
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={pf.cashEditingValue}
|
||||
onChange={(e) => pf.setCashEditingValue(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') pf.handleCashInlineSave(item.broker);
|
||||
if (e.key === 'Escape') pf.handleCashInlineCancel();
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
) : (
|
||||
<strong className="pf-cash-amount">
|
||||
{formatNumber(item.cash)}원
|
||||
</strong>
|
||||
)}
|
||||
<span className="pf-cash-date">
|
||||
{item.updated_at
|
||||
? new Date(item.updated_at).toLocaleDateString('ko-KR')
|
||||
: ''}
|
||||
</span>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<button
|
||||
className="button primary small"
|
||||
onClick={() => pf.handleCashInlineSave(item.broker)}
|
||||
disabled={pf.cashEditSaving}
|
||||
>
|
||||
{pf.cashEditSaving ? '저장 중' : '저장'}
|
||||
</button>
|
||||
<button
|
||||
className="button ghost small"
|
||||
onClick={pf.handleCashInlineCancel}
|
||||
disabled={pf.cashEditSaving}
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="button ghost small"
|
||||
onClick={() => pf.handleCashInlineEdit(item)}
|
||||
title="수정"
|
||||
>
|
||||
✏️
|
||||
</button>
|
||||
<button
|
||||
className="button ghost small pf-btn-danger"
|
||||
onClick={() => pf.handleCashDelete(item.broker)}
|
||||
title="삭제"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{pf.cashList.length === 0 && (
|
||||
<p className="stock-empty" style={{ fontSize: 13 }}>
|
||||
등록된 예수금이 없습니다.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<form className="pf-cash-form" onSubmit={pf.handleCashSave}>
|
||||
<label>
|
||||
증권사명
|
||||
<input
|
||||
type="text"
|
||||
value={pf.cashForm.broker}
|
||||
onChange={(e) =>
|
||||
pf.setCashForm((p) => ({ ...p, broker: e.target.value }))
|
||||
}
|
||||
placeholder="KB증권"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
예수금 (원)
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={pf.cashForm.cash}
|
||||
onChange={(e) =>
|
||||
pf.setCashForm((p) => ({ ...p, cash: e.target.value }))
|
||||
}
|
||||
placeholder="1500000"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
className="button primary"
|
||||
type="submit"
|
||||
disabled={pf.cashSaving}
|
||||
>
|
||||
{pf.cashSaving ? '저장 중...' : '저장'}
|
||||
</button>
|
||||
{pf.cashError && <p className="stock-error">{pf.cashError}</p>}
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
|
||||
export default CashPanel;
|
||||
Reference in New Issue
Block a user