import { FullPageEmptyState } from '@/components/full-page-empty-state'; import { OverviewFilterButton } from '@/components/overview/filters/overview-filters-buttons'; import { OverviewInterval } from '@/components/overview/overview-interval'; import { OverviewRange } from '@/components/overview/overview-range'; import { useOverviewOptions } from '@/components/overview/useOverviewOptions'; import { PageContainer } from '@/components/page-container'; import { PageHeader } from '@/components/page-header'; import { Pagination } from '@/components/pagination'; import { FloatingPagination } from '@/components/pagination-floating'; import { ReportChart } from '@/components/report-chart'; import { Skeleton } from '@/components/skeleton'; import { Input } from '@/components/ui/input'; import { TableButtons } from '@/components/ui/table'; import { useAppContext } from '@/hooks/use-app-context'; import { useEventQueryFilters } from '@/hooks/use-event-query-filters'; import { useNumber } from '@/hooks/use-numer-formatter'; import { useSearchQueryState } from '@/hooks/use-search-query-state'; import { useTRPC } from '@/integrations/trpc/react'; import type { RouterOutputs } from '@/trpc/client'; import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; import type { IChartRange, IInterval } from '@openpanel/validation'; import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import { parseAsInteger, useQueryState } from 'nuqs'; import { memo } from 'react'; export const Route = createFileRoute('/_app/$organizationId/$projectId/pages')( { component: Component, head: () => { return { meta: [ { title: createProjectTitle(PAGE_TITLES.PAGES), }, ], }; }, }, ); function Component() { const { projectId } = Route.useParams(); const trpc = useTRPC(); const take = 20; const { range, interval } = useOverviewOptions(); const [filters] = useEventQueryFilters(); const [cursor, setCursor] = useQueryState( 'cursor', parseAsInteger.withDefault(1), ); const { debouncedSearch, setSearch, search } = useSearchQueryState(); const query = useQuery( trpc.event.pages.queryOptions( { projectId, cursor, take, search: debouncedSearch, range, interval, filters, }, { placeholderData: keepPreviousData, }, ), ); const data = query.data ?? []; return ( { setSearch(e.target.value); setCursor(0); }} /> {data.length === 0 && !query.isLoading && ( )} {query.isLoading && (
)}
{data.map((page) => { return ( ); })}
{data.length !== 0 && (
1 ? () => setCursor(1) : undefined} canNextPage={true} canPreviousPage={cursor > 0} pageIndex={cursor - 1} nextPage={() => { setCursor((p) => p + 1); }} previousPage={() => { setCursor((p) => p - 1); }} />
)}
); } const PageCard = memo( ({ page, range, interval, projectId, }: { page: RouterOutputs['event']['pages'][number]; range: IChartRange; interval: IInterval; projectId: string; }) => { const number = useNumber(); const { apiUrl } = useAppContext(); return (
{page.title}
{page.title}
{page.path}
{number.formatWithUnit(page.avg_duration, 'min')}
duration
{number.formatWithUnit(page.bounce_rate / 100, '%')}
bounce rate
{number.format(page.sessions)}
sessions
); }, ); const PageCardSkeleton = memo(() => { return (
); });