Skip to content

Commit

Permalink
Implement pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
l7ssha committed Dec 18, 2024
1 parent b8b0f5f commit bf42e61
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 27 deletions.
13 changes: 9 additions & 4 deletions frontend/src/page/admin/GuildDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import {parseISO} from "date-fns";
import {format} from "date-fns/format";
import {useDebounce} from "use-debounce";
import useUpdateEffect from "../../util";

interface GuildDetailsDataProps {
dataPromise: Promise<GuildDetailsDto>
Expand Down Expand Up @@ -54,14 +55,18 @@ function TagsDataPaper({dataPromise}: GuildDetailsDataProps) {
const [tags, setTags] = useState(data.tags);
const [searchQuery, setSearchQuery] = useState<string|null>(null);
const [searchQueryDebounced] = useDebounce(searchQuery, 500);
const [paginationModel, setPaginationModel] = useState({
pageSize: 5,
page: 0,
});

useEffect(() => {
useUpdateEffect(() => {
if (searchQueryDebounced == null) {
return;
}

fetchGuildTags({id: data.id, query: searchQueryDebounced}).then((t) => setTags(t));
}, [searchQueryDebounced]);
fetchGuildTags({id: data.id, query: searchQueryDebounced, page: paginationModel.page, perPage: paginationModel.pageSize}).then((t) => setTags(t));
}, [searchQueryDebounced, paginationModel]);

const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name' },
Expand All @@ -75,7 +80,7 @@ function TagsDataPaper({dataPromise}: GuildDetailsDataProps) {
<Typography variant='h5'>Tags</Typography>
<TextField id="tag-name-filter" label="Name..." variant="outlined" size='small' onChange={(e) => setSearchQuery(e.target.value)} />
</Stack>
<DataGrid rows={tags} columns={columns} />
<DataGrid rows={tags} columns={columns} paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} />
</Stack>;
}

Expand Down
26 changes: 18 additions & 8 deletions frontend/src/page/admin/Guilds.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React, {Suspense, use} from 'react';
import React, {Suspense, use, useEffect, useState} from 'react';
import {Base} from "../../component/Base";
import {fetchGuilds, GuildSummary} from "../../service/api";
import {fetchGuilds, fetchGuildTags, GuildSummary} from "../../service/api";
import {DataGrid, GridActionsCellItem, GridColDef, GridRowParams} from "@mui/x-data-grid";
import {Alert, Avatar, Stack, Tooltip, Typography} from "@mui/material";
import {getGuildIcon} from "../../constants";
import {Alert, Stack, Tooltip, Typography} from "@mui/material";
import {GridCell} from "../../component/GridCell";
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import {getUser} from "../../service/auth";
import OpenInFullIcon from '@mui/icons-material/OpenInFull';
import {useNavigate} from "react-router-dom";
import {NavigateFunction} from "react-router/dist/development";
import {getGuildNameElement} from "../../guildUtil";

interface GuildRowDef {
Expand Down Expand Up @@ -47,8 +45,20 @@ const guildDataPromise = fetchGuilds().then(guilds => {
});

function Grid() {
const rows = use(guildDataPromise);
const navigate = useNavigate();
const initialRows = use(guildDataPromise);

const [paginationModel, setPaginationModel] = useState({
pageSize: 25,
page: 0,
});
const [rows, setRows] = useState(initialRows);

useEffect(() => {
fetchGuilds({page: paginationModel.page, perPage: paginationModel.pageSize}).then(guilds => {
return mapApiDataToRows(guilds);
}).then((r) => setRows(r));
}, [paginationModel]);

const columns: GridColDef[] = [
{ field: 'name', headerName: 'Name', flex: 1, renderCell: params => params.value},
Expand Down Expand Up @@ -83,7 +93,7 @@ function Grid() {
];

return (
<DataGrid rows={rows} columns={columns}/>
<DataGrid rows={rows} columns={columns} paginationModel={paginationModel} onPaginationModelChange={setPaginationModel} />
);
}

Expand All @@ -93,7 +103,7 @@ export default function Guilds() {
<Stack direction="column" spacing={1}>
<Alert severity="error">Table represents cached data that is available for bot at the moment</Alert>
<Suspense fallback={<div>Loading...</div>}>
<Grid/>
<Grid />
</Suspense>
</Stack>
</Base>
Expand Down
26 changes: 16 additions & 10 deletions frontend/src/service/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,26 +124,32 @@ export interface GuildDetails {
tags: Tag[],
}

interface PaginationParameters {
perPage?: number,
page?: number,
}

interface FetchGuildTagsParameters extends PaginationParameters{
id: string,
query?: string,
}

export async function fetchBotInfo(): Promise<BotInfo> {
return await request<BotInfo>({path: "/api/server-info"});
}

export async function fetchGuilds(): Promise<GuildSummary[]> {
return await request<GuildSummary[]>({path: "/api/guilds", auth: true});
export async function fetchGuilds({perPage = 25, page = 0}: PaginationParameters = {}): Promise<GuildSummary[]> {
const params = [["perPage", perPage.toString()], ["page", (page + 1).toString()]];

return await request<GuildSummary[]>({path: "/api/guilds", auth: true, searchParams: params});
}

export async function fetchGuildDetails(id: string): Promise<GuildDetails> {
return await request<GuildDetails>({path: `/api/guilds/${id}`, auth: true});
}

interface FetchGuildTags {
id: string,
perPage?: number,
query?: string,
}

export async function fetchGuildTags({id, perPage = 5, query}: FetchGuildTags): Promise<Tag[]> {
const params = [["perPage", perPage.toString()]];
export async function fetchGuildTags({id, perPage = 5, page = 0, query}: FetchGuildTagsParameters): Promise<Tag[]> {
const params = [["perPage", perPage.toString()], ["page", (page + 1).toString()]];
if (query != null && query !== '') {
params.push(["query", query])
}
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import {useEffect, useRef} from "react";

export function containsAll<T>(haystack: T[], required: T[]): boolean {
return required.every(ai => haystack.includes(ai));
}

export default function useUpdateEffect(effect: Function, dependencies = <any>[]) {
const isInitialMount = useRef(true);

useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
return effect();
}
}, dependencies);
}
12 changes: 9 additions & 3 deletions lib/src/web_app/api_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ class WebServer {
Future<shelf.Response> _handleGuilds(shelf.Request request) async {
final client = Injector.appInstance.get<NyxxGateway>();

final guildData = await mapGuildsToGuildReducedData(client.guilds.cache.values).toList();
final perPage = int.tryParse(request.requestedUri.queryParameters['perPage'] ?? '10') ?? 10;
final page = int.tryParse(request.requestedUri.queryParameters['page'] ?? '1') ?? 1;

final guilds = client.guilds.cache.values.skip(perPage * (page - 1)).take(page);

final guildData = await mapGuildsToGuildReducedData(guilds).toList();

return createOkResponse(guildData);
}
Expand All @@ -42,10 +47,11 @@ class WebServer {
}

final searchQuery = request.requestedUri.queryParameters['query'];
final tagsLimit = int.tryParse(request.requestedUri.queryParameters['perPage'] ?? '5') ?? 5;
final perPage = int.tryParse(request.requestedUri.queryParameters['perPage'] ?? '5') ?? 5;
final page = int.tryParse(request.requestedUri.queryParameters['page'] ?? '1') ?? 1;

return createOkResponse(
await mapGuildTagsToData(Snowflake.parse(guildParam), tagsLimit, searchQuery: searchQuery).toList(),
await mapGuildTagsToData(Snowflake.parse(guildParam), perPage, searchQuery: searchQuery, page: page).toList(),
);
}

Expand Down
4 changes: 2 additions & 2 deletions lib/src/web_app/mapper/tags_mapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import 'package:nyxx/nyxx.dart';
import 'package:running_on_dart/src/modules/tag.dart';
import 'package:running_on_dart/src/web_app/utils.dart';

Stream<JsonApiResponse> mapGuildTagsToData(Snowflake guildId, int tagsLimit, {String? searchQuery}) async* {
Stream<JsonApiResponse> mapGuildTagsToData(Snowflake guildId, int tagsLimit, {String? searchQuery, int page = 1}) async* {
final tagsModule = Injector.appInstance.get<TagModule>();

var tags = tagsModule.getGuildTags(guildId);
if (searchQuery != null) {
tags = tags.where((tag) => tag.name.contains(searchQuery));
}

for (final tag in tags.take(tagsLimit)) {
for (final tag in tags.skip(tagsLimit * (page - 1)).take(tagsLimit)) {
yield {
'id': tag.id,
'name': tag.name,
Expand Down

0 comments on commit bf42e61

Please sign in to comment.