mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 18:56:39 +00:00
Date picker working
This commit is contained in:
@@ -1901,5 +1901,6 @@
|
|||||||
"actor": "Actor",
|
"actor": "Actor",
|
||||||
"timestamp": "Timestamp",
|
"timestamp": "Timestamp",
|
||||||
"accessLogs": "Access Logs",
|
"accessLogs": "Access Logs",
|
||||||
"exportCsv": "Export CSV"
|
"exportCsv": "Export CSV",
|
||||||
|
"actorId": "Actor ID"
|
||||||
}
|
}
|
||||||
|
|||||||
77
package-lock.json
generated
77
package-lock.json
generated
@@ -52,6 +52,7 @@
|
|||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"date-fns": "4.1.0",
|
||||||
"drizzle-orm": "0.44.6",
|
"drizzle-orm": "0.44.6",
|
||||||
"eslint": "9.37.0",
|
"eslint": "9.37.0",
|
||||||
"eslint-config-next": "15.5.6",
|
"eslint-config-next": "15.5.6",
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
"posthog-node": "^5.9.5",
|
"posthog-node": "^5.9.5",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
|
"react-day-picker": "9.11.1",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-easy-sort": "^1.8.0",
|
"react-easy-sort": "^1.8.0",
|
||||||
"react-hook-form": "7.65.0",
|
"react-hook-form": "7.65.0",
|
||||||
@@ -1628,6 +1630,7 @@
|
|||||||
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
|
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.26.2",
|
"@babel/code-frame": "^7.26.2",
|
||||||
@@ -1979,6 +1982,12 @@
|
|||||||
"kuler": "^2.0.0"
|
"kuler": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@date-fns/tz": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@dotenvx/dotenvx": {
|
"node_modules/@dotenvx/dotenvx": {
|
||||||
"version": "1.51.0",
|
"version": "1.51.0",
|
||||||
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.0.tgz",
|
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.51.0.tgz",
|
||||||
@@ -4015,6 +4024,7 @@
|
|||||||
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
|
"integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14.21.3 || >=16"
|
"node": "^14.21.3 || >=16"
|
||||||
},
|
},
|
||||||
@@ -6844,6 +6854,7 @@
|
|||||||
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -7049,6 +7060,7 @@
|
|||||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -7059,6 +7071,7 @@
|
|||||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.25.0"
|
"scheduler": "^0.25.0"
|
||||||
},
|
},
|
||||||
@@ -8490,6 +8503,7 @@
|
|||||||
"integrity": "sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==",
|
"integrity": "sha512-fnQmj8lELIj7BSrZQAdBMHEHX8OZLYIHXqAKT1O7tDfLxaINzf00PMjw22r3N/xXh0w/sGHlO6SVaCQ2mj78lg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -8576,6 +8590,7 @@
|
|||||||
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "*",
|
"@types/body-parser": "*",
|
||||||
"@types/express-serve-static-core": "^5.0.0",
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
@@ -8669,6 +8684,7 @@
|
|||||||
"integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==",
|
"integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.14.0"
|
"undici-types": "~7.14.0"
|
||||||
}
|
}
|
||||||
@@ -8697,6 +8713,7 @@
|
|||||||
"integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==",
|
"integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"pg-protocol": "*",
|
"pg-protocol": "*",
|
||||||
@@ -8730,6 +8747,7 @@
|
|||||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -8740,6 +8758,7 @@
|
|||||||
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
@@ -8883,6 +8902,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.1.tgz",
|
||||||
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
|
"integrity": "sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.46.1",
|
"@typescript-eslint/scope-manager": "8.46.1",
|
||||||
"@typescript-eslint/types": "8.46.1",
|
"@typescript-eslint/types": "8.46.1",
|
||||||
@@ -9556,6 +9576,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -10086,6 +10107,7 @@
|
|||||||
"integrity": "sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==",
|
"integrity": "sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"prebuild-install": "^7.1.1"
|
"prebuild-install": "^7.1.1"
|
||||||
@@ -10199,6 +10221,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.9",
|
"baseline-browser-mapping": "^2.8.9",
|
||||||
"caniuse-lite": "^1.0.30001746",
|
"caniuse-lite": "^1.0.30001746",
|
||||||
@@ -10936,6 +10959,22 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns-jalali": {
|
||||||
|
"version": "4.1.0-0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
|
||||||
|
"integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/debounce": {
|
"node_modules/debounce": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz",
|
||||||
@@ -11839,6 +11878,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
@@ -11935,6 +11975,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
|
||||||
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
|
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -12102,6 +12143,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -12391,6 +12433,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.0",
|
"body-parser": "^2.2.0",
|
||||||
@@ -17514,6 +17557,7 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -18492,6 +18536,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.9.1",
|
"pg-connection-string": "^2.9.1",
|
||||||
"pg-pool": "^3.10.1",
|
"pg-pool": "^3.10.1",
|
||||||
@@ -18668,6 +18713,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -19119,15 +19165,38 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
|
||||||
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-day-picker": {
|
||||||
|
"version": "9.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.11.1.tgz",
|
||||||
|
"integrity": "sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@date-fns/tz": "^1.4.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"date-fns-jalali": "^4.1.0-0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/gpbl"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.0",
|
"version": "19.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -19419,6 +19488,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz",
|
||||||
"integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==",
|
"integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@@ -19903,6 +19973,7 @@
|
|||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
@@ -21068,7 +21139,8 @@
|
|||||||
"version": "4.1.14",
|
"version": "4.1.14",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
@@ -21625,6 +21697,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -22130,6 +22203,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz",
|
||||||
"integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==",
|
"integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@colors/colors": "^1.6.0",
|
"@colors/colors": "^1.6.0",
|
||||||
"@dabh/diagnostics": "^2.0.8",
|
"@dabh/diagnostics": "^2.0.8",
|
||||||
@@ -22437,6 +22511,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"date-fns": "4.1.0",
|
||||||
"drizzle-orm": "0.44.6",
|
"drizzle-orm": "0.44.6",
|
||||||
"eslint": "9.37.0",
|
"eslint": "9.37.0",
|
||||||
"eslint-config-next": "15.5.6",
|
"eslint-config-next": "15.5.6",
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
"posthog-node": "^5.9.5",
|
"posthog-node": "^5.9.5",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
|
"react-day-picker": "9.11.1",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-easy-sort": "^1.8.0",
|
"react-easy-sort": "^1.8.0",
|
||||||
"react-hook-form": "7.65.0",
|
"react-hook-form": "7.65.0",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export function querySites(timeStart: number, timeEnd: number, orgId: string) {
|
|||||||
orgId: actionAuditLog.orgId,
|
orgId: actionAuditLog.orgId,
|
||||||
action: actionAuditLog.action,
|
action: actionAuditLog.action,
|
||||||
actorType: actionAuditLog.actorType,
|
actorType: actionAuditLog.actorType,
|
||||||
|
actorId: actionAuditLog.actorId,
|
||||||
timestamp: actionAuditLog.timestamp,
|
timestamp: actionAuditLog.timestamp,
|
||||||
actor: actionAuditLog.actor
|
actor: actionAuditLog.actor
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,22 +23,31 @@ export default function GeneralPage() {
|
|||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const [totalCount, setTotalCount] = useState<number>(0);
|
||||||
|
const [currentPage, setCurrentPage] = useState<number>(0);
|
||||||
|
const [pageSize, setPageSize] = useState<number>(20);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
// Set default date range to last 24 hours
|
// Set default date range to last 24 hours
|
||||||
const getDefaultDateRange = () => {
|
const getDefaultDateRange = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startDate: {
|
startDate: {
|
||||||
date: yesterday,
|
date: yesterday
|
||||||
},
|
},
|
||||||
endDate: {
|
endDate: {
|
||||||
date: now,
|
date: now
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const [dateRange, setDateRange] = useState<{ startDate: DateTimeValue; endDate: DateTimeValue }>(getDefaultDateRange());
|
const [dateRange, setDateRange] = useState<{
|
||||||
|
startDate: DateTimeValue;
|
||||||
|
endDate: DateTimeValue;
|
||||||
|
}>(getDefaultDateRange());
|
||||||
|
|
||||||
// Trigger search with default values on component mount
|
// Trigger search with default values on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -51,21 +60,42 @@ export default function GeneralPage() {
|
|||||||
endDate: DateTimeValue
|
endDate: DateTimeValue
|
||||||
) => {
|
) => {
|
||||||
setDateRange({ startDate, endDate });
|
setDateRange({ startDate, endDate });
|
||||||
queryDateTime(startDate, endDate);
|
setCurrentPage(0); // Reset to first page when filtering
|
||||||
|
queryDateTime(startDate, endDate, 0, pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle page changes
|
||||||
|
const handlePageChange = (newPage: number) => {
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
queryDateTime(
|
||||||
|
dateRange.startDate,
|
||||||
|
dateRange.endDate,
|
||||||
|
newPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle page size changes
|
||||||
|
const handlePageSizeChange = (newPageSize: number) => {
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
setCurrentPage(0); // Reset to first page when changing page size
|
||||||
|
queryDateTime(dateRange.startDate, dateRange.endDate, 0, newPageSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryDateTime = async (
|
const queryDateTime = async (
|
||||||
startDate: DateTimeValue,
|
startDate: DateTimeValue,
|
||||||
endDate: DateTimeValue
|
endDate: DateTimeValue,
|
||||||
|
page: number = currentPage,
|
||||||
|
size: number = pageSize
|
||||||
) => {
|
) => {
|
||||||
console.log("Date range changed:", { startDate, endDate });
|
console.log("Date range changed:", { startDate, endDate, page, size });
|
||||||
setIsRefreshing(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Convert the date/time values to API parameters
|
// Convert the date/time values to API parameters
|
||||||
let params: any = {
|
let params: any = {
|
||||||
limit: 20,
|
limit: size,
|
||||||
offset: 0
|
offset: page * size
|
||||||
};
|
};
|
||||||
|
|
||||||
if (startDate?.date) {
|
if (startDate?.date) {
|
||||||
@@ -89,14 +119,20 @@ export default function GeneralPage() {
|
|||||||
} else {
|
} else {
|
||||||
// If no time is specified, set to NOW
|
// If no time is specified, set to NOW
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
endDateTime.setHours(now.getHours(), now.getMinutes(), now.getSeconds(), now.getMilliseconds());
|
endDateTime.setHours(
|
||||||
|
now.getHours(),
|
||||||
|
now.getMinutes(),
|
||||||
|
now.getSeconds(),
|
||||||
|
now.getMilliseconds()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
params.timeEnd = endDateTime.toISOString();
|
params.timeEnd = endDateTime.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api.get(`/org/${orgId}/logs/action`, { params });
|
const res = await api.get(`/org/${orgId}/logs/action`, { params });
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
setRows(res.data.data.log);
|
setRows(res.data.data.log || []);
|
||||||
|
setTotalCount(res.data.data.pagination?.total || 0);
|
||||||
console.log("Fetched logs:", res.data);
|
console.log("Fetched logs:", res.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -106,17 +142,21 @@ export default function GeneralPage() {
|
|||||||
variant: "destructive"
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsRefreshing(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const refreshData = async () => {
|
const refreshData = async () => {
|
||||||
console.log("Data refreshed");
|
console.log("Data refreshed");
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
try {
|
try {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
// Refresh data with current date range and pagination
|
||||||
router.refresh();
|
await queryDateTime(
|
||||||
|
dateRange.startDate,
|
||||||
|
dateRange.endDate,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: t("error"),
|
title: t("error"),
|
||||||
@@ -139,8 +179,8 @@ export default function GeneralPage() {
|
|||||||
: undefined,
|
: undefined,
|
||||||
timeEnd: dateRange.endDate?.date
|
timeEnd: dateRange.endDate?.date
|
||||||
? new Date(dateRange.endDate.date).toISOString()
|
? new Date(dateRange.endDate.date).toISOString()
|
||||||
: undefined,
|
: undefined
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a URL for the blob and trigger a download
|
// Create a URL for the blob and trigger a download
|
||||||
@@ -148,7 +188,10 @@ export default function GeneralPage() {
|
|||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
const epoch = Math.floor(Date.now() / 1000);
|
const epoch = Math.floor(Date.now() / 1000);
|
||||||
link.setAttribute("download", `access_audit_logs_${orgId}_${epoch}.csv`);
|
link.setAttribute(
|
||||||
|
"download",
|
||||||
|
`access_audit_logs_${orgId}_${epoch}.csv`
|
||||||
|
);
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
link.parentNode?.removeChild(link);
|
link.parentNode?.removeChild(link);
|
||||||
@@ -166,21 +209,11 @@ export default function GeneralPage() {
|
|||||||
{
|
{
|
||||||
accessorKey: "timestamp",
|
accessorKey: "timestamp",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return t("timestamp");
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() =>
|
|
||||||
column.toggleSorting(column.getIsSorted() === "asc")
|
|
||||||
}
|
|
||||||
className="hidden md:flex whitespace-nowrap"
|
|
||||||
>
|
|
||||||
{t("timestamp")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<div className="whitespace-nowrap">
|
<div className="whitespace-nowrap">
|
||||||
{new Date(
|
{new Date(
|
||||||
row.original.timestamp * 1000
|
row.original.timestamp * 1000
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
@@ -191,48 +224,48 @@ export default function GeneralPage() {
|
|||||||
{
|
{
|
||||||
accessorKey: "action",
|
accessorKey: "action",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return t("action");
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() =>
|
|
||||||
column.toggleSorting(column.getIsSorted() === "asc")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("action")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
// make the value capitalized
|
// make the value capitalized
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<span className="hitespace-nowrap">
|
<span className="hitespace-nowrap">
|
||||||
{row.original.action.charAt(0).toUpperCase() + row.original.action.slice(1)}
|
{row.original.action.charAt(0).toUpperCase() +
|
||||||
|
row.original.action.slice(1)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "actor",
|
accessorKey: "actor",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return t("actor");
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() =>
|
|
||||||
column.toggleSorting(column.getIsSorted() === "asc")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("actor")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
{row.original.actorType == "user" ? <User className="h-4 w-4" /> : <Key className="h-4 w-4" />}
|
{row.original.actorType == "user" ? (
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Key className="h-4 w-4" />
|
||||||
|
)}
|
||||||
{row.original.actor}
|
{row.original.actor}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "actorId",
|
||||||
|
header: ({ column }) => {
|
||||||
|
return t("actorId");
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
{row.original.actorId}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -258,6 +291,13 @@ export default function GeneralPage() {
|
|||||||
id: "timestamp",
|
id: "timestamp",
|
||||||
desc: false
|
desc: false
|
||||||
}}
|
}}
|
||||||
|
// Server-side pagination props
|
||||||
|
totalCount={totalCount}
|
||||||
|
currentPage={currentPage}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
onPageSizeChange={handlePageSizeChange}
|
||||||
|
isLoading={isLoading}
|
||||||
|
defaultPageSize={pageSize}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ export default async function GeneralSettingsPage({
|
|||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{
|
{
|
||||||
title: t("access"),
|
title: t("action"),
|
||||||
href: `/{orgId}/settings/logs/access`
|
href: `/{orgId}/settings/logs/action`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("request"),
|
title: t("request"),
|
||||||
|
|||||||
@@ -19,11 +19,19 @@ import { useTranslations } from "next-intl";
|
|||||||
interface DataTablePaginationProps<TData> {
|
interface DataTablePaginationProps<TData> {
|
||||||
table: Table<TData>;
|
table: Table<TData>;
|
||||||
onPageSizeChange?: (pageSize: number) => void;
|
onPageSizeChange?: (pageSize: number) => void;
|
||||||
|
onPageChange?: (pageIndex: number) => void;
|
||||||
|
totalCount?: number;
|
||||||
|
isServerPagination?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTablePagination<TData>({
|
export function DataTablePagination<TData>({
|
||||||
table,
|
table,
|
||||||
onPageSizeChange
|
onPageSizeChange,
|
||||||
|
onPageChange,
|
||||||
|
totalCount,
|
||||||
|
isServerPagination = false,
|
||||||
|
isLoading = false
|
||||||
}: DataTablePaginationProps<TData>) {
|
}: DataTablePaginationProps<TData>) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -37,6 +45,51 @@ export function DataTablePagination<TData>({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePageNavigation = (action: 'first' | 'previous' | 'next' | 'last') => {
|
||||||
|
if (isServerPagination && onPageChange) {
|
||||||
|
const currentPage = table.getState().pagination.pageIndex;
|
||||||
|
const pageCount = table.getPageCount();
|
||||||
|
|
||||||
|
let newPage: number;
|
||||||
|
switch (action) {
|
||||||
|
case 'first':
|
||||||
|
newPage = 0;
|
||||||
|
break;
|
||||||
|
case 'previous':
|
||||||
|
newPage = Math.max(0, currentPage - 1);
|
||||||
|
break;
|
||||||
|
case 'next':
|
||||||
|
newPage = Math.min(pageCount - 1, currentPage + 1);
|
||||||
|
break;
|
||||||
|
case 'last':
|
||||||
|
newPage = pageCount - 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPage !== currentPage) {
|
||||||
|
onPageChange(newPage);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use table's built-in navigation for client-side pagination
|
||||||
|
switch (action) {
|
||||||
|
case 'first':
|
||||||
|
table.setPageIndex(0);
|
||||||
|
break;
|
||||||
|
case 'previous':
|
||||||
|
table.previousPage();
|
||||||
|
break;
|
||||||
|
case 'next':
|
||||||
|
table.nextPage();
|
||||||
|
break;
|
||||||
|
case 'last':
|
||||||
|
table.setPageIndex(table.getPageCount() - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between text-muted-foreground">
|
<div className="flex items-center justify-between text-muted-foreground">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
@@ -61,14 +114,21 @@ export function DataTablePagination<TData>({
|
|||||||
|
|
||||||
<div className="flex items-center space-x-3 lg:space-x-8">
|
<div className="flex items-center space-x-3 lg:space-x-8">
|
||||||
<div className="flex items-center justify-center text-sm font-medium">
|
<div className="flex items-center justify-center text-sm font-medium">
|
||||||
{t('paginator', {current: table.getState().pagination.pageIndex + 1, last: table.getPageCount()})}
|
{isServerPagination && totalCount !== undefined ? (
|
||||||
|
t('paginator', {
|
||||||
|
current: table.getState().pagination.pageIndex + 1,
|
||||||
|
last: Math.ceil(totalCount / table.getState().pagination.pageSize)
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
t('paginator', {current: table.getState().pagination.pageIndex + 1, last: table.getPageCount()})
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
onClick={() => table.setPageIndex(0)}
|
onClick={() => handlePageNavigation('first')}
|
||||||
disabled={!table.getCanPreviousPage()}
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
>
|
>
|
||||||
<span className="sr-only">{t('paginatorToFirst')}</span>
|
<span className="sr-only">{t('paginatorToFirst')}</span>
|
||||||
<DoubleArrowLeftIcon className="h-4 w-4" />
|
<DoubleArrowLeftIcon className="h-4 w-4" />
|
||||||
@@ -76,8 +136,8 @@ export function DataTablePagination<TData>({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
onClick={() => table.previousPage()}
|
onClick={() => handlePageNavigation('previous')}
|
||||||
disabled={!table.getCanPreviousPage()}
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
>
|
>
|
||||||
<span className="sr-only">{t('paginatorToPrevious')}</span>
|
<span className="sr-only">{t('paginatorToPrevious')}</span>
|
||||||
<ChevronLeftIcon className="h-4 w-4" />
|
<ChevronLeftIcon className="h-4 w-4" />
|
||||||
@@ -85,8 +145,8 @@ export function DataTablePagination<TData>({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
onClick={() => table.nextPage()}
|
onClick={() => handlePageNavigation('next')}
|
||||||
disabled={!table.getCanNextPage()}
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
>
|
>
|
||||||
<span className="sr-only">{t('paginatorToNext')}</span>
|
<span className="sr-only">{t('paginatorToNext')}</span>
|
||||||
<ChevronRightIcon className="h-4 w-4" />
|
<ChevronRightIcon className="h-4 w-4" />
|
||||||
@@ -94,10 +154,8 @@ export function DataTablePagination<TData>({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
onClick={() =>
|
onClick={() => handlePageNavigation('last')}
|
||||||
table.setPageIndex(table.getPageCount() - 1)
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
>
|
||||||
<span className="sr-only">{t('paginatorToLast')}</span>
|
<span className="sr-only">{t('paginatorToLast')}</span>
|
||||||
<DoubleArrowRightIcon className="h-4 w-4" />
|
<DoubleArrowRightIcon className="h-4 w-4" />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { ChevronDownIcon, CalendarIcon } from "lucide-react";
|
import { ChevronDownIcon, CalendarIcon } from "lucide-react";
|
||||||
|
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import { Calendar } from "@app/components/ui/calendar";
|
||||||
import { Input } from "@app/components/ui/input";
|
import { Input } from "@app/components/ui/input";
|
||||||
import { Label } from "@app/components/ui/label";
|
import { Label } from "@app/components/ui/label";
|
||||||
import {
|
import {
|
||||||
@@ -60,14 +61,20 @@ export function DateTimePicker({
|
|||||||
onChange?.(newValue);
|
onChange?.(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDisplayText = () => {
|
const getDisplayText = () => {
|
||||||
if (!internalDate) return placeholder;
|
if (!internalDate) return placeholder;
|
||||||
|
|
||||||
const dateStr = internalDate.toLocaleDateString();
|
const dateStr = internalDate.toLocaleDateString();
|
||||||
if (!showTime || !internalTime) return dateStr;
|
if (!showTime || !internalTime) return dateStr;
|
||||||
|
|
||||||
return `${dateStr} ${internalTime}`;
|
// Parse time and format in local timezone
|
||||||
};
|
const [hours, minutes, seconds] = internalTime.split(':');
|
||||||
|
const timeDate = new Date();
|
||||||
|
timeDate.setHours(parseInt(hours, 10), parseInt(minutes, 10), parseInt(seconds || '0', 10));
|
||||||
|
const timeStr = timeDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
|
||||||
|
return `${dateStr} ${timeStr}`;
|
||||||
|
};
|
||||||
|
|
||||||
const hasValue = internalDate || (showTime && internalTime);
|
const hasValue = internalDate || (showTime && internalTime);
|
||||||
|
|
||||||
@@ -93,36 +100,26 @@ export function DateTimePicker({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getDisplayText()}
|
{getDisplayText()}
|
||||||
<CalendarIcon className="h-4 w-4" />
|
<ChevronDownIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||||
<div className="p-3">
|
{showTime ? (
|
||||||
<div className="space-y-3">
|
<div className="flex">
|
||||||
<div>
|
<Calendar
|
||||||
<Label htmlFor="date-input" className="text-sm font-medium">
|
mode="single"
|
||||||
Date
|
selected={internalDate}
|
||||||
</Label>
|
captionLayout="dropdown"
|
||||||
<Input
|
onSelect={(date) => {
|
||||||
id="date-input"
|
handleDateChange(date);
|
||||||
type="date"
|
if (!showTime) {
|
||||||
value={internalDate ?
|
setOpen(false);
|
||||||
`${internalDate.getFullYear()}-${String(internalDate.getMonth() + 1).padStart(2, '0')}-${String(internalDate.getDate()).padStart(2, '0')}`
|
}
|
||||||
: ''}
|
}}
|
||||||
onChange={(e) => {
|
className="flex-grow w-[250px]"
|
||||||
let dateValue = undefined;
|
/>
|
||||||
if (e.target.value) {
|
<div className="p-3 border-l">
|
||||||
// Create date in local timezone to avoid offset issues
|
<div className="flex flex-col gap-3">
|
||||||
const parts = e.target.value.split('-');
|
|
||||||
dateValue = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
|
|
||||||
}
|
|
||||||
handleDateChange(dateValue);
|
|
||||||
}}
|
|
||||||
className="mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{showTime && (
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="time-input" className="text-sm font-medium">
|
<Label htmlFor="time-input" className="text-sm font-medium">
|
||||||
Time
|
Time
|
||||||
</Label>
|
</Label>
|
||||||
@@ -132,12 +129,22 @@ export function DateTimePicker({
|
|||||||
step="1"
|
step="1"
|
||||||
value={internalTime}
|
value={internalTime}
|
||||||
onChange={handleTimeChange}
|
onChange={handleTimeChange}
|
||||||
className="mt-1 bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={internalDate}
|
||||||
|
captionLayout="dropdown"
|
||||||
|
onSelect={(date) => {
|
||||||
|
handleDateChange(date);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ type DataTableProps<TData, TValue> = {
|
|||||||
start: DateTimeValue;
|
start: DateTimeValue;
|
||||||
end: DateTimeValue;
|
end: DateTimeValue;
|
||||||
};
|
};
|
||||||
|
// Server-side pagination props
|
||||||
|
totalCount?: number;
|
||||||
|
currentPage?: number;
|
||||||
|
onPageChange?: (page: number) => void;
|
||||||
|
onPageSizeChange?: (pageSize: number) => void;
|
||||||
|
isLoading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LogDataTable<TData, TValue>({
|
export function LogDataTable<TData, TValue>({
|
||||||
@@ -121,7 +127,12 @@ export function LogDataTable<TData, TValue>({
|
|||||||
persistPageSize = false,
|
persistPageSize = false,
|
||||||
defaultPageSize = 20,
|
defaultPageSize = 20,
|
||||||
onDateRangeChange,
|
onDateRangeChange,
|
||||||
dateRange
|
dateRange,
|
||||||
|
totalCount,
|
||||||
|
currentPage = 0,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange: onPageSizeChangeProp,
|
||||||
|
isLoading = false
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -175,26 +186,39 @@ export function LogDataTable<TData, TValue>({
|
|||||||
return data.filter(activeTabFilter.filterFn);
|
return data.filter(activeTabFilter.filterFn);
|
||||||
}, [data, tabs, activeTab]);
|
}, [data, tabs, activeTab]);
|
||||||
|
|
||||||
|
// Determine if using server-side pagination
|
||||||
|
const isServerPagination = totalCount !== undefined;
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: filteredData,
|
data: filteredData,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
// Only use client-side pagination if totalCount is not provided
|
||||||
|
...(isServerPagination ? {} : { getPaginationRowModel: getPaginationRowModel() }),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
onGlobalFilterChange: setGlobalFilter,
|
onGlobalFilterChange: setGlobalFilter,
|
||||||
|
// Configure pagination state
|
||||||
|
...(isServerPagination ? {
|
||||||
|
manualPagination: true,
|
||||||
|
pageCount: totalCount ? Math.ceil(totalCount / pageSize) : 0,
|
||||||
|
} : {}),
|
||||||
initialState: {
|
initialState: {
|
||||||
pagination: {
|
pagination: {
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
pageIndex: 0
|
pageIndex: currentPage
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
globalFilter
|
globalFilter,
|
||||||
|
pagination: {
|
||||||
|
pageSize: pageSize,
|
||||||
|
pageIndex: currentPage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,6 +234,16 @@ export function LogDataTable<TData, TValue>({
|
|||||||
}
|
}
|
||||||
}, [pageSize, table, persistPageSize, tableId]);
|
}, [pageSize, table, persistPageSize, tableId]);
|
||||||
|
|
||||||
|
// Update table page index when currentPage prop changes (server pagination)
|
||||||
|
useEffect(() => {
|
||||||
|
if (isServerPagination) {
|
||||||
|
const currentPageIndex = table.getState().pagination.pageIndex;
|
||||||
|
if (currentPageIndex !== currentPage) {
|
||||||
|
table.setPageIndex(currentPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [currentPage, table, isServerPagination]);
|
||||||
|
|
||||||
const handleTabChange = (value: string) => {
|
const handleTabChange = (value: string) => {
|
||||||
setActiveTab(value);
|
setActiveTab(value);
|
||||||
// Reset to first page when changing tabs
|
// Reset to first page when changing tabs
|
||||||
@@ -225,6 +259,18 @@ export function LogDataTable<TData, TValue>({
|
|||||||
if (persistPageSize) {
|
if (persistPageSize) {
|
||||||
setStoredPageSize(newPageSize, tableId);
|
setStoredPageSize(newPageSize, tableId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For server pagination, notify parent component
|
||||||
|
if (isServerPagination && onPageSizeChangeProp) {
|
||||||
|
onPageSizeChangeProp(newPageSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle page changes for server pagination
|
||||||
|
const handlePageChange = (newPageIndex: number) => {
|
||||||
|
if (isServerPagination && onPageChange) {
|
||||||
|
onPageChange(newPageIndex);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateRangeChange = (
|
const handleDateRangeChange = (
|
||||||
@@ -276,7 +322,6 @@ export function LogDataTable<TData, TValue>({
|
|||||||
)}
|
)}
|
||||||
{onExport && (
|
{onExport && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
|
||||||
onClick={onExport}
|
onClick={onExport}
|
||||||
disabled={isExporting}
|
disabled={isExporting}
|
||||||
>
|
>
|
||||||
@@ -342,6 +387,10 @@ export function LogDataTable<TData, TValue>({
|
|||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
table={table}
|
table={table}
|
||||||
onPageSizeChange={handlePageSizeChange}
|
onPageSizeChange={handlePageSizeChange}
|
||||||
|
onPageChange={isServerPagination ? handlePageChange : undefined}
|
||||||
|
totalCount={totalCount}
|
||||||
|
isServerPagination={isServerPagination}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
213
src/components/ui/calendar.tsx
Normal file
213
src/components/ui/calendar.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
} from "lucide-react"
|
||||||
|
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
||||||
|
|
||||||
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
|
import { cn } from "@app/lib/cn"
|
||||||
|
|
||||||
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
captionLayout = "label",
|
||||||
|
buttonVariant = "ghost",
|
||||||
|
formatters,
|
||||||
|
components,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
|
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
||||||
|
}) {
|
||||||
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DayPicker
|
||||||
|
showOutsideDays={showOutsideDays}
|
||||||
|
className={cn(
|
||||||
|
"bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||||
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
captionLayout={captionLayout}
|
||||||
|
formatters={{
|
||||||
|
formatMonthDropdown: (date) =>
|
||||||
|
date.toLocaleString("default", { month: "short" }),
|
||||||
|
...formatters,
|
||||||
|
}}
|
||||||
|
classNames={{
|
||||||
|
root: cn("w-fit", defaultClassNames.root),
|
||||||
|
months: cn(
|
||||||
|
"relative flex flex-col gap-4 md:flex-row",
|
||||||
|
defaultClassNames.months
|
||||||
|
),
|
||||||
|
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
||||||
|
nav: cn(
|
||||||
|
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
||||||
|
defaultClassNames.nav
|
||||||
|
),
|
||||||
|
button_previous: cn(
|
||||||
|
buttonVariants({ variant: buttonVariant }),
|
||||||
|
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
||||||
|
defaultClassNames.button_previous
|
||||||
|
),
|
||||||
|
button_next: cn(
|
||||||
|
buttonVariants({ variant: buttonVariant }),
|
||||||
|
"h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
|
||||||
|
defaultClassNames.button_next
|
||||||
|
),
|
||||||
|
month_caption: cn(
|
||||||
|
"flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
|
||||||
|
defaultClassNames.month_caption
|
||||||
|
),
|
||||||
|
dropdowns: cn(
|
||||||
|
"flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
|
||||||
|
defaultClassNames.dropdowns
|
||||||
|
),
|
||||||
|
dropdown_root: cn(
|
||||||
|
"has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
|
||||||
|
defaultClassNames.dropdown_root
|
||||||
|
),
|
||||||
|
dropdown: cn(
|
||||||
|
"bg-popover absolute inset-0 opacity-0",
|
||||||
|
defaultClassNames.dropdown
|
||||||
|
),
|
||||||
|
caption_label: cn(
|
||||||
|
"select-none font-medium",
|
||||||
|
captionLayout === "label"
|
||||||
|
? "text-sm"
|
||||||
|
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
||||||
|
defaultClassNames.caption_label
|
||||||
|
),
|
||||||
|
table: "w-full border-collapse",
|
||||||
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
|
weekday: cn(
|
||||||
|
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
||||||
|
defaultClassNames.weekday
|
||||||
|
),
|
||||||
|
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||||
|
week_number_header: cn(
|
||||||
|
"w-[--cell-size] select-none",
|
||||||
|
defaultClassNames.week_number_header
|
||||||
|
),
|
||||||
|
week_number: cn(
|
||||||
|
"text-muted-foreground select-none text-[0.8rem]",
|
||||||
|
defaultClassNames.week_number
|
||||||
|
),
|
||||||
|
day: cn(
|
||||||
|
"group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
|
||||||
|
defaultClassNames.day
|
||||||
|
),
|
||||||
|
range_start: cn(
|
||||||
|
"bg-accent rounded-l-md",
|
||||||
|
defaultClassNames.range_start
|
||||||
|
),
|
||||||
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||||
|
range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
|
||||||
|
today: cn(
|
||||||
|
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
|
||||||
|
defaultClassNames.today
|
||||||
|
),
|
||||||
|
outside: cn(
|
||||||
|
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||||
|
defaultClassNames.outside
|
||||||
|
),
|
||||||
|
disabled: cn(
|
||||||
|
"text-muted-foreground opacity-50",
|
||||||
|
defaultClassNames.disabled
|
||||||
|
),
|
||||||
|
hidden: cn("invisible", defaultClassNames.hidden),
|
||||||
|
...classNames,
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="calendar"
|
||||||
|
ref={rootRef}
|
||||||
|
className={cn(className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
|
if (orientation === "left") {
|
||||||
|
return (
|
||||||
|
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation === "right") {
|
||||||
|
return (
|
||||||
|
<ChevronRightIcon
|
||||||
|
className={cn("size-4", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
DayButton: CalendarDayButton,
|
||||||
|
WeekNumber: ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<td {...props}>
|
||||||
|
<div className="flex size-[--cell-size] items-center justify-center text-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
...components,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CalendarDayButton({
|
||||||
|
className,
|
||||||
|
day,
|
||||||
|
modifiers,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayButton>) {
|
||||||
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
|
const ref = React.useRef<HTMLButtonElement>(null)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (modifiers.focused) ref.current?.focus()
|
||||||
|
}, [modifiers.focused])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
data-day={day.date.toLocaleDateString()}
|
||||||
|
data-selected-single={
|
||||||
|
modifiers.selected &&
|
||||||
|
!modifiers.range_start &&
|
||||||
|
!modifiers.range_end &&
|
||||||
|
!modifiers.range_middle
|
||||||
|
}
|
||||||
|
data-range-start={modifiers.range_start}
|
||||||
|
data-range-end={modifiers.range_end}
|
||||||
|
data-range-middle={modifiers.range_middle}
|
||||||
|
className={cn(
|
||||||
|
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
|
||||||
|
defaultClassNames.day,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Calendar, CalendarDayButton }
|
||||||
Reference in New Issue
Block a user