WarStatsPage works in 100%

This commit is contained in:
Dawid Wysokiński 2020-12-29 19:24:40 +01:00
parent a16e2ea752
commit 48da47a8ab
19 changed files with 384 additions and 60 deletions

View File

@ -9,6 +9,7 @@
"@material-ui/icons": "^4.11.0",
"@material-ui/lab": "^4.0.0-alpha.57",
"@material-ui/pickers": "3.2.10",
"@nivo/bar": "0.64.0",
"@nivo/core": "0.64.0",
"@nivo/line": "0.64.0",
"@testing-library/jest-dom": "^5.11.4",

View File

@ -0,0 +1,64 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { LINE_CHART } from '@config/namespaces';
import { darkTheme } from './theme';
import { ResponsiveBar, BarSvgProps } from '@nivo/bar';
import { BasicTooltip } from '@nivo/tooltip';
import { Box, Typography } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';
export interface Props extends BarSvgProps {
loading?: boolean;
}
const BarChart = ({ data, loading, tooltipFormat, ...rest }: Props) => {
const { t } = useTranslation(LINE_CHART);
if (loading) {
return <Skeleton height="100%" variant="rect" />;
}
if (data.length === 0) {
return (
<Box
display="flex"
justifyContent="center"
height="100%"
alignItems="center"
>
<Typography variant="h4" align="center">
{t('emptyDataSourceMessage')}
</Typography>
</Box>
);
}
return (
<ResponsiveBar
data={data}
tooltipFormat={tooltipFormat}
tooltip={props => {
return (
<BasicTooltip
{...props}
enableChip
id={
typeof props.data[`${props.id}Label`] === 'string'
? props.data[`${props.id}Label`]
: `${props.id} - ${props.indexValue}`
}
value={
typeof tooltipFormat === 'function'
? tooltipFormat(props.value)
: props.value
}
/>
);
}}
{...rest}
theme={darkTheme}
/>
);
};
export default BarChart;

View File

@ -6,7 +6,6 @@ import { darkTheme } from './theme';
import { ResponsiveLine, LineSvgProps } from '@nivo/line';
import { Box, Typography } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';
import PointTooltip from './PointTooltip';
export interface Props extends LineSvgProps {
loading?: boolean;
@ -35,7 +34,7 @@ const LineChart = ({ data, loading, ...rest }: Props) => {
}
return (
<ResponsiveLine
tooltip={PointTooltip}
// tooltip={PointTooltip}
data={data}
{...rest}
theme={darkTheme}

View File

@ -1,15 +1,16 @@
import React from 'react';
import { Tooltip } from '@material-ui/core';
import { PointTooltipProps } from '@nivo/line';
import { BarTooltipDatum } from '@nivo/bar';
function PointTooltip(props: PointTooltipProps) {
function PointTooltip(props: PointTooltipProps | BarTooltipDatum) {
const title =
'point' in props
? `X: ${props.point.data.xFormatted}, Y: ${props.point.data.yFormatted}`
: `${props.indexValue} - ${props.value}`;
console.log(props);
return (
<Tooltip
open
placement="top"
arrow
title={`X: ${props.point.data.xFormatted}, Y: ${props.point.data.yFormatted}`}
>
<Tooltip open placement="top" arrow title={title}>
<div></div>
</Tooltip>
);

View File

@ -12,6 +12,12 @@ export const darkTheme: Theme = {
strokeWidth: 1,
},
},
textColor: '#fff',
tooltip: {
container: {
backgroundColor: '#000',
},
},
axis: {
legend: {
text: {

View File

@ -19,6 +19,7 @@ import {
Map as MapIcon,
Grade as GradeIcon,
Beenhere as BeenhereIcon,
Fireplace as FireplaceIcon,
} from '@material-ui/icons';
import DevNote from '@common/DevNote/DevNote';
import Nav from './components/Nav/Nav';
@ -123,6 +124,13 @@ const Sidebar = ({ t, className, open, variant, onClose, onOpen }: Props) => {
Icon: <MapIcon color="inherit" />,
exact: true,
},
{
name: t('pageLayout.sidebar.routes.warStats'),
to: ROUTES.SERVER_PAGE.WAR_STATS_PAGE,
params: { key },
Icon: <FireplaceIcon color="inherit" />,
exact: true,
},
];
return (

View File

@ -22,19 +22,21 @@ import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout'
import Spinner from '@common/Spinner/Spinner';
import Card from './components/Card/Card';
import OneSide from './components/OneSide/OneSide';
import Result from './components/Result/Result';
import Results from './components/Results/Results';
import {
EnnoblementsQueryResult,
EnnoblementsQueryVariables,
Result as ResultT,
Results as ResultsT,
SideResult,
Player,
Tribe,
} from './types';
function WarStatsPage() {
const now = useRef(new Date());
const [isSubmitting, setIsSubmitting] = useState(false);
const [result, setResult] = useState<ResultT | null>(null);
const [results, setResults] = useState<ResultsT | null>(null);
const [query, setQuery] = useQueryParams({
ennobledAtGTE: withDefault(DateTimeParam, subDays(now.current, 1)),
ennobledAtLTE: withDefault(DateTimeParam, now.current),
@ -70,13 +72,17 @@ function WarStatsPage() {
totalGained = 0,
totalLost = 0,
tribesGained = 0,
playersGained = 0
playersGained = 0,
players: Player[] = [],
tribes: Tribe[] = []
): SideResult => {
return {
gained: totalGained,
lost: totalLost,
againstOppositeSide: tribesGained + playersGained,
difference: Math.abs(totalGained - totalLost),
difference: totalGained - totalLost,
players,
tribes,
};
};
@ -171,15 +177,19 @@ function WarStatsPage() {
data.sideOneTotalGained?.total,
data.sideOneTotalLost?.total,
data.sideOneTribes?.total,
data.sideOnePlayers?.total
data.sideOnePlayers?.total,
sideOnePlayers,
sideOneTribes
);
const computedSideTwoResult: SideResult = getSideResult(
data.sideTwoTotalGained?.total,
data.sideTwoTotalLost?.total,
data.sideTwoTribes?.total,
data.sideTwoPlayers?.total
data.sideTwoPlayers?.total,
sideTwoPlayers,
sideTwoTribes
);
setResult({
setResults({
sideOne: computedSideOneResult,
sideTwo: computedSideTwoResult,
difference: Math.abs(
@ -303,9 +313,9 @@ function WarStatsPage() {
</Button>
</Typography>
</Grid>
{result && (
{results && (
<Grid item xs={12}>
<Result data={result} />
<Results data={results} server={server.key} />
</Grid>
)}
</Grid>

View File

@ -164,7 +164,7 @@ function OneSide({
</Typography>
<div>
<Typography variant="h5" align="center" gutterBottom>
{t('oneSide.players')}
{t('players')}
</Typography>
<Autocomplete
{...autocompleteProps}
@ -189,7 +189,7 @@ function OneSide({
</div>
<div>
<Typography variant="h5" align="center" gutterBottom>
{t('oneSide.tribes')}
{t('tribes')}
</Typography>
<Autocomplete
{...autocompleteProps}

View File

@ -1,27 +0,0 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SERVER_PAGE } from '@config/namespaces';
import { Card, CardContent, Typography } from '@material-ui/core';
import { Result as ResultT } from '../../types';
export interface Props {
data: ResultT;
}
function Result({ data }: Props) {
const { t } = useTranslation(SERVER_PAGE.WAR_STATS_PAGE);
console.log(data);
return (
<Card>
<CardContent>
<Typography variant="h4" align="center">
{t('sections.result')}
</Typography>
</CardContent>
</Card>
);
}
export default Result;

View File

@ -0,0 +1,11 @@
import React from 'react';
export interface Props {
children?: React.ReactNode;
}
function ChartWrapper({ children }: Props) {
return <div style={{ height: '400px' }}>{children}</div>;
}
export default ChartWrapper;

View File

@ -0,0 +1,67 @@
import React from 'react';
import formatNumber from '@utils/formatNumber';
import * as ROUTES from '@config/routes';
import { Typography } from '@material-ui/core';
import Link from '@common/Link/Link';
import { TFunction } from 'i18next';
import { SideResult } from '../../types';
export interface Props {
data: SideResult;
t: TFunction;
server: string;
title: string;
}
function OneSideResult({ data, t, server, title }: Props) {
return (
<div>
<Typography variant="h5" gutterBottom>
{title}
</Typography>
{data.players.length > 0 && (
<Typography>
{t('players')}:{' '}
{data.players.map(player => (
<Link
key={player.id}
to={ROUTES.SERVER_PAGE.PLAYER_PAGE.INDEX_PAGE}
params={{ id: player.id, key: server }}
>
{player.name}{' '}
</Link>
))}
</Typography>
)}
{data.tribes.length > 0 && (
<Typography>
{t('tribes')}:{' '}
{data.tribes.map(tribe => (
<Link
key={tribe.id}
to={ROUTES.SERVER_PAGE.TRIBE_PAGE.INDEX_PAGE}
params={{ id: tribe.id, key: server }}
>
{tribe.tag}{' '}
</Link>
))}
</Typography>
)}
<Typography>
{t('results.gained')}:{' '}
<strong>{formatNumber('commas', data.gained)}</strong>
</Typography>
<Typography>
{t('results.lost')}:{' '}
<strong>{formatNumber('commas', data.lost)}</strong>
</Typography>
<Typography>
{t('results.difference')}:{' '}
<strong>{formatNumber('commas', data.difference)}</strong>
</Typography>
</div>
);
}
export default OneSideResult;

View File

@ -0,0 +1,137 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import formatNumber from '@utils/formatNumber';
import { SERVER_PAGE } from '@config/namespaces';
import { useTheme } from '@material-ui/core/styles';
import {
Card,
CardContent,
Typography,
useMediaQuery,
Grid,
} from '@material-ui/core';
import BarChart from '@common/Chart/BarChart';
import OneSideResult from './OneSideResult';
import ChartWrapper from './ChartWrapper';
import { Results as ResultsT } from '../../types';
export interface Props {
data: ResultsT;
server: string;
}
function Results({ data, server }: Props) {
const theme = useTheme();
const isWidthDown750 = useMediaQuery(theme.breakpoints.down(750));
const { t } = useTranslation(SERVER_PAGE.WAR_STATS_PAGE);
const tooltipFormat = (v: string | number | Date) =>
typeof v === 'string' || typeof v === 'number'
? formatNumber('commas', v)
: v.toLocaleString();
return (
<Card>
<CardContent>
<Typography variant="h4" align="center" gutterBottom>
{t('sections.results')}
</Typography>
<Grid container spacing={1}>
<Grid item xs={12} sm={4}>
<OneSideResult
server={server}
title={t('results.sideOne')}
data={data.sideOne}
t={t}
/>
</Grid>
<Grid item xs={12} sm={4}>
<OneSideResult
server={server}
title={t('results.sideTwo')}
data={data.sideTwo}
t={t}
/>
</Grid>
<Grid item xs={12} sm={4}>
<Typography variant="h5" gutterBottom>
{t('results.ennoblementsAgainstOppositeSide')}
</Typography>
<Typography>
{t('results.sideOne')}:{' '}
<strong>
{formatNumber('commas', data.sideOne.againstOppositeSide)}
</strong>
</Typography>
<Typography>
{t('results.sideTwo')}:{' '}
<strong>
{formatNumber('commas', data.sideTwo.againstOppositeSide)}
</strong>
</Typography>
<Typography>
{t('results.difference')}:{' '}
<strong>{formatNumber('commas', data.difference)}</strong>
</Typography>
</Grid>
</Grid>
<ChartWrapper>
<BarChart
data={[
{
id: t('results.sideOne'),
val1: data.sideOne.gained,
val1Label: t('results.chart.sideOneGained'),
val2: data.sideOne.lost,
val2Label: t('results.chart.sideOneLost'),
},
{
id: t('results.sideTwo'),
val1: data.sideTwo.gained,
val1Label: t('results.chart.sideTwoGained'),
val2: data.sideTwo.lost,
val2Label: t('results.chart.sideTwoLost'),
},
{
id: t('results.ennoblementsAgainstOppositeSide'),
val1: data.sideOne.againstOppositeSide,
val1Label: t('results.sideOne'),
val2: data.sideTwo.againstOppositeSide,
val2Label: t('results.sideTwo'),
},
]}
keys={['val1', 'val2']}
colors={['green', 'red']}
indexBy="id"
margin={{ top: 30, right: 35, bottom: 30, left: 35 }}
padding={0.1}
groupMode="grouped"
tooltipFormat={tooltipFormat}
axisTop={null}
axisRight={null}
axisBottom={{
tickSize: 0,
tickPadding: 5,
tickRotation: 0,
format: isWidthDown750 ? () => '' : undefined,
}}
axisLeft={{
tickSize: 0,
tickPadding: 5,
tickRotation: 0,
format: tooltipFormat,
}}
labelSkipWidth={12}
labelSkipHeight={12}
animate={true}
motionStiffness={90}
motionDamping={15}
/>
</ChartWrapper>
</CardContent>
</Card>
);
}
export default Results;

View File

@ -66,9 +66,11 @@ export type SideResult = {
lost: number;
difference: number;
againstOppositeSide: number;
players: Player[];
tribes: Tribe[];
};
export type Result = {
export type Results = {
sideOne: SideResult;
sideTwo: SideResult;
difference: number;

View File

@ -23,6 +23,7 @@ const translations = {
},
ennoblements: 'Ennoblements',
map: 'Map tool',
warStats: 'War stats',
},
serverInfo: {
numberOfPlayers: '{{num}} player',

View File

@ -8,17 +8,29 @@ const translations = {
settings: 'Settings',
sideOne: 'Side one',
sideTwo: 'Side two',
result: 'Result',
},
oneSide: {
players: 'Players',
tribes: 'Tribes',
results: 'Results',
},
players: 'Players',
tribes: 'Tribes',
loading: 'Loading...',
noOptions: 'No options',
buttons: {
generateStats: 'Generate stats',
},
results: {
sideOne: 'Side one',
sideTwo: 'Side two',
lost: 'Lost',
gained: 'Gained',
difference: 'Difference',
ennoblementsAgainstOppositeSide: 'Ennoblements against opposite side',
chart: {
sideOneGained: 'Side one - gained',
sideOneLost: 'Side one - lost',
sideTwoGained: 'Side two - gained',
sideTwoLost: 'Side two - lost',
},
},
};
export default translations;

View File

@ -16,6 +16,7 @@ const init = (): i18nT => {
load: 'languageOnly',
detection: {
order: ['subdomain'],
lookupFromSubdomainIndex: 0,
},
resources: {
en,

View File

@ -23,6 +23,7 @@ const translations = {
},
ennoblements: 'Przejęcia',
map: 'Narzędzie mapy',
warStats: 'Statystyki wojenne',
},
serverInfo: {
numberOfPlayers_0: '{{num}} gracz',

View File

@ -8,17 +8,29 @@ const translations = {
settings: 'Opcje',
sideOne: 'Strona pierwsza',
sideTwo: 'Strona druga',
result: 'Wynik',
},
oneSide: {
players: 'Gracze',
tribes: 'Plemiona',
results: 'Wyniki',
},
players: 'Gracze',
tribes: 'Plemiona',
loading: 'Ładowanie...',
noOptions: 'Brak opcji',
buttons: {
generateStats: 'Wygeneruj statystyki',
},
results: {
sideOne: 'Strona pierwsza',
sideTwo: 'Strona druga',
lost: 'Stracone',
gained: 'Zdobyte',
difference: 'Różnica',
ennoblementsAgainstOppositeSide: 'Przejęcia przeciwnej strony',
chart: {
sideOneGained: 'Strona pierwsza - zdobyte',
sideOneLost: 'Strona pierwsza - stracone',
sideTwoGained: 'Strona druga - zdobyte',
sideTwoLost: 'Strona druga - stracone',
},
},
};
export default translations;

View File

@ -1428,6 +1428,24 @@
d3-time-format "^2.1.3"
react-spring "^8.0.27"
"@nivo/bar@0.64.0":
version "0.64.0"
resolved "https://registry.yarnpkg.com/@nivo/bar/-/bar-0.64.0.tgz#ed6e793792fe4de62878be3645a5c3e6d437a0d7"
integrity sha512-/AFeuvlok5+Hx3umEEtBXU3I5Pmo9oauWeqBrEejsA2Vb/Te/tdsruiSBI6d2s69XVnGU0i/E9lmC7HmcIp+/Q==
dependencies:
"@nivo/annotations" "0.64.0"
"@nivo/axes" "0.64.0"
"@nivo/colors" "0.64.0"
"@nivo/core" "0.64.0"
"@nivo/legends" "0.64.0"
"@nivo/scales" "0.64.0"
"@nivo/tooltip" "0.64.0"
d3-scale "^3.0.0"
d3-shape "^1.2.2"
lodash "^4.17.11"
react-motion "^0.5.2"
recompose "^0.30.0"
"@nivo/colors@0.64.0":
version "0.64.0"
resolved "https://registry.yarnpkg.com/@nivo/colors/-/colors-0.64.0.tgz#b754950809738bd8add710f56bcd344689347743"
@ -4196,7 +4214,7 @@ d3-scale@^3.0.0:
d3-time "1 - 2"
d3-time-format "2 - 3"
d3-shape@^1.3.5:
d3-shape@^1.2.2, d3-shape@^1.3.5:
version "1.3.7"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==