feat(tarot): History.jsx — 마이페이지 (T16)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 00:48:01 +09:00
parent d08b20a4b5
commit bc6c45dee3

131
src/pages/tarot/History.jsx Normal file
View 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>
);
}