183 lines
9.0 KiB
JavaScript
183 lines
9.0 KiB
JavaScript
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>메모</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__hits">
|
|
{(rec.results || []).map((r, i) => (
|
|
<span key={i} className={`hit-badge hit-${r.correct}`}>{r.correct}</span>
|
|
))}
|
|
{(rec.results || []).some((r) => r.correct >= 4) && (
|
|
<span className="prize-flag">🚨 4등↑ 확인 필요</span>
|
|
)}
|
|
</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;
|