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 AddHoldingForm from './portfolio/AddHoldingForm';
|
||||||
import PortfolioSummary from './portfolio/PortfolioSummary';
|
import PortfolioSummary from './portfolio/PortfolioSummary';
|
||||||
import AssetHistoryChart from './portfolio/AssetHistoryChart';
|
import AssetHistoryChart from './portfolio/AssetHistoryChart';
|
||||||
|
import CashPanel from './portfolio/CashPanel';
|
||||||
|
|
||||||
const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
||||||
<>
|
<>
|
||||||
@@ -53,131 +54,7 @@ const PortfolioTab = ({ pf, asset, handleSell, handleSaveSnapshot }) => (
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 예수금 패널 */}
|
{/* 예수금 패널 */}
|
||||||
<section className="stock-panel stock-panel--wide">
|
<CashPanel pf={pf} />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Broker cards stacked */}
|
{/* Broker cards stacked */}
|
||||||
{pf.brokerGroups.map(([broker, items]) => {
|
{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