mirror of
https://github.com/umami-software/umami.git
synced 2026-02-18 11:35:37 +01:00
Renamed (app) folder to (main).
This commit is contained in:
parent
5c15778c9b
commit
c990459238
167 changed files with 48 additions and 114 deletions
74
src/app/(main)/reports/funnel/FunnelChart.js
Normal file
74
src/app/(main)/reports/funnel/FunnelChart.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { Loading, StatusLight } from 'react-basics';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useTheme from 'components/hooks/useTheme';
|
||||
import BarChart from 'components/metrics/BarChart';
|
||||
import { formatLongNumber } from 'lib/format';
|
||||
import styles from './FunnelChart.module.css';
|
||||
import { ReportContext } from '../Report';
|
||||
|
||||
export function FunnelChart({ className, loading }) {
|
||||
const { report } = useContext(ReportContext);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const { parameters, data } = report || {};
|
||||
|
||||
const renderXLabel = useCallback(
|
||||
(label, index) => {
|
||||
return parameters.urls[index];
|
||||
},
|
||||
[parameters],
|
||||
);
|
||||
|
||||
const renderTooltipPopup = useCallback((setTooltipPopup, model) => {
|
||||
const { opacity, labelColors, dataPoints } = model.tooltip;
|
||||
|
||||
if (!dataPoints?.length || !opacity) {
|
||||
setTooltipPopup(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setTooltipPopup(
|
||||
<>
|
||||
<div>
|
||||
{formatLongNumber(dataPoints[0].raw.y)} {formatMessage(labels.visitors)}
|
||||
</div>
|
||||
<div>
|
||||
<StatusLight color={labelColors?.[0]?.backgroundColor}>
|
||||
{formatLongNumber(dataPoints[0].raw.z)}% {formatMessage(labels.dropoff)}
|
||||
</StatusLight>
|
||||
</div>
|
||||
</>,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const datasets = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: formatMessage(labels.uniqueVisitors),
|
||||
data: data,
|
||||
borderWidth: 1,
|
||||
...colors.chart.visitors,
|
||||
},
|
||||
];
|
||||
}, [data, colors, formatMessage, labels]);
|
||||
|
||||
if (loading) {
|
||||
return <Loading icon="dots" className={styles.loading} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BarChart
|
||||
className={className}
|
||||
datasets={datasets}
|
||||
unit="day"
|
||||
loading={loading}
|
||||
renderXLabel={renderXLabel}
|
||||
renderTooltipPopup={renderTooltipPopup}
|
||||
XAxisType="category"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunnelChart;
|
||||
3
src/app/(main)/reports/funnel/FunnelChart.module.css
Normal file
3
src/app/(main)/reports/funnel/FunnelChart.module.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.loading {
|
||||
height: 300px;
|
||||
}
|
||||
91
src/app/(main)/reports/funnel/FunnelParameters.js
Normal file
91
src/app/(main)/reports/funnel/FunnelParameters.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { useContext, useRef } from 'react';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import {
|
||||
Icon,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormInput,
|
||||
FormRow,
|
||||
PopupTrigger,
|
||||
Popup,
|
||||
SubmitButton,
|
||||
TextField,
|
||||
} from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import UrlAddForm from './UrlAddForm';
|
||||
import { ReportContext } from '../Report';
|
||||
import BaseParameters from '../BaseParameters';
|
||||
import ParameterList from '../ParameterList';
|
||||
import PopupForm from '../PopupForm';
|
||||
|
||||
export function FunnelParameters() {
|
||||
const { report, runReport, updateReport, isRunning } = useContext(ReportContext);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const ref = useRef(null);
|
||||
|
||||
const { parameters } = report || {};
|
||||
const { websiteId, dateRange, urls } = parameters || {};
|
||||
const queryDisabled = !websiteId || !dateRange || urls?.length < 2;
|
||||
|
||||
const handleSubmit = (data, e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (!queryDisabled) {
|
||||
runReport(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddUrl = url => {
|
||||
updateReport({ parameters: { urls: parameters.urls.concat(url) } });
|
||||
};
|
||||
|
||||
const handleRemoveUrl = (index, e) => {
|
||||
e.stopPropagation();
|
||||
const urls = [...parameters.urls];
|
||||
urls.splice(index, 1);
|
||||
updateReport({ parameters: { urls } });
|
||||
};
|
||||
|
||||
const AddUrlButton = () => {
|
||||
return (
|
||||
<PopupTrigger>
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Popup position="bottom" alignment="start">
|
||||
{(close, element) => {
|
||||
return (
|
||||
<PopupForm element={element} onClose={close}>
|
||||
<UrlAddForm onAdd={handleAddUrl} />
|
||||
</PopupForm>
|
||||
);
|
||||
}}
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||
<BaseParameters />
|
||||
<FormRow label={formatMessage(labels.window)}>
|
||||
<FormInput
|
||||
name="window"
|
||||
rules={{ required: formatMessage(labels.required), pattern: /[0-9]+/ }}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.urls)} action={<AddUrlButton />}>
|
||||
<ParameterList items={urls} onRemove={handleRemoveUrl} />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
|
||||
{formatMessage(labels.runQuery)}
|
||||
</SubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunnelParameters;
|
||||
30
src/app/(main)/reports/funnel/FunnelReport.js
Normal file
30
src/app/(main)/reports/funnel/FunnelReport.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
'use client';
|
||||
import FunnelChart from './FunnelChart';
|
||||
import FunnelTable from './FunnelTable';
|
||||
import FunnelParameters from './FunnelParameters';
|
||||
import Report from '../Report';
|
||||
import ReportHeader from '../ReportHeader';
|
||||
import ReportMenu from '../ReportMenu';
|
||||
import ReportBody from '../ReportBody';
|
||||
import Funnel from 'assets/funnel.svg';
|
||||
import { REPORT_TYPES } from 'lib/constants';
|
||||
|
||||
const defaultParameters = {
|
||||
type: REPORT_TYPES.funnel,
|
||||
parameters: { window: 60, urls: [] },
|
||||
};
|
||||
|
||||
export default function FunnelReport({ reportId }) {
|
||||
return (
|
||||
<Report reportId={reportId} defaultParameters={defaultParameters}>
|
||||
<ReportHeader icon={<Funnel />} />
|
||||
<ReportMenu>
|
||||
<FunnelParameters />
|
||||
</ReportMenu>
|
||||
<ReportBody>
|
||||
<FunnelChart />
|
||||
<FunnelTable />
|
||||
</ReportBody>
|
||||
</Report>
|
||||
);
|
||||
}
|
||||
10
src/app/(main)/reports/funnel/FunnelReport.module.css
Normal file
10
src/app/(main)/reports/funnel/FunnelReport.module.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
.filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--base400);
|
||||
border-radius: var(--border-radius);
|
||||
line-height: 32px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
19
src/app/(main)/reports/funnel/FunnelTable.js
Normal file
19
src/app/(main)/reports/funnel/FunnelTable.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useContext } from 'react';
|
||||
import ListTable from 'components/metrics/ListTable';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { ReportContext } from '../Report';
|
||||
|
||||
export function FunnelTable() {
|
||||
const { report } = useContext(ReportContext);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
return (
|
||||
<ListTable
|
||||
data={report?.data}
|
||||
title={formatMessage(labels.url)}
|
||||
metric={formatMessage(labels.visitors)}
|
||||
showPercentage={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunnelTable;
|
||||
47
src/app/(main)/reports/funnel/UrlAddForm.js
Normal file
47
src/app/(main)/reports/funnel/UrlAddForm.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { useState } from 'react';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { Button, Form, FormRow, TextField, Flexbox } from 'react-basics';
|
||||
import styles from './UrlAddForm.module.css';
|
||||
|
||||
export function UrlAddForm({ defaultValue = '', onAdd }) {
|
||||
const [url, setUrl] = useState(defaultValue);
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
const handleSave = () => {
|
||||
onAdd(url);
|
||||
setUrl('');
|
||||
};
|
||||
|
||||
const handleChange = e => {
|
||||
setUrl(e.target.value);
|
||||
};
|
||||
|
||||
const handleKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FormRow label={formatMessage(labels.url)}>
|
||||
<Flexbox gap={10}>
|
||||
<TextField
|
||||
className={styles.input}
|
||||
value={url}
|
||||
onChange={handleChange}
|
||||
autoFocus={true}
|
||||
autoComplete="off"
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
<Button variant="primary" onClick={handleSave}>
|
||||
{formatMessage(labels.add)}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default UrlAddForm;
|
||||
14
src/app/(main)/reports/funnel/UrlAddForm.module.css
Normal file
14
src/app/(main)/reports/funnel/UrlAddForm.module.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.form {
|
||||
position: absolute;
|
||||
background: var(--base50);
|
||||
width: 300px;
|
||||
padding: 30px;
|
||||
margin-top: 10px;
|
||||
border: 1px solid var(--base400);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
10
src/app/(main)/reports/funnel/page.tsx
Normal file
10
src/app/(main)/reports/funnel/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import FunnelReport from './FunnelReport';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export default function FunnelReportPage() {
|
||||
return <FunnelReport reportId={null} />;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Funnel Report | umami',
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue