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;
}
const LineChart = ({ data, loading, ...rest }: Props) => {
const LineChart = ({ data, loading, yScale, xScale, ...rest }: Props) => {
const { t } = useTranslation(LINE_CHART);
if (loading) {
@ -37,6 +37,8 @@ const LineChart = ({ data, loading, ...rest }: Props) => {
// tooltip={PointTooltip}
data={data}
{...rest}
xScale={xScale?.type === 'time' ? { useUTC: false, ...xScale } : xScale}
yScale={yScale?.type === 'time' ? { useUTC: false, ...yScale } : yScale}
theme={darkTheme}
/>
);

View File

@ -8,7 +8,6 @@ function PointTooltip(props: PointTooltipProps | BarTooltipDatum) {
'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={title}>
<div></div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,15 +4,11 @@ import { ThemeProvider } from '@material-ui/styles';
import { ApolloProvider } from '@apollo/client';
import { BrowserRouter, Route } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import { MuiPickersUtilsProvider } from '@material-ui/pickers';
import { QueryParamProvider } from 'use-query-params';
import DateFnsUtils from '@date-io/date-fns';
import App from './features/App';
import createTheme from './theme/createTheme';
import createGraphQLClient from './libs/graphql/createClient';
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 reportWebVitals from './reportWebVitals';
@ -21,16 +17,9 @@ const jsx = (
<ThemeProvider theme={createTheme()}>
<I18nextProvider i18n={initI18N()}>
<ApolloProvider client={createGraphQLClient(API_URI)}>
<MuiPickersUtilsProvider
utils={DateFnsUtils}
locale={getLocale(
extractVersionCodeFromHostname(window.location.hostname)
)}
>
<QueryParamProvider ReactRouterRoute={Route}>
<App />
</QueryParamProvider>
</MuiPickersUtilsProvider>
<QueryParamProvider ReactRouterRoute={Route}>
<App />
</QueryParamProvider>
</ApolloProvider>
</I18nextProvider>
</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',
open: 'Open',
},
versionContextProvider: {
loading: 'Loading...',
},
devNote: `This website is still under development and some things may be broken.`,
mainLayout: {
header: {

View File

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