From 7b5591a3ce12dd825186b95ad21d801897be4d95 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 12 Jun 2025 23:07:25 -0700 Subject: [PATCH] Fixed chart rendering when using date nav buttons. --- package.json | 3 +- pnpm-lock.yaml | 156 +++--------------- .../websites/[websiteId]/WebsiteChart.tsx | 20 ++- .../sessions/SessionProperties.tsx | 4 +- .../[websiteId]/sessions/SessionsWeekly.tsx | 4 +- .../[sessionId]/SessionActivity.module.css | 21 --- .../sessions/[sessionId]/SessionActivity.tsx | 66 ++++---- src/components/charts/BarChart.tsx | 13 +- src/components/charts/Chart.tsx | 112 +++++-------- src/components/common/LinkButton.tsx | 4 +- src/components/icons.ts | 4 + src/components/input/ReportEditButton.tsx | 1 + src/components/input/WebsiteDateFilter.tsx | 2 +- src/components/metrics/EventsChart.tsx | 38 +++-- src/components/metrics/ListTable.tsx | 11 +- src/components/metrics/PageviewsChart.tsx | 21 +-- src/components/metrics/RealtimeChart.tsx | 4 +- .../sql/sessions/getSessionActivity.ts | 13 +- 18 files changed, 177 insertions(+), 320 deletions(-) delete mode 100644 src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.module.css diff --git a/package.json b/package.json index 6ef6f988..207335a2 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "lucide-react": "^0.511.0", "maxmind": "^4.3.24", "md5": "^2.3.0", - "next": "15.3.1", + "next": "15.3.3", "node-fetch": "^3.2.8", "npm-run-all": "^4.1.5", "pg": "^8.16.0", @@ -148,6 +148,7 @@ "@types/react-window": "^1.8.8", "@typescript-eslint/eslint-plugin": "^8.32.1", "@typescript-eslint/parser": "^8.32.1", + "babel-plugin-react-compiler": "19.1.0-rc.2", "cross-env": "^7.0.3", "cypress": "^13.6.6", "esbuild": "^0.25.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8cf8f50a..af5d6cd1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,7 @@ importers: version: 5.77.2(react@19.1.0) '@umami/react-zen': specifier: ^0.137.0 - version: 0.137.0(@babel/core@7.27.1)(@types/react@19.1.5)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) + version: 0.137.0(@babel/core@7.27.1)(@types/react@19.1.5)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0)) '@umami/redis-client': specifier: ^0.27.0 version: 0.27.0 @@ -132,8 +132,8 @@ importers: specifier: ^2.3.0 version: 2.3.0 next: - specifier: 15.3.1 - version: 15.3.1(@babel/core@7.27.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 15.3.3 + version: 15.3.3(@babel/core@7.27.1)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) node-fetch: specifier: ^3.2.8 version: 3.3.2 @@ -240,6 +240,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.32.1 version: 8.32.1(eslint@8.57.1)(typescript@5.8.3) + babel-plugin-react-compiler: + specifier: 19.1.0-rc.2 + version: 19.1.0-rc.2 cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -1378,105 +1381,54 @@ packages: resolution: {integrity: sha512-9Hgd/J5nP2U/Vv0teytq9uUAGppiKV9t5tzpsuMLqeqUGD9STxXwKmyZd2v8Z4THSW9rw4+8w7dH7LVlFoym2A==} engines: {node: '>=18.0.0'} - '@next/env@15.3.1': - resolution: {integrity: sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==} - '@next/env@15.3.3': resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} '@next/eslint-plugin-next@14.2.29': resolution: {integrity: sha512-qpxSYiPNJTr9RzqjGi5yom8AIC8Kgdtw4oNIXAB/gDYMDctmfMEv452FRUhT06cWPgcmSsbZiEPYhbFiQtCWTg==} - '@next/swc-darwin-arm64@15.3.1': - resolution: {integrity: sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - '@next/swc-darwin-arm64@15.3.3': resolution: {integrity: sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.3.1': - resolution: {integrity: sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - '@next/swc-darwin-x64@15.3.3': resolution: {integrity: sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.3.1': - resolution: {integrity: sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - '@next/swc-linux-arm64-gnu@15.3.3': resolution: {integrity: sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.3.1': - resolution: {integrity: sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - '@next/swc-linux-arm64-musl@15.3.3': resolution: {integrity: sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.3.1': - resolution: {integrity: sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - '@next/swc-linux-x64-gnu@15.3.3': resolution: {integrity: sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.3.1': - resolution: {integrity: sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - '@next/swc-linux-x64-musl@15.3.3': resolution: {integrity: sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.3.1': - resolution: {integrity: sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - '@next/swc-win32-arm64-msvc@15.3.3': resolution: {integrity: sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.3.1': - resolution: {integrity: sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - '@next/swc-win32-x64-msvc@15.3.3': resolution: {integrity: sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==} engines: {node: '>= 10'} @@ -2907,6 +2859,9 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-react-compiler@19.1.0-rc.2: + resolution: {integrity: sha512-kSNA//p5fMO6ypG8EkEVPIqAjwIXm5tMjfD1XRPL/sRjYSbJ6UsvORfaeolNWnZ9n310aM0xJP7peW26BuCVzA==} + babel-plugin-react-intl@7.9.4: resolution: {integrity: sha512-cMKrHEXrw43yT4M89Wbgq8A8N8lffSquj1Piwov/HVukR7jwOw8gf9btXNsQhT27ccyqEwy+M286JQYy0jby2g==} deprecated: this package has been renamed to babel-plugin-formatjs @@ -3026,8 +2981,8 @@ packages: caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} - caniuse-lite@1.0.30001721: - resolution: {integrity: sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==} + caniuse-lite@1.0.30001722: + resolution: {integrity: sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==} caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -5131,27 +5086,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@15.3.1: - resolution: {integrity: sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g==} - engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - next@15.3.3: resolution: {integrity: sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -8133,59 +8067,33 @@ snapshots: '@netlify/plugin-nextjs@5.11.2': {} - '@next/env@15.3.1': {} - '@next/env@15.3.3': {} '@next/eslint-plugin-next@14.2.29': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.3.1': - optional: true - '@next/swc-darwin-arm64@15.3.3': optional: true - '@next/swc-darwin-x64@15.3.1': - optional: true - '@next/swc-darwin-x64@15.3.3': optional: true - '@next/swc-linux-arm64-gnu@15.3.1': - optional: true - '@next/swc-linux-arm64-gnu@15.3.3': optional: true - '@next/swc-linux-arm64-musl@15.3.1': - optional: true - '@next/swc-linux-arm64-musl@15.3.3': optional: true - '@next/swc-linux-x64-gnu@15.3.1': - optional: true - '@next/swc-linux-x64-gnu@15.3.3': optional: true - '@next/swc-linux-x64-musl@15.3.1': - optional: true - '@next/swc-linux-x64-musl@15.3.3': optional: true - '@next/swc-win32-arm64-msvc@15.3.1': - optional: true - '@next/swc-win32-arm64-msvc@15.3.3': optional: true - '@next/swc-win32-x64-msvc@15.3.1': - optional: true - '@next/swc-win32-x64-msvc@15.3.3': optional: true @@ -9810,7 +9718,7 @@ snapshots: '@typescript-eslint/types': 8.32.1 eslint-visitor-keys: 4.2.0 - '@umami/react-zen@0.137.0(@babel/core@7.27.1)(@types/react@19.1.5)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': + '@umami/react-zen@0.137.0(@babel/core@7.27.1)(@types/react@19.1.5)(babel-plugin-react-compiler@19.1.0-rc.2)(immer@9.0.21)(use-sync-external-store@1.5.0(react@19.1.0))': dependencies: '@fontsource/jetbrains-mono': 5.2.6 '@internationalized/date': 3.8.2 @@ -9820,7 +9728,7 @@ snapshots: glob: 10.4.5 highlight.js: 11.11.1 lucide-react: 0.511.0(react@19.1.0) - next: 15.3.3(@babel/core@7.27.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.3(@babel/core@7.27.1)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-aria-components: 1.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-dom: 19.1.0(react@19.1.0) @@ -10172,6 +10080,10 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 + babel-plugin-react-compiler@19.1.0-rc.2: + dependencies: + '@babel/types': 7.27.1 + babel-plugin-react-intl@7.9.4(ts-jest@29.3.4(@babel/core@7.27.1)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.1))(esbuild@0.25.5)(jest@29.7.0(@types/node@22.15.21)(ts-node@10.9.2(@types/node@22.15.21)(typescript@5.8.3)))(typescript@5.8.3)): dependencies: '@babel/core': 7.27.1 @@ -10315,13 +10227,13 @@ snapshots: caniuse-api@3.0.0: dependencies: browserslist: 4.24.5 - caniuse-lite: 1.0.30001718 + caniuse-lite: 1.0.30001722 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 caniuse-lite@1.0.30001718: {} - caniuse-lite@1.0.30001721: {} + caniuse-lite@1.0.30001722: {} caseless@0.12.0: {} @@ -12838,38 +12750,13 @@ snapshots: natural-compare@1.4.0: {} - next@15.3.1(@babel/core@7.27.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - '@next/env': 15.3.1 - '@swc/counter': 0.1.3 - '@swc/helpers': 0.5.15 - busboy: 1.6.0 - caniuse-lite: 1.0.30001718 - postcss: 8.4.31 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.27.1)(react@19.1.0) - optionalDependencies: - '@next/swc-darwin-arm64': 15.3.1 - '@next/swc-darwin-x64': 15.3.1 - '@next/swc-linux-arm64-gnu': 15.3.1 - '@next/swc-linux-arm64-musl': 15.3.1 - '@next/swc-linux-x64-gnu': 15.3.1 - '@next/swc-linux-x64-musl': 15.3.1 - '@next/swc-win32-arm64-msvc': 15.3.1 - '@next/swc-win32-x64-msvc': 15.3.1 - sharp: 0.34.2 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - next@15.3.3(@babel/core@7.27.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.3.3(@babel/core@7.27.1)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.3.3 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001721 + caniuse-lite: 1.0.30001722 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -12883,6 +12770,7 @@ snapshots: '@next/swc-linux-x64-musl': 15.3.3 '@next/swc-win32-arm64-msvc': 15.3.3 '@next/swc-win32-x64-msvc': 15.3.3 + babel-plugin-react-compiler: 19.1.0-rc.2 sharp: 0.34.2 transitivePeerDependencies: - '@babel/core' diff --git a/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx b/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx index 00aecebf..a20c1e48 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteChart.tsx @@ -1,4 +1,5 @@ import { useMemo } from 'react'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; import { PageviewsChart } from '@/components/metrics/PageviewsChart'; import { useWebsitePageviewsQuery } from '@/components/hooks/queries/useWebsitePageviewsQuery'; import { useDateRange } from '@/components/hooks'; @@ -12,7 +13,7 @@ export function WebsiteChart({ }) { const { dateRange, dateCompare } = useDateRange(websiteId); const { startDate, endDate, unit, value } = dateRange; - const { data, isLoading } = useWebsitePageviewsQuery( + const { data, isLoading, error } = useWebsitePageviewsQuery( websiteId, compareMode ? dateCompare : undefined, ); @@ -46,13 +47,14 @@ export function WebsiteChart({ }, [data, startDate, endDate, unit]); return ( - + + + ); } diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx index d4ad4099..7fbb63ea 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionProperties.tsx @@ -30,7 +30,7 @@ export function SessionProperties({ websiteId }: { websiteId: string }) { : null; return ( - +
@@ -45,7 +45,7 @@ export function SessionProperties({ websiteId }: { websiteId: string }) { {propertyName && (
{propertyName}
- +
)}
diff --git a/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx b/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx index 03415f4d..dba6806e 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/SessionsWeekly.tsx @@ -6,7 +6,7 @@ import { getDayOfWeekAsDate } from '@/lib/date'; import { Focusable, Tooltip, TooltipTrigger } from '@umami/react-zen'; export function SessionsWeekly({ websiteId }: { websiteId: string }) { - const { data, ...props } = useWebsiteSessionsWeeklyQuery(websiteId); + const { data, isLoading, error } = useWebsiteSessionsWeeklyQuery(websiteId); const { dateLocale } = useLocale(); const { labels, formatMessage } = useMessages(); const { weekStartsOn } = dateLocale.options; @@ -36,7 +36,7 @@ export function SessionsWeekly({ websiteId }: { websiteId: string }) { : []; return ( - +   diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.module.css b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.module.css deleted file mode 100644 index fb830d38..00000000 --- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.module.css +++ /dev/null @@ -1,21 +0,0 @@ -.timeline { - display: flex; - flex-direction: column; - gap: 20px; -} - -.row { - display: grid; - grid-template-columns: max-content max-content 1fr; - align-items: center; - gap: 20px; -} - -.time { - color: var(--font-color200); - width: 150px; -} - -.header { - font-weight: bold; -} diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx index 00495cb1..dab2c87b 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx @@ -1,9 +1,8 @@ import { isSameDay } from 'date-fns'; -import { Loading, Icon, StatusLight } from '@umami/react-zen'; -import { Bolt, Eye } from '@/components/icons'; +import { Icon, StatusLight, Column, Row, Heading, Text, Button } from '@umami/react-zen'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; +import { Bolt, Eye, FileText } from '@/components/icons'; import { useSessionActivityQuery, useTimezone } from '@/components/hooks'; -import styles from './SessionActivity.module.css'; -import { Fragment } from 'react'; export function SessionActivity({ websiteId, @@ -17,37 +16,46 @@ export function SessionActivity({ endDate: Date; }) { const { formatTimezoneDate } = useTimezone(); - const { data, isLoading } = useSessionActivityQuery(websiteId, sessionId, startDate, endDate); - - if (isLoading) { - return ; - } - + const { data, isLoading, error } = useSessionActivityQuery( + websiteId, + sessionId, + startDate, + endDate, + ); let lastDay = null; return ( -
- {data.map(({ id, createdAt, urlPath, eventName, visitId }) => { - const showHeader = !lastDay || !isSameDay(new Date(lastDay), new Date(createdAt)); - lastDay = createdAt; + + + {data?.map(({ eventId, createdAt, urlPath, eventName, visitId, hasData }) => { + const showHeader = !lastDay || !isSameDay(new Date(lastDay), new Date(createdAt)); + lastDay = createdAt; - return ( - - {showHeader && ( -
{formatTimezoneDate(createdAt, 'PPPP')}
- )} -
-
+ return ( + + {showHeader && {formatTimezoneDate(createdAt, 'PPPP')}} + {formatTimezoneDate(createdAt, 'pp')} -
- {eventName ? : } -
{eventName || urlPath}
-
-
- ); - })} -
+ + {eventName ? : } + {eventName || urlPath} + {hasData > 0 && ( + + )} + + + + ); + })} + +
); } diff --git a/src/components/charts/BarChart.tsx b/src/components/charts/BarChart.tsx index 653c8793..29322cab 100644 --- a/src/components/charts/BarChart.tsx +++ b/src/components/charts/BarChart.tsx @@ -30,10 +30,10 @@ export interface BarChartProps extends ChartProps { YAxisType?: string; minDate?: Date; maxDate?: Date; - isAllTime?: boolean; } export function BarChart({ + chartData, renderXLabel, renderYLabel, unit, @@ -43,7 +43,6 @@ export function BarChart({ minDate, maxDate, currency, - isAllTime, ...props }: BarChartProps) { const [tooltip, setTooltip] = useState(null); @@ -51,14 +50,14 @@ export function BarChart({ const { locale } = useLocale(); const { colors } = useMemo(() => getThemeColors(theme), [theme]); - const options: any = useMemo(() => { + const chartOptions: any = useMemo(() => { return { __id: new Date().getTime(), scales: { x: { type: XAxisType, stacked: true, - min: isAllTime ? '' : minDate, + min: minDate, max: maxDate, time: { unit, @@ -94,7 +93,7 @@ export function BarChart({ }, }, }; - }, [colors, unit, stacked, renderXLabel, renderYLabel]); + }, [chartData, colors, unit, stacked, renderXLabel, renderYLabel]); const handleTooltip = ({ tooltip }: { tooltip: any }) => { const { opacity, labelColors, dataPoints } = tooltip; @@ -121,9 +120,9 @@ export function BarChart({ {tooltip && } diff --git a/src/components/charts/Chart.tsx b/src/components/charts/Chart.tsx index 62084d14..b68e45a0 100644 --- a/src/components/charts/Chart.tsx +++ b/src/components/charts/Chart.tsx @@ -1,29 +1,25 @@ import { useState, useRef, useEffect, useMemo } from 'react'; -import { Loading, Box, Column, BoxProps } from '@umami/react-zen'; -import ChartJS, { LegendItem, ChartOptions } from 'chart.js/auto'; +import { Box, Column, BoxProps } from '@umami/react-zen'; +import ChartJS, { LegendItem, ChartOptions, ChartData, UpdateMode } from 'chart.js/auto'; import { Legend } from '@/components/metrics/Legend'; import { DEFAULT_ANIMATION_DURATION } from '@/lib/constants'; +ChartJS.defaults.font.family = 'Inter'; + export interface ChartProps extends BoxProps { type?: 'bar' | 'bubble' | 'doughnut' | 'pie' | 'line' | 'polarArea' | 'radar' | 'scatter'; - data?: object; - isLoading?: boolean; - animationDuration?: number; - updateMode?: string; - onCreate?: (chart: any) => void; - onUpdate?: (chart: any) => void; - onTooltip?: (model: any) => void; + chartData?: ChartData & { focusLabel?: string }; chartOptions?: ChartOptions; + updateMode?: UpdateMode; + animationDuration?: number; + onTooltip?: (model: any) => void; } export function Chart({ type, - data, - isLoading = false, + chartData, animationDuration = DEFAULT_ANIMATION_DURATION, updateMode, - onCreate, - onUpdate, onTooltip, chartOptions, ...props @@ -59,53 +55,6 @@ export function Chart({ }; }, [chartOptions]); - const createChart = (data: any) => { - ChartJS.defaults.font.family = 'Inter'; - - chart.current = new ChartJS(canvas.current, { - type, - data, - options, - }); - - onCreate?.(chart.current); - - setLegendItems(chart.current.legend.legendItems); - }; - - const updateChart = (data: any) => { - if (data.datasets) { - if (data.datasets.length === chart.current.data.datasets.length) { - chart.current.data.datasets.forEach((dataset: { data: any }, index: string | number) => { - if (data?.datasets[index]) { - dataset.data = data?.datasets[index]?.data; - - if (chart.current.legend.legendItems[index]) { - chart.current.legend.legendItems[index].text = data.datasets[index]?.label; - } - } - }); - } else { - chart.current.data.datasets = data.datasets; - } - } - - if (data.focusLabel !== null) { - chart.current.data.datasets.forEach(ds => { - ds.hidden = data.focusLabel ? ds.label !== data.focusLabel : false; - }); - } - - chart.current.options = options; - - // Allow config changes before update - onUpdate?.(chart.current); - - chart.current.update(updateMode); - - setLegendItems(chart.current.legend.legendItems); - }; - const handleLegendClick = (item: LegendItem) => { if (type === 'bar') { const { datasetIndex } = item; @@ -127,20 +76,47 @@ export function Chart({ setLegendItems(chart.current.legend.legendItems); }; + // Create chart useEffect(() => { - if (data) { - if (!chart.current) { - createChart(data); - } else { - updateChart(data); - } + if (canvas.current) { + chart.current = new ChartJS(canvas.current, { + type, + data: chartData, + options, + }); + + setLegendItems(chart.current.legend.legendItems); } - }, [data, options]); + + return () => { + chart.current?.destroy(); + }; + }, []); + + // Update chart + useEffect(() => { + if (chart.current && chartData) { + // Replace labels and datasets *in-place* + chart.current.data.labels = chartData.labels; + chart.current.data.datasets = chartData.datasets; + + if (chartData.focusLabel !== null) { + chart.current.data.datasets.forEach((ds: { hidden: boolean; label: any }) => { + ds.hidden = chartData.focusLabel ? ds.label !== chartData.focusLabel : false; + }); + } + + chart.current.options = options; + + chart.current.update(updateMode); + + setLegendItems(chart.current.legend.legendItems); + } + }, [chartData, options, updateMode]); return ( - {isLoading && } diff --git a/src/components/common/LinkButton.tsx b/src/components/common/LinkButton.tsx index 1b6a24c5..ca1baccb 100644 --- a/src/components/common/LinkButton.tsx +++ b/src/components/common/LinkButton.tsx @@ -1,9 +1,9 @@ import { ReactNode } from 'react'; import Link from 'next/link'; -import { Button } from '@umami/react-zen'; +import { Button, ButtonProps } from '@umami/react-zen'; import { useLocale } from '@/components/hooks'; -export interface LinkButtonProps { +export interface LinkButtonProps extends ButtonProps { href: string; target?: string; scroll?: boolean; diff --git a/src/components/icons.ts b/src/components/icons.ts index 56b71869..d57d36a9 100644 --- a/src/components/icons.ts +++ b/src/components/icons.ts @@ -6,12 +6,15 @@ export { ChevronRight as Chevron, Clock, Copy, + Database, Download, Edit, Ellipsis, Eye, ExternalLink, File, + FileJson, + FileText, Globe, Grid2X2, KeyRound, @@ -25,6 +28,7 @@ export { Minimize, Moon, MoreHorizontal as More, + Paperclip, PanelLeft, Plus, RefreshCw as Refresh, diff --git a/src/components/input/ReportEditButton.tsx b/src/components/input/ReportEditButton.tsx index b13bf90b..0569ed6f 100644 --- a/src/components/input/ReportEditButton.tsx +++ b/src/components/input/ReportEditButton.tsx @@ -88,6 +88,7 @@ export function ReportEditButton({ title={formatMessage(labels.delete)} onConfirm={handleDelete} onCancel={handleClose} + isDanger > {formatMessage(messages.confirmDelete, { target: {name} })} diff --git a/src/components/input/WebsiteDateFilter.tsx b/src/components/input/WebsiteDateFilter.tsx index 2b35e12e..b2121a42 100644 --- a/src/components/input/WebsiteDateFilter.tsx +++ b/src/components/input/WebsiteDateFilter.tsx @@ -47,7 +47,7 @@ export function WebsiteDateFilter({ }; const handleIncrement = (increment: number) => { - router.push(renderUrl({ increment })); + router.push(renderUrl({ offset: offset + increment })); saveDateRange(getOffsetDateRange(dateRange, increment)); }; diff --git a/src/components/metrics/EventsChart.tsx b/src/components/metrics/EventsChart.tsx index 4a286d9c..88e84b4b 100644 --- a/src/components/metrics/EventsChart.tsx +++ b/src/components/metrics/EventsChart.tsx @@ -1,26 +1,26 @@ import { useMemo, useState, useEffect } from 'react'; import { colord } from 'colord'; -import { BarChart } from '@/components/charts/BarChart'; +import { BarChart, BarChartProps } from '@/components/charts/BarChart'; import { useDateRange, useLocale, useWebsiteEventsSeriesQuery } from '@/components/hooks'; import { renderDateLabels } from '@/lib/charts'; import { CHART_COLORS } from '@/lib/constants'; +import { LoadingPanel } from '@/components/common/LoadingPanel'; -export interface EventsChartProps { +export interface EventsChartProps extends BarChartProps { websiteId: string; - className?: string; focusLabel?: string; } -export function EventsChart({ websiteId, className, focusLabel }: EventsChartProps) { +export function EventsChart({ websiteId, focusLabel }: EventsChartProps) { const { dateRange: { startDate, endDate, unit, value }, } = useDateRange(websiteId); const { locale } = useLocale(); - const { data, isLoading } = useWebsiteEventsSeriesQuery(websiteId); + const { data, isLoading, error } = useWebsiteEventsSeriesQuery(websiteId); const [label, setLabel] = useState(focusLabel); - const chartData = useMemo(() => { - if (!data) return []; + const chartData: any = useMemo(() => { + if (!data) return; const map = (data as any[]).reduce((obj, { x, t, y }) => { if (!obj[x]) { @@ -55,16 +55,18 @@ export function EventsChart({ websiteId, className, focusLabel }: EventsChartPro }, [focusLabel]); return ( - + + {chartData && ( + + )} + ); } diff --git a/src/components/metrics/ListTable.tsx b/src/components/metrics/ListTable.tsx index ca440508..197c1b50 100644 --- a/src/components/metrics/ListTable.tsx +++ b/src/components/metrics/ListTable.tsx @@ -96,13 +96,20 @@ const AnimatedRow = ({ }) => { const props = useSpring({ width: percent, - y: value, + y: !isNaN(value) ? value : 0, from: { width: 0, y: 0 }, config: animate ? config.default : { duration: 0 }, }); return ( - + {label} diff --git a/src/components/metrics/PageviewsChart.tsx b/src/components/metrics/PageviewsChart.tsx index 882eff74..03a86e8d 100644 --- a/src/components/metrics/PageviewsChart.tsx +++ b/src/components/metrics/PageviewsChart.tsx @@ -15,26 +15,16 @@ export interface PageviewsChartProps extends BarChartProps { }; }; unit: string; - isLoading?: boolean; - isAllTime?: boolean; } -export function PageviewsChart({ - data, - unit, - isLoading, - isAllTime, - ...props -}: PageviewsChartProps) { +export function PageviewsChart({ data, unit, ...props }: PageviewsChartProps) { const { formatMessage, labels } = useMessages(); const { theme } = useTheme(); const { locale } = useLocale(); const { colors } = useMemo(() => getThemeColors(theme), [theme]); - const chartData = useMemo(() => { - if (!data) { - return {}; - } + const chartData: any = useMemo(() => { + if (!data) return; return { __id: new Date().getTime(), @@ -84,11 +74,10 @@ export function PageviewsChart({ return ( ); } diff --git a/src/components/metrics/RealtimeChart.tsx b/src/components/metrics/RealtimeChart.tsx index bb886c73..a70361d7 100644 --- a/src/components/metrics/RealtimeChart.tsx +++ b/src/components/metrics/RealtimeChart.tsx @@ -38,8 +38,8 @@ export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) { return (