feat(lotto): 구매탭 4주 추세 차트(너 vs 큐레이터 평균 일치)
This commit is contained in:
@@ -1546,3 +1546,9 @@
|
||||
.lotto-section-fold > summary { cursor: pointer; padding: 12px 16px; background: rgba(255,255,255,0.03);
|
||||
border-radius: 10px; font-weight: 600; font-size: 14px; opacity: 0.85; }
|
||||
.lotto-section-fold[open] > summary { margin-bottom: 12px; opacity: 1; }
|
||||
|
||||
.trend-chart { display: block; margin: 0 auto; }
|
||||
.trend-legend { display: flex; gap: 16px; justify-content: center; font-size: 11px; opacity: 0.7; margin-top: 8px; }
|
||||
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 4px; vertical-align: middle; }
|
||||
.dot--curator { background: #b8a8ff; }
|
||||
.dot--user { background: #76e09a; }
|
||||
|
||||
44
src/pages/lotto/components/PurchaseTrendChart.jsx
Normal file
44
src/pages/lotto/components/PurchaseTrendChart.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getReviewHistory } from '../../../api';
|
||||
|
||||
export default function PurchaseTrendChart() {
|
||||
const [reviews, setReviews] = useState([]);
|
||||
useEffect(() => {
|
||||
getReviewHistory(4).then(rs => setReviews(rs.reverse())); // asc
|
||||
}, []);
|
||||
|
||||
if (reviews.length === 0) return null;
|
||||
|
||||
const maxAvg = Math.max(
|
||||
...reviews.flatMap(r => [r.curator_avg_match || 0, r.user_avg_match || 0]),
|
||||
2.5
|
||||
);
|
||||
const w = 320, h = 80, pad = 16;
|
||||
const xs = (i) => pad + (i / Math.max(reviews.length - 1, 1)) * (w - 2 * pad);
|
||||
const ys = (v) => v == null ? null : h - pad - (v / maxAvg) * (h - 2 * pad);
|
||||
|
||||
const line = (key) => reviews
|
||||
.map((r, i) => ({ x: xs(i), y: ys(r[key]) }))
|
||||
.filter(p => p.y != null)
|
||||
.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x},${p.y}`)
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<section className="lotto-panel">
|
||||
<div className="lotto-panel__head">
|
||||
<div>
|
||||
<p className="lotto-panel__eyebrow">Trend (last 4 weeks)</p>
|
||||
<h3>너 vs 큐레이터 평균 일치 수</h3>
|
||||
</div>
|
||||
</div>
|
||||
<svg width={w} height={h} className="trend-chart">
|
||||
<path d={line('curator_avg_match')} stroke="#b8a8ff" strokeWidth="2" fill="none" />
|
||||
<path d={line('user_avg_match')} stroke="#76e09a" strokeWidth="2" fill="none" />
|
||||
</svg>
|
||||
<div className="trend-legend">
|
||||
<span><span className="dot dot--curator" /> 큐레이터</span>
|
||||
<span><span className="dot dot--user" /> 너</span>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import usePurchases from '../hooks/usePurchases';
|
||||
import PurchasePanel from '../components/PurchasePanel';
|
||||
import PurchaseTrendChart from '../components/PurchaseTrendChart';
|
||||
|
||||
export default function PurchaseTab() {
|
||||
const pur = usePurchases();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PurchaseTrendChart />
|
||||
<PurchasePanel
|
||||
records={pur.purchases}
|
||||
stats={pur.purchaseStats}
|
||||
@@ -21,5 +24,6 @@ export default function PurchaseTab() {
|
||||
onEditStart={pur.handlePurchaseEditStart}
|
||||
onDelete={pur.handlePurchaseDelete}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user