add input to the player daily stats ranking that allows changing the date

This commit is contained in:
Dawid Wysokiński 2020-12-28 11:54:13 +01:00
parent 4d64201c51
commit 5849822fad
18 changed files with 108 additions and 59 deletions

View File

@ -18,6 +18,7 @@
"@types/react-dom": "^16.9.8", "@types/react-dom": "^16.9.8",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"date-fns": "^2.16.1", "date-fns": "^2.16.1",
"date-fns-tz": "^1.0.12",
"graphql": "^15.4.0", "graphql": "^15.4.0",
"i18next": "^19.8.3", "i18next": "^19.8.3",
"i18next-browser-languagedetector": "^6.0.1", "i18next-browser-languagedetector": "^6.0.1",

View File

@ -12,7 +12,7 @@ export type Props = TextFieldProps & {
onResetValue?: () => void; onResetValue?: () => void;
}; };
function SearchInput({ value, onResetValue, style, ...rest }: Props) { function SearchInput({ value, onResetValue, ...rest }: Props) {
const input = useRef<HTMLInputElement | null>(null); const input = useRef<HTMLInputElement | null>(null);
return ( return (
@ -20,7 +20,6 @@ function SearchInput({ value, onResetValue, style, ...rest }: Props) {
{...rest} {...rest}
value={value} value={value}
inputRef={input} inputRef={input}
style={{ backgroundColor: 'rgba(0, 0, 0, 0.1)', ...(style ? style : {}) }}
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment <InputAdornment

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { format } from 'date-fns'; import { format, isValid } from 'date-fns';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { import {
useQueryParams, useQueryParams,
@ -66,6 +66,7 @@ function LatestSavedEnnoblements({ t, server }: Props) {
return ( return (
<TextField <TextField
type="date" type="date"
size="small"
key={id} key={id}
label={t('latestSavedEnnoblements.inputs.' + id)} label={t('latestSavedEnnoblements.inputs.' + id)}
defaultValue={ defaultValue={
@ -74,7 +75,8 @@ function LatestSavedEnnoblements({ t, server }: Props) {
: undefined : undefined
} }
onChange={e => { onChange={e => {
setQuery({ [id]: new Date(e.target.value) }); const date = new Date(e.target.value);
setQuery({ [id]: isValid(date) ? date : undefined });
}} }}
InputLabelProps={{ InputLabelProps={{
shrink: true, shrink: true,

View File

@ -59,10 +59,10 @@ function IndexPage() {
<ODRankingTribes server={key} t={t} /> <ODRankingTribes server={key} t={t} />
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<TodaysBestStatsPlayers server={key} t={t} /> <TodaysBestStatsPlayers t={t} />
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<TodaysBestStatsTribes server={key} t={t} /> <TodaysBestStatsTribes t={t} />
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<RecentlyDeletedPlayers server={key} t={t} /> <RecentlyDeletedPlayers server={key} t={t} />

View File

@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react'; import React, { useState } from 'react';
import { subHours } from 'date-fns';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
import { SERVER_PAGE } from '@config/routes'; import { SERVER_PAGE } from '@config/routes';
import { DAILY_PLAYER_STATS } from './queries'; import { DAILY_PLAYER_STATS } from './queries';
@ -19,12 +19,11 @@ import { DailyPlayerStatsQueryVariables } from '@libs/graphql/types';
import { DailyPlayerStatsList, DailyPlayerStatsRecord, Mode } from './types'; import { DailyPlayerStatsList, DailyPlayerStatsRecord, Mode } from './types';
export interface Props { export interface Props {
server: string;
t: TFunction; t: TFunction;
} }
function TodaysBestStatsPlayers({ server, t }: Props) { function TodaysBestStatsPlayers({ t }: Props) {
const createDateGT = useRef(subHours(new Date(), 30)); const server = useServer();
const [mode, setMode] = useState<Mode>('scoreAtt'); const [mode, setMode] = useState<Mode>('scoreAtt');
const { loading: loadingData, data } = useQuery< const { loading: loadingData, data } = useQuery<
DailyPlayerStatsList, DailyPlayerStatsList,
@ -35,9 +34,9 @@ function TodaysBestStatsPlayers({ server, t }: Props) {
limit: LIMIT, limit: LIMIT,
sort: [mode + ' DESC'], sort: [mode + ' DESC'],
filter: { filter: {
createDateGT: createDateGT.current, createDate: server.historyUpdatedAt,
}, },
server, server: server.key,
}, },
}); });
const records = data?.dailyPlayerStats?.items ?? []; const records = data?.dailyPlayerStats?.items ?? [];
@ -49,7 +48,7 @@ function TodaysBestStatsPlayers({ server, t }: Props) {
<Typography variant="h4"> <Typography variant="h4">
<Link <Link
to={SERVER_PAGE.RANKING_PAGE.PLAYER_PAGE.DAILY_PAGE} to={SERVER_PAGE.RANKING_PAGE.PLAYER_PAGE.DAILY_PAGE}
params={{ key: server }} params={{ key: server.key }}
> >
{t('todaysBestStatsPlayers.title')} {t('todaysBestStatsPlayers.title')}
</Link> </Link>
@ -108,7 +107,10 @@ function TodaysBestStatsPlayers({ server, t }: Props) {
valueFormatter: valueFormatter:
index === 0 index === 0
? (record: DailyPlayerStatsRecord) => ( ? (record: DailyPlayerStatsRecord) => (
<PlayerProfileLink player={record.player} server={server} /> <PlayerProfileLink
player={record.player}
server={server.key}
/>
) )
: index === 1 : index === 1
? (record: DailyPlayerStatsRecord) => ? (record: DailyPlayerStatsRecord) =>

View File

@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react'; import React, { useState } from 'react';
import { subHours } from 'date-fns';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
import { SERVER_PAGE } from '@config/routes'; import { SERVER_PAGE } from '@config/routes';
import { DAILY_TRIBE_STATS } from './queries'; import { DAILY_TRIBE_STATS } from './queries';
@ -18,12 +18,11 @@ import { DailyTribeStatsQueryVariables } from '@libs/graphql/types';
import { DailyTribeStatsList, DailyTribeStatsRecord, Mode } from './types'; import { DailyTribeStatsList, DailyTribeStatsRecord, Mode } from './types';
export interface Props { export interface Props {
server: string;
t: TFunction; t: TFunction;
} }
function TodaysBestStatsTribes({ server, t }: Props) { function TodaysBestStatsTribes({ t }: Props) {
const createDateGT = useRef(subHours(new Date(), 30)); const server = useServer();
const [mode, setMode] = useState<Mode>('scoreAtt'); const [mode, setMode] = useState<Mode>('scoreAtt');
const { loading: loadingData, data } = useQuery< const { loading: loadingData, data } = useQuery<
DailyTribeStatsList, DailyTribeStatsList,
@ -34,9 +33,9 @@ function TodaysBestStatsTribes({ server, t }: Props) {
limit: LIMIT, limit: LIMIT,
sort: [mode + ' DESC'], sort: [mode + ' DESC'],
filter: { filter: {
createDateGT: createDateGT.current, createDate: server.historyUpdatedAt,
}, },
server, server: server.key,
}, },
}); });
const records = data?.dailyTribeStats?.items ?? []; const records = data?.dailyTribeStats?.items ?? [];
@ -48,7 +47,7 @@ function TodaysBestStatsTribes({ server, t }: Props) {
<Typography variant="h4"> <Typography variant="h4">
<Link <Link
to={SERVER_PAGE.RANKING_PAGE.TRIBE_PAGE.DAILY_PAGE} to={SERVER_PAGE.RANKING_PAGE.TRIBE_PAGE.DAILY_PAGE}
params={{ key: server }} params={{ key: server.key }}
> >
{t('todaysBestStatsTribes.title')} {t('todaysBestStatsTribes.title')}
</Link> </Link>
@ -105,7 +104,7 @@ function TodaysBestStatsTribes({ server, t }: Props) {
? (record: DailyTribeStatsRecord) => ( ? (record: DailyTribeStatsRecord) => (
<Link <Link
to={SERVER_PAGE.TRIBE_PAGE.INDEX_PAGE} to={SERVER_PAGE.TRIBE_PAGE.INDEX_PAGE}
params={{ key: server, id: record.tribe.id }} params={{ key: server.key, id: record.tribe.id }}
> >
{record.tribe.tag} {record.tribe.tag}
</Link> </Link>

View File

@ -13,7 +13,7 @@ function DailyPage() {
useTitle(t('title', { key })); useTitle(t('title', { key }));
return ( return (
<Container> <Container>
<Ranking t={t} server={key} /> <Ranking t={t} />
</Container> </Container>
); );
} }

View File

@ -1,19 +1,23 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { format, isValid } from 'date-fns';
import { import {
useQueryParams, useQueryParams,
NumberParam, NumberParam,
withDefault, withDefault,
StringParam, StringParam,
DateTimeParam,
} from 'use-query-params'; } from 'use-query-params';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import useUpdateEffect from '@libs/useUpdateEffect'; import useUpdateEffect from '@libs/useUpdateEffect';
import useScrollToElement from '@libs/useScrollToElement'; import useScrollToElement from '@libs/useScrollToElement';
import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import SortParam from '@libs/serialize-query-params/SortParam'; import SortParam from '@libs/serialize-query-params/SortParam';
import useStats from './useStats'; import useStats from './useStats';
import { validateRowsPerPage } from '@common/Table/helpers'; import { validateRowsPerPage } from '@common/Table/helpers';
import { COLUMNS, LIMIT, DEFAULT_SORT } from './constants'; import { COLUMNS, LIMIT, DEFAULT_SORT } from './constants';
import { Paper } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles';
import { Paper, TextField } from '@material-ui/core';
import Table from '@common/Table/Table'; import Table from '@common/Table/Table';
import TableToolbar from '@common/Table/TableToolbar'; import TableToolbar from '@common/Table/TableToolbar';
import SearchInput from '@common/Form/SearchInput'; import SearchInput from '@common/Form/SearchInput';
@ -23,16 +27,19 @@ import { TFunction } from 'i18next';
import { DailyPlayerStatsRecord } from './types'; import { DailyPlayerStatsRecord } from './types';
export interface Props { export interface Props {
server: string;
t: TFunction; t: TFunction;
} }
function Ranking({ server, t }: Props) { function Ranking({ t }: Props) {
const classes = useStyles();
const server = useServer();
const defaultDate = new Date(server.historyUpdatedAt);
const [query, setQuery] = useQueryParams({ const [query, setQuery] = useQueryParams({
page: withDefault(NumberParam, 0), page: withDefault(NumberParam, 0),
limit: withDefault(NumberParam, LIMIT), limit: withDefault(NumberParam, LIMIT),
q: withDefault(StringParam, ''), q: withDefault(StringParam, ''),
sort: withDefault(SortParam, DEFAULT_SORT), sort: withDefault(SortParam, DEFAULT_SORT),
createDate: withDefault(DateTimeParam, defaultDate),
}); });
const limit = validateRowsPerPage(query.limit); const limit = validateRowsPerPage(query.limit);
const [q, setQ] = useState(query.q); const [q, setQ] = useState(query.q);
@ -47,14 +54,32 @@ function Ranking({ server, t }: Props) {
const { dailyStats, total, loading } = useStats( const { dailyStats, total, loading } = useStats(
query.page, query.page,
limit, limit,
server, server.key,
query.q, query.q,
query.sort.toString() query.sort.toString(),
query.createDate
); );
return ( return (
<Paper> <Paper>
<TableToolbar style={{ justifyContent: 'flex-end' }}> <TableToolbar className={classes.tableToolbar}>
<TextField
type="date"
size="small"
label={t('ranking.createDateInputLabel')}
defaultValue={
query.createDate && query.createDate instanceof Date
? format(query.createDate, 'yyyy-MM-dd')
: undefined
}
onChange={e => {
const date = new Date(e.target.value);
setQuery({ createDate: isValid(date) ? date : undefined });
}}
InputLabelProps={{
shrink: true,
}}
/>
<SearchInput <SearchInput
variant="outlined" variant="outlined"
size="small" size="small"
@ -82,7 +107,7 @@ function Ranking({ server, t }: Props) {
} }
if (index === 1) { if (index === 1) {
newCol.valueFormatter = (record: DailyPlayerStatsRecord) => ( newCol.valueFormatter = (record: DailyPlayerStatsRecord) => (
<PlayerProfileLink player={record.player} server={server} /> <PlayerProfileLink player={record.player} server={server.key} />
); );
} }
return newCol; return newCol;
@ -115,4 +140,22 @@ function Ranking({ server, t }: Props) {
); );
} }
const useStyles = makeStyles(theme => ({
tableToolbar: {
justifyContent: 'flex-end',
flexWrap: 'wrap',
'& > *': {
margin: theme.spacing(0.5),
[theme.breakpoints.down(700)]: {
marginLeft: 0,
marginRight: 0,
width: '100%',
},
},
[theme.breakpoints.down(700)]: {
flexDirection: 'column',
},
},
}));
export default Ranking; export default Ranking;

View File

@ -1,6 +1,4 @@
import { useMemo } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import { DAILY_PLAYER_STATS } from './queries'; import { DAILY_PLAYER_STATS } from './queries';
import { DailyPlayerStatsQueryVariables } from '@libs/graphql/types'; import { DailyPlayerStatsQueryVariables } from '@libs/graphql/types';
@ -12,17 +10,18 @@ export type QueryResult = {
total: number; total: number;
}; };
export type Options = {
addTimezoneOffsetToCreateDate?: boolean;
};
const usePlayers = ( const usePlayers = (
page: number, page: number,
limit: number, limit: number,
server: string, server: string,
q: string, q: string,
sort: string sort: string,
createDate: Date
): QueryResult => { ): QueryResult => {
const { historyUpdatedAt } = useServer();
const createDateGTE = useMemo<string>(() => {
return historyUpdatedAt.toString().split('T')[0] + 'T00:00:00Z';
}, [historyUpdatedAt]);
const { loading: loadingStats, data } = useQuery< const { loading: loadingStats, data } = useQuery<
DailyStats, DailyStats,
DailyPlayerStatsQueryVariables DailyPlayerStatsQueryVariables
@ -37,7 +36,7 @@ const usePlayers = (
exists: true, exists: true,
nameIEQ: '%' + q + '%', nameIEQ: '%' + q + '%',
}, },
createDateGTE, createDate,
}, },
server, server,
}, },

View File

@ -7,9 +7,9 @@ const ctx = createContext<Server>({
numberOfPlayers: 0, numberOfPlayers: 0,
numberOfTribes: 0, numberOfTribes: 0,
numberOfVillages: 0, numberOfVillages: 0,
dataUpdatedAt: new Date(0), dataUpdatedAt: new Date(0).toJSON(),
historyUpdatedAt: new Date(0), historyUpdatedAt: new Date(0).toJSON(),
statsUpdatedAt: new Date(0), statsUpdatedAt: new Date(0).toJSON(),
status: SERVER_STATUS.OPEN, status: SERVER_STATUS.OPEN,
version: { version: {
code: '', code: '',

View File

@ -3,7 +3,6 @@ import { gql } from '@apollo/client';
export const SERVERS = gql` export const SERVERS = gql`
query servers($filter: ServerFilter, $limit: Int) { query servers($filter: ServerFilter, $limit: Int) {
servers(filter: $filter, limit: $limit) { servers(filter: $filter, limit: $limit) {
total
items { items {
key key
numberOfPlayers numberOfPlayers

View File

@ -7,9 +7,9 @@ export type Server = {
numberOfPlayers: number; numberOfPlayers: number;
numberOfTribes: number; numberOfTribes: number;
numberOfVillages: number; numberOfVillages: number;
dataUpdatedAt: string | Date; dataUpdatedAt: string;
historyUpdatedAt: string | Date; historyUpdatedAt: string;
statsUpdatedAt: string | Date; statsUpdatedAt: string;
version: { version: {
code: string; code: string;
host: string; host: string;

View File

@ -0,0 +1,8 @@
import { addMinutes } from 'date-fns';
const addTimezoneOffset = (d: Date): Date => {
const offset = d.getTimezoneOffset();
return addMinutes(d, -1 * offset);
};
export default addTimezoneOffset;

View File

@ -1,12 +0,0 @@
const format = (d: Date) => {
return d.toLocaleString(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
};
export default format;

View File

@ -70,6 +70,7 @@ export type DailyPlayerStatsFilter = {
createDateGT?: Date | string; createDateGT?: Date | string;
createDateGTE?: Date | string; createDateGTE?: Date | string;
createDateLTE?: Date | string; createDateLTE?: Date | string;
createDate?: Date | string;
playerFilter?: PlayerFilter; playerFilter?: PlayerFilter;
playerID?: number[]; playerID?: number[];
}; };
@ -141,6 +142,7 @@ export type LiveEnnoblementsQueryVariables = {
export type DailyTribeStatsFilter = { export type DailyTribeStatsFilter = {
createDateGT?: Date | string; createDateGT?: Date | string;
createDateGTE?: Date | string; createDateGTE?: Date | string;
createDate?: Date | string;
tribeID?: number[]; tribeID?: number[];
tribeFilter?: TribeFilter; tribeFilter?: TribeFilter;
}; };

View File

@ -11,6 +11,7 @@ const translations = {
scoreTotal: 'OD', scoreTotal: 'OD',
}, },
searchInputPlaceholder: 'Search player', searchInputPlaceholder: 'Search player',
createDateInputLabel: 'Date',
}, },
}; };

View File

@ -11,6 +11,7 @@ const translations = {
scoreTotal: 'Pokonani ogólnie', scoreTotal: 'Pokonani ogólnie',
}, },
searchInputPlaceholder: 'Wyszukaj gracza', searchInputPlaceholder: 'Wyszukaj gracza',
createDateInputLabel: 'Data',
}, },
}; };

View File

@ -4225,6 +4225,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0" whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0" whatwg-url "^8.0.0"
date-fns-tz@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.0.12.tgz#2d680e1099767775cff7a30eac34362d52639fed"
integrity sha512-Ca+9pjGkU90XDHnclfSjz9o7g/ZqyYyYI0aCYmbf65P75oy8gktuaRslO3UPXl3ADgAnF9/KCykQkpU3/xvtWQ==
date-fns@^2.16.1: date-fns@^2.16.1:
version "2.16.1" version "2.16.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"