dashboard: add retention and quick fix loading states
This commit is contained in:
@@ -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
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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
|
||||
|
||||
22
apps/dashboard/src/components/full-page-loading-state.tsx
Normal file
22
apps/dashboard/src/components/full-page-loading-state.tsx
Normal 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;
|
||||
@@ -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 />);
|
||||
|
||||
@@ -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>
|
||||
));
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user