From 7a6b8cf20b3ff4dd6af0398b4fa7aca667fc6b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20J=C3=B3=C5=BAwiak?= Date: Wed, 7 Apr 2021 23:42:10 +0200 Subject: [PATCH] refactor(client): lists (#235) * refactor(client): move lists into a single directory, change filters icon * fix(client): add missing translations * fix(client): remove padding from back button on mobile --- .../buttons/BackButton/BackButton.styled.ts | 9 ++ .../src/elements/buttons/BackButton/index.tsx | 11 +- packages/client/src/elements/index.ts | 3 +- .../{ => lists}/CardList/CardList.styled.ts | 15 +-- .../{ => lists}/CardList/CardList.types.d.ts | 0 .../elements/{ => lists}/CardList/index.tsx | 16 +-- .../DetailedList/DetailedList.styled.ts} | 4 +- .../DetailedList/DetailedList.types.d.ts} | 2 +- .../Pagination/Pagination.styled.ts | 2 +- .../Pagination/Pagination.types.d.ts | 0 .../DetailedList}/Pagination/index.tsx | 14 ++- .../{List => lists/DetailedList}/index.tsx | 30 ++--- packages/client/src/elements/lists/index.ts | 2 + packages/client/src/i18n/resources/en.ts | 21 ++++ packages/client/src/i18n/resources/pl.ts | 21 ++++ packages/client/src/icons/FiltersIcon.tsx | 104 +++++++++++++++--- .../pages/authorized/admin/Users/index.tsx | 12 +- 17 files changed, 201 insertions(+), 65 deletions(-) rename packages/client/src/elements/{ => lists}/CardList/CardList.styled.ts (87%) rename packages/client/src/elements/{ => lists}/CardList/CardList.types.d.ts (100%) rename packages/client/src/elements/{ => lists}/CardList/index.tsx (80%) rename packages/client/src/elements/{List/List.styled.ts => lists/DetailedList/DetailedList.styled.ts} (97%) rename packages/client/src/elements/{List/List.types.d.ts => lists/DetailedList/DetailedList.types.d.ts} (78%) rename packages/client/src/elements/{List => lists/DetailedList}/Pagination/Pagination.styled.ts (94%) rename packages/client/src/elements/{List => lists/DetailedList}/Pagination/Pagination.types.d.ts (100%) rename packages/client/src/elements/{List => lists/DetailedList}/Pagination/index.tsx (83%) rename packages/client/src/elements/{List => lists/DetailedList}/index.tsx (85%) create mode 100644 packages/client/src/elements/lists/index.ts diff --git a/packages/client/src/elements/buttons/BackButton/BackButton.styled.ts b/packages/client/src/elements/buttons/BackButton/BackButton.styled.ts index a13a9f0f..1234cd3a 100644 --- a/packages/client/src/elements/buttons/BackButton/BackButton.styled.ts +++ b/packages/client/src/elements/buttons/BackButton/BackButton.styled.ts @@ -1,7 +1,16 @@ import styled from 'styled-components'; import BackArrowIcon from '../../../icons/BackArrowIcon'; +import TextButton from '../TextButton'; export const BackIcon = styled(BackArrowIcon)` margin-right: 10px; `; + +export const StyledTextButton = styled(TextButton)` + ${({ theme: { breakpoints, down } }) => ` + ${down(breakpoints.md)} { + padding: 0; + } + `}; +`; diff --git a/packages/client/src/elements/buttons/BackButton/index.tsx b/packages/client/src/elements/buttons/BackButton/index.tsx index 5a3e86bf..ea7adb4c 100644 --- a/packages/client/src/elements/buttons/BackButton/index.tsx +++ b/packages/client/src/elements/buttons/BackButton/index.tsx @@ -1,17 +1,18 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; -import TextButton from '../TextButton'; -import { BackIcon } from './BackButton.styled'; +import { BackIcon, StyledTextButton } from './BackButton.styled'; const BackButton = () => { const history = useHistory(); + const { t } = useTranslation(); return ( - history.goBack()}> + history.goBack()}> - back - + {t('actions.back')} + ); }; diff --git a/packages/client/src/elements/index.ts b/packages/client/src/elements/index.ts index c23c2b29..0e34ac1f 100644 --- a/packages/client/src/elements/index.ts +++ b/packages/client/src/elements/index.ts @@ -10,12 +10,11 @@ export { export { default as BackgroundSideLogo } from './BackgroundSideLogo'; export { BackButton, Button, IconButton } from './buttons'; export { default as Card } from './Card'; -export { default as CardList } from './CardList'; export { default as Copyright } from './Copyright'; export { DateField, FunctionField, TextField } from './fields'; export { default as Form } from './Form'; export { Checkbox, Select, TextInput } from './inputs'; export { CardLayout, DefaultLayout, GlobalLayout, TabbedLayout } from './layouts'; export { default as Link } from './Link'; -export { default as List } from './List'; +export { CardList, DetailedList } from './lists'; export { default as Snackbar } from './Snackbar'; diff --git a/packages/client/src/elements/CardList/CardList.styled.ts b/packages/client/src/elements/lists/CardList/CardList.styled.ts similarity index 87% rename from packages/client/src/elements/CardList/CardList.styled.ts rename to packages/client/src/elements/lists/CardList/CardList.styled.ts index 42be6db3..f2e300bf 100644 --- a/packages/client/src/elements/CardList/CardList.styled.ts +++ b/packages/client/src/elements/lists/CardList/CardList.styled.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; -import { IconButton } from '../buttons'; -import Card from '../Card'; +import { Button, IconButton } from '../../buttons'; +import Card from '../../Card'; export const Wrapper = styled.div` display: flex; @@ -37,10 +37,11 @@ export const StyledCard = styled(Card)` `}; `; -export const FiltersButton = styled(IconButton)` - svg { - color: ${({ theme }) => theme.palette.primary.light}; - } +export const FiltersButton = styled(Button)` + min-width: 0; + width: 55px; + height: 55px; + padding: 10px; `; export const EditButton = styled(IconButton)` @@ -81,7 +82,7 @@ export const TitleWrapper = styled.div` export const FiltersContainer = styled.div` display: flex; width: 100%; - padding: 40px 20px; + padding: 40px 0; justify-content: space-between; align-items: center; max-width: 740px; diff --git a/packages/client/src/elements/CardList/CardList.types.d.ts b/packages/client/src/elements/lists/CardList/CardList.types.d.ts similarity index 100% rename from packages/client/src/elements/CardList/CardList.types.d.ts rename to packages/client/src/elements/lists/CardList/CardList.types.d.ts diff --git a/packages/client/src/elements/CardList/index.tsx b/packages/client/src/elements/lists/CardList/index.tsx similarity index 80% rename from packages/client/src/elements/CardList/index.tsx rename to packages/client/src/elements/lists/CardList/index.tsx index a8847dfe..c96026c0 100644 --- a/packages/client/src/elements/CardList/index.tsx +++ b/packages/client/src/elements/lists/CardList/index.tsx @@ -1,12 +1,13 @@ import React, { Children, cloneElement, isValidElement } from 'react'; +import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; import { useHistory } from 'react-router-dom'; -import { routes } from '../../constants'; -import { EditIcon, FiltersIcon } from '../../icons'; -import { ApiList } from '../../interfaces/api.types'; -import { getLabelFromSource } from '../../utils'; -import { BaseFieldProps, BaseRecordField } from '../fields/Fields.types'; +import { routes } from '../../../constants'; +import { EditIcon, FiltersIcon } from '../../../icons'; +import { ApiList } from '../../../interfaces/api.types'; +import { ThemeType } from '../../../theme/Theme'; +import { BaseFieldProps, BaseRecordField } from '../../fields/Fields.types'; import { CardFieldContainer, CardsWrapper, @@ -23,6 +24,7 @@ const CardList = ({ children, resource }: CardListProps) => { const queryResult = useQuery>(`/${resource}`); const records = queryResult.data?.data; const history = useHistory(); + const { t } = useTranslation(); const onClickEdit = (id: string) => { // TODO: move to user details @@ -35,7 +37,7 @@ const CardList = ({ children, resource }: CardListProps) => { {/* TODO: add search input */}

Search

- +
@@ -58,7 +60,7 @@ const CardList = ({ children, resource }: CardListProps) => { return {cloneElement(child, { record })}; } - const internalLabel = label || getLabelFromSource(source || ''); + const internalLabel = label || t(`baseApiFields.${source}` as never); return ( diff --git a/packages/client/src/elements/List/List.styled.ts b/packages/client/src/elements/lists/DetailedList/DetailedList.styled.ts similarity index 97% rename from packages/client/src/elements/List/List.styled.ts rename to packages/client/src/elements/lists/DetailedList/DetailedList.styled.ts index a24b7944..68a6c9e9 100644 --- a/packages/client/src/elements/List/List.styled.ts +++ b/packages/client/src/elements/lists/DetailedList/DetailedList.styled.ts @@ -1,7 +1,7 @@ import styled from 'styled-components'; -import { IconButton } from '../buttons'; -import Card from '../Card'; +import { IconButton } from '../../buttons'; +import Card from '../../Card'; export const StyledCard = styled(Card)<{ isBulkActionsOpen: boolean }>` padding: 0; diff --git a/packages/client/src/elements/List/List.types.d.ts b/packages/client/src/elements/lists/DetailedList/DetailedList.types.d.ts similarity index 78% rename from packages/client/src/elements/List/List.types.d.ts rename to packages/client/src/elements/lists/DetailedList/DetailedList.types.d.ts index 8dd28360..e0cdf434 100644 --- a/packages/client/src/elements/List/List.types.d.ts +++ b/packages/client/src/elements/lists/DetailedList/DetailedList.types.d.ts @@ -1,6 +1,6 @@ import { MouseEvent, ReactNode } from 'react'; -export interface ListProps { +export interface DetailedListProps { onRowClick?: (event: MouseEvent) => void; resource: string; children: ReactNode; diff --git a/packages/client/src/elements/List/Pagination/Pagination.styled.ts b/packages/client/src/elements/lists/DetailedList/Pagination/Pagination.styled.ts similarity index 94% rename from packages/client/src/elements/List/Pagination/Pagination.styled.ts rename to packages/client/src/elements/lists/DetailedList/Pagination/Pagination.styled.ts index f42bd720..e8112996 100644 --- a/packages/client/src/elements/List/Pagination/Pagination.styled.ts +++ b/packages/client/src/elements/lists/DetailedList/Pagination/Pagination.styled.ts @@ -1,6 +1,6 @@ import styled from 'styled-components'; -import { IconButton } from '../../buttons'; +import { IconButton } from '../../../buttons'; export const PaginationWrapper = styled.div` border-top: 1px solid ${({ theme }) => theme.palette.divider.paper}; diff --git a/packages/client/src/elements/List/Pagination/Pagination.types.d.ts b/packages/client/src/elements/lists/DetailedList/Pagination/Pagination.types.d.ts similarity index 100% rename from packages/client/src/elements/List/Pagination/Pagination.types.d.ts rename to packages/client/src/elements/lists/DetailedList/Pagination/Pagination.types.d.ts diff --git a/packages/client/src/elements/List/Pagination/index.tsx b/packages/client/src/elements/lists/DetailedList/Pagination/index.tsx similarity index 83% rename from packages/client/src/elements/List/Pagination/index.tsx rename to packages/client/src/elements/lists/DetailedList/Pagination/index.tsx index 21ef1d65..c945e347 100644 --- a/packages/client/src/elements/List/Pagination/index.tsx +++ b/packages/client/src/elements/lists/DetailedList/Pagination/index.tsx @@ -1,5 +1,6 @@ import React, { useMemo } from 'react'; -import { Select } from 'src/elements'; +import { useTranslation } from 'react-i18next'; +import { Select } from 'src/elements/index'; import { PageNavigation, @@ -17,6 +18,8 @@ const Pagination = ({ setCurrentPage, totalRecords, }: PaginationProps) => { + const { t } = useTranslation(); + const totalPages = useMemo((): number => Math.ceil(totalRecords / perPage), [ perPage, totalRecords, @@ -25,7 +28,7 @@ const Pagination = ({ return ( - Rows per page: + {t('lists.detailedList.perPage')}: value={perPage} onChange={(selectedOption) => setPerPage(selectedOption.value)} @@ -36,14 +39,15 @@ const Pagination = ({ - {perPage * currentPage - perPage + 1}-{Math.min(perPage * currentPage, totalRecords)} of{' '} + {perPage * currentPage - perPage + 1}-{Math.min(perPage * currentPage, totalRecords)}  + {t('lists.detailedList.ofTotal')}  {totalRecords} {totalPages > 1 && ( <> {currentPage !== 1 && ( setCurrentPage(currentPage - 1)}> - prev + {t('lists.detailedList.prev')} )} @@ -61,7 +65,7 @@ const Pagination = ({ })} {currentPage !== totalPages && ( setCurrentPage(currentPage + 1)}> - next + {t('lists.detailedList.next')} )} diff --git a/packages/client/src/elements/List/index.tsx b/packages/client/src/elements/lists/DetailedList/index.tsx similarity index 85% rename from packages/client/src/elements/List/index.tsx rename to packages/client/src/elements/lists/DetailedList/index.tsx index 630d1c72..078fb1cf 100644 --- a/packages/client/src/elements/List/index.tsx +++ b/packages/client/src/elements/lists/DetailedList/index.tsx @@ -6,13 +6,13 @@ import React, { useMemo, useState, } from 'react'; +import { useTranslation } from 'react-i18next'; import { useMutation, useQuery } from 'react-query'; -import { CancelIcon, TrashIcon } from '../../icons'; -import { ApiList } from '../../interfaces/api.types'; -import { getLabelFromSource } from '../../utils'; -import { BaseFieldProps, BaseRecordField } from '../fields/Fields.types'; -import { Checkbox } from '../inputs'; +import { CancelIcon, TrashIcon } from '../../../icons'; +import { ApiList } from '../../../interfaces/api.types'; +import { BaseFieldProps, BaseRecordField } from '../../fields/Fields.types'; +import { Checkbox } from '../../inputs'; import { BulkActionsWrapper, BulkCancelButton, @@ -26,19 +26,20 @@ import { TableHeader, TableHeaderCheckbox, TableRow, -} from './List.styled'; -import { ListProps } from './List.types'; +} from './DetailedList.styled'; +import { DetailedListProps } from './DetailedList.types'; import Pagination from './Pagination'; -const List = ({ onRowClick, children, resource }: ListProps) => { - const { data: queryResult } = useQuery>(`/${resource}`); +const DetailedList = ({ onRowClick, children, resource }: DetailedListProps) => { const removeUser = async (id: string) => { console.log('Removed:', id); }; const deleteMutation = useMutation(removeUser); + const { data: queryResult } = useQuery>(`/${resource}`); const [selectedRows, setSelectedRows] = useState>([]); const [perPage, setPerPage] = useState(25); const [currentPage, setCurrentPage] = useState(1); + const { t } = useTranslation(); const totalRecords = useMemo((): number => { if (queryResult) { @@ -119,11 +120,12 @@ const List = ({ onRowClick, children, resource }: ListProps) => { - {selectedRows.length} {selectedRows.length > 1 ? 'items' : 'item'} selected + {selectedRows.length}  + {selectedRows.length > 1 ? t('lists.detailedList.items') : t('lists.detailedList.item')} - Delete + {t('actions.delete')} @@ -134,7 +136,7 @@ const List = ({ onRowClick, children, resource }: ListProps) => { {headers.map(({ label, source }) => ( - {label || getLabelFromSource(source || '')} + {label || t(`baseApiFields.${source}` as never)} ))} @@ -176,6 +178,6 @@ const List = ({ onRowClick, children, resource }: ListProps) => { ); }; -List.displayName = 'List'; +DetailedList.displayName = 'DetailedList'; -export default List; +export default DetailedList; diff --git a/packages/client/src/elements/lists/index.ts b/packages/client/src/elements/lists/index.ts new file mode 100644 index 00000000..d2c00b4e --- /dev/null +++ b/packages/client/src/elements/lists/index.ts @@ -0,0 +1,2 @@ +export { default as CardList } from './CardList'; +export { default as DetailedList } from './DetailedList'; diff --git a/packages/client/src/i18n/resources/en.ts b/packages/client/src/i18n/resources/en.ts index 2aa3459a..19da6989 100644 --- a/packages/client/src/i18n/resources/en.ts +++ b/packages/client/src/i18n/resources/en.ts @@ -1,16 +1,37 @@ const en = { translation: { + baseApiFields: { + id: 'Id', + email: 'Email', + createdAt: 'Created at', + updatedAt: 'Updated at', + }, user: { + user: 'User', firstName: 'First name', lastName: 'Last name', password: 'Password', }, + actions: { + back: 'Back', + delete: 'Delete', + }, menu: { history: 'History', dashboard: 'Dashboard', settings: 'Settings', admin: 'Admin', }, + lists: { + detailedList: { + items: 'items', + item: 'item', + perPage: 'Rows per page', + ofTotal: 'of', + next: 'next', + prev: 'prev', + }, + }, form: { errors: { onSubmitError: 'Oops! Something went wrong. Operation failed.', diff --git a/packages/client/src/i18n/resources/pl.ts b/packages/client/src/i18n/resources/pl.ts index 59b44d9a..60a890ad 100644 --- a/packages/client/src/i18n/resources/pl.ts +++ b/packages/client/src/i18n/resources/pl.ts @@ -2,17 +2,38 @@ import { TranslationStructure } from './en'; const pl: TranslationStructure = { translation: { + baseApiFields: { + id: 'Id', + email: 'Email', + createdAt: 'Data utworzenia', + updatedAt: 'Data aktualizacji', + }, user: { + user: 'Użytkownik', firstName: 'Imię', lastName: 'Nazwisko', password: 'Hasło', }, + actions: { + back: 'Wróć', + delete: 'Usuń', + }, menu: { history: 'Historia', dashboard: 'Pulpit', settings: 'Ustawienia', admin: 'Admin', }, + lists: { + detailedList: { + items: 'wierszy', + item: 'wiersz', + perPage: 'Wierszy na stronę', + ofTotal: 'z', + next: 'następny', + prev: 'poprzedni', + }, + }, form: { errors: { onSubmitError: 'Oops! Coś poszło nie tak.', diff --git a/packages/client/src/icons/FiltersIcon.tsx b/packages/client/src/icons/FiltersIcon.tsx index 73897cdb..146ea56a 100644 --- a/packages/client/src/icons/FiltersIcon.tsx +++ b/packages/client/src/icons/FiltersIcon.tsx @@ -1,20 +1,92 @@ import React, { forwardRef, memo, Ref, SVGProps } from 'react'; +import { useTheme } from 'styled-components'; -const FiltersIcon = (props: SVGProps, svgRef?: Ref) => ( - - - -); +const FiltersIcon = (props: SVGProps, svgRef?: Ref) => { + const { palette } = useTheme(); + const background = palette.primary.dark; + return ( + + + + + + + + + + + + ); +}; export default memo(forwardRef(FiltersIcon)); diff --git a/packages/client/src/pages/authorized/admin/Users/index.tsx b/packages/client/src/pages/authorized/admin/Users/index.tsx index 1e1712e7..6c5b3674 100644 --- a/packages/client/src/pages/authorized/admin/Users/index.tsx +++ b/packages/client/src/pages/authorized/admin/Users/index.tsx @@ -1,18 +1,20 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; -import { CardList, DateField, FunctionField, List, TextField } from '../../../../elements'; +import { CardList, DateField, DetailedList, FunctionField, TextField } from '../../../../elements'; import { useMediaQuery } from '../../../../hooks'; import { ListContainer, Wrapper } from './Users.styled'; const Users = () => { const isMobile = useMediaQuery(({ breakpoints, down }) => down(breakpoints.lg)); + const { t } = useTranslation(); return ( {isMobile ? ( `${firstName} ${lastName}`} /> @@ -21,14 +23,14 @@ const Users = () => { ) : ( - + `${firstName} ${lastName}`} /> - + )}