This commit is contained in:
Carl-Gerhard Lindesvärd
2024-02-13 11:25:14 +01:00
parent 034be63ac0
commit 7f2c0f6cf0
64 changed files with 5820 additions and 1160 deletions

View File

@@ -0,0 +1,11 @@
import { getLiveVisitors } from '@mixan/db';
import type { LiveCounterProps } from './live-counter';
import LiveCounter from './live-counter';
export default async function ServerLiveCounter(
props: Omit<LiveCounterProps, 'data'>
) {
const count = await getLiveVisitors(props.projectId);
return <LiveCounter data={count} {...props} />;
}

View File

@@ -0,0 +1,90 @@
'use client';
import { useRef, useState } from 'react';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn } from '@/utils/cn';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import useWebSocket from 'react-use-websocket';
import { toast } from 'sonner';
export interface LiveCounterProps {
data: number;
projectId: string;
}
const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), {
ssr: false,
loading: () => <div>0</div>,
});
const FIFTEEN_SECONDS = 1000 * 15;
export default function LiveCounter({ data = 0, projectId }: LiveCounterProps) {
const ws = String(process.env.NEXT_PUBLIC_API_URL)
.replace(/^https/, 'wss')
.replace(/^http/, 'ws');
const client = useQueryClient();
const [counter, setCounter] = useState(data);
const [socketUrl] = useState(`${ws}/live/visitors/${projectId}`);
const lastRefresh = useRef(Date.now());
useWebSocket(socketUrl, {
shouldReconnect: () => true,
onMessage(event) {
const value = parseInt(event.data, 10);
if (!isNaN(value)) {
setCounter(value);
if (Date.now() - lastRefresh.current > FIFTEEN_SECONDS) {
lastRefresh.current = Date.now();
toast('Refreshed data');
client.refetchQueries({
type: 'active',
});
}
}
},
});
return (
<Tooltip>
<TooltipTrigger>
<div className="border border-border rounded h-8 px-3 leading-none flex items-center font-medium gap-2">
<div className="relative">
<div
className={cn(
'bg-emerald-500 h-3 w-3 rounded-full animate-ping opacity-100 transition-all',
counter === 0 && 'bg-destructive opacity-0'
)}
></div>
<div
className={cn(
'bg-emerald-500 h-3 w-3 rounded-full absolute top-0 left-0 transition-all',
counter === 0 && 'bg-destructive'
)}
></div>
</div>
<AnimatedNumbers
includeComma
transitions={(index) => ({
type: 'spring',
duration: index + 0.3,
damping: 10,
stiffness: 200,
})}
animateToNumber={counter}
locale="en"
/>
</div>
</TooltipTrigger>
<TooltipContent side="bottom">
{counter} unique visitors last 5 minutes
</TooltipContent>
</Tooltip>
);
}

View File

@@ -20,6 +20,28 @@ export function OverviewFiltersButtons() {
<strong>{options.referrer}</strong>
</Button>
)}
{options.referrerName && (
<Button
size="sm"
variant="outline"
icon={X}
onClick={() => options.setReferrerName(null)}
>
<span className="mr-1">Referrer name is</span>
<strong>{options.referrerName}</strong>
</Button>
)}
{options.referrerType && (
<Button
size="sm"
variant="outline"
icon={X}
onClick={() => options.setReferrerType(null)}
>
<span className="mr-1">Referrer type is</span>
<strong>{options.referrerType}</strong>
</Button>
)}
{options.device && (
<Button
size="sm"

View File

@@ -8,8 +8,10 @@ import { Combobox } from '../ui/combobox';
import { Label } from '../ui/label';
import { useOverviewOptions } from './useOverviewOptions';
export function OverviewFilters() {
const { projectId } = useAppParams();
interface OverviewFiltersProps {
projectId: string;
}
export function OverviewFilters({ projectId }: OverviewFiltersProps) {
const options = useOverviewOptions();
const { data: referrers } = api.chart.values.useQuery({

View File

@@ -0,0 +1,76 @@
'use client';
import { api } from '@/app/_trpc/client';
import { pushModal } from '@/modals';
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import type { ShareOverview } from '@mixan/db';
import { Button } from '../ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
interface OverviewShareProps {
data: ShareOverview | null;
}
export function OverviewShare({ data }: OverviewShareProps) {
const router = useRouter();
const mutation = api.share.shareOverview.useMutation({
onSuccess() {
router.refresh();
},
});
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button icon={data && data.public ? Globe2Icon : LockIcon} responsive>
{data && data.public ? 'Public' : 'Private'}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
{(!data || data.public === false) && (
<DropdownMenuItem onClick={() => pushModal('ShareOverviewModal')}>
<Globe2Icon size={16} className="mr-2" />
Make public
</DropdownMenuItem>
)}
{data?.public && (
<DropdownMenuItem asChild>
<Link
href={`${process.env.NEXT_PUBLIC_DASHBOARD_URL}/share/overview/${data.id}`}
>
<EyeIcon size={16} className="mr-2" />
View
</Link>
</DropdownMenuItem>
)}
{data?.public && (
<DropdownMenuItem
onClick={() => {
mutation.mutate({
public: false,
projectId: data?.project_id,
organizationId: data?.organization_slug,
password: null,
});
}}
>
<LockIcon size={16} className="mr-2" />
Make private
</DropdownMenuItem>
)}
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -10,7 +10,12 @@ import { WidgetButtons, WidgetHead } from './overview-widget';
import { useOverviewOptions } from './useOverviewOptions';
import { useOverviewWidget } from './useOverviewWidget';
export default function OverviewTopDevices() {
interface OverviewTopDevicesProps {
projectId: string;
}
export default function OverviewTopDevices({
projectId,
}: OverviewTopDevicesProps) {
const {
filters,
interval,
@@ -18,19 +23,15 @@ export default function OverviewTopDevices() {
previous,
setBrowser,
setBrowserVersion,
browser,
browserVersion,
setOS,
setOSVersion,
os,
osVersion,
} = useOverviewOptions();
const [widget, setWidget, widgets] = useOverviewWidget('tech', {
devices: {
title: 'Top devices',
btn: 'Devices',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'user',
@@ -58,7 +59,7 @@ export default function OverviewTopDevices() {
title: 'Top browser',
btn: 'Browser',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'user',
@@ -86,7 +87,7 @@ export default function OverviewTopDevices() {
title: 'Top Browser Version',
btn: 'Browser Version',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'user',
@@ -114,7 +115,7 @@ export default function OverviewTopDevices() {
title: 'Top OS',
btn: 'OS',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'user',
@@ -142,7 +143,7 @@ export default function OverviewTopDevices() {
title: 'Top OS version',
btn: 'OS Version',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'user',

View File

@@ -10,14 +10,19 @@ import { WidgetButtons, WidgetHead } from './overview-widget';
import { useOverviewOptions } from './useOverviewOptions';
import { useOverviewWidget } from './useOverviewWidget';
export default function OverviewTopEvents() {
interface OverviewTopEventsProps {
projectId: string;
}
export default function OverviewTopEvents({
projectId,
}: OverviewTopEventsProps) {
const { filters, interval, range, previous } = useOverviewOptions();
const [widget, setWidget, widgets] = useOverviewWidget('ev', {
all: {
title: 'Top events',
btn: 'All',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',

View File

@@ -10,7 +10,10 @@ import { WidgetButtons, WidgetHead } from './overview-widget';
import { useOverviewOptions } from './useOverviewOptions';
import { useOverviewWidget } from './useOverviewWidget';
export default function OverviewTopGeo() {
interface OverviewTopGeoProps {
projectId: string;
}
export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) {
const { filters, interval, range, previous, setCountry, setRegion, setCity } =
useOverviewOptions();
const [widget, setWidget, widgets] = useOverviewWidget('geo', {
@@ -18,7 +21,7 @@ export default function OverviewTopGeo() {
title: 'Map',
btn: 'Map',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -46,7 +49,7 @@ export default function OverviewTopGeo() {
title: 'Top countries',
btn: 'Countries',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -74,7 +77,7 @@ export default function OverviewTopGeo() {
title: 'Top regions',
btn: 'Regions',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -102,7 +105,7 @@ export default function OverviewTopGeo() {
title: 'Top cities',
btn: 'Cities',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',

View File

@@ -10,14 +10,17 @@ import { WidgetButtons, WidgetHead } from './overview-widget';
import { useOverviewOptions } from './useOverviewOptions';
import { useOverviewWidget } from './useOverviewWidget';
export default function OverviewTopPages() {
interface OverviewTopPagesProps {
projectId: string;
}
export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) {
const { filters, interval, range, previous, setPage } = useOverviewOptions();
const [widget, setWidget, widgets] = useOverviewWidget('pages', {
top: {
title: 'Top pages',
btn: 'Top pages',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -45,7 +48,7 @@ export default function OverviewTopPages() {
title: 'Entry Pages',
btn: 'Entries',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -73,7 +76,7 @@ export default function OverviewTopPages() {
title: 'Exit Pages',
btn: 'Exits',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',

View File

@@ -10,7 +10,12 @@ import { WidgetButtons, WidgetHead } from './overview-widget';
import { useOverviewOptions } from './useOverviewOptions';
import { useOverviewWidget } from './useOverviewWidget';
export default function OverviewTopSources() {
interface OverviewTopSourcesProps {
projectId: string;
}
export default function OverviewTopSources({
projectId,
}: OverviewTopSourcesProps) {
const {
filters,
interval,
@@ -22,13 +27,43 @@ export default function OverviewTopSources() {
setUtmCampaign,
setUtmTerm,
setUtmContent,
setReferrerName,
setReferrerType,
} = useOverviewOptions();
const [widget, setWidget, widgets] = useOverviewWidget('sources', {
all: {
title: 'Top sources',
btn: 'All',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
filters: filters,
id: 'A',
name: 'session_start',
},
],
breakdowns: [
{
id: 'A',
name: 'referrer_name',
},
],
chartType: 'bar',
lineType: 'monotone',
interval: interval,
name: 'Top groups',
range: range,
previous: previous,
metric: 'sum',
},
},
domain: {
title: 'Top urls',
btn: 'URLs',
chart: {
projectId,
events: [
{
segment: 'event',
@@ -52,11 +87,39 @@ export default function OverviewTopSources() {
metric: 'sum',
},
},
type: {
title: 'Top types',
btn: 'Types',
chart: {
projectId,
events: [
{
segment: 'event',
filters: filters,
id: 'A',
name: 'session_start',
},
],
breakdowns: [
{
id: 'A',
name: 'referrer_type',
},
],
chartType: 'bar',
lineType: 'monotone',
interval: interval,
name: 'Top types',
range: range,
previous: previous,
metric: 'sum',
},
},
utm_source: {
title: 'UTM Source',
btn: 'Source',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -84,7 +147,7 @@ export default function OverviewTopSources() {
title: 'UTM Medium',
btn: 'Medium',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -112,7 +175,7 @@ export default function OverviewTopSources() {
title: 'UTM Campaign',
btn: 'Campaign',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -140,7 +203,7 @@ export default function OverviewTopSources() {
title: 'UTM Term',
btn: 'Term',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -168,7 +231,7 @@ export default function OverviewTopSources() {
title: 'UTM Content',
btn: 'Content',
chart: {
projectId: '',
projectId,
events: [
{
segment: 'event',
@@ -220,8 +283,16 @@ export default function OverviewTopSources() {
onClick={(item) => {
switch (widget.key) {
case 'all':
setReferrerName(item.name);
setWidget('domain');
break;
case 'domain':
setReferrer(item.name);
break;
case 'type':
setReferrerType(item.name);
setWidget('domain');
break;
case 'utm_source':
setUtmSource(item.name);
break;

View File

@@ -33,7 +33,7 @@ export function WidgetButtons({
}: WidgetHeadProps) {
const container = useRef<HTMLDivElement>(null);
const sizes = useRef<number[]>([]);
const [slice, setSlice] = useState(Children.count(children) - 1);
const [slice, setSlice] = useState(-1);
const gap = 8;
const handleResize = useThrottle(() => {

View File

@@ -30,12 +30,22 @@ export function useOverviewOptions() {
);
// Filters
const [page, setPage] = useQueryState(
'page',
parseAsString.withOptions(nuqsOptions)
);
// Referrer
const [referrer, setReferrer] = useQueryState(
'referrer',
parseAsString.withOptions(nuqsOptions)
);
const [page, setPage] = useQueryState(
'page',
const [referrerName, setReferrerName] = useQueryState(
'referrer_name',
parseAsString.withOptions(nuqsOptions)
);
const [referrerType, setReferrerType] = useQueryState(
'referrer_type',
parseAsString.withOptions(nuqsOptions)
);
@@ -99,14 +109,6 @@ export function useOverviewOptions() {
const filters = useMemo(() => {
const filters: IChartInput['events'][number]['filters'] = [];
if (referrer) {
filters.push({
id: 'referrer',
operator: 'is',
name: 'referrer',
value: [referrer],
});
}
if (page) {
filters.push({
@@ -126,6 +128,33 @@ export function useOverviewOptions() {
});
}
if (referrer) {
filters.push({
id: 'referrer',
operator: 'is',
name: 'referrer',
value: [referrer],
});
}
if (referrerName) {
filters.push({
id: 'referrer_name',
operator: 'is',
name: 'referrer_name',
value: [referrerName],
});
}
if (referrerType) {
filters.push({
id: 'referrer_type',
operator: 'is',
name: 'referrer_type',
value: [referrerType],
});
}
if (utmSource) {
filters.push({
id: 'utm_source',
@@ -236,9 +265,11 @@ export function useOverviewOptions() {
return filters;
}, [
referrer,
page,
device,
referrer,
referrerName,
referrerType,
utmSource,
utmMedium,
utmCampaign,
@@ -260,8 +291,6 @@ export function useOverviewOptions() {
setRange,
metric,
setMetric,
referrer,
setReferrer,
page,
setPage,
@@ -269,6 +298,14 @@ export function useOverviewOptions() {
interval,
filters,
// Refs
referrer,
setReferrer,
referrerName,
setReferrerName,
referrerType,
setReferrerType,
// UTM
utmSource,
setUtmSource,

View File

@@ -22,7 +22,7 @@ export function ChartEmpty() {
return (
<div
className={
'aspect-video w-full max-h-[400px] flex justify-center items-center'
'aspect-video w-full max-h-[400px] min-h-[200px] flex justify-center items-center'
}
>
No data

View File

@@ -1,7 +1,18 @@
import { createContext, memo, useContext, useMemo } from 'react';
'use client';
import {
createContext,
memo,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import type { IChartSerie } from '@/server/api/routers/chart';
import type { IChartInput } from '@/types';
import { ChartLoading } from './ChartLoading';
export interface ChartContextType extends IChartInput {
editMode?: boolean;
hideID?: boolean;
@@ -53,6 +64,16 @@ export function withChartProivder<ComponentProps>(
WrappedComponent: React.FC<ComponentProps>
) {
const WithChartProvider = (props: ComponentProps & ChartContextType) => {
const [mounted, setMounted] = useState(props.chartType === 'metric');
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <ChartLoading />;
}
return (
<ChartProvider {...props}>
<WrappedComponent {...props} />

View File

@@ -1,3 +1,5 @@
'use client';
import type { IChartData } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
import { useNumber } from '@/hooks/useNumerFormatter';

View File

@@ -41,7 +41,7 @@ export function ReportAreaChart({
<>
<div
className={cn(
'max-sm:-mx-3',
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4'
)}
>

View File

@@ -1,35 +1,23 @@
import { useMemo, useState } from 'react';
import type { IChartData, RouterOutputs } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
'use client';
import { useMemo } from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Progress } from '@/components/ui/progress';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} 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';
import { PreviousDiffIndicator } from '../PreviousDiffIndicator';
import { useChartContext } from './ChartProvider';
import { SerieIcon } from './SerieIcon';
interface ReportBarChartProps {
data: IChartData;
}
export function ReportBarChart({ data }: ReportBarChartProps) {
const { editMode, metric, unit, onClick } = useChartContext();
const { editMode, metric, onClick } = useChartContext();
const number = useNumber();
const series = useMemo(
() => (editMode ? data.series : data.series.slice(0, 20)),
@@ -62,7 +50,10 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
)}
{...(isClickable ? { onClick: () => onClick(serie) } : {})}
>
<div className="flex-1 break-all">{serie.name}</div>
<div className="flex-1 break-all flex items-center gap-2">
<SerieIcon name={serie.name} />
{serie.name}
</div>
<div className="flex-shrink-0 flex w-1/4 gap-4 items-center justify-end">
<PreviousDiffIndicator {...serie.metrics.previous[metric]} />
<div className="font-bold">

View File

@@ -19,9 +19,16 @@ interface ReportHistogramChartProps {
interval: IInterval;
}
function BarHover(props: any) {
function BarHover({ x, y, width, height, top, left, right, bottom }: any) {
const bg = theme?.colors?.slate?.['200'] as string;
return <rect {...props} rx="8" fill={bg} fill-opacity={0.5} />;
return (
<rect
{...{ x, y, width, height, top, left, right, bottom }}
rx="8"
fill={bg}
fillOpacity={0.5}
/>
);
}
export function ReportHistogramChart({
@@ -38,7 +45,7 @@ export function ReportHistogramChart({
<>
<div
className={cn(
'max-sm:-mx-3',
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4'
)}
>

View File

@@ -1,3 +1,5 @@
'use client';
import React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { AutoSizer } from '@/components/AutoSizer';
@@ -41,7 +43,7 @@ export function ReportLineChart({
<>
<div
className={cn(
'max-sm:-mx-3',
'max-sm:-mx-3 aspect-video w-full max-h-[400px] min-h-[200px]',
editMode && 'border border-border bg-white rounded-md p-4'
)}
>

View File

@@ -1,3 +1,5 @@
'use client';
import type { IChartData } from '@/app/_trpc/client';
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import { cn } from '@/utils/cn';

View File

@@ -1,3 +1,5 @@
'use client';
import * as React from 'react';
import type { IChartData } from '@/app/_trpc/client';
import { Pagination, usePagination } from '@/components/Pagination';

View File

@@ -0,0 +1,65 @@
import { NOT_SET_VALUE } from '@/utils/constants';
import type { LucideIcon, LucideProps } from 'lucide-react';
import {
ActivityIcon,
ExternalLinkIcon,
HelpCircleIcon,
MonitorIcon,
MonitorPlayIcon,
PhoneIcon,
SmartphoneIcon,
SquareAsteriskIcon,
TabletIcon,
TabletSmartphoneIcon,
TwitterIcon,
} from 'lucide-react';
import {
getKeys,
getNetworks,
networkFor,
register,
SocialIcon,
} from 'react-social-icons';
interface SerieIconProps extends LucideProps {
name: string;
}
const mapper: Record<string, LucideIcon> = {
screen_view: MonitorPlayIcon,
session_start: ActivityIcon,
link_out: ExternalLinkIcon,
mobile: SmartphoneIcon,
desktop: MonitorIcon,
tablet: TabletIcon,
[NOT_SET_VALUE]: HelpCircleIcon,
};
const networks = getNetworks();
register('duckduckgo', {
color: 'red',
path: 'https://duckduckgo.com/favicon.ico',
});
export function SerieIcon({ name, ...props }: SerieIconProps) {
let Icon = mapper[name] ?? null;
if (name.includes('http')) {
Icon = ((_props) => (
<SocialIcon network={networkFor(name)} />
)) as LucideIcon;
}
if (Icon === null && networks.includes(name.toLowerCase())) {
Icon = ((_props) => (
<SocialIcon network={name.toLowerCase()} />
)) as LucideIcon;
}
return (
<div className="w-4 h-4 flex-shrink-0 relative [&_a]:!w-4 [&_a]:!h-4 [&_svg]:!rounded">
{Icon ? <Icon size={16} {...props} /> : null}
</div>
);
}

View File

@@ -1,12 +1,12 @@
'use client';
import { memo } from 'react';
import { memo, useEffect, useState } from 'react';
import type { RouterOutputs } from '@/app/_trpc/client';
import { api } from '@/app/_trpc/client';
import { useAppParams } from '@/hooks/useAppParams';
import type { IChartInput } from '@/types';
import { ChartEmpty } from './ChartEmpty';
import { ChartLoading } from './ChartLoading';
import { withChartProivder } from './ChartProvider';
import { ReportAreaChart } from './ReportAreaChart';
import { ReportBarChart } from './ReportBarChart';
@@ -33,9 +33,8 @@ export const Chart = memo(
formula,
unit,
metric,
initialData,
projectId,
}: ReportChartProps) {
const params = useAppParams();
const [data] = api.chart.chart.useSuspenseQuery(
{
// dont send lineType since it does not need to be sent
@@ -48,7 +47,7 @@ export const Chart = memo(
range,
startDate: null,
endDate: null,
projectId: params.projectId,
projectId,
previous,
formula,
unit,
@@ -56,7 +55,6 @@ export const Chart = memo(
},
{
keepPreviousData: true,
initialData,
}
);

View File

@@ -1,11 +1,11 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import type { IChartEvent } from '@/types';
import { cn } from '@/utils/cn';
import { DatabaseIcon, FilterIcon } from 'lucide-react';
import { DatabaseIcon } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
import { changeEvent } from '../reportSlice';
interface EventPropertiesComboboxProps {
@@ -16,7 +16,7 @@ export function EventPropertiesCombobox({
event,
}: EventPropertiesComboboxProps) {
const dispatch = useDispatch();
const { projectId } = useAppParams();
const { projectId } = useChartContext();
const query = api.chart.properties.useQuery(
{

View File

@@ -3,21 +3,21 @@
import { api } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux';
import type { IChartBreakdown } from '@/types';
import { SplitIcon } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
import { ReportBreakdownMore } from './ReportBreakdownMore';
import type { ReportEventMoreProps } from './ReportEventMore';
export function ReportBreakdowns() {
const params = useAppParams();
const { projectId } = useChartContext();
const selectedBreakdowns = useSelector((state) => state.report.breakdowns);
const dispatch = useDispatch();
const propertiesQuery = api.chart.properties.useQuery({
projectId: params.projectId,
projectId,
});
const propertiesCombobox = (propertiesQuery.data ?? []).map((item) => ({
value: item,

View File

@@ -6,12 +6,12 @@ import { Dropdown } from '@/components/Dropdown';
import { Checkbox } from '@/components/ui/checkbox';
import { Combobox } from '@/components/ui/combobox';
import { Input } from '@/components/ui/input';
import { useAppParams } from '@/hooks/useAppParams';
import { useDebounceFn } from '@/hooks/useDebounceFn';
import { useDispatch, useSelector } from '@/redux';
import type { IChartEvent } from '@/types';
import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
import { useChartContext } from '../chart/ChartProvider';
import {
addEvent,
changeEvent,
@@ -28,9 +28,9 @@ export function ReportEvents() {
const previous = useSelector((state) => state.report.previous);
const selectedEvents = useSelector((state) => state.report.events);
const dispatch = useDispatch();
const params = useAppParams();
const { projectId } = useChartContext();
const eventsQuery = api.chart.events.useQuery({
projectId: params.projectId,
projectId,
});
const eventsCombobox = (eventsQuery.data ?? []).map((item) => ({
value: item.name,

View File

@@ -4,7 +4,6 @@ import { Dropdown } from '@/components/Dropdown';
import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { RenderDots } from '@/components/ui/RenderDots';
import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings';
import { useDispatch } from '@/redux';
import type {
@@ -14,8 +13,8 @@ import type {
} from '@/types';
import { operators } from '@/utils/constants';
import { SlidersHorizontal, Trash } from 'lucide-react';
import { useParams } from 'next/navigation';
import { useChartContext } from '../../chart/ChartProvider';
import { changeEvent } from '../../reportSlice';
interface FilterProps {
@@ -24,7 +23,7 @@ interface FilterProps {
}
export function FilterItem({ filter, event }: FilterProps) {
const { projectId } = useAppParams();
const { projectId } = useChartContext();
const getLabel = useMappings();
const dispatch = useDispatch();
const potentialValues = api.chart.values.useQuery({

View File

@@ -1,10 +1,10 @@
import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux';
import type { IChartEvent } from '@/types';
import { FilterIcon } from 'lucide-react';
import { useChartContext } from '../../chart/ChartProvider';
import { changeEvent } from '../../reportSlice';
interface FiltersComboboxProps {
@@ -13,7 +13,7 @@ interface FiltersComboboxProps {
export function FiltersCombobox({ event }: FiltersComboboxProps) {
const dispatch = useDispatch();
const { projectId } = useAppParams();
const { projectId } = useChartContext();
const query = api.chart.properties.useQuery(
{