Functions.jsx 컴포넌트 분할: 1,583→460줄 (3훅+8컴포넌트+유틸)
- lottoUtils.jsx: 공통 유틸·상수 추출 (Ball, NumberRow, 통계 헬퍼 등) - hooks/useLottoData.js: 핵심 데이터 로드 (최신회차, 통계, 시뮬레이션, 리포트) - hooks/usePurchases.js: 구매 기록 CRUD - hooks/useManualRecommend.js: 수동 추천 + 히스토리 - components/: MetricBlock, FrequencyChart, PerformanceBanner, ConfidenceRing, CombinedRecommendPanel, ReportPanel, PersonalAnalysisPanel, PurchasePanel 분리 - getReport import 누락 버그 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
173
src/pages/lotto/components/PurchasePanel.jsx
Normal file
173
src/pages/lotto/components/PurchasePanel.jsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import React from 'react';
|
||||
import { fmtWon } from '../lottoUtils';
|
||||
|
||||
const PurchasePanel = ({
|
||||
records, stats, loading,
|
||||
formOpen, form, formSaving, formError, editId,
|
||||
onFormOpen, onFormClose, onFormChange, onFormSubmit,
|
||||
onEditStart, onDelete,
|
||||
}) => {
|
||||
const winRate = stats?.total_records > 0
|
||||
? ((stats.prize_count / stats.total_records) * 100).toFixed(1)
|
||||
: '0.0';
|
||||
const netColor = (stats?.net ?? 0) >= 0 ? 'is-pos' : 'is-neg';
|
||||
|
||||
return (
|
||||
<section className="lotto-panel lotto-panel--wide">
|
||||
<div className="lotto-panel__head">
|
||||
<div>
|
||||
<p className="lotto-panel__eyebrow">Purchase Tracker</p>
|
||||
<h3>구매 기록</h3>
|
||||
<p className="lotto-panel__sub">구매 내역 기록 및 수익률 추적</p>
|
||||
</div>
|
||||
<div className="lotto-panel__actions">
|
||||
{loading && <span className="lotto-chip">로딩 중</span>}
|
||||
<button className="button small" onClick={onFormOpen} disabled={formOpen}>
|
||||
+ 추가
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 통계 바 */}
|
||||
{stats && stats.total_records > 0 && (
|
||||
<div className="lotto-purchase-stats">
|
||||
<div className="lotto-purchase-stat">
|
||||
<span className="lotto-purchase-stat__val">{fmtWon(stats.total_invested)}</span>
|
||||
<span className="lotto-purchase-stat__lbl">총 투자</span>
|
||||
</div>
|
||||
<div className="lotto-purchase-stat">
|
||||
<span className="lotto-purchase-stat__val">{fmtWon(stats.total_prize)}</span>
|
||||
<span className="lotto-purchase-stat__lbl">총 당첨금</span>
|
||||
</div>
|
||||
<div className="lotto-purchase-stat">
|
||||
<span className={`lotto-purchase-stat__val ${netColor}`}>
|
||||
{(stats.net ?? 0) >= 0 ? '+' : ''}{fmtWon(stats.net)}
|
||||
</span>
|
||||
<span className="lotto-purchase-stat__lbl">순손익</span>
|
||||
</div>
|
||||
<div className="lotto-purchase-stat">
|
||||
<span className="lotto-purchase-stat__val">{stats.return_rate?.toFixed(1)}%</span>
|
||||
<span className="lotto-purchase-stat__lbl">회수율</span>
|
||||
</div>
|
||||
<div className="lotto-purchase-stat">
|
||||
<span className="lotto-purchase-stat__val">{winRate}%</span>
|
||||
<span className="lotto-purchase-stat__lbl">당첨률</span>
|
||||
</div>
|
||||
{stats.max_prize > 0 && (
|
||||
<div className="lotto-purchase-stat">
|
||||
<span className="lotto-purchase-stat__val is-prize">{fmtWon(stats.max_prize)}</span>
|
||||
<span className="lotto-purchase-stat__lbl">최대 당첨금</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 입력 폼 */}
|
||||
{formOpen && (
|
||||
<form className="lotto-purchase-form" onSubmit={onFormSubmit}>
|
||||
<p className="lotto-purchase-form__title">
|
||||
{editId != null ? '기록 수정' : '구매 기록 추가'}
|
||||
</p>
|
||||
<div className="lotto-purchase-form__grid">
|
||||
<label className="lotto-field">
|
||||
회차
|
||||
<input
|
||||
type="number" min={1}
|
||||
value={form.draw_no}
|
||||
onChange={(e) => onFormChange('draw_no', e.target.value)}
|
||||
placeholder="예: 1181"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="lotto-field">
|
||||
구매금액
|
||||
<input
|
||||
type="number" step={1000} min={1000}
|
||||
value={form.amount}
|
||||
onChange={(e) => onFormChange('amount', Number(e.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<label className="lotto-field">
|
||||
세트 수
|
||||
<input
|
||||
type="number" min={1} max={20}
|
||||
value={form.sets}
|
||||
onChange={(e) => onFormChange('sets', Number(e.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<label className="lotto-field">
|
||||
당첨금
|
||||
<input
|
||||
type="number" min={0}
|
||||
value={form.prize}
|
||||
onChange={(e) => onFormChange('prize', Number(e.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<label className="lotto-field lotto-purchase-form__note">
|
||||
메모
|
||||
<input
|
||||
type="text"
|
||||
value={form.note}
|
||||
onChange={(e) => onFormChange('note', e.target.value)}
|
||||
placeholder="예: 5등 1개"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{formError && (
|
||||
<p className="lotto-empty" style={{ color: '#f9b6b1' }}>{formError}</p>
|
||||
)}
|
||||
<div className="lotto-purchase-form__actions">
|
||||
<button type="button" className="button ghost small" onClick={onFormClose}>
|
||||
취소
|
||||
</button>
|
||||
<button type="submit" className="button primary small" disabled={formSaving}>
|
||||
{formSaving ? '저장 중...' : editId != null ? '수정 완료' : '추가'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{/* 기록 목록 */}
|
||||
{records.length === 0 ? (
|
||||
<p className="lotto-empty">구매 기록이 없습니다.</p>
|
||||
) : (
|
||||
<div className="lotto-purchase-list">
|
||||
<div className="lotto-purchase-list__head">
|
||||
<span>회차</span>
|
||||
<span>투자금</span>
|
||||
<span>당첨금</span>
|
||||
<span>손익</span>
|
||||
<span>메모</span>
|
||||
<span />
|
||||
</div>
|
||||
{records.map((rec) => {
|
||||
const net = (rec.prize ?? 0) - (rec.amount ?? 0);
|
||||
return (
|
||||
<div key={rec.id} className="lotto-purchase-row">
|
||||
<span className="lotto-purchase-row__drw">{rec.draw_no}회</span>
|
||||
<span>{fmtWon(rec.amount)}</span>
|
||||
<span className={(rec.prize ?? 0) > 0 ? 'is-prize' : ''}>
|
||||
{fmtWon(rec.prize)}
|
||||
</span>
|
||||
<span className={net >= 0 ? 'is-pos' : 'is-neg'}>
|
||||
{net >= 0 ? '+' : ''}{fmtWon(net)}
|
||||
</span>
|
||||
<span className="lotto-purchase-row__note">{rec.note || '-'}</span>
|
||||
<div className="lotto-purchase-row__actions">
|
||||
<button className="button ghost small" onClick={() => onEditStart(rec)}>
|
||||
수정
|
||||
</button>
|
||||
<button className="button danger small" onClick={() => onDelete(rec.id)}>
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PurchasePanel;
|
||||
Reference in New Issue
Block a user