mirror of
https://github.com/jaypyles/Scraperr.git
synced 2025-12-12 10:45:58 +00:00
256 lines
7.8 KiB
TypeScript
256 lines
7.8 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableRow,
|
|
IconButton,
|
|
Box,
|
|
Typography,
|
|
Accordion,
|
|
AccordionSummary,
|
|
AccordionDetails,
|
|
Checkbox,
|
|
Tooltip,
|
|
Button,
|
|
} from "@mui/material";
|
|
import DeleteIcon from "@mui/icons-material/Delete";
|
|
import SelectAllIcon from "@mui/icons-material/SelectAll";
|
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
import { useRouter } from "next/router";
|
|
|
|
interface Job {
|
|
id: string;
|
|
url: string;
|
|
elements: Object[];
|
|
result: Object;
|
|
time_created: Date;
|
|
}
|
|
|
|
interface JobTableProps {
|
|
jobs: Job[];
|
|
fetchJobs: () => void;
|
|
}
|
|
|
|
const JobTable: React.FC<JobTableProps> = ({ jobs, fetchJobs }) => {
|
|
const [selectedJobs, setSelectedJobs] = useState<Set<string>>(new Set());
|
|
const [allSelected, setAllSelected] = useState(false);
|
|
const router = useRouter();
|
|
|
|
const handleDownload = async (id: string) => {
|
|
const response = await fetch("/api/download", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ id: id }),
|
|
});
|
|
|
|
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_${id}.csv`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
} else {
|
|
console.error("Failed to download the file.");
|
|
}
|
|
};
|
|
|
|
const handleNavigate = (elements: Object[], url: string) => {
|
|
router.push({
|
|
pathname: "/",
|
|
query: {
|
|
elements: JSON.stringify(elements),
|
|
url: url,
|
|
},
|
|
});
|
|
};
|
|
|
|
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());
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Box
|
|
width="100%"
|
|
bgcolor="background.default"
|
|
display="flex"
|
|
justifyContent="center"
|
|
minHeight="100vh"
|
|
p={3}
|
|
>
|
|
<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 w-3/4 items-center p-2"
|
|
bgcolor="background.paper"
|
|
>
|
|
<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>
|
|
</Box>
|
|
<Box sx={{ overflow: "auto", width: "75%" }}>
|
|
<Table sx={{ tableLayout: "fixed", width: "100%" }}>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Select</TableCell>
|
|
<TableCell>Id</TableCell>
|
|
<TableCell>Url</TableCell>
|
|
<TableCell>Elements</TableCell>
|
|
<TableCell>Result</TableCell>
|
|
<TableCell>Time Created</TableCell>
|
|
<TableCell>Actions</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{jobs.map((row, index) => (
|
|
<TableRow key={index}>
|
|
<TableCell padding="checkbox">
|
|
<Checkbox
|
|
checked={selectedJobs.has(row.id)}
|
|
onChange={() => handleSelectJob(row.id)}
|
|
/>
|
|
</TableCell>
|
|
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
{row.id}
|
|
</Box>
|
|
</TableCell>
|
|
<TableCell sx={{ maxWidth: 200, overflow: "auto" }}>
|
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
{row.url}
|
|
</Box>
|
|
</TableCell>
|
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
{JSON.stringify(row.elements)}
|
|
</Box>
|
|
</TableCell>
|
|
<TableCell
|
|
sx={{ maxWidth: 150, overflow: "auto", padding: 0 }}
|
|
>
|
|
<Accordion sx={{ margin: 0, padding: 0.5 }}>
|
|
<AccordionSummary
|
|
expandIcon={<ExpandMoreIcon />}
|
|
aria-controls="panel1a-content"
|
|
id="panel1a-header"
|
|
sx={{
|
|
minHeight: 0,
|
|
"&.Mui-expanded": { minHeight: 0 },
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
maxHeight: 150,
|
|
overflow: "auto",
|
|
width: "100%",
|
|
}}
|
|
>
|
|
<Typography sx={{ fontSize: "0.875rem" }}>
|
|
Show Result
|
|
</Typography>
|
|
</Box>
|
|
</AccordionSummary>
|
|
<AccordionDetails sx={{ padding: 1 }}>
|
|
<Box sx={{ maxHeight: 200, overflow: "auto" }}>
|
|
<Typography
|
|
sx={{
|
|
fontSize: "0.875rem",
|
|
whiteSpace: "pre-wrap",
|
|
}}
|
|
>
|
|
{JSON.stringify(row.result, null, 2)}
|
|
</Typography>
|
|
</Box>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
</TableCell>
|
|
<TableCell sx={{ maxWidth: 150, overflow: "auto" }}>
|
|
<Box sx={{ maxHeight: 100, overflow: "auto" }}>
|
|
{new Date(row.time_created).toLocaleString()}
|
|
</Box>
|
|
</TableCell>
|
|
<TableCell sx={{ maxWidth: 100, overflow: "auto" }}>
|
|
<Button
|
|
onClick={() => {
|
|
handleDownload(row.id);
|
|
}}
|
|
>
|
|
Download
|
|
</Button>
|
|
<Button
|
|
onClick={() => handleNavigate(row.elements, row.url)}
|
|
>
|
|
Rerun
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default JobTable;
|