Files
nuzlocke-tracker/frontend/src/components/journal/JournalList.tsx
Julian Tabel 088cd35002
Some checks failed
CI / frontend-tests (push) Has been cancelled
CI / backend-tests (push) Has been cancelled
add Ko-fi bean
2026-03-20 16:39:52 +01:00

103 lines
3.2 KiB
TypeScript

import { Link } from 'react-router-dom'
import type { JournalEntry } from '../../types/journal'
import type { BossResult, BossBattle } from '../../types/game'
interface JournalListProps {
entries: JournalEntry[]
runId: number
bossResults?: BossResult[]
bosses?: BossBattle[]
onNewEntry?: () => void
}
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
function getPreviewSnippet(body: string, maxLength = 120): string {
const stripped = body.replace(/[#*_`~[\]]/g, '').replace(/\n+/g, ' ').trim()
if (stripped.length <= maxLength) return stripped
return stripped.slice(0, maxLength).trim() + '...'
}
export function JournalList({
entries,
runId,
bossResults = [],
bosses = [],
onNewEntry,
}: JournalListProps) {
const bossResultMap = new Map(bossResults.map((br) => [br.id, br]))
const bossMap = new Map(bosses.map((b) => [b.id, b]))
const getBossName = (bossResultId: number | null): string | null => {
if (bossResultId == null) return null
const result = bossResultMap.get(bossResultId)
if (!result) return null
const boss = bossMap.get(result.bossBattleId)
return boss?.name ?? null
}
if (entries.length === 0) {
return (
<div className="text-center py-12">
<p className="text-text-secondary mb-4">No journal entries yet.</p>
{onNewEntry && (
<button
type="button"
onClick={onNewEntry}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Write your first entry
</button>
)}
</div>
)
}
return (
<div className="space-y-3">
{onNewEntry && (
<div className="flex justify-end mb-4">
<button
type="button"
onClick={onNewEntry}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
New Entry
</button>
</div>
)}
{entries.map((entry) => {
const bossName = getBossName(entry.bossResultId)
return (
<Link
key={entry.id}
to={`/runs/${runId}/journal/${entry.id}`}
className="block p-4 bg-surface-1 rounded-lg hover:bg-surface-2 transition-colors border border-border"
>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<h3 className="font-semibold text-text-primary truncate">{entry.title}</h3>
<p className="text-sm text-text-secondary mt-1">{getPreviewSnippet(entry.body)}</p>
</div>
<div className="flex flex-col items-end shrink-0 gap-1">
<time className="text-xs text-text-tertiary">{formatDate(entry.createdAt)}</time>
{bossName && (
<span className="text-xs px-2 py-0.5 bg-purple-900/40 text-purple-300 light:bg-purple-100 light:text-purple-800 rounded-full">
{bossName}
</span>
)}
</div>
</div>
</Link>
)
})}
</div>
)
}