make table translatable

This commit is contained in:
Dawid Wysokiński 2020-11-13 15:26:30 +01:00
parent 88eb65ff65
commit b51b33d7b6
18 changed files with 243 additions and 67 deletions

View File

@ -1,4 +1,6 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { TABLE } from '@config/namespaces';
import isObjKey from '@utils/isObjKey'; import isObjKey from '@utils/isObjKey';
import { Action, Column, OrderDirection } from './types'; import { Action, Column, OrderDirection } from './types';
@ -7,6 +9,7 @@ import {
TableBody, TableBody,
TableProps, TableProps,
TableBodyProps, TableBodyProps,
TableContainer,
} from '@material-ui/core'; } from '@material-ui/core';
import TableHead from './TableHead'; import TableHead from './TableHead';
import TableRow from './TableRow'; import TableRow from './TableRow';
@ -32,6 +35,7 @@ export interface Props<T> {
tableBodyProps?: TableBodyProps; tableBodyProps?: TableBodyProps;
footerProps?: TableFooterProps; footerProps?: TableFooterProps;
hideFooter?: boolean; hideFooter?: boolean;
size?: 'medium' | 'small';
} }
function Table<T extends object>({ function Table<T extends object>({
@ -48,46 +52,58 @@ function Table<T extends object>({
tableProps = {}, tableProps = {},
hideFooter = false, hideFooter = false,
footerProps, footerProps,
size,
}: Props<T>) { }: Props<T>) {
const { t } = useTranslation(TABLE);
return ( return (
<MUITable {...tableProps}> <TableContainer>
<TableHead <MUITable size={size} {...tableProps}>
columns={columns} <TableHead
selection={selection} columns={columns}
orderBy={orderBy} selection={selection}
orderDirection={orderDirection} orderBy={orderBy}
onRequestSort={onRequestSort} orderDirection={orderDirection}
allSelected={false} onRequestSort={onRequestSort}
/> allSelected={false}
<TableBody {...tableBodyProps}> size={size}
{loading ? (
<TableLoading />
) : data.length > 0 ? (
data.map((item, index) => {
return (
<TableRow
key={
isObjKey(item, idFieldName) ? item[idFieldName] + '' : index
}
row={item}
actions={actions}
selected={false}
selection={selection}
columns={columns}
/>
);
})
) : (
<TableEmpty />
)}
</TableBody>
{!hideFooter && (
<TableFooter
count={footerProps?.count ?? data.length}
{...footerProps}
/> />
)} <TableBody {...tableBodyProps}>
</MUITable> {loading ? (
<TableLoading
columns={columns}
size={size}
rowsPerPage={footerProps?.rowsPerPage ?? 50}
/>
) : data.length > 0 ? (
data.map((item, index) => {
return (
<TableRow
key={
isObjKey(item, idFieldName) ? item[idFieldName] + '' : index
}
row={item}
actions={actions}
selected={false}
selection={selection}
columns={columns}
size={size}
/>
);
})
) : (
<TableEmpty t={t} />
)}
</TableBody>
{!hideFooter && (
<TableFooter
t={t}
count={footerProps?.count ?? data.length}
size={size}
{...footerProps}
/>
)}
</MUITable>
</TableContainer>
); );
} }

View File

@ -1,12 +1,17 @@
import React from 'react'; import React from 'react';
import { TFunction } from 'i18next';
import { TableRow, TableCell, Typography } from '@material-ui/core'; import { TableRow, TableCell, Typography } from '@material-ui/core';
function TableEmpty() { export interface Props {
t: TFunction;
}
function TableEmpty({ t }: Props) {
return ( return (
<TableRow> <TableRow>
<TableCell colSpan={100}> <TableCell colSpan={100}>
<Typography align="center">No records to display</Typography> <Typography align="center">{t('emptyDataSourceMessage')}</Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
); );

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { TFunction } from 'i18next';
import { import {
TablePagination, TablePagination,
@ -24,7 +25,8 @@ function TableFooter({
rowsPerPageOptions = [25, 50, 100], rowsPerPageOptions = [25, 50, 100],
rowsPerPage = 50, rowsPerPage = 50,
size = 'small', size = 'small',
}: Props) { t,
}: Props & { t: TFunction }) {
const handlePageChange = ( const handlePageChange = (
event: React.MouseEvent<HTMLButtonElement> | null, event: React.MouseEvent<HTMLButtonElement> | null,
page: number page: number
@ -53,6 +55,10 @@ function TableFooter({
rowsPerPageOptions={rowsPerPageOptions} rowsPerPageOptions={rowsPerPageOptions}
size={size} size={size}
colSpan={100} colSpan={100}
labelDisplayedRows={payload => t('labelDisplayedRows', payload)}
labelRowsPerPage={t('labelRowsPerPage')}
nextIconButtonText={t('next')}
backIconButtonText={t('prev')}
/> />
</TableRow> </TableRow>
</MUITableFooter> </MUITableFooter>

View File

@ -17,6 +17,7 @@ export interface Props {
allSelected: boolean; allSelected: boolean;
orderDirection: OrderDirection; orderDirection: OrderDirection;
orderBy: string; orderBy: string;
size?: 'small' | 'medium';
onRequestSort?: ( onRequestSort?: (
property: string, property: string,
orderDirection: OrderDirection orderDirection: OrderDirection
@ -31,6 +32,7 @@ function TableHead({
selection = false, selection = false,
allSelected = false, allSelected = false,
onRequestSort, onRequestSort,
size = 'medium',
}: Props) { }: Props) {
const createSortHandler = (property: string) => () => { const createSortHandler = (property: string) => () => {
if (onRequestSort) { if (onRequestSort) {
@ -52,13 +54,14 @@ function TableHead({
<MUITableHead> <MUITableHead>
<TableRow> <TableRow>
{selection && ( {selection && (
<TableCell padding="checkbox"> <TableCell size={size} padding="checkbox">
<Checkbox checked={allSelected} onClick={handleSelectAll} /> <Checkbox checked={allSelected} onClick={handleSelectAll} />
</TableCell> </TableCell>
)} )}
{columns.map(col => { {columns.map(col => {
return ( return (
<TableCell <TableCell
size={size}
key={col.field} key={col.field}
padding={col.disablePadding ? 'none' : 'default'} padding={col.disablePadding ? 'none' : 'default'}
align={col.align ? col.align : 'left'} align={col.align ? col.align : 'left'}

View File

@ -1,21 +1,30 @@
import React from 'react'; import React, { Fragment } from 'react';
import { Column } from './types';
import { TableRow, CircularProgress, Box, TableCell } from '@material-ui/core'; import { TableRow, TableCell } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';
function TableLoading() { export interface Props {
rowsPerPage: number;
columns: Column[];
size?: 'small' | 'medium';
}
function TableLoading({ rowsPerPage, columns, size = 'medium' }: Props) {
return ( return (
<TableRow> <Fragment>
<TableCell colSpan={100}> {new Array(rowsPerPage).fill(0).map((_, index) => {
<Box return (
paddingY={2} <TableRow key={index}>
display="flex" {columns.map(col => (
alignItems="center" <TableCell size={size} key={col.label}>
justifyContent="center" <Skeleton variant="text" />
> </TableCell>
<CircularProgress size={200} /> ))}
</Box> </TableRow>
</TableCell> );
</TableRow> })}
</Fragment>
); );
} }

View File

@ -11,6 +11,7 @@ export type Props<T> = {
row: T; row: T;
selection: boolean; selection: boolean;
selected: boolean; selected: boolean;
size?: 'small' | 'medium';
onSelect?: (row: T) => void; onSelect?: (row: T) => void;
}; };
@ -21,6 +22,7 @@ function EnhancedTableRow<T extends object>({
selection = false, selection = false,
selected = false, selected = false,
onSelect, onSelect,
size = 'medium',
}: Props<T>) { }: Props<T>) {
const handleSelect = () => { const handleSelect = () => {
if (onSelect) { if (onSelect) {
@ -44,7 +46,7 @@ function EnhancedTableRow<T extends object>({
return ( return (
<TableRow> <TableRow>
{selection && ( {selection && (
<TableCell padding="checkbox"> <TableCell size={size} padding="checkbox">
<Checkbox checked={selected} onClick={handleSelect} /> <Checkbox checked={selected} onClick={handleSelect} />
</TableCell> </TableCell>
)} )}
@ -52,6 +54,7 @@ function EnhancedTableRow<T extends object>({
const val = get(row, col.field, ''); const val = get(row, col.field, '');
return ( return (
<TableCell <TableCell
size={size}
key={col.field} key={col.field}
padding={col.disablePadding ? 'none' : 'default'} padding={col.disablePadding ? 'none' : 'default'}
align={col.align ? col.align : 'left'} align={col.align ? col.align : 'left'}
@ -65,7 +68,7 @@ function EnhancedTableRow<T extends object>({
); );
})} })}
{actions.length > 0 && ( {actions.length > 0 && (
<TableCell> <TableCell size={size}>
{actions.map((action, index) => {actions.map((action, index) =>
action.tooltip ? ( action.tooltip ? (
<Tooltip key={index} title={action.tooltip}> <Tooltip key={index} title={action.tooltip}>

View File

@ -1,4 +1,5 @@
export const COMMON = 'common'; export const COMMON = 'common';
export const TABLE = 'table';
export const INDEX_PAGE = 'index-page'; export const INDEX_PAGE = 'index-page';
export const NOT_FOUND_PAGE = 'not-found-page'; export const NOT_FOUND_PAGE = 'not-found-page';
export const SERVER_PAGE = { export const SERVER_PAGE = {

View File

@ -1,16 +1,24 @@
import React from 'react'; import React from 'react';
import useServer from '../../libs/ServerContext/useServer'; import useServer from '../../libs/ServerContext/useServer';
import { Container } from '@material-ui/core'; import { Container, Grid } from '@material-ui/core';
import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout'; import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import RecentlyDeletedPlayers from './components/RecentlyDeletedPlayers/RecentlyDeletedPlayers'; import RecentlyDeletedPlayers from './components/RecentlyDeletedPlayers/RecentlyDeletedPlayers';
import RecentlyDeletedTribes from './components/RecentlyDeletedTribes/RecentlyDeletedTribes';
function IndexPage() { function IndexPage() {
const { key } = useServer(); const { key } = useServer();
return ( return (
<PageLayout> <PageLayout>
<Container> <Container>
<RecentlyDeletedPlayers server={key} /> <Grid container spacing={1}>
<Grid item xs={6}>
<RecentlyDeletedPlayers server={key} />
</Grid>
<Grid item xs={6}>
<RecentlyDeletedTribes server={key} />
</Grid>
</Grid>
</Container> </Container>
</PageLayout> </PageLayout>
); );

View File

@ -34,13 +34,15 @@ function RecentlyDeletedPlayers({ server }: Props) {
return ( return (
<Paper> <Paper>
<Toolbar> <Toolbar>
<Typography variant="h4">Test?</Typography> <Typography variant="h4">Recently deleted players</Typography>
</Toolbar> </Toolbar>
<Table <Table
columns={COLUMNS} columns={COLUMNS}
loading={loading} loading={loading}
data={players} data={players}
footerProps={{ rowsPerPage: LIMIT }} size="small"
hideFooter
footerProps={{ rowsPerPage: LIMIT, rowsPerPageOptions: [LIMIT] }}
/> />
</Paper> </Paper>
); );

View File

@ -0,0 +1,51 @@
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 { TribeList } from './types';
import { Paper, Toolbar, Typography } from '@material-ui/core';
import Table from '@common/Table/Table';
export interface Props {
server: string;
}
function RecentlyDeletedPlayers({ server }: Props) {
const { loading: loadingPlayers, data } = useQuery<
TribeList,
PlayersQueryVariables
>(RECENTLY_DELETED_TRIBES, {
fetchPolicy: 'cache-and-network',
variables: {
filter: {
limit: LIMIT,
sort: 'deletedAt DESC',
deletedAtGT: new Date(0),
},
server,
},
});
const tribes = data?.tribes?.items ?? [];
const loading = loadingPlayers && tribes.length === 0;
console.log(tribes, loading);
return (
<Paper>
<Toolbar>
<Typography variant="h4">Recently deleted tribes</Typography>
</Toolbar>
<Table
columns={COLUMNS}
loading={loading}
data={tribes}
size="small"
hideFooter
footerProps={{ rowsPerPage: LIMIT, rowsPerPageOptions: [LIMIT] }}
/>
</Paper>
);
}
export default RecentlyDeletedPlayers;

View File

@ -0,0 +1,24 @@
import { Column } from '@common/Table/types';
export const COLUMNS: Column[] = [
{
field: 'name',
label: 'recentlyDeletedPlayers.columns.name',
sortable: false,
},
{
field: 'mostPoints',
label: 'recentlyDeletedPlayers.columns.mostPoints',
sortable: false,
valueFormatter: (param: string | number | boolean) =>
(param as number).toLocaleString(),
},
{
field: 'deletedAt',
label: 'recentlyDeletedPlayers.columns.deletedAt',
sortable: false,
type: 'datetime',
},
];
export const LIMIT = 5;

View File

@ -0,0 +1,14 @@
import { gql } from '@apollo/client';
export const RECENTLY_DELETED_TRIBES = gql`
query tribes($server: String!, $filter: TribeFilter) {
tribes(server: $server, filter: $filter) {
items {
id
name
mostPoints
deletedAt
}
}
}
`;

View File

@ -0,0 +1,12 @@
import { List } from '@libs/graphql/types';
export type Tribe = {
id: number;
name: string;
mostPoints: number;
deletedAt: string | Date;
};
export type TribeList = {
tribes?: List<Tribe[]>;
};

View File

@ -3,11 +3,13 @@ import common from './common';
import indexPage from './index-page'; import indexPage from './index-page';
import notFoundPage from './not-found-page'; import notFoundPage from './not-found-page';
import serverPage from './server-page'; import serverPage from './server-page';
import table from './table';
const translations = { const translations = {
[NAMESPACES.COMMON]: common, [NAMESPACES.COMMON]: common,
[NAMESPACES.INDEX_PAGE]: indexPage, [NAMESPACES.INDEX_PAGE]: indexPage,
[NAMESPACES.NOT_FOUND_PAGE]: notFoundPage, [NAMESPACES.NOT_FOUND_PAGE]: notFoundPage,
[NAMESPACES.TABLE]: table,
...serverPage, ...serverPage,
}; };

View File

@ -1,11 +1,11 @@
const translations = { const translations = {
emptyDataSourceMessage: 'No records to display', emptyDataSourceMessage: 'No records to display',
labelDisplayedRows: '{from}-{to} of {count}', labelDisplayedRows: '{{from}}-{{to}} of {{count}}',
labelRowsPerPage: 'Rows per page:', labelRowsPerPage: 'Rows per page:',
firstTooltip: 'First Page', first: 'First page',
previousTooltip: 'Previous Page', last: 'Last page',
nextTooltip: 'Next Page', next: 'Next page',
lastTooltip: 'Last Page', previous: 'Previous page',
}; };
export default translations; export default translations;

View File

@ -3,11 +3,13 @@ import common from './common';
import indexPage from './index-page'; import indexPage from './index-page';
import notFoundPage from './not-found-page'; import notFoundPage from './not-found-page';
import serverPage from './server-page'; import serverPage from './server-page';
import table from './table';
const translations = { const translations = {
[NAMESPACES.COMMON]: common, [NAMESPACES.COMMON]: common,
[NAMESPACES.INDEX_PAGE]: indexPage, [NAMESPACES.INDEX_PAGE]: indexPage,
[NAMESPACES.NOT_FOUND_PAGE]: notFoundPage, [NAMESPACES.NOT_FOUND_PAGE]: notFoundPage,
[NAMESPACES.TABLE]: table,
...serverPage, ...serverPage,
}; };

11
src/libs/i18n/pl/table.ts Normal file
View File

@ -0,0 +1,11 @@
const translations = {
emptyDataSourceMessage: 'Brak danych do wyświetlenia',
labelDisplayedRows: '{{from}}-{{to}} z {{count}}',
labelRowsPerPage: 'Wierszy na stronę:',
first: 'Pierwsza strona',
last: 'Ostatnia strona',
next: 'Następna strona',
previous: 'Poprzednia strona',
};
export default translations;

View File

@ -23,6 +23,13 @@ const createTheme = (): Theme => {
color: 'default', color: 'default',
}, },
}, },
overrides: {
MuiTableContainer: {
root: {
overflow: 'auto',
},
},
},
}) })
); );
}; };