Files
Scraperr/src/components/JobTable.tsx
2024-07-21 21:30:26 -05:00

255 lines
7.3 KiB
TypeScript

import React, { useState } from "react";
import {
IconButton,
Box,
Typography,
Tooltip,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
SelectChangeEvent,
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import SelectAllIcon from "@mui/icons-material/SelectAll";
import DownloadIcon from "@mui/icons-material/Download";
import StarIcon from "@mui/icons-material/Star";
import { useRouter } from "next/router";
import { Favorites, JobQueue } from "./jobs";
import { Job } from "../types";
interface JobTableProps {
jobs: Job[];
fetchJobs: () => void;
}
interface ColorMap {
[key: string]: string;
}
const COLOR_MAP: ColorMap = {
Queued: "rgba(255,201,5,0.25)",
Scraping: "rgba(3,104,255,0.25)",
Completed: "rgba(5,255,51,0.25)",
};
const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
const [selectedJobs, setSelectedJobs] = useState<Set<string>>(new Set());
const [allSelected, setAllSelected] = useState(false);
const [searchQuery, setSearchQuery] = useState<string>("");
const [searchMode, setSearchMode] = useState<string>("url");
const [favoriteView, setFavoriteView] = useState<boolean>(false);
const router = useRouter();
const handleDownload = async (ids: string[]) => {
const response = await fetch("/api/download", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ids: ids }),
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = `job_${ids.splice(0, 1)}.xlsx`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
} else {
console.error("Failed to download the file.");
}
};
const handleNavigate = (elements: Object[], url: string, options: any) => {
router.push({
pathname: "/",
query: {
elements: JSON.stringify(elements),
url: url,
job_options: JSON.stringify(options),
},
});
};
const handleSelectJob = (id: string) => {
setSelectedJobs((prevSelected) => {
const newSelected = new Set(prevSelected);
if (newSelected.has(id)) {
newSelected.delete(id);
} else {
newSelected.add(id);
}
return newSelected;
});
};
const handleSelectAll = () => {
if (allSelected) {
setSelectedJobs(new Set());
} else {
const allJobIds = new Set(jobs.map((job) => job.id));
setSelectedJobs(allJobIds);
}
setAllSelected(!allSelected);
};
const handleDeleteSelected = async () => {
const response = await fetch("/api/delete-scrape-jobs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ids: Array.from(selectedJobs) }),
});
if (response.ok) {
fetchJobs();
setSelectedJobs(new Set());
}
};
const filteredJobs = jobs.filter((job) => {
if (searchMode === "url") {
return job.url.toLowerCase().includes(searchQuery.toLowerCase());
} else if (searchMode === "id") {
return job.id.toLowerCase().includes(searchQuery.toLowerCase());
} else if (searchMode === "status") {
return job.status.toLowerCase().includes(searchQuery.toLowerCase());
}
return true;
});
const favoriteJob = async (ids: string[], field: string, value: any) => {
const postBody = {
ids: ids,
field: field,
value: value,
};
await fetch("/api/update", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(postBody),
});
await fetchJobs();
};
return (
<Box
width="100%"
bgcolor="background.default"
display="flex"
justifyContent="center"
minHeight="100vh"
>
<Box
className="flex flex-col justify-start align-center items-center"
width="100%"
maxWidth="100%"
bgcolor="background.default"
overflow="auto"
>
<Box
className="flex flex-row justify-between p-2 w-full"
bgcolor="background.paper"
>
<div className="flex flex-row w-1/2 items-center p-2">
<Typography className="mr-2" variant="body1">
Scrape Jobs
</Typography>
<Tooltip title="Select All">
<span>
<IconButton color="primary" onClick={handleSelectAll}>
<SelectAllIcon />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Delete Selected">
<span>
<IconButton
color="secondary"
onClick={handleDeleteSelected}
disabled={selectedJobs.size === 0}
>
<DeleteIcon />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Download Selected">
<span>
<IconButton
color="primary"
onClick={() => handleDownload(Array.from(selectedJobs))}
disabled={selectedJobs.size === 0}
>
<DownloadIcon />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Favorites">
<span>
<IconButton
color={favoriteView ? "warning" : "default"}
onClick={() => setFavoriteView(!favoriteView)}
>
<StarIcon />
</IconButton>
</span>
</Tooltip>
</div>
<div className="flex flex-row space-x-2 w-1/2">
<TextField
label="Search"
variant="outlined"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-3/4"
/>
<FormControl className="w-1/2">
<InputLabel id="search-mode-label">Search Mode</InputLabel>
<Select
labelId="search-mode-label"
id="search-mode"
value={searchMode}
label="Mode"
onChange={(e: SelectChangeEvent) =>
setSearchMode(e.target.value as string)
}
>
<MenuItem value="url">URL</MenuItem>
<MenuItem value="id">ID</MenuItem>
<MenuItem value="status">Status</MenuItem>
</Select>
</FormControl>
</div>
</Box>
<Box sx={{ overflow: "auto" }}>
{!favoriteView ? (
<JobQueue
stateProps={{ selectedJobs, filteredJobs }}
colors={COLOR_MAP}
onDownload={handleDownload}
onNavigate={handleNavigate}
onSelectJob={handleSelectJob}
onFavorite={favoriteJob}
></JobQueue>
) : (
<Favorites
stateProps={{ selectedJobs, filteredJobs }}
onNavigate={handleNavigate}
onSelectJob={handleSelectJob}
onFavorite={favoriteJob}
></Favorites>
)}
</Box>
</Box>
</Box>
);
};
export default JobTable;