ability to use custom dates everywhere
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header';
|
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header';
|
||||||
|
import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
|
||||||
import { LazyChart } from '@/components/report/chart/LazyChart';
|
import { LazyChart } from '@/components/report/chart/LazyChart';
|
||||||
import { ReportRange } from '@/components/report/ReportRange';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -18,9 +17,13 @@ import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { getDefaultIntervalByRange } from '@mixan/constants';
|
import {
|
||||||
|
getDefaultIntervalByDates,
|
||||||
|
getDefaultIntervalByRange,
|
||||||
|
} from '@mixan/constants';
|
||||||
import type { getReportsByDashboardId } from '@mixan/db';
|
import type { getReportsByDashboardId } from '@mixan/db';
|
||||||
import type { IChartRange } from '@mixan/validation';
|
|
||||||
|
import { OverviewReportRange } from '../../overview-sticky-header';
|
||||||
|
|
||||||
interface ListReportsProps {
|
interface ListReportsProps {
|
||||||
reports: Awaited<ReturnType<typeof getReportsByDashboardId>>;
|
reports: Awaited<ReturnType<typeof getReportsByDashboardId>>;
|
||||||
@@ -29,16 +32,12 @@ interface ListReportsProps {
|
|||||||
export function ListReports({ reports }: ListReportsProps) {
|
export function ListReports({ reports }: ListReportsProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useAppParams<{ dashboardId: string }>();
|
const params = useAppParams<{ dashboardId: string }>();
|
||||||
const [range, setRange] = useState<null | IChartRange>(null);
|
const { range, startDate, endDate } = useOverviewOptions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StickyBelowHeader className="p-4 items-center justify-between flex">
|
<StickyBelowHeader className="p-4 items-center justify-between flex">
|
||||||
<ReportRange
|
<OverviewReportRange />
|
||||||
placeholder="Override range"
|
|
||||||
value={range}
|
|
||||||
onChange={(value) => setRange((p) => (p === value ? null : value))}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
icon={PlusIcon}
|
icon={PlusIcon}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -72,10 +71,20 @@ export function ListReports({ reports }: ListReportsProps) {
|
|||||||
<div className="font-medium">{report.name}</div>
|
<div className="font-medium">{report.name}</div>
|
||||||
{chartRange !== null && (
|
{chartRange !== null && (
|
||||||
<div className="mt-2 text-sm flex gap-2">
|
<div className="mt-2 text-sm flex gap-2">
|
||||||
<span className={range !== null ? 'line-through' : ''}>
|
<span
|
||||||
|
className={
|
||||||
|
range !== null || (startDate && endDate)
|
||||||
|
? 'line-through'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
>
|
||||||
{chartRange}
|
{chartRange}
|
||||||
</span>
|
</span>
|
||||||
{range !== null && <span>{range}</span>}
|
{startDate && endDate ? (
|
||||||
|
<span>Custom dates</span>
|
||||||
|
) : (
|
||||||
|
range !== null && <span>{range}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -116,8 +125,11 @@ export function ListReports({ reports }: ListReportsProps) {
|
|||||||
<LazyChart
|
<LazyChart
|
||||||
{...report}
|
{...report}
|
||||||
range={range ?? report.range}
|
range={range ?? report.range}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
interval={
|
interval={
|
||||||
range ? getDefaultIntervalByRange(range) : report.interval
|
getDefaultIntervalByDates(startDate, endDate) ||
|
||||||
|
(range ? getDefaultIntervalByRange(range) : report.interval)
|
||||||
}
|
}
|
||||||
editMode={false}
|
editMode={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,13 +14,16 @@ interface OverviewMetricsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
||||||
const { previous, range, interval, metric, setMetric } = useOverviewOptions();
|
const { previous, range, interval, metric, setMetric, startDate, endDate } =
|
||||||
|
useOverviewOptions();
|
||||||
const [filters] = useEventQueryFilters();
|
const [filters] = useEventQueryFilters();
|
||||||
|
|
||||||
const reports = [
|
const reports = [
|
||||||
{
|
{
|
||||||
id: 'Visitors',
|
id: 'Visitors',
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user',
|
segment: 'user',
|
||||||
@@ -42,6 +45,8 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
|||||||
{
|
{
|
||||||
id: 'Sessions',
|
id: 'Sessions',
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -63,6 +68,8 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
|||||||
{
|
{
|
||||||
id: 'Pageviews',
|
id: 'Pageviews',
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -84,6 +91,8 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
|||||||
{
|
{
|
||||||
id: 'Views per session',
|
id: 'Views per session',
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user_average',
|
segment: 'user_average',
|
||||||
@@ -105,6 +114,8 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
|||||||
{
|
{
|
||||||
id: 'Bounce rate',
|
id: 'Bounce rate',
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -143,6 +154,8 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
|
|||||||
{
|
{
|
||||||
id: 'Visit duration',
|
id: 'Visit duration',
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'property_average',
|
segment: 'property_average',
|
||||||
|
|||||||
@@ -2,8 +2,36 @@
|
|||||||
|
|
||||||
import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
|
import { useOverviewOptions } from '@/components/overview/useOverviewOptions';
|
||||||
import { ReportRange } from '@/components/report/ReportRange';
|
import { ReportRange } from '@/components/report/ReportRange';
|
||||||
|
import { endOfDay, startOfDay } from 'date-fns';
|
||||||
|
|
||||||
export function OverviewReportRange() {
|
export function OverviewReportRange() {
|
||||||
const { range, setRange } = useOverviewOptions();
|
const { range, setRange, setEndDate, setStartDate, startDate, endDate } =
|
||||||
return <ReportRange value={range} onChange={(value) => setRange(value)} />;
|
useOverviewOptions();
|
||||||
|
return (
|
||||||
|
<ReportRange
|
||||||
|
range={range}
|
||||||
|
onRangeChange={(value) => {
|
||||||
|
setRange(value);
|
||||||
|
setStartDate(null);
|
||||||
|
setEndDate(null);
|
||||||
|
}}
|
||||||
|
dates={{
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
}}
|
||||||
|
onDatesChange={(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
if (val.from && val.to) {
|
||||||
|
setRange(null);
|
||||||
|
setStartDate(startOfDay(val.from).toISOString());
|
||||||
|
setEndDate(endOfDay(val.to).toISOString());
|
||||||
|
} else if (val.from) {
|
||||||
|
setStartDate(startOfDay(val.from).toISOString());
|
||||||
|
} else if (val.to) {
|
||||||
|
setEndDate(endOfDay(val.to).toISOString());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import OverviewTopEvents from '@/components/overview/overview-top-events';
|
|||||||
import OverviewTopGeo from '@/components/overview/overview-top-geo';
|
import OverviewTopGeo from '@/components/overview/overview-top-geo';
|
||||||
import OverviewTopPages from '@/components/overview/overview-top-pages';
|
import OverviewTopPages from '@/components/overview/overview-top-pages';
|
||||||
import OverviewTopSources from '@/components/overview/overview-top-sources';
|
import OverviewTopSources from '@/components/overview/overview-top-sources';
|
||||||
import { Dialog } from '@/components/ui/dialog';
|
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
|
|
||||||
import { db } from '@mixan/db';
|
import { db } from '@mixan/db';
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { ListProperties } from '@/components/events/ListProperties';
|
|
||||||
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
|
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
|
||||||
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
|
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
|
||||||
import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
|
import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
|
||||||
import { ChartSwitch } from '@/components/report/chart';
|
import { ChartSwitch } from '@/components/report/chart';
|
||||||
import { GradientBackground } from '@/components/ui/gradient-background';
|
|
||||||
import { KeyValue } from '@/components/ui/key-value';
|
import { KeyValue } from '@/components/ui/key-value';
|
||||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||||
import {
|
import {
|
||||||
@@ -12,25 +10,21 @@ import {
|
|||||||
eventQueryNamesFilter,
|
eventQueryNamesFilter,
|
||||||
} from '@/hooks/useEventQueryFilters';
|
} from '@/hooks/useEventQueryFilters';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { formatDateTime } from '@/utils/date';
|
|
||||||
import { getProfileName } from '@/utils/getters';
|
import { getProfileName } from '@/utils/getters';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { parseAsInteger } from 'nuqs';
|
import { parseAsInteger, parseAsString } from 'nuqs';
|
||||||
|
|
||||||
import type { GetEventListOptions } from '@mixan/db';
|
import type { GetEventListOptions } from '@mixan/db';
|
||||||
import {
|
import {
|
||||||
getConversionEventNames,
|
getConversionEventNames,
|
||||||
getEventList,
|
getEventList,
|
||||||
getEventMeta,
|
|
||||||
getEventsCount,
|
getEventsCount,
|
||||||
getProfileById,
|
getProfileById,
|
||||||
getProfilesByExternalId,
|
|
||||||
} from '@mixan/db';
|
} from '@mixan/db';
|
||||||
import type { IChartInput } from '@mixan/validation';
|
import type { IChartEvent, IChartInput } from '@mixan/validation';
|
||||||
|
|
||||||
import { EventList } from '../../events/event-list';
|
import { EventList } from '../../events/event-list';
|
||||||
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
||||||
import ListProfileEvents from './list-profile-events';
|
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: {
|
params: {
|
||||||
@@ -42,6 +36,8 @@ interface PageProps {
|
|||||||
events?: string;
|
events?: string;
|
||||||
cursor?: string;
|
cursor?: string;
|
||||||
f?: string;
|
f?: string;
|
||||||
|
startDate: string;
|
||||||
|
endDate: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +53,8 @@ export default async function Page({
|
|||||||
events: eventQueryNamesFilter.parse(searchParams.events ?? ''),
|
events: eventQueryNamesFilter.parse(searchParams.events ?? ''),
|
||||||
filters: eventQueryFiltersParser.parse(searchParams.f ?? '') ?? undefined,
|
filters: eventQueryFiltersParser.parse(searchParams.f ?? '') ?? undefined,
|
||||||
};
|
};
|
||||||
|
const startDate = parseAsString.parse(searchParams.startDate);
|
||||||
|
const endDate = parseAsString.parse(searchParams.endDate);
|
||||||
const [profile, events, count, conversions] = await Promise.all([
|
const [profile, events, count, conversions] = await Promise.all([
|
||||||
getProfileById(profileId),
|
getProfileById(profileId),
|
||||||
getEventList(eventListOptions),
|
getEventList(eventListOptions),
|
||||||
@@ -65,7 +63,7 @@ export default async function Page({
|
|||||||
getExists(organizationId, projectId),
|
getExists(organizationId, projectId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const chartSelectedEvents = [
|
const chartSelectedEvents: IChartEvent[] = [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
filters: [
|
filters: [
|
||||||
@@ -107,6 +105,8 @@ export default async function Page({
|
|||||||
|
|
||||||
const profileChart: IChartInput = {
|
const profileChart: IChartInput = {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
chartType: 'histogram',
|
chartType: 'histogram',
|
||||||
events: chartSelectedEvents,
|
events: chartSelectedEvents,
|
||||||
breakdowns: [],
|
breakdowns: [],
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { ReportRange } from '@/components/report/ReportRange';
|
|||||||
import { ReportSaveButton } from '@/components/report/ReportSaveButton';
|
import { ReportSaveButton } from '@/components/report/ReportSaveButton';
|
||||||
import {
|
import {
|
||||||
changeDateRanges,
|
changeDateRanges,
|
||||||
|
changeDates,
|
||||||
|
changeEndDate,
|
||||||
|
changeStartDate,
|
||||||
ready,
|
ready,
|
||||||
reset,
|
reset,
|
||||||
setReport,
|
setReport,
|
||||||
@@ -19,6 +22,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
|
import { endOfDay, startOfDay } from 'date-fns';
|
||||||
import { GanttChartSquareIcon } from 'lucide-react';
|
import { GanttChartSquareIcon } from 'lucide-react';
|
||||||
|
|
||||||
import type { IServiceReport } from '@mixan/db';
|
import type { IServiceReport } from '@mixan/db';
|
||||||
@@ -61,10 +65,30 @@ export default function ReportEditor({
|
|||||||
<ReportChartType className="min-w-0 flex-1" />
|
<ReportChartType className="min-w-0 flex-1" />
|
||||||
<ReportRange
|
<ReportRange
|
||||||
className="min-w-0 flex-1"
|
className="min-w-0 flex-1"
|
||||||
value={report.range}
|
range={report.range}
|
||||||
onChange={(value) => {
|
onRangeChange={(value) => {
|
||||||
dispatch(changeDateRanges(value));
|
dispatch(changeDateRanges(value));
|
||||||
}}
|
}}
|
||||||
|
dates={{
|
||||||
|
startDate: report.startDate,
|
||||||
|
endDate: report.endDate,
|
||||||
|
}}
|
||||||
|
onDatesChange={(val) => {
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
if (val.from && val.to) {
|
||||||
|
dispatch(
|
||||||
|
changeDates({
|
||||||
|
startDate: startOfDay(val.from).toISOString(),
|
||||||
|
endDate: endOfDay(val.to).toISOString(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (val.from) {
|
||||||
|
dispatch(changeStartDate(startOfDay(val.from).toISOString()));
|
||||||
|
} else if (val.to) {
|
||||||
|
dispatch(changeEndDate(endOfDay(val.to).toISOString()));
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ReportInterval className="min-w-0 flex-1" />
|
<ReportInterval className="min-w-0 flex-1" />
|
||||||
<ReportLineType className="min-w-0 flex-1" />
|
<ReportLineType className="min-w-0 flex-1" />
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ interface OverviewTopDevicesProps {
|
|||||||
export default function OverviewTopDevices({
|
export default function OverviewTopDevices({
|
||||||
projectId,
|
projectId,
|
||||||
}: OverviewTopDevicesProps) {
|
}: OverviewTopDevicesProps) {
|
||||||
const { interval, range, previous } = useOverviewOptions();
|
const { interval, range, previous, startDate, endDate } =
|
||||||
|
useOverviewOptions();
|
||||||
const [filters, setFilter] = useEventQueryFilters();
|
const [filters, setFilter] = useEventQueryFilters();
|
||||||
const [widget, setWidget, widgets] = useOverviewWidget('tech', {
|
const [widget, setWidget, widgets] = useOverviewWidget('tech', {
|
||||||
devices: {
|
devices: {
|
||||||
@@ -23,6 +24,8 @@ export default function OverviewTopDevices({
|
|||||||
btn: 'Devices',
|
btn: 'Devices',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user',
|
segment: 'user',
|
||||||
@@ -51,6 +54,8 @@ export default function OverviewTopDevices({
|
|||||||
btn: 'Browser',
|
btn: 'Browser',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user',
|
segment: 'user',
|
||||||
@@ -79,6 +84,8 @@ export default function OverviewTopDevices({
|
|||||||
btn: 'Browser Version',
|
btn: 'Browser Version',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user',
|
segment: 'user',
|
||||||
@@ -107,6 +114,8 @@ export default function OverviewTopDevices({
|
|||||||
btn: 'OS',
|
btn: 'OS',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user',
|
segment: 'user',
|
||||||
@@ -135,6 +144,8 @@ export default function OverviewTopDevices({
|
|||||||
btn: 'OS Version',
|
btn: 'OS Version',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'user',
|
segment: 'user',
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ interface OverviewTopEventsProps {
|
|||||||
export default function OverviewTopEvents({
|
export default function OverviewTopEvents({
|
||||||
projectId,
|
projectId,
|
||||||
}: OverviewTopEventsProps) {
|
}: OverviewTopEventsProps) {
|
||||||
const { interval, range, previous } = useOverviewOptions();
|
const { interval, range, previous, startDate, endDate } =
|
||||||
|
useOverviewOptions();
|
||||||
const [filters] = useEventQueryFilters();
|
const [filters] = useEventQueryFilters();
|
||||||
const [widget, setWidget, widgets] = useOverviewWidget('ev', {
|
const [widget, setWidget, widgets] = useOverviewWidget('ev', {
|
||||||
all: {
|
all: {
|
||||||
@@ -23,6 +24,8 @@ export default function OverviewTopEvents({
|
|||||||
btn: 'All',
|
btn: 'All',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ interface OverviewTopGeoProps {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
|
export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
|
||||||
const { interval, range, previous } = useOverviewOptions();
|
const { interval, range, previous, startDate, endDate } =
|
||||||
|
useOverviewOptions();
|
||||||
const [filters, setFilter] = useEventQueryFilters();
|
const [filters, setFilter] = useEventQueryFilters();
|
||||||
const [widget, setWidget, widgets] = useOverviewWidget('geo', {
|
const [widget, setWidget, widgets] = useOverviewWidget('geo', {
|
||||||
map: {
|
map: {
|
||||||
@@ -21,6 +22,8 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
|
|||||||
btn: 'Map',
|
btn: 'Map',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -49,6 +52,8 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
|
|||||||
btn: 'Countries',
|
btn: 'Countries',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -77,6 +82,8 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
|
|||||||
btn: 'Regions',
|
btn: 'Regions',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -105,6 +112,8 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
|
|||||||
btn: 'Cities',
|
btn: 'Cities',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ interface OverviewTopPagesProps {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
|
export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
|
||||||
const { interval, range, previous } = useOverviewOptions();
|
const { interval, range, previous, startDate, endDate } =
|
||||||
|
useOverviewOptions();
|
||||||
const [filters, setFilter] = useEventQueryFilters();
|
const [filters, setFilter] = useEventQueryFilters();
|
||||||
const [widget, setWidget, widgets] = useOverviewWidget('pages', {
|
const [widget, setWidget, widgets] = useOverviewWidget('pages', {
|
||||||
top: {
|
top: {
|
||||||
@@ -21,6 +22,8 @@ export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
|
|||||||
btn: 'Top pages',
|
btn: 'Top pages',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -49,6 +52,8 @@ export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
|
|||||||
btn: 'Entries',
|
btn: 'Entries',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -77,6 +82,8 @@ export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
|
|||||||
btn: 'Exits',
|
btn: 'Exits',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ interface OverviewTopSourcesProps {
|
|||||||
export default function OverviewTopSources({
|
export default function OverviewTopSources({
|
||||||
projectId,
|
projectId,
|
||||||
}: OverviewTopSourcesProps) {
|
}: OverviewTopSourcesProps) {
|
||||||
const { interval, range, previous } = useOverviewOptions();
|
const { interval, range, previous, startDate, endDate } =
|
||||||
|
useOverviewOptions();
|
||||||
const [filters, setFilter] = useEventQueryFilters();
|
const [filters, setFilter] = useEventQueryFilters();
|
||||||
const [widget, setWidget, widgets] = useOverviewWidget('sources', {
|
const [widget, setWidget, widgets] = useOverviewWidget('sources', {
|
||||||
all: {
|
all: {
|
||||||
@@ -23,6 +24,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'All',
|
btn: 'All',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -51,6 +54,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'URLs',
|
btn: 'URLs',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -79,6 +84,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'Types',
|
btn: 'Types',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -107,6 +114,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'Source',
|
btn: 'Source',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -135,6 +144,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'Medium',
|
btn: 'Medium',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -163,6 +174,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'Campaign',
|
btn: 'Campaign',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -191,6 +204,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'Term',
|
btn: 'Term',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
@@ -219,6 +234,8 @@ export default function OverviewTopSources({
|
|||||||
btn: 'Content',
|
btn: 'Content',
|
||||||
chart: {
|
chart: {
|
||||||
projectId,
|
projectId,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
events: [
|
events: [
|
||||||
{
|
{
|
||||||
segment: 'event',
|
segment: 'event',
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
parseAsBoolean,
|
parseAsBoolean,
|
||||||
parseAsInteger,
|
parseAsInteger,
|
||||||
|
parseAsString,
|
||||||
parseAsStringEnum,
|
parseAsStringEnum,
|
||||||
useQueryState,
|
useQueryState,
|
||||||
} from 'nuqs';
|
} from 'nuqs';
|
||||||
|
|
||||||
import { getDefaultIntervalByRange, timeRanges } from '@mixan/constants';
|
import {
|
||||||
|
getDefaultIntervalByDates,
|
||||||
|
getDefaultIntervalByRange,
|
||||||
|
timeRanges,
|
||||||
|
} from '@mixan/constants';
|
||||||
import { mapKeys } from '@mixan/validation';
|
import { mapKeys } from '@mixan/validation';
|
||||||
|
|
||||||
const nuqsOptions = { history: 'push' } as const;
|
const nuqsOptions = { history: 'push' } as const;
|
||||||
@@ -15,13 +21,25 @@ export function useOverviewOptions() {
|
|||||||
'compare',
|
'compare',
|
||||||
parseAsBoolean.withDefault(true).withOptions(nuqsOptions)
|
parseAsBoolean.withDefault(true).withOptions(nuqsOptions)
|
||||||
);
|
);
|
||||||
|
const [startDate, setStartDate] = useQueryState(
|
||||||
|
'start',
|
||||||
|
parseAsString.withOptions(nuqsOptions)
|
||||||
|
);
|
||||||
|
const [endDate, setEndDate] = useQueryState(
|
||||||
|
'end',
|
||||||
|
parseAsString.withOptions(nuqsOptions)
|
||||||
|
);
|
||||||
const [range, setRange] = useQueryState(
|
const [range, setRange] = useQueryState(
|
||||||
'range',
|
'range',
|
||||||
parseAsStringEnum(mapKeys(timeRanges))
|
parseAsStringEnum(mapKeys(timeRanges))
|
||||||
.withDefault('7d')
|
.withDefault('7d')
|
||||||
.withOptions(nuqsOptions)
|
.withOptions(nuqsOptions)
|
||||||
);
|
);
|
||||||
const interval = getDefaultIntervalByRange(range);
|
|
||||||
|
const interval =
|
||||||
|
getDefaultIntervalByDates(startDate, endDate) ||
|
||||||
|
getDefaultIntervalByRange(range);
|
||||||
|
|
||||||
const [metric, setMetric] = useQueryState(
|
const [metric, setMetric] = useQueryState(
|
||||||
'metric',
|
'metric',
|
||||||
parseAsInteger.withDefault(0).withOptions(nuqsOptions)
|
parseAsInteger.withDefault(0).withOptions(nuqsOptions)
|
||||||
@@ -40,6 +58,10 @@ export function useOverviewOptions() {
|
|||||||
setRange,
|
setRange,
|
||||||
metric,
|
metric,
|
||||||
setMetric,
|
setMetric,
|
||||||
|
startDate,
|
||||||
|
setStartDate,
|
||||||
|
endDate,
|
||||||
|
setEndDate,
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
interval,
|
interval,
|
||||||
|
|||||||
@@ -7,48 +7,32 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/components/ui/popover';
|
} from '@/components/ui/popover';
|
||||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||||
import { pushModal } from '@/modals';
|
|
||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import { addDays, endOfDay, format, startOfDay, subDays } from 'date-fns';
|
import { endOfDay, format, startOfDay } from 'date-fns';
|
||||||
import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react';
|
import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react';
|
||||||
import type { DateRange, SelectRangeEventHandler } from 'react-day-picker';
|
import type { SelectRangeEventHandler } from 'react-day-picker';
|
||||||
|
|
||||||
import { timeRanges } from '@mixan/constants';
|
import { timeRanges } from '@mixan/constants';
|
||||||
import type { IChartRange } from '@mixan/validation';
|
import type { IChartRange } from '@mixan/validation';
|
||||||
|
|
||||||
import type { ExtendedComboboxProps } from '../ui/combobox';
|
import type { ExtendedComboboxProps } from '../ui/combobox';
|
||||||
import { Combobox } from '../ui/combobox';
|
|
||||||
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
||||||
import { changeDates, changeEndDate, changeStartDate } from './reportSlice';
|
import { changeDates, changeEndDate, changeStartDate } from './reportSlice';
|
||||||
|
|
||||||
export function ReportRange({
|
export function ReportRange({
|
||||||
onChange,
|
range,
|
||||||
value,
|
onRangeChange,
|
||||||
|
onDatesChange,
|
||||||
|
dates,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ExtendedComboboxProps<IChartRange>) {
|
}: {
|
||||||
const dispatch = useDispatch();
|
range: IChartRange;
|
||||||
const startDate = useSelector((state) => state.report.startDate);
|
onRangeChange: (range: IChartRange) => void;
|
||||||
const endDate = useSelector((state) => state.report.endDate);
|
onDatesChange: SelectRangeEventHandler;
|
||||||
|
dates: { startDate: string | null; endDate: string | null };
|
||||||
const setDate: SelectRangeEventHandler = (val) => {
|
} & Omit<ExtendedComboboxProps<string>, 'value' | 'onChange'>) {
|
||||||
if (!val) return;
|
|
||||||
|
|
||||||
if (val.from && val.to) {
|
|
||||||
dispatch(
|
|
||||||
changeDates({
|
|
||||||
startDate: startOfDay(val.from).toISOString(),
|
|
||||||
endDate: endOfDay(val.to).toISOString(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else if (val.from) {
|
|
||||||
dispatch(changeStartDate(startOfDay(val.from).toISOString()));
|
|
||||||
} else if (val.to) {
|
|
||||||
dispatch(changeEndDate(endOfDay(val.to).toISOString()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { isBelowSm } = useBreakpoint('sm');
|
const { isBelowSm } = useBreakpoint('sm');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -63,16 +47,17 @@ export function ReportRange({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">
|
<span className="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
{startDate ? (
|
{dates.startDate ? (
|
||||||
endDate ? (
|
dates.endDate ? (
|
||||||
<>
|
<>
|
||||||
{format(startDate, 'LLL dd')} - {format(endDate, 'LLL dd')}
|
{format(dates.startDate, 'LLL dd')} -{' '}
|
||||||
|
{format(dates.endDate, 'LLL dd')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
format(startDate, 'LLL dd, y')
|
format(dates.startDate, 'LLL dd, y')
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<span>{value}</span>
|
<span>{range}</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<ChevronsUpDownIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" />
|
<ChevronsUpDownIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" />
|
||||||
@@ -81,9 +66,9 @@ export function ReportRange({
|
|||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<div className="p-4 border-b border-border">
|
<div className="p-4 border-b border-border">
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
value={value}
|
value={range}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
if (value) onChange(value);
|
if (value) onRangeChange(value as IChartRange);
|
||||||
}}
|
}}
|
||||||
type="single"
|
type="single"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -99,12 +84,14 @@ export function ReportRange({
|
|||||||
<Calendar
|
<Calendar
|
||||||
initialFocus
|
initialFocus
|
||||||
mode="range"
|
mode="range"
|
||||||
defaultMonth={startDate ? new Date(startDate) : new Date()}
|
defaultMonth={
|
||||||
|
dates.startDate ? new Date(dates.startDate) : new Date()
|
||||||
|
}
|
||||||
selected={{
|
selected={{
|
||||||
from: startDate ? new Date(startDate) : undefined,
|
from: dates.startDate ? new Date(dates.startDate) : undefined,
|
||||||
to: endDate ? new Date(endDate) : undefined,
|
to: dates.endDate ? new Date(dates.endDate) : undefined,
|
||||||
}}
|
}}
|
||||||
onSelect={setDate}
|
onSelect={onDatesChange}
|
||||||
numberOfMonths={isBelowSm ? 1 : 2}
|
numberOfMonths={isBelowSm ? 1 : 2}
|
||||||
className="[&_table]:mx-auto [&_table]:w-auto"
|
className="[&_table]:mx-auto [&_table]:w-auto"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { isSameDay, isSameMonth } from 'date-fns';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
alphabetIds,
|
alphabetIds,
|
||||||
|
getDefaultIntervalByDates,
|
||||||
getDefaultIntervalByRange,
|
getDefaultIntervalByRange,
|
||||||
isHourIntervalEnabledByRange,
|
isHourIntervalEnabledByRange,
|
||||||
isMinuteIntervalEnabledByRange,
|
isMinuteIntervalEnabledByRange,
|
||||||
@@ -39,8 +40,8 @@ const initialState: InitialState = {
|
|||||||
breakdowns: [],
|
breakdowns: [],
|
||||||
events: [],
|
events: [],
|
||||||
range: '1m',
|
range: '1m',
|
||||||
startDate: new Date('2024-02-24 00:00:00').toISOString(),
|
startDate: null,
|
||||||
endDate: new Date('2024-02-24 23:59:59').toISOString(),
|
endDate: null,
|
||||||
previous: false,
|
previous: false,
|
||||||
formula: undefined,
|
formula: undefined,
|
||||||
unit: undefined,
|
unit: undefined,
|
||||||
@@ -205,14 +206,12 @@ export const reportSlice = createSlice({
|
|||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
state.startDate = action.payload;
|
state.startDate = action.payload;
|
||||||
|
|
||||||
if (state.startDate && state.endDate) {
|
const interval = getDefaultIntervalByDates(
|
||||||
if (isSameDay(state.startDate, state.endDate)) {
|
state.startDate,
|
||||||
state.interval = 'hour';
|
state.endDate
|
||||||
} else if (isSameMonth(state.startDate, state.endDate)) {
|
);
|
||||||
state.interval = 'day';
|
if (interval) {
|
||||||
} else {
|
state.interval = interval;
|
||||||
state.interval = 'month';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -221,14 +220,12 @@ export const reportSlice = createSlice({
|
|||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
state.endDate = action.payload;
|
state.endDate = action.payload;
|
||||||
|
|
||||||
if (state.startDate && state.endDate) {
|
const interval = getDefaultIntervalByDates(
|
||||||
if (isSameDay(state.startDate, state.endDate)) {
|
state.startDate,
|
||||||
state.interval = 'hour';
|
state.endDate
|
||||||
} else if (isSameMonth(state.startDate, state.endDate)) {
|
);
|
||||||
state.interval = 'day';
|
if (interval) {
|
||||||
} else {
|
state.interval = interval;
|
||||||
state.interval = 'month';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { isValidElement } from 'react';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface KeyValueProps {
|
interface KeyValueProps {
|
||||||
name: string;
|
name: string;
|
||||||
value: string | number | undefined;
|
value: unknown;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
href?: string;
|
href?: string;
|
||||||
}
|
}
|
||||||
@@ -11,6 +12,10 @@ interface KeyValueProps {
|
|||||||
export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
|
export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
|
||||||
const clickable = href || onClick;
|
const clickable = href || onClick;
|
||||||
const Component = href ? (Link as any) : onClick ? 'button' : 'div';
|
const Component = href ? (Link as any) : onClick ? 'button' : 'div';
|
||||||
|
if (!isValidElement(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -35,6 +40,10 @@ export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
|
|||||||
export function KeyValueSubtle({ href, onClick, name, value }: KeyValueProps) {
|
export function KeyValueSubtle({ href, onClick, name, value }: KeyValueProps) {
|
||||||
const clickable = href || onClick;
|
const clickable = href || onClick;
|
||||||
const Component = href ? (Link as any) : onClick ? 'button' : 'div';
|
const Component = href ? (Link as any) : onClick ? 'button' : 'div';
|
||||||
|
if (!isValidElement(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
className="group flex text-[10px] sm:text-xs gap-2 font-medium self-start min-w-0 max-w-full items-center"
|
className="group flex text-[10px] sm:text-xs gap-2 font-medium self-start min-w-0 max-w-full items-center"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { isSameDay, isSameMonth } from 'date-fns';
|
||||||
|
|
||||||
export const NOT_SET_VALUE = '(not set)';
|
export const NOT_SET_VALUE = '(not set)';
|
||||||
|
|
||||||
export const operators = {
|
export const operators = {
|
||||||
@@ -100,3 +102,19 @@ export function getDefaultIntervalByRange(
|
|||||||
}
|
}
|
||||||
return 'month';
|
return 'month';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDefaultIntervalByDates(
|
||||||
|
startDate: string | null,
|
||||||
|
endDate: string | null
|
||||||
|
): null | keyof typeof intervals {
|
||||||
|
if (startDate && endDate) {
|
||||||
|
if (isSameDay(startDate, endDate)) {
|
||||||
|
return 'hour';
|
||||||
|
} else if (isSameMonth(startDate, endDate)) {
|
||||||
|
return 'day';
|
||||||
|
}
|
||||||
|
return 'month';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"@mixan/eslint-config": "workspace:*",
|
"@mixan/eslint-config": "workspace:*",
|
||||||
"@mixan/prettier-config": "workspace:*",
|
"@mixan/prettier-config": "workspace:*",
|
||||||
"@mixan/tsconfig": "workspace:*",
|
"@mixan/tsconfig": "workspace:*",
|
||||||
|
"date-fns": "^3.3.1",
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.48.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"prisma": "^5.1.1",
|
"prisma": "^5.1.1",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ CREATE TABLE openpanel.events_bots (
|
|||||||
ORDER BY
|
ORDER BY
|
||||||
(project_id, created_at) SETTINGS index_granularity = 8192;
|
(project_id, created_at) SETTINGS index_granularity = 8192;
|
||||||
|
|
||||||
CREATE TABLE profiles (
|
CREATE TABLE openpanel.profiles (
|
||||||
`id` String,
|
`id` String,
|
||||||
`external_id` String,
|
`external_id` String,
|
||||||
`first_name` String,
|
`first_name` String,
|
||||||
|
|||||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -716,6 +716,9 @@ importers:
|
|||||||
'@mixan/tsconfig':
|
'@mixan/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../tooling/typescript
|
version: link:../../tooling/typescript
|
||||||
|
date-fns:
|
||||||
|
specifier: ^3.3.1
|
||||||
|
version: 3.3.1
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^8.48.0
|
specifier: ^8.48.0
|
||||||
version: 8.56.0
|
version: 8.56.0
|
||||||
@@ -7947,7 +7950,6 @@ packages:
|
|||||||
|
|
||||||
/date-fns@3.3.1:
|
/date-fns@3.3.1:
|
||||||
resolution: {integrity: sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==}
|
resolution: {integrity: sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/dayjs@1.11.10:
|
/dayjs@1.11.10:
|
||||||
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
|
||||||
|
|||||||
Reference in New Issue
Block a user