feat(tarot): History.jsx — 마이페이지 (T16)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
131
src/pages/tarot/History.jsx
Normal file
131
src/pages/tarot/History.jsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import './Tarot.css';
|
||||
import { tarotListReadings, tarotPatchReading, tarotDeleteReading } from '../../api';
|
||||
import { findCard, SPREADS } from './data/cards';
|
||||
|
||||
function pickLine(r) {
|
||||
const labels = (r.cards || []).map((c) => {
|
||||
const card = findCard(c.card_id);
|
||||
const name = card ? card.name : c.card_id;
|
||||
return `${c.position} · ${name}${c.reversed ? '(역)' : ''}`;
|
||||
});
|
||||
return labels.join(' / ');
|
||||
}
|
||||
|
||||
export default function History() {
|
||||
const [items, setItems] = useState([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [favoriteOnly, setFavoriteOnly] = useState(false);
|
||||
const [spreadFilter, setSpreadFilter] = useState('');
|
||||
const [categoryFilter, setCategoryFilter] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [openId, setOpenId] = useState(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await tarotListReadings({
|
||||
page, size: 20,
|
||||
favorite: favoriteOnly || undefined,
|
||||
spread_type: spreadFilter || undefined,
|
||||
category: categoryFilter || undefined,
|
||||
});
|
||||
setItems(res.items);
|
||||
setTotal(res.total);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page, favoriteOnly, spreadFilter, categoryFilter]);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
const toggleFav = async (id, cur) => {
|
||||
await tarotPatchReading(id, { favorite: !cur });
|
||||
load();
|
||||
};
|
||||
|
||||
const remove = async (id) => {
|
||||
if (!window.confirm('삭제할까요?')) return;
|
||||
await tarotDeleteReading(id);
|
||||
load();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tarot tarot-history">
|
||||
<h2 style={{ fontFamily: 'Cormorant Garamond, serif', fontSize: 32, marginBottom: 16 }}>
|
||||
리딩 히스토리
|
||||
</h2>
|
||||
|
||||
<div className="tarot-reading__chips" style={{ marginBottom: 16 }}>
|
||||
<button className={`tarot-chip ${favoriteOnly ? 'is-active' : ''}`}
|
||||
onClick={() => setFavoriteOnly((v) => !v)}>
|
||||
⭐ 즐겨찾기만
|
||||
</button>
|
||||
<button className={`tarot-chip ${spreadFilter === 'three_card' ? 'is-active' : ''}`}
|
||||
onClick={() => setSpreadFilter((v) => v === 'three_card' ? '' : 'three_card')}>
|
||||
3장
|
||||
</button>
|
||||
<button className={`tarot-chip ${spreadFilter === 'one_card' ? 'is-active' : ''}`}
|
||||
onClick={() => setSpreadFilter((v) => v === 'one_card' ? '' : 'one_card')}>
|
||||
1장
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loading && <p style={{ color: 'var(--tarot-text-dim)' }}>불러오는 중…</p>}
|
||||
{!loading && items.length === 0 && <p style={{ color: 'var(--tarot-text-dim)' }}>리딩 기록이 없습니다.</p>}
|
||||
|
||||
{items.map((r) => (
|
||||
<div key={r.id} className="tarot-history__item">
|
||||
<div>
|
||||
<div style={{ fontSize: 12, color: 'var(--tarot-text-dim)' }}>
|
||||
{r.created_at} · {SPREADS[r.spread_type]?.name || r.spread_type}
|
||||
{r.category ? ` · ${r.category}` : ''}
|
||||
</div>
|
||||
<div style={{ marginTop: 6 }}>{r.question || '(질문 없음)'}</div>
|
||||
<div style={{ marginTop: 6, fontSize: 13, color: 'var(--tarot-gold)' }}>
|
||||
{pickLine(r)}
|
||||
</div>
|
||||
<p style={{ marginTop: 8, fontSize: 13, color: 'var(--tarot-text-dim)' }}>
|
||||
{r.summary}
|
||||
</p>
|
||||
{openId === r.id && r.interpretation_json && (
|
||||
<div style={{ marginTop: 12, padding: 12, background: 'rgba(0,0,0,.2)', borderRadius: 6 }}>
|
||||
<p style={{ fontSize: 13 }}>{r.interpretation_json.advice}</p>
|
||||
{r.interpretation_json.warning && (
|
||||
<p style={{ fontSize: 13, color: '#f43f5e' }}>⚠️ {r.interpretation_json.warning}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setOpenId(openId === r.id ? null : r.id)}
|
||||
style={{ marginTop: 8, fontSize: 12, background: 'transparent', border: '1px solid rgba(255,255,255,.15)', color: 'var(--tarot-text-dim)', padding: '4px 8px', borderRadius: 4, cursor: 'pointer' }}
|
||||
>
|
||||
{openId === r.id ? '접기' : '자세히'}
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<button
|
||||
className={`tarot-history__star ${r.favorite ? 'is-fav' : ''}`}
|
||||
onClick={() => toggleFav(r.id, r.favorite)}
|
||||
aria-label="즐겨찾기 토글"
|
||||
>★</button>
|
||||
<button
|
||||
onClick={() => remove(r.id)}
|
||||
style={{ background: 'transparent', border: 'none', color: 'var(--tarot-text-dim)', cursor: 'pointer', fontSize: 12 }}
|
||||
aria-label="삭제"
|
||||
>삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{total > 20 && (
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 16, justifyContent: 'center' }}>
|
||||
<button className="tarot-chip" disabled={page === 1} onClick={() => setPage((p) => p - 1)}>이전</button>
|
||||
<span style={{ color: 'var(--tarot-text-dim)', alignSelf: 'center' }}>{page} / {Math.ceil(total / 20)}</span>
|
||||
<button className="tarot-chip" disabled={page * 20 >= total} onClick={() => setPage((p) => p + 1)}>다음</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user