fix(dashboard): reduce re-renders for pages table
This commit is contained in:
@@ -1,114 +1,123 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { memo } from 'react';
|
||||||
import { LazyChart } from '@/components/report/chart/LazyChart';
|
import { LazyChart } from '@/components/report/chart/LazyChart';
|
||||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
|
import isEqual from 'lodash.isequal';
|
||||||
import { ExternalLinkIcon } from 'lucide-react';
|
import { ExternalLinkIcon } from 'lucide-react';
|
||||||
|
|
||||||
import type { IServicePage } from '@openpanel/db';
|
import type { IServicePage } from '@openpanel/db';
|
||||||
|
|
||||||
export function PagesTable({ data }: { data: IServicePage[] }) {
|
export const PagesTable = memo(
|
||||||
const number = useNumber();
|
({ data }: { data: IServicePage[] }) => {
|
||||||
const cell =
|
const number = useNumber();
|
||||||
'flex min-h-12 whitespace-nowrap px-4 align-middle shadow-[0_0_0_0.5px] shadow-border';
|
const cell =
|
||||||
return (
|
'flex min-h-12 whitespace-nowrap px-4 align-middle shadow-[0_0_0_0.5px] shadow-border';
|
||||||
<div className="overflow-x-auto rounded-md border bg-background">
|
return (
|
||||||
<div className={cn('min-w-[800px]')}>
|
<div className="overflow-x-auto rounded-md border bg-background">
|
||||||
<div className="grid grid-cols-[0.2fr_auto_1fr] overflow-hidden rounded-t-none border-b">
|
<div className={cn('min-w-[800px]')}>
|
||||||
<div className="center-center h-10 rounded-tl-md bg-def-100 p-4 font-semibold text-muted-foreground">
|
<div className="grid grid-cols-[0.2fr_auto_1fr] overflow-hidden rounded-t-none border-b">
|
||||||
Views
|
<div className="center-center h-10 rounded-tl-md bg-def-100 p-4 font-semibold text-muted-foreground">
|
||||||
</div>
|
Views
|
||||||
<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 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>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
},
|
||||||
}
|
(prevProps, nextProps) => {
|
||||||
|
return isEqual(prevProps.data, nextProps.data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PagesTable.displayName = 'PagesTable';
|
||||||
|
|||||||
@@ -618,7 +618,7 @@ export async function getTopPages({
|
|||||||
WHERE name = 'screen_view'
|
WHERE name = 'screen_view'
|
||||||
AND project_id = ${escape(projectId)}
|
AND project_id = ${escape(projectId)}
|
||||||
AND created_at > now() - INTERVAL 30 DAY
|
AND created_at > now() - INTERVAL 30 DAY
|
||||||
${search ? `AND path LIKE '%${search}%'` : ''}
|
${search ? `AND path ILIKE '%${search}%'` : ''}
|
||||||
GROUP BY path, project_id, origin
|
GROUP BY path, project_id, origin
|
||||||
ORDER BY count desc
|
ORDER BY count desc
|
||||||
LIMIT ${take}
|
LIMIT ${take}
|
||||||
|
|||||||
Reference in New Issue
Block a user