ui: comparison designs

This commit is contained in:
nick-delirium 2024-11-28 11:51:13 +01:00
parent 19b5addc95
commit 29fec35046
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
14 changed files with 677 additions and 457 deletions

File diff suppressed because one or more lines are too long

View file

@ -4,4 +4,4 @@ enableGlobalCache: true
nodeLinker: pnpm
yarnPath: .yarn/releases/yarn-4.5.1.cjs
yarnPath: .yarn/releases/yarn-4.5.3.cjs

View file

@ -1,13 +1,38 @@
import React from "react";
import { formatTimeOrDate } from "App/date";
import React from 'react';
import { formatTimeOrDate } from 'App/date';
import cn from 'classnames';
import { ArrowUp, ArrowDown } from 'lucide-react'
function CustomTooltip({ active, payload, label }) {
if (!active) return;
const shownPayloads = payload.filter((p) => !p.hide);
const shownPayloads: Record<string, any>[] = payload.filter((p) => !p.hide);
const currentSeries: { value: number }[] = [];
const previousSeriesMap: Record<string, any> = {};
shownPayloads.forEach((item) => {
if (item.name.startsWith('Previous ')) {
const originalName = item.name.replace('Previous ', '');
previousSeriesMap[originalName] = item.value;
} else {
currentSeries.push(item);
}
});
const transformedArray = currentSeries.map((item) => {
const prevValue = previousSeriesMap[item.name] || null;
return {
...item,
prevValue,
};
});
const isHigher = (item: { value: number; prevValue: number }) => {
return item.prevValue !== null && item.prevValue < item.value;
};
return (
<div className={'flex flex-col gap-1 bg-white shadow border rounded p-2'}>
{shownPayloads.map((p, index) => (
<div
className={'flex flex-col gap-1 bg-white shadow border rounded p-2 z-30'}
>
{transformedArray.map((p, index) => (
<>
<div className={'flex gap-2 items-center'}>
<div
@ -25,7 +50,22 @@ function CustomTooltip({ active, payload, label }) {
<div className={'text-disabled-text text-sm'}>
{label}, {formatTimeOrDate(p.payload.timestamp)}
</div>
<div className={'font-semibold'}>{p.value}</div>
<div className={'flex items-center gap-2'}>
<div className={'font-semibold'}>{p.value}</div>
{p.prevValue !== null ? (
<div
className={cn(
'px-2 py-1 rounded flex items-center gap-1',
isHigher(p) ? 'bg-green2 text-xs' : 'bg-red2 text-xs'
)}
>
{!isHigher(p) ? <ArrowDown size={12} /> : <ArrowUp size={12} />}
<div>
{p.prevValue}
</div>
</div>
) : null}
</div>
</div>
</>
))}
@ -33,4 +73,4 @@ function CustomTooltip({ active, payload, label }) {
);
}
export default CustomTooltip;
export default CustomTooltip;

View file

@ -20,6 +20,7 @@ import cn from 'classnames';
interface Props {
data: any;
compData: any | null;
params: any;
colors: any;
onClick?: (event, index) => void;
@ -31,6 +32,7 @@ interface Props {
function CustomMetricLineChart(props: Props) {
const {
data = { chart: [], namesMap: [] },
compData,
params,
colors,
onClick = () => null,
@ -39,10 +41,14 @@ function CustomMetricLineChart(props: Props) {
hideLegend = false,
} = props;
const resultChart = data.chart.map((item, i) => {
if (compData && compData.chart[i]) return { ...compData.chart[i], ...item }
return item
})
return (
<ResponsiveContainer height={240} width="100%">
<LineChart
data={data.chart}
data={resultChart}
margin={Styles.chartMargins}
onClick={onClick}
>
@ -85,6 +91,25 @@ function CustomMetricLineChart(props: Props) {
}}
/>
) : null)}
{compData ? compData.namesMap.map((key, i) => (
<Line
key={key}
name={key}
animationDuration={0}
type="monotone"
dataKey={key}
stroke={colors[i]}
fillOpacity={1}
strokeWidth={2}
strokeOpacity={0.6}
legendType={'line'}
dot={false}
strokeDasharray={'4 3'}
activeDot={{
fill: colors[i],
}}
/>
)) : null}
</LineChart>
</ResponsiveContainer>
);

View file

@ -61,6 +61,7 @@ function WidgetChart(props: Props) {
const prevMetricRef = useRef<any>();
const isMounted = useIsMounted();
const [data, setData] = useState<any>(metric.data);
const [compData, setCompData] = useState<any>(null);
const [enabledRows, setEnabledRows] = useState([]);
const isTableWidget =
metric.metricType === 'table' && metric.viewType === 'table';
@ -121,14 +122,18 @@ function WidgetChart(props: Props) {
metric: any,
payload: any,
isSaved: any,
period: any
period: any,
isComparison?: boolean
) => {
if (!isMounted()) return;
setLoading(true);
dashboardStore
.fetchMetricChartData(metric, payload, isSaved, period)
.fetchMetricChartData(metric, payload, isSaved, period, isComparison)
.then((res: any) => {
if (isMounted()) setData(res);
if (isMounted()) {
if (isComparison) setCompData(res)
else setData(res);
}
})
.finally(() => {
setLoading(false);
@ -157,6 +162,17 @@ function WidgetChart(props: Props) {
!isSaved ? drillDownPeriod : period
);
};
const loadComparisonData = () => {
if (!inView) return;
if (!dashboardStore.comparisonPeriod) return setCompData(null);
const timestamps = dashboardStore.comparisonPeriod.toTimestamps();
const payload = { ...metricParams, ...timestamps, ...metric.toJson() };
fetchMetricChartData(metric, payload, isSaved, dashboardStore.comparisonPeriod, true);
}
useEffect(() => {
loadComparisonData();
}, [dashboardStore.comparisonPeriod])
useEffect(() => {
_metric.updateKey('page', 1);
loadPage();
@ -210,6 +226,7 @@ function WidgetChart(props: Props) {
return (
<CustomMetricLineChart
data={chartData}
compData={compData}
colors={colors}
params={params}
onClick={onChartClick}

View file

@ -95,13 +95,8 @@ function WidgetDatatable(props: Props) {
const rowSelection: TableProps['rowSelection'] = {
selectedRowKeys: props.enabledRows,
onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => {
onChange: (selectedRowKeys: React.Key[]) => {
props.setEnabledRows(selectedRowKeys as string[]);
console.log(
`selectedRowKeys: ${selectedRowKeys}`,
'selectedRows: ',
selectedRows
);
},
getCheckboxProps: (record: any) => ({
name: record.name,

View file

@ -4,23 +4,31 @@ import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { Space } from 'antd';
function WidgetDateRange({ label = 'Time Range', comparison = false }: any) {
function WidgetDateRange({ label = 'Time Range', isTimeseries = false }: any) {
const { dashboardStore } = useStore();
const period = comparison ? dashboardStore.comparisonPeriod : dashboardStore.drillDownPeriod;
const period = dashboardStore.drillDownPeriod;
const compPeriod = dashboardStore.comparisonPeriod;
const drillDownFilter = dashboardStore.drillDownFilter;
const onChangePeriod = (period: any) => {
if (comparison) dashboardStore.setComparisonPeriod(period);
else {
dashboardStore.setDrillDownPeriod(period);
const periodTimestamps = period.toTimestamps();
drillDownFilter.merge({
startTimestamp: periodTimestamps.startTimestamp,
endTimestamp: periodTimestamps.endTimestamp,
});
}
};
const onChangeComparison = (period: any) => {
dashboardStore.setComparisonPeriod(period);
const periodTimestamps = period.toTimestamps();
const compFilter = dashboardStore.cloneCompFilter();
compFilter.merge({
startTimestamp: periodTimestamps.startTimestamp,
endTimestamp: periodTimestamps.endTimestamp,
});
}
return (
<Space>
{label && <span className="mr-1 color-gray-medium">{label}</span>}
@ -30,8 +38,19 @@ function WidgetDateRange({ label = 'Time Range', comparison = false }: any) {
right={true}
isAnt={true}
useButtonStyle={true}
comparison={comparison}
/>
{isTimeseries ? (
<SelectDateRange
period={period}
compPeriod={compPeriod}
onChange={onChangePeriod}
onChangeComparison={onChangeComparison}
right={true}
isAnt={true}
useButtonStyle={true}
comparison={true}
/>
) : null}
</Space>
);
}

View file

@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite';
import React from 'react';
import WidgetDateRange from "Components/Dashboard/components/WidgetDateRange/WidgetDateRange";
import { useStore } from 'App/mstore';
import { TIMESERIES } from "../../../../constants/card";
import { TIMESERIES } from "App/constants/card";
import WidgetWrapper from '../WidgetWrapper';
import WidgetOptions from 'Components/Dashboard/components/WidgetOptions';
@ -26,8 +26,7 @@ function WidgetPreview(props: Props) {
className={cn(className, 'bg-white rounded-xl border shadow-sm mt-0')}
>
<div className="flex items-center gap-2 px-4 pt-2">
<WidgetDateRange label="" />
{metric.metricType === TIMESERIES ? <WidgetDateRange comparison label="" /> : null}
<WidgetDateRange label="" isTimeseries={metric.metricType === TIMESERIES} />
<div className="flex items-center ml-auto">
<WidgetOptions />
{/*{metric.metricType === USER_PATH && (*/}

View file

@ -5,8 +5,12 @@ import cn from 'classnames';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { components } from 'react-select';
import { CUSTOM_RANGE, DATE_RANGE_OPTIONS, DATE_RANGE_COMPARISON_OPTIONS } from 'App/dateRange';
import { Calendar } from 'lucide-react'
import {
CUSTOM_RANGE,
DATE_RANGE_OPTIONS,
DATE_RANGE_COMPARISON_OPTIONS,
} from 'App/dateRange';
import { Calendar } from 'lucide-react';
import DateRangePopup from 'Shared/DateRangeDropdown/DateRangePopup';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
import Select from 'Shared/Select';
@ -19,23 +23,44 @@ interface Props {
timezone?: string;
isAnt?: boolean;
small?: boolean;
useButtonStyle?: boolean; // New prop to control button style
useButtonStyle?: boolean; // New prop to control button style
compPeriod: any | null;
onChangeComparison: (data: any) => void;
comparison?: boolean;
[x: string]: any;
}
function SelectDateRange(props: Props) {
const [isCustom, setIsCustom] = React.useState(false);
const { right = false, period, disableCustom = false, timezone, useButtonStyle = false } = props;
const dateRangeOptions = props.comparison ? DATE_RANGE_COMPARISON_OPTIONS : DATE_RANGE_OPTIONS;
let selectedValue = period?.rangeName ? dateRangeOptions.find(
(obj: any) => obj.value === period?.rangeName
) : null;
const {
right = false,
period,
disableCustom = false,
timezone,
useButtonStyle = false,
} = props;
const usedPeriod = props.comparison ? props.compPeriod : period;
const dateRangeOptions = props.comparison
? DATE_RANGE_COMPARISON_OPTIONS
: DATE_RANGE_OPTIONS;
let selectedValue = usedPeriod?.rangeName
? dateRangeOptions.find((obj: any) => obj.value === usedPeriod?.rangeName)
: null;
const options = dateRangeOptions.filter((obj: any) =>
disableCustom ? obj.value !== CUSTOM_RANGE : true
);
const onChange = (value: any) => {
if (props.comparison) {
const newPeriod = new Period({
start: props.period.start,
end: props.period.end,
substract: value
});
props.onChangeComparison(newPeriod);
return;
}
if (value === CUSTOM_RANGE) {
setTimeout(() => {
setIsCustom(true);
@ -56,101 +81,27 @@ function SelectDateRange(props: Props) {
};
const isCustomRange = period ? period.rangeName === CUSTOM_RANGE : false;
const isUSLocale = navigator.language === 'en-US' || navigator.language.startsWith('en-US');
const customRange = isCustomRange ? period.rangeFormatted(isUSLocale ? "MMM dd yyyy, hh:mm a" : "MMM dd yyyy, HH:mm") : '';
const isUSLocale =
navigator.language === 'en-US' || navigator.language.startsWith('en-US');
const customRange = isCustomRange
? period.rangeFormatted(
isUSLocale ? 'MMM dd yyyy, hh:mm a' : 'MMM dd yyyy, HH:mm'
)
: '';
if (props.isAnt) {
const menuProps = {
items: options.map((opt) => ({
label: opt.label,
key: opt.value,
})),
selectedKeys: selectedValue?.value ? [selectedValue.value] : undefined,
onClick: (e: any) => {
onChange(e.key);
},
};
return (
<div className={'relative'}>
{props.comparison ? (
<div className={'flex items-center gap-0'}>
<Dropdown menu={menuProps} className={'px-2 py-1'}>
<div className={"cursor-pointer flex items-center gap-2 border-l border-t border-b border-gray-light rounded-l !border-r-0"}>
<span>
{isCustomRange
? customRange
: `Compare to ${selectedValue ? selectedValue?.label : ''}`}
</span>
<DownOutlined />
</div>
</Dropdown>
<div
className={"flex items-center justify-center border border-gray-light p-2 hover:border-main rounded-r"}
style={{ height: 30 }}
onClick={() => props.onChange(null)}
>
<CloseOutlined />
</div>
</div>
) : (
<Dropdown menu={menuProps} className={'px-2 py-1'}>
{useButtonStyle ? (
<div className={'flex items-center gap-2 border border-gray-light rounded cursor-pointer'}>
<Calendar size={16} />
<span>
{isCustomRange ? customRange : selectedValue?.label}
</span>
<DownOutlined />
</div>
) : (
<div className={'cursor-pointer flex items-center gap-2'}>
<span>
{isCustomRange ? customRange : selectedValue?.label}
</span>
<DownOutlined />
</div>
)}
</Dropdown>
)}
{isCustom && (
<OutsideClickDetectingDiv
onClickOutside={(e: any) => {
if (
e.target.className.includes('react-calendar') ||
e.target.parentElement.parentElement.classList.contains(
'rc-time-picker-panel-select'
) ||
e.target.parentElement.parentElement.classList[0]?.includes(
'-menu'
) ||
e.target.className.includes('ant-picker')
) {
return false;
}
setIsCustom(false);
}}
>
<div
className={cn('absolute top-0 mt-10 z-40', { 'right-0': right })}
style={{
width: isUSLocale ? '542px' : '500px',
fontSize: '14px',
textAlign: 'left',
}}
>
<DateRangePopup
timezone={timezone}
onApply={onApplyDateRange}
onCancel={() => setIsCustom(false)}
selectedDateRange={period.range}
className="h-fit"
/>
</div>
</OutsideClickDetectingDiv>
)}
</div>
);
return <AndDateRange
{...props}
options={options}
selectedValue={selectedValue}
onChange={onChange}
isCustomRange={isCustomRange}
customRange={customRange}
setIsCustom={setIsCustom}
onApplyDateRange={onApplyDateRange}
isUSLocale={isUSLocale}
useButtonStyle={useButtonStyle}
/>;
}
return (
@ -194,7 +145,7 @@ function SelectDateRange(props: Props) {
style={{
width: isUSLocale ? '542px' : '520px',
fontSize: '14px',
textAlign: 'left'
textAlign: 'left',
}}
>
<DateRangePopup
@ -208,6 +159,121 @@ function SelectDateRange(props: Props) {
)}
</div>
);
};
}
function AndDateRange({
options,
selectedValue,
onChange,
period,
timezone,
right = false,
useButtonStyle = false,
comparison = false,
isCustomRange,
customRange,
setIsCustom,
isCustom,
isUSLocale,
onApplyDateRange,
}: Props) {
const menuProps = {
items: options.map((opt) => ({
label: opt.label,
key: opt.value,
})),
selectedKeys: selectedValue?.value ? [selectedValue.value] : undefined,
onClick: (e: any) => {
onChange(e.key);
},
};
return (
<div className={'relative'}>
{comparison ? (
<div className={'flex items-center gap-0'}>
<Dropdown menu={menuProps} className={'px-2 py-1'}>
<div
className={
'cursor-pointer flex items-center gap-2 border-l border-t border-b border-gray-light rounded-l !border-r-0'
}
>
<span>
{isCustomRange
? customRange
: `Compare to ${selectedValue ? selectedValue?.label : ''}`}
</span>
<DownOutlined />
</div>
</Dropdown>
<div
className={
'flex items-center justify-center border border-gray-light p-2 hover:border-main rounded-r'
}
style={{ height: 30 }}
onClick={() => onChange(null)}
>
<CloseOutlined />
</div>
</div>
) : (
<Dropdown menu={menuProps} className={'px-2 py-1'}>
{useButtonStyle ? (
<div
className={
'flex items-center gap-2 border border-gray-light rounded cursor-pointer'
}
>
<Calendar size={16} />
<span>{isCustomRange ? customRange : selectedValue?.label}</span>
<DownOutlined />
</div>
) : (
<div className={'cursor-pointer flex items-center gap-2'}>
<span>{isCustomRange ? customRange : selectedValue?.label}</span>
<DownOutlined />
</div>
)}
</Dropdown>
)}
{isCustom && (
<OutsideClickDetectingDiv
onClickOutside={(e: any) => {
if (
e.target.className.includes('react-calendar') ||
e.target.parentElement.parentElement.classList.contains(
'rc-time-picker-panel-select'
) ||
e.target.parentElement.parentElement.classList[0]?.includes(
'-menu'
) ||
e.target.className.includes('ant-picker')
) {
return false;
}
setIsCustom(false);
}}
>
<div
className={cn('absolute top-0 mt-10 z-40', { 'right-0': right })}
style={{
width: isUSLocale ? '542px' : '500px',
fontSize: '14px',
textAlign: 'left',
}}
>
<DateRangePopup
timezone={timezone}
onApply={onApplyDateRange}
onCancel={() => setIsCustom(false)}
selectedDateRange={period.range}
className="h-fit"
/>
</div>
</OutsideClickDetectingDiv>
)}
</div>
);
}
export default observer(SelectDateRange);

View file

@ -18,16 +18,11 @@ const DATE_RANGE_LABELS = {
};
const COMPARISON_DATE_RANGE_LABELS = {
// LAST_30_MINUTES: '30 Minutes',
// TODAY: 'Today',
LAST_24_HOURS: "Previous Day",
// YESTERDAY: 'Yesterday',
LAST_7_DAYS: "Previous Week",
LAST_30_DAYS: "Previous Month",
//THIS_MONTH: 'This Month',
//LAST_MONTH: 'Previous Month',
//THIS_YEAR: 'This Year',
[CUSTOM_RANGE]: "Custom Range",
PREV_24_HOURS: "Previous Day",
PREV_7_DAYS: "Previous Week",
PREV_30_DAYS: "Previous Month",
PREV_QUARTER: "Previous Quarter",
CUSTOM_RANGE: "Custom",
}
const DATE_RANGE_VALUES = {};
@ -86,6 +81,18 @@ export function getDateRangeFromValue(value) {
now.minus({ days: 30 }).startOf('day'),
now.endOf('day')
);
case COMPARISON_DATE_RANGE_LABELS.PREV_24_HOURS:
return Interval.fromDateTimes(now.minus({ hours: 48 }), now.minus({ hours: 24 }));
case COMPARISON_DATE_RANGE_LABELS.PREV_7_DAYS:
return Interval.fromDateTimes(
now.minus({ days: 14 }).startOf('day'),
now.minus({ days: 7 }).endOf('day')
);
case COMPARISON_DATE_RANGE_LABELS.PREV_30_DAYS:
return Interval.fromDateTimes(
now.minus({ days: 60 }).startOf('day'),
now.minus({ days: 30 }).endOf('day')
);
// case DATE_RANGE_VALUES.THIS_MONTH:
// return Interval.fromDateTimes(now.startOf('month'), now.endOf('month'));
// case DATE_RANGE_VALUES.LAST_MONTH:

View file

@ -22,6 +22,7 @@ export default class DashboardStore {
widgets: Widget[] = [];
period: Record<string, any> = Period({ rangeName: LAST_24_HOURS });
drillDownFilter: Filter = new Filter();
comparisonFilter: Filter = new Filter();
drillDownPeriod: Record<string, any> = Period({ rangeName: LAST_7_DAYS });
comparisonPeriod: Record<string, any> | null = null
startTimestamp: number = 0;
@ -420,6 +421,13 @@ export default class DashboardStore {
});
}
cloneCompFilter() {
const filterData = this.drillDownFilter.toData()
this.comparisonFilter = new Filter().fromData(filterData);
return this.comparisonFilter;
}
toggleAlertModal(val: boolean) {
this.showAlertModal = val;
}
@ -436,12 +444,13 @@ export default class DashboardStore {
metric: Widget,
data: any,
isSaved: boolean = false,
period: Record<string, any>
period: Record<string, any>,
isComparison?: boolean
): Promise<any> {
period = period.toTimestamps();
const params = { ...period, ...data, key: metric.predefinedKey };
if (metric.page && metric.limit) {
if (!isComparison && metric.page && metric.limit) {
params['page'] = metric.page;
params['limit'] = metric.limit;
}
@ -449,13 +458,13 @@ export default class DashboardStore {
return new Promise(async (resolve, reject) => {
this.upPendingRequests()
if (metric.metricType === 'table' && metric.metricOf === 'jsException') {
if (!isComparison && metric.metricType === 'table' && metric.metricOf === 'jsException') {
params.limit = 5;
}
try {
const data = await metricService.getMetricChartData(metric, params, isSaved);
resolve(metric.setData(data, period));
resolve(metric.setData(data, period, isComparison));
} catch (error) {
reject(error);
} finally {

View file

@ -293,8 +293,18 @@ export default class Widget {
this.page = page;
}
setData(data: any, period: any) {
const _data: any = {...data};
setData(data: { timestamp: number, [seriesName: string]: number}[], period: any, isComparison?: boolean) {
const _data: any = {};
if (isComparison) {
console.log(data)
data.forEach((point, i) => {
Object.keys(point).forEach((key) => {
if (key === 'timestamp') return;
point[`Previous ${key}`] = point[key];
delete point[key];
})
})
}
if (this.metricType === USER_PATH) {
const _data = processData(data);
@ -350,7 +360,9 @@ export default class Widget {
}
}
Object.assign(this.data, _data);
if (!isComparison) {
Object.assign(this.data, _data);
}
return _data;
}

View file

@ -11,6 +11,10 @@ export const THIS_MONTH = "THIS_MONTH";
export const LAST_MONTH = "LAST_MONTH";
export const THIS_YEAR = "THIS_YEAR";
export const CUSTOM_RANGE = "CUSTOM_RANGE";
export const PREV_24_HOURS = "PREV_24_HOURS";
export const PREV_7_DAYS = "PREV_7_DAYS";
export const PREV_30_DAYS = "PREV_30_DAYS";
export const PREV_QUARTER = "PREV_QUARTER";
function getRange(rangeName, offset) {
const now = DateTime.now().setZone(offset);
@ -47,33 +51,60 @@ function getRange(rangeName, offset) {
return Interval.fromDateTimes(lastMonth.startOf("month"), lastMonth.endOf("month"));
case THIS_YEAR:
return Interval.fromDateTimes(now.startOf("year"), now.endOf("year"));
case PREV_24_HOURS:
return Interval.fromDateTimes(now.minus({ hours: 48 }), now.minus({ hours: 24 }));
case PREV_7_DAYS:
return Interval.fromDateTimes(
now.minus({ days: 14 }).startOf("day"),
now.minus({ days: 7 }).endOf("day")
);
case PREV_30_DAYS:
return Interval.fromDateTimes(
now.minus({ days: 60 }).startOf("day"),
now.minus({ days: 30 }).endOf("day")
);
default:
return Interval.fromDateTimes(now, now);
}
}
const substractValues = {
[PREV_24_HOURS]: { hours: 24 },
[PREV_7_DAYS]: { days: 7 },
[PREV_30_DAYS]: { days: 30 },
[PREV_QUARTER]: { months: 3 },
}
export default Record(
{
start: 0,
end: 0,
rangeName: CUSTOM_RANGE,
range: Interval.fromDateTimes(DateTime.now(), DateTime.now()),
substract: null,
},
{
// type substractors = 'PREV_24_HOURS' | 'PREV_7_DAYS' | 'PREV_30_DAYS' | 'PREV_QUARTER';
fromJS: (period) => {
const offset = period.timezoneOffset || DateTime.now().offset;
if (!period.rangeName || period.rangeName === CUSTOM_RANGE) {
const isLuxon = DateTime.isDateTime(period.start);
const start = isLuxon
let start = isLuxon
? period.start : DateTime.fromMillis(period.start || 0, { zone: Settings.defaultZone });
const end = isLuxon
let end = isLuxon
? period.end : DateTime.fromMillis(period.end || 0, { zone: Settings.defaultZone });
if (period.substract) {
const delta = substractValues[period.substract]
start = start.minus(delta);
end = end.minus(delta);
}
const range = Interval.fromDateTimes(start, end);
return {
...period,
range,
start: range.start.toMillis(),
end: range.end.toMillis(),
rangeName: period.substract ? period.substract : undefined
};
}
const range = getRange(period.rangeName, offset);

View file

@ -148,5 +148,5 @@
"engines": {
"node": ">=20.18.0"
},
"packageManager": "yarn@4.5.1"
"packageManager": "yarn@4.5.3"
}