From ee6c68d27cdfd9574d59359cd2f0d8d4f4627f72 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Wed, 2 Jul 2025 01:44:12 -0700 Subject: [PATCH] Refactor filter handling for queries. --- .github/workflows/cd-manual.yml | 4 +- .github/workflows/cd.yml | 4 +- .../console/[websiteId]/TestConsolePage.tsx | 159 +++++++++--------- .../settings/profile/PasswordEditForm.tsx | 2 +- .../settings/users/[userId]/UserWebsites.tsx | 4 +- .../settings/websites/WebsitesDataTable.tsx | 6 +- .../settings/websites/WebsitesTable.tsx | 2 +- .../websites/[websiteId]/WebsiteChart.tsx | 21 +-- .../[websiteId]/WebsiteDetailsPage.tsx | 7 +- .../websites/[websiteId]/WebsiteMenu.tsx | 3 +- .../[websiteId]/WebsiteMetricsBar.tsx | 13 +- .../[websiteId]/reports/goals/GoalsPage.tsx | 20 ++- .../websites/[websiteId]/reports/utm/UTM.tsx | 2 +- .../sessions/SessionsDataTable.tsx | 2 +- .../sessions/[sessionId]/SessionInfo.tsx | 8 +- src/app/api/realtime/[websiteId]/route.ts | 12 +- src/app/api/reports/goal/route.ts | 7 +- src/app/api/teams/[teamId]/websites/route.ts | 6 +- src/app/api/users/[userId]/usage/route.ts | 8 +- src/app/api/users/[userId]/websites/route.ts | 6 +- .../[websiteId]/event-data/events/route.ts | 10 +- .../[websiteId]/event-data/fields/route.ts | 10 +- .../event-data/properties/route.ts | 9 +- .../[websiteId]/event-data/stats/route.ts | 7 +- .../[websiteId]/event-data/values/route.ts | 10 +- .../api/websites/[websiteId]/events/route.ts | 7 +- .../[websiteId]/events/series/route.ts | 12 +- .../api/websites/[websiteId]/metrics/route.ts | 9 +- .../websites/[websiteId]/pageviews/route.ts | 33 ++-- .../session-data/properties/route.ts | 6 +- .../[websiteId]/session-data/values/route.ts | 7 +- .../sessions/[sessionId]/activity/route.ts | 7 +- .../websites/[websiteId]/sessions/route.ts | 13 +- .../[websiteId]/sessions/stats/route.ts | 12 +- .../[websiteId]/sessions/weekly/route.ts | 8 +- .../api/websites/[websiteId]/stats/route.ts | 25 +-- .../api/websites/[websiteId]/values/route.ts | 8 +- src/components/common/DataGrid.tsx | 35 ++-- src/components/common/FilterEditForm.tsx | 2 +- src/components/hooks/index.ts | 2 +- .../hooks/queries/useDeleteQuery.ts | 2 +- .../hooks/queries/useRealtimeQuery.ts | 21 ++- .../hooks/queries/useResultQuery.ts | 4 +- src/components/hooks/queries/useUserQuery.ts | 2 +- .../hooks/queries/useWebsiteEventsQuery.ts | 6 +- .../hooks/queries/useWebsiteMetricsQuery.ts | 6 +- .../hooks/queries/useWebsitePageviewsQuery.ts | 6 +- .../hooks/queries/useWebsiteQuery.ts | 2 +- .../queries/useWebsiteSessionStatsQuery.ts | 5 +- .../hooks/queries/useWebsiteSessionsQuery.ts | 2 +- .../queries/useWebsiteSessionsWeeklyQuery.ts | 2 +- .../hooks/queries/useWebsiteStatsQuery.ts | 7 +- .../{useWebsites.ts => useWebsitesQuery.ts} | 10 +- src/components/hooks/useFormat.ts | 2 +- src/components/hooks/useMessages.ts | 2 +- src/components/hooks/useNavigation.ts | 4 +- src/components/hooks/usePagedQuery.ts | 23 ++- src/components/input/WebsiteSelect.tsx | 17 +- src/components/metrics/MetricCard.tsx | 6 - src/components/metrics/MetricsTable.tsx | 2 +- src/lib/clickhouse.ts | 55 +++--- src/lib/constants.ts | 2 +- src/lib/data.ts | 2 +- src/lib/date.ts | 8 +- src/lib/kafka.ts | 2 +- src/lib/params.ts | 2 +- src/lib/prisma.ts | 113 ++++--------- src/lib/request.ts | 39 ++++- src/lib/schema.ts | 36 ++-- src/lib/types.ts | 119 +++++-------- src/queries/prisma/report.ts | 12 +- src/queries/prisma/team.ts | 6 +- src/queries/prisma/teamUser.ts | 4 +- src/queries/prisma/user.ts | 8 +- src/queries/prisma/website.ts | 12 +- src/queries/sql/events/getEventDataEvents.ts | 12 +- src/queries/sql/events/getEventDataFields.ts | 8 +- .../sql/events/getEventDataProperties.ts | 12 +- src/queries/sql/events/getEventDataStats.ts | 8 +- src/queries/sql/events/getEventDataUsage.ts | 7 +- src/queries/sql/events/getEventDataValues.ts | 8 +- src/queries/sql/events/getEventMetrics.ts | 10 +- src/queries/sql/events/getEventUsage.ts | 7 +- src/queries/sql/events/getWebsiteEvents.ts | 60 ++++--- src/queries/sql/getChannelMetrics.ts | 8 +- src/queries/sql/getRealtimeActivity.ts | 8 +- src/queries/sql/getRealtimeData.ts | 8 +- src/queries/sql/getValues.ts | 23 +-- src/queries/sql/getWebsiteDateRange.ts | 10 +- src/queries/sql/getWebsiteStats.ts | 32 ++-- .../sql/pageviews/getPageviewMetrics.ts | 11 +- src/queries/sql/pageviews/getPageviewStats.ts | 12 +- src/queries/sql/reports/getAttribution.ts | 18 +- src/queries/sql/reports/getBreakdown.ts | 11 +- src/queries/sql/reports/getFunnel.ts | 6 +- src/queries/sql/reports/getGoal.ts | 22 ++- src/queries/sql/reports/getJourney.ts | 12 +- src/queries/sql/reports/getRetention.ts | 8 +- src/queries/sql/reports/getUTM.ts | 8 +- .../sql/sessions/getSessionActivity.ts | 20 +-- .../sql/sessions/getSessionDataProperties.ts | 8 +- .../sql/sessions/getSessionDataValues.ts | 12 +- src/queries/sql/sessions/getSessionMetrics.ts | 11 +- src/queries/sql/sessions/getSessionStats.ts | 10 +- .../sql/sessions/getWebsiteSessionStats.ts | 32 ++-- .../sql/sessions/getWebsiteSessions.ts | 55 ++---- .../sql/sessions/getWebsiteSessionsWeekly.ts | 10 +- 107 files changed, 731 insertions(+), 835 deletions(-) rename src/components/hooks/queries/{useWebsites.ts => useWebsitesQuery.ts} (73%) diff --git a/.github/workflows/cd-manual.yml b/.github/workflows/cd-manual.yml index 1f8651fa..df6aa628 100644 --- a/.github/workflows/cd-manual.yml +++ b/.github/workflows/cd-manual.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - db-type: [postgresql, mysql] + db-type: [postgresql] steps: - uses: actions/checkout@v3 @@ -55,4 +55,4 @@ jobs: buildArgs: DATABASE_TYPE=${{ matrix.db-type }} registry: docker.io username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} \ No newline at end of file + password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f67f51c3..cf275cb8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - db-type: [postgresql, mysql] + db-type: [postgresql] steps: - uses: actions/checkout@v3 @@ -47,4 +47,4 @@ jobs: buildArgs: DATABASE_TYPE=${{ matrix.db-type }} registry: docker.io username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} \ No newline at end of file + password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/src/app/(main)/console/[websiteId]/TestConsolePage.tsx b/src/app/(main)/console/[websiteId]/TestConsolePage.tsx index e0ae696b..e44ea95d 100644 --- a/src/app/(main)/console/[websiteId]/TestConsolePage.tsx +++ b/src/app/(main)/console/[websiteId]/TestConsolePage.tsx @@ -2,6 +2,7 @@ import { Button, Grid, Column, Heading } from '@umami/react-zen'; import Link from 'next/link'; import Script from 'next/script'; +import { Panel } from '@/components/common/Panel'; import { PageBody } from '@/components/common/PageBody'; import { EventsChart } from '@/components/metrics/EventsChart'; import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart'; @@ -115,87 +116,91 @@ export function TestConsolePage({ websiteId }: { websiteId: string }) { src={`${process.env.basePath || ''}/script.js`} data-cache="true" /> - - - Page links -
- page one -
-
- page two -
-
- - external link (direct) - -
-
- - external link (tab) - -
-
- - Click events - - - - -
DIV with attribute
-
-
-
Nested DIV
+ + + + Page links +
+ page one
-
- - - Javascript events - - - - - +
+ page two +
+
+ + external link (direct) + +
+
+ + external link (tab) + +
+ + + Click events + + + + +
DIV with attribute
+
+
+
Nested DIV
+
+
+
+ + Javascript events + + + + + + Pageviews Events - + + + ); diff --git a/src/app/(main)/settings/profile/PasswordEditForm.tsx b/src/app/(main)/settings/profile/PasswordEditForm.tsx index b29d4989..8d8b6b56 100644 --- a/src/app/(main)/settings/profile/PasswordEditForm.tsx +++ b/src/app/(main)/settings/profile/PasswordEditForm.tsx @@ -24,7 +24,7 @@ export function PasswordEditForm({ onSave, onClose }) { }); }; - const samePassword = (value: string, values: { [key: string]: any }) => { + const samePassword = (value: string, values: Record) => { if (value !== values.newPassword) { return formatMessage(messages.noMatchPassword); } diff --git a/src/app/(main)/settings/users/[userId]/UserWebsites.tsx b/src/app/(main)/settings/users/[userId]/UserWebsites.tsx index 023b3420..8f096353 100644 --- a/src/app/(main)/settings/users/[userId]/UserWebsites.tsx +++ b/src/app/(main)/settings/users/[userId]/UserWebsites.tsx @@ -1,9 +1,9 @@ import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable'; import { DataGrid } from '@/components/common/DataGrid'; -import { useWebsites } from '@/components/hooks'; +import { useWebsitesQuery } from '@/components/hooks'; export function UserWebsites({ userId }) { - const queryResult = useWebsites({ userId }); + const queryResult = useWebsitesQuery({ userId }); return ( diff --git a/src/app/(main)/settings/websites/WebsitesDataTable.tsx b/src/app/(main)/settings/websites/WebsitesDataTable.tsx index 48bb0922..c1aa0cd4 100644 --- a/src/app/(main)/settings/websites/WebsitesDataTable.tsx +++ b/src/app/(main)/settings/websites/WebsitesDataTable.tsx @@ -1,7 +1,7 @@ import { ReactNode } from 'react'; import { WebsitesTable } from '@/app/(main)/settings/websites/WebsitesTable'; import { DataGrid } from '@/components/common/DataGrid'; -import { useWebsites } from '@/components/hooks'; +import { useWebsitesQuery } from '@/components/hooks'; export function WebsitesDataTable({ teamId, @@ -16,10 +16,10 @@ export function WebsitesDataTable({ showActions?: boolean; children?: ReactNode; }) { - const queryResult = useWebsites({ teamId }); + const queryResult = useWebsitesQuery({ teamId }); return ( - children}> + children} allowSearch allowPaging> {({ data }) => ( - {(row: any) => {row.name}} + {(row: any) => {row.name}} {showActions && ( diff --git a/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx b/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx index 2bb2146d..743b4f62 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx @@ -3,7 +3,6 @@ import { LoadingPanel } from '@/components/common/LoadingPanel'; import { PageviewsChart } from '@/components/metrics/PageviewsChart'; import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery'; import { useDateRange } from '@/components/hooks'; -import { Panel } from '@/components/common/Panel'; export function WebsiteChart({ websiteId, @@ -48,16 +47,14 @@ export function WebsiteChart({ }, [data, startDate, endDate, unit]); return ( - - - - - + + + ); } diff --git a/src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx b/src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx index 87b3aca2..bdf7ef74 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteDetailsPage.tsx @@ -1,6 +1,7 @@ 'use client'; import { Column } from '@umami/react-zen'; import { useNavigation } from '@/components/hooks'; +import { Panel } from '@/components/common/Panel'; import { WebsiteChart } from './WebsiteChart'; import { WebsiteExpandedView } from './WebsiteExpandedView'; import { WebsiteMetricsBar } from './WebsiteMetricsBar'; @@ -16,8 +17,10 @@ export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) { return ( - - + + + + {!view && !compare && } {view && !compare && } {compare && } diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx index 55f89d83..6aa67df1 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMenu.tsx @@ -11,13 +11,12 @@ import { import { Fragment } from 'react'; import { More, Share, Edit } from '@/components/icons'; import { useMessages, useNavigation } from '@/components/hooks'; -import { InputItem } from '@/lib/types'; export function WebsiteMenu({ websiteId }: { websiteId: string }) { const { formatMessage, labels } = useMessages(); const { router, updateParams, renderUrl } = useNavigation(); - const menuItems: InputItem[] = [ + const menuItems = [ { id: 'share', label: formatMessage(labels.share), icon: }, { id: 'edit', label: formatMessage(labels.edit), icon: , seperator: true }, ]; diff --git a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx index 246fa576..d2ef3f4b 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteMetricsBar.tsx @@ -3,26 +3,18 @@ import { MetricCard } from '@/components/metrics/MetricCard'; import { MetricsBar } from '@/components/metrics/MetricsBar'; import { formatShortTime, formatLongNumber } from '@/lib/format'; import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery'; -import { useWebsites } from '@/store/websites'; import { LoadingPanel } from '@/components/common/LoadingPanel'; export function WebsiteMetricsBar({ websiteId, - showChange = false, - compareMode = false, }: { websiteId: string; showChange?: boolean; compareMode?: boolean; - showFilter?: boolean; }) { const { dateRange } = useDateRange(websiteId); const { formatMessage, labels } = useMessages(); - const dateCompare = useWebsites(state => state[websiteId]?.dateCompare); - const { data, isLoading, isFetching, error } = useWebsiteStatsQuery( - websiteId, - compareMode && dateCompare, - ); + const { data, isLoading, isFetching, error } = useWebsiteStatsQuery(websiteId); const isAllTime = dateRange.value === 'all'; const { pageviews, visitors, visits, bounces, totaltime, previous } = data || {}; @@ -87,8 +79,7 @@ export function WebsiteMetricsBar({ change={change} formatValue={formatValue} reverseColors={reverseColors} - showChange={!isAllTime && (compareMode || showChange)} - showPrevious={!isAllTime && compareMode} + showChange={!isAllTime} /> ); })} diff --git a/src/app/(main)/websites/[websiteId]/reports/goals/GoalsPage.tsx b/src/app/(main)/websites/[websiteId]/reports/goals/GoalsPage.tsx index f9923871..30f92325 100644 --- a/src/app/(main)/websites/[websiteId]/reports/goals/GoalsPage.tsx +++ b/src/app/(main)/websites/[websiteId]/reports/goals/GoalsPage.tsx @@ -9,7 +9,7 @@ import { LoadingPanel } from '@/components/common/LoadingPanel'; import { Panel } from '@/components/common/Panel'; export function GoalsPage({ websiteId }: { websiteId: string }) { - const { result, query } = useReportsQuery({ websiteId, type: 'goal' }); + const { data, isLoading, error } = useReportsQuery({ websiteId, type: 'goal' }); const { dateRange: { startDate, endDate }, } = useDateRange(websiteId); @@ -20,14 +20,16 @@ export function GoalsPage({ websiteId }: { websiteId: string }) { - - - {result?.data?.map((report: any) => ( - - - - ))} - + + {data && ( + + {data['data'].map((report: any) => ( + + + + ))} + + )} ); diff --git a/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx b/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx index afa8c842..a1469de6 100644 --- a/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx +++ b/src/app/(main)/websites/[websiteId]/reports/utm/UTM.tsx @@ -73,7 +73,7 @@ export function UTM({ websiteId, startDate, endDate }: UTMProps) { ); } -function toArray(data: { [key: string]: number } = {}) { +function toArray(data: Record = {}) { return Object.keys(data) .map(key => { return { name: key, value: data[key] }; diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx index 84a722ac..06323447 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx @@ -14,7 +14,7 @@ export function SessionsDataTable({ const queryResult = useWebsiteSessionsQuery(websiteId); return ( - children}> + children} allowPaging> {({ data }) => } ); diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx index a3336fde..3a1d6829 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionInfo.tsx @@ -1,12 +1,12 @@ import { ReactNode } from 'react'; import { Icon, TextField, Column, Row, Label, Text } from '@umami/react-zen'; -import { useFormat, useLocale, useMessages, useRegionNames, useTimezone } from '@/components/hooks'; +import { useFormat, useLocale, useMessages, useRegionNames } from '@/components/hooks'; import { TypeIcon } from '@/components/common/TypeIcon'; import { Location, KeyRound, Calendar } from '@/components/icons'; +import { DateDistance } from '@/components/common/DateDistance'; export function SessionInfo({ data }) { const { locale } = useLocale(); - const { formatTimezoneDate } = useTimezone(); const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); const { getRegionName } = useRegionNames(locale); @@ -22,11 +22,11 @@ export function SessionInfo({ data }) { }> - {formatTimezoneDate(data?.lastAt, 'PPPPpp')} + }> - {formatTimezoneDate(data?.firstAt, 'PPPPpp')} + }) { const schema = z.object({ @@ -22,14 +22,14 @@ export async function GET(request: Request, { params }: { params: Promise<{ user } const { userId } = await params; - const { startDate, endDate } = await getRequestDateRange(query); + const filters = await getQueryFilters(query); const websites = await getAllUserWebsitesIncludingTeamOwner(userId); const websiteIds = websites.map(a => a.id); - const websiteEventUsage = await getEventUsage(websiteIds, startDate, endDate); - const eventDataUsage = await getEventDataUsage(websiteIds, startDate, endDate); + const websiteEventUsage = await getEventUsage(websiteIds, filters); + const eventDataUsage = await getEventDataUsage(websiteIds, filters); const websiteUsage = websites.map(a => ({ websiteId: a.id, diff --git a/src/app/api/users/[userId]/websites/route.ts b/src/app/api/users/[userId]/websites/route.ts index 77d41084..d19d754f 100644 --- a/src/app/api/users/[userId]/websites/route.ts +++ b/src/app/api/users/[userId]/websites/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { unauthorized, json } from '@/lib/response'; import { getUserWebsites } from '@/queries/prisma/website'; import { pagingParams } from '@/lib/schema'; -import { parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) { const schema = z.object({ @@ -21,7 +21,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ user return unauthorized(); } - const websites = await getUserWebsites(userId, query); + const filters = await getQueryFilters(query); + + const websites = await getUserWebsites(userId, filters); return json(websites); } diff --git a/src/app/api/websites/[websiteId]/event-data/events/route.ts b/src/app/api/websites/[websiteId]/event-data/events/route.ts index f4b950c4..eb984207 100644 --- a/src/app/api/websites/[websiteId]/event-data/events/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/events/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getEventDataEvents } from '@/queries/sql/events/getEventDataEvents'; @@ -20,16 +20,16 @@ export async function GET( } const { websiteId } = await params; - const { event } = query; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } + const { event } = query; + const filters = await getQueryFilters(query); + const data = await getEventDataEvents(websiteId, { - startDate, - endDate, + ...filters, event, }); diff --git a/src/app/api/websites/[websiteId]/event-data/fields/route.ts b/src/app/api/websites/[websiteId]/event-data/fields/route.ts index fa31ca69..34aa6162 100644 --- a/src/app/api/websites/[websiteId]/event-data/fields/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/fields/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getEventDataFields } from '@/queries'; @@ -20,16 +20,14 @@ export async function GET( } const { websiteId } = await params; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getEventDataFields(websiteId, { - startDate, - endDate, - }); + const filters = await getQueryFilters(query); + + const data = await getEventDataFields(websiteId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/event-data/properties/route.ts b/src/app/api/websites/[websiteId]/event-data/properties/route.ts index 2fa7a335..ad39e37a 100644 --- a/src/app/api/websites/[websiteId]/event-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getEventDataProperties } from '@/queries'; @@ -21,14 +21,15 @@ export async function GET( } const { websiteId } = await params; - const { propertyName } = query; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getEventDataProperties(websiteId, { startDate, endDate, propertyName }); + const { propertyName } = query; + const filters = await getQueryFilters(query); + + const data = await getEventDataProperties(websiteId, { ...filters, propertyName }); return json(data); } diff --git a/src/app/api/websites/[websiteId]/event-data/stats/route.ts b/src/app/api/websites/[websiteId]/event-data/stats/route.ts index ccc45b75..79dd0059 100644 --- a/src/app/api/websites/[websiteId]/event-data/stats/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/stats/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getEventDataStats } from '@/queries'; @@ -21,13 +21,14 @@ export async function GET( } const { websiteId } = await params; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getEventDataStats(websiteId, { startDate, endDate }); + const filters = await getQueryFilters(query); + + const data = await getEventDataStats(websiteId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/event-data/values/route.ts b/src/app/api/websites/[websiteId]/event-data/values/route.ts index c25dc014..1eab63b5 100644 --- a/src/app/api/websites/[websiteId]/event-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getEventDataValues } from '@/queries'; @@ -22,16 +22,16 @@ export async function GET( } const { websiteId } = await params; - const { eventName, propertyName } = query; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } + const { eventName, propertyName } = query; + const filters = await getQueryFilters(query); + const data = await getEventDataValues(websiteId, { - startDate, - endDate, + ...filters, eventName, propertyName, }); diff --git a/src/app/api/websites/[websiteId]/events/route.ts b/src/app/api/websites/[websiteId]/events/route.ts index ef20dd7c..86f6dec8 100644 --- a/src/app/api/websites/[websiteId]/events/route.ts +++ b/src/app/api/websites/[websiteId]/events/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { pagingParams } from '@/lib/schema'; @@ -22,13 +22,14 @@ export async function GET( } const { websiteId } = await params; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getWebsiteEvents(websiteId, { ...query, startDate, endDate }, query); + const filters = await getQueryFilters(query); + + const data = await getWebsiteEvents(websiteId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/events/series/route.ts b/src/app/api/websites/[websiteId]/events/series/route.ts index 43bd23a7..98d26c0d 100644 --- a/src/app/api/websites/[websiteId]/events/series/route.ts +++ b/src/app/api/websites/[websiteId]/events/series/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { filterParams, timezoneParam, unitParam } from '@/lib/schema'; @@ -24,20 +24,12 @@ export async function GET( } const { websiteId } = await params; - const { timezone } = query; - const { startDate, endDate, unit } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const filters = { - ...getRequestFilters(query), - startDate, - endDate, - timezone, - unit, - }; + const filters = await getQueryFilters({ ...query, websiteId }); const data = await getEventMetrics(websiteId, filters); diff --git a/src/app/api/websites/[websiteId]/metrics/route.ts b/src/app/api/websites/[websiteId]/metrics/route.ts index 85433904..22fa5b30 100644 --- a/src/app/api/websites/[websiteId]/metrics/route.ts +++ b/src/app/api/websites/[websiteId]/metrics/route.ts @@ -13,7 +13,7 @@ import { VIDEO_DOMAINS, PAID_AD_PARAMS, } from '@/lib/constants'; -import { getRequestFilters, getRequestDateRange, parseRequest } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { json, unauthorized, badRequest } from '@/lib/response'; import { getPageviewMetrics, getSessionMetrics, getChannelMetrics } from '@/queries'; import { filterParams } from '@/lib/schema'; @@ -45,13 +45,8 @@ export async function GET( return unauthorized(); } - const { startDate, endDate } = await getRequestDateRange(query); const column = FILTER_COLUMNS[type] || type; - const filters = { - ...getRequestFilters(query), - startDate, - endDate, - }; + const filters = await getQueryFilters({ ...query, websiteId }); if (search) { filters[type] = { diff --git a/src/app/api/websites/[websiteId]/pageviews/route.ts b/src/app/api/websites/[websiteId]/pageviews/route.ts index e603ae9c..b25f1895 100644 --- a/src/app/api/websites/[websiteId]/pageviews/route.ts +++ b/src/app/api/websites/[websiteId]/pageviews/route.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { canViewWebsite } from '@/lib/auth'; -import { getRequestFilters, getRequestDateRange, parseRequest } from '@/lib/request'; -import { unitParam, timezoneParam, filterParams } from '@/lib/schema'; +import { getQueryFilters, parseRequest } from '@/lib/request'; +import { dateRangeParams, filterParams } from '@/lib/schema'; import { getCompareDate } from '@/lib/date'; import { unauthorized, json } from '@/lib/response'; import { getPageviewStats, getSessionStats } from '@/queries'; @@ -11,11 +11,7 @@ export async function GET( { params }: { params: Promise<{ websiteId: string }> }, ) { const schema = z.object({ - startAt: z.coerce.number().int(), - endAt: z.coerce.number().int(), - unit: unitParam, - timezone: timezoneParam, - compare: z.string().optional(), + ...dateRangeParams, ...filterParams, }); @@ -26,32 +22,23 @@ export async function GET( } const { websiteId } = await params; - const { timezone, compare } = query; if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const { startDate, endDate, unit } = await getRequestDateRange(query); - - const filters = { - ...getRequestFilters(query), - startDate, - endDate, - timezone, - unit, - }; + const filters = await getQueryFilters({ ...query, websiteId }); const [pageviews, sessions] = await Promise.all([ getPageviewStats(websiteId, filters), getSessionStats(websiteId, filters), ]); - if (compare) { + if (filters.compare) { const { startDate: compareStartDate, endDate: compareEndDate } = getCompareDate( - compare, - startDate, - endDate, + filters.compare, + filters.startDate, + filters.endDate, ); const [comparePageviews, compareSessions] = await Promise.all([ @@ -70,8 +57,8 @@ export async function GET( return json({ pageviews, sessions, - startDate, - endDate, + startDate: filters.startDate, + endDate: filters.endDate, compare: { pageviews: comparePageviews, sessions: compareSessions, diff --git a/src/app/api/websites/[websiteId]/session-data/properties/route.ts b/src/app/api/websites/[websiteId]/session-data/properties/route.ts index 901dcdf5..bdc7c53c 100644 --- a/src/app/api/websites/[websiteId]/session-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/properties/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getSessionDataProperties } from '@/queries'; @@ -22,13 +22,13 @@ export async function GET( const { websiteId } = await params; const { propertyName } = query; - const { startDate, endDate } = await getRequestDateRange(query); + const filters = await getQueryFilters(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getSessionDataProperties(websiteId, { startDate, endDate, propertyName }); + const data = await getSessionDataProperties(websiteId, { ...filters, propertyName }); return json(data); } diff --git a/src/app/api/websites/[websiteId]/session-data/values/route.ts b/src/app/api/websites/[websiteId]/session-data/values/route.ts index 0730a70b..5564b4d8 100644 --- a/src/app/api/websites/[websiteId]/session-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/session-data/values/route.ts @@ -1,5 +1,5 @@ import { canViewWebsite } from '@/lib/auth'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { json, unauthorized } from '@/lib/response'; import { getSessionDataValues } from '@/queries'; import { z } from 'zod'; @@ -22,15 +22,14 @@ export async function GET( const { propertyName } = query; const { websiteId } = await params; - const { startDate, endDate } = await getRequestDateRange(query); + const filters = await getQueryFilters({ ...query, websiteId }); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } const data = await getSessionDataValues(websiteId, { - startDate, - endDate, + ...filters, propertyName, }); diff --git a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts index 831d0064..5daf2dfb 100644 --- a/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/[sessionId]/activity/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { parseRequest, getRequestDateRange } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { getSessionActivity } from '@/queries'; @@ -20,13 +20,14 @@ export async function GET( } const { websiteId, sessionId } = await params; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getSessionActivity(websiteId, sessionId, startDate, endDate); + const filters = await getQueryFilters(query); + + const data = await getSessionActivity(websiteId, sessionId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/sessions/route.ts b/src/app/api/websites/[websiteId]/sessions/route.ts index 6f8a671e..c6afc638 100644 --- a/src/app/api/websites/[websiteId]/sessions/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/route.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; -import { pagingParams } from '@/lib/schema'; +import { dateRangeParams, filterParams, pagingParams } from '@/lib/schema'; import { getWebsiteSessions } from '@/queries'; export async function GET( @@ -10,8 +10,8 @@ export async function GET( { params }: { params: Promise<{ websiteId: string }> }, ) { const schema = z.object({ - startAt: z.coerce.number().int(), - endAt: z.coerce.number().int(), + ...dateRangeParams, + ...filterParams, ...pagingParams, }); @@ -21,14 +21,15 @@ export async function GET( return error(); } - const { startDate, endDate } = await getRequestDateRange(query); const { websiteId } = await params; if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getWebsiteSessions(websiteId, { startDate, endDate }, query); + const filters = await getQueryFilters({ ...query, websiteId }); + + const data = await getWebsiteSessions(websiteId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/sessions/stats/route.ts b/src/app/api/websites/[websiteId]/sessions/stats/route.ts index e8e8e6c8..a1746514 100644 --- a/src/app/api/websites/[websiteId]/sessions/stats/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/stats/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { filterParams } from '@/lib/schema'; @@ -27,15 +27,9 @@ export async function GET( return unauthorized(); } - const { startDate, endDate } = await getRequestDateRange(query); + const filters = await getQueryFilters(query); - const filters = getRequestFilters(query); - - const metrics = await getWebsiteSessionStats(websiteId, { - ...filters, - startDate, - endDate, - }); + const metrics = await getWebsiteSessionStats(websiteId, filters); const data = Object.keys(metrics[0]).reduce((obj, key) => { obj[key] = { diff --git a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts index a8216123..757e9da3 100644 --- a/src/app/api/websites/[websiteId]/sessions/weekly/route.ts +++ b/src/app/api/websites/[websiteId]/sessions/weekly/route.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { getRequestDateRange, parseRequest } from '@/lib/request'; +import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; import { pagingParams, timezoneParam } from '@/lib/schema'; @@ -23,14 +23,14 @@ export async function GET( } const { websiteId } = await params; - const { timezone } = query; - const { startDate, endDate } = await getRequestDateRange(query); if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const data = await getWebsiteSessionsWeekly(websiteId, { startDate, endDate, timezone }); + const filters = await getQueryFilters(query); + + const data = await getWebsiteSessionsWeekly(websiteId, filters); return json(data); } diff --git a/src/app/api/websites/[websiteId]/stats/route.ts b/src/app/api/websites/[websiteId]/stats/route.ts index 70c0110e..8e2dd3cb 100644 --- a/src/app/api/websites/[websiteId]/stats/route.ts +++ b/src/app/api/websites/[websiteId]/stats/route.ts @@ -1,8 +1,7 @@ import { z } from 'zod'; -import { parseRequest, getRequestDateRange, getRequestFilters } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/lib/auth'; -import { getCompareDate } from '@/lib/date'; import { filterParams } from '@/lib/schema'; import { getWebsiteStats } from '@/queries'; @@ -24,32 +23,20 @@ export async function GET( } const { websiteId } = await params; - const { compare } = query; if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); } - const { startDate, endDate } = await getRequestDateRange(query); - const { startDate: compareStartDate, endDate: compareEndDate } = getCompareDate( - compare, - startDate, - endDate, - ); + const filters = await getQueryFilters({ ...query, websiteId }); - const filters = getRequestFilters(query); - - const metrics = await getWebsiteStats(websiteId, { - ...filters, - startDate, - endDate, - }); + const data = await getWebsiteStats(websiteId, filters); const previous = await getWebsiteStats(websiteId, { ...filters, - startDate: compareStartDate, - endDate: compareEndDate, + startDate: filters.compareStartDate, + endDate: filters.compareEndDate, }); - return json({ ...metrics, previous }); + return json({ ...data, previous }); } diff --git a/src/app/api/websites/[websiteId]/values/route.ts b/src/app/api/websites/[websiteId]/values/route.ts index 4dcab3a0..290e0db8 100644 --- a/src/app/api/websites/[websiteId]/values/route.ts +++ b/src/app/api/websites/[websiteId]/values/route.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { canViewWebsite } from '@/lib/auth'; import { EVENT_COLUMNS, FILTER_COLUMNS, SESSION_COLUMNS } from '@/lib/constants'; import { getValues } from '@/queries'; -import { parseRequest, getRequestDateRange } from '@/lib/request'; +import { parseRequest, getQueryFilters } from '@/lib/request'; import { badRequest, json, unauthorized } from '@/lib/response'; export async function GET( @@ -23,7 +23,7 @@ export async function GET( } const { websiteId } = await params; - const { type, search } = query; + const { type } = query; if (!(await canViewWebsite(auth, websiteId))) { return unauthorized(); @@ -33,9 +33,9 @@ export async function GET( return badRequest('Invalid type.'); } - const { startDate, endDate } = await getRequestDateRange(query); + const filters = await getQueryFilters(query); - const values = await getValues(websiteId, FILTER_COLUMNS[type], startDate, endDate, search); + const values = await getValues(websiteId, FILTER_COLUMNS[type], { ...filters }); return json(values.filter(n => n).sort()); } diff --git a/src/components/common/DataGrid.tsx b/src/components/common/DataGrid.tsx index 339a9c8c..5b23b8f6 100644 --- a/src/components/common/DataGrid.tsx +++ b/src/components/common/DataGrid.tsx @@ -1,14 +1,13 @@ -import { ReactNode } from 'react'; +import { ReactNode, useState } from 'react'; import { SearchField, Row, Column } from '@umami/react-zen'; import { useMessages, useNavigation } from '@/components/hooks'; import { Pager } from '@/components/common/Pager'; import { LoadingPanel } from '@/components/common/LoadingPanel'; -import { PagedQueryResult } from '@/lib/types'; const DEFAULT_SEARCH_DELAY = 600; export interface DataTableProps { - queryResult: PagedQueryResult; + queryResult: any; searchDelay?: number; allowSearch?: boolean; allowPaging?: boolean; @@ -20,31 +19,30 @@ export interface DataTableProps { export function DataGrid({ queryResult, searchDelay = 600, - allowSearch = true, - allowPaging = true, + allowSearch, + allowPaging, autoFocus, children, }: DataTableProps) { const { formatMessage, labels } = useMessages(); - const { result, params, setParams, query } = queryResult || {}; - const { error, isLoading, isFetching } = query || {}; - const { page, pageSize, count, data } = result || {}; - const { search } = params || {}; - const hasData = Boolean(!isLoading && data?.length); + const { data, error, isLoading, isFetching, setParams } = queryResult || {}; const { router, updateParams } = useNavigation(); + const [search, setSearch] = useState(''); const handleSearch = (search: string) => { - setParams({ ...params, search }); + setSearch(search); + setParams(params => ({ ...params, search })); + router.push(updateParams({ search })); }; const handlePageChange = (page: number) => { - setParams({ ...params, page }); + setParams(params => ({ ...params, page })); router.push(updateParams({ page })); }; return ( - {allowSearch && (hasData || search) && ( + {allowSearch && (data || search) && ( - {hasData ? (typeof children === 'function' ? children(result) : children) : null} + {data ? (typeof children === 'function' ? children(data) : children) : null} - {allowPaging && hasData && ( + {allowPaging && data && ( - + )} diff --git a/src/components/common/FilterEditForm.tsx b/src/components/common/FilterEditForm.tsx index 8448dab8..850bd91d 100644 --- a/src/components/common/FilterEditForm.tsx +++ b/src/components/common/FilterEditForm.tsx @@ -19,7 +19,7 @@ export function FilterEditForm({ websiteId, data = [], onChange, onClose }: Filt dateRange: { startDate, endDate }, } = useDateRange(websiteId); - const updateFilter = (name: string, props: { [key: string]: any }) => { + const updateFilter = (name: string, props: Record) => { setFilters(filters => filters.map(filter => (filter.name === name ? { ...filter, ...props } : filter)), ); diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts index 28a55345..5fb5c61e 100644 --- a/src/components/hooks/index.ts +++ b/src/components/hooks/index.ts @@ -24,7 +24,7 @@ export * from './queries/useTeamMembersQuery'; export * from './queries/useUserQuery'; export * from './queries/useUsersQuery'; export * from './queries/useWebsiteQuery'; -export * from './queries/useWebsites'; +export * from './queries/useWebsitesQuery'; export * from './queries/useWebsiteEventsQuery'; export * from './queries/useWebsiteEventsSeriesQuery'; export * from './queries/useWebsiteMetricsQuery'; diff --git a/src/components/hooks/queries/useDeleteQuery.ts b/src/components/hooks/queries/useDeleteQuery.ts index bf5d8f5d..db5e91d8 100644 --- a/src/components/hooks/queries/useDeleteQuery.ts +++ b/src/components/hooks/queries/useDeleteQuery.ts @@ -1,6 +1,6 @@ import { useApi, useModified } from '@/components/hooks'; -export function useDeleteQuery(path: string, params?: { [key: string]: any }) { +export function useDeleteQuery(path: string, params?: Record) { const { del, useMutation } = useApi(); const { mutate, isPending, error } = useMutation({ mutationFn: () => del(path, params), diff --git a/src/components/hooks/queries/useRealtimeQuery.ts b/src/components/hooks/queries/useRealtimeQuery.ts index 58e72ef5..582fe9fa 100644 --- a/src/components/hooks/queries/useRealtimeQuery.ts +++ b/src/components/hooks/queries/useRealtimeQuery.ts @@ -1,8 +1,27 @@ import { useTimezone } from '@/components/hooks/useTimezone'; import { REALTIME_INTERVAL } from '@/lib/constants'; -import { RealtimeData } from '@/lib/types'; import { useApi } from '../useApi'; +export interface RealtimeData { + countries: Record; + events: any[]; + pageviews: any[]; + referrers: Record; + timestamp: number; + series: { + views: any[]; + visitors: any[]; + }; + totals: { + views: number; + visitors: number; + events: number; + countries: number; + }; + urls: Record; + visitors: any[]; +} + export function useRealtimeQuery(websiteId: string) { const { get, useQuery } = useApi(); const { timezone } = useTimezone(); diff --git a/src/components/hooks/queries/useResultQuery.ts b/src/components/hooks/queries/useResultQuery.ts index 098124c5..bbdf0dff 100644 --- a/src/components/hooks/queries/useResultQuery.ts +++ b/src/components/hooks/queries/useResultQuery.ts @@ -4,7 +4,7 @@ import { ReactQueryOptions } from '@/lib/types'; export function useResultQuery( type: string, - params?: { [key: string]: any }, + params?: Record, options?: ReactQueryOptions, ) { const { websiteId } = params; @@ -21,7 +21,7 @@ export function useResultQuery( ...params, }, ], - queryFn: () => post(`/reports/${type}`, { type, ...filters, ...params }), + queryFn: () => post(`/reports/${type}`, { type, filters, ...params }), enabled: !!type, ...options, }); diff --git a/src/components/hooks/queries/useUserQuery.ts b/src/components/hooks/queries/useUserQuery.ts index f454b96a..baaa084b 100644 --- a/src/components/hooks/queries/useUserQuery.ts +++ b/src/components/hooks/queries/useUserQuery.ts @@ -1,6 +1,6 @@ import { useApi } from '../useApi'; -export function useUserQuery(userId: string, options?: { [key: string]: any }) { +export function useUserQuery(userId: string, options?: Record) { const { get, useQuery } = useApi(); return useQuery({ queryKey: ['users', userId], diff --git a/src/components/hooks/queries/useWebsiteEventsQuery.ts b/src/components/hooks/queries/useWebsiteEventsQuery.ts index 7b465f70..a40f00bc 100644 --- a/src/components/hooks/queries/useWebsiteEventsQuery.ts +++ b/src/components/hooks/queries/useWebsiteEventsQuery.ts @@ -5,11 +5,11 @@ import { ReactQueryOptions } from '@/lib/types'; export function useWebsiteEventsQuery(websiteId: string, options?: ReactQueryOptions) { const { get } = useApi(); - const filterParams = useFilterParams(websiteId); + const queryParams = useFilterParams(websiteId); return usePagedQuery({ - queryKey: ['websites:events', { websiteId, ...filterParams }], - queryFn: () => get(`/websites/${websiteId}/events`, { ...filterParams, pageSize: 20 }), + queryKey: ['websites:events', { websiteId, ...queryParams }], + queryFn: () => get(`/websites/${websiteId}/events`, { ...queryParams, pageSize: 20 }), enabled: !!websiteId, ...options, }); diff --git a/src/components/hooks/queries/useWebsiteMetricsQuery.ts b/src/components/hooks/queries/useWebsiteMetricsQuery.ts index 5952de44..2d6a6ef8 100644 --- a/src/components/hooks/queries/useWebsiteMetricsQuery.ts +++ b/src/components/hooks/queries/useWebsiteMetricsQuery.ts @@ -15,7 +15,7 @@ export function useWebsiteMetricsQuery( options?: ReactQueryOptions, ) { const { get, useQuery } = useApi(); - const filterParams = useFilterParams(websiteId); + const queryParams = useFilterParams(websiteId); const searchParams = useSearchParams(); return useQuery({ @@ -23,13 +23,13 @@ export function useWebsiteMetricsQuery( 'websites:metrics', { websiteId, - ...filterParams, + ...queryParams, ...params, }, ], queryFn: async () => get(`/websites/${websiteId}/metrics`, { - ...filterParams, + ...queryParams, [searchParams.get('view')]: undefined, ...params, }), diff --git a/src/components/hooks/queries/useWebsitePageviewsQuery.ts b/src/components/hooks/queries/useWebsitePageviewsQuery.ts index f815f41a..63ec88ea 100644 --- a/src/components/hooks/queries/useWebsitePageviewsQuery.ts +++ b/src/components/hooks/queries/useWebsitePageviewsQuery.ts @@ -12,11 +12,11 @@ export function useWebsitePageviewsQuery( options?: ReactQueryOptions, ) { const { get, useQuery } = useApi(); - const filterParams = useFilterParams(websiteId); + const queryParams = useFilterParams(websiteId); return useQuery({ - queryKey: ['websites:pageviews', { websiteId, compare, ...filterParams }], - queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...filterParams }), + queryKey: ['websites:pageviews', { websiteId, compare, ...queryParams }], + queryFn: () => get(`/websites/${websiteId}/pageviews`, { compare, ...queryParams }), enabled: !!websiteId, ...options, }); diff --git a/src/components/hooks/queries/useWebsiteQuery.ts b/src/components/hooks/queries/useWebsiteQuery.ts index 30d9fbcf..55fe4eba 100644 --- a/src/components/hooks/queries/useWebsiteQuery.ts +++ b/src/components/hooks/queries/useWebsiteQuery.ts @@ -1,6 +1,6 @@ import { useApi } from '../useApi'; -export function useWebsiteQuery(websiteId: string, options?: { [key: string]: any }) { +export function useWebsiteQuery(websiteId: string, options?: Record) { const { get, useQuery } = useApi(); return useQuery({ diff --git a/src/components/hooks/queries/useWebsiteSessionStatsQuery.ts b/src/components/hooks/queries/useWebsiteSessionStatsQuery.ts index 4fa8e666..d60b3c77 100644 --- a/src/components/hooks/queries/useWebsiteSessionStatsQuery.ts +++ b/src/components/hooks/queries/useWebsiteSessionStatsQuery.ts @@ -1,10 +1,7 @@ import { useApi } from '../useApi'; import { useFilterParams } from '../useFilterParams'; -export function useWebsiteSessionStatsQuery( - websiteId: string, - options?: { [key: string]: string }, -) { +export function useWebsiteSessionStatsQuery(websiteId: string, options?: Record) { const { get, useQuery } = useApi(); const params = useFilterParams(websiteId); diff --git a/src/components/hooks/queries/useWebsiteSessionsQuery.ts b/src/components/hooks/queries/useWebsiteSessionsQuery.ts index 0a24a1b5..38405377 100644 --- a/src/components/hooks/queries/useWebsiteSessionsQuery.ts +++ b/src/components/hooks/queries/useWebsiteSessionsQuery.ts @@ -5,7 +5,7 @@ import { useFilterParams } from '@/components/hooks/useFilterParams'; export function useWebsiteSessionsQuery( websiteId: string, - params?: { [key: string]: string | number }, + params?: Record, ) { const { get } = useApi(); const { modified } = useModified(`sessions`); diff --git a/src/components/hooks/queries/useWebsiteSessionsWeeklyQuery.ts b/src/components/hooks/queries/useWebsiteSessionsWeeklyQuery.ts index 4a07f38d..ecfbde84 100644 --- a/src/components/hooks/queries/useWebsiteSessionsWeeklyQuery.ts +++ b/src/components/hooks/queries/useWebsiteSessionsWeeklyQuery.ts @@ -4,7 +4,7 @@ import { useFilterParams } from '@/components/hooks/useFilterParams'; export function useWebsiteSessionsWeeklyQuery( websiteId: string, - params?: { [key: string]: string | number }, + params?: Record, ) { const { get, useQuery } = useApi(); const { modified } = useModified(`sessions`); diff --git a/src/components/hooks/queries/useWebsiteStatsQuery.ts b/src/components/hooks/queries/useWebsiteStatsQuery.ts index 2d5595e0..4bf5f2d3 100644 --- a/src/components/hooks/queries/useWebsiteStatsQuery.ts +++ b/src/components/hooks/queries/useWebsiteStatsQuery.ts @@ -19,15 +19,14 @@ export interface WebsiteStatsData { export function useWebsiteStatsQuery( websiteId: string, - compare?: string, options?: UseQueryOptions, ) { const { get, useQuery } = useApi(); - const params = useFilterParams(websiteId); + const filterParams = useFilterParams(websiteId); return useQuery({ - queryKey: ['websites:stats', { websiteId, ...params, compare }], - queryFn: () => get(`/websites/${websiteId}/stats`, { ...params, compare }), + queryKey: ['websites:stats', { websiteId, ...filterParams }], + queryFn: () => get(`/websites/${websiteId}/stats`, { ...filterParams }), enabled: !!websiteId, ...options, }); diff --git a/src/components/hooks/queries/useWebsites.ts b/src/components/hooks/queries/useWebsitesQuery.ts similarity index 73% rename from src/components/hooks/queries/useWebsites.ts rename to src/components/hooks/queries/useWebsitesQuery.ts index f9cd5110..fd168ef1 100644 --- a/src/components/hooks/queries/useWebsites.ts +++ b/src/components/hooks/queries/useWebsitesQuery.ts @@ -2,10 +2,12 @@ import { useApi } from '../useApi'; import { usePagedQuery } from '../usePagedQuery'; import { useLoginQuery } from './useLoginQuery'; import { useModified } from '../useModified'; +import { ReactQueryOptions } from '@/lib/types'; -export function useWebsites( +export function useWebsitesQuery( { userId, teamId }: { userId?: string; teamId?: string }, - params?: { [key: string]: string | number }, + params?: Record, + options?: ReactQueryOptions, ) { const { get } = useApi(); const { user } = useLoginQuery(); @@ -13,10 +15,12 @@ export function useWebsites( return usePagedQuery({ queryKey: ['websites', { userId, teamId, modified, ...params }], - queryFn: () => { + queryFn: pageParams => { return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, { ...params, + ...pageParams, }); }, + ...options, }); } diff --git a/src/components/hooks/useFormat.ts b/src/components/hooks/useFormat.ts index 6f0b436f..039959cd 100644 --- a/src/components/hooks/useFormat.ts +++ b/src/components/hooks/useFormat.ts @@ -40,7 +40,7 @@ export function useFormat() { return languageNames[value?.split('-')[0]] || value; }; - const formatValue = (value: string, type: string, data?: { [key: string]: any }): string => { + const formatValue = (value: string, type: string, data?: Record): string => { switch (type) { case 'os': return formatOS(value); diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index f06f936b..e02b5951 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -15,7 +15,7 @@ export function useMessages(): any { id: string; defaultMessage: string; }, - values?: { [key: string]: string }, + values?: Record, opts?: any, ) => { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; diff --git a/src/components/hooks/useNavigation.ts b/src/components/hooks/useNavigation.ts index 2d8644df..3d02a9a0 100644 --- a/src/components/hooks/useNavigation.ts +++ b/src/components/hooks/useNavigation.ts @@ -9,11 +9,11 @@ export function useNavigation() { const [, websiteId] = pathname.match(/\/websites\/([a-f0-9-]+)/) || []; const query = Object.fromEntries(searchParams); - const updateParams = (params?: { [key: string]: string | number }) => { + const updateParams = (params?: Record) => { return !params ? pathname : buildUrl(pathname, { ...query, ...params }); }; - const renderUrl = (path: string, params?: { [key: string]: string | number } | false) => { + const renderUrl = (path: string, params?: Record | false) => { return buildUrl( teamId ? `/teams/${teamId}${path}` : path, params === false ? {} : { ...query, ...params }, diff --git a/src/components/hooks/usePagedQuery.ts b/src/components/hooks/usePagedQuery.ts index 6b995ea5..4cc53476 100644 --- a/src/components/hooks/usePagedQuery.ts +++ b/src/components/hooks/usePagedQuery.ts @@ -1,30 +1,27 @@ import { UseQueryOptions } from '@tanstack/react-query'; import { useState } from 'react'; -import { PageResult, PageParams, PagedQueryResult } from '@/lib/types'; import { useApi } from './useApi'; import { useNavigation } from './useNavigation'; -export function usePagedQuery({ +export function usePagedQuery({ queryKey, queryFn, ...options -}: Omit & { queryFn: (params?: object) => any }): PagedQueryResult { +}: Omit & { queryFn: (params?: object) => any }) { const { query: queryParams } = useNavigation(); - const [params, setParams] = useState({ - search: '', - page: queryParams?.page || '1', + const [params, setParams] = useState({ + search: queryParams?.search ?? '', + page: queryParams?.page ?? '1', }); const { useQuery } = useApi(); - const { data, ...query } = useQuery({ - queryKey: [{ ...queryKey, ...params }], - queryFn: () => queryFn(params as any), - ...options, - }); return { - result: data as PageResult, - query, + ...useQuery({ + queryKey: [{ ...queryKey, ...params }], + queryFn: () => queryFn(params), + ...options, + }), params, setParams, }; diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index fac60755..b0b0f881 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; -import { Select, ListItem } from '@umami/react-zen'; -import { useWebsites, useMessages } from '@/components/hooks'; -import type { SelectProps } from '@umami/react-zen/Select'; +import { Select, SelectProps, ListItem } from '@umami/react-zen'; +import { useWebsitesQuery, useMessages } from '@/components/hooks'; export function WebsiteSelect({ websiteId, @@ -12,14 +11,14 @@ export function WebsiteSelect({ }: { websiteId?: string; teamId?: string; - variant?: 'primary' | 'secondary' | 'outline' | 'quiet' | 'danger' | 'zero'; + variant?: 'primary' | 'outline' | 'quiet' | 'danger' | 'zero'; onSelect?: (key: any) => void; } & SelectProps) { const { formatMessage, labels } = useMessages(); const [search, setSearch] = useState(''); const [selectedId, setSelectedId] = useState(websiteId); - const queryResult = useWebsites({ teamId }, { search, pageSize: 5 }); + const { data, isLoading } = useWebsitesQuery({ teamId }, { search, pageSize: 5 }); const handleSelect = (value: any) => { setSelectedId(value); @@ -30,15 +29,17 @@ export function WebsiteSelect({ setSearch(value); }; - const items = queryResult?.result?.data as any[]; + if (!data) { + return null; + } return (