mirror of
https://github.com/umami-software/umami.git
synced 2026-02-12 16:45:35 +01:00
Updated reports components.
This commit is contained in:
parent
f5bc3dc6c2
commit
0f6cdf8b80
95 changed files with 580 additions and 698 deletions
|
|
@ -78,7 +78,7 @@
|
||||||
"@react-spring/web": "^9.7.5",
|
"@react-spring/web": "^9.7.5",
|
||||||
"@tanstack/react-query": "^5.68.0",
|
"@tanstack/react-query": "^5.68.0",
|
||||||
"@umami/prisma-client": "^0.16.0",
|
"@umami/prisma-client": "^0.16.0",
|
||||||
"@umami/react-zen": "^0.66.0",
|
"@umami/react-zen": "^0.71.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"chalk": "^4.1.2",
|
"chalk": "^4.1.2",
|
||||||
|
|
|
||||||
92
pnpm-lock.yaml
generated
92
pnpm-lock.yaml
generated
|
|
@ -45,8 +45,8 @@ importers:
|
||||||
specifier: ^0.16.0
|
specifier: ^0.16.0
|
||||||
version: 0.16.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))(@prisma/extension-read-replicas@0.4.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)))
|
version: 0.16.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2))(@prisma/extension-read-replicas@0.4.0(@prisma/client@6.5.0(prisma@6.5.0(typescript@5.8.2))(typescript@5.8.2)))
|
||||||
'@umami/react-zen':
|
'@umami/react-zen':
|
||||||
specifier: ^0.66.0
|
specifier: ^0.71.0
|
||||||
version: 0.66.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))
|
version: 0.71.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))
|
||||||
'@umami/redis-client':
|
'@umami/redis-client':
|
||||||
specifier: ^0.27.0
|
specifier: ^0.27.0
|
||||||
version: 0.27.0
|
version: 0.27.0
|
||||||
|
|
@ -1516,27 +1516,42 @@ packages:
|
||||||
'@formatjs/ecma402-abstract@2.3.3':
|
'@formatjs/ecma402-abstract@2.3.3':
|
||||||
resolution: {integrity: sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg==}
|
resolution: {integrity: sha512-pJT1OkhplSmvvr6i3CWTPvC/FGC06MbN5TNBfRO6Ox62AEz90eMq+dVvtX9Bl3jxCEkS0tATzDarRZuOLw7oFg==}
|
||||||
|
|
||||||
|
'@formatjs/ecma402-abstract@2.3.4':
|
||||||
|
resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==}
|
||||||
|
|
||||||
'@formatjs/fast-memoize@2.2.6':
|
'@formatjs/fast-memoize@2.2.6':
|
||||||
resolution: {integrity: sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==}
|
resolution: {integrity: sha512-luIXeE2LJbQnnzotY1f2U2m7xuQNj2DA8Vq4ce1BY9ebRZaoPB1+8eZ6nXpLzsxuW5spQxr7LdCg+CApZwkqkw==}
|
||||||
|
|
||||||
|
'@formatjs/fast-memoize@2.2.7':
|
||||||
|
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
|
||||||
|
|
||||||
'@formatjs/icu-messageformat-parser@2.1.0':
|
'@formatjs/icu-messageformat-parser@2.1.0':
|
||||||
resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==}
|
resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==}
|
||||||
|
|
||||||
'@formatjs/icu-messageformat-parser@2.11.1':
|
'@formatjs/icu-messageformat-parser@2.11.1':
|
||||||
resolution: {integrity: sha512-o0AhSNaOfKoic0Sn1GkFCK4MxdRsw7mPJ5/rBpIqdvcC7MIuyUSW8WChUEvrK78HhNpYOgqCQbINxCTumJLzZA==}
|
resolution: {integrity: sha512-o0AhSNaOfKoic0Sn1GkFCK4MxdRsw7mPJ5/rBpIqdvcC7MIuyUSW8WChUEvrK78HhNpYOgqCQbINxCTumJLzZA==}
|
||||||
|
|
||||||
|
'@formatjs/icu-messageformat-parser@2.11.2':
|
||||||
|
resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==}
|
||||||
|
|
||||||
'@formatjs/icu-skeleton-parser@1.3.6':
|
'@formatjs/icu-skeleton-parser@1.3.6':
|
||||||
resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==}
|
resolution: {integrity: sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==}
|
||||||
|
|
||||||
'@formatjs/icu-skeleton-parser@1.8.13':
|
'@formatjs/icu-skeleton-parser@1.8.13':
|
||||||
resolution: {integrity: sha512-N/LIdTvVc1TpJmMt2jVg0Fr1F7Q1qJPdZSCs19unMskCmVQ/sa0H9L8PWt13vq+gLdLg1+pPsvBLydL1Apahjg==}
|
resolution: {integrity: sha512-N/LIdTvVc1TpJmMt2jVg0Fr1F7Q1qJPdZSCs19unMskCmVQ/sa0H9L8PWt13vq+gLdLg1+pPsvBLydL1Apahjg==}
|
||||||
|
|
||||||
|
'@formatjs/icu-skeleton-parser@1.8.14':
|
||||||
|
resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==}
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.2.25':
|
'@formatjs/intl-localematcher@0.2.25':
|
||||||
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
|
resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==}
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.6.0':
|
'@formatjs/intl-localematcher@0.6.0':
|
||||||
resolution: {integrity: sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA==}
|
resolution: {integrity: sha512-4rB4g+3hESy1bHSBG3tDFaMY2CH67iT7yne1e+0CLTsGLDcmoEWWpJjjpWVaYgYfYuohIRuo0E+N536gd2ZHZA==}
|
||||||
|
|
||||||
|
'@formatjs/intl-localematcher@0.6.1':
|
||||||
|
resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==}
|
||||||
|
|
||||||
'@formatjs/intl-numberformat@5.7.6':
|
'@formatjs/intl-numberformat@5.7.6':
|
||||||
resolution: {integrity: sha512-ZlZfYtvbVHYZY5OG3RXizoCwxKxEKOrzEe2YOw9wbzoxF3PmFn0SAgojCFGLyNXkkR6xVxlylhbuOPf1dkIVNg==}
|
resolution: {integrity: sha512-ZlZfYtvbVHYZY5OG3RXizoCwxKxEKOrzEe2YOw9wbzoxF3PmFn0SAgojCFGLyNXkkR6xVxlylhbuOPf1dkIVNg==}
|
||||||
|
|
||||||
|
|
@ -2949,8 +2964,8 @@ packages:
|
||||||
'@prisma/client': ^4.8.0
|
'@prisma/client': ^4.8.0
|
||||||
'@prisma/extension-read-replicas': ^0.3.0
|
'@prisma/extension-read-replicas': ^0.3.0
|
||||||
|
|
||||||
'@umami/react-zen@0.66.0':
|
'@umami/react-zen@0.71.0':
|
||||||
resolution: {integrity: sha512-RAVrzv+bQQs+UtFpCczJ0ieEb11Q+NLZ0r8GLDTMTXXWlyaLqZxj0GGb2p7BhbsnLh+gQnaiSSeN9Q/ACYA93w==}
|
resolution: {integrity: sha512-SIfbDv/uW7mN5uebDeBeD+MnRiOrjsa2pJmVEdobyiM3ImdshpVVYxyBAJqAbiKbLF4OGZgRVbFsRfUcP/2Y0w==}
|
||||||
|
|
||||||
'@umami/redis-client@0.27.0':
|
'@umami/redis-client@0.27.0':
|
||||||
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
|
resolution: {integrity: sha512-SbHTpxhgeZyTBUSp2zdZM+XUtpsaSL4Tad8QXIEhEtjWhvvfoornyT5kLuyYCVtzSAT4daALeGmOO1z6EE1KcA==}
|
||||||
|
|
@ -4398,6 +4413,10 @@ packages:
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
glob@10.4.5:
|
||||||
|
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
glob@7.2.3:
|
glob@7.2.3:
|
||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
deprecated: Glob versions prior to v9 are no longer supported
|
||||||
|
|
@ -4618,6 +4637,9 @@ packages:
|
||||||
intl-messageformat@10.7.15:
|
intl-messageformat@10.7.15:
|
||||||
resolution: {integrity: sha512-LRyExsEsefQSBjU2p47oAheoKz+EOJxSLDdjOaEjdriajfHsMXOmV/EhMvYSg9bAgCUHasuAC+mcUBe/95PfIg==}
|
resolution: {integrity: sha512-LRyExsEsefQSBjU2p47oAheoKz+EOJxSLDdjOaEjdriajfHsMXOmV/EhMvYSg9bAgCUHasuAC+mcUBe/95PfIg==}
|
||||||
|
|
||||||
|
intl-messageformat@10.7.16:
|
||||||
|
resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==}
|
||||||
|
|
||||||
ipaddr.js@2.2.0:
|
ipaddr.js@2.2.0:
|
||||||
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
|
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
@ -4849,6 +4871,9 @@ packages:
|
||||||
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
jackspeak@3.4.3:
|
||||||
|
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||||
|
|
||||||
jake@10.9.2:
|
jake@10.9.2:
|
||||||
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
|
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
@ -5592,6 +5617,9 @@ packages:
|
||||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
package-json-from-dist@1.0.1:
|
||||||
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -8516,10 +8544,21 @@ snapshots:
|
||||||
decimal.js: 10.5.0
|
decimal.js: 10.5.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/ecma402-abstract@2.3.4':
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/fast-memoize': 2.2.7
|
||||||
|
'@formatjs/intl-localematcher': 0.6.1
|
||||||
|
decimal.js: 10.5.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@formatjs/fast-memoize@2.2.6':
|
'@formatjs/fast-memoize@2.2.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/fast-memoize@2.2.7':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@formatjs/icu-messageformat-parser@2.1.0':
|
'@formatjs/icu-messageformat-parser@2.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@formatjs/ecma402-abstract': 1.11.4
|
'@formatjs/ecma402-abstract': 1.11.4
|
||||||
|
|
@ -8532,6 +8571,12 @@ snapshots:
|
||||||
'@formatjs/icu-skeleton-parser': 1.8.13
|
'@formatjs/icu-skeleton-parser': 1.8.13
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/icu-messageformat-parser@2.11.2':
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/ecma402-abstract': 2.3.4
|
||||||
|
'@formatjs/icu-skeleton-parser': 1.8.14
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@formatjs/icu-skeleton-parser@1.3.6':
|
'@formatjs/icu-skeleton-parser@1.3.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@formatjs/ecma402-abstract': 1.11.4
|
'@formatjs/ecma402-abstract': 1.11.4
|
||||||
|
|
@ -8542,6 +8587,11 @@ snapshots:
|
||||||
'@formatjs/ecma402-abstract': 2.3.3
|
'@formatjs/ecma402-abstract': 2.3.3
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/icu-skeleton-parser@1.8.14':
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/ecma402-abstract': 2.3.4
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.2.25':
|
'@formatjs/intl-localematcher@0.2.25':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
@ -8550,6 +8600,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@formatjs/intl-localematcher@0.6.1':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@formatjs/intl-numberformat@5.7.6':
|
'@formatjs/intl-numberformat@5.7.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@formatjs/ecma402-abstract': 1.4.0
|
'@formatjs/ecma402-abstract': 1.4.0
|
||||||
|
|
@ -8689,7 +8743,7 @@ snapshots:
|
||||||
'@internationalized/message@3.1.6':
|
'@internationalized/message@3.1.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
intl-messageformat: 10.7.15
|
intl-messageformat: 10.7.16
|
||||||
|
|
||||||
'@internationalized/number@3.6.0':
|
'@internationalized/number@3.6.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -10551,13 +10605,13 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@umami/react-zen@0.66.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))':
|
'@umami/react-zen@0.71.0(@babel/core@7.26.9)(@types/react@19.0.10)(immer@9.0.21)(use-sync-external-store@1.4.0(react@19.0.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fontsource/jetbrains-mono': 5.2.5
|
'@fontsource/jetbrains-mono': 5.2.5
|
||||||
'@react-aria/focus': 3.20.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
'@react-aria/focus': 3.20.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
'@react-spring/web': 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
'@react-spring/web': 9.7.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
classnames: 2.5.1
|
classnames: 2.5.1
|
||||||
glob: 10.3.10
|
glob: 10.4.5
|
||||||
highlight.js: 11.11.1
|
highlight.js: 11.11.1
|
||||||
lucide-react: 0.479.0(react@19.0.0)
|
lucide-react: 0.479.0(react@19.0.0)
|
||||||
next: 15.2.4(@babel/core@7.26.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
next: 15.2.4(@babel/core@7.26.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
|
@ -12361,6 +12415,15 @@ snapshots:
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
path-scurry: 1.11.1
|
path-scurry: 1.11.1
|
||||||
|
|
||||||
|
glob@10.4.5:
|
||||||
|
dependencies:
|
||||||
|
foreground-child: 3.3.1
|
||||||
|
jackspeak: 3.4.3
|
||||||
|
minimatch: 9.0.5
|
||||||
|
minipass: 7.1.2
|
||||||
|
package-json-from-dist: 1.0.1
|
||||||
|
path-scurry: 1.11.1
|
||||||
|
|
||||||
glob@7.2.3:
|
glob@7.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
fs.realpath: 1.0.0
|
fs.realpath: 1.0.0
|
||||||
|
|
@ -12564,6 +12627,13 @@ snapshots:
|
||||||
'@formatjs/icu-messageformat-parser': 2.11.1
|
'@formatjs/icu-messageformat-parser': 2.11.1
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
intl-messageformat@10.7.16:
|
||||||
|
dependencies:
|
||||||
|
'@formatjs/ecma402-abstract': 2.3.4
|
||||||
|
'@formatjs/fast-memoize': 2.2.7
|
||||||
|
'@formatjs/icu-messageformat-parser': 2.11.2
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
ipaddr.js@2.2.0: {}
|
ipaddr.js@2.2.0: {}
|
||||||
|
|
||||||
is-array-buffer@3.0.5:
|
is-array-buffer@3.0.5:
|
||||||
|
|
@ -12791,6 +12861,12 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@pkgjs/parseargs': 0.11.0
|
'@pkgjs/parseargs': 0.11.0
|
||||||
|
|
||||||
|
jackspeak@3.4.3:
|
||||||
|
dependencies:
|
||||||
|
'@isaacs/cliui': 8.0.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@pkgjs/parseargs': 0.11.0
|
||||||
|
|
||||||
jake@10.9.2:
|
jake@10.9.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
async: 3.2.6
|
async: 3.2.6
|
||||||
|
|
@ -13735,6 +13811,8 @@ snapshots:
|
||||||
|
|
||||||
p-try@2.2.0: {}
|
p-try@2.2.0: {}
|
||||||
|
|
||||||
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites: 3.1.0
|
callsites: 3.1.0
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { usePathname } from 'next/navigation';
|
||||||
import { UpdateNotice } from './UpdateNotice';
|
import { UpdateNotice } from './UpdateNotice';
|
||||||
import { Nav } from '@/app/(main)/Nav';
|
import { Nav } from '@/app/(main)/Nav';
|
||||||
import { MenuBar } from '@/app/(main)/MenuBar';
|
import { MenuBar } from '@/app/(main)/MenuBar';
|
||||||
import { Page } from '@/components/layout/Page';
|
import { Page } from '@/components/common/Page';
|
||||||
import { useLoginQuery, useConfig } from '@/components/hooks';
|
import { useLoginQuery, useConfig } from '@/components/hooks';
|
||||||
|
|
||||||
export function App({ children }) {
|
export function App({ children }) {
|
||||||
|
|
@ -33,7 +33,13 @@ export function App({ children }) {
|
||||||
<Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr" overflow="hidden">
|
<Grid height="100vh" width="100%" columns="auto 1fr" rows="auto 1fr" overflow="hidden">
|
||||||
<Nav gridColumn="1 / 2" gridRow="1 / 3" />
|
<Nav gridColumn="1 / 2" gridRow="1 / 3" />
|
||||||
<MenuBar gridColumn="2 / 3" gridRow="1 / 2" />
|
<MenuBar gridColumn="2 / 3" gridRow="1 / 2" />
|
||||||
<Column gridColumn="2 / 3" gridRow="2 / 3" alignItems="center" overflow="auto">
|
<Column
|
||||||
|
gridColumn="2 / 3"
|
||||||
|
gridRow="2 / 3"
|
||||||
|
alignItems="center"
|
||||||
|
overflow="auto"
|
||||||
|
backgroundColor="1"
|
||||||
|
>
|
||||||
<Page>
|
<Page>
|
||||||
{children}
|
{children}
|
||||||
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
{process.env.NODE_ENV === 'production' && !pathname.includes('/share/') && (
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ export function MenuBar(props: RowProps) {
|
||||||
paddingY="3"
|
paddingY="3"
|
||||||
paddingX="3"
|
paddingX="3"
|
||||||
paddingRight="5"
|
paddingRight="5"
|
||||||
|
backgroundColor="1"
|
||||||
|
style={{ borderBottom: '1px solid var(--border-color)' }}
|
||||||
>
|
>
|
||||||
<Row>
|
<Row>
|
||||||
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">
|
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { Button } from '@umami/react-zen';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { Page } from '@/components/layout/Page';
|
import { Page } from '@/components/common/Page';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
import { EventsChart } from '@/components/metrics/EventsChart';
|
||||||
import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart';
|
import { WebsiteChart } from '../websites/[websiteId]/WebsiteChart';
|
||||||
import { useApi, useNavigation } from '@/components/hooks';
|
import { useApi, useNavigation } from '@/components/hooks';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Icon, Icons, Loading, Text } from '@umami/react-zen';
|
import { Icon, Icons, Loading, Text } from '@umami/react-zen';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { Pager } from '@/components/common/Pager';
|
import { Pager } from '@/components/common/Pager';
|
||||||
import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList';
|
import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList';
|
||||||
import { DashboardSettingsButton } from '@/app/(main)/dashboard/DashboardSettingsButton';
|
import { DashboardSettingsButton } from '@/app/(main)/dashboard/DashboardSettingsButton';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
|
|
||||||
export function ProfileHeader() {
|
export function ProfileHeader() {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { Icon, Icons, Text } from '@umami/react-zen';
|
import { Icon, Icons, Text } from '@umami/react-zen';
|
||||||
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
import { useLoginQuery, useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useMessages, useLoginQuery, useNavigation } from '@/components/hooks';
|
||||||
import { REPORT_TYPES } from '@/lib/constants';
|
import { REPORT_TYPES } from '@/lib/constants';
|
||||||
import { ReportDeleteButton } from './ReportDeleteButton';
|
import { ReportDeleteButton } from './ReportDeleteButton';
|
||||||
|
|
||||||
export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomain?: boolean }) {
|
export function ReportsTable({ data = [] }: { data: any[]; showDomain?: boolean }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { user } = useLoginQuery();
|
const { user } = useLoginQuery();
|
||||||
const { renderTeamUrl } = useNavigation();
|
const { renderTeamUrl } = useNavigation();
|
||||||
|
|
@ -20,11 +20,6 @@ export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomai
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</DataColumn>
|
</DataColumn>
|
||||||
{showDomain && (
|
|
||||||
<DataColumn id="domain" label={formatMessage(labels.domain)}>
|
|
||||||
{(row: any) => row?.website?.domain}
|
|
||||||
</DataColumn>
|
|
||||||
)}
|
|
||||||
<DataColumn id="action" label="" align="end">
|
<DataColumn id="action" label="" align="end">
|
||||||
{(row: any) => {
|
{(row: any) => {
|
||||||
const { id, name, userId, website } = row;
|
const { id, name, userId, website } = row;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.dropdown div {
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import { Column, Label } from '@umami/react-zen';
|
import { Column, Label } from '@umami/react-zen';
|
||||||
|
import { useReport } from '@/components/hooks';
|
||||||
import { parseDateRange } from '@/lib/date';
|
import { parseDateRange } from '@/lib/date';
|
||||||
import { DateFilter } from '@/components/input/DateFilter';
|
import { DateFilter } from '@/components/input/DateFilter';
|
||||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
import { useMessages, useNavigation, useWebsiteQuery } from '@/components/hooks';
|
import { useMessages, useNavigation, useWebsiteQuery } from '@/components/hooks';
|
||||||
import { ReportContext } from './Report';
|
|
||||||
import styles from './BaseParameters.module.css';
|
|
||||||
|
|
||||||
export interface BaseParametersProps {
|
export interface BaseParametersProps {
|
||||||
showWebsiteSelect?: boolean;
|
showWebsiteSelect?: boolean;
|
||||||
|
|
@ -20,7 +18,7 @@ export function BaseParameters({
|
||||||
showDateSelect = true,
|
showDateSelect = true,
|
||||||
allowDateSelect = true,
|
allowDateSelect = true,
|
||||||
}: BaseParametersProps) {
|
}: BaseParametersProps) {
|
||||||
const { report, updateReport } = useContext(ReportContext);
|
const { report, updateReport } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { teamId } = useNavigation();
|
const { teamId } = useNavigation();
|
||||||
const { parameters } = report || {};
|
const { parameters } = report || {};
|
||||||
|
|
@ -50,7 +48,7 @@ export function BaseParameters({
|
||||||
</Column>
|
</Column>
|
||||||
)}
|
)}
|
||||||
{showDateSelect && (
|
{showDateSelect && (
|
||||||
<Column className={styles.dropdown}>
|
<Column>
|
||||||
<Label>{formatMessage(labels.dateRange)}</Label>
|
<Label>{formatMessage(labels.dateRange)}</Label>
|
||||||
{allowDateSelect && (
|
{allowDateSelect && (
|
||||||
<DateFilter
|
<DateFilter
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { REPORT_PARAMETERS } from '@/lib/constants';
|
import { REPORT_PARAMETERS } from '@/lib/constants';
|
||||||
import { PopupForm } from './PopupForm';
|
|
||||||
import { FieldSelectForm } from './FieldSelectForm';
|
import { FieldSelectForm } from './FieldSelectForm';
|
||||||
|
|
||||||
export function FieldAddForm({
|
export function FieldAddForm({
|
||||||
|
|
@ -39,9 +38,7 @@ export function FieldAddForm({
|
||||||
};
|
};
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<PopupForm>
|
!selected && <FieldSelectForm fields={fields} onSelect={handleSelect} />,
|
||||||
{!selected && <FieldSelectForm fields={fields} onSelect={handleSelect} />}
|
|
||||||
</PopupForm>,
|
|
||||||
document.body,
|
document.body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { useFields, useMessages } from '@/components/hooks';
|
import { useFields, useMessages, useReport } from '@/components/hooks';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { useContext } from 'react';
|
import { Button, Row, Label, Icon, Popover, MenuTrigger, Column } from '@umami/react-zen';
|
||||||
import { Button, Row, Label, Icon, Popover, MenuTrigger } from '@umami/react-zen';
|
|
||||||
import { FieldSelectForm } from '../[reportId]/FieldSelectForm';
|
import { FieldSelectForm } from '../[reportId]/FieldSelectForm';
|
||||||
import { ParameterList } from '../[reportId]/ParameterList';
|
import { ParameterList } from '../[reportId]/ParameterList';
|
||||||
import { ReportContext } from './Report';
|
|
||||||
|
|
||||||
export function FieldParameters() {
|
export function FieldParameters() {
|
||||||
const { report, updateReport } = useContext(ReportContext);
|
const { report, updateReport } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { parameters } = report || {};
|
const { parameters } = report || {};
|
||||||
const { fields } = parameters || {};
|
const { fields } = parameters || {};
|
||||||
|
|
@ -26,12 +24,12 @@ export function FieldParameters() {
|
||||||
const AddButton = () => {
|
const AddButton = () => {
|
||||||
return (
|
return (
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button size="sm">
|
<Button variant="quiet">
|
||||||
<Icon>
|
<Icon size="sm">
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover placement="start">
|
<Popover placement="right top">
|
||||||
<FieldSelectForm
|
<FieldSelectForm
|
||||||
fields={fieldOptions.filter(({ name }) => !fields.find(f => f.name === name))}
|
fields={fieldOptions.filter(({ name }) => !fields.find(f => f.name === name))}
|
||||||
onSelect={handleAdd}
|
onSelect={handleAdd}
|
||||||
|
|
@ -43,8 +41,11 @@ export function FieldParameters() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Column gap="3">
|
||||||
<Label>{formatMessage(labels.fields)}</Label>
|
<Row justifyContent="space-between">
|
||||||
|
<Label>{formatMessage(labels.fields)}</Label>
|
||||||
|
<AddButton />
|
||||||
|
</Row>
|
||||||
<ParameterList>
|
<ParameterList>
|
||||||
{fields.map(({ name }) => {
|
{fields.map(({ name }) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -54,7 +55,6 @@ export function FieldParameters() {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ParameterList>
|
</ParameterList>
|
||||||
<AddButton />
|
</Column>
|
||||||
</Row>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { List, ListItem, Label, Column } from '@umami/react-zen';
|
import { Menu, MenuItem, Text, MenuSection } from '@umami/react-zen';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import styles from './FieldSelectForm.module.css';
|
|
||||||
import { Key } from 'react';
|
import { Key } from 'react';
|
||||||
|
|
||||||
export interface FieldSelectFormProps {
|
export interface FieldSelectFormProps {
|
||||||
|
|
@ -13,18 +12,17 @@ export function FieldSelectForm({ fields = [], onSelect, showType = true }: Fiel
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Menu onSelectionChange={key => onSelect(fields[key as any])}>
|
||||||
<Label>{formatMessage(labels.fields)}</Label>
|
<MenuSection title={formatMessage(labels.fields)}>
|
||||||
<List onSelectionChange={key => onSelect(fields[key as any])}>
|
|
||||||
{fields.map(({ name, label, type }: any, index: Key) => {
|
{fields.map(({ name, label, type }: any, index: Key) => {
|
||||||
return (
|
return (
|
||||||
<ListItem key={index} className={styles.item}>
|
<MenuItem key={index}>
|
||||||
<div>{label || name}</div>
|
<Text>{label || name}</Text>
|
||||||
{showType && type && <div className={styles.type}>{type}</div>}
|
{showType && type && <Text color="muted">{type}</Text>}
|
||||||
</ListItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</List>
|
</MenuSection>
|
||||||
</Column>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
import { useContext } from 'react';
|
import { useMessages, useFormat, useFilters, useFields, useReport } from '@/components/hooks';
|
||||||
import { useMessages, useFormat, useFilters, useFields } from '@/components/hooks';
|
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { Button, Row, Label, Icon, Popover, MenuTrigger } from '@umami/react-zen';
|
import {
|
||||||
|
Button,
|
||||||
|
Row,
|
||||||
|
Column,
|
||||||
|
Label,
|
||||||
|
Icon,
|
||||||
|
Popover,
|
||||||
|
MenuTrigger,
|
||||||
|
Focusable,
|
||||||
|
Text,
|
||||||
|
} from '@umami/react-zen';
|
||||||
import { FilterSelectForm } from '../[reportId]/FilterSelectForm';
|
import { FilterSelectForm } from '../[reportId]/FilterSelectForm';
|
||||||
import { ParameterList } from '../[reportId]/ParameterList';
|
import { ParameterList } from '../[reportId]/ParameterList';
|
||||||
import { PopupForm } from '../[reportId]/PopupForm';
|
|
||||||
import { ReportContext } from './Report';
|
|
||||||
import { FieldFilterEditForm } from '../[reportId]/FieldFilterEditForm';
|
import { FieldFilterEditForm } from '../[reportId]/FieldFilterEditForm';
|
||||||
import { isSearchOperator } from '@/lib/params';
|
import { isSearchOperator } from '@/lib/params';
|
||||||
import styles from './FilterParameters.module.css';
|
|
||||||
|
|
||||||
export function FilterParameters() {
|
export function FilterParameters() {
|
||||||
const { report, updateReport } = useContext(ReportContext);
|
const { report, updateReport } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const { parameters } = report || {};
|
const { parameters } = report || {};
|
||||||
|
|
@ -45,28 +51,26 @@ export function FilterParameters() {
|
||||||
const AddButton = () => {
|
const AddButton = () => {
|
||||||
return (
|
return (
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<Button size="sm">
|
<Button variant="quiet">
|
||||||
<Icon>
|
<Icon size="sm">
|
||||||
<Icons.Plus />
|
<Icons.Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover placement="bottom start">
|
<Popover placement="right top">
|
||||||
<PopupForm>
|
<FilterSelectForm
|
||||||
<FilterSelectForm
|
websiteId={websiteId}
|
||||||
websiteId={websiteId}
|
fields={fields.filter(({ name }) => !filters.find(f => f.name === name))}
|
||||||
fields={fields.filter(({ name }) => !filters.find(f => f.name === name))}
|
startDate={dateRange?.startDate}
|
||||||
startDate={dateRange?.startDate}
|
endDate={dateRange?.endDate}
|
||||||
endDate={dateRange?.endDate}
|
onChange={handleAdd}
|
||||||
onChange={handleAdd}
|
/>
|
||||||
/>
|
|
||||||
</PopupForm>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</MenuTrigger>
|
</MenuTrigger>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Column gap="3">
|
||||||
<Row justifyContent="space-between">
|
<Row justifyContent="space-between">
|
||||||
<Label>{formatMessage(labels.filters)}</Label>
|
<Label>{formatMessage(labels.filters)}</Label>
|
||||||
<AddButton />
|
<AddButton />
|
||||||
|
|
@ -94,7 +98,7 @@ export function FilterParameters() {
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
</ParameterList>
|
</ParameterList>
|
||||||
</>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,12 +117,16 @@ const FilterParameter = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuTrigger>
|
<MenuTrigger>
|
||||||
<div className={styles.item}>
|
<Focusable>
|
||||||
<div className={styles.label}>{label}</div>
|
<Row gap="3" alignItems="center">
|
||||||
<div className={styles.op}>{operatorLabels[operator]}</div>
|
<Text>{label}</Text>
|
||||||
<div className={styles.value}>{value}</div>
|
<Text size="2" transform="uppercase">
|
||||||
</div>
|
{operatorLabels[operator]}
|
||||||
<Popover className={styles.edit} placement="right top">
|
</Text>
|
||||||
|
<Text weight="bold">{value}</Text>
|
||||||
|
</Row>
|
||||||
|
</Focusable>
|
||||||
|
<Popover placement="right top">
|
||||||
{(close: any) => (
|
{(close: any) => (
|
||||||
<FieldFilterEditForm
|
<FieldFilterEditForm
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Icon } from '@umami/react-zen';
|
import { Icon, Row, Text, Button, Column } from '@umami/react-zen';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import styles from './ParameterList.module.css';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
export interface ParameterListProps {
|
export interface ParameterListProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
|
@ -14,34 +12,43 @@ export function ParameterList({ children }: ParameterListProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.list}>
|
<Column>
|
||||||
{!children && <Empty message={formatMessage(labels.none)} />}
|
{!children && <Empty message={formatMessage(labels.none)} />}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item = ({
|
const Item = ({
|
||||||
children,
|
|
||||||
className,
|
|
||||||
icon,
|
icon,
|
||||||
onClick,
|
onClick,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
children,
|
||||||
}: {
|
}: {
|
||||||
children?: ReactNode;
|
|
||||||
className?: string;
|
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
|
children?: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.item, className)} onClick={onClick}>
|
<Row
|
||||||
{icon && <Icon className={styles.icon}>{icon}</Icon>}
|
gap="3"
|
||||||
<div className={styles.value}>{children}</div>
|
alignItems="center"
|
||||||
<Icon className={styles.close} onClick={onRemove}>
|
justifyContent="space-between"
|
||||||
<Icons.Close />
|
onClick={onClick}
|
||||||
</Icon>
|
backgroundColor="2"
|
||||||
</div>
|
border
|
||||||
|
borderRadius="2"
|
||||||
|
paddingLeft="3"
|
||||||
|
>
|
||||||
|
{icon && <Icon>{icon}</Icon>}
|
||||||
|
<Text>{children}</Text>
|
||||||
|
<Button onPress={onRemove} variant="quiet">
|
||||||
|
<Icon>
|
||||||
|
<Icons.Close />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
.form {
|
|
||||||
background: var(--base50);
|
|
||||||
min-width: 300px;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid var(--base400);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import { CSSProperties, ReactNode } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import styles from './PopupForm.module.css';
|
|
||||||
|
|
||||||
export function PopupForm({
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
className?: string;
|
|
||||||
style?: CSSProperties;
|
|
||||||
children: ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(styles.form, className)}
|
|
||||||
style={style}
|
|
||||||
onClick={e => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { createContext, ReactNode } from 'react';
|
import { createContext, ReactNode } from 'react';
|
||||||
import { Loading } from '@umami/react-zen';
|
import { Loading, Grid } from '@umami/react-zen';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useReportQuery } from '@/components/hooks';
|
import { useReportQuery } from '@/components/hooks';
|
||||||
import styles from './Report.module.css';
|
|
||||||
|
|
||||||
export const ReportContext = createContext(null);
|
export const ReportContext = createContext(null);
|
||||||
|
|
||||||
|
|
@ -10,12 +8,10 @@ export function Report({
|
||||||
reportId,
|
reportId,
|
||||||
defaultParameters,
|
defaultParameters,
|
||||||
children,
|
children,
|
||||||
className,
|
|
||||||
}: {
|
}: {
|
||||||
reportId: string;
|
reportId: string;
|
||||||
defaultParameters: { type: string; parameters: { [key: string]: any } };
|
defaultParameters: { type: string; parameters: { [key: string]: any } };
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className?: string;
|
|
||||||
}) {
|
}) {
|
||||||
const report = useReportQuery(reportId, defaultParameters);
|
const report = useReportQuery(reportId, defaultParameters);
|
||||||
|
|
||||||
|
|
@ -25,7 +21,9 @@ export function Report({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReportContext.Provider value={report}>
|
<ReportContext.Provider value={report}>
|
||||||
<div className={classNames(styles.container, className)}>{children}</div>
|
<Grid rows="auto 1fr" columns="auto 1fr" gap="6">
|
||||||
|
{children}
|
||||||
|
</Grid>
|
||||||
</ReportContext.Provider>
|
</ReportContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { useContext } from 'react';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { ReportContext } from './Report';
|
import { useReport } from '@/components/hooks';
|
||||||
import styles from './ReportBody.module.css';
|
|
||||||
|
|
||||||
export function ReportBody({ children }) {
|
export function ReportBody({ children }) {
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
|
|
||||||
if (!report) {
|
if (!report) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={styles.body}>{children}</div>;
|
return <Panel>{children}</Panel>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
.header {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr min-content;
|
|
||||||
align-items: center;
|
|
||||||
grid-row: 1 / 2;
|
|
||||||
grid-column: 1 / 3;
|
|
||||||
margin: 20px 0 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
gap: 20px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--base600);
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
color: var(--font-color300);
|
|
||||||
max-width: 500px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
import { useContext } from 'react';
|
import {
|
||||||
import { Icon, LoadingButton, InlineEditField, useToast } from '@umami/react-zen';
|
Row,
|
||||||
import { useMessages, useApi, useNavigation } from '@/components/hooks';
|
Column,
|
||||||
import { ReportContext } from './Report';
|
Text,
|
||||||
import styles from './ReportHeader.module.css';
|
Heading,
|
||||||
|
Icon,
|
||||||
|
LoadingButton,
|
||||||
|
InlineEditField,
|
||||||
|
useToast,
|
||||||
|
} from '@umami/react-zen';
|
||||||
|
import { useMessages, useApi, useNavigation, useReport } from '@/components/hooks';
|
||||||
import { REPORT_TYPES } from '@/lib/constants';
|
import { REPORT_TYPES } from '@/lib/constants';
|
||||||
import { Breadcrumb } from '@/components/common/Breadcrumb';
|
|
||||||
|
|
||||||
export function ReportHeader({ icon }) {
|
export function ReportHeader({ icon }) {
|
||||||
const { report, updateReport } = useContext(ReportContext);
|
const { report, updateReport } = useReport();
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { router, renderTeamUrl } = useNavigation();
|
const { router, renderTeamUrl } = useNavigation();
|
||||||
|
|
@ -54,41 +59,33 @@ export function ReportHeader({ icon }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<Column marginY="6" gap="3" gridColumn="1 / 3">
|
||||||
<div>
|
<Row gap="3" alignItems="center">
|
||||||
<div className={styles.type}>
|
<Icon size="sm">{icon}</Icon>
|
||||||
<Breadcrumb
|
<Text transform="uppercase" weight="bold">
|
||||||
data={[
|
{formatMessage(
|
||||||
{ label: formatMessage(labels.reports), url: renderTeamUrl('/reports') },
|
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === report?.type)],
|
||||||
{
|
)}
|
||||||
label: formatMessage(
|
</Text>
|
||||||
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === report?.type)],
|
</Row>
|
||||||
),
|
<Row justifyContent="space-between" alignItems="center">
|
||||||
},
|
<Row gap="6">
|
||||||
]}
|
<Column gap="3">
|
||||||
/>
|
<Row gap="3" alignItems="center">
|
||||||
</div>
|
<InlineEditField key={name} name="name" value={name} onCommit={handleNameChange}>
|
||||||
<div className={styles.title}>
|
<Heading>{name}</Heading>
|
||||||
<Icon size="lg">{icon}</Icon>
|
</InlineEditField>
|
||||||
<InlineEditField
|
</Row>
|
||||||
key={name}
|
<InlineEditField
|
||||||
name="name"
|
key={description}
|
||||||
value={name}
|
name="description"
|
||||||
placeholder={defaultName}
|
value={description}
|
||||||
onCommit={handleNameChange}
|
onCommit={handleDescriptionChange}
|
||||||
/>
|
>
|
||||||
</div>
|
<Text>{description || `+ ${formatMessage(labels.addDescription)}`}</Text>
|
||||||
<div className={styles.description}>
|
</InlineEditField>
|
||||||
<InlineEditField
|
</Column>
|
||||||
key={description}
|
</Row>
|
||||||
name="description"
|
|
||||||
value={description}
|
|
||||||
placeholder={`+ ${formatMessage(labels.addDescription)}`}
|
|
||||||
onCommit={handleDescriptionChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.actions}>
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
isLoading={isCreating || isUpdating}
|
isLoading={isCreating || isUpdating}
|
||||||
|
|
@ -97,7 +94,7 @@ export function ReportHeader({ icon }) {
|
||||||
>
|
>
|
||||||
{formatMessage(labels.save)}
|
{formatMessage(labels.save)}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</div>
|
</Row>
|
||||||
</div>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,26 @@
|
||||||
import { useContext, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Icon, Icons } from '@umami/react-zen';
|
import { Button, Column, Icon, Row } from '@umami/react-zen';
|
||||||
import classNames from 'classnames';
|
import { Icons } from '@/components/icons';
|
||||||
import { ReportContext } from './Report';
|
import { useReport } from '@/components/hooks';
|
||||||
import styles from './ReportMenu.module.css';
|
|
||||||
|
|
||||||
export function ReportMenu({ children }) {
|
export function ReportMenu({ children }) {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
|
|
||||||
if (!report) {
|
if (!report) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.menu, collapsed && styles.collapsed)}>
|
<Column>
|
||||||
<div className={styles.button} onClick={() => setCollapsed(!collapsed)}>
|
<Row alignItems="center" justifyContent="flex-end">
|
||||||
<Icon rotate={collapsed ? -90 : 90}>
|
<Button variant="quiet" onPress={() => setCollapsed(!collapsed)}>
|
||||||
<Icons.Chevron />
|
<Icon>
|
||||||
</Icon>
|
<Icons.PanelLeft />
|
||||||
</div>
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
{!collapsed && children}
|
{!collapsed && children}
|
||||||
</div>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Icon, Text, Row, Column, Grid } from '@umami/react-zen';
|
import { Icon, Text, Row, Column, Grid } from '@umami/react-zen';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
|
|
||||||
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
|
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
|
||||||
|
|
@ -71,7 +71,7 @@ function ReportItem({ title, description, url, icon }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap="6" padding="6" borderSize="1" borderRadius="3" justifyContent="space-between">
|
<Column gap="6" padding="6" border borderRadius="3" justifyContent="space-between">
|
||||||
<Row gap="3" alignItems="center">
|
<Row gap="3" alignItems="center">
|
||||||
<Icon size="md">{icon}</Icon>
|
<Icon size="md">{icon}</Icon>
|
||||||
<Text size="5" weight="bold">
|
<Text size="5" weight="bold">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormField,
|
FormField,
|
||||||
|
|
@ -10,9 +9,8 @@ import {
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { Empty } from '@/components/common/Empty';
|
import { Empty } from '@/components/common/Empty';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { useApi, useMessages } from '@/components/hooks';
|
import { useApi, useMessages, useReport } from '@/components/hooks';
|
||||||
import { DATA_TYPES, REPORT_PARAMETERS } from '@/lib/constants';
|
import { DATA_TYPES, REPORT_PARAMETERS } from '@/lib/constants';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { FieldAddForm } from '../[reportId]/FieldAddForm';
|
import { FieldAddForm } from '../[reportId]/FieldAddForm';
|
||||||
import { ParameterList } from '../[reportId]/ParameterList';
|
import { ParameterList } from '../[reportId]/ParameterList';
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
|
|
@ -35,7 +33,7 @@ function useFields(websiteId, startDate, endDate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EventDataParameters() {
|
export function EventDataParameters() {
|
||||||
const { report, runReport, updateReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, updateReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
const { websiteId, dateRange, fields, filters, groups } = parameters || {};
|
const { websiteId, dateRange, fields, filters, groups } = parameters || {};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import { DataTable, DataColumn } from '@umami/react-zen';
|
import { DataTable, DataColumn } from '@umami/react-zen';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
|
|
||||||
export function EventDataTable() {
|
export function EventDataTable() {
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { formatLongNumber } from '@/lib/format';
|
import { formatLongNumber } from '@/lib/format';
|
||||||
import styles from './FunnelChart.module.css';
|
import styles from './FunnelChart.module.css';
|
||||||
|
|
||||||
|
|
@ -11,7 +9,7 @@ export interface FunnelChartProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FunnelChart({ className }: FunnelChartProps) {
|
export function FunnelChart({ className }: FunnelChartProps) {
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useContext } from 'react';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useMessages } from '@/components/hooks';
|
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
Form,
|
Form,
|
||||||
|
|
@ -13,14 +12,12 @@ import {
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { FunnelStepAddForm } from './FunnelStepAddForm';
|
import { FunnelStepAddForm } from './FunnelStepAddForm';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
import { ParameterList } from '../[reportId]/ParameterList';
|
import { ParameterList } from '../[reportId]/ParameterList';
|
||||||
import { PopupForm } from '../[reportId]/PopupForm';
|
|
||||||
import styles from './FunnelParameters.module.css';
|
import styles from './FunnelParameters.module.css';
|
||||||
|
|
||||||
export function FunnelParameters() {
|
export function FunnelParameters() {
|
||||||
const { report, runReport, updateReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, updateReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
|
|
@ -66,9 +63,7 @@ export function FunnelParameters() {
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
<Popover placement="start">
|
<Popover placement="start">
|
||||||
<PopupForm>
|
<FunnelStepAddForm onChange={handleAddStep} />
|
||||||
<FunnelStepAddForm onChange={handleAddStep} />
|
|
||||||
</PopupForm>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
);
|
);
|
||||||
|
|
@ -90,7 +85,6 @@ export function FunnelParameters() {
|
||||||
return (
|
return (
|
||||||
<DialogTrigger key={index}>
|
<DialogTrigger key={index}>
|
||||||
<ParameterList.Item
|
<ParameterList.Item
|
||||||
className={styles.item}
|
|
||||||
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
|
icon={step.type === 'url' ? <Icons.Eye /> : <Icons.Bolt />}
|
||||||
onRemove={() => handleRemoveStep(index)}
|
onRemove={() => handleRemoveStep(index)}
|
||||||
>
|
>
|
||||||
|
|
@ -100,13 +94,11 @@ export function FunnelParameters() {
|
||||||
</ParameterList.Item>
|
</ParameterList.Item>
|
||||||
<Popover placement="start">
|
<Popover placement="start">
|
||||||
{({ close }: any) => (
|
{({ close }: any) => (
|
||||||
<PopupForm>
|
<FunnelStepAddForm
|
||||||
<FunnelStepAddForm
|
type={step.type}
|
||||||
type={step.type}
|
value={step.value}
|
||||||
value={step.value}
|
onChange={handleUpdateStep.bind(null, close, index)}
|
||||||
onChange={handleUpdateStep.bind(null, close, index)}
|
/>
|
||||||
/>
|
|
||||||
</PopupForm>
|
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { formatLongNumber } from '@/lib/format';
|
import { formatLongNumber } from '@/lib/format';
|
||||||
import styles from './GoalsChart.module.css';
|
import styles from './GoalsChart.module.css';
|
||||||
|
|
||||||
export function GoalsChart({ className }: { className?: string; isLoading?: boolean }) {
|
export function GoalsChart({ className }: { className?: string; isLoading?: boolean }) {
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
import { useContext } from 'react';
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Form,
|
Form,
|
||||||
|
|
@ -15,12 +14,11 @@ import {
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
import { ParameterList } from '../[reportId]/ParameterList';
|
import { ParameterList } from '../[reportId]/ParameterList';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { GoalsAddForm } from './GoalsAddForm';
|
import { GoalsAddForm } from './GoalsAddForm';
|
||||||
import styles from './GoalsParameters.module.css';
|
import styles from './GoalsParameters.module.css';
|
||||||
|
|
||||||
export function GoalsParameters() {
|
export function GoalsParameters() {
|
||||||
const { report, runReport, updateReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, updateReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
|
|
@ -76,7 +74,7 @@ export function GoalsParameters() {
|
||||||
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||||
<BaseParameters allowWebsiteSelect={!id} />
|
<BaseParameters allowWebsiteSelect={!id} />
|
||||||
<AddGoalsButton />
|
<AddGoalsButton />
|
||||||
<FormField label={formatMessage(labels.goals)}>
|
<FormField name="goal" label={formatMessage(labels.goals)}>
|
||||||
<ParameterList>
|
<ParameterList>
|
||||||
{goals.map(
|
{goals.map(
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { FieldParameters } from '../[reportId]/FieldParameters';
|
import { FieldParameters } from '../[reportId]/FieldParameters';
|
||||||
import { FilterParameters } from '../[reportId]/FilterParameters';
|
import { FilterParameters } from '../[reportId]/FilterParameters';
|
||||||
|
|
||||||
export function InsightsParameters() {
|
export function InsightsParameters() {
|
||||||
const { report, runReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
const { websiteId, dateRange, fields, filters } = parameters || {};
|
const { websiteId, dateRange, fields, filters } = parameters || {};
|
||||||
|
|
@ -20,7 +18,7 @@ export function InsightsParameters() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form values={parameters} onSubmit={handleSubmit}>
|
<Form values={parameters} onSubmit={handleSubmit} gap="6">
|
||||||
<BaseParameters allowWebsiteSelect={!id} />
|
<BaseParameters allowWebsiteSelect={!id} />
|
||||||
{parametersSelected && <FieldParameters />}
|
{parametersSelected && <FieldParameters />}
|
||||||
{parametersSelected && <FilterParameters />}
|
{parametersSelected && <FilterParameters />}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { DataTable, DataColumn } from '@umami/react-zen';
|
import { DataTable, DataColumn } from '@umami/react-zen';
|
||||||
import { useFormat, useMessages } from '@/components/hooks';
|
import { useFormat, useMessages, useReport } from '@/components/hooks';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||||
import { formatShortTime } from '@/lib/format';
|
import { formatShortTime } from '@/lib/format';
|
||||||
|
|
||||||
export function InsightsTable() {
|
export function InsightsTable() {
|
||||||
const [fields, setFields] = useState([]);
|
const [fields, setFields] = useState([]);
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { useContext } from 'react';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useMessages } from '@/components/hooks';
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
Form,
|
Form,
|
||||||
|
|
@ -9,11 +8,10 @@ import {
|
||||||
FormSubmitButton,
|
FormSubmitButton,
|
||||||
TextField,
|
TextField,
|
||||||
} from '@umami/react-zen';
|
} from '@umami/react-zen';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
|
|
||||||
export function JourneyParameters() {
|
export function JourneyParameters() {
|
||||||
const { report, runReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { useContext, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { TooltipTrigger, Tooltip, Focusable } from '@umami/react-zen';
|
import { TooltipTrigger, Tooltip, Focusable } from '@umami/react-zen';
|
||||||
import { firstBy } from 'thenby';
|
import { firstBy } from 'thenby';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEscapeKey, useMessages } from '@/components/hooks';
|
import { useEscapeKey, useMessages, useReport } from '@/components/hooks';
|
||||||
import { objectToArray } from '@/lib/data';
|
import { objectToArray } from '@/lib/data';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import styles from './JourneyView.module.css';
|
import styles from './JourneyView.module.css';
|
||||||
import { formatLongNumber } from '@/lib/format';
|
import { formatLongNumber } from '@/lib/format';
|
||||||
|
|
||||||
|
|
@ -15,7 +14,7 @@ const LINE_WIDTH = 3;
|
||||||
export function JourneyView() {
|
export function JourneyView() {
|
||||||
const [selectedNode, setSelectedNode] = useState(null);
|
const [selectedNode, setSelectedNode] = useState(null);
|
||||||
const [activeNode, setActiveNode] = useState(null);
|
const [activeNode, setActiveNode] = useState(null);
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { data, parameters } = report || {};
|
const { data, parameters } = report || {};
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { useContext } from 'react';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useMessages } from '@/components/hooks';
|
|
||||||
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
import { parseDateRange } from '@/lib/date';
|
|
||||||
|
|
||||||
export function RetentionParameters() {
|
export function RetentionParameters() {
|
||||||
const { report, runReport, isRunning, updateReport } = useContext(ReportContext);
|
const { report, runReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||||
import { useMessages, useLocale } from '@/components/hooks';
|
import { useMessages, useLocale, useReport } from '@/components/hooks';
|
||||||
import { formatDate } from '@/lib/date';
|
import { formatDate } from '@/lib/date';
|
||||||
import styles from './RetentionTable.module.css';
|
import styles from './RetentionTable.module.css';
|
||||||
|
|
||||||
|
|
@ -11,7 +9,7 @@ const DAYS = [1, 2, 3, 4, 5, 6, 7, 14, 21, 28];
|
||||||
export function RetentionTable({ days = DAYS }) {
|
export function RetentionTable({ days = DAYS }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useRevenueValuesQuery } from '@/components/hooks/queries/useRevenueValuesQuery';
|
import { useRevenueValuesQuery } from '@/components/hooks/queries/useRevenueValuesQuery';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { Select, Form, FormButtons, FormField, ListItem, FormSubmitButton } from '@umami/react-zen';
|
import { Select, Form, FormButtons, FormField, ListItem, FormSubmitButton } from '@umami/react-zen';
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
|
|
||||||
export function RevenueParameters() {
|
export function RevenueParameters() {
|
||||||
const { report, runReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
const { websiteId, dateRange } = parameters || {};
|
const { websiteId, dateRange } = parameters || {};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { DataColumn, DataTable } from '@umami/react-zen';
|
import { DataColumn, DataTable } from '@umami/react-zen';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { formatLongCurrency } from '@/lib/format';
|
import { formatLongCurrency } from '@/lib/format';
|
||||||
|
|
||||||
export function RevenueTable() {
|
export function RevenueTable() {
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,15 @@ import { colord } from 'colord';
|
||||||
import { BarChart } from '@/components/charts/BarChart';
|
import { BarChart } from '@/components/charts/BarChart';
|
||||||
import { PieChart } from '@/components/charts/PieChart';
|
import { PieChart } from '@/components/charts/PieChart';
|
||||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||||
import { useCountryNames, useLocale, useMessages } from '@/components/hooks';
|
import { useCountryNames, useLocale, useMessages, useReport } from '@/components/hooks';
|
||||||
import { GridRow } from '@/components/layout/GridRow';
|
import { GridRow } from '@/components/common/GridRow';
|
||||||
import { ListTable } from '@/components/metrics/ListTable';
|
import { ListTable } from '@/components/metrics/ListTable';
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||||
import { renderDateLabels } from '@/lib/charts';
|
import { renderDateLabels } from '@/lib/charts';
|
||||||
import { CHART_COLORS } from '@/lib/constants';
|
import { CHART_COLORS } from '@/lib/constants';
|
||||||
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
import { formatLongCurrency, formatLongNumber } from '@/lib/format';
|
||||||
import { useCallback, useContext, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { RevenueTable } from './RevenueTable';
|
import { RevenueTable } from './RevenueTable';
|
||||||
import styles from './RevenueView.module.css';
|
import styles from './RevenueView.module.css';
|
||||||
|
|
||||||
|
|
@ -24,7 +23,7 @@ export function RevenueView({ isLoading }: RevenueViewProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const { countryNames } = useCountryNames(locale);
|
const { countryNames } = useCountryNames(locale);
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
parameters: { dateRange, currency },
|
parameters: { dateRange, currency },
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { useContext } from 'react';
|
import { useMessages, useReport } from '@/components/hooks';
|
||||||
import { useMessages } from '@/components/hooks';
|
|
||||||
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||||
|
|
||||||
export function UTMParameters() {
|
export function UTMParameters() {
|
||||||
const { report, runReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, isRunning } = useReport();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useContext } from 'react';
|
|
||||||
import { firstBy } from 'thenby';
|
import { firstBy } from 'thenby';
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
|
||||||
import { CHART_COLORS, UTM_PARAMS } from '@/lib/constants';
|
import { CHART_COLORS, UTM_PARAMS } from '@/lib/constants';
|
||||||
|
import { useReport } from '@/components/hooks';
|
||||||
import { PieChart } from '@/components/charts/PieChart';
|
import { PieChart } from '@/components/charts/PieChart';
|
||||||
import { ListTable } from '@/components/metrics/ListTable';
|
import { ListTable } from '@/components/metrics/ListTable';
|
||||||
import styles from './UTMView.module.css';
|
import styles from './UTMView.module.css';
|
||||||
|
|
@ -17,7 +16,7 @@ function toArray(data: { [key: string]: number } = {}) {
|
||||||
|
|
||||||
export function UTMView() {
|
export function UTMView() {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { report } = useContext(ReportContext);
|
const { report } = useReport();
|
||||||
const { data } = report || {};
|
const { data } = report || {};
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Grid, Column } from '@umami/react-zen';
|
import { Grid, Column } from '@umami/react-zen';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { SideBar } from '@/components/layout/SideBar';
|
import { SideBar } from '@/components/common/SideBar';
|
||||||
|
|
||||||
export function SettingsLayout({ children }: { children: ReactNode }) {
|
export function SettingsLayout({ children }: { children: ReactNode }) {
|
||||||
const { user } = useLoginQuery();
|
const { user } = useLoginQuery();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Row } from '@umami/react-zen';
|
import { Row } from '@umami/react-zen';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { TeamsJoinButton } from './TeamsJoinButton';
|
import { TeamsJoinButton } from './TeamsJoinButton';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { UserAddButton } from './UserAddButton';
|
import { UserAddButton } from './UserAddButton';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useContext } from 'react';
|
||||||
import { Tabs, Tab, TabList, TabPanel } from '@umami/react-zen';
|
import { Tabs, Tab, TabList, TabPanel } from '@umami/react-zen';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { UserEditForm } from './UserEditForm';
|
import { UserEditForm } from './UserEditForm';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { UserWebsites } from './UserWebsites';
|
import { UserWebsites } from './UserWebsites';
|
||||||
import { UserContext } from './UserProvider';
|
import { UserContext } from './UserProvider';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { WebsiteAddButton } from './WebsiteAddButton';
|
import { WebsiteAddButton } from './WebsiteAddButton';
|
||||||
|
|
||||||
export interface WebsitesHeaderProps {
|
export interface WebsitesHeaderProps {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { WebsiteDeleteForm } from './WebsiteDeleteForm';
|
import { WebsiteDeleteForm } from './WebsiteDeleteForm';
|
||||||
import { WebsiteResetForm } from './WebsiteResetForm';
|
import { WebsiteResetForm } from './WebsiteResetForm';
|
||||||
import { WebsiteTransferForm } from './WebsiteTransferForm';
|
import { WebsiteTransferForm } from './WebsiteTransferForm';
|
||||||
import { ActionForm } from '@/components/layout/ActionForm';
|
import { ActionForm } from '@/components/common/ActionForm';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
|
|
||||||
export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {
|
export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Link from 'next/link';
|
||||||
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { ShareUrl } from './ShareUrl';
|
import { ShareUrl } from './ShareUrl';
|
||||||
import { TrackingCode } from './TrackingCode';
|
import { TrackingCode } from './TrackingCode';
|
||||||
import { WebsiteData } from './WebsiteData';
|
import { WebsiteData } from './WebsiteData';
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { Grid, Column } from '@umami/react-zen';
|
import { Grid, Column } from '@umami/react-zen';
|
||||||
import { SideBar } from '@/components/layout/SideBar';
|
import { SideBar } from '@/components/common/SideBar';
|
||||||
|
|
||||||
export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
export function TeamSettingsLayout({ children }: { children: ReactNode }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
import { TeamMembersDataTable } from './TeamMembersDataTable';
|
import { TeamMembersDataTable } from './TeamMembersDataTable';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { Icons } from '@/components/icons';
|
import { Icons } from '@/components/icons';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||||
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
||||||
import { TeamManage } from './TeamManage';
|
import { TeamManage } from './TeamManage';
|
||||||
import { TeamEditForm } from './TeamEditForm';
|
import { TeamEditForm } from './TeamEditForm';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
|
||||||
export function TeamDetails({ teamId }: { teamId: string }) {
|
export function TeamDetails({ teamId }: { teamId: string }) {
|
||||||
const team = useContext(TeamContext);
|
const team = useContext(TeamContext);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useMessages, useModified } from '@/components/hooks';
|
import { useMessages, useModified } from '@/components/hooks';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Button, Modal, DialogTrigger, Dialog } from '@umami/react-zen';
|
import { Button, Modal, DialogTrigger, Dialog } from '@umami/react-zen';
|
||||||
import { ActionForm } from '@/components/layout/ActionForm';
|
import { ActionForm } from '@/components/common/ActionForm';
|
||||||
import { TeamDeleteForm } from './TeamDeleteForm';
|
import { TeamDeleteForm } from './TeamDeleteForm';
|
||||||
|
|
||||||
export function TeamManage({ teamId }: { teamId: string }) {
|
export function TeamManage({ teamId }: { teamId: string }) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||||
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
import { WebsiteAddButton } from '@/app/(main)/settings/websites/WebsiteAddButton';
|
||||||
import { useLoginQuery, useMessages } from '@/components/hooks';
|
import { useLoginQuery, useMessages } from '@/components/hooks';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { TeamWebsitesDataTable } from './TeamWebsitesDataTable';
|
import { TeamWebsitesDataTable } from './TeamWebsitesDataTable';
|
||||||
import { ROLES } from '@/lib/constants';
|
import { ROLES } from '@/lib/constants';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Column } from '@umami/react-zen';
|
import { Column } from '@umami/react-zen';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { FilterTags } from '@/components/metrics/FilterTags';
|
|
||||||
import { useNavigation } from '@/components/hooks';
|
import { useNavigation } from '@/components/hooks';
|
||||||
import { FILTER_COLUMNS } from '@/lib/constants';
|
|
||||||
import { WebsiteChart } from './WebsiteChart';
|
import { WebsiteChart } from './WebsiteChart';
|
||||||
import { WebsiteExpandedView } from './WebsiteExpandedView';
|
import { WebsiteExpandedView } from './WebsiteExpandedView';
|
||||||
import { WebsiteHeader } from './WebsiteHeader';
|
import { WebsiteHeader } from './WebsiteHeader';
|
||||||
|
|
@ -11,21 +9,16 @@ import { WebsiteMetricsBar } from './WebsiteMetricsBar';
|
||||||
import { WebsiteTableView } from './WebsiteTableView';
|
import { WebsiteTableView } from './WebsiteTableView';
|
||||||
|
|
||||||
export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
|
export function WebsiteDetailsPage({ websiteId }: { websiteId: string }) {
|
||||||
const { query } = useNavigation();
|
const {
|
||||||
const { view } = query;
|
query: { view },
|
||||||
|
} = useNavigation();
|
||||||
const params = Object.keys(query).reduce((obj, key) => {
|
|
||||||
if (FILTER_COLUMNS[key]) {
|
|
||||||
obj[key] = query[key];
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column gap="3">
|
<Column gap="3">
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<FilterTags websiteId={websiteId} params={params} />
|
<Panel>
|
||||||
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
|
<WebsiteMetricsBar websiteId={websiteId} showFilter={true} showChange={true} />
|
||||||
|
</Panel>
|
||||||
<Panel>
|
<Panel>
|
||||||
<WebsiteChart websiteId={websiteId} />
|
<WebsiteChart websiteId={websiteId} />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Icon, Icons, Text, Grid, Column } from '@umami/react-zen';
|
import { Icon, Icons, Text, Grid, Column } from '@umami/react-zen';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
import { useMessages, useNavigation } from '@/components/hooks';
|
import { useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { SideBar } from '@/components/layout/SideBar';
|
import { SideBar } from '@/components/common/SideBar';
|
||||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||||
import { CountriesTable } from '@/components/metrics/CountriesTable';
|
import { CountriesTable } from '@/components/metrics/CountriesTable';
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,80 @@
|
||||||
import { ReactNode } from 'react';
|
import {
|
||||||
import { Row, Heading } from '@umami/react-zen';
|
Column,
|
||||||
|
Row,
|
||||||
|
Heading,
|
||||||
|
MenuTrigger,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Icons,
|
||||||
|
Popover,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuSeparator,
|
||||||
|
} from '@umami/react-zen';
|
||||||
import { Favicon } from '@/components/common/Favicon';
|
import { Favicon } from '@/components/common/Favicon';
|
||||||
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
import { ActiveUsers } from '@/components/metrics/ActiveUsers';
|
||||||
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
|
import { WebsiteTabs } from '@/app/(main)/websites/[websiteId]/WebsiteTabs';
|
||||||
import { useWebsite } from '@/components/hooks/useWebsite';
|
import { useWebsite } from '@/components/hooks/useWebsite';
|
||||||
|
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||||
|
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||||
|
import { FilterTags } from '@/components/metrics/FilterTags';
|
||||||
|
import { useMessages } from '@/components/hooks';
|
||||||
|
|
||||||
export function WebsiteHeader({
|
export function WebsiteHeader({
|
||||||
websiteId,
|
websiteId,
|
||||||
children,
|
showFilter = true,
|
||||||
|
allowEdit = true,
|
||||||
|
compareMode = false,
|
||||||
}: {
|
}: {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
children?: ReactNode;
|
showFilter?: boolean;
|
||||||
|
allowEdit?: boolean;
|
||||||
|
compareMode?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const website = useWebsite();
|
const website = useWebsite();
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
const { name, domain } = website || {};
|
const { name, domain } = website || {};
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
|
||||||
|
{ label: formatMessage(labels.previousYear), value: 'yoy' },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Column marginY="6" gap="6">
|
||||||
<Row alignItems="center" gap="3" marginY="6">
|
<Row alignItems="center" justifyContent="space-between" gap="3">
|
||||||
<Favicon domain={domain} />
|
<Row alignItems="center" gap="3">
|
||||||
<Heading>
|
<Favicon domain={domain} />
|
||||||
{name}
|
<Heading>
|
||||||
<ActiveUsers websiteId={websiteId} />
|
{name}
|
||||||
</Heading>
|
<ActiveUsers websiteId={websiteId} />
|
||||||
{children}
|
</Heading>
|
||||||
|
</Row>
|
||||||
|
<Row alignItems="center" gap="3">
|
||||||
|
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
||||||
|
<WebsiteDateFilter websiteId={websiteId} />
|
||||||
|
{allowEdit && (
|
||||||
|
<MenuTrigger>
|
||||||
|
<Button variant="quiet">
|
||||||
|
<Icon>
|
||||||
|
<Icons.More />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Popover placement="bottom end">
|
||||||
|
<Menu>
|
||||||
|
<MenuItem>Compare dates</MenuItem>
|
||||||
|
<MenuItem>Share</MenuItem>
|
||||||
|
<MenuSeparator />
|
||||||
|
<MenuItem>Settings</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</Popover>
|
||||||
|
</MenuTrigger>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
</Row>
|
</Row>
|
||||||
|
{compareMode && items.map(item => <div key={item.value}>{item.label}</div>)}
|
||||||
|
<FilterTags websiteId={websiteId} />
|
||||||
<WebsiteTabs websiteId={websiteId} />
|
<WebsiteTabs websiteId={websiteId} />
|
||||||
</>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,16 @@
|
||||||
import { Select, ListItem } from '@umami/react-zen';
|
import { useDateRange, useMessages } from '@/components/hooks';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useDateRange, useMessages, useSticky } from '@/components/hooks';
|
|
||||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||||
import { formatShortTime, formatLongNumber } from '@/lib/format';
|
import { formatShortTime, formatLongNumber } from '@/lib/format';
|
||||||
import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery';
|
import { useWebsiteStatsQuery } from '@/components/hooks/queries/useWebsiteStatsQuery';
|
||||||
import { useWebsites, setWebsiteDateCompare } from '@/store/websites';
|
import { useWebsites } from '@/store/websites';
|
||||||
import { WebsiteFilterButton } from './WebsiteFilterButton';
|
|
||||||
import styles from './WebsiteMetricsBar.module.css';
|
|
||||||
|
|
||||||
export function WebsiteMetricsBar({
|
export function WebsiteMetricsBar({
|
||||||
websiteId,
|
websiteId,
|
||||||
sticky,
|
|
||||||
showChange = false,
|
showChange = false,
|
||||||
compareMode = false,
|
compareMode = false,
|
||||||
showFilter = false,
|
|
||||||
}: {
|
}: {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
sticky?: boolean;
|
|
||||||
showChange?: boolean;
|
showChange?: boolean;
|
||||||
compareMode?: boolean;
|
compareMode?: boolean;
|
||||||
showFilter?: boolean;
|
showFilter?: boolean;
|
||||||
|
|
@ -26,7 +18,6 @@ export function WebsiteMetricsBar({
|
||||||
const { dateRange } = useDateRange(websiteId);
|
const { dateRange } = useDateRange(websiteId);
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare);
|
const dateCompare = useWebsites(state => state[websiteId]?.dateCompare);
|
||||||
const { ref } = useSticky({ enabled: sticky });
|
|
||||||
const { data, isLoading, isFetched, error } = useWebsiteStatsQuery(
|
const { data, isLoading, isFetched, error } = useWebsiteStatsQuery(
|
||||||
websiteId,
|
websiteId,
|
||||||
compareMode && dateCompare,
|
compareMode && dateCompare,
|
||||||
|
|
@ -76,51 +67,23 @@ export function WebsiteMetricsBar({
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ label: formatMessage(labels.previousPeriod), value: 'prev' },
|
|
||||||
{ label: formatMessage(labels.previousYear), value: 'yoy' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={classNames(styles.container)}>
|
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||||
<div>
|
{metrics.map(({ label, value, prev, change, formatValue, reverseColors }) => {
|
||||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
return (
|
||||||
{metrics.map(({ label, value, prev, change, formatValue, reverseColors }) => {
|
<MetricCard
|
||||||
return (
|
key={label}
|
||||||
<MetricCard
|
value={value}
|
||||||
key={label}
|
previousValue={prev}
|
||||||
value={value}
|
label={label}
|
||||||
previousValue={prev}
|
change={change}
|
||||||
label={label}
|
formatValue={formatValue}
|
||||||
change={change}
|
reverseColors={reverseColors}
|
||||||
formatValue={formatValue}
|
showChange={!isAllTime && (compareMode || showChange)}
|
||||||
reverseColors={reverseColors}
|
showPrevious={!isAllTime && compareMode}
|
||||||
showChange={!isAllTime && (compareMode || showChange)}
|
/>
|
||||||
showPrevious={!isAllTime && compareMode}
|
);
|
||||||
/>
|
})}
|
||||||
);
|
</MetricsBar>
|
||||||
})}
|
|
||||||
</MetricsBar>
|
|
||||||
</div>
|
|
||||||
<div className={styles.actions}>
|
|
||||||
{showFilter && <WebsiteFilterButton websiteId={websiteId} />}
|
|
||||||
<WebsiteDateFilter websiteId={websiteId} showAllTime={!compareMode} />
|
|
||||||
{compareMode && (
|
|
||||||
<div className={styles.vs}>
|
|
||||||
<b>VS</b>
|
|
||||||
<Select
|
|
||||||
className={styles.dropdown}
|
|
||||||
items={items}
|
|
||||||
value={dateCompare || 'prev'}
|
|
||||||
onChange={(value: any) => setWebsiteDateCompare(websiteId, value)}
|
|
||||||
>
|
|
||||||
{items.map(({ label, value }) => (
|
|
||||||
<ListItem key={value}>{label}</ListItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Grid } from '@umami/react-zen';
|
import { Grid } from '@umami/react-zen';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { PagesTable } from '@/components/metrics/PagesTable';
|
import { PagesTable } from '@/components/metrics/PagesTable';
|
||||||
import { ReferrersTable } from '@/components/metrics/ReferrersTable';
|
import { ReferrersTable } from '@/components/metrics/ReferrersTable';
|
||||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,17 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Grid } from '@umami/react-zen';
|
import { Grid } from '@umami/react-zen';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { WebsiteHeader } from '../WebsiteHeader';
|
import { WebsiteHeader } from '../WebsiteHeader';
|
||||||
import { WebsiteMetricsBar } from '../WebsiteMetricsBar';
|
import { WebsiteMetricsBar } from '../WebsiteMetricsBar';
|
||||||
import { FilterTags } from '@/components/metrics/FilterTags';
|
import { FilterTags } from '@/components/metrics/FilterTags';
|
||||||
import { useNavigation } from '@/components/hooks';
|
|
||||||
import { FILTER_COLUMNS } from '@/lib/constants';
|
|
||||||
import { WebsiteChart } from '../WebsiteChart';
|
import { WebsiteChart } from '../WebsiteChart';
|
||||||
import { WebsiteCompareTables } from './WebsiteCompareTables';
|
import { WebsiteCompareTables } from './WebsiteCompareTables';
|
||||||
|
|
||||||
export function WebsiteComparePage({ websiteId }) {
|
export function WebsiteComparePage({ websiteId }) {
|
||||||
const { query } = useNavigation();
|
|
||||||
|
|
||||||
const params = Object.keys(query).reduce((obj, key) => {
|
|
||||||
if (FILTER_COLUMNS[key]) {
|
|
||||||
obj[key] = query[key];
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid gap="3">
|
<Grid gap="3">
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<FilterTags websiteId={websiteId} params={params} />
|
<FilterTags websiteId={websiteId} />
|
||||||
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} />
|
<WebsiteMetricsBar websiteId={websiteId} compareMode={true} showFilter={true} />
|
||||||
<Panel>
|
<Panel>
|
||||||
<WebsiteChart websiteId={websiteId} compareMode={true} />
|
<WebsiteChart websiteId={websiteId} compareMode={true} />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Grid, Heading, Column } from '@umami/react-zen';
|
import { Grid, Heading, Column } from '@umami/react-zen';
|
||||||
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
import { useDateRange, useMessages, useNavigation } from '@/components/hooks';
|
||||||
import { SideBar } from '@/components/layout/SideBar';
|
import { SideBar } from '@/components/common/SideBar';
|
||||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||||
import { ChangeLabel } from '@/components/metrics/ChangeLabel';
|
import { ChangeLabel } from '@/components/metrics/ChangeLabel';
|
||||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,35 @@
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery';
|
import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery';
|
||||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||||
import { formatLongNumber } from '@/lib/format';
|
import { formatLongNumber } from '@/lib/format';
|
||||||
import { Flexbox } from '@umami/react-zen';
|
|
||||||
|
|
||||||
export function EventsMetricsBar({ websiteId }: { websiteId: string }) {
|
export function EventsMetricsBar({ websiteId }: { websiteId: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
|
const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox direction="row" justifyContent="space-between" style={{ minHeight: 120 }}>
|
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.visitors?.value}
|
||||||
value={data?.visitors?.value}
|
label={formatMessage(labels.visitors)}
|
||||||
label={formatMessage(labels.visitors)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.visits?.value}
|
||||||
value={data?.visits?.value}
|
label={formatMessage(labels.visits)}
|
||||||
label={formatMessage(labels.visits)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.pageviews?.value}
|
||||||
value={data?.pageviews?.value}
|
label={formatMessage(labels.views)}
|
||||||
label={formatMessage(labels.views)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.events?.value}
|
||||||
value={data?.events?.value}
|
label={formatMessage(labels.events)}
|
||||||
label={formatMessage(labels.events)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
</MetricsBar>
|
||||||
</MetricsBar>
|
|
||||||
<WebsiteDateFilter websiteId={websiteId} />
|
|
||||||
</Flexbox>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { TabList, Tab, Tabs, TabPanel, Grid } from '@umami/react-zen';
|
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { WebsiteHeader } from '../WebsiteHeader';
|
import { WebsiteHeader } from '../WebsiteHeader';
|
||||||
import { EventsDataTable } from './EventsDataTable';
|
import { EventsDataTable } from './EventsDataTable';
|
||||||
import { EventsMetricsBar } from './EventsMetricsBar';
|
import { EventsMetricsBar } from './EventsMetricsBar';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { EventsChart } from '@/components/metrics/EventsChart';
|
import { EventsChart } from '@/components/metrics/EventsChart';
|
||||||
import { GridRow } from '@/components/layout/GridRow';
|
import { GridRow } from '@/components/common/GridRow';
|
||||||
import { MetricsTable } from '@/components/metrics/MetricsTable';
|
import { MetricsTable } from '@/components/metrics/MetricsTable';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { EventProperties } from './EventProperties';
|
import { EventProperties } from './EventProperties';
|
||||||
|
|
@ -16,9 +16,11 @@ export function EventsPage({ websiteId }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid gap="3">
|
<Column gap="3">
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<EventsMetricsBar websiteId={websiteId} />
|
<Panel>
|
||||||
|
<EventsMetricsBar websiteId={websiteId} />
|
||||||
|
</Panel>
|
||||||
<GridRow layout="two-one">
|
<GridRow layout="two-one">
|
||||||
<Panel>
|
<Panel>
|
||||||
<EventsChart websiteId={websiteId} />
|
<EventsChart websiteId={websiteId} />
|
||||||
|
|
@ -46,6 +48,6 @@ export function EventsPage({ websiteId }) {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Grid>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,18 @@
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { RealtimeData } from '@/lib/types';
|
import { RealtimeData } from '@/lib/types';
|
||||||
import styles from './RealtimeHeader.module.css';
|
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||||
|
|
||||||
export function RealtimeHeader({ data }: { data: RealtimeData }) {
|
export function RealtimeHeader({ data }: { data: RealtimeData }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { totals }: any = data || {};
|
const { totals }: any = data || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<MetricsBar isFetched={true}>
|
||||||
<div className={styles.metrics}>
|
<MetricCard label={formatMessage(labels.views)} value={totals.views} />
|
||||||
<MetricCard
|
<MetricCard label={formatMessage(labels.visitors)} value={totals.visitors} />
|
||||||
className={styles.card}
|
<MetricCard label={formatMessage(labels.events)} value={totals.events} />
|
||||||
label={formatMessage(labels.views)}
|
<MetricCard label={formatMessage(labels.countries)} value={totals.countries} />
|
||||||
value={totals.views}
|
</MetricsBar>
|
||||||
/>
|
|
||||||
<MetricCard
|
|
||||||
className={styles.card}
|
|
||||||
label={formatMessage(labels.visitors)}
|
|
||||||
value={totals.visitors}
|
|
||||||
/>
|
|
||||||
<MetricCard
|
|
||||||
className={styles.card}
|
|
||||||
label={formatMessage(labels.events)}
|
|
||||||
value={totals.events}
|
|
||||||
/>
|
|
||||||
<MetricCard
|
|
||||||
className={styles.card}
|
|
||||||
label={formatMessage(labels.countries)}
|
|
||||||
value={totals.countries}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Page } from '@/components/layout/Page';
|
import { Page } from '@/components/common/Page';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { useApi, useMessages } from '@/components/hooks';
|
import { useApi, useMessages } from '@/components/hooks';
|
||||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { firstBy } from 'thenby';
|
import { firstBy } from 'thenby';
|
||||||
import { Grid } from '@umami/react-zen';
|
import { Grid } from '@umami/react-zen';
|
||||||
import { GridRow } from '@/components/layout/GridRow';
|
import { GridRow } from '@/components/common/GridRow';
|
||||||
import { Page } from '@/components/layout/Page';
|
import { Page } from '@/components/common/Page';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
import { RealtimeChart } from '@/components/metrics/RealtimeChart';
|
import { RealtimeChart } from '@/components/metrics/RealtimeChart';
|
||||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||||
import { useRealtimeQuery } from '@/components/hooks';
|
import { useRealtimeQuery } from '@/components/hooks';
|
||||||
|
|
@ -30,7 +30,9 @@ export function WebsiteRealtimePage({ websiteId }) {
|
||||||
return (
|
return (
|
||||||
<Grid gap="3">
|
<Grid gap="3">
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<RealtimeHeader data={data} />
|
<Panel>
|
||||||
|
<RealtimeHeader data={data} />
|
||||||
|
</Panel>
|
||||||
<Panel>
|
<Panel>
|
||||||
<RealtimeChart data={data} unit="minute" />
|
<RealtimeChart data={data} unit="minute" />
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,35 @@
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery';
|
import { useWebsiteSessionStatsQuery } from '@/components/hooks/queries/useWebsiteSessionStatsQuery';
|
||||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
|
||||||
import { MetricCard } from '@/components/metrics/MetricCard';
|
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||||
import { formatLongNumber } from '@/lib/format';
|
import { formatLongNumber } from '@/lib/format';
|
||||||
import { Flexbox } from '@umami/react-zen';
|
|
||||||
|
|
||||||
export function SessionsMetricsBar({ websiteId }: { websiteId: string }) {
|
export function SessionsMetricsBar({ websiteId }: { websiteId: string }) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
|
const { data, isLoading, isFetched, error } = useWebsiteSessionStatsQuery(websiteId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flexbox direction="row" justifyContent="space-between" style={{ minHeight: 120 }}>
|
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
||||||
<MetricsBar isLoading={isLoading} isFetched={isFetched} error={error}>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.visitors?.value}
|
||||||
value={data?.visitors?.value}
|
label={formatMessage(labels.visitors)}
|
||||||
label={formatMessage(labels.visitors)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.visits?.value}
|
||||||
value={data?.visits?.value}
|
label={formatMessage(labels.visits)}
|
||||||
label={formatMessage(labels.visits)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.pageviews?.value}
|
||||||
value={data?.pageviews?.value}
|
label={formatMessage(labels.views)}
|
||||||
label={formatMessage(labels.views)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
<MetricCard
|
||||||
<MetricCard
|
value={data?.countries?.value}
|
||||||
value={data?.countries?.value}
|
label={formatMessage(labels.countries)}
|
||||||
label={formatMessage(labels.countries)}
|
formatValue={formatLongNumber}
|
||||||
formatValue={formatLongNumber}
|
/>
|
||||||
/>
|
</MetricsBar>
|
||||||
</MetricsBar>
|
|
||||||
<WebsiteDateFilter websiteId={websiteId} />
|
|
||||||
</Flexbox>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,26 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { TabList, Tab, Tabs, TabPanel, Column } from '@umami/react-zen';
|
||||||
import { WebsiteHeader } from '../WebsiteHeader';
|
import { WebsiteHeader } from '../WebsiteHeader';
|
||||||
import { SessionsDataTable } from './SessionsDataTable';
|
import { SessionsDataTable } from './SessionsDataTable';
|
||||||
import { SessionsMetricsBar } from './SessionsMetricsBar';
|
import { SessionsMetricsBar } from './SessionsMetricsBar';
|
||||||
import { SessionProperties } from './SessionProperties';
|
import { SessionProperties } from './SessionProperties';
|
||||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||||
import { GridRow } from '@/components/layout/GridRow';
|
import { GridRow } from '@/components/common/GridRow';
|
||||||
import { TabList, Tab, Tabs, TabPanel } from '@umami/react-zen';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
import { SessionsWeekly } from './SessionsWeekly';
|
import { SessionsWeekly } from './SessionsWeekly';
|
||||||
import { Panel } from '@/components/layout/Panel';
|
import { Panel } from '@/components/common/Panel';
|
||||||
|
|
||||||
export function SessionsPage({ websiteId }) {
|
export function SessionsPage({ websiteId }) {
|
||||||
const [tab, setTab] = useState('activity');
|
const [tab, setTab] = useState('activity');
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Column gap="3">
|
||||||
<WebsiteHeader websiteId={websiteId} />
|
<WebsiteHeader websiteId={websiteId} />
|
||||||
<SessionsMetricsBar websiteId={websiteId} />
|
<Panel>
|
||||||
|
<SessionsMetricsBar websiteId={websiteId} />
|
||||||
|
</Panel>
|
||||||
<GridRow layout="two-one">
|
<GridRow layout="two-one">
|
||||||
<Panel padding="0">
|
<Panel padding="0">
|
||||||
<WorldMap websiteId={websiteId} />
|
<WorldMap websiteId={websiteId} />
|
||||||
|
|
@ -41,6 +43,6 @@ export function SessionsPage({ websiteId }) {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Panel>
|
</Panel>
|
||||||
</>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { WebsiteDetailsPage } from '../../(main)/websites/[websiteId]/WebsiteDetailsPage';
|
import { WebsiteDetailsPage } from '../../(main)/websites/[websiteId]/WebsiteDetailsPage';
|
||||||
import { useShareTokenQuery } from '@/components/hooks';
|
import { useShareTokenQuery } from '@/components/hooks';
|
||||||
import { Page } from '@/components/layout/Page';
|
import { Page } from '@/components/common/Page';
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { Footer } from './Footer';
|
import { Footer } from './Footer';
|
||||||
import styles from './SharePage.module.css';
|
import styles from './SharePage.module.css';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { Column } from '@umami/react-zen';
|
import { Column } from '@umami/react-zen';
|
||||||
|
|
||||||
export interface BoardProps {}
|
export interface BoardProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
export function Board(props: BoardProps) {
|
}
|
||||||
return <Column>{}</Column>;
|
|
||||||
|
export function Board({ children }: BoardProps) {
|
||||||
|
return <Column>{children}</Column>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ export function Chart({
|
||||||
onCreate,
|
onCreate,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onTooltip,
|
onTooltip,
|
||||||
className,
|
|
||||||
chartOptions,
|
chartOptions,
|
||||||
tooltip,
|
tooltip,
|
||||||
...props
|
...props
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Row, Column, Text } from '@umami/react-zen';
|
||||||
|
|
||||||
export function ActionForm({ label, description, children }) {
|
export function ActionForm({ label, description, children }) {
|
||||||
return (
|
return (
|
||||||
<Row padding="6" borderSize="1" borderRadius="3" justifyContent="space-between" shadow="2">
|
<Row padding="6" border borderRadius="3" justifyContent="space-between" shadow="2">
|
||||||
<Column>
|
<Column>
|
||||||
<Text weight="bold">{label}</Text>
|
<Text weight="bold">{label}</Text>
|
||||||
<Text>{description}</Text>
|
<Text>{description}</Text>
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Heading, Icon, Row } from '@umami/react-zen';
|
import { Heading, Icon, Row, Text } from '@umami/react-zen';
|
||||||
|
|
||||||
export function PageHeader({
|
export function PageHeader({
|
||||||
title,
|
title,
|
||||||
|
description,
|
||||||
icon,
|
icon,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
title?: ReactNode;
|
title: string;
|
||||||
|
description?: string;
|
||||||
icon?: ReactNode;
|
icon?: ReactNode;
|
||||||
|
allowEdit?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Row justifyContent="space-between" alignItems="center">
|
<Row justifyContent="space-between" alignItems="center" marginY="6">
|
||||||
<Row gap="3">
|
<Row gap="3">
|
||||||
{icon && <Icon size="lg">{icon}</Icon>}
|
{icon && <Icon size="lg">{icon}</Icon>}
|
||||||
{title && <Heading>{title}</Heading>}
|
{title && <Heading size="2">{title}</Heading>}
|
||||||
|
{description && <Text color="muted">{description}</Text>}
|
||||||
</Row>
|
</Row>
|
||||||
<Row justifyContent="flex-end">{children}</Row>
|
<Row justifyContent="flex-end">{children}</Row>
|
||||||
</Row>
|
</Row>
|
||||||
6
src/components/common/Panel.tsx
Normal file
6
src/components/common/Panel.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Box } from '@umami/react-zen';
|
||||||
|
import type { BoxProps } from '@umami/react-zen/Box';
|
||||||
|
|
||||||
|
export function Panel(props: BoxProps) {
|
||||||
|
return <Box padding="6" border borderRadius="3" backgroundColor="solid" shadow="4" {...props} />;
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ export * from './useMessages';
|
||||||
export * from './useModified';
|
export * from './useModified';
|
||||||
export * from './usePagedQuery';
|
export * from './usePagedQuery';
|
||||||
export * from './useRegionNames';
|
export * from './useRegionNames';
|
||||||
|
export * from './useReport';
|
||||||
export * from './useSticky';
|
export * from './useSticky';
|
||||||
export * from './useNavigation';
|
export * from './useNavigation';
|
||||||
export * from './useTheme';
|
export * from './useTheme';
|
||||||
|
|
|
||||||
6
src/components/hooks/useReport.ts
Normal file
6
src/components/hooks/useReport.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { ReportContext } from '@/app/(main)/reports/[reportId]/Report';
|
||||||
|
|
||||||
|
export function useReport() {
|
||||||
|
return useContext(ReportContext);
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ const selector = (state: { theme: string }) => state.theme;
|
||||||
|
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
const theme = useApp(selector) || getItem(THEME_CONFIG) || DEFAULT_THEME;
|
const theme = useApp(selector) || getItem(THEME_CONFIG) || DEFAULT_THEME;
|
||||||
|
const { primary, text, line, fill } = THEME_COLORS[theme];
|
||||||
const primaryColor = colord(THEME_COLORS[theme].primary);
|
const primaryColor = colord(THEME_COLORS[theme].primary);
|
||||||
|
|
||||||
const colors = useMemo(() => {
|
const colors = useMemo(() => {
|
||||||
|
|
@ -16,8 +17,8 @@ export function useTheme() {
|
||||||
...THEME_COLORS[theme],
|
...THEME_COLORS[theme],
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
text: THEME_COLORS[theme].gray700,
|
text,
|
||||||
line: THEME_COLORS[theme].gray200,
|
line,
|
||||||
views: {
|
views: {
|
||||||
hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(),
|
hoverBackgroundColor: primaryColor.alpha(0.7).toRgbString(),
|
||||||
backgroundColor: primaryColor.alpha(0.4).toRgbString(),
|
backgroundColor: primaryColor.alpha(0.4).toRgbString(),
|
||||||
|
|
@ -32,10 +33,10 @@ export function useTheme() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
map: {
|
map: {
|
||||||
baseColor: THEME_COLORS[theme].primary,
|
baseColor: primary,
|
||||||
fillColor: THEME_COLORS[theme].gray100,
|
fillColor: fill,
|
||||||
strokeColor: THEME_COLORS[theme].primary,
|
strokeColor: primary,
|
||||||
hoverColor: THEME_COLORS[theme].primary,
|
hoverColor: primary,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,9 @@ export function DateFilter({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{divider && <ListSeparator />}
|
{divider && <ListSeparator />}
|
||||||
<ListItem id={value}>{label}</ListItem>
|
<ListItem key={value} id={value}>
|
||||||
|
{label}
|
||||||
|
</ListItem>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { useDateRange } from '@/components/hooks';
|
import { useDateRange } from '@/components/hooks';
|
||||||
import { isAfter } from 'date-fns';
|
import { isAfter } from 'date-fns';
|
||||||
import { getOffsetDateRange } from '@/lib/date';
|
import { getOffsetDateRange } from '@/lib/date';
|
||||||
import { Button, Icon, Icons } from '@umami/react-zen';
|
import { Button, Icon, Icons, Row } from '@umami/react-zen';
|
||||||
import { DateFilter } from './DateFilter';
|
import { DateFilter } from './DateFilter';
|
||||||
import styles from './WebsiteDateFilter.module.css';
|
|
||||||
import { DateRange } from '@/lib/types';
|
import { DateRange } from '@/lib/types';
|
||||||
|
|
||||||
export function WebsiteDateFilter({
|
export function WebsiteDateFilter({
|
||||||
|
|
@ -27,9 +26,22 @@ export function WebsiteDateFilter({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<Row gap="3">
|
||||||
|
{value !== 'all' && !value.startsWith('range') && (
|
||||||
|
<Row gap="1">
|
||||||
|
<Button onPress={() => handleIncrement(-1)} variant="quiet">
|
||||||
|
<Icon size="xs" rotate={180}>
|
||||||
|
<Icons.Chevron />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Button onPress={() => handleIncrement(1)} variant="quiet" isDisabled={disableForward}>
|
||||||
|
<Icon size="xs">
|
||||||
|
<Icons.Chevron />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
<DateFilter
|
<DateFilter
|
||||||
className={styles.dropdown}
|
|
||||||
value={value}
|
value={value}
|
||||||
startDate={startDate}
|
startDate={startDate}
|
||||||
endDate={endDate}
|
endDate={endDate}
|
||||||
|
|
@ -37,20 +49,6 @@ export function WebsiteDateFilter({
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
showAllTime={showAllTime}
|
showAllTime={showAllTime}
|
||||||
/>
|
/>
|
||||||
{value !== 'all' && !value.startsWith('range') && (
|
</Row>
|
||||||
<div className={styles.buttons}>
|
|
||||||
<Button onPress={() => handleIncrement(-1)}>
|
|
||||||
<Icon size="sm" rotate={180}>
|
|
||||||
<Icons.Chevron />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
<Button onPress={() => handleIncrement(1)} isDisabled={disableForward}>
|
|
||||||
<Icon size="sm">
|
|
||||||
<Icons.Chevron />
|
|
||||||
</Icon>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { Box } from '@umami/react-zen';
|
|
||||||
import type { BoxProps } from '@umami/react-zen/Box';
|
|
||||||
|
|
||||||
export function Panel(props: BoxProps) {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
padding="6"
|
|
||||||
borderSize="1"
|
|
||||||
borderRadius="3"
|
|
||||||
backgroundColor="solid"
|
|
||||||
shadow="4"
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -9,18 +9,12 @@ import {
|
||||||
useFilters,
|
useFilters,
|
||||||
} from '@/components/hooks';
|
} from '@/components/hooks';
|
||||||
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
|
import { FieldFilterEditForm } from '@/app/(main)/reports/[reportId]/FieldFilterEditForm';
|
||||||
import { OPERATOR_PREFIXES } from '@/lib/constants';
|
import { FILTER_COLUMNS, OPERATOR_PREFIXES } from '@/lib/constants';
|
||||||
import { isSearchOperator, parseParameterValue } from '@/lib/params';
|
import { isSearchOperator, parseParameterValue } from '@/lib/params';
|
||||||
import styles from './FilterTags.module.css';
|
import styles from './FilterTags.module.css';
|
||||||
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
import { WebsiteFilterButton } from '@/app/(main)/websites/[websiteId]/WebsiteFilterButton';
|
||||||
|
|
||||||
export function FilterTags({
|
export function FilterTags({ websiteId }: { websiteId: string }) {
|
||||||
websiteId,
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
websiteId: string;
|
|
||||||
params: { [key: string]: string };
|
|
||||||
}) {
|
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const { dateRange } = useDateRange(websiteId);
|
const { dateRange } = useDateRange(websiteId);
|
||||||
|
|
@ -32,6 +26,14 @@ export function FilterTags({
|
||||||
const { fields } = useFields();
|
const { fields } = useFields();
|
||||||
const { operatorLabels } = useFilters();
|
const { operatorLabels } = useFilters();
|
||||||
const { startDate, endDate } = dateRange;
|
const { startDate, endDate } = dateRange;
|
||||||
|
const { query } = useNavigation();
|
||||||
|
|
||||||
|
const params = Object.keys(query).reduce((obj, key) => {
|
||||||
|
if (FILTER_COLUMNS[key]) {
|
||||||
|
obj[key] = query[key];
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
if (Object.keys(params).filter(key => params[key]).length === 0) {
|
if (Object.keys(params).filter(key => params[key]).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -60,13 +62,13 @@ export function FilterTags({
|
||||||
return (
|
return (
|
||||||
<Row
|
<Row
|
||||||
gap="3"
|
gap="3"
|
||||||
backgroundColor="1"
|
backgroundColor="2"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
paddingX="3"
|
paddingX="5"
|
||||||
paddingY="2"
|
paddingY="3"
|
||||||
|
border
|
||||||
borderRadius="2"
|
borderRadius="2"
|
||||||
borderSize="1"
|
wrap="wrap"
|
||||||
marginBottom="6"
|
|
||||||
>
|
>
|
||||||
<Text weight="bold">{formatMessage(labels.filters)}</Text>
|
<Text weight="bold">{formatMessage(labels.filters)}</Text>
|
||||||
{Object.keys(params).map(key => {
|
{Object.keys(params).map(key => {
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,8 @@
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
border-right: 1px solid var(--border-color);
|
||||||
flex-direction: column;
|
padding: 0 50px;
|
||||||
justify-content: center;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.compare .change {
|
|
||||||
font-size: 16px;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:first-child {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:last-child {
|
.card:last-child {
|
||||||
border: 0;
|
border-right: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: 36px;
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--base900);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value.prev {
|
|
||||||
color: var(--base800);
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: var(--base800);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import classNames from 'classnames';
|
import { Text, Column } from '@umami/react-zen';
|
||||||
import { useSpring } from '@react-spring/web';
|
import { useSpring } from '@react-spring/web';
|
||||||
import { formatNumber } from '@/lib/format';
|
import { formatNumber } from '@/lib/format';
|
||||||
import { AnimatedDiv } from '@/components/common/AnimatedDiv';
|
import { AnimatedDiv } from '@/components/common/AnimatedDiv';
|
||||||
|
|
@ -15,7 +15,6 @@ export interface MetricCardProps {
|
||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
showChange?: boolean;
|
showChange?: boolean;
|
||||||
showPrevious?: boolean;
|
showPrevious?: boolean;
|
||||||
className?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MetricCard = ({
|
export const MetricCard = ({
|
||||||
|
|
@ -27,7 +26,6 @@ export const MetricCard = ({
|
||||||
showLabel = true,
|
showLabel = true,
|
||||||
showChange = false,
|
showChange = false,
|
||||||
showPrevious = false,
|
showPrevious = false,
|
||||||
className,
|
|
||||||
}: MetricCardProps) => {
|
}: MetricCardProps) => {
|
||||||
const diff = value - change;
|
const diff = value - change;
|
||||||
const pct = ((value - diff) / diff) * 100;
|
const pct = ((value - diff) / diff) * 100;
|
||||||
|
|
@ -36,26 +34,19 @@ export const MetricCard = ({
|
||||||
const prevProps = useSpring({ x: Number(diff) || 0, from: { x: 0 } });
|
const prevProps = useSpring({ x: Number(diff) || 0, from: { x: 0 } });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.card, className, showPrevious && styles.compare)}>
|
<Column className={styles.card} justifyContent="center">
|
||||||
{showLabel && <div className={styles.label}>{label}</div>}
|
{showLabel && <Text weight="bold">{label}</Text>}
|
||||||
<AnimatedDiv className={styles.value} title={value?.toString()}>
|
<Text size="8" weight="bold" wrap="nowrap">
|
||||||
{props?.x?.to(x => formatValue(x))}
|
<AnimatedDiv title={value?.toString()}>{props?.x?.to(x => formatValue(x))}</AnimatedDiv>
|
||||||
</AnimatedDiv>
|
</Text>
|
||||||
{showChange && (
|
{showChange && (
|
||||||
<ChangeLabel
|
<ChangeLabel value={change} title={formatValue(change)} reverseColors={reverseColors}>
|
||||||
className={styles.change}
|
|
||||||
value={change}
|
|
||||||
title={formatValue(change)}
|
|
||||||
reverseColors={reverseColors}
|
|
||||||
>
|
|
||||||
<AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv>
|
<AnimatedDiv>{changeProps?.x?.to(x => `${Math.abs(~~x)}%`)}</AnimatedDiv>
|
||||||
</ChangeLabel>
|
</ChangeLabel>
|
||||||
)}
|
)}
|
||||||
{showPrevious && (
|
{showPrevious && (
|
||||||
<AnimatedDiv className={classNames(styles.value, styles.prev)} title={diff.toString()}>
|
<AnimatedDiv title={diff.toString()}>{prevProps?.x?.to(x => formatValue(x))}</AnimatedDiv>
|
||||||
{prevProps?.x?.to(x => formatValue(x))}
|
|
||||||
</AnimatedDiv>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
.bar {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
|
|
||||||
gap: 20px;
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.bar {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Loading, Row } from '@umami/react-zen';
|
import { Grid, Loading } from '@umami/react-zen';
|
||||||
import { cloneChildren } from '@/lib/react';
|
|
||||||
import { ErrorMessage } from '@/components/common/ErrorMessage';
|
import { ErrorMessage } from '@/components/common/ErrorMessage';
|
||||||
import { formatLongNumber } from '@/lib/format';
|
|
||||||
|
|
||||||
export interface MetricsBarProps {
|
export interface MetricsBarProps {
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|
@ -12,18 +10,15 @@ export interface MetricsBarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBarProps) {
|
export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBarProps) {
|
||||||
const formatFunc = n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<>
|
||||||
{isLoading && !isFetched && <Loading icon="dots" />}
|
{isLoading && !isFetched && <Loading icon="dots" />}
|
||||||
{error && <ErrorMessage />}
|
{error && <ErrorMessage />}
|
||||||
{!isLoading &&
|
{!isLoading && !error && isFetched && (
|
||||||
!error &&
|
<Grid columns="repeat(auto-fill, minmax(200px, 1fr))" width="100%" gapY="3">
|
||||||
isFetched &&
|
{children}
|
||||||
cloneChildren(children, child => {
|
</Grid>
|
||||||
return { format: child?.props['format'] || formatFunc };
|
)}
|
||||||
})}
|
</>
|
||||||
</Row>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ export * from '@/components/common/Favicon';
|
||||||
export * from '@/components/common/FilterButtons';
|
export * from '@/components/common/FilterButtons';
|
||||||
export * from '@/components/common/FilterLink';
|
export * from '@/components/common/FilterLink';
|
||||||
export * from '@/components/common/HamburgerButton';
|
export * from '@/components/common/HamburgerButton';
|
||||||
export * from '@/components/common/HoverTooltip';
|
|
||||||
export * from '@/components/common/LinkButton';
|
export * from '@/components/common/LinkButton';
|
||||||
export * from '@/components/common/MobileMenu';
|
export * from '@/components/common/MobileMenu';
|
||||||
export * from '@/components/common/Pager';
|
export * from '@/components/common/Pager';
|
||||||
|
|
|
||||||
|
|
@ -195,31 +195,15 @@ export const ROLE_PERMISSIONS = {
|
||||||
export const THEME_COLORS = {
|
export const THEME_COLORS = {
|
||||||
light: {
|
light: {
|
||||||
primary: '#2680eb',
|
primary: '#2680eb',
|
||||||
gray50: '#ffffff',
|
text: '#838383',
|
||||||
gray75: '#fafafa',
|
line: '#d9d9d9',
|
||||||
gray100: '#f5f5f5',
|
fill: '#f9f9f9',
|
||||||
gray200: '#eaeaea',
|
|
||||||
gray300: '#e1e1e1',
|
|
||||||
gray400: '#cacaca',
|
|
||||||
gray500: '#b3b3b3',
|
|
||||||
gray600: '#8e8e8e',
|
|
||||||
gray700: '#6e6e6e',
|
|
||||||
gray800: '#4b4b4b',
|
|
||||||
gray900: '#2c2c2c',
|
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
primary: '#2680eb',
|
primary: '#2680eb',
|
||||||
gray50: '#252525',
|
text: '#7b7b7b',
|
||||||
gray75: '#2f2f2f',
|
line: '#3a3a3a',
|
||||||
gray100: '#323232',
|
fill: '#191919',
|
||||||
gray200: '#3e3e3e',
|
|
||||||
gray300: '#4a4a4a',
|
|
||||||
gray400: '#5a5a5a',
|
|
||||||
gray500: '#6e6e6e',
|
|
||||||
gray600: '#909090',
|
|
||||||
gray700: '#b9b9b9',
|
|
||||||
gray800: '#e3e3e3',
|
|
||||||
gray900: '#ffffff',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export function isValidTimezone(timezone: string) {
|
||||||
try {
|
try {
|
||||||
Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export function formatCurrency(value: number, currency: string, locale = 'en-US'
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: currency,
|
currency: currency,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Fallback to default currency format if an error occurs
|
// Fallback to default currency format if an error occurs
|
||||||
formattedValue = new Intl.NumberFormat(locale, {
|
formattedValue = new Intl.NumberFormat(locale, {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function safeDecodeURI(s: string | undefined | null): string | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return decodeURI(s);
|
return decodeURI(s);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ export function safeDecodeURIComponent(s: string | undefined | null): string | u
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return decodeURIComponent(s);
|
return decodeURIComponent(s);
|
||||||
} catch (e) {
|
} catch {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@
|
||||||
disabled = !!data.disabled;
|
disabled = !!data.disabled;
|
||||||
cache = data.cache;
|
cache = data.cache;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue