Files
Magent/frontend/app/admin/requests-all/page.tsx
T
Assclaw f852e7c941
Magent CI/CD / verify (push) Failing after 9m34s
Magent CI/CD / deploy-beta (push) Skipped
chore: standardize security and quality foundations
2026-09-17 20:03:47 +12:00

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>
);
}