2020-08-30 20:45:17 +03:00
|
|
|
import { faCaretDown as caretDownIcon, faCaretUp as caretUpIcon } from '@fortawesome/free-solid-svg-icons';
|
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
|
|
import { head, isEmpty, keys, values } from 'ramda';
|
|
|
|
import React, { useState, useEffect, FC } from 'react';
|
|
|
|
import qs from 'qs';
|
2020-09-04 20:05:41 +03:00
|
|
|
import { RouteComponentProps } from 'react-router';
|
2020-08-30 20:45:17 +03:00
|
|
|
import SortingDropdown from '../utils/SortingDropdown';
|
|
|
|
import { determineOrderDir, OrderDir } from '../utils/utils';
|
|
|
|
import { SelectedServer } from '../servers/data';
|
2020-09-06 20:41:15 +03:00
|
|
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
2020-08-30 20:45:17 +03:00
|
|
|
import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList';
|
|
|
|
import { ShortUrlsRowProps } from './helpers/ShortUrlsRow';
|
|
|
|
import { ShortUrl } from './data';
|
|
|
|
import { ShortUrlsListParams } from './reducers/shortUrlsListParams';
|
|
|
|
import './ShortUrlsList.scss';
|
|
|
|
|
|
|
|
export const SORTABLE_FIELDS = {
|
|
|
|
dateCreated: 'Created at',
|
|
|
|
shortCode: 'Short URL',
|
|
|
|
longUrl: 'Long URL',
|
|
|
|
visits: 'Visits',
|
|
|
|
};
|
|
|
|
type OrderableFields = keyof typeof SORTABLE_FIELDS;
|
|
|
|
|
|
|
|
interface RouteParams {
|
|
|
|
page: string;
|
|
|
|
serverId: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface WithList {
|
|
|
|
shortUrlsList: ShortUrl[];
|
|
|
|
}
|
|
|
|
|
2020-09-06 20:41:15 +03:00
|
|
|
export interface ShortUrlsListProps extends ShortUrlsListState, RouteComponentProps<RouteParams> {
|
2020-08-30 20:45:17 +03:00
|
|
|
selectedServer: SelectedServer;
|
|
|
|
listShortUrls: (params: ShortUrlsListParams) => void;
|
|
|
|
shortUrlsListParams: ShortUrlsListParams;
|
|
|
|
resetShortUrlParams: () => void;
|
|
|
|
}
|
|
|
|
|
2020-09-06 20:41:15 +03:00
|
|
|
const ShortUrlsList = (ShortUrlsRow: FC<ShortUrlsRowProps>) => boundToMercureHub(({
|
2020-08-30 20:45:17 +03:00
|
|
|
listShortUrls,
|
|
|
|
resetShortUrlParams,
|
|
|
|
shortUrlsListParams,
|
|
|
|
match,
|
|
|
|
location,
|
|
|
|
loading,
|
|
|
|
error,
|
|
|
|
shortUrlsList,
|
|
|
|
selectedServer,
|
|
|
|
}: ShortUrlsListProps & WithList) => {
|
|
|
|
const { orderBy } = shortUrlsListParams;
|
|
|
|
const [ order, setOrder ] = useState<{ orderField?: OrderableFields; orderDir?: OrderDir }>({
|
|
|
|
orderField: orderBy && (head(keys(orderBy)) as OrderableFields),
|
|
|
|
orderDir: orderBy && head(values(orderBy)),
|
|
|
|
});
|
|
|
|
const refreshList = (extraParams: ShortUrlsListParams) => listShortUrls({ ...shortUrlsListParams, ...extraParams });
|
2020-08-31 19:38:27 +03:00
|
|
|
const handleOrderBy = (orderField?: OrderableFields, orderDir?: OrderDir) => {
|
2020-08-30 20:45:17 +03:00
|
|
|
setOrder({ orderField, orderDir });
|
2020-08-31 19:38:27 +03:00
|
|
|
refreshList({ orderBy: orderField ? { [orderField]: orderDir } : undefined });
|
2020-08-30 20:45:17 +03:00
|
|
|
};
|
|
|
|
const orderByColumn = (field: OrderableFields) => () =>
|
|
|
|
handleOrderBy(field, determineOrderDir(field, order.orderField, order.orderDir));
|
|
|
|
const renderOrderIcon = (field: OrderableFields) => {
|
|
|
|
if (order.orderField !== field) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!order.orderDir) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<FontAwesomeIcon
|
|
|
|
icon={order.orderDir === 'ASC' ? caretUpIcon : caretDownIcon}
|
|
|
|
className="short-urls-list__header-icon"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
const renderShortUrls = () => {
|
|
|
|
if (error) {
|
|
|
|
return (
|
|
|
|
<tr>
|
|
|
|
<td colSpan={6} className="text-center table-danger">Something went wrong while loading short URLs :(</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
return <tr><td colSpan={6} className="text-center">Loading...</td></tr>;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!loading && isEmpty(shortUrlsList)) {
|
|
|
|
return <tr><td colSpan={6} className="text-center">No results found</td></tr>;
|
|
|
|
}
|
|
|
|
|
|
|
|
return shortUrlsList.map((shortUrl) => (
|
|
|
|
<ShortUrlsRow
|
|
|
|
key={shortUrl.shortUrl}
|
|
|
|
shortUrl={shortUrl}
|
|
|
|
selectedServer={selectedServer}
|
|
|
|
refreshList={refreshList}
|
|
|
|
shortUrlsListParams={shortUrlsListParams}
|
|
|
|
/>
|
|
|
|
));
|
|
|
|
};
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const query = qs.parse(location.search, { ignoreQueryPrefix: true });
|
|
|
|
const tags = query.tag ? [ query.tag as string ] : shortUrlsListParams.tags;
|
|
|
|
|
2020-09-04 20:05:41 +03:00
|
|
|
refreshList({ page: match.params.page, tags });
|
2020-08-30 20:45:17 +03:00
|
|
|
|
|
|
|
return resetShortUrlParams;
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<React.Fragment>
|
|
|
|
<div className="d-block d-md-none mb-3">
|
|
|
|
<SortingDropdown
|
|
|
|
items={SORTABLE_FIELDS}
|
|
|
|
orderField={order.orderField}
|
|
|
|
orderDir={order.orderDir}
|
|
|
|
onChange={handleOrderBy}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<table className="table table-striped table-hover">
|
|
|
|
<thead className="short-urls-list__header">
|
|
|
|
<tr>
|
|
|
|
<th
|
|
|
|
className="short-urls-list__header-cell short-urls-list__header-cell--with-action"
|
|
|
|
onClick={orderByColumn('dateCreated')}
|
|
|
|
>
|
|
|
|
{renderOrderIcon('dateCreated')}
|
|
|
|
Created at
|
|
|
|
</th>
|
|
|
|
<th
|
|
|
|
className="short-urls-list__header-cell short-urls-list__header-cell--with-action"
|
|
|
|
onClick={orderByColumn('shortCode')}
|
|
|
|
>
|
|
|
|
{renderOrderIcon('shortCode')}
|
|
|
|
Short URL
|
|
|
|
</th>
|
|
|
|
<th
|
|
|
|
className="short-urls-list__header-cell short-urls-list__header-cell--with-action"
|
|
|
|
onClick={orderByColumn('longUrl')}
|
|
|
|
>
|
|
|
|
{renderOrderIcon('longUrl')}
|
|
|
|
Long URL
|
|
|
|
</th>
|
|
|
|
<th className="short-urls-list__header-cell">Tags</th>
|
|
|
|
<th
|
|
|
|
className="short-urls-list__header-cell short-urls-list__header-cell--with-action"
|
|
|
|
onClick={orderByColumn('visits')}
|
|
|
|
>
|
|
|
|
<span className="indivisible">{renderOrderIcon('visits')} Visits</span>
|
|
|
|
</th>
|
|
|
|
<th className="short-urls-list__header-cell"> </th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
{renderShortUrls()}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
2020-09-06 20:41:15 +03:00
|
|
|
}, () => 'https://shlink.io/new-visit');
|
2020-08-30 20:45:17 +03:00
|
|
|
|
|
|
|
export default ShortUrlsList;
|