import React, { useState, useEffect, useRef } from "react"; import { toast } from "react-toastify"; import { DataTable } from "primereact/datatable"; import { Column } from "primereact/column"; import { Dropdown } from "primereact/dropdown"; import { Button } from "@mui/joy"; import { Dialog } from "primereact/dialog"; import { authFetch } from "@/utils/authFetch"; import { confirmDialog, ConfirmDialog } from "primereact/confirmdialog"; import BreadcrumbNav from "./BreadcrumbNav"; import { API_URL } from "@/config"; const STATUS_OPTIONS = ["queued", "started", "compressing", "finished", "failed"]; const TAR_BASE_URL = "https://codey.lol/m/m2"; // configurable prefix export default function RequestManagement() { const [requests, setRequests] = useState([]); const [filterType, setFilterType] = useState(null); const [filterStatus, setFilterStatus] = useState(null); const [filteredRequests, setFilteredRequests] = useState([]); const [selectedRequest, setSelectedRequest] = useState(null); const [isDialogVisible, setIsDialogVisible] = useState(false); const pollingRef = useRef(null); const pollingDetailRef = useRef(null); const tarballUrl = (absPath, quality) => { if (!absPath) return null; const filename = absPath.split("/").pop(); // get "SOMETHING.tar.gz" return `${TAR_BASE_URL}/${quality}/${filename}`; }; const fetchJobs = async () => { try { const res = await authFetch(`${API_URL}/trip/jobs/list`); if (!res.ok) throw new Error("Failed to fetch jobs"); const data = await res.json(); setRequests(Array.isArray(data.jobs) ? data.jobs : []); } catch (err) { console.error(err); if (!toast.isActive('fetch-fail-toast')) { toast.error("Failed to fetch jobs list", { toastId: 'fetch-fail-toast', }); } } }; const fetchJobDetail = async (jobId) => { try { const res = await authFetch(`${API_URL}/trip/job/${jobId}`); if (!res.ok) throw new Error("Failed to fetch job details"); return await res.json(); } catch (err) { console.error(err); if (!toast.isActive('fetch-job-fail-toast')) { toast.error("Failed to fetch job details", { toastId: "fetch-job-fail-toast", }); } return null; } }; useEffect(() => { fetchJobs(); }, []); useEffect(() => { if (isDialogVisible && selectedRequest) { // Start polling const poll = async () => { const updated = await fetchJobDetail(selectedRequest.id); if (updated) setSelectedRequest(updated); }; pollingDetailRef.current = setInterval(poll, 1500); return () => { if (pollingDetailRef.current) { clearInterval(pollingDetailRef.current); pollingDetailRef.current = null; } }; } }, [isDialogVisible, selectedRequest?.id]); useEffect(() => { const hasActive = requests.some((j) => ["queued", "started", "compressing"].includes(j.status)); if (hasActive && !pollingRef.current) pollingRef.current = setInterval(fetchJobs, 1500); else if (!hasActive && pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; } return () => { if (pollingRef.current) clearInterval(pollingRef.current); pollingRef.current = null; }; }, [requests]); useEffect(() => { const filtered = requests.filter((r) => { const typeMatch = !filterType || r.type === filterType; const statusMatch = filterStatus === "all" || filterStatus === null || r.status === filterStatus; return typeMatch && statusMatch; }); setFilteredRequests(filtered); }, [filterType, filterStatus, requests]); const getStatusColorClass = (status) => { switch (status) { case "queued": return "bg-yellow-500 text-black"; case "started": return "bg-blue-500 text-white"; case "compressing": return "bg-orange-500 text-white"; case "finished": return "bg-green-500 text-white"; case "failed": return "bg-red-500 text-white"; default: return "bg-gray-500 text-white"; } }; const getQualityColorClass = (quality) => { switch (quality) { case "FLAC": return "bg-green-500 text-white"; case "Lossy": return "bg-yellow-500 text-white"; default: return "bg-gray-500 text-white"; } }; const statusBodyTemplate = (rowData) => ( {rowData.status} ); const qualityBodyTemplate = (rowData) => ( {rowData.quality} ); const safeText = (val) => (val === 0 ? "0" : val || "—"); const textWithEllipsis = (val, width = "12rem") => ( {val || "—"} ); const truncate = (text, maxLen) => maxLen <= 3 ? text.slice(0, maxLen) : text.length <= maxLen ? text : text.slice(0, maxLen - 3) + '...'; const basename = (p) => (typeof p === "string" ? p.split("/").pop() : ""); const formatProgress = (p) => { if (p === null || p === undefined || p === "") return "—"; const num = Number(p); if (Number.isNaN(num)) return "—"; const pct = num > 1 ? Math.round(num) : num; return `${pct}%`; }; const confirmDelete = (requestId) => { confirmDialog({ message: "Are you sure you want to delete this request?", header: "Confirm Delete", icon: "pi pi-exclamation-triangle", accept: () => deleteRequest(requestId), }); }; const deleteRequest = (requestId) => { setRequests((prev) => prev.filter((r) => r.id !== requestId)); toast.success("Request deleted"); }; const actionBodyTemplate = (rowData) => ( ); const handleRowClick = async (e) => { const detail = await fetchJobDetail(e.data.id); if (detail) { setSelectedRequest(detail); setIsDialogVisible(true); } }; return (

Media Request Management

({ label: s, value: s }))]} onChange={(e) => setFilterStatus(e.value)} placeholder="Filter by Status" className="min-w-[180px]" />
textWithEllipsis(row.id, "6rem")} /> textWithEllipsis(row.target, "10rem")} /> row.tracks} /> formatProgress(row.progress)} style={{ width: "8rem", textAlign: "center" }} sortable /> { const url = tarballUrl(row.tarball, row.quality || "FLAC"); return url ? ( {truncate(url.split("/").pop(), 16)} ) : ( "—" ); }} style={{ width: "10rem" }} />
setIsDialogVisible(false)} breakpoints={{ "960px": "95vw" }} modal dismissableMask className="dark:bg-neutral-900 dark:text-neutral-100" > {selectedRequest ? (
{selectedRequest.id && (

ID: {selectedRequest.id}

)} {selectedRequest.target && (

Target: {selectedRequest.target}

)} {selectedRequest.tracks && (

# Tracks: {selectedRequest.tracks}

)} {selectedRequest.status && (

Status:{" "} {selectedRequest.status}

)} {selectedRequest.progress !== undefined && selectedRequest.progress !== null && (

Progress: {formatProgress(selectedRequest.progress)}

)} {selectedRequest.enqueued_at && (

Enqueued: {new Date(selectedRequest.enqueued_at).toLocaleString()}

)} {selectedRequest.started_at && (

Started: {new Date(selectedRequest.started_at).toLocaleString()}

)} {selectedRequest.ended_at && (

Ended: {new Date(selectedRequest.ended_at).toLocaleString()}

)} {selectedRequest.tarball && (

Tarball:{" "} {tarballUrl(selectedRequest.tarball).split("/").pop()}

)} {selectedRequest.quality && (

Quality:{" "} {selectedRequest.quality}

)}
) : (

Loading...

) }
); }