Created button to use when anything needs to be exported

This commit is contained in:
Alejandro Celaya 2022-03-12 20:51:30 +01:00
parent 187e26810d
commit 7fd360495b
7 changed files with 57 additions and 35 deletions

View file

@ -5,12 +5,12 @@ import { SimpleCard } from '../utils/SimpleCard';
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup'; import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings as ShortUrlsSettings } from './reducers/settings'; import { DEFAULT_SHORT_URLS_ORDERING, Settings, ShortUrlsListSettings as ShortUrlsSettings } from './reducers/settings';
interface ShortUrlsListProps { interface ShortUrlsListSettingsProps {
settings: Settings; settings: Settings;
setShortUrlsListSettings: (settings: ShortUrlsSettings) => void; setShortUrlsListSettings: (settings: ShortUrlsSettings) => void;
} }
export const ShortUrlsListSettings: FC<ShortUrlsListProps> = ( export const ShortUrlsListSettings: FC<ShortUrlsListSettingsProps> = (
{ settings: { shortUrlsList }, setShortUrlsListSettings }, { settings: { shortUrlsList }, setShortUrlsListSettings },
) => ( ) => (
<SimpleCard title="Short URLs list" className="h-100"> <SimpleCard title="Short URLs list" className="h-100">

View file

@ -1,7 +1,10 @@
import { FC } from 'react';
import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEmpty, pipe } from 'ramda'; import { isEmpty, pipe } from 'ramda';
import { parseISO } from 'date-fns'; import { parseISO } from 'date-fns';
import { Row } from 'reactstrap';
import classNames from 'classnames';
import SearchField from '../utils/SearchField'; import SearchField from '../utils/SearchField';
import Tag from '../tags/helpers/Tag'; import Tag from '../tags/helpers/Tag';
import { DateRangeSelector } from '../utils/dates/DateRangeSelector'; import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
@ -11,16 +14,20 @@ import { DateRange } from '../utils/dates/types';
import { supportsAllTagsFiltering } from '../utils/helpers/features'; import { supportsAllTagsFiltering } from '../utils/helpers/features';
import { SelectedServer } from '../servers/data'; import { SelectedServer } from '../servers/data';
import { TooltipToggleSwitch } from '../utils/TooltipToggleSwitch'; import { TooltipToggleSwitch } from '../utils/TooltipToggleSwitch';
import { ExportBtn } from '../utils/ExportBtn';
import { useShortUrlsQuery } from './helpers/hooks'; import { useShortUrlsQuery } from './helpers/hooks';
import './ShortUrlsFilteringBar.scss'; import './ShortUrlsFilteringBar.scss';
interface ShortUrlsFilteringProps { export interface ShortUrlsFilteringProps {
selectedServer: SelectedServer; selectedServer: SelectedServer;
className?: string;
} }
const dateOrNull = (date?: string) => date ? parseISO(date) : null; const dateOrNull = (date?: string) => date ? parseISO(date) : null;
const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => ({ selectedServer }: ShortUrlsFilteringProps) => { const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator): FC<ShortUrlsFilteringProps> => (
{ selectedServer, className },
) => {
const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage ] = useShortUrlsQuery(); const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage ] = useShortUrlsQuery();
const selectedTags = tags?.split(',') ?? []; const selectedTags = tags?.split(',') ?? [];
const setDates = pipe( const setDates = pipe(
@ -46,12 +53,14 @@ const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => ({ selectedSer
); );
return ( return (
<div className="short-urls-filtering-bar-container"> <div className={classNames('short-urls-filtering-bar-container', className)}>
<SearchField initialValue={search} onChange={setSearch} /> <SearchField initialValue={search} onChange={setSearch} />
<div className="mt-3"> <Row>
<div className="row"> <div className="col-lg-4 col-xl-6 mt-3">
<div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6"> <ExportBtn className="btn-md-block" amount={4} onClick={() => {}} />
</div>
<div className="col-lg-8 col-xl-6 mt-3">
<DateRangeSelector <DateRangeSelector
defaultText="All short URLs" defaultText="All short URLs"
initialDateRange={{ initialDateRange={{
@ -61,8 +70,7 @@ const ShortUrlsFilteringBar = (colorGenerator: ColorGenerator) => ({ selectedSer
onDatesChange={setDates} onDatesChange={setDates}
/> />
</div> </div>
</div> </Row>
</div>
{selectedTags.length > 0 && ( {selectedTags.length > 0 && (
<h4 className="mt-3"> <h4 className="mt-3">

View file

@ -15,6 +15,7 @@ import { ShortUrlsTableProps } from './ShortUrlsTable';
import Paginator from './Paginator'; import Paginator from './Paginator';
import { useShortUrlsQuery } from './helpers/hooks'; import { useShortUrlsQuery } from './helpers/hooks';
import { ShortUrlsOrderableFields, SHORT_URLS_ORDERABLE_FIELDS } from './data'; import { ShortUrlsOrderableFields, SHORT_URLS_ORDERABLE_FIELDS } from './data';
import { ShortUrlsFilteringProps } from './ShortUrlsFilteringBar';
interface ShortUrlsListProps { interface ShortUrlsListProps {
selectedServer: SelectedServer; selectedServer: SelectedServer;
@ -23,12 +24,10 @@ interface ShortUrlsListProps {
settings: Settings; settings: Settings;
} }
const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, ShortUrlsFilteringBar: FC) => boundToMercureHub(({ const ShortUrlsList = (
listShortUrls, ShortUrlsTable: FC<ShortUrlsTableProps>,
shortUrlsList, ShortUrlsFilteringBar: FC<ShortUrlsFilteringProps>,
selectedServer, ) => boundToMercureHub(({ listShortUrls, shortUrlsList, selectedServer, settings }: ShortUrlsListProps) => {
settings,
}: ShortUrlsListProps) => {
const serverId = getServerId(selectedServer); const serverId = getServerId(selectedServer);
const { page } = useParams(); const { page } = useParams();
const location = useLocation(); const location = useLocation();
@ -66,7 +65,7 @@ const ShortUrlsList = (ShortUrlsTable: FC<ShortUrlsTableProps>, ShortUrlsFilteri
return ( return (
<> <>
<div className="mb-3"><ShortUrlsFilteringBar /></div> <ShortUrlsFilteringBar selectedServer={selectedServer} className="mb-3" />
<div className="d-block d-lg-none mb-3"> <div className="d-block d-lg-none mb-3">
<OrderingDropdown items={SHORT_URLS_ORDERABLE_FIELDS} order={actualOrderBy} onChange={handleOrderBy} /> <OrderingDropdown items={SHORT_URLS_ORDERABLE_FIELDS} order={actualOrderBy} onChange={handleOrderBy} />
</div> </div>

View file

@ -50,7 +50,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.decorator('QrCodeModal', connect([ 'selectedServer' ])); bottle.decorator('QrCodeModal', connect([ 'selectedServer' ]));
bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator'); bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ColorGenerator');
bottle.decorator('ShortUrlsFilteringBar', connect([ 'selectedServer' ]));
// Actions // Actions
bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient'); bottle.serviceFactory('listShortUrls', listShortUrls, 'buildShlinkApiClient');

17
src/utils/ExportBtn.tsx Normal file
View file

@ -0,0 +1,17 @@
import { FC } from 'react';
import { Button } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFileDownload } from '@fortawesome/free-solid-svg-icons';
import { prettify } from './helpers/numbers';
interface ExportBtnProps {
onClick: () => void;
amount?: number;
className?: string;
}
export const ExportBtn: FC<ExportBtnProps> = ({ onClick, className, amount = 0 }) => (
<Button outline color="primary" className={className} onClick={onClick}>
<FontAwesomeIcon icon={faFileDownload} /> Export ({prettify(amount)})
</Button>
);

View file

@ -2,7 +2,7 @@ import { isEmpty, propEq, values } from 'ramda';
import { useState, useEffect, useMemo, FC, useRef } from 'react'; import { useState, useEffect, useMemo, FC, useRef } from 'react';
import { Button, Progress, Row } from 'reactstrap'; import { Button, Progress, Row } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCalendarAlt, faMapMarkedAlt, faList, faChartPie, faFileDownload } from '@fortawesome/free-solid-svg-icons'; import { faCalendarAlt, faMapMarkedAlt, faList, faChartPie } from '@fortawesome/free-solid-svg-icons';
import { IconDefinition } from '@fortawesome/fontawesome-common-types'; import { IconDefinition } from '@fortawesome/fontawesome-common-types';
import { Route, Routes, Navigate } from 'react-router-dom'; import { Route, Routes, Navigate } from 'react-router-dom';
import classNames from 'classnames'; import classNames from 'classnames';
@ -16,6 +16,7 @@ import { SelectedServer } from '../servers/data';
import { supportsBotVisits } from '../utils/helpers/features'; import { supportsBotVisits } from '../utils/helpers/features';
import { prettify } from '../utils/helpers/numbers'; import { prettify } from '../utils/helpers/numbers';
import { NavPillItem, NavPills } from '../utils/NavPills'; import { NavPillItem, NavPills } from '../utils/NavPills';
import { ExportBtn } from '../utils/ExportBtn';
import LineChartCard from './charts/LineChartCard'; import LineChartCard from './charts/LineChartCard';
import VisitsTable from './VisitsTable'; import VisitsTable from './VisitsTable';
import { NormalizedOrphanVisit, NormalizedVisit, VisitsFilter, VisitsInfo, VisitsParams } from './types'; import { NormalizedOrphanVisit, NormalizedVisit, VisitsFilter, VisitsInfo, VisitsParams } from './types';
@ -308,14 +309,11 @@ const VisitsStats: FC<VisitsStatsProps> = ({
> >
Clear selection {highlightedVisits.length > 0 && <>({prettify(highlightedVisits.length)})</>} Clear selection {highlightedVisits.length > 0 && <>({prettify(highlightedVisits.length)})</>}
</Button> </Button>
<Button <ExportBtn
outline
color="primary"
className="btn-md-block" className="btn-md-block"
amount={normalizedVisits.length}
onClick={() => exportCsv(normalizedVisits)} onClick={() => exportCsv(normalizedVisits)}
> />
<FontAwesomeIcon icon={faFileDownload} /> Export ({prettify(normalizedVisits.length)})
</Button>
</div> </div>
</div> </div>
)} )}

View file

@ -1,5 +1,5 @@
import { shallow, ShallowWrapper } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { Button, Progress } from 'reactstrap'; import { Progress } from 'reactstrap';
import { sum } from 'ramda'; import { sum } from 'ramda';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { Route } from 'react-router-dom'; import { Route } from 'react-router-dom';
@ -13,6 +13,7 @@ import { Settings } from '../../src/settings/reducers/settings';
import { SelectedServer } from '../../src/servers/data'; import { SelectedServer } from '../../src/servers/data';
import { SortableBarChartCard } from '../../src/visits/charts/SortableBarChartCard'; import { SortableBarChartCard } from '../../src/visits/charts/SortableBarChartCard';
import { DoughnutChartCard } from '../../src/visits/charts/DoughnutChartCard'; import { DoughnutChartCard } from '../../src/visits/charts/DoughnutChartCard';
import { ExportBtn } from '../../src/utils/ExportBtn';
describe('<VisitsStats />', () => { describe('<VisitsStats />', () => {
const visits = [ Mock.all<Visit>(), Mock.all<Visit>(), Mock.all<Visit>() ]; const visits = [ Mock.all<Visit>(), Mock.all<Visit>(), Mock.all<Visit>() ];
@ -106,7 +107,7 @@ describe('<VisitsStats />', () => {
it('exports CSV when export btn is clicked', () => { it('exports CSV when export btn is clicked', () => {
const wrapper = createComponent({ visits }); const wrapper = createComponent({ visits });
const exportBtn = wrapper.find(Button).last(); const exportBtn = wrapper.find(ExportBtn).last();
expect(exportBtn).toHaveLength(1); expect(exportBtn).toHaveLength(1);
exportBtn.simulate('click'); exportBtn.simulate('click');