Skip to content

Commit ac2a09c

Browse files
authored
Merge pull request #434 from tweedegolf/frontend-pagination
Add frontend-based pagination to relevant places
2 parents 56c3bb8 + 547efc1 commit ac2a09c

7 files changed

Lines changed: 116 additions & 29 deletions

File tree

frontend/src/components/StyledTable.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ import React from "react";
44
interface StyledTableProps {
55
headers: Array<TableThProps | string>;
66
children?: React.ReactNode[];
7+
ref?: React.RefObject<HTMLTableSectionElement | null>;
78
}
89

9-
export default function StyledTable({ headers, children }: StyledTableProps) {
10+
export default function StyledTable({ headers, children, ref }: StyledTableProps) {
1011
if (!children || children.length === 0) {
1112
return null;
1213
}
1314

1415
return (
1516
<div>
1617
<Table highlightOnHover>
17-
<Table.Thead>
18+
<Table.Thead ref={ref}>
1819
<Table.Tr>
1920
{headers.map((props, i) =>
2021
typeof props === "string" ? <Table.Th key={i}>{props}</Table.Th> : <Table.Th key={i} {...props} />

frontend/src/components/admin/ApiUserOverview.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
import { useApiUsers } from "../../hooks/useApiUsers.ts";
22
import StyledTable from "../StyledTable.tsx";
3-
import { Anchor, Group, Table } from "@mantine/core";
3+
import { Anchor, Flex, Group, Pagination, Table } from "@mantine/core";
44
import TableId from "../TableId.tsx";
55
import RoleSelect from "./RoleSelect.tsx";
66
import OrgPopover from "./OrgPopover.tsx";
77
import { formatDateTime } from "../../util.ts";
8+
import { useState } from "react";
9+
import { useScrollIntoView } from "@mantine/hooks";
10+
11+
const PER_PAGE = 20;
812

913
export default function ApiUserOverview() {
14+
const [activePage, setPage] = useState(1);
1015
const { apiUsers } = useApiUsers();
1116

12-
const rows = apiUsers.map((user) => (
17+
const { scrollIntoView, targetRef } = useScrollIntoView<HTMLDivElement>({
18+
duration: 500,
19+
offset: 100,
20+
});
21+
22+
const rows = apiUsers.slice((activePage - 1) * PER_PAGE, activePage * PER_PAGE).map((user) => (
1323
<Table.Tr key={user.id}>
1424
<Table.Td w={80}>
1525
<TableId id={user.id} />
@@ -35,10 +45,20 @@ export default function ApiUserOverview() {
3545
));
3646

3747
return (
38-
<>
48+
<div ref={targetRef}>
3949
<StyledTable headers={["ID", "Name", "Email", "Global Role", "Organizations", "Created", "Updated"]}>
4050
{rows}
4151
</StyledTable>
42-
</>
52+
<Flex justify="center" mt="md">
53+
<Pagination
54+
value={activePage}
55+
onChange={(p) => {
56+
setPage(p);
57+
scrollIntoView({ alignment: "start" });
58+
}}
59+
total={Math.ceil(apiUsers.length / PER_PAGE)}
60+
/>
61+
</Flex>
62+
</div>
4363
);
4464
}

frontend/src/components/admin/GlobalAdmin.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default function GlobalAdmin() {
1010
<>
1111
<Header name="Settings" entityType="Admin" Icon={IconGavel} />
1212
<Tabs
13+
keepMounted={false}
1314
tabs={[
1415
{
1516
route: "admin",

frontend/src/components/admin/OrganizationsOverview.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
import { ActionIcon, Anchor, Button, Flex, Table } from "@mantine/core";
1+
import { ActionIcon, Anchor, Button, Flex, Pagination, Stack, Table } from "@mantine/core";
22
import { formatDateTime } from "../../util";
33
import { useRemails } from "../../hooks/useRemails.ts";
4-
import { useDisclosure } from "@mantine/hooks";
4+
import { useDisclosure, useScrollIntoView } from "@mantine/hooks";
55
import { IconExternalLink, IconGavel, IconPlus, IconSquare, IconSquareCheck } from "@tabler/icons-react";
66
import StyledTable from "../StyledTable.tsx";
77
import { useOrganizations } from "../../hooks/useOrganizations.ts";
88
import { NewOrganization } from "../organizations/NewOrganization.tsx";
99
import TableId from "../TableId.tsx";
10+
import { useState } from "react";
11+
12+
const PER_PAGE = 20;
1013

1114
export default function OrganizationsOverview() {
1215
const [opened, { open, close }] = useDisclosure(false);
1316
const { currentOrganization, organizations } = useOrganizations();
17+
const [activePage, setPage] = useState(1);
1418

1519
const {
1620
state: { config },
1721
} = useRemails();
1822
const { navigate } = useRemails();
1923

20-
const rows = organizations.map((organization) => (
24+
const { scrollIntoView, targetRef } = useScrollIntoView<HTMLTableSectionElement>({
25+
duration: 500,
26+
offset: 100,
27+
});
28+
29+
const rows = organizations.slice((activePage - 1) * PER_PAGE, activePage * PER_PAGE).map((organization) => (
2130
<Table.Tr
2231
key={organization.id}
2332
bg={currentOrganization?.id == organization.id ? "var(--mantine-color-blue-light)" : undefined}
@@ -74,12 +83,24 @@ export default function OrganizationsOverview() {
7483
close={close}
7584
done={(newOrg) => navigate("admin.organizations", { org_id: newOrg.id })}
7685
/>
77-
<StyledTable headers={["ID", "Name", "", "Moneybird contact ID", "Quota", "Updated", ""]}>{rows}</StyledTable>
86+
<StyledTable ref={targetRef} headers={["ID", "Name", "", "Moneybird contact ID", "Quota", "Updated", ""]}>
87+
{rows}
88+
</StyledTable>
7889

7990
<Flex justify="center" mt="md">
80-
<Button onClick={() => open()} leftSection={<IconPlus />}>
81-
New Organization
82-
</Button>
91+
<Stack>
92+
<Pagination
93+
value={activePage}
94+
onChange={(p) => {
95+
setPage(p);
96+
scrollIntoView({ alignment: "start" });
97+
}}
98+
total={Math.ceil(organizations.length / PER_PAGE)}
99+
/>
100+
<Button onClick={() => open()} leftSection={<IconPlus />}>
101+
New Organization
102+
</Button>
103+
</Stack>
83104
</Flex>
84105
</>
85106
);

frontend/src/components/domains/DomainsOverview.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useDomains } from "../../hooks/useDomains.ts";
22
import { Loader } from "../../Loader.tsx";
3-
import { Flex, Group, Table, Text } from "@mantine/core";
3+
import { Flex, Group, Pagination, Stack, Table, Text } from "@mantine/core";
44
import { formatDateTime } from "../../util.ts";
55
import { IconPlus, IconServer } from "@tabler/icons-react";
6-
import { useDisclosure } from "@mantine/hooks";
6+
import { useDisclosure, useScrollIntoView } from "@mantine/hooks";
77
import { NewDomain } from "./NewDomain.tsx";
88
import { Link } from "../../Link.tsx";
99
import InfoAlert from "../InfoAlert.tsx";
@@ -14,6 +14,9 @@ import OrganizationHeader from "../organizations/OrganizationHeader.tsx";
1414
import { MaintainerButton } from "../RoleButtons.tsx";
1515
import { useProjectWithId } from "../../hooks/useProjects.ts";
1616
import { Domain } from "../../types.ts";
17+
import { useState } from "react";
18+
19+
const PER_PAGE = 20;
1720

1821
function DomainRow({ domain }: { domain: Domain }) {
1922
const project_name = useProjectWithId(domain.project_id)?.name;
@@ -59,6 +62,12 @@ function DomainRow({ domain }: { domain: Domain }) {
5962
export default function DomainsOverview() {
6063
const [opened, { open, close }] = useDisclosure(false);
6164
const { domains } = useDomains();
65+
const [activePage, setPage] = useState(1);
66+
67+
const { scrollIntoView, targetRef } = useScrollIntoView<HTMLTableSectionElement>({
68+
duration: 500,
69+
offset: 100,
70+
});
6271

6372
if (domains === null) {
6473
return <Loader />;
@@ -73,15 +82,27 @@ export default function DomainsOverview() {
7382
</InfoAlert>
7483

7584
<NewDomain opened={opened} close={close} />
76-
<StyledTable headers={["Domains", "DNS Status", "Usable by", "Updated", ""]}>
77-
{domains.map((domain) => (
85+
<StyledTable ref={targetRef} headers={["Domains", "DNS Status", "Usable by", "Updated", ""]}>
86+
{domains.slice((activePage - 1) * PER_PAGE, activePage * PER_PAGE).map((domain) => (
7887
<DomainRow domain={domain} key={domain.id} />
7988
))}
8089
</StyledTable>
8190
<Flex justify="center" mt="md">
82-
<MaintainerButton onClick={() => open()} leftSection={<IconPlus />}>
83-
New Domain
84-
</MaintainerButton>
91+
<Stack>
92+
{domains.length > PER_PAGE && (
93+
<Pagination
94+
value={activePage}
95+
onChange={(p) => {
96+
setPage(p);
97+
scrollIntoView({ alignment: "start" });
98+
}}
99+
total={Math.ceil(domains.length / PER_PAGE)}
100+
/>
101+
)}
102+
<MaintainerButton onClick={() => open()} leftSection={<IconPlus />}>
103+
New Domain
104+
</MaintainerButton>
105+
</Stack>
85106
</Flex>
86107
</>
87108
);

frontend/src/components/projects/ProjectsOverview.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1-
import { Flex, Table } from "@mantine/core";
1+
import { Flex, Pagination, Stack, Table } from "@mantine/core";
22
import { Loader } from "../../Loader";
33
import { formatDateTime } from "../../util";
44
import { useProjects } from "../../hooks/useProjects.ts";
55
import { IconPlus } from "@tabler/icons-react";
6-
import { useDisclosure } from "@mantine/hooks";
6+
import { useDisclosure, useScrollIntoView } from "@mantine/hooks";
77
import { NewProject } from "./NewProject.tsx";
88
import { Link } from "../../Link.tsx";
99
import EditButton from "../EditButton.tsx";
1010
import StyledTable from "../StyledTable.tsx";
1111
import InfoAlert from "../InfoAlert.tsx";
1212
import OrganizationHeader from "../organizations/OrganizationHeader.tsx";
1313
import { MaintainerButton } from "../RoleButtons.tsx";
14+
import { useState } from "react";
15+
16+
const PER_PAGE = 20;
1417

1518
export default function ProjectsOverview() {
1619
const [opened, { open, close }] = useDisclosure(false);
1720
const { projects } = useProjects();
21+
const [activePage, setPage] = useState(1);
22+
23+
const { scrollIntoView, targetRef } = useScrollIntoView<HTMLTableSectionElement>({
24+
duration: 500,
25+
offset: 100,
26+
});
1827

1928
if (projects === null) {
2029
return <Loader />;
2130
}
2231

23-
const rows = projects.map((project) => (
32+
const rows = projects.slice((activePage - 1) * PER_PAGE, activePage * PER_PAGE).map((project) => (
2433
<Table.Tr key={project.id}>
2534
<Table.Td>
2635
<Link to="projects.project.emails" params={{ proj_id: project.id }}>
@@ -47,12 +56,26 @@ export default function ProjectsOverview() {
4756
</InfoAlert>
4857
<NewProject opened={opened} close={close} />
4958

50-
<StyledTable headers={["Name", "Updated", ""]}>{rows}</StyledTable>
59+
<StyledTable ref={targetRef} headers={["Name", "Updated", ""]}>
60+
{rows}
61+
</StyledTable>
5162

5263
<Flex justify="center" mt="md">
53-
<MaintainerButton onClick={() => open()} leftSection={<IconPlus />}>
54-
New Project
55-
</MaintainerButton>
64+
<Stack>
65+
{projects.length > PER_PAGE && (
66+
<Pagination
67+
value={activePage}
68+
onChange={(p) => {
69+
setPage(p);
70+
scrollIntoView({ alignment: "start" });
71+
}}
72+
total={Math.ceil(projects.length / PER_PAGE)}
73+
/>
74+
)}
75+
<MaintainerButton onClick={() => open()} leftSection={<IconPlus />}>
76+
New Project
77+
</MaintainerButton>
78+
</Stack>
5679
</Flex>
5780
</>
5881
);

frontend/src/layout/Tabs.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ type Tab = {
1212
notSoWide?: boolean;
1313
};
1414

15-
export default function Tabs({ tabs }: { tabs: Tab[] }) {
15+
export default function Tabs({ tabs, keepMounted }: { tabs: Tab[]; keepMounted?: boolean }) {
1616
const {
1717
state: { routerState },
1818
navigate,
@@ -27,7 +27,7 @@ export default function Tabs({ tabs }: { tabs: Tab[] }) {
2727
};
2828

2929
return (
30-
<MTabs value={tab_route} onChange={setActiveTab}>
30+
<MTabs value={tab_route} onChange={setActiveTab} keepMounted={keepMounted}>
3131
<MTabs.List mb="md" mx="-lg" px="lg" className={classes.header}>
3232
{tabs.map((t) => (
3333
<MTabs.Tab size="lg" value={t.route} leftSection={t.icon} key={t.route}>

0 commit comments

Comments
 (0)