"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 (
{title}
{count}
{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) => (
))}
)}
)}
);
}