mirror of
https://github.com/umami-software/umami.git
synced 2026-02-08 06:37:18 +01:00
Retention report UI updates.
This commit is contained in:
parent
9b8fa08d82
commit
2c8996b68f
12 changed files with 110 additions and 93 deletions
|
|
@ -1,29 +1,11 @@
|
|||
import { createPortal } from 'react-dom';
|
||||
import { useDocumentClick, useKeyDown } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import styles from './PopupForm.module.css';
|
||||
|
||||
export function PopupForm({ element, className, children, onClose }) {
|
||||
const { right, top } = element.getBoundingClientRect();
|
||||
const style = { position: 'absolute', left: right, top };
|
||||
|
||||
useKeyDown('Escape', onClose);
|
||||
|
||||
useDocumentClick(e => {
|
||||
if (e.target !== element && !element?.parentElement?.contains(e.target)) {
|
||||
onClose();
|
||||
}
|
||||
});
|
||||
|
||||
const handleClick = e => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div className={classNames(styles.form, className)} style={style} onClick={handleClick}>
|
||||
export function PopupForm({ className, style, children }) {
|
||||
return (
|
||||
<div className={classNames(styles.form, className)} style={style}>
|
||||
{children}
|
||||
</div>,
|
||||
document.body,
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
background: var(--base50);
|
||||
min-width: 300px;
|
||||
padding: 20px;
|
||||
margin-left: 30px;
|
||||
border: 1px solid var(--base400);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,10 +80,10 @@ export function InsightsParameters() {
|
|||
<Icons.Plus />
|
||||
</Icon>
|
||||
</TooltipPopup>
|
||||
<Popup position="bottom" alignment="start">
|
||||
{(close, element) => {
|
||||
<Popup position="bottom" alignment="start" className={styles.popup}>
|
||||
{close => {
|
||||
return (
|
||||
<PopupForm element={element} onClose={close}>
|
||||
<PopupForm onClose={close}>
|
||||
{id === 'fields' && (
|
||||
<FieldSelectForm
|
||||
items={fieldOptions}
|
||||
|
|
@ -114,7 +114,7 @@ export function InsightsParameters() {
|
|||
return (
|
||||
<FormRow key={label} label={label} action={<AddButton id={id} onAdd={handleAdd} />}>
|
||||
<ParameterList items={parameterData[id]} onRemove={index => handleRemove(id, index)}>
|
||||
{({ name, filter, value, label }) => {
|
||||
{({ name, filter, value }) => {
|
||||
return (
|
||||
<div className={styles.parameter}>
|
||||
{id === 'fields' && (
|
||||
|
|
|
|||
|
|
@ -10,3 +10,8 @@
|
|||
.op {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.popup {
|
||||
margin-top: -10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function RetentionParameters() {
|
|||
return (
|
||||
<Form ref={ref} values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||
<BaseParameters showDateSelect={false} />
|
||||
<FormRow label={formatMessage(labels.dateRange)}>
|
||||
<FormRow label={formatMessage(labels.date)}>
|
||||
<MonthSelect date={startDate} onChange={handleDateChange} />
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@ import ReportMenu from '../ReportMenu';
|
|||
import ReportBody from '../ReportBody';
|
||||
import Magnet from 'assets/magnet.svg';
|
||||
import { REPORT_TYPES } from 'lib/constants';
|
||||
import { parseDateRange } from 'lib/date';
|
||||
import { endOfMonth, startOfMonth } from 'date-fns';
|
||||
|
||||
const defaultParameters = {
|
||||
type: REPORT_TYPES.retention,
|
||||
parameters: {},
|
||||
parameters: {
|
||||
dateRange: parseDateRange(
|
||||
`range:${startOfMonth(new Date()).getTime()}:${endOfMonth(new Date()).getTime()}`,
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export default function RetentionReport({ reportId }) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { useContext } from 'react';
|
||||
import { GridTable, GridColumn } from 'react-basics';
|
||||
import classNames from 'classnames';
|
||||
import { ReportContext } from '../Report';
|
||||
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
|
||||
|
|
@ -16,14 +15,26 @@ export function RetentionTable() {
|
|||
return <EmptyPlaceholder />;
|
||||
}
|
||||
|
||||
const rows = data.reduce((arr, { date, visitors }) => {
|
||||
if (!arr.find(a => a.date === date)) {
|
||||
return arr.concat({ date, visitors });
|
||||
const days = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
|
||||
|
||||
const rows = data.reduce((arr, row) => {
|
||||
const { date, visitors, day } = row;
|
||||
if (day === 0) {
|
||||
return arr.concat({
|
||||
date,
|
||||
visitors,
|
||||
records: days
|
||||
.reduce((arr, day) => {
|
||||
arr[day] = data.find(x => x.date === date && x.day === day);
|
||||
return arr;
|
||||
}, [])
|
||||
.filter(n => n),
|
||||
});
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
const days = [1, 2, 3, 4, 5, 6, 7, 14, 21, 30];
|
||||
const totalDays = rows.length;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -37,15 +48,22 @@ export function RetentionTable() {
|
|||
</div>
|
||||
))}
|
||||
</div>
|
||||
{rows.map(({ date, visitors }, i) => {
|
||||
{rows.map(({ date, visitors, records }, rowIndex) => {
|
||||
return (
|
||||
<div key={i} className={styles.row}>
|
||||
<div key={rowIndex} className={styles.row}>
|
||||
<div className={styles.date}>{formatDate(`${date} 00:00:00`, 'PP')}</div>
|
||||
<div className={styles.visitors}>{visitors}</div>
|
||||
{days.map((n, day) => {
|
||||
{days.map(day => {
|
||||
if (totalDays - rowIndex < day) {
|
||||
return null;
|
||||
}
|
||||
const percentage = records[day]?.percentage;
|
||||
return (
|
||||
<div key={day} className={styles.cell}>
|
||||
{data.find(row => row.date === date && row.day === day)?.percentage.toFixed(2)}
|
||||
<div
|
||||
key={day}
|
||||
className={classNames(styles.cell, { [styles.empty]: !percentage })}
|
||||
>
|
||||
{percentage ? `${percentage.toFixed(2)}%` : ''}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
@ -53,31 +71,8 @@ export function RetentionTable() {
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
<DataTable data={data} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DataTable({ data }) {
|
||||
return (
|
||||
<GridTable data={data || []}>
|
||||
<GridColumn name="date" label={'Date'}>
|
||||
{row => row.date}
|
||||
</GridColumn>
|
||||
<GridColumn name="day" label={'Day'}>
|
||||
{row => row.day}
|
||||
</GridColumn>
|
||||
<GridColumn name="visitors" label={'Visitors'}>
|
||||
{row => row.visitors}
|
||||
</GridColumn>
|
||||
<GridColumn name="returnVisitors" label={'Return Visitors'}>
|
||||
{row => row.returnVisitors}
|
||||
</GridColumn>
|
||||
<GridColumn name="percentage" label={'Percentage'}>
|
||||
{row => row.percentage}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
);
|
||||
}
|
||||
|
||||
export default RetentionTable;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: var(--blue100);
|
||||
background: var(--blue200);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
|
|
@ -46,3 +46,7 @@
|
|||
font-size: var(--font-size-sm);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.empty {
|
||||
background: var(--blue100);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue