dashboard: add retention and quick fix loading states

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-05-01 14:15:31 +02:00
parent c3815bf6ab
commit 5e743a3502
52 changed files with 1324 additions and 205 deletions

View File

@@ -20,7 +20,7 @@ export function Card({ children, hover, className }: CardProps) {
<div
className={cn(
'card relative',
hover && 'transition-all hover:border-black',
hover && 'transition-all hover:-translate-y-0.5',
className
)}
>

View File

@@ -41,7 +41,7 @@ export function ChartSSR({
}
return (
<div className="@container relative h-full w-full">
<div className="relative h-full w-full">
{/* Chart area */}
<svg className="absolute inset-0 h-full w-full overflow-visible">
<svg

View File

@@ -0,0 +1,22 @@
import type { LucideIcon } from 'lucide-react';
import { Loader2Icon } from 'lucide-react';
import { FullPageEmptyState } from './full-page-empty-state';
const FullPageLoadingState = () => {
return (
<FullPageEmptyState
className="min-h-[calc(100vh-theme(spacing.16))]"
title="Fetching..."
icon={
((props) => (
<Loader2Icon {...props} className="animate-spin" />
)) as LucideIcon
}
>
Wait a moment while we fetch your dashboards
</FullPageEmptyState>
);
};
export default FullPageLoadingState;

View File

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

View File

@@ -0,0 +1,20 @@
import { Button } from '@/components/ui/button';
import withSuspense from '@/hocs/with-suspense';
import { Globe2Icon } from 'lucide-react';
import { getShareByProjectId } from '@openpanel/db';
import { OverviewShare } from './overview-share';
type Props = {
projectId: string;
};
const OverviewShareServer = async ({ projectId }: Props) => {
const share = await getShareByProjectId(projectId);
return <OverviewShare data={share} />;
};
export default withSuspense(OverviewShareServer, () => (
<Button icon={Globe2Icon}>Private</Button>
));

View File

@@ -1,5 +1,13 @@
'use client';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { pushModal } from '@/modals';
import { api } from '@/trpc/client';
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
@@ -8,15 +16,6 @@ import { useRouter } from 'next/navigation';
import type { ShareOverview } from '@openpanel/db';
import { Button } from '../ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
interface OverviewShareProps {
data: ShareOverview | null;
}

View File

@@ -9,6 +9,7 @@ import type { IChartData } from '@/trpc/client';
import { getChartColor } from '@/utils/theme';
import {
CartesianGrid,
Legend,
Line,
LineChart,
ReferenceLine,
@@ -79,6 +80,7 @@ export function ReportLineChart({
allowDecimals={false}
tickFormatter={number.short}
/>
<Legend wrapperStyle={{ fontSize: '10px' }} />
<Tooltip content={<ReportChartTooltip />} />
<XAxis
axisLine={false}

View File

@@ -10,6 +10,25 @@ interface Props<T> {
className?: string;
}
export const WidgetTableHead = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<thead
className={cn(
'sticky top-0 z-10 border-b border-border bg-slate-50 text-sm text-slate-500 [&_th:last-child]:text-right [&_th]:whitespace-nowrap [&_th]:p-4 [&_th]:py-2 [&_th]:text-left [&_th]:font-medium',
className
)}
>
{children}
</thead>
);
};
export function WidgetTable<T>({
className,
columns,
@@ -18,13 +37,13 @@ export function WidgetTable<T>({
}: Props<T>) {
return (
<table className={cn('w-full', className)}>
<thead className="sticky top-0 z-50 border-b border-border bg-slate-50 text-sm text-slate-500 [&_th:last-child]:text-right [&_th]:whitespace-nowrap [&_th]:p-4 [&_th]:py-2 [&_th]:text-left [&_th]:font-medium">
<WidgetTableHead>
<tr>
{columns.map((column) => (
<th key={column.name}>{column.name}</th>
))}
</tr>
</thead>
</WidgetTableHead>
<tbody>
{data.map((item) => (
<tr