mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 12:47:13 +01:00
Added download functionality.
This commit is contained in:
parent
0debe89d05
commit
7670ec4136
17 changed files with 216 additions and 5 deletions
|
|
@ -29,7 +29,7 @@ export function useReport(
|
|||
data.parameters = {
|
||||
...defaultParameters?.parameters,
|
||||
...data.parameters,
|
||||
dateRange: parseDateRange(dateRange.value),
|
||||
dateRange: dateRange ? parseDateRange(dateRange?.value) : {},
|
||||
};
|
||||
|
||||
setReport(data);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import Change from '@/assets/change.svg';
|
|||
import Clock from '@/assets/clock.svg';
|
||||
import Compare from '@/assets/compare.svg';
|
||||
import Dashboard from '@/assets/dashboard.svg';
|
||||
import Download from '@/assets/download.svg';
|
||||
import Export from '@/assets/export.svg';
|
||||
import Eye from '@/assets/eye.svg';
|
||||
import Gear from '@/assets/gear.svg';
|
||||
import Globe from '@/assets/globe.svg';
|
||||
|
|
@ -37,6 +39,8 @@ const icons = {
|
|||
Clock,
|
||||
Compare,
|
||||
Dashboard,
|
||||
Download,
|
||||
Export,
|
||||
Eye,
|
||||
Gear,
|
||||
Globe,
|
||||
|
|
|
|||
41
src/components/input/DownloadButton.tsx
Normal file
41
src/components/input/DownloadButton.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import Papa from 'papaparse';
|
||||
import { Button, Icon, TooltipPopup } from 'react-basics';
|
||||
import Icons from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function DownloadButton({
|
||||
filename = 'data',
|
||||
data,
|
||||
}: {
|
||||
filename?: string;
|
||||
data?: any;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleClick = async () => {
|
||||
downloadCsv(`${filename}.csv`, Papa.unparse(data));
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipPopup label={formatMessage(labels.download)} position="top">
|
||||
<Button variant="quiet" onClick={handleClick} disabled={!data}>
|
||||
<Icon>
|
||||
<Icons.Download />
|
||||
</Icon>
|
||||
</Button>
|
||||
</TooltipPopup>
|
||||
);
|
||||
}
|
||||
|
||||
function downloadCsv(filename: string, data: any) {
|
||||
const blob = new Blob([data], { type: 'text/csv' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
47
src/components/input/ExportButton.tsx
Normal file
47
src/components/input/ExportButton.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { useState } from 'react';
|
||||
import { Icon, TooltipPopup, LoadingButton } from 'react-basics';
|
||||
import Icons from '@/components/icons';
|
||||
import { useMessages, useApi } from '@/components/hooks';
|
||||
import { useFilterParams } from '@/components/hooks/useFilterParams';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
export function ExportButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const params = useFilterParams(websiteId);
|
||||
const searchParams = useSearchParams();
|
||||
const { get } = useApi();
|
||||
|
||||
const handleClick = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
const { zip } = await get(`/websites/${websiteId}/export`, { ...params, ...searchParams });
|
||||
|
||||
const binary = atob(zip);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
|
||||
const blob = new Blob([bytes], { type: 'application/zip' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'download.zip';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipPopup label={formatMessage(labels.download)} position="top">
|
||||
<LoadingButton variant="quiet" isLoading={isLoading} onClick={handleClick}>
|
||||
<Icon>
|
||||
<Icons.Download />
|
||||
</Icon>
|
||||
</LoadingButton>
|
||||
</TooltipPopup>
|
||||
);
|
||||
}
|
||||
|
|
@ -316,6 +316,7 @@ export const labels = defineMessages({
|
|||
other: { id: 'label.other', defaultMessage: 'Other' },
|
||||
chart: { id: 'label.chart', defaultMessage: 'Chart' },
|
||||
table: { id: 'label.table', defaultMessage: 'Table' },
|
||||
download: { id: 'label.download', defaultMessage: 'Download' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export function EventsTable({ onLabelClick, ...props }: EventsTableProps) {
|
|||
metric={formatMessage(labels.actions)}
|
||||
onDataLoad={handleDataLoad}
|
||||
renderLabel={renderLabel}
|
||||
allowDownload={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,13 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
import Icons from '@/components/icons';
|
||||
import ListTable, { ListTableProps } from './ListTable';
|
||||
import styles from './MetricsTable.module.css';
|
||||
import { DownloadButton } from '@/components/input/DownloadButton';
|
||||
|
||||
export interface MetricsTableProps extends ListTableProps {
|
||||
websiteId: string;
|
||||
|
|
@ -29,6 +30,7 @@ export interface MetricsTableProps extends ListTableProps {
|
|||
searchFormattedValues?: boolean;
|
||||
showMore?: boolean;
|
||||
params?: { [key: string]: any };
|
||||
allowDownload?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ export function MetricsTable({
|
|||
searchFormattedValues = false,
|
||||
showMore = true,
|
||||
params,
|
||||
allowDownload = true,
|
||||
children,
|
||||
...props
|
||||
}: MetricsTableProps) {
|
||||
|
|
@ -104,7 +107,10 @@ export function MetricsTable({
|
|||
autoFocus={true}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
<div className={styles.buttons}>
|
||||
{children}
|
||||
{allowDownload && <DownloadButton filename={type} data={filteredData} />}
|
||||
</div>
|
||||
</div>
|
||||
{data && !error && (
|
||||
<ListTable {...(props as ListTableProps)} data={filteredData} className={className} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue