189 lines
6.0 KiB
TypeScript
189 lines
6.0 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, 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 REQUEST_STAGE_OPTIONS = [
|
|
{ value: "all", label: "All stages" },
|
|
{ value: "pending", label: "Waiting for approval" },
|
|
{ value: "approved", label: "Approved" },
|
|
{ value: "in_progress", label: "In progress" },
|
|
{ value: "working", label: "Working on it" },
|
|
{ value: "partial", label: "Partially ready" },
|
|
{ value: "ready", label: "Ready to watch" },
|
|
{ value: "declined", label: "Declined" },
|
|
];
|
|
|
|
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 [stage, setStage] = useState("all");
|
|
|
|
const pageCount = useMemo(() => {
|
|
if (!total || pageSize <= 0) return 1;
|
|
return Math.max(1, Math.ceil(total / pageSize));
|
|
}, [total, pageSize]);
|
|
|
|
const load = useCallback(async () => {
|
|
if (!getToken()) {
|
|
router.push("/login");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const baseUrl = getApiBase();
|
|
const skip = (page - 1) * pageSize;
|
|
const params = new URLSearchParams({
|
|
take: String(pageSize),
|
|
skip: String(skip),
|
|
});
|
|
if (stage !== "all") {
|
|
params.set("stage", stage);
|
|
}
|
|
const response = await authFetch(`${baseUrl}/admin/requests/all?${params.toString()}`);
|
|
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);
|
|
}
|
|
}, [page, pageSize, router, stage]);
|
|
|
|
useEffect(() => {
|
|
void load();
|
|
}, [load]);
|
|
|
|
useEffect(() => {
|
|
if (page > pageCount) {
|
|
setPage(pageCount);
|
|
}
|
|
}, [pageCount, page]);
|
|
|
|
useEffect(() => {
|
|
void stage;
|
|
setPage(1);
|
|
}, [stage]);
|
|
|
|
return (
|
|
<AdminShell title="All requests" subtitle="Paginated view of every cached request.">
|
|
<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>Stage</span>
|
|
<select value={stage} onChange={(e) => setStage(e.target.value)}>
|
|
{REQUEST_STAGE_OPTIONS.map((option) => (
|
|
<option key={option.value} value={option.value}>
|
|
{option.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<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>
|
|
);
|
|
}
|