Files
trumpsignal-frontend/app/[locale]/archive/ArchivePageClient.tsx
T

151 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect } from 'react'
import type { TrumpPost } from '@/types'
import { getPosts, getPostsPage, type PostListResponse } from '@/lib/api'
import { hasCached, swrFetch } from '@/lib/cache'
import PostRow from '@/components/dashboard/PostCards'
import PageHint from '@/components/ui/PageHint'
import Pagination from '@/components/ui/Pagination'
import { buildArchiveFallbackResponse } from '@/lib/postPage'
const ARCHIVE_PAGE_SIZE = 30
interface ArchivePageClientProps {
initialData?: PostListResponse | null
}
export default function ArchivePageClient({ initialData = null }: ArchivePageClientProps) {
const [posts, setPosts] = useState<TrumpPost[]>(initialData?.items ?? [])
const [totalPosts, setTotalPosts] = useState(initialData?.total ?? 0)
const [sourceCounts, setSourceCounts] = useState(initialData?.source_counts ?? [])
const [loading, setLoading] = useState(initialData === null)
const [loadErr, setLoadErr] = useState('')
const [src, setSrc] = useState<string>('all')
const [archivePage, setArchivePage] = useState(1)
useEffect(() => {
const key = `archive-page-${archivePage}-src-${src}`
setLoadErr('')
setLoading(posts.length === 0 && !hasCached(key))
swrFetch(
key,
3 * 60_000,
() => getPostsPage(
ARCHIVE_PAGE_SIZE,
archivePage,
undefined,
{
archiveOnly: true,
sourceIn: src === 'all' ? undefined : [src],
},
),
fresh => {
setPosts(fresh.items)
setTotalPosts(fresh.total)
setSourceCounts(fresh.source_counts)
},
)
.then(r => {
setPosts(r.items)
setTotalPosts(r.total)
setSourceCounts(r.source_counts)
})
.catch(async e => {
const detail = e instanceof Error ? e.message : 'Failed to load archive'
if (!detail.includes('404')) {
setLoadErr(detail)
return
}
try {
const legacyPosts = await swrFetch(
'archive-legacy-500',
3 * 60_000,
() => getPosts(500, 1),
)
const fallback = buildArchiveFallbackResponse(legacyPosts, archivePage, ARCHIVE_PAGE_SIZE, src)
setPosts(fallback.items)
setTotalPosts(fallback.total)
setSourceCounts(fallback.source_counts)
setLoadErr('')
} catch (legacyErr) {
setLoadErr(legacyErr instanceof Error ? legacyErr.message : detail)
}
})
.finally(() => setLoading(false))
}, [archivePage, posts.length, src])
const archiveTotalPages = Math.max(1, Math.ceil(totalPosts / ARCHIVE_PAGE_SIZE))
const archiveSafePage = Math.min(archivePage, archiveTotalPages)
const allSourcesCount = sourceCounts.reduce((sum, item) => sum + item.count, 0)
const sourceTabs: [string, number][] = [
['all', allSourcesCount],
...sourceCounts.map(item => [item.source, item.count] as [string, number]),
]
return (
<div className="page">
<div className="page-head">
<div>
<h1 className="page-title">Archive</h1>
{/* No count slot — the source tabs and pagination already show
per-source and total counts. */}
<PageHint>
Signals from retired scanner experiments
(rsi_reversal, sma_reclaim, breakout, test/phase1). Read-only
the bot no longer acts on any of these.
</PageHint>
</div>
</div>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>
{sourceTabs.map(([s, n]) => (
<button
key={s}
onClick={() => { setSrc(s); setArchivePage(1) }}
style={{
padding: '4px 10px', borderRadius: 6, border: '1px solid var(--line)',
background: src === s ? 'var(--ink)' : 'transparent',
color: src === s ? 'var(--bg)' : 'var(--ink-3)',
fontSize: 11, cursor: 'pointer', fontWeight: src === s ? 600 : 400,
}}
>
{s} <span style={{ opacity: 0.6 }}>{n}</span>
</button>
))}
</div>
{loading && <div style={{ textAlign: 'center', padding: 60, color: 'var(--ink-3)' }}>Loading</div>}
{!loading && loadErr && (
<div className="card" style={{ padding: 24, textAlign: 'center', color: 'var(--down)' }}>
{`Couldn't load archive — ${loadErr}`}
<div style={{ marginTop: 10 }}>
<button className="btn ghost" style={{ fontSize: 12, padding: '6px 14px' }}
onClick={() => location.reload()}>Retry</button>
</div>
</div>
)}
{!loading && !loadErr && totalPosts === 0 && (
<div className="card" style={{ padding: 48, textAlign: 'center', color: 'var(--ink-3)' }}>
No archived signals.
</div>
)}
{!loading && posts.length > 0 && (
<>
<div className="post-stream">
{posts.map(p => <PostRow key={p.id} post={p} />)}
</div>
<Pagination
page={archiveSafePage}
total={archiveTotalPages}
count={totalPosts}
pageSize={ARCHIVE_PAGE_SIZE}
onChange={setArchivePage}
/>
</>
)}
</div>
)
}