diff --git a/src/index.scss b/src/index.scss index 5251b46e..419f1638 100644 --- a/src/index.scss +++ b/src/index.scss @@ -5,6 +5,7 @@ @import './common/react-tag-autocomplete.scss'; @import './theme/theme'; @import './utils/table/ResponsiveTable'; +@import './utils/StickyCardPaginator'; * { outline: none !important; diff --git a/src/short-urls/Paginator.tsx b/src/short-urls/Paginator.tsx index fff07aea..82aa54c1 100644 --- a/src/short-urls/Paginator.tsx +++ b/src/short-urls/Paginator.tsx @@ -2,7 +2,6 @@ import { Link } from 'react-router-dom'; import { Pagination, PaginationItem, PaginationLink } from 'reactstrap'; import { pageIsEllipsis, keyForPage, progressivePagination, prettifyPageNumber } from '../utils/helpers/pagination'; import { ShlinkPaginator } from '../api/types'; -import './Paginator.scss'; interface PaginatorProps { paginator?: ShlinkPaginator; @@ -33,7 +32,7 @@ const Paginator = ({ paginator, serverId }: PaginatorProps) => { )); return ( - + = ({ mode, onChange, renderTitle }) => ( - onChange('cards')}> + onChange('cards')}> Cards - onChange('list')}> + onChange('list')}> List diff --git a/src/tags/TagsTable.tsx b/src/tags/TagsTable.tsx index e6601274..2d00333c 100644 --- a/src/tags/TagsTable.tsx +++ b/src/tags/TagsTable.tsx @@ -1,34 +1,60 @@ -import { FC } from 'react'; +import { FC, useEffect } from 'react'; +import { splitEvery } from 'ramda'; +import { RouteChildrenProps } from 'react-router'; import { SimpleCard } from '../utils/SimpleCard'; import ColorGenerator from '../utils/services/ColorGenerator'; +import SimplePaginator from '../common/SimplePaginator'; +import { useQueryState } from '../utils/helpers/hooks'; +import { parseQuery } from '../utils/helpers/query'; import { TagsListChildrenProps } from './data/TagsListChildrenProps'; import { TagsTableRowProps } from './TagsTableRow'; +const TAGS_PER_PAGE = 20; // TODO Allow customizing this value in settings + export const TagsTable = (colorGenerator: ColorGenerator, TagsTableRow: FC) => ( - { tagsList, selectedServer }: TagsListChildrenProps, -) => ( - - - - - - - - - - - {tagsList.filteredTags.length === 0 && } - {tagsList.filteredTags.map((tag) => ( - - ))} - -
TagShort URLsVisits -
No results found
-
-); + { tagsList, selectedServer, location }: TagsListChildrenProps & RouteChildrenProps, +) => { + const { page: pageFromQuery = 1 } = parseQuery<{ page?: number | string }>(location.search); + const [ page, setPage ] = useQueryState('page', Number(pageFromQuery)); + const sortedTags = tagsList.filteredTags; + const pages = splitEvery(TAGS_PER_PAGE, sortedTags); + const showPaginator = pages.length > 1; + const currentPage = pages[page - 1] ?? []; + + useEffect(() => { + setPage(1); + }, [ tagsList.filteredTags ]); + + return ( + + + + + + + + + + + {currentPage.length === 0 && } + {currentPage.map((tag) => ( + + ))} + +
TagShort URLsVisits +
No results found
+ + {showPaginator && ( +
+ +
+ )} +
+ ); +}; diff --git a/src/tags/services/provideServices.ts b/src/tags/services/provideServices.ts index f17e856e..ac1753dc 100644 --- a/src/tags/services/provideServices.ts +++ b/src/tags/services/provideServices.ts @@ -1,4 +1,5 @@ import Bottle, { IContainer } from 'bottlejs'; +import { withRouter } from 'react-router-dom'; import TagsSelector from '../helpers/TagsSelector'; import TagCard from '../TagCard'; import DeleteTagConfirmModal from '../helpers/DeleteTagConfirmModal'; @@ -34,7 +35,9 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('TagsCards', TagsCards, 'TagCard'); bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal'); + bottle.serviceFactory('TagsTable', TagsTable, 'ColorGenerator', 'TagsTableRow'); + bottle.decorator('TagsTable', withRouter); bottle.serviceFactory('TagsList', TagsList, 'TagsCards', 'TagsTable'); bottle.decorator('TagsList', connect( diff --git a/src/short-urls/Paginator.scss b/src/utils/StickyCardPaginator.scss similarity index 85% rename from src/short-urls/Paginator.scss rename to src/utils/StickyCardPaginator.scss index b28b471a..61badd48 100644 --- a/src/short-urls/Paginator.scss +++ b/src/utils/StickyCardPaginator.scss @@ -1,4 +1,4 @@ -.short-urls-paginator { +.sticky-card-paginator { position: sticky; bottom: 0; background-color: var(--primary-color-alfa); diff --git a/src/utils/helpers/hooks.ts b/src/utils/helpers/hooks.ts index 89a50d89..6e9548ad 100644 --- a/src/utils/helpers/hooks.ts +++ b/src/utils/helpers/hooks.ts @@ -1,5 +1,6 @@ import { useState, useRef } from 'react'; import { useSwipeable as useReactSwipeable } from 'react-swipeable'; +import { parseQuery, stringifyQuery } from './query'; const DEFAULT_DELAY = 2000; @@ -51,3 +52,17 @@ export const useSwipeable = (showSidebar: () => void, hideSidebar: () => void) = onSwipedRight: swipeMenuIfNoModalExists(showSidebar), }); }; + +export const useQueryState = (paramName: string, initialState: T): [ T, (newValue: T) => void ] => { + const [ value, setValue ] = useState(initialState); + const setValueWithLocation = (value: T) => { + const { location, history } = window; + const query = parseQuery(location.search); + + query[paramName] = value; + history.pushState(null, '', `${location.pathname}?${stringifyQuery(query)}`); + setValue(value); + }; + + return [ value, setValueWithLocation ]; +};