diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx index 2beeb2af..a37d3032 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx @@ -58,10 +58,7 @@ export function ListReports({ reports }: ListReportsProps) { {reports.map((report) => { const chartRange = report.range; // timeRanges[report.range]; return ( -
+
-
+
@@ -65,9 +65,7 @@ export default async function Page({ params: { id } }: PageProps) { -
- -
+
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 188ec3b4..65f850ad 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -24,7 +24,7 @@ export default function RootLayout({ return ( {children} diff --git a/apps/web/src/components/Card.tsx b/apps/web/src/components/Card.tsx index 281856a1..ad3763a5 100644 --- a/apps/web/src/components/Card.tsx +++ b/apps/web/src/components/Card.tsx @@ -19,8 +19,8 @@ export function Card({ children, hover, className }: CardProps) { return (
diff --git a/apps/web/src/components/Widget.tsx b/apps/web/src/components/Widget.tsx index 926d6366..41ecc8dd 100644 --- a/apps/web/src/components/Widget.tsx +++ b/apps/web/src/components/Widget.tsx @@ -30,14 +30,5 @@ export interface WidgetProps { className?: string; } export function Widget({ children, className }: WidgetProps) { - return ( -
- {children} -
- ); + return
{children}
; } diff --git a/apps/web/src/components/general/ExpandableListItem.tsx b/apps/web/src/components/general/ExpandableListItem.tsx index c25e0d38..5108d4d9 100644 --- a/apps/web/src/components/general/ExpandableListItem.tsx +++ b/apps/web/src/components/general/ExpandableListItem.tsx @@ -23,9 +23,7 @@ export function ExpandableListItem({ }: ExpandableListItemProps) { const [open, setOpen] = useState(initialOpen ?? false); return ( -
+
{image}
diff --git a/apps/web/src/components/overview/overview-latest-events/index.tsx b/apps/web/src/components/overview/overview-latest-events/index.tsx new file mode 100644 index 00000000..b29cea30 --- /dev/null +++ b/apps/web/src/components/overview/overview-latest-events/index.tsx @@ -0,0 +1,16 @@ +import { getConversionEventNames } from '@mixan/db'; + +import type { OverviewLatestEventsProps } from './overview-latest-events'; +import OverviewLatestEvents from './overview-latest-events'; + +export default async function OverviewLatestEventsServer({ + projectId, +}: Omit) { + const eventNames = await getConversionEventNames(projectId); + return ( + item.name)} + /> + ); +} diff --git a/apps/web/src/components/overview/overview-latest-events/overview-latest-events.tsx b/apps/web/src/components/overview/overview-latest-events/overview-latest-events.tsx new file mode 100644 index 00000000..b3d9f5cb --- /dev/null +++ b/apps/web/src/components/overview/overview-latest-events/overview-latest-events.tsx @@ -0,0 +1,128 @@ +'use client'; + +import { ChartSwitch } from '@/components/report/chart'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; +import { cn } from '@/utils/cn'; + +import { Widget, WidgetBody } from '../../Widget'; +import { WidgetButtons, WidgetHead } from '../overview-widget'; +import { useOverviewOptions } from '../useOverviewOptions'; +import { useOverviewWidget } from '../useOverviewWidget'; + +export interface OverviewLatestEventsProps { + projectId: string; + conversions: string[]; +} +export default function OverviewLatestEvents({ + projectId, + conversions, +}: OverviewLatestEventsProps) { + const { interval, range, previous, startDate, endDate } = + useOverviewOptions(); + const [filters] = useEventQueryFilters(); + const [widget, setWidget, widgets] = useOverviewWidget('ev', { + all: { + title: 'Top events', + btn: 'All', + chart: { + projectId, + startDate, + endDate, + events: [ + { + segment: 'event', + filters: [ + ...filters, + { + id: 'ex_session', + name: 'name', + operator: 'isNot', + value: ['session_start', 'session_end'], + }, + ], + id: 'A', + name: '*', + }, + ], + breakdowns: [ + { + id: 'A', + name: 'name', + }, + ], + chartType: 'bar', + lineType: 'monotone', + interval: interval, + name: 'Top sources', + range: range, + previous: previous, + metric: 'sum', + }, + }, + conversions: { + title: 'Conversions', + btn: 'Conversions', + hide: conversions.length === 0, + chart: { + projectId, + startDate, + endDate, + events: [ + { + segment: 'event', + filters: [ + ...filters, + { + id: 'conversion', + name: 'name', + operator: 'is', + value: conversions, + }, + ], + id: 'A', + name: '*', + }, + ], + breakdowns: [ + { + id: 'A', + name: 'name', + }, + ], + chartType: 'bar', + lineType: 'monotone', + interval: interval, + name: 'Top sources', + range: range, + previous: previous, + metric: 'sum', + }, + }, + }); + + return ( + <> + + +
{widget.title}
+ + {widgets + .filter((item) => item.hide !== true) + .map((w) => ( + + ))} + +
+ + + +
+ + ); +} diff --git a/apps/web/src/components/overview/overview-live-histogram.tsx b/apps/web/src/components/overview/overview-live-histogram.tsx index fc434345..90549100 100644 --- a/apps/web/src/components/overview/overview-live-histogram.tsx +++ b/apps/web/src/components/overview/overview-live-histogram.tsx @@ -1,22 +1,25 @@ 'use client'; +import { Fragment } from 'react'; +import { api } from '@/app/_trpc/client'; import { cn } from '@/utils/cn'; import { ChevronsUpDownIcon } from 'lucide-react'; import AnimateHeight from 'react-animate-height'; import type { IChartInput } from '@mixan/validation'; -import { ChartSwitch } from '../report/chart'; -import { Widget, WidgetBody, WidgetHead } from '../Widget'; +import { redisSub } from '../../../../../packages/redis'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { useOverviewOptions } from './useOverviewOptions'; interface OverviewLiveHistogramProps { projectId: string; } + export function OverviewLiveHistogram({ projectId, }: OverviewLiveHistogramProps) { - const { liveHistogram, setLiveHistogram } = useOverviewOptions(); + const { liveHistogram } = useOverviewOptions(); const report: IChartInput = { projectId, events: [ @@ -44,26 +47,112 @@ export function OverviewLiveHistogram({ lineType: 'monotone', previous: false, }; + const countReport: IChartInput = { + name: '', + projectId, + events: [ + { + segment: 'user', + filters: [], + id: 'A', + name: 'session_start', + }, + ], + breakdowns: [], + chartType: 'metric', + lineType: 'monotone', + interval: 'minute', + range: '30min', + previous: false, + metric: 'sum', + }; + + const res = api.chart.chart.useQuery(report); + const countRes = api.chart.chart.useQuery(countReport); + + const metrics = res.data?.series[0]?.metrics; + const minutes = (res.data?.series[0]?.data || []).slice(-30); + const liveCount = countRes.data?.series[0]?.metrics?.sum ?? 0; + + if (res.isInitialLoading || countRes.isInitialLoading) { + // prettier-ignore + const staticArray = [ + 10, 25, 30, 45, 20, 5, 55, 18, 40, 12, + 50, 35, 8, 22, 38, 42, 15, 28, 52, 5, + 48, 14, 32, 58, 7, 19, 33, 56, 24, 5 + ]; + + return ( + + {staticArray.map((percent, i) => ( +
+ ))} + + ); + } + + if (!res.isSuccess && !countRes.isSuccess) { + return null; + } return ( - - - - - - - - - + + {minutes.map((minute) => { + return ( + + +
+ + +
{minute.count} active users
+
@ {new Date(minute.date).toLocaleTimeString()}
+
+ + ); + })} + + ); +} + +interface WrapperProps { + open: boolean; + children: React.ReactNode; + count: number; +} + +function Wrapper({ open, children, count }: WrapperProps) { + return ( + +
+
+
Last 30 minutes
+
+ {count} +
+
+
+
+ NOW +
+ {/*
*/} + {children} +
+
+ ); } diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx b/apps/web/src/components/overview/overview-metrics.tsx similarity index 94% rename from apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx rename to apps/web/src/components/overview/overview-metrics.tsx index e1a32842..60fa1ad0 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx +++ b/apps/web/src/components/overview/overview-metrics.tsx @@ -202,12 +202,6 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) { }} > -
{/* add active border */} ))} diff --git a/apps/web/src/components/overview/overview-top-devices.tsx b/apps/web/src/components/overview/overview-top-devices.tsx index ca2085ce..04d2cbb1 100644 --- a/apps/web/src/components/overview/overview-top-devices.tsx +++ b/apps/web/src/components/overview/overview-top-devices.tsx @@ -18,6 +18,7 @@ export default function OverviewTopDevices({ const { interval, range, previous, startDate, endDate } = useOverviewOptions(); const [filters, setFilter] = useEventQueryFilters(); + const isPageFilter = filters.find((filter) => filter.name === 'path'); const [widget, setWidget, widgets] = useOverviewWidget('tech', { devices: { title: 'Top devices', @@ -31,7 +32,7 @@ export default function OverviewTopDevices({ segment: 'user', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -61,7 +62,7 @@ export default function OverviewTopDevices({ segment: 'user', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -91,7 +92,7 @@ export default function OverviewTopDevices({ segment: 'user', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -121,7 +122,7 @@ export default function OverviewTopDevices({ segment: 'user', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -151,7 +152,7 @@ export default function OverviewTopDevices({ segment: 'user', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ diff --git a/apps/web/src/components/overview/overview-top-geo.tsx b/apps/web/src/components/overview/overview-top-geo.tsx index 9a1e3146..91aa2d2c 100644 --- a/apps/web/src/components/overview/overview-top-geo.tsx +++ b/apps/web/src/components/overview/overview-top-geo.tsx @@ -16,6 +16,7 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { const { interval, range, previous, startDate, endDate } = useOverviewOptions(); const [filters, setFilter] = useEventQueryFilters(); + const isPageFilter = filters.find((filter) => filter.name === 'path'); const [widget, setWidget, widgets] = useOverviewWidget('geo', { countries: { title: 'Top countries', @@ -29,7 +30,7 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { segment: 'event', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -59,7 +60,7 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { segment: 'event', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -89,7 +90,7 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { segment: 'event', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ @@ -165,7 +166,7 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { segment: 'event', filters, id: 'A', - name: '*', + name: isPageFilter ? 'screen_view' : 'session_start', }, ], breakdowns: [ diff --git a/apps/web/src/components/overview/overview-widget.tsx b/apps/web/src/components/overview/overview-widget.tsx index 041af62e..9bead633 100644 --- a/apps/web/src/components/overview/overview-widget.tsx +++ b/apps/web/src/components/overview/overview-widget.tsx @@ -19,10 +19,7 @@ import { WidgetHead as WidgetHeadBase } from '../Widget'; export function WidgetHead({ className, ...props }: WidgetHeadProps) { return ( ); diff --git a/apps/web/src/components/overview/useOverviewOptions.ts b/apps/web/src/components/overview/useOverviewOptions.ts index 7dfc4576..48f38ba5 100644 --- a/apps/web/src/components/overview/useOverviewOptions.ts +++ b/apps/web/src/components/overview/useOverviewOptions.ts @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { parseAsBoolean, parseAsInteger, @@ -48,7 +47,7 @@ export function useOverviewOptions() { // Toggles const [liveHistogram, setLiveHistogram] = useQueryState( 'live', - parseAsBoolean.withDefault(false).withOptions(nuqsOptions) + parseAsBoolean.withDefault(true).withOptions(nuqsOptions) ); return { diff --git a/apps/web/src/components/report/PreviousDiffIndicator.tsx b/apps/web/src/components/report/PreviousDiffIndicator.tsx index ff8dc8ed..92993552 100644 --- a/apps/web/src/components/report/PreviousDiffIndicator.tsx +++ b/apps/web/src/components/report/PreviousDiffIndicator.tsx @@ -107,7 +107,7 @@ export function PreviousDiffIndicatorText({ ])} > {renderIcon()} - {number.format(diff)}% + {number.short(diff)}%
); } diff --git a/apps/web/src/components/report/chart/MetricCard.tsx b/apps/web/src/components/report/chart/MetricCard.tsx index 5d8fb760..28082e95 100644 --- a/apps/web/src/components/report/chart/MetricCard.tsx +++ b/apps/web/src/components/report/chart/MetricCard.tsx @@ -56,7 +56,7 @@ export function MetricCard({ return (
@@ -64,28 +64,14 @@ export function MetricCard({ {({ width, height }) => ( - - - - - - - - - - - - - - +
No data
@@ -130,7 +116,7 @@ export function MetricCardEmpty() { export function MetricCardLoading() { return ( -
+
diff --git a/apps/web/src/components/report/chart/ReportBarChart.tsx b/apps/web/src/components/report/chart/ReportBarChart.tsx index 1a16025c..d0ed96d2 100644 --- a/apps/web/src/components/report/chart/ReportBarChart.tsx +++ b/apps/web/src/components/report/chart/ReportBarChart.tsx @@ -30,16 +30,17 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
- {series.map((serie) => { + {series.map((serie, index) => { const isClickable = serie.name !== NOT_SET_VALUE && onClick; return (
onClick(serie) } : {})} @@ -57,14 +58,12 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
{number.format(serie.metrics.sum)}
+
- -
); })} diff --git a/apps/web/src/components/report/chart/ReportPieChart.tsx b/apps/web/src/components/report/chart/ReportPieChart.tsx index 4683e46a..dc4211e0 100644 --- a/apps/web/src/components/report/chart/ReportPieChart.tsx +++ b/apps/web/src/components/report/chart/ReportPieChart.tsx @@ -31,12 +31,7 @@ export function ReportPieChart({ data }: ReportPieChartProps) { return ( <> -
+
{({ width }) => { const height = Math.min(Math.max(width * 0.5625, 250), 400); diff --git a/apps/web/src/components/report/chart/ResponsiveContainer.tsx b/apps/web/src/components/report/chart/ResponsiveContainer.tsx index 890f94dd..55205f2a 100644 --- a/apps/web/src/components/report/chart/ResponsiveContainer.tsx +++ b/apps/web/src/components/report/chart/ResponsiveContainer.tsx @@ -17,10 +17,7 @@ export function ResponsiveContainer({ children }: ResponsiveContainerProps) { maxHeight, minHeight, }} - className={cn( - 'max-sm:-mx-3 aspect-video w-full', - editMode && 'border border-border bg-white rounded-md p-4' - )} + className={cn('max-sm:-mx-3 aspect-video w-full', editMode && 'card p-4')} > {({ width }) => diff --git a/apps/web/src/components/report/chart/SerieIcon.tsx b/apps/web/src/components/report/chart/SerieIcon.tsx index f751b915..a79160d3 100644 --- a/apps/web/src/components/report/chart/SerieIcon.tsx +++ b/apps/web/src/components/report/chart/SerieIcon.tsx @@ -44,6 +44,11 @@ const mapper: Record = { link_out: ExternalLinkIcon, // Websites + linkedin: createImageIcon(getProxyImage('https://linkedin.com')), + slack: createImageIcon(getProxyImage('https://slack.com')), + pinterest: createImageIcon(getProxyImage('https://www.pinterest.se')), + ecosia: createImageIcon(getProxyImage('https://ecosia.com')), + yandex: createImageIcon(getProxyImage('https://yandex.com')), google: createImageIcon(getProxyImage('https://google.com')), facebook: createImageIcon(getProxyImage('https://facebook.com')), bing: createImageIcon(getProxyImage('https://bing.com')), diff --git a/apps/web/src/components/report/funnel/Funnel.tsx b/apps/web/src/components/report/funnel/Funnel.tsx index 15782e95..43c97e85 100644 --- a/apps/web/src/components/report/funnel/Funnel.tsx +++ b/apps/web/src/components/report/funnel/Funnel.tsx @@ -99,7 +99,7 @@ export function FunnelSteps({ )} key={step.event.id} > -
+

Step {index + 1}

diff --git a/apps/web/src/components/ui/table.tsx b/apps/web/src/components/ui/table.tsx index fda58dff..e99dfa41 100644 --- a/apps/web/src/components/ui/table.tsx +++ b/apps/web/src/components/ui/table.tsx @@ -10,7 +10,7 @@ const Table = React.forwardRef< overflow?: boolean; } >(({ className, wrapper, overflow = true, ...props }, ref) => ( -
+