"use client"; import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { TransferApprovalCard } from "@/components/frontend/transfers/TransferApprovalCard"; import { TransferResolutionCard } from "@/components/frontend/transfers/TransferResolutionCard"; import { TransferRequestCard } from "@/components/frontend/transfers/TransferRequestCard"; import { LeadershipTransfersSection } from "@/components/frontend/transfers/LeadershipTransfersSection"; import { relationId, assignmentLeaderId, parseTransferPage, type TransferRequest, type TransferSide, } from "@/components/frontend/transfers/transferTypes"; import { ClipboardCheckIcon, FileClockIcon, RefreshCwIcon, ShieldAlertIcon, } from "lucide-react"; import type { LucideIcon } from "lucide-react"; interface TransfersViewProps { readonly currentUserId: number; readonly canResolve: boolean; } interface ApprovalEntry { readonly request: TransferRequest; readonly sides: readonly TransferSide[]; } const TRANSFER_QUERY = new URLSearchParams({ depth: "1", limit: "100", sort: "-createdAt", }); function approvalSides(request: TransferRequest, currentUserId: number): readonly TransferSide[] { if (request.status !== "pending") return []; const sides: TransferSide[] = []; if ( request.fromLeaderDecision === "pending" && assignmentLeaderId(request.fromAssignment) === currentUserId ) { sides.push("from"); } if ( request.toLeaderDecision === "pending" && assignmentLeaderId(request.toAssignment) === currentUserId ) { sides.push("to"); } return sides; } function TransferSection({ icon: Icon, title, count, children, }: { readonly icon: LucideIcon; readonly title: string; readonly count: number; readonly children: ReactNode; }) { return (
{children}
); } function EmptyTransferSection({ message }: { readonly message: string }) { return ( {message} ); } function LoadingTransferSections({ canResolve }: { readonly canResolve: boolean }) { return (
{["my-requests", "approvals", ...(canResolve ? ["appeals"] : [])].map((section) => (
))}
); } export function TransfersView({ currentUserId, canResolve }: TransfersViewProps) { const [requests, setRequests] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const loadRequests = useCallback(async () => { setLoading(true); setError(null); try { const response = await fetch(`/api/assignment-transfers?${TRANSFER_QUERY.toString()}`, { cache: "no-store", }); if (!response.ok) throw new Error("Could not load transfer requests."); const body: unknown = await response.json(); const parsed = parseTransferPage(body); if (!parsed) throw new Error("The transfer request response was invalid."); setRequests(parsed); } catch (caughtError) { setError( caughtError instanceof Error ? caughtError.message : "Could not load transfer requests.", ); } finally { setLoading(false); } }, []); useEffect(() => { void loadRequests(); }, [loadRequests]); const myRequests = useMemo( () => (requests ?? []).filter((request) => relationId(request.requester) === currentUserId), [currentUserId, requests], ); const approvals = useMemo( () => (requests ?? []) .map((request) => ({ request, sides: approvalSides(request, currentUserId) })) .filter((entry) => entry.sides.length > 0), [currentUserId, requests], ); const appeals = useMemo( () => (canResolve ? (requests ?? []).filter((request) => request.status === "awaiting_superuser") : []), [canResolve, requests], ); if (!requests && loading) return ; if (!requests && error) { return (
Transfer requests unavailable {error}
); } return (
{error && ( Could not refresh transfer requests {error} )}
{loading && Refreshing requests...}
{myRequests.length === 0 ? ( ) : (
{myRequests.map((request) => ( ))}
)}
{approvals.length === 0 ? ( ) : (
{approvals.map((entry) => ( entry.sides.map((side) => ( )) ))}
)}
{canResolve && ( {appeals.length === 0 ? ( ) : (
{appeals.map((request) => ( ))}
)}
)}
); }