huge refactor

This commit is contained in:
Dawid Wysokiński 2020-12-31 14:02:38 -09:00
parent 819abb008f
commit 645af393f8
43 changed files with 1054 additions and 131 deletions

View File

@ -11,7 +11,7 @@ export interface Props extends LineSvgProps {
loading?: boolean; loading?: boolean;
} }
const LineChart = ({ data, loading, ...rest }: Props) => { const LineChart = ({ data, loading, yScale, xScale, ...rest }: Props) => {
const { t } = useTranslation(LINE_CHART); const { t } = useTranslation(LINE_CHART);
if (loading) { if (loading) {
@ -37,6 +37,8 @@ const LineChart = ({ data, loading, ...rest }: Props) => {
// tooltip={PointTooltip} // tooltip={PointTooltip}
data={data} data={data}
{...rest} {...rest}
xScale={xScale?.type === 'time' ? { useUTC: false, ...xScale } : xScale}
yScale={yScale?.type === 'time' ? { useUTC: false, ...yScale } : yScale}
theme={darkTheme} theme={darkTheme}
/> />
); );

View File

@ -8,7 +8,6 @@ function PointTooltip(props: PointTooltipProps | BarTooltipDatum) {
'point' in props 'point' in props
? `X: ${props.point.data.xFormatted}, Y: ${props.point.data.yFormatted}` ? `X: ${props.point.data.xFormatted}, Y: ${props.point.data.yFormatted}`
: `${props.indexValue} - ${props.value}`; : `${props.indexValue} - ${props.value}`;
console.log(props);
return ( return (
<Tooltip open placement="top" arrow title={title}> <Tooltip open placement="top" arrow title={title}>
<div></div> <div></div>

View File

@ -1,10 +1,12 @@
import React from 'react'; import React from 'react';
import useDateUtils from '@libs/date/useDateUtils';
import { AUTHOR } from '@config/app'; import { AUTHOR } from '@config/app';
import useStyles from './styles'; import useStyles from './styles';
import { AppBar, Toolbar, Container, Typography } from '@material-ui/core'; import { AppBar, Toolbar, Container, Typography } from '@material-ui/core';
export default function Header() { export default function Header() {
const dateUtils = useDateUtils();
const classes = useStyles(); const classes = useStyles();
return ( return (
@ -12,7 +14,7 @@ export default function Header() {
<Container> <Container>
<Toolbar disableGutters className={classes.toolbar}> <Toolbar disableGutters className={classes.toolbar}>
<Typography align="center" className={classes.copyright}> <Typography align="center" className={classes.copyright}>
&copy; {new Date().getFullYear()} {AUTHOR} &copy; {dateUtils.getYear(dateUtils.date())} {AUTHOR}
</Typography> </Typography>
</Toolbar> </Toolbar>
</Container> </Container>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { get, isString, isNumber } from 'lodash'; import { get, isString, isNumber } from 'lodash';
import { format } from 'date-fns'; import useDateUtils from '@libs/date/useDateUtils';
import { DATE_FORMAT } from '@config/app'; import { DATE_FORMAT } from '@config/app';
import { TableRow, TableCell, Checkbox, Tooltip } from '@material-ui/core'; import { TableRow, TableCell, Checkbox, Tooltip } from '@material-ui/core';
@ -28,6 +28,8 @@ function EnhancedTableRow<T extends object>({
size = 'medium', size = 'medium',
index, index,
}: Props<T>) { }: Props<T>) {
const dateUtils = useDateUtils();
const handleSelect = () => { const handleSelect = () => {
if (onSelect) { if (onSelect) {
onSelect(row); onSelect(row);
@ -35,15 +37,30 @@ function EnhancedTableRow<T extends object>({
}; };
const formatValue = ( const formatValue = (
v: string | number, v: string | number | Date,
type: 'datetime' | 'date' | 'normal' type: 'datetime' | 'dateutc' | 'date' | 'normal'
) => { ) => {
if ((isString(v) || isNumber(v)) && type === 'date') { if ((isString(v) || isNumber(v) || v instanceof Date) && type === 'date') {
return format(new Date(v), DATE_FORMAT.DAY_MONTH_AND_YEAR); return dateUtils.format(
dateUtils.date(v),
DATE_FORMAT.DAY_MONTH_AND_YEAR
);
} }
if ((isString(v) || isNumber(v)) && type === 'datetime') { if (
return format( (isString(v) || isNumber(v) || v instanceof Date) &&
new Date(v), type === 'dateutc'
) {
return dateUtils.format(
dateUtils.dateInTZ(v, 'UTC'),
DATE_FORMAT.DAY_MONTH_AND_YEAR
);
}
if (
(isString(v) || isNumber(v) || v instanceof Date) &&
type === 'datetime'
) {
return dateUtils.format(
dateUtils.date(v),
DATE_FORMAT.HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_SECONDS_DAY_MONTH_AND_YEAR
); );
} }

View File

@ -9,7 +9,7 @@ export type Column<T = any> = {
sortable?: boolean; sortable?: boolean;
valueFormatter?: (v: T, i: number) => React.ReactNode; valueFormatter?: (v: T, i: number) => React.ReactNode;
disablePadding?: boolean; disablePadding?: boolean;
type?: 'normal' | 'datetime' | 'date'; type?: 'normal' | 'dateutc' | 'datetime' | 'date';
align?: 'left' | 'right' | 'center'; align?: 'left' | 'right' | 'center';
}; };

View File

@ -1,4 +1,4 @@
export const DEFAULT_LANGUAGE = process.env.REACT_APP_DEFAULT_LANGUAGE ?? 'en'; export const DEFAULT_LANGUAGE = process.env.REACT_APP_DEFAULT_LANGUAGE ?? 'pl';
export const NAME = 'TWHelp'; export const NAME = 'TWHelp';

View File

@ -5,6 +5,8 @@ import { Switch, Route } from 'react-router-dom';
import { CssBaseline } from '@material-ui/core'; import { CssBaseline } from '@material-ui/core';
import ScrollToTop from '@common/ScrollToTop/ScrollToTop'; import ScrollToTop from '@common/ScrollToTop/ScrollToTop';
import VersionProvider from '../libs/VersionContext/Provider';
import DateUtilsProvider from '../libs/date/DateUtilsProvider';
import IndexPage from './IndexPage/IndexPage'; import IndexPage from './IndexPage/IndexPage';
import NotFoundPage from './NotFoundPage/NotFoundPage'; import NotFoundPage from './NotFoundPage/NotFoundPage';
import SearchPage from './SearchPage/SearchPage'; import SearchPage from './SearchPage/SearchPage';
@ -13,20 +15,24 @@ import ServerPage from './ServerPage/ServerPage';
function App() { function App() {
return ( return (
<Fragment> <Fragment>
<Switch> <VersionProvider>
<Route path={ROUTES.INDEX_PAGE} exact> <DateUtilsProvider>
<IndexPage /> <Switch>
</Route> <Route path={ROUTES.INDEX_PAGE} exact>
<Route path={ROUTES.SEARCH_PAGE} exact> <IndexPage />
<SearchPage /> </Route>
</Route> <Route path={ROUTES.SEARCH_PAGE} exact>
<Route path={ROUTES.SERVER_PAGE.INDEX_PAGE}> <SearchPage />
<ServerPage /> </Route>
</Route> <Route path={ROUTES.SERVER_PAGE.INDEX_PAGE}>
<Route path="*"> <ServerPage />
<NotFoundPage /> </Route>
</Route> <Route path="*">
</Switch> <NotFoundPage />
</Route>
</Switch>
</DateUtilsProvider>
</VersionProvider>
<CssBaseline /> <CssBaseline />
<ScrollToTop /> <ScrollToTop />
</Fragment> </Fragment>

View File

@ -76,7 +76,6 @@ function GridItem({ t, server, hideTooltip = true }: Props) {
classes={{ classes={{
tooltip: classes.tooltip, tooltip: classes.tooltip,
}} }}
arrow
title={serverInfo} title={serverInfo}
> >
<InfoIcon color="inherit" /> <InfoIcon color="inherit" />

View File

@ -1,13 +1,11 @@
import React from 'react'; import React from 'react';
import useDateUtils from '@libs/date/useDateUtils';
import useServer from '@features/ServerPage/libs/ServerContext/useServer'; import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import useStyles from './useStyles'; import useStyles from './useStyles';
import formatDistanceToNow from '@libs/date/formatDistanceToNow';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
import extractVersionCodeFromHostname from '@utils/extractVersionCodeFromHostname';
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import { Locale } from '@libs/date/locales';
import { TFunction } from 'i18next'; import { TFunction } from 'i18next';
export interface Props { export interface Props {
@ -21,6 +19,7 @@ const ServerInfo = ({ t }: Props) => {
dataUpdatedAt, dataUpdatedAt,
numberOfVillages, numberOfVillages,
} = useServer(); } = useServer();
const dateUtils = useDateUtils();
const classes = useStyles(); const classes = useStyles();
@ -46,10 +45,7 @@ const ServerInfo = ({ t }: Props) => {
</Typography> </Typography>
<Typography> <Typography>
{t('pageLayout.sidebar.serverInfo.dataUpdatedAt', { {t('pageLayout.sidebar.serverInfo.dataUpdatedAt', {
date: formatDistanceToNow(new Date(dataUpdatedAt), { date: dateUtils.formatDistanceToNow(new Date(dataUpdatedAt), {
locale: extractVersionCodeFromHostname(
window.location.hostname
) as Locale,
addSuffix: true, addSuffix: true,
}), }),
})} })}

View File

@ -6,6 +6,7 @@ import {
withDefault, withDefault,
DateTimeParam, DateTimeParam,
} from 'use-query-params'; } from 'use-query-params';
import useDateUtils from '@libs/date/useDateUtils';
import useScrollToElement from '@libs/useScrollToElement'; import useScrollToElement from '@libs/useScrollToElement';
import { validateRowsPerPage } from '@common/Table/helpers'; import { validateRowsPerPage } from '@common/Table/helpers';
import { ENNOBLEMENTS } from './queries'; import { ENNOBLEMENTS } from './queries';
@ -27,11 +28,12 @@ export interface Props {
function LatestSavedEnnoblements({ t, server }: Props) { function LatestSavedEnnoblements({ t, server }: Props) {
const classes = useStyles(); const classes = useStyles();
const now = useRef(new Date()); const dateUtils = useDateUtils();
const now = useRef(dateUtils.date());
const [query, setQuery] = useQueryParams({ const [query, setQuery] = useQueryParams({
page: withDefault(NumberParam, 0), page: withDefault(NumberParam, 0),
limit: withDefault(NumberParam, LIMIT), limit: withDefault(NumberParam, LIMIT),
ennobledAtGTE: withDefault(DateTimeParam, new Date(0)), ennobledAtGTE: withDefault(DateTimeParam, dateUtils.date(0)),
ennobledAtLTE: withDefault(DateTimeParam, now.current), ennobledAtLTE: withDefault(DateTimeParam, now.current),
}); });
const limit = validateRowsPerPage(query.limit); const limit = validateRowsPerPage(query.limit);
@ -46,8 +48,8 @@ function LatestSavedEnnoblements({ t, server }: Props) {
offset: query.page * limit, offset: query.page * limit,
sort: ['ennobledAt DESC'], sort: ['ennobledAt DESC'],
filter: { filter: {
ennobledAtGTE: query.ennobledAtGTE, ennobledAtGTE: dateUtils.zonedTimeToUTC(query.ennobledAtGTE),
ennobledAtLTE: query.ennobledAtLTE, ennobledAtLTE: dateUtils.zonedTimeToUTC(query.ennobledAtLTE),
}, },
server, server,
}, },

View File

@ -1,5 +1,6 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useDateUtils from '@libs/date/useDateUtils';
import { STATISTICS } from './queries'; import { STATISTICS } from './queries';
import { LIMIT } from './constants'; import { LIMIT } from './constants';
@ -19,6 +20,7 @@ export interface Props {
} }
function PlayerStatistics({ server, t }: Props) { function PlayerStatistics({ server, t }: Props) {
const dateUtils = useDateUtils();
const { loading: loadingData, data: queryRes } = useQuery< const { loading: loadingData, data: queryRes } = useQuery<
ServerStats, ServerStats,
ServerStatsQueryVariables ServerStatsQueryVariables
@ -40,16 +42,18 @@ function PlayerStatistics({ server, t }: Props) {
return [ return [
{ {
id: t<string>('playerStatistics.players'), id: t<string>('playerStatistics.players'),
data: items.map(item => ({ data: items.map(item => {
x: new Date(item.createDate), return {
y: item.activePlayers, x: dateUtils.dateInTZ(item.createDate, 'UTC'),
})), y: item.activePlayers,
};
}),
}, },
]; ];
}, [items, loading, t]); }, [items, loading, t, dateUtils]);
return ( return (
<Paper size="large" style={{ overflow: 'hidden' }}> <Paper size="large" style={{ overflow: 'visible' }}>
<TableToolbar> <TableToolbar>
<Typography variant="h4">{t('playerStatistics.title')}</Typography> <Typography variant="h4">{t('playerStatistics.title')}</Typography>
</TableToolbar> </TableToolbar>
@ -70,7 +74,6 @@ function PlayerStatistics({ server, t }: Props) {
}} }}
xFormat="time:%Y-%m-%d" xFormat="time:%Y-%m-%d"
axisBottom={{ axisBottom={{
tickSize: 0,
tickValues: 'every 6 days', tickValues: 'every 6 days',
tickPadding: 5, tickPadding: 5,
tickRotation: 0, tickRotation: 0,

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useServer from '@features/ServerPage/libs/ServerContext/useServer'; import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import useDateUtils from '@libs/date/useDateUtils';
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';
@ -24,6 +25,7 @@ export interface Props {
function TodaysBestStatsPlayers({ t }: Props) { function TodaysBestStatsPlayers({ t }: Props) {
const server = useServer(); const server = useServer();
const dateUtils = useDateUtils();
const [mode, setMode] = useState<Mode>('scoreAtt'); const [mode, setMode] = useState<Mode>('scoreAtt');
const { loading: loadingData, data } = useQuery< const { loading: loadingData, data } = useQuery<
DailyPlayerStatsList, DailyPlayerStatsList,
@ -34,7 +36,7 @@ function TodaysBestStatsPlayers({ t }: Props) {
limit: LIMIT, limit: LIMIT,
sort: [mode + ' DESC'], sort: [mode + ' DESC'],
filter: { filter: {
createDate: server.historyUpdatedAt, createDate: dateUtils.toJSON(dateUtils.date(server.historyUpdatedAt)),
}, },
server: server.key, server: server.key,
}, },

View File

@ -16,7 +16,7 @@ export const COLUMNS: Column<DailyPlayerStatsRecord>[] = [
field: 'createDate', field: 'createDate',
label: 'todaysBestStatsPlayers.columns.createDate', label: 'todaysBestStatsPlayers.columns.createDate',
sortable: false, sortable: false,
type: 'date', type: 'dateutc',
}, },
]; ];

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useServer from '@features/ServerPage/libs/ServerContext/useServer'; import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import useDateUtils from '@libs/date/useDateUtils';
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';
@ -23,6 +24,7 @@ export interface Props {
function TodaysBestStatsTribes({ t }: Props) { function TodaysBestStatsTribes({ t }: Props) {
const server = useServer(); const server = useServer();
const dateUtils = useDateUtils();
const [mode, setMode] = useState<Mode>('scoreAtt'); const [mode, setMode] = useState<Mode>('scoreAtt');
const { loading: loadingData, data } = useQuery< const { loading: loadingData, data } = useQuery<
DailyTribeStatsList, DailyTribeStatsList,
@ -33,11 +35,12 @@ function TodaysBestStatsTribes({ t }: Props) {
limit: LIMIT, limit: LIMIT,
sort: [mode + ' DESC'], sort: [mode + ' DESC'],
filter: { filter: {
createDate: server.historyUpdatedAt, createDate: dateUtils.toJSON(dateUtils.date(server.historyUpdatedAt)),
}, },
server: server.key, server: server.key,
}, },
}); });
console.log(dateUtils.toJSON(dateUtils.date(server.historyUpdatedAt)));
const records = data?.dailyTribeStats?.items ?? []; const records = data?.dailyTribeStats?.items ?? [];
const loading = loadingData && records.length === 0; const loading = loadingData && records.length === 0;

View File

@ -16,7 +16,7 @@ export const COLUMNS: Column<DailyTribeStatsRecord>[] = [
field: 'createDate', field: 'createDate',
label: 'todaysBestStatsTribes.columns.createDate', label: 'todaysBestStatsTribes.columns.createDate',
sortable: false, sortable: false,
type: 'date', type: 'dateutc',
}, },
]; ];

View File

@ -1,5 +1,6 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useDateUtils from '@libs/date/useDateUtils';
import { STATISTICS } from './queries'; import { STATISTICS } from './queries';
import { LIMIT } from './constants'; import { LIMIT } from './constants';
@ -19,6 +20,7 @@ export interface Props {
} }
function TribeStatistics({ server, t }: Props) { function TribeStatistics({ server, t }: Props) {
const dateUtils = useDateUtils();
const { loading: loadingData, data: queryRes } = useQuery< const { loading: loadingData, data: queryRes } = useQuery<
ServerStats, ServerStats,
ServerStatsQueryVariables ServerStatsQueryVariables
@ -40,16 +42,18 @@ function TribeStatistics({ server, t }: Props) {
return [ return [
{ {
id: t<string>('tribeStatistics.tribes'), id: t<string>('tribeStatistics.tribes'),
data: items.map(item => ({ data: items.map(item => {
x: new Date(item.createDate), return {
y: item.activeTribes, x: dateUtils.dateInTZ(item.createDate, 'UTC'),
})), y: item.activeTribes,
};
}),
}, },
]; ];
}, [items, loading, t]); }, [items, loading, t, dateUtils]);
return ( return (
<Paper size="large" style={{ overflow: 'hidden' }}> <Paper size="large" style={{ overflow: 'visible' }}>
<TableToolbar> <TableToolbar>
<Typography variant="h4">{t('tribeStatistics.title')}</Typography> <Typography variant="h4">{t('tribeStatistics.title')}</Typography>
</TableToolbar> </TableToolbar>

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { subDays, isEqual as isEqualDate } from 'date-fns';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useQueryParams, NumberParam, withDefault } from 'use-query-params'; import { useQueryParams, NumberParam, withDefault } from 'use-query-params';
import useDateUtils from '@libs/date/useDateUtils';
import useScrollToElement from '@libs/useScrollToElement'; import useScrollToElement from '@libs/useScrollToElement';
import { validateRowsPerPage } from '@common/Table/helpers'; import { validateRowsPerPage } from '@common/Table/helpers';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
@ -27,6 +27,7 @@ export interface Props {
} }
function PlayerHistory({ t, server, playerID }: Props) { function PlayerHistory({ t, server, playerID }: Props) {
const dateUtils = useDateUtils();
const [query, setQuery] = useQueryParams({ const [query, setQuery] = useQueryParams({
page: withDefault(NumberParam, 0), page: withDefault(NumberParam, 0),
limit: withDefault(NumberParam, LIMIT), limit: withDefault(NumberParam, LIMIT),
@ -55,15 +56,21 @@ function PlayerHistory({ t, server, playerID }: Props) {
const playerHistoryItems = useMemo(() => { const playerHistoryItems = useMemo(() => {
const dailyPlayerStatsItems = queryData?.dailyPlayerStats?.items ?? []; const dailyPlayerStatsItems = queryData?.dailyPlayerStats?.items ?? [];
return (queryData?.playerHistory?.items ?? []).map(phItem => { return (queryData?.playerHistory?.items ?? []).map(phItem => {
const dateOfTheDayBeforeDate = subDays(new Date(phItem.createDate), 1); const dateOfTheDayBeforeDate = dateUtils.subDays(
dateUtils.date(phItem.createDate),
1
);
return { return {
...phItem, ...phItem,
stats: dailyPlayerStatsItems.find(dpsItem => stats: dailyPlayerStatsItems.find(dpsItem =>
isEqualDate(new Date(dpsItem.createDate), dateOfTheDayBeforeDate) dateUtils.isEqual(
dateUtils.date(dpsItem.createDate),
dateOfTheDayBeforeDate
)
), ),
}; };
}); });
}, [queryData]); }, [queryData, dateUtils]);
const loading = playerHistoryItems.length === 0 && queryLoading; const loading = playerHistoryItems.length === 0 && queryLoading;
const total = queryData?.playerHistory?.total ?? 0; const total = queryData?.playerHistory?.total ?? 0;

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { format } from 'date-fns'; import useDateUtils from '@libs/date/useDateUtils';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useTitle from '@libs/useTitle'; import useTitle from '@libs/useTitle';
import useServer from '@features/ServerPage/libs/ServerContext/useServer'; import useServer from '@features/ServerPage/libs/ServerContext/useServer';
@ -28,6 +28,7 @@ function IndexPage() {
const { key } = useServer(); const { key } = useServer();
const player = usePlayer(); const player = usePlayer();
const { t } = useTranslation(SERVER_PAGE.PLAYER_PAGE.INDEX_PAGE); const { t } = useTranslation(SERVER_PAGE.PLAYER_PAGE.INDEX_PAGE);
const dateUtils = useDateUtils();
useTitle(t('title', { key, name: player.name })); useTitle(t('title', { key, name: player.name }));
return ( return (
@ -39,8 +40,8 @@ function IndexPage() {
{[ {[
{ {
field: 'joinedAt', field: 'joinedAt',
value: format( value: dateUtils.format(
new Date(player.joinedAt), dateUtils.date(player.joinedAt),
DATE_FORMAT.DAY_MONTH_AND_YEAR DATE_FORMAT.DAY_MONTH_AND_YEAR
), ),
}, },
@ -83,32 +84,32 @@ function IndexPage() {
{ {
field: 'deletedAt', field: 'deletedAt',
value: player.deletedAt value: player.deletedAt
? format( ? dateUtils.format(
new Date(player.deletedAt), dateUtils.date(player.deletedAt),
DATE_FORMAT.DAY_MONTH_AND_YEAR DATE_FORMAT.DAY_MONTH_AND_YEAR
) )
: '-', : '-',
}, },
{ {
field: 'bestRank', field: 'bestRank',
subtitle: format( subtitle: dateUtils.format(
new Date(player.bestRankAt), dateUtils.date(player.bestRankAt),
DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR
), ),
value: player.bestRank, value: player.bestRank,
}, },
{ {
field: 'mostPoints', field: 'mostPoints',
subtitle: format( subtitle: dateUtils.format(
new Date(player.mostPointsAt), dateUtils.date(player.mostPointsAt),
DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR
), ),
value: formatNumber('commas', player.mostPoints), value: formatNumber('commas', player.mostPoints),
}, },
{ {
field: 'mostVillages', field: 'mostVillages',
subtitle: format( subtitle: dateUtils.format(
new Date(player.mostVillagesAt), dateUtils.date(player.mostVillagesAt),
DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR
), ),
value: formatNumber('commas', player.mostVillages), value: formatNumber('commas', player.mostVillages),

View File

@ -1,5 +1,6 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useDateUtils from '@libs/date/useDateUtils';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
import { PLAYER_HISTORY } from './queries'; import { PLAYER_HISTORY } from './queries';
import { LIMIT } from './constants'; import { LIMIT } from './constants';
@ -24,6 +25,7 @@ function Statistics({ t, server, playerID }: Props) {
const [mode, setMode] = useState<Mode>('points'); const [mode, setMode] = useState<Mode>('points');
const theme = useTheme(); const theme = useTheme();
const isMobileDevice = useMediaQuery(theme.breakpoints.down('sm')); const isMobileDevice = useMediaQuery(theme.breakpoints.down('sm'));
const dateUtils = useDateUtils();
const { data: queryRes, loading } = useQuery< const { data: queryRes, loading } = useQuery<
PlayerHistory, PlayerHistory,
PlayerHistoryQueryVariables PlayerHistoryQueryVariables
@ -51,12 +53,12 @@ function Statistics({ t, server, playerID }: Props) {
{ {
id: t<string>('statistics.modes.' + mode), id: t<string>('statistics.modes.' + mode),
data: items.map(item => ({ data: items.map(item => ({
x: new Date(item.createDate), x: dateUtils.dateInTZ(item.createDate, 'UTC'),
y: item[mode], y: item[mode],
})), })),
}, },
]; ];
}, [items, loading, t, mode]); }, [items, loading, t, mode, dateUtils]);
const xyFormat = (v: string | number | Date) => const xyFormat = (v: string | number | Date) =>
typeof v === 'string' || typeof v === 'number' typeof v === 'string' || typeof v === 'number'
? formatNumber('commas', v) ? formatNumber('commas', v)

View File

@ -1,12 +1,13 @@
import React, { useState } from 'react'; import React, { useState, useRef } from 'react';
import { import {
useQueryParams, useQueryParams,
NumberParam, NumberParam,
withDefault, withDefault,
StringParam, StringParam,
DateTimeParam, DateParam,
} from 'use-query-params'; } from 'use-query-params';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import useDateUtils from '@libs/date/useDateUtils';
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 useServer from '@features/ServerPage/libs/ServerContext/useServer';
@ -33,13 +34,14 @@ export interface Props {
function Ranking({ t }: Props) { function Ranking({ t }: Props) {
const classes = useStyles(); const classes = useStyles();
const server = useServer(); const server = useServer();
const defaultDate = new Date(server.historyUpdatedAt); const dateUtils = useDateUtils();
const defaultDate = useRef(dateUtils.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), createDate: withDefault(DateParam, defaultDate.current),
}); });
const limit = validateRowsPerPage(query.limit); const limit = validateRowsPerPage(query.limit);
const [q, setQ] = useState(query.q); const [q, setQ] = useState(query.q);
@ -58,7 +60,7 @@ function Ranking({ t }: Props) {
server.key, server.key,
query.q, query.q,
query.sort.toString(), query.sort.toString(),
query.createDate dateUtils.toJSON(query.createDate)
); );
return ( return (

View File

@ -16,7 +16,7 @@ const usePlayers = (
server: string, server: string,
q: string, q: string,
sort: string, sort: string,
createDate: Date createDate: Date | string
): QueryResult => { ): QueryResult => {
const { loading: loadingStats, data } = useQuery< const { loading: loadingStats, data } = useQuery<
DailyStats, DailyStats,

View File

@ -1,12 +1,13 @@
import React, { useState } from 'react'; import React, { useState, useRef } from 'react';
import { import {
useQueryParams, useQueryParams,
NumberParam, NumberParam,
withDefault, withDefault,
StringParam, StringParam,
DateTimeParam, DateParam,
} from 'use-query-params'; } from 'use-query-params';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import useDateUtils from '@libs/date/useDateUtils';
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 useServer from '@features/ServerPage/libs/ServerContext/useServer';
@ -34,13 +35,14 @@ export interface Props {
function Ranking({ t }: Props) { function Ranking({ t }: Props) {
const classes = useStyles(); const classes = useStyles();
const server = useServer(); const server = useServer();
const defaultDate = new Date(server.historyUpdatedAt); const dateUtils = useDateUtils();
const defaultDate = useRef(dateUtils.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), createDate: withDefault(DateParam, defaultDate.current),
}); });
const limit = validateRowsPerPage(query.limit); const limit = validateRowsPerPage(query.limit);
const [q, setQ] = useState(query.q); const [q, setQ] = useState(query.q);
@ -59,7 +61,7 @@ function Ranking({ t }: Props) {
server.key, server.key,
query.q, query.q,
query.sort.toString(), query.sort.toString(),
query.createDate dateUtils.toJSON(query.createDate)
); );
return ( return (

View File

@ -16,7 +16,7 @@ const useTribes = (
server: string, server: string,
q: string, q: string,
sort: string, sort: string,
createDate: Date createDate: Date | string
): QueryResult => { ): QueryResult => {
const { loading: loadingStats, data } = useQuery< const { loading: loadingStats, data } = useQuery<
DailyStats, DailyStats,

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { subDays, isEqual as isEqualDate } from 'date-fns';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { useQueryParams, NumberParam, withDefault } from 'use-query-params'; import { useQueryParams, NumberParam, withDefault } from 'use-query-params';
import useDateUtils from '@libs/date/useDateUtils';
import useScrollToElement from '@libs/useScrollToElement'; import useScrollToElement from '@libs/useScrollToElement';
import { validateRowsPerPage } from '@common/Table/helpers'; import { validateRowsPerPage } from '@common/Table/helpers';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
@ -25,6 +25,7 @@ export interface Props {
} }
function TribeHistory({ t, server, tribeID }: Props) { function TribeHistory({ t, server, tribeID }: Props) {
const dateUtils = useDateUtils();
const [query, setQuery] = useQueryParams({ const [query, setQuery] = useQueryParams({
page: withDefault(NumberParam, 0), page: withDefault(NumberParam, 0),
limit: withDefault(NumberParam, LIMIT), limit: withDefault(NumberParam, LIMIT),
@ -53,15 +54,21 @@ function TribeHistory({ t, server, tribeID }: Props) {
const tribeHistoryItems = useMemo(() => { const tribeHistoryItems = useMemo(() => {
const dailyTribeStatsItems = queryData?.dailyTribeStats?.items ?? []; const dailyTribeStatsItems = queryData?.dailyTribeStats?.items ?? [];
return (queryData?.tribeHistory?.items ?? []).map(phItem => { return (queryData?.tribeHistory?.items ?? []).map(phItem => {
const dateOfTheDayBeforeDate = subDays(new Date(phItem.createDate), 1); const dateOfTheDayBeforeDate = dateUtils.subDays(
dateUtils.date(phItem.createDate),
1
);
return { return {
...phItem, ...phItem,
stats: dailyTribeStatsItems.find(dpsItem => stats: dailyTribeStatsItems.find(dpsItem =>
isEqualDate(new Date(dpsItem.createDate), dateOfTheDayBeforeDate) dateUtils.isEqual(
dateUtils.date(dpsItem.createDate),
dateOfTheDayBeforeDate
)
), ),
}; };
}); });
}, [queryData]); }, [queryData, dateUtils]);
const loading = tribeHistoryItems.length === 0 && queryLoading; const loading = tribeHistoryItems.length === 0 && queryLoading;
const total = queryData?.tribeHistory?.total ?? 0; const total = queryData?.tribeHistory?.total ?? 0;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { format } from 'date-fns';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useDateUtils from '@libs/date/useDateUtils';
import useTitle from '@libs/useTitle'; import useTitle from '@libs/useTitle';
import useServer from '@features/ServerPage/libs/ServerContext/useServer'; import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import useTribe from '../../libs/TribePageContext/useTribe'; import useTribe from '../../libs/TribePageContext/useTribe';
@ -20,6 +20,7 @@ import {
import Statistics from './components/Statistics/Statistics'; import Statistics from './components/Statistics/Statistics';
function IndexPage() { function IndexPage() {
const dateUtils = useDateUtils();
const classes = useStyles(); const classes = useStyles();
const { key } = useServer(); const { key } = useServer();
const tribe = useTribe(); const tribe = useTribe();
@ -35,8 +36,8 @@ function IndexPage() {
{[ {[
{ {
field: 'createdAt', field: 'createdAt',
value: format( value: dateUtils.format(
new Date(tribe.createdAt), dateUtils.date(tribe.createdAt),
DATE_FORMAT.DAY_MONTH_AND_YEAR DATE_FORMAT.DAY_MONTH_AND_YEAR
), ),
}, },
@ -77,32 +78,32 @@ function IndexPage() {
{ {
field: 'deletedAt', field: 'deletedAt',
value: tribe.deletedAt value: tribe.deletedAt
? format( ? dateUtils.format(
new Date(tribe.deletedAt), dateUtils.date(tribe.deletedAt),
DATE_FORMAT.DAY_MONTH_AND_YEAR DATE_FORMAT.DAY_MONTH_AND_YEAR
) )
: '-', : '-',
}, },
{ {
field: 'bestRank', field: 'bestRank',
subtitle: format( subtitle: dateUtils.format(
new Date(tribe.bestRankAt), dateUtils.date(tribe.bestRankAt),
DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR
), ),
value: tribe.bestRank, value: tribe.bestRank,
}, },
{ {
field: 'mostPoints', field: 'mostPoints',
subtitle: format( subtitle: dateUtils.format(
new Date(tribe.mostPointsAt), dateUtils.date(tribe.mostPointsAt),
DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR
), ),
value: formatNumber('commas', tribe.mostPoints), value: formatNumber('commas', tribe.mostPoints),
}, },
{ {
field: 'mostVillages', field: 'mostVillages',
subtitle: format( subtitle: dateUtils.format(
new Date(tribe.mostVillagesAt), dateUtils.date(tribe.mostVillagesAt),
DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR DATE_FORMAT.HOUR_MINUTES_DAY_MONTH_AND_YEAR
), ),
value: formatNumber('commas', tribe.mostVillages), value: formatNumber('commas', tribe.mostVillages),

View File

@ -1,5 +1,6 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import useDateUtils from '@libs/date/useDateUtils';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
import { TRIBE_HISTORY } from './queries'; import { TRIBE_HISTORY } from './queries';
import { LIMIT } from './constants'; import { LIMIT } from './constants';
@ -23,6 +24,7 @@ export interface Props {
function Statistics({ t, server, tribeID }: Props) { function Statistics({ t, server, tribeID }: Props) {
const [mode, setMode] = useState<Mode>('points'); const [mode, setMode] = useState<Mode>('points');
const theme = useTheme(); const theme = useTheme();
const dateUtils = useDateUtils();
const isMobileDevice = useMediaQuery(theme.breakpoints.down('sm')); const isMobileDevice = useMediaQuery(theme.breakpoints.down('sm'));
const { data: queryRes, loading } = useQuery< const { data: queryRes, loading } = useQuery<
TribeHistory, TribeHistory,
@ -51,12 +53,12 @@ function Statistics({ t, server, tribeID }: Props) {
{ {
id: t<string>('statistics.modes.' + mode), id: t<string>('statistics.modes.' + mode),
data: items.map(item => ({ data: items.map(item => ({
x: new Date(item.createDate), x: dateUtils.dateInTZ(item.createDate, 'UTC'),
y: item[mode], y: item[mode],
})), })),
}, },
]; ];
}, [items, loading, t, mode]); }, [items, loading, t, mode, dateUtils]);
const xyFormat = (v: string | number | Date) => const xyFormat = (v: string | number | Date) =>
typeof v === 'string' || typeof v === 'number' typeof v === 'string' || typeof v === 'number'
? formatNumber('commas', v) ? formatNumber('commas', v)

View File

@ -1,5 +1,5 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { addDays, differenceInDays, format, isBefore } from 'date-fns'; import useDateUtils from '@libs/date/useDateUtils';
import useMembers from './useMembers'; import useMembers from './useMembers';
import formatNumber from '@utils/formatNumber'; import formatNumber from '@utils/formatNumber';
import { DATE_FORMAT } from '@config/app'; import { DATE_FORMAT } from '@config/app';
@ -28,6 +28,7 @@ function Members({ t, server, tribeID }: Props) {
server, server,
HOW_MANY_DAYS_BACK HOW_MANY_DAYS_BACK
); );
const dateUtils = useDateUtils();
const columns = useMemo<Column<Player>[]>(() => { const columns = useMemo<Column<Player>[]>(() => {
const columns: Column<Player>[] = [ const columns: Column<Player>[] = [
{ {
@ -66,19 +67,24 @@ function Members({ t, server, tribeID }: Props) {
const maxDate = const maxDate =
dailyPlayerStats.length > 0 dailyPlayerStats.length > 0
? new Date(dailyPlayerStats[0].createDate) ? dateUtils.date(dailyPlayerStats[0].createDate)
: null; : null;
const minDate = const minDate =
dailyPlayerStats.length > 0 dailyPlayerStats.length > 0
? new Date(dailyPlayerStats[dailyPlayerStats.length - 1].createDate) ? dateUtils.date(
dailyPlayerStats[dailyPlayerStats.length - 1].createDate
)
: null; : null;
if (maxDate && minDate && isBefore(minDate, maxDate)) { if (maxDate && minDate && dateUtils.isBefore(minDate, maxDate)) {
let diff = differenceInDays(maxDate, minDate) + 1; let diff = dateUtils.differenceInDays(maxDate, minDate) + 1;
if (diff <= HOW_MANY_DAYS_BACK) { if (diff <= HOW_MANY_DAYS_BACK) {
for (let i = 0; i < diff; i++) { for (let i = 0; i < diff; i++) {
const date = addDays(minDate, i); const date = dateUtils.addDays(minDate, i);
const formatted = format(date, DATE_FORMAT.DAY_MONTH_AND_YEAR); const formatted = dateUtils.format(
date,
DATE_FORMAT.DAY_MONTH_AND_YEAR
);
columns.push({ columns.push({
field: formatted, field: formatted,
label: formatted, label: formatted,
@ -86,7 +92,8 @@ function Members({ t, server, tribeID }: Props) {
valueFormatter: (p: Player) => { valueFormatter: (p: Player) => {
const record = p.dailyPlayerStatsRecords const record = p.dailyPlayerStatsRecords
? p.dailyPlayerStatsRecords.find( ? p.dailyPlayerStatsRecords.find(
r => new Date(r.createDate).getTime() === date.getTime() r =>
dateUtils.date(r.createDate).getTime() === date.getTime()
) )
: undefined; : undefined;
return <ColouredNumber num={record ? record[mode] : 0} />; return <ColouredNumber num={record ? record[mode] : 0} />;
@ -116,7 +123,7 @@ function Members({ t, server, tribeID }: Props) {
} }
return columns; return columns;
}, [dailyPlayerStats, t, server, mode]); }, [dailyPlayerStats, t, server, mode, dateUtils]);
return ( return (
<Paper> <Paper>

View File

@ -1,8 +1,8 @@
import React, { useRef, useMemo, useState } from 'react'; import React, { useRef, useMemo, useState } from 'react';
import { subDays } from 'date-fns';
import { useQueryParams, DateTimeParam, withDefault } from 'use-query-params'; import { useQueryParams, DateTimeParam, withDefault } from 'use-query-params';
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import useDateUtils from '@libs/date/useDateUtils';
import useTitle from '@libs/useTitle'; import useTitle from '@libs/useTitle';
import useServer from '../../libs/ServerContext/useServer'; import useServer from '../../libs/ServerContext/useServer';
import useSide from './useSide'; import useSide from './useSide';
@ -34,11 +34,15 @@ import {
} from './types'; } from './types';
function WarStatsPage() { function WarStatsPage() {
const now = useRef(new Date()); const dateUtils = useDateUtils();
const now = useRef(dateUtils.date());
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [results, setResults] = useState<ResultsT | null>(null); const [results, setResults] = useState<ResultsT | null>(null);
const [query, setQuery] = useQueryParams({ const [query, setQuery] = useQueryParams({
ennobledAtGTE: withDefault(DateTimeParam, subDays(now.current, 1)), ennobledAtGTE: withDefault(
DateTimeParam,
dateUtils.subDays(now.current, 1)
),
ennobledAtLTE: withDefault(DateTimeParam, now.current), ennobledAtLTE: withDefault(DateTimeParam, now.current),
}); });
const client = useApolloClient(); const client = useApolloClient();
@ -96,8 +100,8 @@ function WarStatsPage() {
const sideTwoPlayerIDs = sideTwoPlayers.map(player => player.id); const sideTwoPlayerIDs = sideTwoPlayers.map(player => player.id);
const sideTwoTribeIDs = sideTwoTribes.map(tribe => tribe.id); const sideTwoTribeIDs = sideTwoTribes.map(tribe => tribe.id);
const defFilter = { const defFilter = {
ennobledAtGTE: query.ennobledAtGTE, ennobledAtGTE: dateUtils.zonedTimeToUTC(query.ennobledAtGTE),
ennobledAtLTE: query.ennobledAtLTE, ennobledAtLTE: dateUtils.zonedTimeToUTC(query.ennobledAtLTE),
}; };
const { data } = await client.query< const { data } = await client.query<
EnnoblementsQueryResult, EnnoblementsQueryResult,

View File

@ -111,7 +111,6 @@ function Results({ data, server }: Props) {
axisTop={null} axisTop={null}
axisRight={null} axisRight={null}
axisBottom={{ axisBottom={{
tickSize: 0,
tickPadding: 5, tickPadding: 5,
tickRotation: 0, tickRotation: 0,
format: isWidthDown750 ? () => '' : undefined, format: isWidthDown750 ? () => '' : undefined,

View File

@ -4,15 +4,11 @@ import { ThemeProvider } from '@material-ui/styles';
import { ApolloProvider } from '@apollo/client'; import { ApolloProvider } from '@apollo/client';
import { BrowserRouter, Route } from 'react-router-dom'; import { BrowserRouter, Route } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import { MuiPickersUtilsProvider } from '@material-ui/pickers';
import { QueryParamProvider } from 'use-query-params'; import { QueryParamProvider } from 'use-query-params';
import DateFnsUtils from '@date-io/date-fns';
import App from './features/App'; import App from './features/App';
import createTheme from './theme/createTheme'; import createTheme from './theme/createTheme';
import createGraphQLClient from './libs/graphql/createClient'; import createGraphQLClient from './libs/graphql/createClient';
import initI18N from './libs/i18n/init'; import initI18N from './libs/i18n/init';
import { getLocale } from './libs/date/locales';
import extractVersionCodeFromHostname from './utils/extractVersionCodeFromHostname';
import { URI as API_URI } from './config/api'; import { URI as API_URI } from './config/api';
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals';
@ -21,16 +17,9 @@ const jsx = (
<ThemeProvider theme={createTheme()}> <ThemeProvider theme={createTheme()}>
<I18nextProvider i18n={initI18N()}> <I18nextProvider i18n={initI18N()}>
<ApolloProvider client={createGraphQLClient(API_URI)}> <ApolloProvider client={createGraphQLClient(API_URI)}>
<MuiPickersUtilsProvider <QueryParamProvider ReactRouterRoute={Route}>
utils={DateFnsUtils} <App />
locale={getLocale( </QueryParamProvider>
extractVersionCodeFromHostname(window.location.hostname)
)}
>
<QueryParamProvider ReactRouterRoute={Route}>
<App />
</QueryParamProvider>
</MuiPickersUtilsProvider>
</ApolloProvider> </ApolloProvider>
</I18nextProvider> </I18nextProvider>
</ThemeProvider> </ThemeProvider>

View File

@ -0,0 +1,65 @@
import React, { useMemo } from 'react';
import { useQuery } from '@apollo/client';
import { useTranslation } from 'react-i18next';
import extractVersionCodeFromHostname from '@utils/extractVersionCodeFromHostname';
import { VERSIONS } from './queries';
import Context from './context';
import * as NAMESPACES from '@config/namespaces';
import { VersionsQueryVariables } from '@libs/graphql/types';
import { VersionList } from './types';
import NotFoundPage from '@features/NotFoundPage/NotFoundPage';
import Spinner from '@common/Spinner/Spinner';
export interface Props {
children: React.ReactNode;
}
function Provider({ children }: Props) {
const { t } = useTranslation(NAMESPACES.COMMON);
const versionCode = useMemo(() => {
return extractVersionCodeFromHostname(window.location.hostname);
}, []);
const { loading: loadingVersion, data } = useQuery<
VersionList,
VersionsQueryVariables
>(VERSIONS, {
fetchPolicy: 'cache-first',
variables: {
limit: 1,
filter: {
code: [versionCode],
},
},
});
const version =
data?.versions?.items && data.versions.items.length > 0
? data.versions.items[0]
: undefined;
const loading = loadingVersion && !version;
if (loading) {
return (
<Spinner
containerProps={{
width: '100%',
height: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}
description={t('versionContextProvider.loading')}
/>
);
}
if (!version) {
return <NotFoundPage />;
}
return <Context.Provider value={version}>{children}</Context.Provider>;
}
export default Provider;

View File

@ -0,0 +1,11 @@
import { createContext } from 'react';
import { Version } from './types';
const ctx = createContext<Version>({
code: '',
host: '',
name: '',
timezone: '',
});
export default ctx;

View File

@ -0,0 +1,324 @@
import addDays from 'date-fns/addDays';
import addMonths from 'date-fns/addMonths';
import addYears from 'date-fns/addYears';
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds';
import eachDayOfInterval from 'date-fns/eachDayOfInterval';
import endOfDay from 'date-fns/endOfDay';
import endOfWeek from 'date-fns/endOfWeek';
import endOfYear from 'date-fns/endOfYear';
import format from 'date-fns/format';
import getHours from 'date-fns/getHours';
import getSeconds from 'date-fns/getSeconds';
import getYear from 'date-fns/getYear';
import isAfter from 'date-fns/isAfter';
import isBefore from 'date-fns/isBefore';
import isEqual from 'date-fns/isEqual';
import isSameDay from 'date-fns/isSameDay';
import isSameYear from 'date-fns/isSameYear';
import isSameMonth from 'date-fns/isSameMonth';
import isSameHour from 'date-fns/isSameHour';
import isValid from 'date-fns/isValid';
import dateFnsParse from 'date-fns/parse';
import setHours from 'date-fns/setHours';
import setMinutes from 'date-fns/setMinutes';
import setMonth from 'date-fns/setMonth';
import setSeconds from 'date-fns/setSeconds';
import setYear from 'date-fns/setYear';
import startOfDay from 'date-fns/startOfDay';
import startOfMonth from 'date-fns/startOfMonth';
import endOfMonth from 'date-fns/endOfMonth';
import startOfWeek from 'date-fns/startOfWeek';
import startOfYear from 'date-fns/startOfYear';
import { utcToZonedTime } from 'date-fns-tz';
import { Locale } from 'date-fns';
import { IUtils } from '@date-io/core/IUtils';
export default class DateUtils implements IUtils<Date> {
public static timezone?: string;
public locale?: Locale;
public yearFormat = 'yyyy';
public yearMonthFormat = 'MMMM yyyy';
public dateTime12hFormat = 'MMMM do hh:mm aaaa';
public dateTime24hFormat = 'MMMM do HH:mm';
public time12hFormat = 'hh:mm a';
public time24hFormat = 'HH:mm';
public dateFormat = 'MMMM do';
constructor({ locale }: { locale?: Locale } = {}) {
this.locale = locale;
}
public addDays(value: Date, count: number) {
return addDays(value, count);
}
public isValid(value: any) {
return isValid(this.date(value));
}
public getDiff(value: Date, comparing: Date | string) {
return differenceInMilliseconds(value, this.date(comparing));
}
public isAfter(value: Date, comparing: Date) {
return isAfter(value, comparing);
}
public isBefore(value: Date, comparing: Date) {
return isBefore(value, comparing);
}
public startOfDay(value: Date) {
return startOfDay(value);
}
public endOfDay(value: Date) {
return endOfDay(value);
}
public getHours(value: Date) {
return getHours(value);
}
public setHours(value: Date, count: number) {
return setHours(value, count);
}
public setMinutes(value: Date, count: number) {
return setMinutes(value, count);
}
public getSeconds(value: Date) {
return getSeconds(value);
}
public setSeconds(value: Date, count: number) {
return setSeconds(value, count);
}
public isSameDay(value: Date, comparing: Date) {
return isSameDay(value, comparing);
}
public isSameMonth(value: Date, comparing: Date) {
return isSameMonth(value, comparing);
}
public isSameYear(value: Date, comparing: Date) {
return isSameYear(value, comparing);
}
public isSameHour(value: Date, comparing: Date) {
return isSameHour(value, comparing);
}
public startOfMonth(value: Date) {
return startOfMonth(value);
}
public endOfMonth(value: Date) {
return endOfMonth(value);
}
public getYear(value: Date) {
return getYear(value);
}
public setYear(value: Date, count: number) {
return setYear(value, count);
}
public date(value?: any) {
if (typeof value === 'undefined') {
if (!DateUtils.timezone) {
return new Date();
}
return utcToZonedTime(new Date(), DateUtils.timezone);
}
if (value instanceof Date) {
return value;
}
if (!DateUtils.timezone) {
return new Date(value);
}
return utcToZonedTime(value, DateUtils.timezone);
}
public parse(value: string, formatString: string) {
if (value === '') {
return null;
}
return dateFnsParse(value, formatString, this.date(), {
locale: this.locale,
});
}
public format(date: Date, formatString: string) {
return format(date, formatString, { locale: this.locale });
}
public isEqual(date: any, comparing: any) {
if (date === null && comparing === null) {
return true;
}
return isEqual(date, comparing);
}
public isNull(date: Date) {
return date === null;
}
public isAfterDay(date: Date, value: Date) {
return isAfter(date, endOfDay(value));
}
public isBeforeDay(date: Date, value: Date) {
return isBefore(date, startOfDay(value));
}
public isBeforeYear(date: Date, value: Date) {
return isBefore(date, startOfYear(value));
}
public isAfterYear(date: Date, value: Date) {
return isAfter(date, endOfYear(value));
}
public formatNumber(numberToFormat: string) {
return numberToFormat;
}
public getMinutes(date: Date) {
return date.getMinutes();
}
public getMonth(date: Date) {
return date.getMonth();
}
public setMonth(date: Date, count: number) {
return setMonth(date, count);
}
public getMeridiemText(ampm: 'am' | 'pm') {
return ampm === 'am' ? 'AM' : 'PM';
}
public getNextMonth(date: Date) {
return addMonths(date, 1);
}
public getPreviousMonth(date: Date) {
return addMonths(date, -1);
}
public getMonthArray(date: Date) {
const firstMonth = startOfYear(date);
const monthArray = [firstMonth];
while (monthArray.length < 12) {
const prevMonth = monthArray[monthArray.length - 1];
monthArray.push(this.getNextMonth(prevMonth));
}
return monthArray;
}
public mergeDateAndTime(date: Date, time: Date) {
return this.setMinutes(
this.setHours(date, this.getHours(time)),
this.getMinutes(time)
);
}
public getWeekdays() {
const now = new Date();
return eachDayOfInterval({
start: startOfWeek(now, { locale: this.locale }),
end: endOfWeek(now, { locale: this.locale }),
}).map(day => this.format(day, 'EEEEEE'));
}
public getWeekArray(date: Date) {
const start = startOfWeek(startOfMonth(date), { locale: this.locale });
const end = endOfWeek(endOfMonth(date), { locale: this.locale });
let count = 0;
let current = start;
const nestedWeeks: Date[][] = [];
while (isBefore(current, end)) {
const weekNumber = Math.floor(count / 7);
nestedWeeks[weekNumber] = nestedWeeks[weekNumber] || [];
nestedWeeks[weekNumber].push(current);
current = addDays(current, 1);
count += 1;
}
return nestedWeeks;
}
public getYearRange(start: Date, end: Date) {
const startDate = startOfYear(start);
const endDate = endOfYear(end);
const years: Date[] = [];
let current = startDate;
while (isBefore(current, endDate)) {
years.push(current);
current = addYears(current, 1);
}
return years;
}
// displaying methpds
public getCalendarHeaderText(date: Date) {
return this.format(date, this.yearMonthFormat);
}
public getYearText(date: Date) {
return this.format(date, 'yyyy');
}
public getDatePickerHeaderText(date: Date) {
return this.format(date, 'EEE, MMM d');
}
public getDateTimePickerHeaderText(date: Date) {
return this.format(date, 'MMM d');
}
public getMonthText(date: Date) {
return this.format(date, 'MMMM');
}
public getDayText(date: Date) {
return this.format(date, 'd');
}
public getHourText(date: Date, ampm: boolean) {
return this.format(date, ampm ? 'hh' : 'HH');
}
public getMinuteText(date: Date) {
return this.format(date, 'mm');
}
public getSecondText(date: Date) {
return this.format(date, 'ss');
}
}

View File

@ -0,0 +1,14 @@
import { gql } from '@apollo/client';
export const VERSIONS = gql`
query versions($filter: VersionFilter, $sort: [String!], $limit: Int) {
versions(filter: $filter, sort: $sort, limit: $limit) {
items {
code
host
timezone
name
}
}
}
`;

View File

@ -0,0 +1,12 @@
import { List } from '@libs/graphql/types';
export type Version = {
code: string;
host: string;
name: string;
timezone: string;
};
export type VersionList = {
versions?: List<Version[]>;
};

View File

@ -0,0 +1,3 @@
import { useUtils } from '@material-ui/pickers';
export default useUtils;

View File

@ -0,0 +1,9 @@
import { useContext } from 'react';
import ctx from './context';
import { Version } from './types';
const useVersion = (): Version => {
return useContext(ctx);
};
export default useVersion;

376
src/libs/date/DateUtils.ts Normal file
View File

@ -0,0 +1,376 @@
import addDays from 'date-fns/addDays';
import addMonths from 'date-fns/addMonths';
import addYears from 'date-fns/addYears';
import subDays from 'date-fns/subDays';
import differenceInMilliseconds from 'date-fns/differenceInMilliseconds';
import eachDayOfInterval from 'date-fns/eachDayOfInterval';
import endOfDay from 'date-fns/endOfDay';
import endOfWeek from 'date-fns/endOfWeek';
import endOfYear from 'date-fns/endOfYear';
import format from 'date-fns/format';
import getHours from 'date-fns/getHours';
import getSeconds from 'date-fns/getSeconds';
import getYear from 'date-fns/getYear';
import isAfter from 'date-fns/isAfter';
import isBefore from 'date-fns/isBefore';
import isEqual from 'date-fns/isEqual';
import isSameDay from 'date-fns/isSameDay';
import isSameYear from 'date-fns/isSameYear';
import isSameMonth from 'date-fns/isSameMonth';
import isSameHour from 'date-fns/isSameHour';
import isValid from 'date-fns/isValid';
import dateFnsParse from 'date-fns/parse';
import setHours from 'date-fns/setHours';
import setMinutes from 'date-fns/setMinutes';
import setMonth from 'date-fns/setMonth';
import setSeconds from 'date-fns/setSeconds';
import setYear from 'date-fns/setYear';
import startOfDay from 'date-fns/startOfDay';
import startOfMonth from 'date-fns/startOfMonth';
import endOfMonth from 'date-fns/endOfMonth';
import startOfWeek from 'date-fns/startOfWeek';
import startOfYear from 'date-fns/startOfYear';
import differenceInDays from 'date-fns/differenceInDays';
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';
import { Locale } from 'date-fns';
import { IUtils } from '@date-io/core/IUtils';
export default class DateUtils implements IUtils<Date> {
public static timezone?: string;
public locale?: Locale;
public yearFormat = 'yyyy';
public yearMonthFormat = 'MMMM yyyy';
public dateTime12hFormat = 'MMMM do hh:mm aaaa';
public dateTime24hFormat = 'MMMM do HH:mm';
public time12hFormat = 'hh:mm a';
public time24hFormat = 'HH:mm';
public dateFormat = 'MMMM do';
constructor({ locale }: { locale?: Locale } = {}) {
this.locale = locale;
}
public addDays(value: Date, count: number) {
return addDays(value, count);
}
public subDays(date: Date, amount: number): Date {
return subDays(date, amount);
}
public isValid(value: any) {
return isValid(this.date(value));
}
public getDiff(value: Date, comparing: Date | string) {
return differenceInMilliseconds(value, this.date(comparing));
}
public differenceInDays(dateLeft: Date, dateRight: Date): number {
return differenceInDays(dateLeft, dateRight);
}
public isAfter(value: Date, comparing: Date) {
return isAfter(value, comparing);
}
public isBefore(value: Date, comparing: Date) {
return isBefore(value, comparing);
}
public startOfDay(value: Date) {
return startOfDay(value);
}
public endOfDay(value: Date) {
return endOfDay(value);
}
public getHours(value: Date) {
return getHours(value);
}
public setHours(value: Date, count: number) {
return setHours(value, count);
}
public setMinutes(value: Date, count: number) {
return setMinutes(value, count);
}
public getSeconds(value: Date) {
return getSeconds(value);
}
public setSeconds(value: Date, count: number) {
return setSeconds(value, count);
}
public isSameDay(value: Date, comparing: Date) {
return isSameDay(value, comparing);
}
public isSameMonth(value: Date, comparing: Date) {
return isSameMonth(value, comparing);
}
public isSameYear(value: Date, comparing: Date) {
return isSameYear(value, comparing);
}
public isSameHour(value: Date, comparing: Date) {
return isSameHour(value, comparing);
}
public startOfMonth(value: Date) {
return startOfMonth(value);
}
public endOfMonth(value: Date) {
return endOfMonth(value);
}
public getYear(value: Date) {
return getYear(value);
}
public setYear(value: Date, count: number) {
return setYear(value, count);
}
public dateInTZ(value: any, timezone: string): Date {
const date = new Date(value);
return new Date(date.toLocaleString('en-US', { timeZone: timezone }));
}
public date(value?: any) {
if (typeof value === 'undefined') {
if (!DateUtils.timezone) {
return new Date();
}
return this.dateInTZ(new Date(), DateUtils.timezone);
}
if (value instanceof Date) {
return value;
}
if (!DateUtils.timezone) {
return new Date(value);
}
return this.dateInTZ(value, DateUtils.timezone);
}
public parse(value: string, formatString: string) {
if (value === '') {
return null;
}
return dateFnsParse(value, formatString, this.date(), {
locale: this.locale,
});
}
public format(date: Date, formatString: string) {
return format(date, formatString, { locale: this.locale });
}
public toJSON(date: Date) {
return (
this.format(date, 'yyyy-MM-dd') +
'T' +
this.format(date, 'HH:mm:ss') +
'Z'
);
}
public isEqual(date: any, comparing: any) {
if (date === null && comparing === null) {
return true;
}
return isEqual(date, comparing);
}
public isNull(date: Date) {
return date === null;
}
public isAfterDay(date: Date, value: Date) {
return isAfter(date, endOfDay(value));
}
public isBeforeDay(date: Date, value: Date) {
return isBefore(date, startOfDay(value));
}
public isBeforeYear(date: Date, value: Date) {
return isBefore(date, startOfYear(value));
}
public isAfterYear(date: Date, value: Date) {
return isAfter(date, endOfYear(value));
}
public formatNumber(numberToFormat: string) {
return numberToFormat;
}
public getMinutes(date: Date) {
return date.getMinutes();
}
public getMonth(date: Date) {
return date.getMonth();
}
public setMonth(date: Date, count: number) {
return setMonth(date, count);
}
public getMeridiemText(ampm: 'am' | 'pm') {
return ampm === 'am' ? 'AM' : 'PM';
}
public getNextMonth(date: Date) {
return addMonths(date, 1);
}
public getPreviousMonth(date: Date) {
return addMonths(date, -1);
}
public getMonthArray(date: Date) {
const firstMonth = startOfYear(date);
const monthArray = [firstMonth];
while (monthArray.length < 12) {
const prevMonth = monthArray[monthArray.length - 1];
monthArray.push(this.getNextMonth(prevMonth));
}
return monthArray;
}
public mergeDateAndTime(date: Date, time: Date) {
return this.setMinutes(
this.setHours(date, this.getHours(time)),
this.getMinutes(time)
);
}
public getWeekdays() {
const now = new Date();
return eachDayOfInterval({
start: startOfWeek(now, { locale: this.locale }),
end: endOfWeek(now, { locale: this.locale }),
}).map(day => this.format(day, 'EEEEEE'));
}
public getWeekArray(date: Date) {
const start = startOfWeek(startOfMonth(date), { locale: this.locale });
const end = endOfWeek(endOfMonth(date), { locale: this.locale });
let count = 0;
let current = start;
const nestedWeeks: Date[][] = [];
while (isBefore(current, end)) {
const weekNumber = Math.floor(count / 7);
nestedWeeks[weekNumber] = nestedWeeks[weekNumber] || [];
nestedWeeks[weekNumber].push(current);
current = addDays(current, 1);
count += 1;
}
return nestedWeeks;
}
public getYearRange(start: Date, end: Date) {
const startDate = startOfYear(start);
const endDate = endOfYear(end);
const years: Date[] = [];
let current = startDate;
while (isBefore(current, endDate)) {
years.push(current);
current = addYears(current, 1);
}
return years;
}
// displaying methpds
public getCalendarHeaderText(date: Date) {
return this.format(date, this.yearMonthFormat);
}
public getYearText(date: Date) {
return this.format(date, 'yyyy');
}
public getDatePickerHeaderText(date: Date) {
return this.format(date, 'EEE, MMM d');
}
public getDateTimePickerHeaderText(date: Date) {
return this.format(date, 'MMM d');
}
public getMonthText(date: Date) {
return this.format(date, 'MMMM');
}
public getDayText(date: Date) {
return this.format(date, 'd');
}
public getHourText(date: Date, ampm: boolean) {
return this.format(date, ampm ? 'hh' : 'HH');
}
public getMinuteText(date: Date) {
return this.format(date, 'mm');
}
public getSecondText(date: Date) {
return this.format(date, 'ss');
}
public formatDistanceToNow(
date: Date,
opts: {
includeSeconds?: boolean;
addSuffix?: boolean;
} = {}
) {
return formatDistanceToNow(date, {
...opts,
locale: this.locale,
});
}
public UTCToZonedTime(date: Date): Date {
if (!DateUtils.timezone) {
return date;
}
return utcToZonedTime(date, DateUtils.timezone);
}
public zonedTimeToUTC(date: Date): Date {
if (!DateUtils.timezone) {
return date;
}
return zonedTimeToUtc(date, DateUtils.timezone);
}
}

View File

@ -0,0 +1,30 @@
import React, { useMemo } from 'react';
import useVersion from '@libs/VersionContext/useVersion';
import { getLocale } from './locales';
import DateUtils from './DateUtils';
import { MuiPickersUtilsProvider } from '@material-ui/pickers';
import Context from './context';
export interface Props {
children: React.ReactNode;
}
function DateUtilsProvider({ children }: Props) {
const version = useVersion();
const locale = useMemo(() => {
return getLocale(version.code);
}, [version.code]);
const dateUtils = useMemo(() => {
DateUtils.timezone = version.timezone;
return new DateUtils({ locale });
}, [version.timezone, locale]);
return (
<MuiPickersUtilsProvider utils={DateUtils}>
<Context.Provider value={dateUtils}>{children}</Context.Provider>
</MuiPickersUtilsProvider>
);
}
export default DateUtilsProvider;

7
src/libs/date/context.ts Normal file
View File

@ -0,0 +1,7 @@
import { createContext } from 'react';
import DateUtils from './DateUtils';
const ctx = createContext<DateUtils>(new DateUtils());
ctx.displayName = 'DateUtilsProvider';
export default ctx;

View File

@ -0,0 +1,8 @@
import { useContext } from 'react';
import ctx from './context';
const useDateUtils = () => {
return useContext(ctx);
};
export default useDateUtils;

View File

@ -13,6 +13,9 @@ const translations = {
closed: 'Closed', closed: 'Closed',
open: 'Open', open: 'Open',
}, },
versionContextProvider: {
loading: 'Loading...',
},
devNote: `This website is still under development and some things may be broken.`, devNote: `This website is still under development and some things may be broken.`,
mainLayout: { mainLayout: {
header: { header: {

View File

@ -13,6 +13,9 @@ const translations = {
closed: 'Zamknięty', closed: 'Zamknięty',
open: 'Otwarty', open: 'Otwarty',
}, },
versionContextProvider: {
loading: 'Ładowanie...',
},
devNote: `Strona jest ciągle w procesie tworzenia i mogą występować błędy.`, devNote: `Strona jest ciągle w procesie tworzenia i mogą występować błędy.`,
mainLayout: { mainLayout: {
header: { header: {