feat(dashboard): added new Pages

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-09-04 11:19:10 +02:00
parent 4e5adbedff
commit 7e941080dc
26 changed files with 635 additions and 132 deletions

View File

@@ -17,7 +17,7 @@ const ListDashboardsServer = async ({ projectId }: Props) => {
return (
<Padding>
<HeaderDashboards />
<ListDashboards dashboards={dashboards} />;
<ListDashboards dashboards={dashboards} />
</Padding>
);
};

View File

@@ -9,6 +9,7 @@ import { useUser } from '@clerk/nextjs';
import {
GanttChartIcon,
Globe2Icon,
LayersIcon,
LayoutPanelTopIcon,
PlusIcon,
ScanEyeIcon,
@@ -89,6 +90,11 @@ export default function LayoutMenu({ dashboards }: LayoutMenuProps) {
label="Dashboards"
href={`/${params.organizationSlug}/${projectId}/dashboards`}
/>
<LinkWithIcon
icon={LayersIcon}
label="Pages"
href={`/${params.organizationSlug}/${projectId}/pages`}
/>
<LinkWithIcon
icon={Globe2Icon}
label="Realtime"

View File

@@ -0,0 +1,34 @@
import { PageTabs, PageTabsLink } from '@/components/page-tabs';
import { Padding } from '@/components/ui/padding';
import { parseAsStringEnum } from 'nuqs';
import { Pages } from './pages';
interface PageProps {
params: {
projectId: string;
};
searchParams: {
tab: string;
};
}
export default function Page({
params: { projectId },
searchParams,
}: PageProps) {
const tab = parseAsStringEnum(['pages', 'trends'])
.withDefault('pages')
.parseServerSide(searchParams.tab);
return (
<Padding>
<PageTabs className="mb-4">
<PageTabsLink href="?tab=pages" isActive={tab === 'pages'}>
Pages
</PageTabsLink>
</PageTabs>
{tab === 'pages' && <Pages projectId={projectId} />}
</Padding>
);
}

View File

@@ -0,0 +1,114 @@
'use client';
import { LazyChart } from '@/components/report/chart/LazyChart';
import { useNumber } from '@/hooks/useNumerFormatter';
import { cn } from '@/utils/cn';
import { ExternalLinkIcon } from 'lucide-react';
import type { IServicePage } from '@openpanel/db';
export function PagesTable({ data }: { data: IServicePage[] }) {
const number = useNumber();
const cell =
'flex min-h-12 whitespace-nowrap px-4 align-middle shadow-[0_0_0_0.5px] shadow-border';
return (
<div className="overflow-x-auto rounded-md border bg-background">
<div className={cn('min-w-[800px]')}>
<div className="grid grid-cols-[0.2fr_auto_1fr] overflow-hidden rounded-t-none border-b">
<div className="center-center h-10 rounded-tl-md bg-def-100 p-4 font-semibold text-muted-foreground">
Views
</div>
<div className="flex h-10 w-80 items-center bg-def-100 p-4 font-semibold text-muted-foreground">
Path
</div>
<div className="flex h-10 items-center rounded-tr-md bg-def-100 p-4 font-semibold text-muted-foreground">
Chart
</div>
</div>
{data.map((item, index) => {
return (
<div
key={item.path + item.origin + item.title}
className="grid grid-cols-[0.2fr_auto_1fr] border-b transition-colors last:border-b-0 hover:bg-muted/50 data-[state=selected]:bg-muted"
>
<div
className={cn(
cell,
'center-center font-mono text-lg font-semibold',
index === data.length - 1 && 'rounded-bl-md'
)}
>
{number.short(item.count)}
</div>
<div
className={cn(
cell,
'flex w-80 flex-col justify-center gap-2 text-left'
)}
>
<span className="truncate font-medium">{item.title}</span>
{item.origin ? (
<a
href={item.origin + item.path}
className="truncate font-mono text-sm text-muted-foreground underline"
>
<ExternalLinkIcon className="mr-2 inline-block size-3" />
{item.path}
</a>
) : (
<span className="truncate font-mono text-sm text-muted-foreground">
{item.path}
</span>
)}
</div>
<div
className={cn(
cell,
'p-1',
index === data.length - 1 && 'rounded-br-md'
)}
>
<LazyChart
hideYAxis
hideXAxis
className="w-full"
lineType="linear"
breakdowns={[]}
name="screen_view"
metric="sum"
range="30d"
interval="day"
previous
aspectRatio={0.15}
chartType="linear"
projectId={item.project_id}
events={[
{
id: 'A',
name: 'screen_view',
segment: 'event',
filters: [
{
id: 'path',
name: 'path',
value: [item.path],
operator: 'is',
},
{
id: 'origin',
name: 'origin',
value: [item.origin],
operator: 'is',
},
],
},
]}
/>
</div>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,65 @@
'use client';
import { TableButtons } from '@/components/data-table';
import { Pagination } from '@/components/pagination';
import { Input } from '@/components/ui/input';
import { TableSkeleton } from '@/components/ui/table';
import { useDebounceValue } from '@/hooks/useDebounceValue';
import { api } from '@/trpc/client';
import { Loader2Icon } from 'lucide-react';
import { parseAsInteger, useQueryState } from 'nuqs';
import { PagesTable } from './pages-table';
export function Pages({ projectId }: { projectId: string }) {
const take = 20;
const [cursor, setCursor] = useQueryState(
'cursor',
parseAsInteger.withDefault(0)
);
const [search, setSearch] = useQueryState('search', {
defaultValue: '',
shallow: true,
});
const debouncedSearch = useDebounceValue(search, 500);
const query = api.event.pages.useQuery(
{
projectId,
cursor,
take,
search: debouncedSearch,
},
{
keepPreviousData: true,
}
);
const data = query.data ?? [];
return (
<>
<TableButtons>
<Input
placeholder="Serch path"
value={search ?? ''}
onChange={(e) => {
setSearch(e.target.value);
setCursor(0);
}}
/>
</TableButtons>
{query.isLoading ? (
<TableSkeleton cols={3} />
) : (
<PagesTable data={data} />
)}
<Pagination
className="mt-2"
setCursor={setCursor}
cursor={cursor}
count={Infinity}
take={take}
loading={query.isFetching}
/>
</>
);
}