Skip to content
This repository was archived by the owner on Sep 22, 2025. It is now read-only.

Commit 1a1d9d7

Browse files
committed
Added pages table to properties screen
1 parent a5cc1b1 commit 1a1d9d7

File tree

4 files changed

+339
-11
lines changed

4 files changed

+339
-11
lines changed

src/queries/properties.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const propertiesQuery = () => queryOptions({
1111
export const propertyQuery = (propertyId: string) => queryOptions({
1212
queryKey: ['property', propertyId],
1313
queryFn: async () => {
14-
const property = await getPropertyById(propertyId);
14+
const property = await getPropertyById(propertyId, "10", "10");
1515
if (!property) {
1616
throw new Response('', {
1717
status: 404,

src/routes/protected/pages/page-detail.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ const pageDetail = () => {
186186
<>
187187
<SEO
188188
title="Pages - Equalify"
189-
description="Manage and monitor your properties on Equalify to improve their accessibility."
190-
url="https://dashboard.equalify.app/properties"
189+
description="Manage and monitor your pages on Equalify to improve their accessibility."
190+
url={`https://dashboard.equalify.app/pages/${pageId}`}
191191
/>
192192
<div className="flex w-full flex-col-reverse justify-between sm:flex-row sm:items-center">
193193
<div className="flex flex-row items-center gap-2">

src/routes/protected/properties/edit-property.tsx

Lines changed: 319 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from 'react';
2-
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
2+
import { CheckCircledIcon, DownloadIcon, ExclamationTriangleIcon, ReloadIcon } from '@radix-ui/react-icons';
33
import {
44
QueryClient,
55
useMutation,
@@ -8,20 +8,32 @@ import {
88
} from '@tanstack/react-query';
99
import {
1010
ActionFunctionArgs,
11+
Link,
1112
LoaderFunctionArgs,
1213
redirect,
1314
useLoaderData,
1415
useNavigate,
1516
} from 'react-router-dom';
17+
import {
18+
Table,
19+
TableBody,
20+
TableCell,
21+
TableHead,
22+
TableHeader,
23+
TableRow,
24+
} from '~/components/tables';
1625
import { toast } from '~/components/alerts';
1726
import { Button } from '~/components/buttons';
1827
import { DangerDialog } from '~/components/dialogs';
1928
import { PropertyForm } from '~/components/forms';
2029
import { SEO } from '~/components/layout';
2130
import { propertyQuery } from '~/queries/properties';
22-
import { deleteProperty, sendToScan, updateProperty } from '~/services';
31+
import { deleteProperty, getScan, IPage, IPageScan, sendToScan, updateProperty } from '~/services';
2332
import { assertNonNull } from '~/utils/safety';
2433
import { LoadingProperty } from './loading';
34+
import { ColumnDef, flexRender, getCoreRowModel, PaginationState, useReactTable } from '@tanstack/react-table';
35+
import React from 'react';
36+
import * as Tooltip from '@radix-ui/react-tooltip';
2537

2638
/**
2739
* Loader function to fetch property data
@@ -98,6 +110,12 @@ const EditProperty = () => {
98110
initialData: initialProperty,
99111
});
100112

113+
// pagination
114+
const [pagination, setPagination] = useState<PaginationState>({
115+
pageIndex: 0,
116+
pageSize: 10,
117+
});
118+
101119
const { mutate: deleteMutate } = useMutation({
102120
mutationFn: () => {
103121
const response = deleteProperty(propertyId!);
@@ -152,6 +170,155 @@ const EditProperty = () => {
152170
setIsSending(false);
153171
}
154172
};
173+
174+
// returns the index of the newest scan
175+
const getIndexOfNewestScan = (scansArray: IPageScan[]) => {
176+
return scansArray.reduce(
177+
(highestIndex, scan, index, arr) =>
178+
new Date(scan.updated_at).getTime() >
179+
new Date(arr[highestIndex].updated_at).getTime()
180+
? index
181+
: highestIndex,
182+
0,
183+
);
184+
};
185+
186+
// Define the columns
187+
const columns = React.useMemo<ColumnDef<IPage>[]>(
188+
() => [
189+
{
190+
accessorKey: 'url',
191+
header: 'URL',
192+
cell: ({ row }) => (
193+
<Link
194+
className="text-blue-500 hover:opacity-50"
195+
to={'./'+row.original.id}
196+
>
197+
{row.original.url}
198+
</Link>
199+
),
200+
},
201+
{
202+
accessorKey: 'status',
203+
header: 'Status',
204+
cell: ({ row }) => (
205+
<div>
206+
{row.original?.scans.length > 0 ? (
207+
row.original.scans[getIndexOfNewestScan(row.original.scans)].processing ? (
208+
<ReloadIcon aria-label="Processing" className="animate-spin" />
209+
):(
210+
211+
<div className="inline-flex items-center">
212+
<Tooltip.Provider>
213+
<Tooltip.Root>
214+
<Tooltip.Trigger>
215+
<CheckCircledIcon aria-label="Complete" />
216+
</Tooltip.Trigger>
217+
<Tooltip.Portal>
218+
<Tooltip.Content
219+
className="TooltipContent"
220+
sideOffset={5}
221+
>
222+
<div className="text-center text-sm">
223+
Last scanned <br />
224+
{new Date(
225+
row.original.scans[
226+
getIndexOfNewestScan(row.original.scans)
227+
].updated_at,
228+
).toLocaleString()}
229+
</div>
230+
<Tooltip.Arrow className="TooltipArrow" />
231+
</Tooltip.Content>
232+
</Tooltip.Portal>
233+
</Tooltip.Root>
234+
</Tooltip.Provider>
235+
</div>
236+
)
237+
238+
) : (
239+
<ExclamationTriangleIcon aria-label="No Scans Found!"/>
240+
)}
241+
</div>
242+
),
243+
},
244+
{
245+
accessorKey: 'report',
246+
header: 'Results JSON',
247+
cell: ({ row }) =>
248+
row.original?.scans.length > 0 ? (
249+
row.original.scans[getIndexOfNewestScan(row.original.scans)]
250+
.processing ? (
251+
<span className="select-none text-[#666]">Not ready</span>
252+
) : (
253+
<button
254+
className="inline-flex items-center text-blue-500 hover:opacity-50"
255+
onClick={async () => {
256+
const element = document.getElementById('downloadReportLink');
257+
if (element) {
258+
const response = await getScan(
259+
row.original.scans[
260+
getIndexOfNewestScan(row.original.scans)
261+
].id,
262+
);
263+
element.setAttribute(
264+
'href',
265+
'data:text/json;charset=utf-8,' +
266+
encodeURIComponent(JSON.stringify(response)),
267+
);
268+
element.setAttribute('download', 'results.json');
269+
element.click();
270+
} else {
271+
console.log(
272+
'Error fetching scan:',
273+
row.original.scans[
274+
getIndexOfNewestScan(row.original.scans)
275+
].id,
276+
);
277+
}
278+
}}
279+
>
280+
<DownloadIcon className="ml-1" aria-label="Download" />
281+
</button>
282+
)
283+
) : (
284+
<></>
285+
),
286+
},
287+
],
288+
[],
289+
);
290+
291+
//const defaultData = React.useMemo(() => [], []);
292+
// data fetching
293+
/* const dataQuery = useQuery({
294+
queryKey: ['property', pagination],
295+
queryFn: async () => {
296+
const theParams = {
297+
property_id:
298+
limit: pagination.pageSize,
299+
offset: pagination.pageIndex * pagination.pageSize,
300+
};
301+
console.log(theParams);
302+
return getPages({ params: theParams });
303+
},
304+
placeholderData: keepPreviousData,
305+
}); */
306+
const table = useReactTable({
307+
data: property.urls as IPage[] ?? initialProperty.urls,
308+
columns,
309+
// pageCount: dataQuery.data?.pageCount ?? -1, //you can now pass in `rowCount` instead of pageCount and `pageCount` will be calculated internally (new in v8.13.0)
310+
//rowCount: dataQuery.data?.total, // new in v8.13.0 - alternatively, just pass in `pageCount` directly
311+
state: {
312+
pagination,
313+
},
314+
enableRowSelection: false,
315+
onPaginationChange: setPagination,
316+
getCoreRowModel: getCoreRowModel(),
317+
manualPagination: true, //we're doing manual "server-side" pagination
318+
//debugTable: true,
319+
});
320+
321+
155322
return (
156323
<>
157324
<SEO
@@ -220,6 +387,156 @@ const EditProperty = () => {
220387
</div>
221388
</section>
222389

390+
{table.getRowCount() === 0 ? (
391+
<div className="mt-7 text-center">
392+
<h2 className="text-xl font-semibold text-gray-700">
393+
No Pages Added
394+
</h2>
395+
<p className="mt-2 text-gray-600">
396+
You haven't added any pages yet. Get started by adding your first
397+
page and monitor its accessibility status.
398+
</p>
399+
<Link
400+
to="/pages/add"
401+
className="mt-4 inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md bg-[#005031] px-4 py-2 text-sm text-white shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#1D781D] focus-visible:ring-offset-2"
402+
>
403+
Add Your First Page
404+
</Link>
405+
</div>
406+
) : (
407+
<section
408+
aria-labelledby="pages-list-heading"
409+
className="mt-7 space-y-6 rounded-lg bg-white p-6 shadow"
410+
>
411+
<div className="w-full overflow-x-auto">
412+
<div className="p-2">
413+
<Table role="table" aria-label="Pages List">
414+
<TableHeader>
415+
{table.getHeaderGroups().map((headerGroup) => (
416+
<TableRow key={headerGroup.id}>
417+
{headerGroup.headers.map((header) => {
418+
return (
419+
<TableHead key={header.id} role="columnheader">
420+
{header.isPlaceholder
421+
? null
422+
: flexRender(
423+
header.column.columnDef.header,
424+
header.getContext(),
425+
)}
426+
</TableHead>
427+
);
428+
})}
429+
</TableRow>
430+
))}
431+
</TableHeader>
432+
<TableBody>
433+
{table.getRowModel().rows?.length ? (
434+
table.getRowModel().rows.map((row) => (
435+
<TableRow
436+
key={row.id}
437+
role="row"
438+
>
439+
{row.getVisibleCells().map((cell) => (
440+
<TableCell key={cell.id} role="cell">
441+
{flexRender(
442+
cell.column.columnDef.cell,
443+
cell.getContext(),
444+
)}
445+
</TableCell>
446+
))}
447+
</TableRow>
448+
))
449+
) : (
450+
<TableRow role="row">
451+
<TableCell
452+
colSpan={columns.length}
453+
className="h-24 text-center"
454+
role="cell"
455+
>
456+
No results.
457+
</TableCell>
458+
</TableRow>
459+
)}
460+
</TableBody>
461+
</Table>
462+
<nav
463+
role="navigation"
464+
aria-label="Pagination Navigation"
465+
className="flex items-center gap-2"
466+
>
467+
<button
468+
className="rounded border p-1"
469+
onClick={() => table.firstPage()}
470+
disabled={!table.getCanPreviousPage()}
471+
>
472+
{'<<'}
473+
</button>
474+
<button
475+
className="rounded border p-1"
476+
onClick={() => table.previousPage()}
477+
disabled={!table.getCanPreviousPage()}
478+
>
479+
{'<'}
480+
</button>
481+
<button
482+
className="rounded border p-1"
483+
onClick={() => table.nextPage()}
484+
disabled={!table.getCanNextPage()}
485+
>
486+
{'>'}
487+
</button>
488+
<button
489+
className="rounded border p-1"
490+
onClick={() => table.lastPage()}
491+
disabled={!table.getCanNextPage()}
492+
>
493+
{'>>'}
494+
</button>
495+
<span className="flex items-center gap-1">
496+
<div>Page</div>
497+
<strong>
498+
{table.getState().pagination.pageIndex + 1} of{' '}
499+
{table.getPageCount().toLocaleString()}
500+
</strong>
501+
</span>
502+
<span className="flex items-center gap-1">
503+
| Go to page:
504+
<input
505+
type="number"
506+
min="1"
507+
max={table.getPageCount()}
508+
defaultValue={table.getState().pagination.pageIndex + 1}
509+
onChange={(e) => {
510+
const page = e.target.value
511+
? Number(e.target.value) - 1
512+
: 0;
513+
table.setPageIndex(page);
514+
}}
515+
className="w-16 rounded border p-1"
516+
/>
517+
</span>
518+
<select
519+
value={table.getState().pagination.pageSize}
520+
onChange={(e) => {
521+
table.setPageSize(Number(e.target.value));
522+
}}
523+
>
524+
{[10, 20, 30, 40, 50].map((pageSize) => (
525+
<option key={pageSize} value={pageSize}>
526+
Show {pageSize}
527+
</option>
528+
))}
529+
</select>
530+
531+
</nav>
532+
533+
534+
</div>
535+
</div>
536+
<a id="downloadReportLink" style={{ display: 'none' }}></a>
537+
</section>
538+
)}
539+
223540
<section
224541
aria-labelledby="danger-zone-heading"
225542
className="mt-7 space-y-6 rounded-lg bg-white p-6 shadow"

0 commit comments

Comments
 (0)