sdk changes
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { api } from '@/app/_trpc/client';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { Button } from '../ui/button';
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { Label } from '../ui/label';
|
||||
import { useOverviewOptions } from './useOverviewOptions';
|
||||
|
||||
export function OverviewFiltersButtons() {
|
||||
@@ -17,111 +12,166 @@ export function OverviewFiltersButtons() {
|
||||
{options.referrer && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setReferrer(null)}
|
||||
>
|
||||
{options.referrer}
|
||||
<span className="mr-1">Referrer is</span>
|
||||
<strong>{options.referrer}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.device && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setDevice(null)}
|
||||
>
|
||||
{options.device}
|
||||
<span className="mr-1">Device is</span>
|
||||
<strong>{options.device}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.page && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setPage(null)}
|
||||
>
|
||||
{options.page}
|
||||
<span className="mr-1">Page is</span>
|
||||
<strong>{options.page}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.utmSource && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setUtmSource(null)}
|
||||
>
|
||||
{options.utmSource}
|
||||
<span className="mr-1">Utm Source is</span>
|
||||
<strong>{options.utmSource}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.utmMedium && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setUtmMedium(null)}
|
||||
>
|
||||
{options.utmMedium}
|
||||
<span className="mr-1">Utm Medium is</span>
|
||||
<strong>{options.utmMedium}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.utmCampaign && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setUtmCampaign(null)}
|
||||
>
|
||||
{options.utmCampaign}
|
||||
<span className="mr-1">Utm Campaign is</span>
|
||||
<strong>{options.utmCampaign}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.utmTerm && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setUtmTerm(null)}
|
||||
>
|
||||
{options.utmTerm}
|
||||
<span className="mr-1">Utm Term is</span>
|
||||
<strong>{options.utmTerm}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.utmContent && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setUtmContent(null)}
|
||||
>
|
||||
{options.utmContent}
|
||||
<span className="mr-1">Utm Content is</span>
|
||||
<strong>{options.utmContent}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.country && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setCountry(null)}
|
||||
>
|
||||
{options.country}
|
||||
<span className="mr-1">Country is</span>
|
||||
<strong>{options.country}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.region && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setRegion(null)}
|
||||
>
|
||||
{options.region}
|
||||
<span className="mr-1">Region is</span>
|
||||
<strong>{options.region}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.city && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setCity(null)}
|
||||
>
|
||||
{options.city}
|
||||
<span className="mr-1">City is</span>
|
||||
<strong>{options.city}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.browser && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setBrowser(null)}
|
||||
>
|
||||
<span className="mr-1">Browser is</span>
|
||||
<strong>{options.browser}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.browserVersion && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setBrowserVersion(null)}
|
||||
>
|
||||
<span className="mr-1">Browser Version is</span>
|
||||
<strong>{options.browserVersion}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.os && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setOS(null)}
|
||||
>
|
||||
<span className="mr-1">OS is</span>
|
||||
<strong>{options.os}</strong>
|
||||
</Button>
|
||||
)}
|
||||
{options.osVersion && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
icon={X}
|
||||
onClick={() => options.setOSVersion(null)}
|
||||
>
|
||||
<span className="mr-1">OS Version is</span>
|
||||
<strong>{options.osVersion}</strong>
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -11,8 +11,20 @@ import { useOverviewOptions } from './useOverviewOptions';
|
||||
import { useOverviewWidget } from './useOverviewWidget';
|
||||
|
||||
export default function OverviewTopDevices() {
|
||||
const { filters, interval, range, previous, setCountry, setRegion, setCity } =
|
||||
useOverviewOptions();
|
||||
const {
|
||||
filters,
|
||||
interval,
|
||||
range,
|
||||
previous,
|
||||
setBrowser,
|
||||
setBrowserVersion,
|
||||
browser,
|
||||
browserVersion,
|
||||
setOS,
|
||||
setOSVersion,
|
||||
os,
|
||||
osVersion,
|
||||
} = useOverviewOptions();
|
||||
const [widget, setWidget, widgets] = useOverviewWidget('tech', {
|
||||
devices: {
|
||||
title: 'Top devices',
|
||||
@@ -180,19 +192,22 @@ export default function OverviewTopDevices() {
|
||||
{...widget.chart}
|
||||
previous={false}
|
||||
onClick={(item) => {
|
||||
// switch (widget.key) {
|
||||
// case 'browser':
|
||||
// setWidget('browser_version');
|
||||
// // setCountry(item.name);
|
||||
// break;
|
||||
// case 'regions':
|
||||
// setWidget('cities');
|
||||
// setRegion(item.name);
|
||||
// break;
|
||||
// case 'cities':
|
||||
// setCity(item.name);
|
||||
// break;
|
||||
// }
|
||||
switch (widget.key) {
|
||||
case 'browser':
|
||||
setWidget('browser_version');
|
||||
setBrowser(item.name);
|
||||
break;
|
||||
case 'browser_version':
|
||||
setBrowserVersion(item.name);
|
||||
break;
|
||||
case 'os':
|
||||
setWidget('os_version');
|
||||
setOS(item.name);
|
||||
break;
|
||||
case 'os_version':
|
||||
setOSVersion(item.name);
|
||||
break;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { Children, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useThrottle } from '@/hooks/useThrottle';
|
||||
import { cn } from '@/utils/cn';
|
||||
import throttle from 'lodash.throttle';
|
||||
import { ChevronsUpDownIcon } from 'lucide-react';
|
||||
import { last } from 'ramda';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '../ui/dropdown-menu';
|
||||
import type { WidgetHeadProps } from '../Widget';
|
||||
import { WidgetHead as WidgetHeadBase } from '../Widget';
|
||||
|
||||
@@ -14,14 +26,98 @@ export function WidgetHead({ className, ...props }: WidgetHeadProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function WidgetButtons({ className, ...props }: WidgetHeadProps) {
|
||||
export function WidgetButtons({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: WidgetHeadProps) {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
const sizes = useRef<number[]>([]);
|
||||
const [slice, setSlice] = useState(Children.count(children) - 1);
|
||||
const gap = 8;
|
||||
|
||||
const handleResize = useThrottle(() => {
|
||||
if (container.current) {
|
||||
if (sizes.current.length === 0) {
|
||||
// Get buttons
|
||||
const buttons: HTMLButtonElement[] = Array.from(
|
||||
container.current.querySelectorAll(`button`)
|
||||
);
|
||||
// Get sizes and cache them
|
||||
sizes.current = buttons.map(
|
||||
(button) => Math.ceil(button.offsetWidth) + gap
|
||||
);
|
||||
}
|
||||
const containerWidth = container.current.offsetWidth;
|
||||
const buttonsWidth = sizes.current.reduce((acc, size) => acc + size, 0);
|
||||
const moreWidth = (last(sizes.current) ?? 0) + gap;
|
||||
|
||||
if (buttonsWidth > containerWidth) {
|
||||
const res = sizes.current.reduce(
|
||||
(acc, size, index) => {
|
||||
if (acc.size + size + moreWidth > containerWidth) {
|
||||
return { index: acc.index, size: acc.size + size };
|
||||
}
|
||||
return { index, size: acc.size + size };
|
||||
},
|
||||
{ index: 0, size: 0 }
|
||||
);
|
||||
|
||||
setSlice(res.index);
|
||||
} else {
|
||||
setSlice(sizes.current.length - 1);
|
||||
}
|
||||
}
|
||||
}, 30);
|
||||
|
||||
useEffect(() => {
|
||||
handleResize();
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, [handleResize, children]);
|
||||
|
||||
const hidden = '!opacity-0 absolute pointer-events-none';
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={container}
|
||||
className={cn(
|
||||
'flex gap-2 [&_button]:text-xs [&_button]:opacity-50 [&_button.active]:opacity-100',
|
||||
'flex-1 justify-end transition-opacity flex flex-wrap [&_button]:text-xs [&_button]:opacity-50 [&_button]:whitespace-nowrap [&_button.active]:opacity-100',
|
||||
className
|
||||
)}
|
||||
style={{ gap }}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
{Children.map(children, (child, index) => {
|
||||
return (
|
||||
<div className={cn('flex', slice < index ? hidden : 'opacity-100')}>
|
||||
{child}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center gap-1 select-none',
|
||||
sizes.current.length - 1 === slice ? hidden : 'opacity-50'
|
||||
)}
|
||||
>
|
||||
More <ChevronsUpDownIcon size={12} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="[&_button]:w-full">
|
||||
<DropdownMenuGroup>
|
||||
{Children.map(children, (child, index) => {
|
||||
if (index <= slice) {
|
||||
return null;
|
||||
}
|
||||
return <DropdownMenuItem asChild>{child}</DropdownMenuItem>;
|
||||
})}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,15 +34,12 @@ export function useOverviewOptions() {
|
||||
'referrer',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
const [device, setDevice] = useQueryState(
|
||||
'device',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
const [page, setPage] = useQueryState(
|
||||
'page',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
|
||||
// Sources
|
||||
const [utmSource, setUtmSource] = useQueryState(
|
||||
'utm_source',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
@@ -64,6 +61,7 @@ export function useOverviewOptions() {
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
|
||||
// Geo
|
||||
const [country, setCountry] = useQueryState(
|
||||
'country',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
@@ -77,6 +75,28 @@ export function useOverviewOptions() {
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
|
||||
//
|
||||
const [device, setDevice] = useQueryState(
|
||||
'device',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
const [browser, setBrowser] = useQueryState(
|
||||
'browser',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
const [browserVersion, setBrowserVersion] = useQueryState(
|
||||
'browser_version',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
const [os, setOS] = useQueryState(
|
||||
'os',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
const [osVersion, setOSVersion] = useQueryState(
|
||||
'os_version',
|
||||
parseAsString.withOptions(nuqsOptions)
|
||||
);
|
||||
|
||||
const filters = useMemo(() => {
|
||||
const filters: IChartInput['events'][number]['filters'] = [];
|
||||
if (referrer) {
|
||||
@@ -178,6 +198,42 @@ export function useOverviewOptions() {
|
||||
});
|
||||
}
|
||||
|
||||
if (browser) {
|
||||
filters.push({
|
||||
id: 'browser',
|
||||
operator: 'is',
|
||||
name: 'browser',
|
||||
value: [browser],
|
||||
});
|
||||
}
|
||||
|
||||
if (browserVersion) {
|
||||
filters.push({
|
||||
id: 'browser_version',
|
||||
operator: 'is',
|
||||
name: 'browser_version',
|
||||
value: [browserVersion],
|
||||
});
|
||||
}
|
||||
|
||||
if (os) {
|
||||
filters.push({
|
||||
id: 'os',
|
||||
operator: 'is',
|
||||
name: 'os',
|
||||
value: [os],
|
||||
});
|
||||
}
|
||||
|
||||
if (osVersion) {
|
||||
filters.push({
|
||||
id: 'os_version',
|
||||
operator: 'is',
|
||||
name: 'os_version',
|
||||
value: [osVersion],
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [
|
||||
referrer,
|
||||
@@ -191,6 +247,10 @@ export function useOverviewOptions() {
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
browser,
|
||||
browserVersion,
|
||||
os,
|
||||
osVersion,
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -202,8 +262,6 @@ export function useOverviewOptions() {
|
||||
setMetric,
|
||||
referrer,
|
||||
setReferrer,
|
||||
device,
|
||||
setDevice,
|
||||
page,
|
||||
setPage,
|
||||
|
||||
@@ -230,5 +288,17 @@ export function useOverviewOptions() {
|
||||
setRegion,
|
||||
city,
|
||||
setCity,
|
||||
|
||||
// Tech
|
||||
device,
|
||||
setDevice,
|
||||
browser,
|
||||
setBrowser,
|
||||
browserVersion,
|
||||
setBrowserVersion,
|
||||
os,
|
||||
setOS,
|
||||
osVersion,
|
||||
setOSVersion,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { createContext, memo, useContext, useMemo } from 'react';
|
||||
import type { IChartSerie } from '@/server/api/routers/chart';
|
||||
import type { IChartInput } from '@/types';
|
||||
|
||||
export interface ChartContextType extends IChartInput {
|
||||
editMode?: boolean;
|
||||
hideID?: boolean;
|
||||
onClick?: (item: any) => void;
|
||||
onClick?: (item: IChartSerie) => void;
|
||||
}
|
||||
|
||||
type ChartProviderProps = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { NOT_SET_VALUE } from '@/utils/constants';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
@@ -51,10 +52,15 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
|
||||
</div>
|
||||
)}
|
||||
{series.map((serie, index) => {
|
||||
const isClickable = serie.name !== NOT_SET_VALUE && onClick;
|
||||
return (
|
||||
<div
|
||||
key={serie.name}
|
||||
className="py-2 flex flex-1 w-full gap-4 items-center"
|
||||
className={cn(
|
||||
'py-2 flex flex-1 w-full gap-4 items-center',
|
||||
isClickable && 'cursor-pointer hover:bg-gray-100'
|
||||
)}
|
||||
{...(isClickable ? { onClick: () => onClick(serie) } : {})}
|
||||
>
|
||||
<div className="flex-1 break-all">{serie.name}</div>
|
||||
<div className="flex-shrink-0 flex w-1/4 gap-4 items-center justify-end">
|
||||
|
||||
@@ -6,7 +6,6 @@ import { api } from '@/app/_trpc/client';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import type { IChartInput } from '@/types';
|
||||
|
||||
import { ChartAnimation, ChartAnimationContainer } from './ChartAnimation';
|
||||
import { ChartEmpty } from './ChartEmpty';
|
||||
import { withChartProivder } from './ChartProvider';
|
||||
import { ReportAreaChart } from './ReportAreaChart';
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ButtonProps
|
||||
asChild?: boolean;
|
||||
loading?: boolean;
|
||||
icon?: LucideIcon;
|
||||
responsive?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
@@ -57,6 +58,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
loading,
|
||||
disabled,
|
||||
icon,
|
||||
responsive,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
@@ -71,9 +73,19 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
{...props}
|
||||
>
|
||||
{Icon && (
|
||||
<Icon className={cn('h-4 w-4 mr-2', loading && 'animate-spin')} />
|
||||
<Icon
|
||||
className={cn(
|
||||
'h-4 w-4 mr-2',
|
||||
responsive && 'mr-0 sm:mr-2',
|
||||
loading && 'animate-spin'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{responsive ? (
|
||||
<span className="hidden sm:block">{children}</span>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
{children}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user