journal-quest / src /components /EventCard.tsx
MATSUMURA Taishi
Add 第2問(勘定記入)・第3問(財務諸表)
ebe620b
Raw
History Blame Contribute Delete
3.87 kB
import { useState } from 'react'
import { motion } from 'framer-motion'
import { useGame } from '../store/gameStore'
import { TOPIC_MASTER } from '../data/accounts'
import type { ProblemDocument } from '../types'
interface Props {
// 第2問/第3問から直接 props で渡す場合(省略時は gameStore から取得)
eventText?: string
hint?: string | null
documents?: ProblemDocument[]
sectionLabel?: string
topicLabel?: string
problemId?: string
}
export function EventCard(props: Props) {
// props がなければ gameStore から読む(第1問)
const storeProblem = useGame((s) => s.problem)
const storeTopic = useGame((s) => s.topic)
const [showHint, setShowHint] = useState(false)
const eventText = props.eventText ?? storeProblem?.event_text
const hint = props.hint !== undefined ? props.hint : storeProblem?.hint
const documents = props.documents ?? storeProblem?.documents
const sectionLabel = props.sectionLabel ?? '第1問 仕訳'
const topicLabel = props.topicLabel ?? (TOPIC_MASTER[storeTopic]?.label ?? storeTopic)
const problemId = props.problemId ?? storeProblem?.id ?? 'card'
if (!eventText) return null
return (
<motion.div
key={problemId}
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
className="rounded-2xl border border-cyan-500/30 bg-gradient-to-br from-slate-800 to-slate-900 p-5 shadow-xl"
>
<div className="mb-2 flex items-center gap-2 text-xs">
<span className="rounded-full bg-cyan-500/20 px-2 py-0.5 text-cyan-200">{topicLabel}</span>
<span className="rounded-full bg-slate-600/50 px-2 py-0.5 text-slate-200">{sectionLabel}</span>
</div>
<p className="text-base font-bold leading-relaxed text-slate-50">{eventText}</p>
{/* 証憑テーブル */}
{documents && documents.length > 0 && (
<div className="mt-4 space-y-3">
{documents.map((doc, di) => (
<DocumentTable key={di} doc={doc} />
))}
</div>
)}
{hint && (
<div className="mt-3">
{showHint ? (
<p className="rounded-lg bg-amber-500/10 px-3 py-2 text-sm text-amber-200">💡 {hint}</p>
) : (
<button
onClick={() => setShowHint(true)}
className="text-sm text-amber-300/80 underline-offset-2 hover:underline"
>
ヒントを見る
</button>
)}
</div>
)}
</motion.div>
)
}
function DocumentTable({ doc }: { doc: ProblemDocument }) {
const isSummaryRow = (row: string[]) =>
row[0] === '小計' || row[0] === '合計' || row[0].includes('消費税')
return (
<div className="overflow-x-auto rounded-xl border border-slate-600">
{doc.title && (
<div className="border-b border-slate-600 bg-slate-700/50 px-3 py-1.5 text-xs font-semibold text-slate-300">
{doc.title}
</div>
)}
<table className="w-full text-sm">
<thead>
<tr className="bg-slate-700/40">
{doc.headers.map((h, i) => (
<th key={i} className={`px-3 py-2 text-xs font-semibold text-slate-300 ${i === 0 ? 'text-left' : 'text-right'}`}>
{h}
</th>
))}
</tr>
</thead>
<tbody>
{doc.rows.map((row, ri) => (
<tr key={ri} className={isSummaryRow(row) ? 'border-t border-slate-600 bg-slate-700/30 font-semibold' : 'border-t border-slate-700/50'}>
{row.map((cell, ci) => (
<td key={ci} className={`px-3 py-1.5 tabular-nums text-slate-200 ${ci === 0 ? 'text-left' : 'text-right'}`}>
{cell}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}