translate RecentlyDeletedPlayers and RecentlyDeletedTribes into English and Polish | add new graphql types - TribesQueryVariables, TribeFilter | add all planned routes to the ServerPage sidebar | Table cmp - row selection works from now

This commit is contained in:
Dawid Wysokiński 2020-11-13 18:44:54 +01:00
parent b51b33d7b6
commit 8a39f95142
26 changed files with 285 additions and 42 deletions

View File

@ -18,7 +18,7 @@ import TableEmpty from './TableEmpty';
import TableFooter, { Props as TableFooterProps } from './TableFooter';
export interface Props<T> {
columns: Column[];
columns: Column<T>[];
actions?: Action[];
data: T[];
orderBy?: string;
@ -36,6 +36,7 @@ export interface Props<T> {
footerProps?: TableFooterProps;
hideFooter?: boolean;
size?: 'medium' | 'small';
selected?: T[];
}
function Table<T extends object>({
@ -53,8 +54,23 @@ function Table<T extends object>({
hideFooter = false,
footerProps,
size,
selected,
onSelect,
}: Props<T>) {
const { t } = useTranslation(TABLE);
const isSelected = (row: T): boolean => {
return (
Array.isArray(selected) &&
selected.some(
otherRow =>
isObjKey(otherRow, idFieldName) &&
isObjKey(row, idFieldName) &&
otherRow[idFieldName] === row[idFieldName]
)
);
};
return (
<TableContainer>
<MUITable size={size} {...tableProps}>
@ -64,14 +80,17 @@ function Table<T extends object>({
orderBy={orderBy}
orderDirection={orderDirection}
onRequestSort={onRequestSort}
allSelected={false}
size={size}
onSelectAll={() => {
if (onSelect) {
onSelect(data);
}
}}
allSelected={selected?.length === data.length}
/>
<TableBody {...tableBodyProps}>
{loading ? (
<TableLoading
columns={columns}
size={size}
rowsPerPage={footerProps?.rowsPerPage ?? 50}
/>
) : data.length > 0 ? (
@ -83,10 +102,14 @@ function Table<T extends object>({
}
row={item}
actions={actions}
selected={false}
selected={isSelected(item)}
selection={selection}
columns={columns}
size={size}
onSelect={row => {
if (onSelect) {
onSelect([row]);
}
}}
/>
);
})

View File

@ -17,7 +17,7 @@ function TableLoading({ rowsPerPage, columns, size = 'medium' }: Props) {
return (
<TableRow key={index}>
{columns.map(col => (
<TableCell size={size} key={col.label}>
<TableCell size={size} key={col.field}>
<Skeleton variant="text" />
</TableCell>
))}

View File

@ -7,7 +7,7 @@ import { TableRow, TableCell, Checkbox, Tooltip } from '@material-ui/core';
export type Props<T> = {
actions: Action[];
columns: Column[];
columns: Column<T>[];
row: T;
selection: boolean;
selected: boolean;
@ -60,7 +60,7 @@ function EnhancedTableRow<T extends object>({
align={col.align ? col.align : 'left'}
>
{col.valueFormatter
? col.valueFormatter(val)
? col.valueFormatter(row)
: col.type
? formatValue(val, col.type)
: val}

View File

@ -3,11 +3,11 @@ export type Action = {
tooltip?: string;
};
export type Column = {
export type Column<T = any> = {
field: string;
label?: string;
sortable?: boolean;
valueFormatter?: (v: boolean | string | number) => boolean | string | number;
valueFormatter?: (v: T) => React.ReactNode;
disablePadding?: boolean;
type?: 'normal' | 'datetime' | 'date';
align?: 'left' | 'right' | 'center';

View File

@ -1,7 +1,35 @@
export const INDEX_PAGE = '/';
export const SERVER_PAGE = {
BASE: '/server',
get INDEX_PAGE() {
return this.BASE + '/:key';
INDEX_PAGE: '/server/:key',
TRIBE_PAGE: {
INDEX_PAGE: '/server/:key/tribe/:id',
HISTORY_PAGE: '/server/:key/tribe/:id/history',
TRIBE_CHANGES_PAGE: '/server/:key/tribe/:id/tribe-changes',
CONQUERS_PAGE: '/server/:key/tribe/:id/conquers',
},
PLAYER_PAGE: {
INDEX_PAGE: '/server/:key/player/:id',
HISTORY_PAGE: '/server/:key/player/:id/history',
TRIBE_CHANGES_PAGE: '/server/:key/player/:id/tribe-changes',
CONQUERS_PAGE: '/server/:key/player/:id/conquers',
},
VILLAGE_PAGE: {
INDEX_PAGE: '/server/:key/village/:id',
},
RANKINGS_PAGE: {
PLAYER_PAGE: {
INDEX_PAGE: '/server/:key/rankings/player',
OD_PAGE: '/server/:key/rankings/player/od',
ARCHIVE_PAGE: '/server/:key/rankings/player/archive',
},
TRIBE_PAGE: {
INDEX_PAGE: '/server/:key/rankings/tribe',
OD_PAGE: '/server/:key/rankings/tribe/od',
ARCHIVE_PAGE: '/server/:key/rankings/tribe/archive',
},
},
ENNOBLEMENTS_PAGE: '/server/:key/ennoblements',
MAP_PAGE: '/server/:key/map',
};

View File

@ -18,7 +18,7 @@ function App() {
<Route path={ROUTES.SERVER_PAGE.BASE}>
<ServerPage />
</Route>
<Route>
<Route path="*">
<NotFoundPage />
</Route>
</Switch>

View File

@ -17,7 +17,7 @@ const EnhancedRoute = ({ children, ...rest }: RouteProps) => {
function ServerPage() {
return (
<Switch>
<EnhancedRoute path={SERVER_PAGE.INDEX_PAGE}>
<EnhancedRoute exact path={SERVER_PAGE.INDEX_PAGE}>
<IndexPage />
</EnhancedRoute>
<Route path="*">

View File

@ -27,7 +27,7 @@ const useStyles = makeStyles(theme => ({
},
content: {
height: '100%',
padding: theme.spacing(2),
padding: theme.spacing(3, 0),
},
}));

View File

@ -14,7 +14,12 @@ import {
DrawerProps,
Toolbar,
} from '@material-ui/core';
import DashboardIcon from '@material-ui/icons/Dashboard';
import {
Dashboard as DashboardIcon,
Map as MapIcon,
Grade as GradeIcon,
Beenhere as BeenhereIcon,
} from '@material-ui/icons';
import Nav from './components/Nav/Nav';
import ServerInfo from './components/ServerInfo/ServerInfo';
@ -41,6 +46,86 @@ const Sidebar = ({ t, className, open, variant, onClose, onOpen }: Props) => {
}),
Icon: <DashboardIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.rankings.name'),
Icon: <GradeIcon color="inherit" />,
nested: [
{
name: t('pageLayout.sidebar.routes.rankings.player.index'),
to: generatePath(
ROUTES.SERVER_PAGE.RANKINGS_PAGE.PLAYER_PAGE.INDEX_PAGE,
{
key: key,
}
),
Icon: <GradeIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.rankings.player.od'),
to: generatePath(
ROUTES.SERVER_PAGE.RANKINGS_PAGE.PLAYER_PAGE.OD_PAGE,
{
key: key,
}
),
Icon: <GradeIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.rankings.player.archive'),
to: generatePath(
ROUTES.SERVER_PAGE.RANKINGS_PAGE.PLAYER_PAGE.ARCHIVE_PAGE,
{
key: key,
}
),
Icon: <GradeIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.rankings.tribe.index'),
to: generatePath(
ROUTES.SERVER_PAGE.RANKINGS_PAGE.TRIBE_PAGE.INDEX_PAGE,
{
key: key,
}
),
Icon: <GradeIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.rankings.tribe.od'),
to: generatePath(
ROUTES.SERVER_PAGE.RANKINGS_PAGE.TRIBE_PAGE.OD_PAGE,
{
key: key,
}
),
Icon: <GradeIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.rankings.tribe.archive'),
to: generatePath(
ROUTES.SERVER_PAGE.RANKINGS_PAGE.TRIBE_PAGE.ARCHIVE_PAGE,
{
key: key,
}
),
Icon: <GradeIcon color="inherit" />,
},
],
},
{
name: t('pageLayout.sidebar.routes.ennoblements'),
to: generatePath(ROUTES.SERVER_PAGE.ENNOBLEMENTS_PAGE, {
key: key,
}),
Icon: <BeenhereIcon color="inherit" />,
},
{
name: t('pageLayout.sidebar.routes.map'),
to: generatePath(ROUTES.SERVER_PAGE.MAP_PAGE, {
key: key,
}),
Icon: <MapIcon color="inherit" />,
},
];
return (

View File

@ -60,7 +60,7 @@ function ListItem({ route, nestedLevel }: Props) {
return (
<Fragment>
{!hasNested ? (
{!hasNested && route.to ? (
<Link
to={route.to}
className={classes.link}

View File

@ -1,6 +1,6 @@
export interface Route {
name: string;
to: string;
to?: string;
Icon: React.ReactElement;
nested?: Route[];
}

View File

@ -1,5 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import useServer from '../../libs/ServerContext/useServer';
import { SERVER_PAGE } from '@config/namespaces';
import { Container, Grid } from '@material-ui/core';
import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
@ -8,15 +10,16 @@ import RecentlyDeletedTribes from './components/RecentlyDeletedTribes/RecentlyDe
function IndexPage() {
const { key } = useServer();
const { t } = useTranslation(SERVER_PAGE.INDEX_PAGE);
return (
<PageLayout>
<Container>
<Grid container spacing={1}>
<Grid item xs={6}>
<RecentlyDeletedPlayers server={key} />
<RecentlyDeletedPlayers server={key} t={t} />
</Grid>
<Grid item xs={6}>
<RecentlyDeletedTribes server={key} />
<RecentlyDeletedTribes server={key} t={t} />
</Grid>
</Grid>
</Container>

View File

@ -2,6 +2,8 @@ import React from 'react';
import { useQuery } from '@apollo/client';
import { RECENTLY_DELETED_PLAYERS } from './queries';
import { COLUMNS, LIMIT } from './constants';
import { TFunction } from 'i18next';
import { PlayersQueryVariables } from '@libs/graphql/types';
import { PlayerList } from './types';
@ -10,9 +12,10 @@ import Table from '@common/Table/Table';
export interface Props {
server: string;
t: TFunction;
}
function RecentlyDeletedPlayers({ server }: Props) {
function RecentlyDeletedPlayers({ server, t }: Props) {
const { loading: loadingPlayers, data } = useQuery<
PlayerList,
PlayersQueryVariables
@ -34,10 +37,15 @@ function RecentlyDeletedPlayers({ server }: Props) {
return (
<Paper>
<Toolbar>
<Typography variant="h4">Recently deleted players</Typography>
<Typography variant="h4">
{t('recentlyDeletedPlayers.title')}
</Typography>
</Toolbar>
<Table
columns={COLUMNS}
columns={COLUMNS.map(column => ({
...column,
label: column.label ? t<string>(column.label) : '',
}))}
loading={loading}
data={players}
size="small"

View File

@ -1,6 +1,7 @@
import { Column } from '@common/Table/types';
import { Player } from './types';
export const COLUMNS: Column[] = [
export const COLUMNS: Column<Player>[] = [
{
field: 'name',
label: 'recentlyDeletedPlayers.columns.name',
@ -10,8 +11,7 @@ export const COLUMNS: Column[] = [
field: 'mostPoints',
label: 'recentlyDeletedPlayers.columns.mostPoints',
sortable: false,
valueFormatter: (param: string | number | boolean) =>
(param as number).toLocaleString(),
valueFormatter: (player: Player) => player.mostPoints.toLocaleString(),
},
{
field: 'deletedAt',

View File

@ -2,7 +2,9 @@ import React from 'react';
import { useQuery } from '@apollo/client';
import { RECENTLY_DELETED_TRIBES } from './queries';
import { COLUMNS, LIMIT } from './constants';
import { PlayersQueryVariables } from '@libs/graphql/types';
import { TFunction } from 'i18next';
import { TribesQueryVariables } from '@libs/graphql/types';
import { TribeList } from './types';
import { Paper, Toolbar, Typography } from '@material-ui/core';
@ -10,12 +12,13 @@ import Table from '@common/Table/Table';
export interface Props {
server: string;
t: TFunction;
}
function RecentlyDeletedPlayers({ server }: Props) {
const { loading: loadingPlayers, data } = useQuery<
function RecentlyDeletedTribes({ server, t }: Props) {
const { loading: loadingTribes, data } = useQuery<
TribeList,
PlayersQueryVariables
TribesQueryVariables
>(RECENTLY_DELETED_TRIBES, {
fetchPolicy: 'cache-and-network',
variables: {
@ -28,16 +31,19 @@ function RecentlyDeletedPlayers({ server }: Props) {
},
});
const tribes = data?.tribes?.items ?? [];
const loading = loadingPlayers && tribes.length === 0;
const loading = loadingTribes && tribes.length === 0;
console.log(tribes, loading);
return (
<Paper>
<Toolbar>
<Typography variant="h4">Recently deleted tribes</Typography>
<Typography variant="h4">{t('recentlyDeletedTribes.title')}</Typography>
</Toolbar>
<Table
columns={COLUMNS}
columns={COLUMNS.map(column => ({
...column,
label: column.label ? t<string>(column.label) : '',
}))}
loading={loading}
data={tribes}
size="small"
@ -48,4 +54,4 @@ function RecentlyDeletedPlayers({ server }: Props) {
);
}
export default RecentlyDeletedPlayers;
export default RecentlyDeletedTribes;

View File

@ -1,21 +1,22 @@
import { Column } from '@common/Table/types';
import { Tribe } from './types';
export const COLUMNS: Column[] = [
export const COLUMNS: Column<Tribe>[] = [
{
field: 'name',
label: 'recentlyDeletedPlayers.columns.name',
label: 'recentlyDeletedTribes.columns.name',
valueFormatter: (tribe: Tribe) => `${tribe.name} (${tribe.tag})`,
sortable: false,
},
{
field: 'mostPoints',
label: 'recentlyDeletedPlayers.columns.mostPoints',
label: 'recentlyDeletedTribes.columns.mostPoints',
sortable: false,
valueFormatter: (param: string | number | boolean) =>
(param as number).toLocaleString(),
valueFormatter: (tribe: Tribe) => tribe.mostPoints.toLocaleString(),
},
{
field: 'deletedAt',
label: 'recentlyDeletedPlayers.columns.deletedAt',
label: 'recentlyDeletedTribes.columns.deletedAt',
sortable: false,
type: 'datetime',
},

View File

@ -5,6 +5,7 @@ export const RECENTLY_DELETED_TRIBES = gql`
tribes(server: $server, filter: $filter) {
items {
id
tag
name
mostPoints
deletedAt

View File

@ -2,6 +2,7 @@ import { List } from '@libs/graphql/types';
export type Tribe = {
id: number;
tag: string;
name: string;
mostPoints: number;
deletedAt: string | Date;

View File

@ -16,6 +16,19 @@ export type PlayersQueryVariables = {
filter?: PlayerFilter;
};
export type TribeFilter = {
limit?: number;
offset?: number;
sort?: string;
id?: number[];
deletedAtGT?: Date | string;
};
export type TribesQueryVariables = {
server: string;
filter?: TribeFilter;
};
export type ServerFilter = {
limit?: number;
offset?: number;

View File

@ -6,6 +6,21 @@ const translations = {
sidebar: {
routes: {
dashboard: 'Dashboard',
rankings: {
name: 'Rankings',
player: {
index: 'Players',
od: 'Players OD',
archive: 'Past players',
},
tribe: {
index: 'Tribes',
od: 'Tribes OD',
archive: 'Past tribes',
},
},
ennoblements: 'Latest ennoblements',
map: 'Map tool',
},
serverInfo: {
numberOfPlayers: '{{num}} player',

View File

@ -0,0 +1,20 @@
const translations = {
recentlyDeletedPlayers: {
title: 'Recently deleted players',
columns: {
name: 'Name',
mostPoints: 'Most points',
deletedAt: 'Deleted at',
},
},
recentlyDeletedTribes: {
title: 'Recently deleted tribes',
columns: {
name: 'Name',
mostPoints: 'Most points',
deletedAt: 'Deleted at',
},
},
};
export default translations;

View File

@ -1,8 +1,10 @@
import * as NAMESPACES from '@config/namespaces';
import common from './common';
import indexPage from './index-page';
const translations = {
[NAMESPACES.SERVER_PAGE.COMMON]: common,
[NAMESPACES.SERVER_PAGE.INDEX_PAGE]: indexPage,
};
export default translations;

View File

@ -12,7 +12,7 @@ const init = (): i18nT => {
.use(initReactI18next)
.init({
fallbackLng: DEFAULT_LANGUAGE,
debug: false,
debug: process.env.NODE_ENV === 'development',
load: 'languageOnly',
detection: {
order: ['subdomain'],

View File

@ -6,6 +6,21 @@ const translations = {
sidebar: {
routes: {
dashboard: 'Dashboard',
rankings: {
name: 'Rankingi',
player: {
index: 'Gracze',
od: 'Gracze OD',
archive: 'Byli gracze',
},
tribe: {
index: 'Plemiona',
od: 'Plemiona OD',
archive: 'Byłe plemiona',
},
},
ennoblements: 'Ostatnie podbicia',
map: 'Narzędzie mapy',
},
serverInfo: {
numberOfPlayers_0: '{{num}} gracz',

View File

@ -0,0 +1,20 @@
const translations = {
recentlyDeletedPlayers: {
title: 'Ostatnio usunięci gracze',
columns: {
name: 'Nazwa',
mostPoints: 'Najwięcej punktów',
deletedAt: 'Usunięty o',
},
},
recentlyDeletedTribes: {
title: 'Recently deleted tribes',
columns: {
name: 'Nazwa',
mostPoints: 'Najwięcej punktów',
deletedAt: 'Usunięty o',
},
},
};
export default translations;

View File

@ -1,8 +1,10 @@
import * as NAMESPACES from '@config/namespaces';
import common from './common';
import indexPage from './index-page';
const translations = {
[NAMESPACES.SERVER_PAGE.COMMON]: common,
[NAMESPACES.SERVER_PAGE.INDEX_PAGE]: indexPage,
};
export default translations;