diff --git a/src/common/Table/TableRow.tsx b/src/common/Table/TableRow.tsx index bb8a895..074169f 100644 --- a/src/common/Table/TableRow.tsx +++ b/src/common/Table/TableRow.tsx @@ -1,10 +1,12 @@ import React from 'react'; import { get, isString, isNumber } from 'lodash'; import { format } from 'date-fns'; -import { Action, Column } from './types'; +import { DATE_FORMAT } from '@config/app'; import { TableRow, TableCell, Checkbox, Tooltip } from '@material-ui/core'; +import { Action, Column } from './types'; + export interface Props { actions: Action[]; columns: Column[]; @@ -35,10 +37,13 @@ function EnhancedTableRow({ type: 'datetime' | 'date' | 'normal' ) => { if ((isString(v) || isNumber(v)) && type === 'date') { - return format(new Date(v), 'yyyy-MM-dd'); + return format(new Date(v), DATE_FORMAT.DAY_MONTH_AND_YEAR); } if ((isString(v) || isNumber(v)) && type === 'datetime') { - return format(new Date(v), 'yyyy-MM-dd HH:mm:ss'); + return format( + new Date(v), + DATE_FORMAT.HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR + ); } return v; }; diff --git a/src/config/app.ts b/src/config/app.ts index 0ba51ef..0d285ad 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -12,3 +12,10 @@ export const SERVER_STATUS = { export const TWHELP = process.env.TWHelp ?? 'https://tribalwarshelp.com'; export const AUTHOR = 'Dawid Wysokiński'; + +export const DATE_FORMAT = { + MONTH_AND_YEAR: 'yyyy-MM', + DAY_MONTH_AND_YEAR: 'yyyy-MM-dd', + HOUR_MINUTES_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm', + HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR: 'yyyy-MM-dd HH:mm', +}; diff --git a/src/features/ServerPage/features/PlayerPage/common/PageLayout/PageLayout.tsx b/src/features/ServerPage/features/PlayerPage/common/PageLayout/PageLayout.tsx index 5cf59e6..dc885a0 100644 --- a/src/features/ServerPage/features/PlayerPage/common/PageLayout/PageLayout.tsx +++ b/src/features/ServerPage/features/PlayerPage/common/PageLayout/PageLayout.tsx @@ -21,57 +21,6 @@ import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout' import background from './profile-background-dark.png'; -const useStyles = makeStyles(theme => ({ - header: { - width: '100%', - minHeight: theme.spacing(30), - backgroundPosition: 'center', - backgroundImage: `url(${background})`, - backgroundSize: 'cover', - backgroundRepeat: 'no-repeat', - display: 'flex', - justifyContent: 'flex-end', - flexDirection: 'column', - boxShadow: theme.shadows[4], - }, - playerNameContainer: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - '& > *:not(:last-child)': { - marginRight: theme.spacing(2), - }, - [theme.breakpoints.down('xs')]: { - flexDirection: 'column', - '& > *': { - marginRight: 0, - marginBottom: theme.spacing(1), - }, - }, - }, - toolbar: { - [theme.breakpoints.down('xs')]: { - textAlign: 'center', - }, - }, - chipContainer: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - flexWrap: 'wrap', - '& > *': { - margin: theme.spacing(0.5), - }, - }, - content: { - height: '100%', - padding: theme.spacing(3, 0), - '&.no-padding': { - padding: '0 0', - }, - }, -})); - export interface Props { children: React.ReactNode; } @@ -210,4 +159,56 @@ function PageLayout({ children }: Props) { ); } +const useStyles = makeStyles(theme => ({ + header: { + width: '100%', + minHeight: theme.spacing(30), + backgroundPosition: 'center', + backgroundImage: `url(${background})`, + backgroundSize: 'cover', + backgroundRepeat: 'no-repeat', + display: 'flex', + justifyContent: 'flex-end', + flexDirection: 'column', + boxShadow: theme.shadows[4], + }, + playerNameContainer: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + '& > *:not(:last-child)': { + marginRight: theme.spacing(2), + }, + [theme.breakpoints.down('xs')]: { + flexDirection: 'column', + '& > *': { + marginRight: 0, + marginBottom: theme.spacing(1), + }, + }, + }, + toolbar: { + [theme.breakpoints.down('xs')]: { + textAlign: 'center', + }, + }, + chipContainer: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + '& > *': { + margin: theme.spacing(0.5), + }, + }, + content: { + height: '100%', + padding: theme.spacing(3, 0), + '&.no-padding': { + padding: '0 0', + }, + }, +})); + export default PageLayout; diff --git a/src/features/ServerPage/features/PlayerPage/features/IndexPage/IndexPage.tsx b/src/features/ServerPage/features/PlayerPage/features/IndexPage/IndexPage.tsx index 8f8738a..6944b40 100644 --- a/src/features/ServerPage/features/PlayerPage/features/IndexPage/IndexPage.tsx +++ b/src/features/ServerPage/features/PlayerPage/features/IndexPage/IndexPage.tsx @@ -4,7 +4,9 @@ import { useTranslation } from 'react-i18next'; import useTitle from '@libs/useTitle'; import useServer from '@features/ServerPage/libs/ServerContext/useServer'; import usePlayer from '../../libs/PlayerPageContext/usePlayer'; +import { DATE_FORMAT } from '@config/app'; import { SERVER_PAGE } from '@config/namespaces'; +import * as ROUTES from '@config/routes'; import { makeStyles } from '@material-ui/core/styles'; import { @@ -14,9 +16,12 @@ import { Card, CardContent, Typography, + Chip, } from '@material-ui/core'; +import Link from '@common/Link/Link'; import PageLayout from '../../common/PageLayout/PageLayout'; import Statistics from './components/Statistics/Statistics'; +import NameChanges from './components/NameChanges/NameChanges'; function IndexPage() { const classes = useStyles(); @@ -29,13 +34,16 @@ function IndexPage() { - + {[ { field: 'joinedAt', - value: format(new Date(player.joinedAt), 'yyyy-MM-dd HH:mm'), + value: format( + new Date(player.joinedAt), + DATE_FORMAT.DAY_MONTH_AND_YEAR + ), }, { field: 'points', @@ -70,25 +78,34 @@ function IndexPage() { { field: 'deletedAt', value: player.deletedAt - ? format(new Date(player.deletedAt), 'yyyy-MM-dd HH:mm') + ? format( + new Date(player.deletedAt), + DATE_FORMAT.DAY_MONTH_AND_YEAR + ) : '-', }, { field: 'bestRank', - subtitle: format(new Date(player.bestRankAt), 'yyyy-MM-dd HH:mm'), + subtitle: format( + new Date(player.bestRankAt), + DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR + ), value: `${player.bestRank.toString()}`, }, { field: 'mostPoints', subtitle: format( new Date(player.mostPointsAt), - 'yyyy-MM-dd HH:mm' + DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR ), value: `${player.mostPoints.toLocaleString()}`, }, { field: 'mostVillages', - subtitle: format(new Date(player.bestRankAt), 'yyyy-MM-dd HH:mm'), + subtitle: format( + new Date(player.bestRankAt), + DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR + ), value: `${player.mostVillages.toLocaleString()}`, }, ].map(({ field, value, subtitle }) => { @@ -100,7 +117,7 @@ function IndexPage() { {t('fields.' + field)}
{subtitle && ( - + {subtitle} )} @@ -111,19 +128,56 @@ function IndexPage() {
); })} + + + + + {t('fields.servers')} + +
+ {[...player.servers].sort().map(server => { + return ( + + + + ); + })} +
+
+
+
+ + +
); } -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles(theme => ({ card: { height: '100%', }, cardContent: { height: '100%', }, + serverContainer: { + textAlign: 'justify', + }, + chip: { + margin: theme.spacing(0.5), + cursor: 'pointer', + }, })); export default IndexPage; diff --git a/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/NameChanges/NameChanges.tsx b/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/NameChanges/NameChanges.tsx new file mode 100644 index 0000000..aa08890 --- /dev/null +++ b/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/NameChanges/NameChanges.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import { Card, CardContent, Typography } from '@material-ui/core'; +import Table from '@common/Table/Table'; +import { NameChange } from '../../../../libs/PlayerPageContext/types'; + +import { TFunction } from 'i18next'; + +export interface Props { + t: TFunction; + nameChanges: NameChange[]; +} + +function NameChanges({ t, nameChanges }: Props) { + return ( + + + {t('nameChanges.title')} + + + + ); +} + +export default NameChanges; diff --git a/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/Statistics/Statistics.tsx b/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/Statistics/Statistics.tsx index 217a02b..46eefaa 100644 --- a/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/Statistics/Statistics.tsx +++ b/src/features/ServerPage/features/PlayerPage/features/IndexPage/components/Statistics/Statistics.tsx @@ -3,12 +3,14 @@ import { useQuery } from '@apollo/client'; import { PLAYER_HISTORY } from './queries'; import { LIMIT } from './constants'; -import { Paper } from '@material-ui/core'; -import { Serie } from '@nivo/line'; +import { makeStyles, useTheme } from '@material-ui/core/styles'; +import { Paper, useMediaQuery } from '@material-ui/core'; +import { Skeleton } from '@material-ui/lab'; import LineChart from '@common/Chart/LineChart'; import ModeSelector from '@features/ServerPage/common/ModeSelector/ModeSelector'; import { TFunction } from 'i18next'; +import { Serie } from '@nivo/line'; import { PlayerHistoryQueryVariables } from '@libs/graphql/types'; import { Mode, PlayerHistory } from './types'; @@ -20,7 +22,9 @@ export interface Props { function Statistics({ t, server, playerID }: Props) { const [mode, setMode] = useState('points'); - const { loading: loadingData, data: queryRes } = useQuery< + const theme = useTheme(); + const isMobileDevice = useMediaQuery(theme.breakpoints.down('sm')); + const { loading, data: queryRes } = useQuery< PlayerHistory, PlayerHistoryQueryVariables >(PLAYER_HISTORY, { @@ -35,10 +39,12 @@ function Statistics({ t, server, playerID }: Props) { }, }); const items = useMemo( - () => [...(queryRes?.playerHistory?.items ?? [])].reverse(), - [queryRes] + () => + [...(queryRes?.playerHistory?.items ?? [])] + .slice(0, isMobileDevice ? 20 : undefined) + .reverse(), + [queryRes, isMobileDevice] ); - const loading = loadingData && items.length === 0; const data = useMemo(() => { if (loading) return []; return [ @@ -101,63 +107,67 @@ function Statistics({ t, server, playerID }: Props) { }, ]} /> -
- v.toLocaleString(), - }} - pointSize={10} - pointColor={{ theme: 'background' }} - pointBorderWidth={2} - pointBorderColor={{ from: 'serieColor' }} - pointLabelYOffset={-12} - useMesh={true} - colors={{ scheme: 'nivo' }} - yFormat={(v: string | number | Date) => v.toLocaleString()} - legends={[ - { - anchor: 'bottom-right', - direction: 'column', - justify: false, - translateX: 90, - translateY: 0, - itemsSpacing: 0, - itemDirection: 'left-to-right', - itemWidth: 80, - itemHeight: 20, - itemOpacity: 0.75, - symbolSize: 12, - symbolShape: 'circle', - symbolBorderColor: 'rgba(0, 0, 0, .5)', - }, - ]} - /> -
+ {loading ? ( + + ) : ( +
+ v.toLocaleString(), + }} + pointSize={10} + pointColor={{ theme: 'background' }} + pointBorderWidth={2} + pointBorderColor={{ from: 'serieColor' }} + pointLabelYOffset={-12} + useMesh={true} + colors={{ scheme: 'nivo' }} + yFormat={(v: string | number | Date) => v.toLocaleString()} + legends={[ + { + anchor: 'bottom-right', + direction: 'column', + justify: false, + translateX: 90, + translateY: 0, + itemsSpacing: 0, + itemDirection: 'left-to-right', + itemWidth: 80, + itemHeight: 20, + itemOpacity: 0.75, + symbolSize: 12, + symbolShape: 'circle', + symbolBorderColor: 'rgba(0, 0, 0, .5)', + }, + ]} + /> +
+ )} ); } diff --git a/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/Provider.tsx b/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/Provider.tsx index 2d26a23..0d8f0f1 100644 --- a/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/Provider.tsx +++ b/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/Provider.tsx @@ -24,7 +24,7 @@ function Provider({ children }: Props) { PlayerQueryResult, PlayerQueryVariables >(PLAYER, { - fetchPolicy: 'cache-first', + fetchPolicy: 'cache-and-network', variables: { id: parseInt(id, 10), server: key, diff --git a/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/types.ts b/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/types.ts index ae76d51..35c3797 100644 --- a/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/types.ts +++ b/src/features/ServerPage/features/PlayerPage/libs/PlayerPageContext/types.ts @@ -1,3 +1,9 @@ +export type NameChange = { + oldName: string; + newName: string; + changeDate: Date | string; +}; + export type Player = { id: number; name: string; @@ -23,11 +29,7 @@ export type Player = { joinedAt: Date | string; deletedAt?: Date | string; servers: string[]; - nameChanges: { - oldName: string; - newName: string; - changeDate: Date | string; - }[]; + nameChanges: NameChange[]; tribe?: { id: number; tag: string; diff --git a/src/libs/i18n/en/server-page/player-page/index-page.ts b/src/libs/i18n/en/server-page/player-page/index-page.ts index 6093946..3785ad3 100644 --- a/src/libs/i18n/en/server-page/player-page/index-page.ts +++ b/src/libs/i18n/en/server-page/player-page/index-page.ts @@ -23,6 +23,15 @@ const translations = { bestRank: 'Best rank', mostPoints: 'Most points', mostVillages: 'Most villages', + servers: 'Servers', + }, + nameChanges: { + title: 'Name changes', + columns: { + changeDate: 'Date', + newName: 'New name', + oldName: 'Old name', + }, }, }; diff --git a/src/libs/i18n/pl/server-page/player-page/index-page.ts b/src/libs/i18n/pl/server-page/player-page/index-page.ts index 5637722..d30a76d 100644 --- a/src/libs/i18n/pl/server-page/player-page/index-page.ts +++ b/src/libs/i18n/pl/server-page/player-page/index-page.ts @@ -23,6 +23,15 @@ const translations = { bestRank: 'Najlepszy ranking', mostPoints: 'Najwięcej punktów', mostVillages: 'Najwięcej wiosek', + servers: 'Serwery', + }, + nameChanges: { + title: 'Zmiany nicków', + columns: { + changeDate: 'Data', + newName: 'Nowy nick', + oldName: 'Stary nick', + }, }, };