sdk changes

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-11 21:31:12 +01:00
parent 484a6b1d41
commit 447fa5896e
65 changed files with 9428 additions and 723 deletions

View File

@@ -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>
)}
</>

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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,
};
}

View File

@@ -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 = {

View File

@@ -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">

View File

@@ -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';

View File

@@ -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>
);
}