1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113import { QueryClient, useQuery } from '@tanstack/react-query';
import { ColumnDef } from '@tanstack/react-table';
import { useLoaderData } from 'react-router-dom';
import { SEO } from '~/components/layout';
import DataTable from '~/components/tables/data-table';
import { scansQuery } from '~/queries';
import { getScan } from '~/services';
interface Scan {
jobId: string;
createdAt: string;
results: string;
url: {
id: string;
url: string;
};
property: {
id: string;
name: string;
};
processing: boolean;
}
const scansColumns: ColumnDef<Scan>[] = [
{
accessorKey: 'jobId',
header: 'Job ID',
cell: ({ row }) => <span>{row.getValue('jobId')}</span>,
},
{
accessorKey: 'createdAt',
header: 'Date',
cell: ({ row }) => <span>{new Date(row.original.createdAt).toLocaleString()}</span>,
},
{
accessorKey: 'url',
header: 'URL',
cell: ({ row }) => <a className='text-blue-500 hover:opacity-50' target='_blank' href={row.original.url.url}>{row.original.url.url}</a>,
},
{
accessorKey: 'property',
header: 'Property',
cell: ({ row }) => <span>{row.original.property?.name}</span>,
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => <span className={`${row.original.processing ? 'bg-[#663808]' : 'bg-[#005031]'} text-white px-2 py-1 rounded-full`}>{row.original.processing ? 'Processing' : 'Complete'}</span>,
},
{
accessorKey: 'report',
header: 'Raw Data',
cell: ({ row }) => row.original.processing ? <span className='select-none text-[#666]'>Not ready</span> : <button className='text-blue-500 hover:opacity-50' onClick={async () => {
const element = document.getElementById('downloadReportLink');
if (element) {
const response = await getScan(row.original.id);
console.log(response);
element.setAttribute('href', 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(response.results)));
element.setAttribute('download', 'results.json');
element.click();
}
}}>Download</button>,
},
];
export const scansLoader = (queryClient: QueryClient) => async () => {
const initialScans = await queryClient.ensureQueryData(scansQuery());
return { initialScans };
};
const Scans = () => {
const { initialScans } = useLoaderData() as Awaited<
ReturnType<ReturnType<typeof scansLoader>>
>;
const { data: scans } = useQuery({
...scansQuery(),
initialData: initialScans,
refetchInterval: 1000,
});
return (
<>
<SEO
title="Scans - Equalify"
description="View and manage your scans on Equalify."
url="https://dashboard.equalify.app/scans"
/>
<h1 id="scans-heading" className="text-2xl font-bold md:text-3xl">
Scans
</h1>
<section
aria-labelledby="queue-heading"
className="mt-7 space-y-6 rounded-lg bg-white p-6 shadow"
>
<h2 id="queue-heading" className="text-lg">
Queue
</h2>
<div className="w-full overflow-x-auto">
{scans && (
<DataTable columns={scansColumns} data={scans ?? []} type="scans" />
)}
</div>
</section>
<a id="downloadReportLink" style={{ display: 'none' }}></a>
</>
);
};
export default Scans;