173 lines
5.0 KiB
TypeScript
173 lines
5.0 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { authFetch, clearToken, getApiBase, getToken } from '../../lib/auth'
|
|
import AdminShell from '../../ui/AdminShell'
|
|
|
|
type RequestRow = {
|
|
id: number
|
|
title?: string | null
|
|
year?: number | null
|
|
type?: string | null
|
|
statusLabel?: string | null
|
|
requestedBy?: string | null
|
|
createdAt?: string | null
|
|
}
|
|
|
|
const formatDateTime = (value?: string | null) => {
|
|
if (!value) return 'Unknown'
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.valueOf())) return value
|
|
return date.toLocaleString()
|
|
}
|
|
|
|
export default function AdminRequestsAllPage() {
|
|
const router = useRouter()
|
|
const [rows, setRows] = useState<RequestRow[]>([])
|
|
const [total, setTotal] = useState(0)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [pageSize, setPageSize] = useState(50)
|
|
const [page, setPage] = useState(1)
|
|
|
|
const pageCount = useMemo(() => {
|
|
if (!total || pageSize <= 0) return 1
|
|
return Math.max(1, Math.ceil(total / pageSize))
|
|
}, [total, pageSize])
|
|
|
|
const load = async () => {
|
|
if (!getToken()) {
|
|
router.push('/login')
|
|
return
|
|
}
|
|
setLoading(true)
|
|
setError(null)
|
|
try {
|
|
const baseUrl = getApiBase()
|
|
const skip = (page - 1) * pageSize
|
|
const response = await authFetch(
|
|
`${baseUrl}/admin/requests/all?take=${pageSize}&skip=${skip}`
|
|
)
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
clearToken()
|
|
router.push('/login')
|
|
return
|
|
}
|
|
if (response.status === 403) {
|
|
router.push('/')
|
|
return
|
|
}
|
|
throw new Error(`Load failed: ${response.status}`)
|
|
}
|
|
const data = await response.json()
|
|
setRows(Array.isArray(data?.results) ? data.results : [])
|
|
setTotal(Number(data?.total ?? 0))
|
|
} catch (err) {
|
|
console.error(err)
|
|
setError('Unable to load requests.')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
void load()
|
|
}, [page, pageSize])
|
|
|
|
useEffect(() => {
|
|
if (page > pageCount) {
|
|
setPage(pageCount)
|
|
}
|
|
}, [pageCount, page])
|
|
|
|
return (
|
|
<AdminShell
|
|
title="All requests"
|
|
subtitle="Paginated view of every cached request."
|
|
actions={
|
|
<button type="button" onClick={() => router.push('/admin')}>
|
|
Back to settings
|
|
</button>
|
|
}
|
|
>
|
|
<section className="admin-section">
|
|
<div className="admin-toolbar">
|
|
<div className="admin-toolbar-info">
|
|
<span>{total.toLocaleString()} total</span>
|
|
</div>
|
|
<div className="admin-toolbar-actions">
|
|
<label className="admin-select">
|
|
<span>Per page</span>
|
|
<select value={pageSize} onChange={(e) => setPageSize(Number(e.target.value))}>
|
|
<option value={25}>25</option>
|
|
<option value={50}>50</option>
|
|
<option value={100}>100</option>
|
|
<option value={200}>200</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
{loading ? (
|
|
<div className="status-banner">Loading requests…</div>
|
|
) : error ? (
|
|
<div className="error-banner">{error}</div>
|
|
) : rows.length === 0 ? (
|
|
<div className="status-banner">No requests found.</div>
|
|
) : (
|
|
<div className="admin-table">
|
|
<div className="admin-table-head">
|
|
<span>Request</span>
|
|
<span>Status</span>
|
|
<span>Requested by</span>
|
|
<span>Created</span>
|
|
</div>
|
|
{rows.map((row) => (
|
|
<button
|
|
key={row.id}
|
|
type="button"
|
|
className="admin-table-row"
|
|
onClick={() => router.push(`/requests/${row.id}`)}
|
|
>
|
|
<span>
|
|
{row.title || `Request #${row.id}`}
|
|
{row.year ? ` (${row.year})` : ''}
|
|
</span>
|
|
<span>{row.statusLabel || 'Unknown'}</span>
|
|
<span>{row.requestedBy || 'Unknown'}</span>
|
|
<span>{formatDateTime(row.createdAt)}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div className="admin-pagination">
|
|
<button type="button" onClick={() => setPage(1)} disabled={page <= 1}>
|
|
First
|
|
</button>
|
|
<button type="button" onClick={() => setPage(page - 1)} disabled={page <= 1}>
|
|
Previous
|
|
</button>
|
|
<span>
|
|
Page {page} of {pageCount}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => setPage(page + 1)}
|
|
disabled={page >= pageCount}
|
|
>
|
|
Next
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setPage(pageCount)}
|
|
disabled={page >= pageCount}
|
|
>
|
|
Last
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</AdminShell>
|
|
)
|
|
}
|