diff --git a/.husky/pre-commit b/.husky/pre-commit index 36af21989..2312dc587 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - npx lint-staged diff --git a/package.components.json b/package.components.json index d231f5b9b..51214f892 100644 --- a/package.components.json +++ b/package.components.json @@ -1,6 +1,6 @@ { "name": "@umami/components", - "version": "0.128.0", + "version": "0.129.0", "description": "Umami React components.", "author": "Mike Cao ", "license": "MIT", diff --git a/package.json b/package.json index d52a7777a..78ee92685 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@react-spring/web": "^10.0.3", "@svgr/cli": "^8.1.0", "@tanstack/react-query": "^5.90.2", - "@umami/react-zen": "^0.189.0", + "@umami/react-zen": "^0.196.0", "@umami/redis-client": "^0.29.0", "bcryptjs": "^3.0.2", "chalk": "^5.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3ad08bc8..5f8e64277 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^5.90.2 version: 5.90.2(react@19.1.1) '@umami/react-zen': - specifier: ^0.189.0 - version: 0.189.0(@babel/core@7.28.3)(@types/react@19.1.16)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.3)(use-sync-external-store@1.6.0(react@19.1.1)) + specifier: ^0.196.0 + version: 0.196.0(@babel/core@7.28.3)(@types/react@19.1.16)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.3)(use-sync-external-store@1.6.0(react@19.1.1)) '@umami/redis-client': specifier: ^0.29.0 version: 0.29.0 @@ -364,8 +364,6 @@ importers: specifier: ^5.9.3 version: 5.9.3 - dist: {} - packages: '@ampproject/remapping@2.3.0': @@ -2756,8 +2754,8 @@ packages: resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@umami/react-zen@0.189.0': - resolution: {integrity: sha512-E5t5HvMrGfuilrnF6LJV+jeooC4qXpwUC4VGhnTPV24B1vdMC2W9ByzZreNaomgZy8XOVAk1wZf8QX1elloUjA==} + '@umami/react-zen@0.196.0': + resolution: {integrity: sha512-CLxrDAJOdo+0aJAclOq7naIDg+2I5wP9wXxAFhxhQVPXHV8yUHqH9Ula632cLMo51JYp0l+eEtOtuimpuKX3jg==} '@umami/redis-client@0.29.0': resolution: {integrity: sha512-Jaqh++jskqDB7ny75pfC02OvKp1JTS4asGDsFrRL3qy8sxL3PAl9+/mybCJe4/6vWrXDJKqpgkSfUDJq2bFjyw==} @@ -6350,8 +6348,8 @@ packages: peerDependencies: react: '>=16.13.1' - react-hook-form@7.64.0: - resolution: {integrity: sha512-fnN+vvTiMLnRqKNTVhDysdrUay0kUUAymQnFIznmgDvapjveUWOOPqMNzPg+A+0yf9DuE2h6xzBjN1s+Qx8wcg==} + react-hook-form@7.65.0: + resolution: {integrity: sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -10387,7 +10385,7 @@ snapshots: '@typescript-eslint/types': 8.45.0 eslint-visitor-keys: 4.2.1 - '@umami/react-zen@0.189.0(@babel/core@7.28.3)(@types/react@19.1.16)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.3)(use-sync-external-store@1.6.0(react@19.1.1))': + '@umami/react-zen@0.196.0(@babel/core@7.28.3)(@types/react@19.1.16)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@10.1.3)(use-sync-external-store@1.6.0(react@19.1.1))': dependencies: '@fontsource/jetbrains-mono': 5.2.8 '@internationalized/date': 3.10.0 @@ -10401,7 +10399,7 @@ snapshots: react: 19.1.1 react-aria-components: 1.13.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react-dom: 19.1.1(react@19.1.1) - react-hook-form: 7.64.0(react@19.1.1) + react-hook-form: 7.65.0(react@19.1.1) react-icons: 5.5.0(react@19.1.1) thenby: 1.3.4 zustand: 5.0.8(@types/react@19.1.16)(immer@10.1.3)(react@19.1.1)(use-sync-external-store@1.6.0(react@19.1.1)) @@ -14534,7 +14532,7 @@ snapshots: '@babel/runtime': 7.28.3 react: 19.1.1 - react-hook-form@7.64.0(react@19.1.1): + react-hook-form@7.65.0(react@19.1.1): dependencies: react: 19.1.1 diff --git a/src/app/(main)/App.tsx b/src/app/(main)/App.tsx index 52c9257a3..32218d115 100644 --- a/src/app/(main)/App.tsx +++ b/src/app/(main)/App.tsx @@ -4,6 +4,7 @@ import Script from 'next/script'; import { UpdateNotice } from './UpdateNotice'; import { SideNav } from '@/app/(main)/SideNav'; import { useLoginQuery, useConfig, useNavigation } from '@/components/hooks'; +import { MobileNav } from '@/app/(main)/MobileNav'; export function App({ children }) { const { user, isLoading, error } = useLoginQuery(); @@ -28,9 +29,16 @@ export function App({ children }) { } return ( - - - + + + + + diff --git a/src/app/(main)/MobileNav.tsx b/src/app/(main)/MobileNav.tsx new file mode 100644 index 000000000..32751f0e5 --- /dev/null +++ b/src/app/(main)/MobileNav.tsx @@ -0,0 +1,79 @@ +import { + Row, + Dialog, + DialogTrigger, + Button, + Icon, + Modal, + NavMenu, + NavMenuItem, + IconLabel, + Text, + Grid, +} from '@umami/react-zen'; +import { Globe, Grid2x2, LinkIcon, Menu } from '@/components/icons'; +import { useMessages, useNavigation } from '@/components/hooks'; +import Link from 'next/link'; +import { WebsiteNav } from '@/app/(main)/websites/[websiteId]/WebsiteNav'; +import { Logo } from '@/components/svg'; +import { NavButton } from '@/components/input/NavButton'; + +export function MobileNav() { + const { formatMessage, labels } = useMessages(); + const { websiteId } = useNavigation(); + + const links = [ + { + id: 'websites', + label: formatMessage(labels.websites), + path: '/websites', + icon: , + }, + { + id: 'links', + label: formatMessage(labels.links), + path: '/links', + icon: , + }, + { + id: 'pixels', + label: formatMessage(labels.pixels), + path: '/pixels', + icon: , + }, + ]; + + return ( + + + + + + + + {links.map(link => { + return ( + + + + + + ); + })} + + {websiteId && } + + + + + } style={{ width: 'auto' }}> + umami + + + + ); +} diff --git a/src/app/(main)/links/LinksTable.tsx b/src/app/(main)/links/LinksTable.tsx index b84718299..5df15b787 100644 --- a/src/app/(main)/links/LinksTable.tsx +++ b/src/app/(main)/links/LinksTable.tsx @@ -1,23 +1,18 @@ import Link from 'next/link'; -import { DataTable, DataColumn, Row } from '@umami/react-zen'; +import { DataTable, DataColumn, Row, DataTableProps } from '@umami/react-zen'; import { useMessages, useNavigation, useSlug } from '@/components/hooks'; -import { Empty } from '@/components/common/Empty'; import { DateDistance } from '@/components/common/DateDistance'; import { ExternalLink } from '@/components/common/ExternalLink'; import { LinkEditButton } from './LinkEditButton'; import { LinkDeleteButton } from './LinkDeleteButton'; -export function LinksTable({ data = [] }) { +export function LinksTable(props: DataTableProps) { const { formatMessage, labels } = useMessages(); const { websiteId, renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('link'); - if (data.length === 0) { - return ; - } - return ( - + {({ id, name }: any) => { return {name}; diff --git a/src/app/(main)/pixels/PixelsTable.tsx b/src/app/(main)/pixels/PixelsTable.tsx index dcb5307c1..4edbb1cf4 100644 --- a/src/app/(main)/pixels/PixelsTable.tsx +++ b/src/app/(main)/pixels/PixelsTable.tsx @@ -1,23 +1,18 @@ import Link from 'next/link'; -import { DataTable, DataColumn, Row } from '@umami/react-zen'; +import { DataTable, DataColumn, Row, DataTableProps } from '@umami/react-zen'; import { useMessages, useNavigation, useSlug } from '@/components/hooks'; -import { Empty } from '@/components/common/Empty'; import { DateDistance } from '@/components/common/DateDistance'; import { PixelEditButton } from './PixelEditButton'; import { PixelDeleteButton } from './PixelDeleteButton'; import { ExternalLink } from '@/components/common/ExternalLink'; -export function PixelsTable({ data = [] }) { +export function PixelsTable(props: DataTableProps) { const { formatMessage, labels } = useMessages(); const { renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('pixel'); - if (data.length === 0) { - return ; - } - return ( - + {({ id, name }: any) => { return {name}; diff --git a/src/app/(main)/teams/TeamsTable.tsx b/src/app/(main)/teams/TeamsTable.tsx index 5a22bcda8..b5f112331 100644 --- a/src/app/(main)/teams/TeamsTable.tsx +++ b/src/app/(main)/teams/TeamsTable.tsx @@ -1,19 +1,17 @@ -import { DataColumn, DataTable } from '@umami/react-zen'; +import { DataColumn, DataTable, DataTableProps } from '@umami/react-zen'; import { useMessages } from '@/components/hooks'; import { ROLES } from '@/lib/constants'; import { ReactNode } from 'react'; -export function TeamsTable({ - data = [], - renderLink, -}: { - data: any[]; +export interface TeamsTableProps extends DataTableProps { renderLink?: (row: any) => ReactNode; -}) { +} + +export function TeamsTable({ renderLink, ...props }: TeamsTableProps) { const { formatMessage, labels } = useMessages(); return ( - + {renderLink} diff --git a/src/app/(main)/websites/WebsitesTable.tsx b/src/app/(main)/websites/WebsitesTable.tsx index d6d743906..c2f7e0d46 100644 --- a/src/app/(main)/websites/WebsitesTable.tsx +++ b/src/app/(main)/websites/WebsitesTable.tsx @@ -1,30 +1,22 @@ import { ReactNode } from 'react'; -import { Icon, DataTable, DataColumn } from '@umami/react-zen'; +import { Icon, DataTable, DataColumn, DataTableProps } from '@umami/react-zen'; import { LinkButton } from '@/components/common/LinkButton'; import { useMessages, useNavigation } from '@/components/hooks'; import { SquarePen } from '@/components/icons'; -import { Empty } from '@/components/common/Empty'; -export function WebsitesTable({ - data = [], - showActions, - renderLink, -}: { - data: Record[]; +export interface WebsitesTableProps extends DataTableProps { showActions?: boolean; allowEdit?: boolean; allowView?: boolean; renderLink?: (row: any) => ReactNode; -}) { +} + +export function WebsitesTable({ showActions, renderLink, ...props }: WebsitesTableProps) { const { formatMessage, labels } = useMessages(); const { renderUrl } = useNavigation(); - if (data.length === 0) { - return ; - } - return ( - + {renderLink} diff --git a/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx b/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx index d357c6a3c..c92bbd9c8 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteLayout.tsx @@ -12,6 +12,7 @@ export function WebsiteLayout({ websiteId, children }: { websiteId: string; chil ; - } - return ( - + {(row: any) => { return ( - - - - - {row.eventName ? : } - - {formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)} - - + + + : } + label={formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)} + /> + + {row.eventName || row.urlPath} ); }} + + {(row: any) => { + return ( + + + + ); + }} + {(row: any) => ( diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx index 4c459bdc1..53b35b214 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsDataTable.tsx @@ -8,7 +8,7 @@ export function SessionsDataTable({ websiteId }: { websiteId?: string; teamId?: return ( {({ data }) => { - return ; + return ; }} ); diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx index 391c57f43..ab4977ea2 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx @@ -1,17 +1,17 @@ import Link from 'next/link'; -import { DataColumn, DataTable } from '@umami/react-zen'; +import { DataColumn, DataTable, DataTableProps } from '@umami/react-zen'; import { useFormat, useMessages, useNavigation } from '@/components/hooks'; import { Avatar } from '@/components/common/Avatar'; import { TypeIcon } from '@/components/common/TypeIcon'; import { DateDistance } from '@/components/common/DateDistance'; -export function SessionsTable({ data = [] }: { data: any[]; showDomain?: boolean }) { +export function SessionsTable(props: DataTableProps) { const { formatMessage, labels } = useMessages(); const { formatValue } = useFormat(); const { updateParams } = useNavigation(); return ( - + {(row: any) => ( diff --git a/src/app/api/admin/teams/route.ts b/src/app/api/admin/teams/route.ts index fa203a299..ceb16ab11 100644 --- a/src/app/api/admin/teams/route.ts +++ b/src/app/api/admin/teams/route.ts @@ -24,22 +24,26 @@ export async function GET(request: Request) { const teams = await getTeams( { include: { - _count: { - select: { - members: true, - websites: true, - }, - }, members: { - select: { + include: { user: { - omit: { - password: true, + select: { + id: true, + username: true, }, }, }, - where: { - role: 'team-owner', + }, + _count: { + select: { + websites: { + where: { deletedAt: null }, + }, + members: { + where: { + user: { deletedAt: null }, + }, + }, }, }, }, diff --git a/src/app/api/reports/route.ts b/src/app/api/reports/route.ts index ec8a4d239..123a7e66a 100644 --- a/src/app/api/reports/route.ts +++ b/src/app/api/reports/route.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; import { uuid } from '@/lib/crypto'; -import { pagingParams, reportSchema } from '@/lib/schema'; +import { pagingParams, reportSchema, reportTypeParam } from '@/lib/schema'; import { parseRequest } from '@/lib/request'; import { canViewWebsite, canUpdateWebsite } from '@/permissions'; import { unauthorized, json } from '@/lib/response'; @@ -9,7 +9,7 @@ import { getReports, createReport } from '@/queries/prisma'; export async function GET(request: Request) { const schema = z.object({ websiteId: z.uuid(), - type: z.string().optional(), + type: reportTypeParam.optional(), ...pagingParams, }); diff --git a/src/app/api/users/[userId]/route.ts b/src/app/api/users/[userId]/route.ts index fbb794746..c15b4b71b 100644 --- a/src/app/api/users/[userId]/route.ts +++ b/src/app/api/users/[userId]/route.ts @@ -26,9 +26,9 @@ export async function GET(request: Request, { params }: { params: Promise<{ user export async function POST(request: Request, { params }: { params: Promise<{ userId: string }> }) { const schema = z.object({ - username: z.string().max(255), + username: z.string().max(255).optional(), password: z.string().max(255).optional(), - role: userRoleParam, + role: userRoleParam.optional(), }); const { auth, body, error } = await parseRequest(request, schema); 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 0028d7477..c3b884ae7 100644 --- a/src/app/api/websites/[websiteId]/event-data/properties/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/properties/route.ts @@ -3,15 +3,16 @@ import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/permissions'; import { getEventDataProperties } from '@/queries/sql'; +import { dateRangeParams, filterParams } from '@/lib/schema'; export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { const schema = z.object({ - startAt: z.coerce.number().int(), - endAt: z.coerce.number().int(), propertyName: z.string().optional(), + ...dateRangeParams, + ...filterParams, }); const { auth, query, error } = await parseRequest(request, schema); 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 4966764b6..5cf406658 100644 --- a/src/app/api/websites/[websiteId]/event-data/values/route.ts +++ b/src/app/api/websites/[websiteId]/event-data/values/route.ts @@ -3,16 +3,17 @@ import { getQueryFilters, parseRequest } from '@/lib/request'; import { unauthorized, json } from '@/lib/response'; import { canViewWebsite } from '@/permissions'; import { getEventDataValues } from '@/queries/sql'; +import { dateRangeParams, filterParams } from '@/lib/schema'; export async function GET( request: Request, { params }: { params: Promise<{ websiteId: string }> }, ) { const schema = z.object({ - startAt: z.coerce.number().int(), - endAt: z.coerce.number().int(), eventName: z.string().optional(), propertyName: z.string().optional(), + ...dateRangeParams, + ...filterParams, }); const { auth, query, error } = await parseRequest(request, schema); diff --git a/src/components/common/DataGrid.tsx b/src/components/common/DataGrid.tsx index d95ee5155..18cb763c4 100644 --- a/src/components/common/DataGrid.tsx +++ b/src/components/common/DataGrid.tsx @@ -1,5 +1,12 @@ -import { ReactNode, useState, useCallback } from 'react'; -import { SearchField, Row, Column } from '@umami/react-zen'; +import { + ReactNode, + useState, + useCallback, + ReactElement, + cloneElement, + isValidElement, +} from 'react'; +import { SearchField, Row, Column, useBreakpoint } from '@umami/react-zen'; import { UseQueryResult } from '@tanstack/react-query'; import { useMessages, useNavigation } from '@/components/hooks'; import { Pager } from '@/components/common/Pager'; @@ -35,6 +42,8 @@ export function DataGrid({ const { router, updateParams, query: queryParams } = useNavigation(); const [search, setSearch] = useState(queryParams?.search || data?.search || ''); const showPager = allowPaging && data && data.count > data.pageSize; + const breakpoint = useBreakpoint(); + const displayMode = ['xs', 'sm', 'md', 'lg'].includes(breakpoint) ? 'cards' : undefined; const handleSearch = (value: string) => { if (value !== search) { @@ -50,6 +59,8 @@ export function DataGrid({ [search], ); + const child = data ? (typeof children === 'function' ? children(data) : children) : null; + return ( {allowSearch && ( @@ -73,7 +84,11 @@ export function DataGrid({ > {data && ( <> - {typeof children === 'function' ? children(data) : children} + + {isValidElement(child) + ? cloneElement(child as ReactElement, { displayMode }) + : child} + {showPager && ( {children} diff --git a/src/components/common/PageHeader.tsx b/src/components/common/PageHeader.tsx index 27d33a72b..8905d0cda 100644 --- a/src/components/common/PageHeader.tsx +++ b/src/components/common/PageHeader.tsx @@ -37,7 +37,7 @@ export function PageHeader({ {icon} )} - {title && {title}} + {title && {title}} {description && ( diff --git a/src/components/common/SideMenu.tsx b/src/components/common/SideMenu.tsx index 2848e3046..c8a7c9983 100644 --- a/src/components/common/SideMenu.tsx +++ b/src/components/common/SideMenu.tsx @@ -57,7 +57,6 @@ export function SideMenu({ get(`/websites/${websiteId}/event-data/events`, { ...date, ...filters }), + queryKey: [ + 'websites:event-data:events', + { websiteId, startAt, endAt, unit, timezone, ...filters }, + ], + queryFn: () => + get(`/websites/${websiteId}/event-data/events`, { + startAt, + endAt, + unit, + timezone, + ...filters, + }), enabled: !!websiteId, ...options, }); diff --git a/src/components/hooks/queries/useEventDataPropertiesQuery.ts b/src/components/hooks/queries/useEventDataPropertiesQuery.ts index 19a2eb61d..74dd58ef2 100644 --- a/src/components/hooks/queries/useEventDataPropertiesQuery.ts +++ b/src/components/hooks/queries/useEventDataPropertiesQuery.ts @@ -5,12 +5,22 @@ import { ReactQueryOptions } from '@/lib/types'; export function useEventDataPropertiesQuery(websiteId: string, options?: ReactQueryOptions) { const { get, useQuery } = useApi(); - const date = useDateParameters(); + const { startAt, endAt, unit, timezone } = useDateParameters(); const filters = useFilterParameters(); return useQuery({ - queryKey: ['websites:event-data:properties', { websiteId, ...date, ...filters }], - queryFn: () => get(`/websites/${websiteId}/event-data/properties`, { ...date, ...filters }), + queryKey: [ + 'websites:event-data:properties', + { websiteId, startAt, endAt, unit, timezone, ...filters }, + ], + queryFn: () => + get(`/websites/${websiteId}/event-data/properties`, { + startAt, + endAt, + unit, + timezone, + ...filters, + }), enabled: !!websiteId, ...options, }); diff --git a/src/components/hooks/queries/useEventDataQuery.ts b/src/components/hooks/queries/useEventDataQuery.ts index 7e6f66ec2..5e21081ac 100644 --- a/src/components/hooks/queries/useEventDataQuery.ts +++ b/src/components/hooks/queries/useEventDataQuery.ts @@ -5,12 +5,22 @@ import { ReactQueryOptions } from '@/lib/types'; export function useEventDataQuery(websiteId: string, eventId: string, options?: ReactQueryOptions) { const { get, useQuery } = useApi(); - const date = useDateParameters(); + const { startAt, endAt, unit, timezone } = useDateParameters(); const params = useFilterParameters(); return useQuery({ - queryKey: ['websites:event-data', { websiteId, eventId, ...date, ...params }], - queryFn: () => get(`/websites/${websiteId}/event-data/${eventId}`, { ...date, ...params }), + queryKey: [ + 'websites:event-data', + { websiteId, eventId, startAt, endAt, unit, timezone, ...params }, + ], + queryFn: () => + get(`/websites/${websiteId}/event-data/${eventId}`, { + startAt, + endAt, + unit, + timezone, + ...params, + }), enabled: !!(websiteId && eventId), ...options, }); diff --git a/src/components/hooks/queries/useEventDataValuesQuery.ts b/src/components/hooks/queries/useEventDataValuesQuery.ts index de3b0590b..6394a1bb4 100644 --- a/src/components/hooks/queries/useEventDataValuesQuery.ts +++ b/src/components/hooks/queries/useEventDataValuesQuery.ts @@ -1,7 +1,7 @@ -import { useApi } from '../useApi'; -import { useFilterParameters } from '../useFilterParameters'; -import { useDateParameters } from '../useDateParameters'; import { ReactQueryOptions } from '@/lib/types'; +import { useApi } from '../useApi'; +import { useDateParameters } from '../useDateParameters'; +import { useFilterParameters } from '../useFilterParameters'; export function useEventDataValuesQuery( websiteId: string, @@ -10,17 +10,20 @@ export function useEventDataValuesQuery( options?: ReactQueryOptions, ) { const { get, useQuery } = useApi(); - const date = useDateParameters(); + const { startAt, endAt, unit, timezone } = useDateParameters(); const filters = useFilterParameters(); return useQuery({ queryKey: [ 'websites:event-data:values', - { websiteId, eventName, propertyName, ...date, ...filters }, + { websiteId, eventName, propertyName, startAt, endAt, unit, timezone, ...filters }, ], queryFn: () => get(`/websites/${websiteId}/event-data/values`, { - ...date, + startAt, + endAt, + unit, + timezone, ...filters, eventName, propertyName, diff --git a/src/components/input/NavButton.tsx b/src/components/input/NavButton.tsx index 257cd685a..8be136968 100644 --- a/src/components/input/NavButton.tsx +++ b/src/components/input/NavButton.tsx @@ -61,6 +61,7 @@ export function NavButton({ showText = true }: TeamsButtonProps) { borderRadius shadow="1" maxHeight="40px" + role="button" style={{ cursor: 'pointer', textWrap: 'nowrap', outline: 'none' }} > diff --git a/src/components/input/WebsiteSelect.tsx b/src/components/input/WebsiteSelect.tsx index 01861fbd4..29ae040b0 100644 --- a/src/components/input/WebsiteSelect.tsx +++ b/src/components/input/WebsiteSelect.tsx @@ -1,6 +1,11 @@ import { useState } from 'react'; import { Select, SelectProps, ListItem, Text, Row } from '@umami/react-zen'; -import { useUserWebsitesQuery, useMessages, useLoginQuery, useWebsite } from '@/components/hooks'; +import { + useUserWebsitesQuery, + useMessages, + useLoginQuery, + useWebsiteQuery, +} from '@/components/hooks'; import { Empty } from '@/components/common/Empty'; export function WebsiteSelect({ @@ -15,7 +20,7 @@ export function WebsiteSelect({ includeTeams?: boolean; } & SelectProps) { const { formatMessage, messages } = useMessages(); - const website = useWebsite(); + const { data: website } = useWebsiteQuery(websiteId); const [name, setName] = useState(website?.name); const [search, setSearch] = useState(''); const { user } = useLoginQuery(); diff --git a/src/queries/sql/events/getEventDataProperties.ts b/src/queries/sql/events/getEventDataProperties.ts index 60b46bcce..7a618fd2e 100644 --- a/src/queries/sql/events/getEventDataProperties.ts +++ b/src/queries/sql/events/getEventDataProperties.ts @@ -68,10 +68,15 @@ async function clickhouseQuery( event_name as eventName, data_key as propertyName, count(*) as total - from event_data website_event + from event_data + join website_event + on website_event.event_id = event_data.event_id + and website_event.website_id = event_data.website_id + and website_event.website_id = {websiteId:UUID} + and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64} ${cohortQuery} - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} + where event_data.website_id = {websiteId:UUID} + and event_data.created_at between {startDate:DateTime64} and {endDate:DateTime64} ${filterQuery} group by event_name, data_key order by 1, 3 desc diff --git a/src/queries/sql/events/getEventDataValues.ts b/src/queries/sql/events/getEventDataValues.ts index bdfe7482e..0af938304 100644 --- a/src/queries/sql/events/getEventDataValues.ts +++ b/src/queries/sql/events/getEventDataValues.ts @@ -75,12 +75,17 @@ async function clickhouseQuery( data_type = 4, toString(date_trunc('hour', date_value)), string_value) as "value", count(*) as "total" - from event_data website_event + from event_data + join website_event + on website_event.event_id = event_data.event_id + and website_event.website_id = event_data.website_id + and website_event.website_id = {websiteId:UUID} + and website_event.created_at between {startDate:DateTime64} and {endDate:DateTime64} ${cohortQuery} - where website_id = {websiteId:UUID} - and created_at between {startDate:DateTime64} and {endDate:DateTime64} - and data_key = {propertyName:String} - and event_name = {eventName:String} + where event_data.website_id = {websiteId:UUID} + and event_data.created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_data.data_key = {propertyName:String} + and event_data.event_name = {eventName:String} ${filterQuery} group by value order by 2 desc