mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added support for tag mode on short URLs list
This commit is contained in:
parent
1011b062ae
commit
2de0276195
7 changed files with 67 additions and 8 deletions
|
@ -86,6 +86,8 @@ export interface ShlinkDomainsResponse {
|
|||
defaultRedirects?: ShlinkDomainRedirects; // Optional only for Shlink older than 2.10
|
||||
}
|
||||
|
||||
export type TagsFilteringMode = 'all' | 'any';
|
||||
|
||||
export interface ShlinkShortUrlsListParams {
|
||||
page?: string;
|
||||
itemsPerPage?: number;
|
||||
|
@ -94,6 +96,7 @@ export interface ShlinkShortUrlsListParams {
|
|||
startDate?: string;
|
||||
endDate?: string;
|
||||
orderBy?: ShortUrlsOrder;
|
||||
tagsMode?: TagsFilteringMode;
|
||||
}
|
||||
|
||||
export interface ShlinkShortUrlsListNormalizedParams extends Omit<ShlinkShortUrlsListParams, 'orderBy'> {
|
||||
|
|
|
@ -9,15 +9,22 @@ import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
|||
import { formatIsoDate } from '../utils/helpers/date';
|
||||
import ColorGenerator from '../utils/services/ColorGenerator';
|
||||
import { DateRange } from '../utils/dates/types';
|
||||
import { supportsAllTagsFiltering } from '../utils/helpers/features';
|
||||
import { SelectedServer } from '../servers/data';
|
||||
import { TooltipToggleSwitch } from '../utils/TooltipToggleSwitch';
|
||||
import { ShortUrlListRouteParams, useShortUrlsQuery } from './helpers/hooks';
|
||||
import './ShortUrlsFilteringBar.scss';
|
||||
|
||||
export type ShortUrlsFilteringProps = RouteChildrenProps<ShortUrlListRouteParams>;
|
||||
export type ShortUrlsFilteringProps = RouteChildrenProps<ShortUrlListRouteParams> & {
|
||||
selectedServer: SelectedServer;
|
||||
};
|
||||
|
||||
const dateOrNull = (date?: string) => date ? parseISO(date) : null;
|
||||
|
||||
const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (props: ShortUrlsFilteringProps) => {
|
||||
const [{ search, tags, startDate, endDate }, toFirstPage ] = useShortUrlsQuery(props);
|
||||
const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (
|
||||
{ selectedServer, ...rest }: ShortUrlsFilteringProps,
|
||||
) => {
|
||||
const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage ] = useShortUrlsQuery(rest);
|
||||
const selectedTags = tags?.split(',') ?? [];
|
||||
const setDates = pipe(
|
||||
({ startDate, endDate }: DateRange) => ({
|
||||
|
@ -35,6 +42,11 @@ const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (props: ShortU
|
|||
(tagsList) => tagsList.length === 0 ? undefined : tagsList.join(','),
|
||||
(tags) => toFirstPage({ tags }),
|
||||
);
|
||||
const canChangeTagsMode = supportsAllTagsFiltering(selectedServer);
|
||||
const toggleTagsMode = pipe(
|
||||
() => tagsMode === 'any' ? 'all' : 'any',
|
||||
(tagsMode) => toFirstPage({ tagsMode }),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="short-urls-filtering-bar-container">
|
||||
|
@ -56,9 +68,20 @@ const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => (props: ShortU
|
|||
</div>
|
||||
|
||||
{selectedTags.length > 0 && (
|
||||
<h4 className="short-urls-filtering-bar__selected-tag mt-3">
|
||||
<FontAwesomeIcon icon={tagsIcon} className="short-urls-filtering-bar__tags-icon" />
|
||||
|
||||
<h4 className="mt-3">
|
||||
{canChangeTagsMode && selectedTags.length > 1 && (
|
||||
<div className="float-right ml-2 mt-1">
|
||||
<TooltipToggleSwitch
|
||||
checked={tagsMode === 'all'}
|
||||
tooltip={{ placement: 'left' }}
|
||||
onChange={toggleTagsMode}
|
||||
>
|
||||
{tagsMode === 'all' && 'Short URLs including all tags.'}
|
||||
{tagsMode !== 'all' && 'Short URLs including any tag.'}
|
||||
</TooltipToggleSwitch>
|
||||
</div>
|
||||
)}
|
||||
<FontAwesomeIcon icon={tagsIcon} className="short-urls-filtering-bar__tags-icon mr-1" />
|
||||
{selectedTags.map((tag) =>
|
||||
<Tag colorGenerator={colorGenerator} key={tag} text={tag} clearable onClose={() => removeTag(tag)} />)}
|
||||
</h4>
|
||||
|
|
|
@ -33,7 +33,10 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, ShortUrlsFilteri
|
|||
settings,
|
||||
}: ShortUrlsListProps) => {
|
||||
const serverId = getServerId(selectedServer);
|
||||
const [{ tags, search, startDate, endDate, orderBy }, toFirstPage ] = useShortUrlsQuery({ history, match, location });
|
||||
const [
|
||||
{ tags, search, startDate, endDate, orderBy, tagsMode },
|
||||
toFirstPage,
|
||||
] = useShortUrlsQuery({ history, match, location });
|
||||
const [ actualOrderBy, setActualOrderBy ] = useState(
|
||||
// This separated state handling is needed to be able to fall back to settings value, but only once when loaded
|
||||
orderBy ?? settings.shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING,
|
||||
|
@ -61,8 +64,9 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, ShortUrlsFilteri
|
|||
startDate,
|
||||
endDate,
|
||||
orderBy: actualOrderBy,
|
||||
tagsMode,
|
||||
});
|
||||
}, [ match.params.page, search, selectedTags, startDate, endDate, actualOrderBy ]);
|
||||
}, [ match.params.page, search, selectedTags, startDate, endDate, actualOrderBy, tagsMode ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { isEmpty, pipe } from 'ramda';
|
|||
import { parseQuery, stringifyQuery } from '../../utils/helpers/query';
|
||||
import { ShortUrlsOrder, ShortUrlsOrderableFields } from '../data';
|
||||
import { orderToString, stringToOrder } from '../../utils/helpers/ordering';
|
||||
import { TagsFilteringMode } from '../../api/types';
|
||||
|
||||
type ServerIdRouteProps = RouteChildrenProps<{ serverId: string }>;
|
||||
type ToFirstPage = (extra: Partial<ShortUrlsFiltering>) => void;
|
||||
|
@ -18,6 +19,7 @@ interface ShortUrlsQueryCommon {
|
|||
search?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
tagsMode?: TagsFilteringMode;
|
||||
}
|
||||
|
||||
interface ShortUrlsQuery extends ShortUrlsQueryCommon {
|
||||
|
|
|
@ -51,6 +51,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
|||
|
||||
// Services
|
||||
bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator');
|
||||
bottle.decorator('ShortUrlsFilteringBar', connect([ 'selectedServer' ]));
|
||||
bottle.decorator('ShortUrlsFilteringBar', withRouter);
|
||||
|
||||
// Actions
|
||||
|
|
24
src/utils/TooltipToggleSwitch.tsx
Normal file
24
src/utils/TooltipToggleSwitch.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { FC, useRef } from 'react';
|
||||
import { UncontrolledTooltip } from 'reactstrap';
|
||||
import { UncontrolledTooltipProps } from 'reactstrap/lib/Tooltip';
|
||||
import { BooleanControlProps } from './BooleanControl';
|
||||
import ToggleSwitch from './ToggleSwitch';
|
||||
|
||||
export const TooltipToggleSwitch: FC<BooleanControlProps & { tooltip?: Omit<UncontrolledTooltipProps, 'target'> }> = (
|
||||
{ children, tooltip = {}, ...rest },
|
||||
) => {
|
||||
const ref = useRef<HTMLSpanElement>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
ref={(el) => {
|
||||
ref.current = el ?? undefined;
|
||||
}}
|
||||
>
|
||||
<ToggleSwitch {...rest} />
|
||||
</span>
|
||||
<UncontrolledTooltip target={(() => ref.current) as any} {...tooltip}>{children}</UncontrolledTooltip>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -25,3 +25,5 @@ export const supportsDomainRedirects = supportsQrErrorCorrection;
|
|||
export const supportsForwardQuery = serverMatchesVersions({ minVersion: '2.9.0' });
|
||||
|
||||
export const supportsDefaultDomainRedirectsEdition = serverMatchesVersions({ minVersion: '2.10.0' });
|
||||
|
||||
export const supportsAllTagsFiltering = serverMatchesVersions({ minVersion: '3.0.0' });
|
||||
|
|
Loading…
Reference in a new issue