mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added pagination to tags table
This commit is contained in:
parent
1da7119c5c
commit
12f6a132bd
7 changed files with 77 additions and 33 deletions
|
@ -5,6 +5,7 @@
|
|||
@import './common/react-tag-autocomplete.scss';
|
||||
@import './theme/theme';
|
||||
@import './utils/table/ResponsiveTable';
|
||||
@import './utils/StickyCardPaginator';
|
||||
|
||||
* {
|
||||
outline: none !important;
|
||||
|
|
|
@ -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 (
|
||||
<Pagination className="short-urls-paginator" listClassName="flex-wrap justify-content-center mb-0">
|
||||
<Pagination className="sticky-card-paginator" listClassName="flex-wrap justify-content-center mb-0">
|
||||
<PaginationItem disabled={currentPage === 1}>
|
||||
<PaginationLink
|
||||
previous
|
||||
|
|
|
@ -13,10 +13,10 @@ interface TagsModeDropdownProps {
|
|||
|
||||
export const TagsModeDropdown: FC<TagsModeDropdownProps> = ({ mode, onChange, renderTitle }) => (
|
||||
<DropdownBtn text={renderTitle?.(mode) ?? `Display mode: ${mode}`}>
|
||||
<DropdownItem outline active={mode === 'cards'} onClick={() => onChange('cards')}>
|
||||
<DropdownItem active={mode === 'cards'} onClick={() => onChange('cards')}>
|
||||
<FontAwesomeIcon icon={cardsIcon} fixedWidth className="mr-1" /> Cards
|
||||
</DropdownItem>
|
||||
<DropdownItem outline active={mode === 'list'} onClick={() => onChange('list')}>
|
||||
<DropdownItem active={mode === 'list'} onClick={() => onChange('list')}>
|
||||
<FontAwesomeIcon icon={listIcon} fixedWidth className="mr-1" /> List
|
||||
</DropdownItem>
|
||||
</DropdownBtn>
|
||||
|
|
|
@ -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<TagsTableRowProps>) => (
|
||||
{ tagsList, selectedServer }: TagsListChildrenProps,
|
||||
) => (
|
||||
<SimpleCard>
|
||||
<table className="table table-hover mb-0">
|
||||
<thead className="responsive-table__header">
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<th className="text-lg-right">Short URLs</th>
|
||||
<th className="text-lg-right">Visits</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tagsList.filteredTags.length === 0 && <tr><td colSpan={4} className="text-center">No results found</td></tr>}
|
||||
{tagsList.filteredTags.map((tag) => (
|
||||
<TagsTableRow
|
||||
key={tag}
|
||||
tag={tag}
|
||||
tagStats={tagsList.stats[tag]}
|
||||
selectedServer={selectedServer}
|
||||
colorGenerator={colorGenerator}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</SimpleCard>
|
||||
);
|
||||
{ tagsList, selectedServer, location }: TagsListChildrenProps & RouteChildrenProps,
|
||||
) => {
|
||||
const { page: pageFromQuery = 1 } = parseQuery<{ page?: number | string }>(location.search);
|
||||
const [ page, setPage ] = useQueryState<number>('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 (
|
||||
<SimpleCard key={page} bodyClassName={showPaginator ? 'pb-1' : ''}>
|
||||
<table className="table table-hover mb-0">
|
||||
<thead className="responsive-table__header">
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<th className="text-lg-right">Short URLs</th>
|
||||
<th className="text-lg-right">Visits</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{currentPage.length === 0 && <tr><td colSpan={4} className="text-center">No results found</td></tr>}
|
||||
{currentPage.map((tag) => (
|
||||
<TagsTableRow
|
||||
key={tag}
|
||||
tag={tag}
|
||||
tagStats={tagsList.stats[tag]}
|
||||
selectedServer={selectedServer}
|
||||
colorGenerator={colorGenerator}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{showPaginator && (
|
||||
<div className="sticky-card-paginator">
|
||||
<SimplePaginator pagesCount={pages.length} currentPage={page} setCurrentPage={setPage} />
|
||||
</div>
|
||||
)}
|
||||
</SimpleCard>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.short-urls-paginator {
|
||||
.sticky-card-paginator {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background-color: var(--primary-color-alfa);
|
|
@ -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 = <T>(paramName: string, initialState: T): [ T, (newValue: T) => void ] => {
|
||||
const [ value, setValue ] = useState(initialState);
|
||||
const setValueWithLocation = (value: T) => {
|
||||
const { location, history } = window;
|
||||
const query = parseQuery<any>(location.search);
|
||||
|
||||
query[paramName] = value;
|
||||
history.pushState(null, '', `${location.pathname}?${stringifyQuery(query)}`);
|
||||
setValue(value);
|
||||
};
|
||||
|
||||
return [ value, setValueWithLocation ];
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue