From 13d36012a80c1f7f97cd296464b60d85d2f2b38d Mon Sep 17 00:00:00 2001 From: Tim Howitz Date: Sun, 1 Jun 2025 14:30:18 +0100 Subject: [PATCH] Made some small requests changes --- src/app/api/requests/route.ts | 68 +++--- src/app/requests/page.tsx | 392 ++++++++++++++++------------------ 2 files changed, 213 insertions(+), 247 deletions(-) diff --git a/src/app/api/requests/route.ts b/src/app/api/requests/route.ts index 0df7883..24f2254 100644 --- a/src/app/api/requests/route.ts +++ b/src/app/api/requests/route.ts @@ -3,39 +3,39 @@ import { prisma } from "@utils/prisma"; // GET requests, just requestingUser only export async function GET() { - try { - const requests = await prisma.request.findMany({ - orderBy: { createdAt: "desc" }, - include: { - requestingUser: true, - }, - }); - return NextResponse.json({ requests }, { status: 200 }); - } catch (err) { - console.error("Failed to get requests", err); - return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); - } + try { + const requests = await prisma.request.findMany({ + orderBy: { createdAt: "desc" }, + include: { + requestingUser: true, + }, + }); + return NextResponse.json({ requests }, { status: 200 }); + } catch (err) { + console.error("Failed to get requests", err); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } } -export async function PUT(req: NextRequest) { - try { - const { id, outcome } = await req.json(); - if (!["FULFILLED", "REJECTED"].includes(outcome)) { - return NextResponse.json({ error: "Invalid outcome" }, { status: 400 }); - } - const existing = await prisma.request.findUnique({ where: { id } }); - if (!existing) { - return NextResponse.json({ error: "Request not found" }, { status: 404 }); - } - const updated = await prisma.request.update({ - where: { id }, - data: { - outcome, - }, - }); - return NextResponse.json({ request: updated }, { status: 200 }); - } catch (err) { - console.error("Update request failed", err); - return NextResponse.json({ error: "Update failed" }, { status: 500 }); - } -} \ No newline at end of file +export async function POST(req: NextRequest) { + try { + const { id, outcome } = await req.json(); + if (!["FULFILLED", "REJECTED"].includes(outcome)) { + return NextResponse.json({ error: "Invalid outcome" }, { status: 400 }); + } + const existing = await prisma.request.findUnique({ where: { id } }); + if (!existing) { + return NextResponse.json({ error: "Request not found" }, { status: 404 }); + } + const updated = await prisma.request.update({ + where: { id }, + data: { + outcome, + }, + }); + return NextResponse.json({ request: updated }, { status: 200 }); + } catch (err) { + console.error("Update request failed", err); + return NextResponse.json({ error: "Update failed" }, { status: 500 }); + } +} diff --git a/src/app/requests/page.tsx b/src/app/requests/page.tsx index 9d0807f..ecefcea 100644 --- a/src/app/requests/page.tsx +++ b/src/app/requests/page.tsx @@ -1,242 +1,208 @@ "use client"; +import axios from "axios"; import React, { useState, useEffect } from "react"; import { useStoreState } from "@hooks/store"; // Helper dicts/types const outcomeColors: Record = { - FULFILLED: "text-green-700 bg-green-100", - REJECTED: "text-red-700 bg-red-100", - IN_PROGRESS: "text-blue-700 bg-blue-100", - CANCELLED: "text-gray-600 bg-gray-100", - OTHER: "text-yellow-800 bg-yellow-100", + FULFILLED: "text-green-700 bg-green-100", + REJECTED: "text-red-700 bg-red-100", + IN_PROGRESS: "text-blue-700 bg-blue-100", + CANCELLED: "text-gray-600 bg-gray-100", + OTHER: "text-yellow-800 bg-yellow-100", }; const requestTypeLabels: Record = { - NEW_USER: "New User", - CHANGE_LEVEL: "Change Level", - DELETE: "Removal", + NEW_USER: "New User", + CHANGE_LEVEL: "Change Level", + DELETE: "Removal", }; function formatDate(val?: string) { - if (!val) return "--"; - return new Date(val).toLocaleString(undefined, { - year: "numeric", - month: "short", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - }); + if (!val) return "--"; + return new Date(val).toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }); } // Minimal types type User = { - id: number; - name: string; - email: string; - role?: string; - scientist?: { level: string } | null; + id: number; + name: string; + email: string; + role?: string; + scientist?: { level: string } | null; }; type Request = { - id: number; - createdAt: string; - requestType: string; - requestingUser: User; - outcome: string; + id: number; + createdAt: string; + requestType: string; + requestingUser: User; + outcome: string; }; export default function RequestManagementPage() { - const user = useStoreState((s) => s.user); + const user = useStoreState((s) => s.user); - // All hooks first! - const [requests, setRequests] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [actionLoading, setActionLoading] = useState(null); - const [actionError, setActionError] = useState(null); - const [actionSuccess, setActionSuccess] = useState(null); + // All hooks first! + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [actionLoading, setActionLoading] = useState(null); + const [actionError, setActionError] = useState(null); + const [actionSuccess, setActionSuccess] = useState(null); - // User role logic must remain invariant per render - const userRole = user?.role as string | undefined; - const isAdmin = userRole === "ADMIN"; - const isSeniorScientist = userRole === "SCIENTIST" && user?.scientist?.level === "SENIOR"; - const userId = user?.id; + // User role logic must remain invariant per render + const userRole = user?.role as string | undefined; + const isAdmin = userRole === "ADMIN"; + const isSeniorScientist = userRole === "SCIENTIST" && user?.scientist?.level === "SENIOR"; + const userId = user?.id; - // Requests fetch - useEffect(() => { - setLoading(true); - setError(null); - fetch("/api/requests") - .then((res) => { - if (!res.ok) throw new Error("Failed to fetch requests"); - return res.json(); - }) - .then((data) => { - setRequests(data.requests || []); - setLoading(false); - }) - .catch((err) => { - setError("Failed to load requests."); - setLoading(false); - }); - }, []); + // Requests fetch + useEffect(() => { + setLoading(true); + setError(null); + axios + .get("/api/requests") + .then((res) => { + setRequests(res.data.requests || []); + setLoading(false); + }) + .catch(() => { + setError("Failed to load requests."); + setLoading(false); + }); + }, []); - // Filtering for non-admins to only their requests - const filteredRequests = React.useMemo( - () => - isAdmin - ? requests - : requests.filter((r) => r.requestingUser.id === userId), - [isAdmin, requests, userId] - ); + // Filtering for non-admins to only their requests + const filteredRequests = React.useMemo( + () => (isAdmin ? requests : requests.filter((r) => r.requestingUser.id === userId)), + [isAdmin, requests, userId] + ); - // Sorted: newest first - filteredRequests.sort((a, b) => - (b.createdAt ?? "").localeCompare(a.createdAt ?? "") - ); + // Sorted: newest first + filteredRequests.sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? "")); - async function handleAction( - requestId: number, - action: "FULFILLED" | "REJECTED" - ) { - setActionLoading(requestId); - setActionError(null); - setActionSuccess(null); - try { - const res = await fetch("/api/requests", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id: requestId, outcome: action }), - }); - if (!res.ok) { - const data = await res.json().catch(() => ({})); - throw new Error(data?.error || "Failed to update request"); - } - setRequests((prev) => - prev.map((r) => - r.id === requestId ? { ...r, outcome: action } : r - ) - ); - setActionSuccess("Request updated."); - } catch (err: any) { - setActionError(err?.message || "Failed to update request"); - } finally { - setActionLoading(null); - } - } + async function handleAction(requestId: number, action: "FULFILLED" | "REJECTED") { + setActionLoading(requestId); + setActionError(null); + setActionSuccess(null); + try { + const res = await axios.post("/api/requests", { id: requestId, outcome: action }); + setRequests((prev) => prev.map((r) => (r.id === requestId ? { ...r, outcome: action } : r))); + setActionSuccess("Request updated."); + } catch (err: any) { + setActionError(err.response?.data?.error || "Failed to update request"); + } finally { + setActionLoading(null); + } + } - // Unauthorized access should return early, but not before hooks! - if (!isAdmin && !isSeniorScientist) { - return ( -
-

Unauthorized Access

-
You do not have access to this page.
-
- ); - } + // Unauthorized access should return early, but not before hooks! + if (!isAdmin && !isSeniorScientist) { + return ( +
+

Unauthorized Access

+
You do not have access to this page.
+
+ ); + } - return ( -
-
-

- {isAdmin ? "All Requests" : "My Requests"} -

-

- View {isAdmin ? "and manage pending" : "your"} requests related to scientist management. -

-
+ return ( +
+
+

{isAdmin ? "All Requests" : "My Requests"}

+

+ View {isAdmin ? "and manage pending" : "your"} requests related to scientist management. +

+
- {loading ? ( -
Loading...
- ) : error ? ( -
{error}
- ) : ( -
- - - - - - - - {isAdmin && } - - - - {filteredRequests.length === 0 ? ( - - - - ) : ( - filteredRequests.map((req) => ( - - - - - - {isAdmin && ( - - )} - - )) - )} - -
DateTypeRequested ByStatusActions
- No requests found. -
- {formatDate(req.createdAt)} - - {requestTypeLabels[req.requestType] || req.requestType} - - {req.requestingUser.name} - - ({req.requestingUser.email}) - - - - {req.outcome.replace(/_/g, " ")} - - - {req.outcome === "IN_PROGRESS" ? ( -
- - -
- ) : ( - No actions - )} - {(actionError && actionLoading === req.id) && ( -
{actionError}
- )} - {(actionSuccess && actionLoading === req.id) && ( -
{actionSuccess}
- )} -
-
- )} -
- ); -} \ No newline at end of file + {loading ? ( +
Loading...
+ ) : error ? ( +
{error}
+ ) : ( +
+ + + + + + + + {isAdmin && } + + + + {filteredRequests.length === 0 ? ( + + + + ) : ( + filteredRequests.map((req) => ( + + + + + + {isAdmin && ( + + )} + + )) + )} + +
DateTypeRequested ByStatusActions
+ No requests found. +
{formatDate(req.createdAt)}{requestTypeLabels[req.requestType] || req.requestType} + {req.requestingUser.name} + ({req.requestingUser.email}) + + + {req.outcome.replace(/_/g, " ")} + + + {req.outcome === "IN_PROGRESS" ? ( +
+ + +
+ ) : ( + No actions + )} + {actionError && actionLoading === req.id &&
{actionError}
} + {actionSuccess && actionLoading === req.id && ( +
{actionSuccess}
+ )} +
+
+ )} +
+ ); +}