Skip to content

Commit

Permalink
Paginate results
Browse files Browse the repository at this point in the history
  • Loading branch information
gausie committed Sep 30, 2024
1 parent 0b547b5 commit a37430b
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 44 deletions.
79 changes: 79 additions & 0 deletions packages/excavator-web/app/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
Button,
ButtonGroup,
Flex,
HStack,
Select,
Text,
} from "@chakra-ui/react";
import { useMemo } from "react";

type Props = {
pages: number;
value: number;
onChange: (page: number) => any;
};

export function Pagination({ pages, value, onChange }: Props) {
const options = useMemo(
() =>
[...Array(pages).keys()].map((i) => (
<option key={i} value={i}>
{i + 1}
</option>
)),
[pages],
);

return (
<Flex justifyContent="space-between">
<ButtonGroup isAttached>
<Button
isDisabled={value === 0}
onClick={() => onChange(0)}
title="First Page"
>
{"<<"}
</Button>
<Button
isDisabled={value === 0}
onClick={() => onChange(value - 1)}
title="Previous Page"
>
{"<"}
</Button>
</ButtonGroup>
<HStack>
<Text>Page</Text>
<Select
title="Current page"
textAlign="center"
value={value}
onChange={(e) => {
onChange(Number(e.target.value));
}}
width="auto"
>
{options}
</Select>{" "}
<Text>of {pages}</Text>
</HStack>
<ButtonGroup isAttached>
<Button
isDisabled={value >= pages - 1}
onClick={() => onChange(value + 1)}
title="Next Page"
>
{">"}
</Button>
<Button
isDisabled={value >= pages - 1}
onClick={() => onChange(pages - 1)}
title="Last Page"
>
{">>"}
</Button>
</ButtonGroup>
</Flex>
);
}
98 changes: 65 additions & 33 deletions packages/excavator-web/app/routes/projects.$project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,26 @@ import {
Thead,
Tr,
} from "@chakra-ui/react";
import { getSpadingDataCounts } from "@prisma/client/sql";
import { LoaderFunctionArgs, MetaFunction, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { useLoaderData, useLocation, useNavigate } from "@remix-run/react";
import { projects } from "excavator-projects";
import { useCallback } from "react";

import { Frequency } from "../components/Frequency.js";
import { Pagination } from "../components/Pagination.js";
import { ProjectHeader } from "../components/ProjectHeader.js";
import { db } from "../db.server.js";
import { fromSlug, getValuesInKeyOrder } from "../utils/utils.js";
import { loadProjectData } from "../utils/utils.server.js";

export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [{ title: `Excavator ♠️ - ${data?.project.name}` }];
};

export async function loader({ params }: LoaderFunctionArgs) {
const PER_PAGE = 50;

export async function loader({ params, request }: LoaderFunctionArgs) {
const page = (Number(new URL(request.url).searchParams.get("page")) || 1) - 1;
const projectName = fromSlug(params.project || "");

const project = projects.find((p) => p.name === projectName);
Expand All @@ -34,23 +39,46 @@ export async function loader({ params }: LoaderFunctionArgs) {
where: { data: { project: { equals: projectName, mode: "insensitive" } } },
});

const data = await loadProjectData(projectName);
const pages = Math.ceil(
(
await db.spadingData.groupBy({
by: ["dataHash"],
where: { project: { equals: projectName, mode: "insensitive" } },
})
).length / PER_PAGE,
);

const data = await db.$queryRawTyped(
getSpadingDataCounts(project.name, page * PER_PAGE, PER_PAGE),
);

return json({
projectNames: projects.map((p) => p.name).sort(),
project,
data,
total,
page,
pages,
});
}

export default function Project() {
const { data, total, project, projectNames } = useLoaderData<typeof loader>();
const { data, total, project, projectNames, pages, page } =
useLoaderData<typeof loader>();

const headers = Object.keys(data.at(0)?.data ?? {});

const navigate = useNavigate();

const changePage = useCallback(
(nextPage: number) => {
navigate(`?page=${nextPage + 1}`);
},
[navigate],
);

return (
<Stack spacing={8} mt={8}>
<Stack spacing={8} my={8}>
<ProjectHeader project={project} projects={projectNames} />
{project.completed && (
<Alert>
Expand All @@ -60,35 +88,39 @@ export default function Project() {
{data.length === 0 ? (
<Alert>No data for this project yet - you better get excavating!</Alert>
) : (
<TableContainer>
<Table>
<Thead>
<Tr>
<Th>
<span title="Frequency">&#119891;</span>
</Th>
{headers.map((h) => (
<Th key={h}>{h.replace(/_/g, " ")}</Th>
))}
</Tr>
</Thead>
<Tbody>
{data.map((d) => (
<Tr key={d.dataHash}>
<Td>
<Frequency count={d.count ?? 0} total={total} />
</Td>
{getValuesInKeyOrder(
d.data as Record<string, any>,
headers,
).map((v, i) => (
<Td key={headers[i]}>{String(v)}</Td>
<Stack>
<Pagination pages={pages} value={page} onChange={changePage} />
<TableContainer>
<Table>
<Thead>
<Tr>
<Th>
<span title="Frequency">&#119891;</span>
</Th>
{headers.map((h) => (
<Th key={h}>{h.replace(/_/g, " ")}</Th>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Thead>
<Tbody>
{data.map((d) => (
<Tr key={d.dataHash}>
<Td>
<Frequency count={d.count ?? 0} total={total} />
</Td>
{getValuesInKeyOrder(
d.data as Record<string, any>,
headers,
).map((v, i) => (
<Td key={headers[i]}>{String(v)}</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Pagination pages={pages} value={page} onChange={changePage} />
</Stack>
)}
</Stack>
);
Expand Down
7 changes: 5 additions & 2 deletions packages/excavator-web/app/routes/projects.$project[.csv].tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { getSpadingDataCounts } from "@prisma/client/sql";
import {
LoaderFunctionArgs,
createReadableStreamFromReadable,
} from "@remix-run/node";
import { stringify } from "csv-stringify/sync";
import { Readable } from "node:stream";

import { db } from "../db.server.js";
import { fromSlug } from "../utils/utils.js";
import { loadProjectData } from "../utils/utils.server.js";

export async function loader({ params }: LoaderFunctionArgs) {
const project = fromSlug(params.project || "");

const data = await loadProjectData(project);
const data = await db.$queryRawTyped(
getSpadingDataCounts(project, 0, Infinity),
);

if (data.length === 0) {
throw new Response("No data found for this project", { status: 404 });
Expand Down
7 changes: 0 additions & 7 deletions packages/excavator-web/app/utils/utils.server.ts

This file was deleted.

9 changes: 7 additions & 2 deletions packages/excavator-web/prisma/sql/getSpadingDataCounts.sql
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
-- @param {String} $1:project
-- @param {Int} $2:offset
-- @param {Int} $3:limit For no limit, use NULL or "ALL"
SELECT
"SpadingData"."dataHash",
"SpadingData"."data",
Expand All @@ -8,7 +11,9 @@ LEFT JOIN
"Report"
ON
"SpadingData"."id" = "Report"."dataId"
WHERE "project" = $1
WHERE "project" LIKE $1
GROUP BY
"SpadingData"."dataHash",
"SpadingData"."data";
"SpadingData"."data"
OFFSET $2
LIMIT $3;

0 comments on commit a37430b

Please sign in to comment.