feature(dashboard): add new retention chart type
This commit is contained in:
committed by
Carl-Gerhard Lindesvärd
parent
e2065da16e
commit
f977c5454a
@@ -25,9 +25,11 @@ export function getYAxisWidth(value: string | undefined | null) {
|
||||
export const useYAxisProps = ({
|
||||
data,
|
||||
hide,
|
||||
tickFormatter,
|
||||
}: {
|
||||
data: number[];
|
||||
hide?: boolean;
|
||||
tickFormatter?: (value: number) => string;
|
||||
}) => {
|
||||
const [width, setWidth] = useState(24);
|
||||
const setWidthDebounced = useDebounceFn(setWidth, 100);
|
||||
@@ -41,7 +43,7 @@ export const useYAxisProps = ({
|
||||
tickLine: false,
|
||||
allowDecimals: false,
|
||||
tickFormatter: (value: number) => {
|
||||
const tick = number.short(value);
|
||||
const tick = tickFormatter ? tickFormatter(value) : number.short(value);
|
||||
const newWidth = getYAxisWidth(tick);
|
||||
ref.current.push(newWidth);
|
||||
setWidthDebounced(Math.max(...ref.current));
|
||||
|
||||
@@ -1,13 +1,62 @@
|
||||
import { BirdIcon } from 'lucide-react';
|
||||
import { cn } from '@/utils/cn';
|
||||
import {
|
||||
ArrowUpLeftIcon,
|
||||
BirdIcon,
|
||||
CornerLeftUpIcon,
|
||||
Forklift,
|
||||
ForkliftIcon,
|
||||
} from 'lucide-react';
|
||||
import { useReportChartContext } from '../context';
|
||||
|
||||
export function ReportChartEmpty({
|
||||
title = 'No data',
|
||||
children,
|
||||
}: {
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const {
|
||||
isEditMode,
|
||||
report: { events },
|
||||
} = useReportChartContext();
|
||||
|
||||
if (events.length === 0) {
|
||||
return (
|
||||
<div className="card p-4 center-center h-full w-full flex-col relative">
|
||||
<div className="row gap-2 items-end absolute top-4 left-4">
|
||||
<CornerLeftUpIcon
|
||||
strokeWidth={1.2}
|
||||
className="size-8 animate-pulse text-muted-foreground"
|
||||
/>
|
||||
<div className="text-muted-foreground">Start here</div>
|
||||
</div>
|
||||
<ForkliftIcon
|
||||
strokeWidth={1.2}
|
||||
className="mb-4 size-1/3 animate-pulse text-muted-foreground"
|
||||
/>
|
||||
<div className="font-medium text-muted-foreground">
|
||||
Ready when you're
|
||||
</div>
|
||||
<div className="text-muted-foreground mt-2">
|
||||
Pick atleast one event to start visualize
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ReportChartEmpty() {
|
||||
return (
|
||||
<div className="center-center h-full w-full flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
'center-center h-full w-full flex-col',
|
||||
isEditMode && 'card p-4',
|
||||
)}
|
||||
>
|
||||
<BirdIcon
|
||||
strokeWidth={1.2}
|
||||
className="mb-4 size-10 animate-pulse text-muted-foreground"
|
||||
className="mb-4 size-1/3 animate-pulse text-muted-foreground"
|
||||
/>
|
||||
<div className="text-sm font-medium text-muted-foreground">No data</div>
|
||||
<div className="font-medium text-muted-foreground">{title}</div>
|
||||
<div className="text-muted-foreground mt-2">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { cn } from '@/utils/cn';
|
||||
import { ServerCrashIcon } from 'lucide-react';
|
||||
import { useReportChartContext } from '../context';
|
||||
|
||||
export function ReportChartError() {
|
||||
const { isEditMode } = useReportChartContext();
|
||||
return (
|
||||
<div className="center-center h-full w-full flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
'center-center h-full w-full flex-col',
|
||||
isEditMode && 'card p-4',
|
||||
)}
|
||||
>
|
||||
<ServerCrashIcon
|
||||
strokeWidth={1.2}
|
||||
className="mb-4 size-10 animate-pulse text-muted-foreground"
|
||||
|
||||
@@ -1,3 +1,88 @@
|
||||
export function ReportChartLoading() {
|
||||
return <div className="h-full w-full animate-pulse rounded bg-def-100" />;
|
||||
import { cn } from '@/utils/cn';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import {
|
||||
ActivityIcon,
|
||||
AlarmClockIcon,
|
||||
BarChart2Icon,
|
||||
BarChartIcon,
|
||||
ChartLineIcon,
|
||||
ChartPieIcon,
|
||||
LineChartIcon,
|
||||
MessagesSquareIcon,
|
||||
PieChartIcon,
|
||||
TrendingUpIcon,
|
||||
} from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useReportChartContext } from '../context';
|
||||
|
||||
const icons = [
|
||||
{ Icon: ActivityIcon, color: 'text-chart-6' },
|
||||
{ Icon: BarChart2Icon, color: 'text-chart-9' },
|
||||
{ Icon: ChartLineIcon, color: 'text-chart-0' },
|
||||
{ Icon: AlarmClockIcon, color: 'text-chart-1' },
|
||||
{ Icon: ChartPieIcon, color: 'text-chart-2' },
|
||||
{ Icon: MessagesSquareIcon, color: 'text-chart-3' },
|
||||
{ Icon: BarChartIcon, color: 'text-chart-4' },
|
||||
{ Icon: TrendingUpIcon, color: 'text-chart-5' },
|
||||
{ Icon: PieChartIcon, color: 'text-chart-7' },
|
||||
{ Icon: LineChartIcon, color: 'text-chart-8' },
|
||||
];
|
||||
|
||||
export function ReportChartLoading({ things }: { things?: boolean }) {
|
||||
const { isEditMode } = useReportChartContext();
|
||||
const [currentIconIndex, setCurrentIconIndex] = React.useState(0);
|
||||
const [isSlow, setSlow] = useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentIconIndex((prevIndex) => (prevIndex + 1) % icons.length);
|
||||
}, 1500);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentIconIndex >= 3) {
|
||||
setSlow(true);
|
||||
}
|
||||
}, [currentIconIndex]);
|
||||
|
||||
const { Icon, color } = icons[currentIconIndex]!;
|
||||
|
||||
return (
|
||||
<div className={cn('h-full w-full', isEditMode && 'card p-4')}>
|
||||
<div
|
||||
className={
|
||||
'relative h-full w-full rounded bg-def-100 overflow-hidden center-center flex'
|
||||
}
|
||||
>
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
<motion.div
|
||||
key={currentIconIndex}
|
||||
initial={{ x: '100%', opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: '-100%', opacity: 0 }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
stiffness: 500,
|
||||
damping: 30,
|
||||
duration: 0.5,
|
||||
}}
|
||||
className={cn('absolute size-1/3', color)}
|
||||
>
|
||||
<Icon className="w-full h-full" />
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'absolute top-3/4 opacity-0 transition-opacity text-muted-foreground',
|
||||
isSlow && 'opacity-100',
|
||||
)}
|
||||
>
|
||||
Stay calm, its coming 🙄
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { IChartData } from '@/trpc/client';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import type * as React from 'react';
|
||||
|
||||
import { logDependencies } from 'mathjs';
|
||||
import { PreviousDiffIndicator } from './previous-diff-indicator';
|
||||
import { SerieName } from './serie-name';
|
||||
|
||||
@@ -80,7 +81,7 @@ export function ReportTable({
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow key={serie.id}>
|
||||
<TableRow key={`${serie.id}-1`}>
|
||||
{serie.names.map((name, nameIndex) => {
|
||||
return (
|
||||
<TableCell className="h-10" key={name}>
|
||||
@@ -140,7 +141,7 @@ export function ReportTable({
|
||||
<TableBody>
|
||||
{paginate(data.series).map((serie) => {
|
||||
return (
|
||||
<TableRow key={serie.id}>
|
||||
<TableRow key={`${serie.id}-2`}>
|
||||
<TableCell className="h-10">
|
||||
<div className="flex items-center gap-2 font-medium">
|
||||
{number.format(serie.metrics.sum)}
|
||||
|
||||
Reference in New Issue
Block a user