mirror of
https://github.com/jaypyles/Scraperr.git
synced 2025-12-16 04:36:14 +00:00
255 lines
7.3 KiB
TypeScript
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;
|