Merge pull request #903 from notmd/leaderboard_enhancement

Show more stats in leaderboard table
This commit is contained in:
Keith Stevens
2023-01-24 10:54:48 +09:00
committed by GitHub
15 changed files with 174 additions and 211 deletions
@@ -1,11 +1,9 @@
import { Box, Link, Text, useColorModeValue } from "@chakra-ui/react";
import { Card, CardBody, Link, Text } from "@chakra-ui/react";
import NextLink from "next/link";
import { LeaderboardGridCell } from "src/components/LeaderboardGridCell";
import { LeaderboardTable } from "src/components/LeaderboardTable";
import { LeaderboardTimeFrame } from "src/types/Leaderboard";
export function LeaderboardTable() {
const backgroundColor = useColorModeValue("white", "gray.700");
const accentColor = useColorModeValue("gray.200", "gray.900");
export function LeaderboardWidget() {
return (
<main className="h-fit col-span-3">
<div className="flex flex-col gap-4">
@@ -17,15 +15,11 @@ export function LeaderboardTable() {
</Text>
</Link>
</div>
<Box
backgroundColor={backgroundColor}
boxShadow="base"
dropShadow={accentColor}
borderRadius="xl"
className="p-6 shadow-sm"
>
<LeaderboardGridCell timeFrame={LeaderboardTimeFrame.day} />
</Box>
<Card>
<CardBody>
<LeaderboardTable timeFrame={LeaderboardTimeFrame.day} limit={5} />
</CardBody>
</Card>
</div>
</main>
);
+1 -1
View File
@@ -1,3 +1,3 @@
export { LeaderboardTable } from "./LeaderboardTable";
export { LeaderboardWidget } from "./LeaderboardWidget";
export { TaskOption } from "./TaskOption";
export { WelcomeCard } from "./WelcomeCard";
+40 -40
View File
@@ -1,8 +1,6 @@
import {
Box,
Button,
Card,
CardBody,
Flex,
FormControl,
FormLabel,
@@ -49,6 +47,7 @@ export type DataTableProps<T> = {
onFilterChange?: (items: FilterItem[]) => void;
disableNext?: boolean;
disablePrevious?: boolean;
disablePagination?: boolean;
};
export const DataTable = <T,>({
@@ -61,6 +60,7 @@ export const DataTable = <T,>({
onFilterChange,
disableNext,
disablePrevious,
disablePagination,
}: DataTableProps<T>) => {
const { getHeaderGroups, getRowModel } = useReactTable<T>({
data,
@@ -79,8 +79,8 @@ export const DataTable = <T,>({
onFilterChange(newValues);
};
return (
<Card>
<CardBody>
<>
{!disablePagination && (
<Flex mb="2">
<Button onClick={onPreviousClick} disabled={disablePrevious}>
Previous
@@ -90,42 +90,42 @@ export const DataTable = <T,>({
Next
</Button>
</Flex>
<TableContainer>
<Table variant="simple">
<TableCaption>{caption}</TableCaption>
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Box display="flex" alignItems="center">
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
{(header.column.columnDef as DataTableColumnDef<T>).filterable && (
<FilterModal
value={filterValues.find((value) => value.id === header.id)?.value ?? ""}
onChange={(value) => handleFilterChange({ id: header.id, value })}
label={flexRender(header.column.columnDef.header, header.getContext())}
></FilterModal>
)}
</Box>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</CardBody>
</Card>
)}
<TableContainer>
<Table variant="simple">
<TableCaption>{caption}</TableCaption>
<Thead>
{getHeaderGroups().map((headerGroup) => (
<Tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<Th key={header.id}>
<Box display="flex" alignItems="center">
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
{(header.column.columnDef as DataTableColumnDef<T>).filterable && (
<FilterModal
value={filterValues.find((value) => value.id === header.id)?.value ?? ""}
onChange={(value) => handleFilterChange({ id: header.id, value })}
label={flexRender(header.column.columnDef.header, header.getContext())}
></FilterModal>
)}
</Box>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody>
{getRowModel().rows.map((row) => (
<Tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<Td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</Td>
))}
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</>
);
};
@@ -1,90 +0,0 @@
import { Table, TableContainer, Tbody, Td, Text, Th, Thead, Tr, useColorModeValue } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import React, { useMemo } from "react";
import { useTable } from "react-table";
import { get } from "src/lib/api";
import { LeaderboardReply, LeaderboardTimeFrame } from "src/types/Leaderboard";
import useSWRImmutable from "swr/immutable";
const getColumns = (t) => [
{
Header: t("rank"),
accessor: "rank",
style: { width: "90px" },
},
{
Header: t("score"),
accessor: "leader_score",
style: { width: "90px" },
},
{
Header: t("user"),
accessor: "display_name",
},
];
/**
* Presents a grid of leaderboard entries with more detailed information.
*/
const LeaderboardGridCell = ({ timeFrame }: { timeFrame: LeaderboardTimeFrame }) => {
const { t } = useTranslation(["leaderboard", "common"]);
const { data: reply } = useSWRImmutable<LeaderboardReply>(`/api/leaderboard?time_frame=${timeFrame}`, get, {
revalidateOnMount: true,
});
const columns = useMemo(() => getColumns(t), [t]);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({
columns,
data: reply?.leaderboard ?? [],
});
const backgroundColor = useColorModeValue("white", "gray.800");
const lastUpdated = useMemo(() => {
const val = new Date(reply?.last_updated);
return t("last_updated_at", { val, formatParams: { val: { dateStyle: "full", timeStyle: "short" } } });
}, [t, reply?.last_updated]);
if (!reply) {
return null;
}
return (
<TableContainer>
<Table {...getTableProps()}>
<Thead bg={backgroundColor}>
{headerGroups.map((headerGroup, idx) => (
<Tr key={idx} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<Th {...column.getHeaderProps([{ style: column.style }])} key={column.id}>
{column.render("Header")}
</Th>
))}
</Tr>
))}
</Thead>
<Tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<Tr key={row.id} {...row.getRowProps()}>
{row.cells.map((cell, idx) => {
return (
<Td key={row.id + idx} {...cell.getCellProps([{ style: cell.column.style }])}>
{cell.render("Cell")}
</Td>
);
})}
</Tr>
);
})}
</Tbody>
</Table>
<Text p="2">{lastUpdated}</Text>
</TableContainer>
);
};
export { LeaderboardGridCell };
@@ -1 +0,0 @@
export * from "./LeaderboardGridCell";
@@ -0,0 +1,64 @@
import { CircularProgress } from "@chakra-ui/react";
import { createColumnHelper } from "@tanstack/react-table";
import { useTranslation } from "next-i18next";
import React, { useMemo } from "react";
import { get } from "src/lib/api";
import { LeaderboardEntity, LeaderboardReply, LeaderboardTimeFrame } from "src/types/Leaderboard";
import useSWRImmutable from "swr/immutable";
import { DataTable } from "../DataTable";
const columnHelper = createColumnHelper<LeaderboardEntity>();
/**
* Presents a grid of leaderboard entries with more detailed information.
*/
export const LeaderboardTable = ({ timeFrame, limit }: { timeFrame: LeaderboardTimeFrame; limit: number }) => {
const { t } = useTranslation("leaderboard");
const {
data: reply,
isLoading,
error,
} = useSWRImmutable<LeaderboardReply>(`/api/leaderboard?time_frame=${timeFrame}&limit=${limit}`, get, {
revalidateOnMount: true,
});
const columns = useMemo(
() => [
columnHelper.accessor("rank", {
header: t("rank"),
}),
columnHelper.accessor("display_name", {
header: t("user"),
}),
columnHelper.accessor("leader_score", {
header: t("score"),
}),
columnHelper.accessor("prompts", {
header: t("prompt"),
}),
columnHelper.accessor((row) => row.replies_assistant + row.replies_prompter, {
header: t("reply"),
}),
columnHelper.accessor((row) => row.labels_full + row.labels_simple, {
header: t("label"),
}),
],
[t]
);
const lastUpdated = useMemo(() => {
const val = new Date(reply?.last_updated);
return t("last_updated_at", { val, formatParams: { val: { dateStyle: "full", timeStyle: "short" } } });
}, [t, reply?.last_updated]);
if (isLoading) {
return <CircularProgress isIndeterminate></CircularProgress>;
}
if (error) {
return <span>Unable to load leaderboard</span>;
}
return <DataTable data={reply.leaderboard} columns={columns} caption={lastUpdated} disablePagination></DataTable>;
};
@@ -0,0 +1 @@
export * from "./LeaderboardTable";
+17 -15
View File
@@ -1,4 +1,4 @@
import { IconButton } from "@chakra-ui/react";
import { Card, CardBody, IconButton } from "@chakra-ui/react";
import { createColumnHelper } from "@tanstack/react-table";
import { Pencil } from "lucide-react";
import Link from "next/link";
@@ -90,19 +90,21 @@ export const UserTable = memo(function UserTable() {
};
return (
<>
<DataTable
data={data?.items || []}
columns={columns}
caption="Users"
onNextClick={toNextPage}
onPreviousClick={toPreviousPage}
disableNext={!data?.next}
disablePrevious={!data?.prev}
filterValues={filterValues}
onFilterChange={handleFilterValuesChange}
></DataTable>
{error && "Unable to load users."}
</>
<Card>
<CardBody>
<DataTable
data={data?.items || []}
columns={columns}
caption="Users"
onNextClick={toNextPage}
onPreviousClick={toPreviousPage}
disableNext={!data?.next}
disablePrevious={!data?.prev}
filterValues={filterValues}
onFilterChange={handleFilterValuesChange}
></DataTable>
{error && "Unable to load users."}
</CardBody>
</Card>
);
});