diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..9d6d63f
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,3 @@
+#VITE_API_BASE_URL=https://admin-api.eventhub.local
+VITE_WS_URL=wss://admin-ws.eventhub.local
+VITE_APP_TITLE=EventHub Admin
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9145c03..a547bf3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,24 @@
-node_modules/
-dist/
-.env
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7dbf7eb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,73 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..ef614d2
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,22 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d28dcbf
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ eventhubfrontadmin
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..2d649a4
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,4631 @@
+{
+ "name": "eventhubfrontadmin",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "eventhubfrontadmin",
+ "version": "0.0.0",
+ "dependencies": {
+ "@ant-design/icons": "^6.2.3",
+ "@hookform/resolvers": "^5.2.2",
+ "@tanstack/react-query": "^5.100.10",
+ "antd": "^6.4.3",
+ "axios": "^1.16.1",
+ "dayjs": "^1.11.20",
+ "i18next": "^26.2.0",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
+ "react-hook-form": "^7.76.0",
+ "react-i18next": "^17.0.8",
+ "react-router-dom": "^7.15.1",
+ "recharts": "^3.8.1",
+ "zod": "^4.4.3",
+ "zustand": "^5.0.13"
+ },
+ "devDependencies": {
+ "@eslint/js": "^10.0.1",
+ "@types/node": "^24.12.4",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "eslint": "^10.3.0",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.6.0",
+ "typescript": "~6.0.2",
+ "typescript-eslint": "^8.59.2",
+ "vite": "^8.0.12"
+ }
+ },
+ "node_modules/@ant-design/colors": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz",
+ "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^3.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz",
+ "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs-utils": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz",
+ "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/cssinjs": "^2.1.2",
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/util": "^1.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/@ant-design/fast-color": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz",
+ "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@ant-design/icons": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.2.3.tgz",
+ "integrity": "sha512-Pl3aoAtxQeKryYnt6VvDJtOxMOtA8wrRSACe/pTjOAIG3fdHrWm6Ivb4ku9tsFjYroSXBKirvuxG4QkwBXD9gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^8.0.1",
+ "@ant-design/icons-svg": "^4.4.2",
+ "@rc-component/util": "^1.10.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/icons-svg": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
+ "license": "MIT"
+ },
+ "node_modules/@ant-design/react-slick": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz",
+ "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.4",
+ "clsx": "^2.1.1",
+ "json2mq": "^0.2.0",
+ "throttle-debounce": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
+ "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.5",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
+ "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
+ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "eslint": "^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
+ "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@hookform/resolvers": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz",
+ "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/utils": "^0.3.0"
+ },
+ "peerDependencies": {
+ "react-hook-form": "^7.55.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.130.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
+ "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rc-component/async-validator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz",
+ "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.4"
+ },
+ "engines": {
+ "node": ">=14.x"
+ }
+ },
+ "node_modules/@rc-component/cascader": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.15.0.tgz",
+ "integrity": "sha512-ZzpMtwFCRo3fbXHuDnncARJMZQjdqA2w7aDuPofNQt+aDx39st1hgfIpEwTBLhe2Hqsvs/zOr8RTtgxTkCPySw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/select": "~1.6.0",
+ "@rc-component/tree": "~1.3.0",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/checkbox": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz",
+ "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/collapse": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz",
+ "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/color-picker": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz",
+ "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/fast-color": "^3.0.1",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/context": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz",
+ "integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/dialog": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.9.0.tgz",
+ "integrity": "sha512-zbAAogkg4kkKum79sLE6M+vq1jSAW25zdkafrahgcTP9t9S//SD634Znd1A4c8F2Gc12ZKnehGLsVaaOvZzD2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.3",
+ "@rc-component/portal": "^2.1.0",
+ "@rc-component/util": "^1.9.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/drawer": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz",
+ "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/portal": "^2.1.3",
+ "@rc-component/util": "^1.9.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/dropdown": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz",
+ "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.2.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.11.0",
+ "react-dom": ">=16.11.0"
+ }
+ },
+ "node_modules/@rc-component/form": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.1.tgz",
+ "integrity": "sha512-8O7TB55Fi2mWIGvSnwZjk8jFqVNYyKDAswglwGShcbndxqzKz4cHwNtNaLjZlAeRge9wcB0LL8IWsC/Bl18raQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/async-validator": "^5.1.0",
+ "@rc-component/util": "^1.6.2",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/image": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.9.0.tgz",
+ "integrity": "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.0.0",
+ "@rc-component/portal": "^2.1.2",
+ "@rc-component/util": "^1.10.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/input": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.3.0.tgz",
+ "integrity": "sha512-IUUNOdAuWuEvDEFFgfmwQl818tiDbvXwLgon4HL1q2hJeYkqrRrYwYhJN0zfPHGTDxs3gvyVC/C02D4hWFoIcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/resize-observer": "^1.1.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@rc-component/input-number": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz",
+ "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/mini-decimal": "^1.0.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mentions": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.9.0.tgz",
+ "integrity": "sha512-WUwfFKDSOF5S9UPsNsXcLYtzjTxBGsftTXWRbZuxX6BYrsySISTnujfJNgaaQ6qVzaCDJ35QUkZKvsYxip1C5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/input": "~1.3.0",
+ "@rc-component/menu": "~1.3.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/menu": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.3.0.tgz",
+ "integrity": "sha512-u3NfiwpiEgT177qa5Yxm5QsI8i/93EBGpWj8HYZQDnh2pCZ2xtQCe/+w3pSR2NlwKOZDTCKzEhEyD09mGphssA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/overflow": "^1.0.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mini-decimal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz",
+ "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@rc-component/motion": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.2.tgz",
+ "integrity": "sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mutate-observer": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz",
+ "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/notification": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.7.tgz",
+ "integrity": "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.11.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/overflow": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz",
+ "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@rc-component/resize-observer": "^1.0.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/pagination": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz",
+ "integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/picker": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.10.0.tgz",
+ "integrity": "sha512-vVOXP2RVWozwpERGUFAehVH1Jz6o/uRrAb9qSZm1LC+iJs8rvEwFo1bzz2jlOYV+uWwu0dIuG86tnDui14Ea0w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/overflow": "^1.0.0",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/trigger": "^3.6.15",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12.x"
+ },
+ "peerDependencies": {
+ "date-fns": ">= 2.x",
+ "dayjs": ">= 1.x",
+ "luxon": ">= 3.x",
+ "moment": ">= 2.x",
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ },
+ "peerDependenciesMeta": {
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rc-component/portal": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.0.tgz",
+ "integrity": "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=12.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/progress": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz",
+ "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.1",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/qrcode": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz",
+ "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/rate": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz",
+ "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/resize-observer": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz",
+ "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/segmented": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz",
+ "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@rc-component/select": {
+ "version": "1.6.15",
+ "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.6.15.tgz",
+ "integrity": "sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/overflow": "^1.0.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.3.0",
+ "@rc-component/virtual-list": "^1.0.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/@rc-component/slider": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz",
+ "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/steps": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz",
+ "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.2.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/switch": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz",
+ "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/table": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.0.tgz",
+ "integrity": "sha512-SjtpcCf+rL7dDc62GKT3rXTdERjVuJvRiqjpU7g0Jc/ewCifXynHc7Nm3Em1XsD+WhGrgQtxNDScI/0+Lpfr0w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/context": "^2.0.1",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/util": "^1.1.0",
+ "@rc-component/virtual-list": "^1.0.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/tabs": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.9.0.tgz",
+ "integrity": "sha512-tn1slmbbaTyt8mgwyWJcT8jo/qNiYUs6u1H7OgGQt9faYO06BJIkU5cTmMqORzIrNmSEeeUY6pD5i+JlqSHYhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/dropdown": "~1.0.0",
+ "@rc-component/menu": "~1.3.0",
+ "@rc-component/motion": "^1.1.3",
+ "@rc-component/resize-observer": "^1.0.0",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tooltip": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz",
+ "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/trigger": "^3.7.1",
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/tour": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz",
+ "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/portal": "^2.2.0",
+ "@rc-component/trigger": "^3.0.0",
+ "@rc-component/util": "^1.7.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tree": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.3.1.tgz",
+ "integrity": "sha512-zlL0PW0bTFlveTtLcA01VD/yMWKK73EywItFMgIZUY5sb6tMOAw7zV6qGzqldufqrV93ZWQB4H3NBNoTMCueJA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.0.0",
+ "@rc-component/util": "^1.8.1",
+ "@rc-component/virtual-list": "^1.0.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=10.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/@rc-component/tree-select": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.9.0.tgz",
+ "integrity": "sha512-GXcFe15a+trUl1/J3OHWQhsVWFpwFpGFK2cqYWZ1sK22Zs3KZTvMwDpzr75PIo1s6QVioVxpE/pRwRopkeDQ6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/select": "~1.6.0",
+ "@rc-component/tree": "~1.3.0",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/@rc-component/trigger": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.0.tgz",
+ "integrity": "sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/motion": "^1.1.4",
+ "@rc-component/portal": "^2.2.0",
+ "@rc-component/resize-observer": "^1.1.1",
+ "@rc-component/util": "^1.2.1",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/upload": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz",
+ "integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@rc-component/util": "^1.3.0",
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/util": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz",
+ "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-mobile": "^5.0.0",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rc-component/virtual-list": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.1.0.tgz",
+ "integrity": "sha512-fSzVg8xFDCK+xKJJ9lljBpaGDmur4REZwXHJjsNFRi4UbfOXuH/FSbGC2oliCE5krFqM3lSZqB4Ly4vc3xQHOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.0",
+ "@rc-component/resize-observer": "^1.0.1",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz",
+ "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^11.0.0",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@reduxjs/toolkit/node_modules/immer": {
+ "version": "11.1.8",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz",
+ "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
+ "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
+ "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
+ "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
+ "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
+ "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
+ "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
+ "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
+ "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
+ "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
+ "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
+ "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
+ "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
+ "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
+ "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
+ "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.100.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.10.tgz",
+ "integrity": "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.100.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.10.tgz",
+ "integrity": "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.100.10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.12.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz",
+ "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz",
+ "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/type-utils": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.59.4",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz",
+ "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz",
+ "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.59.4",
+ "@typescript-eslint/types": "^8.59.4",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz",
+ "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz",
+ "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz",
+ "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz",
+ "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz",
+ "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.59.4",
+ "@typescript-eslint/tsconfig-utils": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/visitor-keys": "8.59.4",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
+ "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz",
+ "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.59.4",
+ "@typescript-eslint/types": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz",
+ "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.59.4",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
+ "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/antd": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-6.4.3.tgz",
+ "integrity": "sha512-6H2avkxCGfxcF67r3J2mwm9Ck50el1pks/73vfM1wDsPL/tPtj5vHuauMgJFnrqmq7CH3g8aoZ0VBQbt+jpAsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/colors": "^8.0.1",
+ "@ant-design/cssinjs": "^2.1.2",
+ "@ant-design/cssinjs-utils": "^2.1.2",
+ "@ant-design/fast-color": "^3.0.1",
+ "@ant-design/icons": "^6.2.3",
+ "@ant-design/react-slick": "~2.0.0",
+ "@babel/runtime": "^7.29.2",
+ "@rc-component/cascader": "~1.15.0",
+ "@rc-component/checkbox": "~2.0.0",
+ "@rc-component/collapse": "~1.2.0",
+ "@rc-component/color-picker": "~3.1.1",
+ "@rc-component/dialog": "~1.9.0",
+ "@rc-component/drawer": "~1.4.2",
+ "@rc-component/dropdown": "~1.0.2",
+ "@rc-component/form": "~1.8.1",
+ "@rc-component/image": "~1.9.0",
+ "@rc-component/input": "~1.3.0",
+ "@rc-component/input-number": "~1.6.2",
+ "@rc-component/mentions": "~1.9.0",
+ "@rc-component/menu": "~1.3.0",
+ "@rc-component/motion": "^1.3.2",
+ "@rc-component/mutate-observer": "^2.0.1",
+ "@rc-component/notification": "~2.0.7",
+ "@rc-component/pagination": "~1.2.0",
+ "@rc-component/picker": "~1.10.0",
+ "@rc-component/progress": "~1.0.2",
+ "@rc-component/qrcode": "~1.1.1",
+ "@rc-component/rate": "~1.0.1",
+ "@rc-component/resize-observer": "^1.1.2",
+ "@rc-component/segmented": "~1.3.0",
+ "@rc-component/select": "~1.6.15",
+ "@rc-component/slider": "~1.0.1",
+ "@rc-component/steps": "~1.2.2",
+ "@rc-component/switch": "~1.0.3",
+ "@rc-component/table": "~1.10.0",
+ "@rc-component/tabs": "~1.9.0",
+ "@rc-component/tooltip": "~1.4.0",
+ "@rc-component/tour": "~2.4.0",
+ "@rc-component/tree": "~1.3.1",
+ "@rc-component/tree-select": "~1.9.0",
+ "@rc-component/trigger": "^3.9.0",
+ "@rc-component/upload": "~1.1.0",
+ "@rc-component/util": "^1.11.0",
+ "clsx": "^2.1.1",
+ "dayjs": "^1.11.11",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "throttle-debounce": "^5.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ant-design"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
+ "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
+ "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.357",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.357.tgz",
+ "integrity": "sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-toolkit": {
+ "version": "1.46.1",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz",
+ "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==",
+ "license": "MIT",
+ "workspaces": [
+ "docs",
+ "benchmarks"
+ ]
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.0.tgz",
+ "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.6.0",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
+ "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^9 || ^10"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "17.6.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz",
+ "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "26.2.0",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.2.0.tgz",
+ "integrity": "sha512-zwBHldHdTmwN7r6UNc7lC6GWNN+YYg3DrRSeHR5PRRBf5QnJZcYHrQc0uaU26qZeYxR7iFZD+Y315dPnKP47wA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://www.locize.com/i18next"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.locize.com"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immer": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
+ "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-mobile": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
+ "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.44",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
+ "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
+ "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.6"
+ }
+ },
+ "node_modules/react-hook-form": {
+ "version": "7.76.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.76.0.tgz",
+ "integrity": "sha512-eKtLGgFeSgkHqQD8J59AMZ9a4uD1D83iSIzt4YlTGD7liDen5rrjcUO1rVIGd9yC1gofryjtHbv+4ny4hkLWlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-i18next": {
+ "version": "17.0.8",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz",
+ "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.29.2",
+ "html-parse-stringify": "^3.0.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "i18next": ">= 26.2.0",
+ "react": ">= 16.8.0",
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/react-redux": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz",
+ "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.1.tgz",
+ "integrity": "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.15.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.15.1.tgz",
+ "integrity": "sha512-AzF62gjY6U9rkMq4RfP/r2EVtQ7DMfNMjyOp/flLTCrtRylLiK4wT4pSq6O8rOXZ2eXdZYJPEYe+ifomiv+Igg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.15.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz",
+ "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==",
+ "license": "MIT",
+ "workspaces": [
+ "www"
+ ],
+ "dependencies": {
+ "@reduxjs/toolkit": "^1.9.0 || 2.x.x",
+ "clsx": "^2.1.1",
+ "decimal.js-light": "^2.5.1",
+ "es-toolkit": "^1.39.3",
+ "eventemitter3": "^5.0.1",
+ "immer": "^10.1.1",
+ "react-redux": "8.x.x || 9.x.x",
+ "reselect": "5.1.1",
+ "tiny-invariant": "^1.3.3",
+ "use-sync-external-store": "^1.2.2",
+ "victory-vendor": "^37.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
+ "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.130.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.1",
+ "@rolldown/binding-darwin-arm64": "1.0.1",
+ "@rolldown/binding-darwin-x64": "1.0.1",
+ "@rolldown/binding-freebsd-x64": "1.0.1",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.1",
+ "@rolldown/binding-linux-arm64-musl": "1.0.1",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.1",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.1",
+ "@rolldown/binding-linux-x64-gnu": "1.0.1",
+ "@rolldown/binding-linux-x64-musl": "1.0.1",
+ "@rolldown/binding-openharmony-arm64": "1.0.1",
+ "@rolldown/binding-wasm32-wasi": "1.0.1",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.1",
+ "@rolldown/binding-win32-x64-msvc": "1.0.1"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
+ "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "compute-scroll-into-view": "^3.0.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
+ "license": "MIT"
+ },
+ "node_modules/stylis": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
+ "license": "MIT"
+ },
+ "node_modules/throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.59.4",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz",
+ "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.59.4",
+ "@typescript-eslint/parser": "8.59.4",
+ "@typescript-eslint/typescript-estree": "8.59.4",
+ "@typescript-eslint/utils": "8.59.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "37.3.6",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+ "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.13",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
+ "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.1",
+ "tinyglobby": "^0.2.16"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.13",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz",
+ "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e059ac1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "eventhubfrontadmin",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@ant-design/icons": "^6.2.3",
+ "@hookform/resolvers": "^5.2.2",
+ "@tanstack/react-query": "^5.100.10",
+ "antd": "^6.4.3",
+ "axios": "^1.16.1",
+ "dayjs": "^1.11.20",
+ "i18next": "^26.2.0",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
+ "react-hook-form": "^7.76.0",
+ "react-i18next": "^17.0.8",
+ "react-router-dom": "^7.15.1",
+ "recharts": "^3.8.1",
+ "zod": "^4.4.3",
+ "zustand": "^5.0.13"
+ },
+ "devDependencies": {
+ "@eslint/js": "^10.0.1",
+ "@types/node": "^24.12.4",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "eslint": "^10.3.0",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.6.0",
+ "typescript": "~6.0.2",
+ "typescript-eslint": "^8.59.2",
+ "vite": "^8.0.12"
+ }
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..6893eb1
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons.svg b/public/icons.svg
new file mode 100644
index 0000000..e952219
--- /dev/null
+++ b/public/icons.svg
@@ -0,0 +1,24 @@
+
diff --git a/request.sh b/request.sh
new file mode 100644
index 0000000..ae80d84
--- /dev/null
+++ b/request.sh
@@ -0,0 +1,17 @@
+#curl -v -k 'https://admin-api.eventhub.local/api/v1/admin/subscriptions?limit=20&offset=1' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,hu;q=0.6' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhZG1pbiIsImV4cCI6MTc3OTI3MzU1OCwiaWF0IjoxNzc5MTg3MTU4LCJyb2xlIjoic3VwZXJhZG1pbiIsInVzZXJfaWQiOiJKMlgzalBjRTFpYU9CSkgxbFRhUUJBIn0.tHgwrUItF790D0gE7NoSRQiGbgl80lvF3I0g3DOpbTw' -H 'Cache-Control: no-cache' -H 'Connection: keep-alive' -b 'grafana_session=7dd9380cd5241089924cf811d4df38b4; grafana_session_expiry=1779187380' -H 'Pragma: no-cache' -H 'Referer: http://localhost:5173/subscriptions' -H 'Sec-Fetch-Dest: empty' -H 'Sec-Fetch-Mode: cors' -H 'Sec-Fetch-Site: same-origin' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36' -H 'sec-ch-ua: "Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99"' -H 'sec-ch-ua-mobile: ?0' -H 'sec-ch-ua-platform: "Windows"'
+curl -v -k 'https://admin-api.eventhub.local/v1/admin/events?limit=20&offset=0&sort=id&order=asc' \
+ -H 'Accept: application/json, text/plain, */*' \
+ -H 'Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,hu;q=0.6' \
+ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhZG1pbiIsImV4cCI6MTc3OTYzMDI3NSwiaWF0IjoxNzc5NTQzODc1LCJyb2xlIjoic3VwZXJhZG1pbiIsInVzZXJfaWQiOiJPRE05TjFFY1JWWmlJUGhSbVRMdjZ3In0.yAGwX5XJNC68WALUjDkhpoPPOugN1lEdGNyPDmbUleU' \
+ -H 'Cache-Control: no-cache' \
+ -H 'Connection: keep-alive' \
+ -b 'Idea-23b6b0c0=2ed73421-e1d7-45f6-8927-b1acac9a7d3e; grafana_session=c877553020e32ff47a3ce4b19fcae7e8; grafana_session_expiry=1779542493' \
+ -H 'Pragma: no-cache' \
+ -H 'Referer: http://localhost:5173/events' \
+ -H 'Sec-Fetch-Dest: empty' \
+ -H 'Sec-Fetch-Mode: cors' \
+ -H 'Sec-Fetch-Site: same-origin' \
+ -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36' \
+ -H 'sec-ch-ua: "Chromium";v="148", "Google Chrome";v="148", "Not/A)Brand";v="99"' \
+ -H 'sec-ch-ua-mobile: ?0' \
+ -H 'sec-ch-ua-platform: "Windows"'
\ No newline at end of file
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000..f90339d
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,184 @@
+.counter {
+ font-size: 16px;
+ padding: 5px 10px;
+ border-radius: 5px;
+ color: var(--accent);
+ background: var(--accent-bg);
+ border: 2px solid transparent;
+ transition: border-color 0.3s;
+ margin-bottom: 24px;
+
+ &:hover {
+ border-color: var(--accent-border);
+ }
+ &:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ }
+}
+
+.hero {
+ position: relative;
+
+ .base,
+ .framework,
+ .vite {
+ inset-inline: 0;
+ margin: 0 auto;
+ }
+
+ .base {
+ width: 170px;
+ position: relative;
+ z-index: 0;
+ }
+
+ .framework,
+ .vite {
+ position: absolute;
+ }
+
+ .framework {
+ z-index: 1;
+ top: 34px;
+ height: 28px;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
+ scale(1.4);
+ }
+
+ .vite {
+ z-index: 0;
+ top: 107px;
+ height: 26px;
+ width: auto;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
+ scale(0.8);
+ }
+}
+
+#center {
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ place-content: center;
+ place-items: center;
+ flex-grow: 1;
+
+ @media (max-width: 1024px) {
+ padding: 32px 20px 24px;
+ gap: 18px;
+ }
+}
+
+#next-steps {
+ display: flex;
+ border-top: 1px solid var(--border);
+ text-align: left;
+
+ & > div {
+ flex: 1 1 0;
+ padding: 32px;
+ @media (max-width: 1024px) {
+ padding: 24px 20px;
+ }
+ }
+
+ .icon {
+ margin-bottom: 16px;
+ width: 22px;
+ height: 22px;
+ }
+
+ @media (max-width: 1024px) {
+ flex-direction: column;
+ text-align: center;
+ }
+}
+
+#docs {
+ border-right: 1px solid var(--border);
+
+ @media (max-width: 1024px) {
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ }
+}
+
+#next-steps ul {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ gap: 8px;
+ margin: 32px 0 0;
+
+ .logo {
+ height: 18px;
+ }
+
+ a {
+ color: var(--text-h);
+ font-size: 16px;
+ border-radius: 6px;
+ background: var(--social-bg);
+ display: flex;
+ padding: 6px 12px;
+ align-items: center;
+ gap: 8px;
+ text-decoration: none;
+ transition: box-shadow 0.3s;
+
+ &:hover {
+ box-shadow: var(--shadow);
+ }
+ .button-icon {
+ height: 18px;
+ width: 18px;
+ }
+ }
+
+ @media (max-width: 1024px) {
+ margin-top: 20px;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ li {
+ flex: 1 1 calc(50% - 8px);
+ }
+
+ a {
+ width: 100%;
+ justify-content: center;
+ box-sizing: border-box;
+ }
+ }
+}
+
+#spacer {
+ height: 88px;
+ border-top: 1px solid var(--border);
+ @media (max-width: 1024px) {
+ height: 48px;
+ }
+}
+
+.ticks {
+ position: relative;
+ width: 100%;
+
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ top: -4.5px;
+ border: 5px solid transparent;
+ }
+
+ &::before {
+ left: 0;
+ border-left-color: var(--border);
+ }
+ &::after {
+ right: 0;
+ border-right-color: var(--border);
+ }
+}
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..e38828a
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,77 @@
+import React, { useEffect } from 'react';
+import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { Spin } from 'antd';
+import ProtectedRoute from './components/ProtectedRoute';
+import AdminLayout from './layouts/AdminLayout';
+import LoginPage from './pages/auth/LoginPage';
+import ProfilePage from './pages/profile/ProfilePage';
+import DashboardPage from './pages/dashboard/DashboardPage';
+import UserListPage from './pages/users/UserListPage';
+import UserDetailPage from './pages/users/UserDetailPage';
+import EventListPage from './pages/events/EventListPage';
+import EventDetailPage from './pages/events/EventDetailPage';
+import ReportListPage from './pages/reports/ReportListPage';
+import ReportDetailPage from './pages/reports/ReportDetailPage';
+import ReviewListPage from './pages/reviews/ReviewListPage';
+import ReviewDetailPage from './pages/reviews/ReviewDetailPage';
+import BannedWordsPage from './pages/banned-words/BannedWordsPage';
+import TicketListPage from './pages/tickets/TicketListPage';
+import TicketDetailPage from './pages/tickets/TicketDetailPage';
+import SubscriptionListPage from './pages/subscriptions/SubscriptionListPage';
+import AdminListPage from './pages/admins/AdminListPage';
+import AdminDetailPage from './pages/admins/AdminDetailPage';
+import AuditPage from './pages/audit/AuditPage';
+import { useAuthStore } from './store/authStore';
+
+const queryClient = new QueryClient();
+
+const App: React.FC = () => {
+ const { isInitialized, checkAuth } = useAuthStore();
+
+ useEffect(() => {
+ checkAuth();
+ }, []);
+
+ if (!isInitialized) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ } />
+ }>
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+ );
+};
+
+export default App;
\ No newline at end of file
diff --git a/src/api/adminsApi.ts b/src/api/adminsApi.ts
new file mode 100644
index 0000000..743103d
--- /dev/null
+++ b/src/api/adminsApi.ts
@@ -0,0 +1,25 @@
+import apiClient from './client';
+import { Admin, AdminListParams, PaginatedResponse } from '../types/api';
+
+export const adminsApi = {
+ getAdmins: async (params: AdminListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/admins', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getAdmin: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/admins/${id}`);
+ return data;
+ },
+ createAdmin: async (payload: { email: string; password: string; role: string }): Promise => {
+ const { data } = await apiClient.post('/v1/admin/admins', payload);
+ return data;
+ },
+ updateAdmin: async (id: string, payload: Partial): Promise => {
+ const { data } = await apiClient.put(`/v1/admin/admins/${id}`, payload);
+ return data;
+ },
+ deleteAdmin: async (id: string): Promise => {
+ await apiClient.delete(`/v1/admin/admins/${id}`);
+ },
+};
\ No newline at end of file
diff --git a/src/api/auditApi.ts b/src/api/auditApi.ts
new file mode 100644
index 0000000..0e61c26
--- /dev/null
+++ b/src/api/auditApi.ts
@@ -0,0 +1,10 @@
+import apiClient from './client';
+import { AuditRecord, AuditListParams, PaginatedResponse } from '../types/api';
+
+export const auditApi = {
+ getAuditRecords: async (params: AuditListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/audit', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+};
\ No newline at end of file
diff --git a/src/api/authApi.ts b/src/api/authApi.ts
new file mode 100644
index 0000000..6a54830
--- /dev/null
+++ b/src/api/authApi.ts
@@ -0,0 +1,19 @@
+import apiClient from './client';
+import { Admin } from '../types/api';
+
+interface LoginResponse {
+ token: string;
+ refresh_token: string;
+ user: Admin;
+}
+
+export const authApi = {
+ login: async (email: string, password: string): Promise => {
+ const { data } = await apiClient.post('/v1/admin/login', { email, password });
+ return data;
+ },
+ getMe: async (): Promise => {
+ const { data } = await apiClient.get('/v1/admin/me');
+ return data;
+ },
+};
\ No newline at end of file
diff --git a/src/api/bannedWordsApi.ts b/src/api/bannedWordsApi.ts
new file mode 100644
index 0000000..4bd95ff
--- /dev/null
+++ b/src/api/bannedWordsApi.ts
@@ -0,0 +1,24 @@
+import apiClient from './client';
+import { BannedWord, PaginatedResponse } from '../types/api';
+
+export interface BannedWordListParams {
+ q?: string;
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+export const bannedWordsApi = {
+ getBannedWords: async (params?: BannedWordListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/banned-words', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ addBannedWord: async (word: string): Promise => {
+ await apiClient.post('/v1/admin/banned-words', { word });
+ },
+ removeBannedWord: async (word: string): Promise => {
+ await apiClient.delete(`/v1/admin/banned-words/${encodeURIComponent(word)}`);
+ },
+};
\ No newline at end of file
diff --git a/src/api/client.ts b/src/api/client.ts
new file mode 100644
index 0000000..daf7204
--- /dev/null
+++ b/src/api/client.ts
@@ -0,0 +1,32 @@
+import axios from 'axios';
+import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../utils/constants';
+
+const apiClient = axios.create({
+ baseURL: import.meta.env.VITE_API_BASE_URL || '',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+});
+
+apiClient.interceptors.request.use((config) => {
+ const token = localStorage.getItem(ACCESS_TOKEN_KEY);
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+});
+
+apiClient.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ // Временно отключаем рефреш и просто редиректим при 401
+ if (error.response?.status === 401) {
+ localStorage.removeItem(ACCESS_TOKEN_KEY);
+ localStorage.removeItem(REFRESH_TOKEN_KEY);
+ window.location.href = '/login';
+ }
+ return Promise.reject(error);
+ }
+);
+
+export default apiClient;
\ No newline at end of file
diff --git a/src/api/dashboardApi.ts b/src/api/dashboardApi.ts
new file mode 100644
index 0000000..992d203
--- /dev/null
+++ b/src/api/dashboardApi.ts
@@ -0,0 +1,12 @@
+import apiClient from './client';
+import { DashboardStats } from '../types/api';
+
+export const dashboardApi = {
+ getStats: async (from?: string, to?: string): Promise => {
+ const params: Record = {};
+ if (from) params.from = from;
+ if (to) params.to = to;
+ const { data } = await apiClient.get('/v1/admin/stats', { params });
+ return data;
+ },
+};
\ No newline at end of file
diff --git a/src/api/eventsApi.ts b/src/api/eventsApi.ts
new file mode 100644
index 0000000..c35c704
--- /dev/null
+++ b/src/api/eventsApi.ts
@@ -0,0 +1,20 @@
+import apiClient from './client';
+import { Event, EventListParams, PaginatedResponse } from '../types/api';
+
+export const eventsApi = {
+ getEvents: async (params: EventListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/events', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getEvent: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/events/${id}`);
+ return data;
+ },
+ updateEvent: async (id: string, payload: Partial): Promise => {
+ await apiClient.put(`/v1/admin/events/${id}`, payload);
+ },
+ deleteEvent: async (id: string): Promise => {
+ await apiClient.delete(`/v1/admin/events/${id}`);
+ },
+};
\ No newline at end of file
diff --git a/src/api/reportsApi.ts b/src/api/reportsApi.ts
new file mode 100644
index 0000000..4cdc82d
--- /dev/null
+++ b/src/api/reportsApi.ts
@@ -0,0 +1,17 @@
+import apiClient from './client';
+import { Report, ReportListParams, PaginatedResponse } from '../types/api';
+
+export const reportsApi = {
+ getReports: async (params: ReportListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/reports', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getReport: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/reports/${id}`);
+ return data;
+ },
+ updateReport: async (id: string, payload: { status: 'reviewed' | 'dismissed' }): Promise => {
+ await apiClient.put(`/v1/admin/reports/${id}`, payload);
+ },
+};
\ No newline at end of file
diff --git a/src/api/reviewsApi.ts b/src/api/reviewsApi.ts
new file mode 100644
index 0000000..8072eb1
--- /dev/null
+++ b/src/api/reviewsApi.ts
@@ -0,0 +1,21 @@
+import apiClient from './client';
+import { Review, ReviewListParams, PaginatedResponse } from '../types/api';
+
+export const reviewsApi = {
+ getReviews: async (params: ReviewListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/reviews', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getReview: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/reviews/${id}`);
+ return data;
+ },
+ updateReview: async (id: string, payload: Partial): Promise => {
+ await apiClient.put(`/v1/admin/reviews/${id}`, payload);
+ },
+ bulkUpdateReviews: async (updates: { id: string; status: string }[]): Promise => {
+ const { data } = await apiClient.patch('/v1/admin/reviews', updates);
+ return data;
+ },
+};
\ No newline at end of file
diff --git a/src/api/subscriptionsApi.ts b/src/api/subscriptionsApi.ts
new file mode 100644
index 0000000..a42c0cc
--- /dev/null
+++ b/src/api/subscriptionsApi.ts
@@ -0,0 +1,22 @@
+import apiClient from './client';
+import { Subscription, SubscriptionListParams, PaginatedResponse } from '../types/api';
+
+export const subscriptionsApi = {
+ getSubscriptions: async (params: SubscriptionListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/subscriptions', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getSubscription: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/subscriptions/${id}`);
+ return data;
+ },
+ updateSubscription: async (id: string, payload: Partial): Promise => {
+ const { data } = await apiClient.put(`/v1/admin/subscriptions/${id}`, payload);
+ return data;
+ },
+ deleteSubscription: async (id: string): Promise => {
+ const { data } = await apiClient.delete(`/v1/admin/subscriptions/${id}`);
+ return data;
+ },
+};
\ No newline at end of file
diff --git a/src/api/ticketsApi.ts b/src/api/ticketsApi.ts
new file mode 100644
index 0000000..75598e1
--- /dev/null
+++ b/src/api/ticketsApi.ts
@@ -0,0 +1,24 @@
+import apiClient from './client';
+import { Ticket, TicketListParams, TicketStats, PaginatedResponse } from '../types/api';
+
+export const ticketsApi = {
+ getTickets: async (params: TicketListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/tickets', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getTicket: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/tickets/${id}`);
+ return data;
+ },
+ updateTicket: async (id: string, payload: Partial): Promise => {
+ await apiClient.put(`/v1/admin/tickets/${id}`, payload);
+ },
+ deleteTicket: async (id: string): Promise => {
+ await apiClient.delete(`/v1/admin/tickets/${id}`);
+ },
+ getTicketStats: async (): Promise => {
+ const { data } = await apiClient.get('/v1/admin/tickets/stats');
+ return data;
+ },
+};
\ No newline at end of file
diff --git a/src/api/usersApi.ts b/src/api/usersApi.ts
new file mode 100644
index 0000000..69a3b55
--- /dev/null
+++ b/src/api/usersApi.ts
@@ -0,0 +1,20 @@
+import apiClient from './client';
+import { User, UserListParams, PaginatedResponse } from '../types/api';
+
+export const usersApi = {
+ getUsers: async (params: UserListParams): Promise> => {
+ const { data, headers } = await apiClient.get('/v1/admin/users', { params });
+ const total = parseInt(headers['x-total-count'] || '0', 10);
+ return { data, total };
+ },
+ getUser: async (id: string): Promise => {
+ const { data } = await apiClient.get(`/v1/admin/users/${id}`);
+ return data;
+ },
+ updateUser: async (id: string, payload: Partial): Promise => {
+ await apiClient.put(`/v1/admin/users/${id}`, payload);
+ },
+ deleteUser: async (id: string): Promise => {
+ await apiClient.delete(`/v1/admin/users/${id}`);
+ },
+};
\ No newline at end of file
diff --git a/src/assets/hero.png b/src/assets/hero.png
new file mode 100644
index 0000000..02251f4
Binary files /dev/null and b/src/assets/hero.png differ
diff --git a/src/assets/react.svg b/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/vite.svg b/src/assets/vite.svg
new file mode 100644
index 0000000..5101b67
--- /dev/null
+++ b/src/assets/vite.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..a8498fb
--- /dev/null
+++ b/src/components/ProtectedRoute.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { Navigate, Outlet } from 'react-router-dom';
+import { Spin } from 'antd';
+import { useAuthStore } from '../store/authStore';
+
+interface Props {
+ allowedRoles?: string[];
+}
+
+const ProtectedRoute: React.FC = ({ allowedRoles }) => {
+ const { isAuthenticated, isInitialized, user } = useAuthStore();
+
+ if (!isInitialized) {
+ return (
+
+
+
+ );
+ }
+
+ if (!isAuthenticated) {
+ return ;
+ }
+
+ if (allowedRoles && user && !allowedRoles.includes(user.role)) {
+ return ;
+ }
+
+ return ;
+};
+
+export default ProtectedRoute;
\ No newline at end of file
diff --git a/src/hooks/useAdminWebSocket.ts b/src/hooks/useAdminWebSocket.ts
new file mode 100644
index 0000000..b8930cb
--- /dev/null
+++ b/src/hooks/useAdminWebSocket.ts
@@ -0,0 +1,101 @@
+import { useEffect, useRef } from 'react';
+import { useQueryClient } from '@tanstack/react-query';
+import { useAuthStore } from '../store/authStore';
+
+type WsMessage = {
+ type: 'report_created' | 'ticket_created';
+ data?: {
+ report_id?: string;
+ ticket_id?: string;
+ target_type?: string;
+ target_id?: string;
+ reason?: string;
+ };
+ status?: string;
+ channel?: string;
+ timestamp?: number;
+};
+
+export const useAdminWebSocket = () => {
+ const queryClient = useQueryClient();
+ const accessToken = useAuthStore((s) => s.accessToken);
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
+ const reconnectTimeoutRef = useRef>();
+ const pingIntervalRef = useRef>();
+ const lastMessageTimestamp = useRef(0);
+ const wsRef = useRef(null);
+
+ useEffect(() => {
+ if (!isAuthenticated || !accessToken) return;
+
+ let isMounted = true;
+ const connect = () => {
+ const wsUrl = import.meta.env.DEV
+ ? `ws://localhost:5173/admin/ws?token=${accessToken}`
+ : `${import.meta.env.VITE_WS_URL || 'wss://admin-ws.eventhub.local'}/admin/ws?token=${accessToken}`;
+
+ const ws = new WebSocket(wsUrl);
+ wsRef.current = ws;
+
+ ws.onopen = () => {
+ if (!isMounted) return;
+ console.log('[WS] Connected');
+ ws.send(JSON.stringify({ action: 'subscribe', channel: 'reports' }));
+ ws.send(JSON.stringify({ action: 'subscribe', channel: 'tickets' }));
+
+ pingIntervalRef.current = setInterval(() => {
+ if (ws.readyState === WebSocket.OPEN) {
+ ws.send(JSON.stringify({ action: 'ping' }));
+ }
+ }, 30_000);
+ };
+
+ ws.onmessage = (event) => {
+ if (!isMounted) return;
+ try {
+ const msg: WsMessage = JSON.parse(event.data);
+ console.log('[WS] Message:', msg);
+
+ if (msg.timestamp && msg.timestamp === lastMessageTimestamp.current) {
+ return;
+ }
+ if (msg.timestamp) {
+ lastMessageTimestamp.current = msg.timestamp;
+ }
+
+ if (msg.type === 'report_created' || msg.type === 'ticket_created') {
+ window.dispatchEvent(
+ new CustomEvent('admin-ws-message', { detail: msg })
+ );
+ if (msg.type === 'report_created') {
+ queryClient.invalidateQueries({ queryKey: ['reports'] });
+ } else if (msg.type === 'ticket_created') {
+ queryClient.invalidateQueries({ queryKey: ['tickets'] });
+ queryClient.invalidateQueries({ queryKey: ['ticket-stats'] });
+ }
+ }
+ } catch (e) {
+ console.warn('[WS] Failed to parse message:', e);
+ }
+ };
+
+ ws.onclose = () => {
+ if (!isMounted) return;
+ console.log('[WS] Disconnected, will reconnect in 5s');
+ if (pingIntervalRef.current) clearInterval(pingIntervalRef.current);
+ reconnectTimeoutRef.current = setTimeout(connect, 5000);
+ };
+
+ // Не вызываем ws.close() при ошибке, браузер закроет сокет сам
+ };
+
+ connect();
+
+ return () => {
+ isMounted = false;
+ if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
+ if (pingIntervalRef.current) clearInterval(pingIntervalRef.current);
+ // Никакого принудительного закрытия wsRef.current – браузер сам управится
+ };
+ }, [isAuthenticated, accessToken, queryClient]);
+};
\ No newline at end of file
diff --git a/src/hooks/useAdmins.ts b/src/hooks/useAdmins.ts
new file mode 100644
index 0000000..b763052
--- /dev/null
+++ b/src/hooks/useAdmins.ts
@@ -0,0 +1,62 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { adminsApi } from '../api/adminsApi';
+import { AdminListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useAdmins = (params: AdminListParams) => {
+ return useQuery({
+ queryKey: ['admins', params],
+ queryFn: async () => {
+ const res = await adminsApi.getAdmins(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useAdmin = (id: string) => {
+ return useQuery({
+ queryKey: ['admins', id],
+ queryFn: () => adminsApi.getAdmin(id),
+ enabled: !!id && id !== '-' && id !== 'undefined',
+ retry: false,
+ });
+};
+
+export const useCreateAdmin = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (data: { email: string; password: string; role: string }) =>
+ adminsApi.createAdmin(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['admins'] });
+ message.success('Администратор создан');
+ },
+ onError: () => message.error('Ошибка создания'),
+ });
+};
+
+export const useUpdateAdmin = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: Partial }) =>
+ adminsApi.updateAdmin(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['admins'] });
+ message.success('Администратор обновлён');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
+
+export const useDeleteAdmin = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (id: string) => adminsApi.deleteAdmin(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['admins'] });
+ message.success('Администратор удалён');
+ },
+ onError: () => message.error('Ошибка удаления'),
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useAudit.ts b/src/hooks/useAudit.ts
new file mode 100644
index 0000000..9648f02
--- /dev/null
+++ b/src/hooks/useAudit.ts
@@ -0,0 +1,14 @@
+import { useQuery } from '@tanstack/react-query';
+import { auditApi } from '../api/auditApi';
+import { AuditListParams } from '../types/api';
+import { normalizeData } from '../utils/normalize';
+
+export const useAudit = (params: AuditListParams) => {
+ return useQuery({
+ queryKey: ['audit', params],
+ queryFn: async () => {
+ const res = await auditApi.getAuditRecords(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useBannedWords.ts b/src/hooks/useBannedWords.ts
new file mode 100644
index 0000000..9e4b7dd
--- /dev/null
+++ b/src/hooks/useBannedWords.ts
@@ -0,0 +1,38 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { bannedWordsApi, BannedWordListParams } from '../api/bannedWordsApi';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useBannedWords = (params?: BannedWordListParams) => {
+ return useQuery({
+ queryKey: ['banned-words', params],
+ queryFn: async () => {
+ const res = await bannedWordsApi.getBannedWords(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useAddBannedWord = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (word: string) => bannedWordsApi.addBannedWord(word),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['banned-words'] });
+ message.success('Слово добавлено');
+ },
+ onError: () => message.error('Ошибка добавления'),
+ });
+};
+
+export const useRemoveBannedWord = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (word: string) => bannedWordsApi.removeBannedWord(word),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['banned-words'] });
+ message.success('Слово удалено');
+ },
+ onError: () => message.error('Ошибка удаления'),
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useDashboard.ts b/src/hooks/useDashboard.ts
new file mode 100644
index 0000000..5878a11
--- /dev/null
+++ b/src/hooks/useDashboard.ts
@@ -0,0 +1,13 @@
+import { useQuery } from '@tanstack/react-query';
+import { dashboardApi } from '../api/dashboardApi';
+import { normalizeData } from '../utils/normalize';
+
+export const useDashboardStats = (from?: string, to?: string) => {
+ return useQuery({
+ queryKey: ['dashboard', from, to],
+ queryFn: async () => {
+ const data = await dashboardApi.getStats(from, to);
+ return normalizeData(data);
+ },
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useEvents.ts b/src/hooks/useEvents.ts
new file mode 100644
index 0000000..c14b464
--- /dev/null
+++ b/src/hooks/useEvents.ts
@@ -0,0 +1,48 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { eventsApi } from '../api/eventsApi';
+import { EventListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useEvents = (params: EventListParams) => {
+ return useQuery({
+ queryKey: ['events', params],
+ queryFn: async () => {
+ const res = await eventsApi.getEvents(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useEvent = (id: string) => {
+ return useQuery({
+ queryKey: ['events', id],
+ queryFn: () => eventsApi.getEvent(id), // сырые данные для формы
+ enabled: !!id && id !== '-',
+ });
+};
+
+export const useUpdateEvent = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: Partial }) =>
+ eventsApi.updateEvent(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['events'] });
+ message.success('Событие обновлено');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
+
+export const useDeleteEvent = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (id: string) => eventsApi.deleteEvent(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['events'] });
+ message.success('Событие удалено');
+ },
+ onError: () => message.error('Ошибка удаления'),
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useReports.ts b/src/hooks/useReports.ts
new file mode 100644
index 0000000..7316189
--- /dev/null
+++ b/src/hooks/useReports.ts
@@ -0,0 +1,36 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { reportsApi } from '../api/reportsApi';
+import { ReportListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useReports = (params: ReportListParams) => {
+ return useQuery({
+ queryKey: ['reports', params],
+ queryFn: async () => {
+ const res = await reportsApi.getReports(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useReport = (id: string) => {
+ return useQuery({
+ queryKey: ['reports', id],
+ queryFn: () => reportsApi.getReport(id),
+ enabled: !!id,
+ });
+};
+
+export const useUpdateReport = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: { status: 'reviewed' | 'dismissed' } }) =>
+ reportsApi.updateReport(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['reports'] });
+ message.success('Статус обновлён');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useReviews.ts b/src/hooks/useReviews.ts
new file mode 100644
index 0000000..758bae7
--- /dev/null
+++ b/src/hooks/useReviews.ts
@@ -0,0 +1,54 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { reviewsApi } from '../api/reviewsApi';
+import { ReviewListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useReviews = (params: ReviewListParams) => {
+ return useQuery({
+ queryKey: ['reviews', params],
+ queryFn: async () => {
+ const res = await reviewsApi.getReviews(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+// Вот этот хук отсутствовал
+export const useReview = (id: string) => {
+ return useQuery({
+ queryKey: ['reviews', id],
+ queryFn: () => reviewsApi.getReview(id),
+ enabled: !!id && id !== '-',
+ retry: false,
+ });
+};
+
+export const useUpdateReview = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: Partial }) =>
+ reviewsApi.updateReview(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['reviews'] });
+ message.success('Отзыв обновлён');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
+
+export const useBulkUpdateReviews = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (updates: { id: string; status: string }[]) =>
+ reviewsApi.bulkUpdateReviews(updates),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['reviews'] });
+ message.success('Статусы обновлены');
+ },
+ onError: (error) => {
+ message.error('Ошибка массового обновления');
+ console.error(error);
+ },
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useSubscriptions.ts b/src/hooks/useSubscriptions.ts
new file mode 100644
index 0000000..7224752
--- /dev/null
+++ b/src/hooks/useSubscriptions.ts
@@ -0,0 +1,48 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { subscriptionsApi } from '../api/subscriptionsApi';
+import { SubscriptionListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useSubscriptions = (params: SubscriptionListParams) => {
+ return useQuery({
+ queryKey: ['subscriptions', params],
+ queryFn: async () => {
+ const res = await subscriptionsApi.getSubscriptions(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useSubscription = (id: string) => {
+ return useQuery({
+ queryKey: ['subscriptions', id],
+ queryFn: () => subscriptionsApi.getSubscription(id), // сырые данные
+ enabled: !!id,
+ });
+};
+
+export const useUpdateSubscription = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: Partial }) =>
+ subscriptionsApi.updateSubscription(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['subscriptions'] });
+ message.success('Подписка обновлена');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
+
+export const useDeleteSubscription = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (id: string) => subscriptionsApi.deleteSubscription(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['subscriptions'] });
+ message.success('Подписка удалена');
+ },
+ onError: () => message.error('Ошибка удаления'),
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useTickets.ts b/src/hooks/useTickets.ts
new file mode 100644
index 0000000..97e1292
--- /dev/null
+++ b/src/hooks/useTickets.ts
@@ -0,0 +1,58 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { ticketsApi } from '../api/ticketsApi';
+import { TicketListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useTickets = (params: TicketListParams) => {
+ return useQuery({
+ queryKey: ['tickets', params],
+ queryFn: async () => {
+ const res = await ticketsApi.getTickets(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useTicket = (id: string) => {
+ return useQuery({
+ queryKey: ['tickets', id],
+ queryFn: () => ticketsApi.getTicket(id), // сырые данные
+ enabled: !!id,
+ });
+};
+
+export const useUpdateTicket = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: Partial }) =>
+ ticketsApi.updateTicket(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['tickets'] });
+ message.success('Тикет обновлён');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
+
+export const useDeleteTicket = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (id: string) => ticketsApi.deleteTicket(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['tickets'] });
+ message.success('Тикет удалён');
+ },
+ onError: () => message.error('Ошибка удаления'),
+ });
+};
+
+export const useTicketStats = () => {
+ return useQuery({
+ queryKey: ['ticket-stats'],
+ queryFn: async () => {
+ const data = await ticketsApi.getTicketStats();
+ return normalizeData(data);
+ },
+ });
+};
\ No newline at end of file
diff --git a/src/hooks/useUsers.ts b/src/hooks/useUsers.ts
new file mode 100644
index 0000000..764760c
--- /dev/null
+++ b/src/hooks/useUsers.ts
@@ -0,0 +1,47 @@
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { usersApi } from '../api/usersApi';
+import { UserListParams } from '../types/api';
+import { message } from 'antd';
+import { normalizeData } from '../utils/normalize';
+
+export const useUsers = (params: UserListParams) => {
+ return useQuery({
+ queryKey: ['users', params],
+ queryFn: async () => {
+ const res = await usersApi.getUsers(params);
+ return { data: normalizeData(res.data), total: res.total };
+ },
+ });
+};
+
+export const useUser = (id: string) => {
+ return useQuery({
+ queryKey: ['users', id],
+ queryFn: () => usersApi.getUser(id),
+ enabled: !!id && id !== '-',
+ });
+};
+
+export const useUpdateUser = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ id, data }: { id: string; data: Partial }) => usersApi.updateUser(id, data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['users'] });
+ message.success('Пользователь обновлен');
+ },
+ onError: () => message.error('Ошибка обновления'),
+ });
+};
+
+export const useDeleteUser = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (id: string) => usersApi.deleteUser(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['users'] });
+ message.success('Пользователь удален');
+ },
+ onError: () => message.error('Ошибка удаления'),
+ });
+};
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..5fb3313
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,111 @@
+:root {
+ --text: #6b6375;
+ --text-h: #08060d;
+ --bg: #fff;
+ --border: #e5e4e7;
+ --code-bg: #f4f3ec;
+ --accent: #aa3bff;
+ --accent-bg: rgba(170, 59, 255, 0.1);
+ --accent-border: rgba(170, 59, 255, 0.5);
+ --social-bg: rgba(244, 243, 236, 0.5);
+ --shadow:
+ rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
+
+ --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
+ --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
+ --mono: ui-monospace, Consolas, monospace;
+
+ font: 18px/145% var(--sans);
+ letter-spacing: 0.18px;
+ color-scheme: light dark;
+ color: var(--text);
+ background: var(--bg);
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ @media (max-width: 1024px) {
+ font-size: 16px;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --text: #9ca3af;
+ --text-h: #f3f4f6;
+ --bg: #16171d;
+ --border: #2e303a;
+ --code-bg: #1f2028;
+ --accent: #c084fc;
+ --accent-bg: rgba(192, 132, 252, 0.15);
+ --accent-border: rgba(192, 132, 252, 0.5);
+ --social-bg: rgba(47, 48, 58, 0.5);
+ --shadow:
+ rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
+ }
+
+ #social .button-icon {
+ filter: invert(1) brightness(2);
+ }
+}
+
+#root {
+ width: 1126px;
+ max-width: 100%;
+ margin: 0 auto;
+ text-align: center;
+ border-inline: 1px solid var(--border);
+ min-height: 100svh;
+ display: flex;
+ flex-direction: column;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+}
+
+h1,
+h2 {
+ font-family: var(--heading);
+ font-weight: 500;
+ color: var(--text-h);
+}
+
+h1 {
+ font-size: 56px;
+ letter-spacing: -1.68px;
+ margin: 32px 0;
+ @media (max-width: 1024px) {
+ font-size: 36px;
+ margin: 20px 0;
+ }
+}
+h2 {
+ font-size: 24px;
+ line-height: 118%;
+ letter-spacing: -0.24px;
+ margin: 0 0 8px;
+ @media (max-width: 1024px) {
+ font-size: 20px;
+ }
+}
+p {
+ margin: 0;
+}
+
+code,
+.counter {
+ font-family: var(--mono);
+ display: inline-flex;
+ border-radius: 4px;
+ color: var(--text-h);
+}
+
+code {
+ font-size: 15px;
+ line-height: 135%;
+ padding: 4px 8px;
+ background: var(--code-bg);
+}
diff --git a/src/layouts/AdminLayout.tsx b/src/layouts/AdminLayout.tsx
new file mode 100644
index 0000000..7cff764
--- /dev/null
+++ b/src/layouts/AdminLayout.tsx
@@ -0,0 +1,281 @@
+import React, { useEffect, useState } from 'react';
+import { Layout, Menu, Button, theme, notification, Avatar, Dropdown, Space, Typography, Badge } from 'antd';
+import { Outlet, useNavigate, useLocation } from 'react-router-dom';
+import { useAuthStore } from '../store/authStore';
+import { useAdminWebSocket } from '../hooks/useAdminWebSocket';
+import {
+ DashboardOutlined,
+ UserOutlined,
+ CalendarOutlined,
+ WarningOutlined,
+ StarOutlined,
+ StopOutlined,
+ BugOutlined,
+ DollarOutlined,
+ TeamOutlined,
+ AuditOutlined,
+ LogoutOutlined,
+ SettingOutlined,
+ MenuFoldOutlined,
+ MenuUnfoldOutlined,
+} from '@ant-design/icons';
+
+const { Header, Sider, Content } = Layout;
+const { Text } = Typography;
+
+const AdminLayout: React.FC = () => {
+ useAdminWebSocket();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { user, logout } = useAuthStore();
+ const { token: { colorBgContainer } } = theme.useToken();
+ const [collapsed, setCollapsed] = useState(false);
+
+ // Обработка WebSocket-уведомлений
+ useEffect(() => {
+ const handler = (event: CustomEvent) => {
+ const msg = event.detail;
+ if (msg.type === 'report_created') {
+ const { report_id, target_type, target_id, reason } = msg.data || {};
+ notification.info({
+ title: 'Новая жалоба',
+ description: (
+
+ Жалоба{' '}
+ {report_id ? (
+ {
+ e.preventDefault();
+ navigate(`/reports/${report_id}`);
+ }}
+ style={{ fontWeight: 600 }}
+ >
+ #{report_id}
+
+ ) : (
+ '#?'
+ )}{' '}
+ на {target_type || 'неизвестный тип'}{' '}
+ {target_id ? (
+ {
+ e.preventDefault();
+ navigate(`/${target_type}s/${target_id}`);
+ }}
+ style={{ fontWeight: 600 }}
+ >
+ {target_id}
+
+ ) : (
+ '?'
+ )}{' '}
+ {reason ? `(${reason})` : ''}
+
+ ),
+ placement: 'topRight',
+ });
+ } else if (msg.type === 'ticket_created') {
+ const { ticket_id } = msg.data || {};
+ notification.info({
+ title: 'Новый тикет',
+ description: (
+
+ Тикет{' '}
+ {ticket_id ? (
+ {
+ e.preventDefault();
+ navigate(`/tickets/${ticket_id}`);
+ }}
+ style={{ fontWeight: 600 }}
+ >
+ #{ticket_id}
+
+ ) : (
+ 'без ID'
+ )}{' '}
+ создан
+
+ ),
+ placement: 'topRight',
+ });
+ }
+ };
+
+ window.addEventListener('admin-ws-message', handler as EventListener);
+ return () => window.removeEventListener('admin-ws-message', handler as EventListener);
+ }, [navigate]);
+
+ const menuItems = [
+ { key: '/dashboard', icon: , label: 'Дашборд' },
+ { key: '/users', icon: , label: 'Пользователи', roles: ['superadmin', 'admin'] },
+ { key: '/events', icon: , label: 'События', roles: ['superadmin', 'admin'] },
+ { key: '/reports', icon: , label: 'Жалобы', roles: ['superadmin', 'admin', 'moderator'] },
+ { key: '/reviews', icon: , label: 'Отзывы', roles: ['superadmin', 'admin', 'moderator'] },
+ { key: '/banned-words', icon: , label: 'Бан-слова', roles: ['superadmin', 'admin'] },
+ { key: '/tickets', icon: , label: 'Тикеты', roles: ['superadmin', 'admin', 'support'] },
+ { key: '/subscriptions', icon: , label: 'Подписки', roles: ['superadmin', 'admin'] },
+ { key: '/admins', icon: , label: 'Администраторы', roles: ['superadmin'] },
+ { key: '/audit', icon: , label: 'Аудит', roles: ['superadmin'] },
+ ];
+
+ const filteredMenu = menuItems.filter(
+ (item) => !item.roles || (user && item.roles.includes(user.role))
+ );
+
+ const handleLogout = async () => {
+ await logout();
+ navigate('/login');
+ };
+
+ const getDisplayName = () => {
+ const nick = user?.nickname;
+ const email = user?.email;
+ if (nick && nick !== '-' && nick !== 'undefined') return nick;
+ if (email && email !== '-' && email !== 'undefined') return email;
+ return user?.id ?? '—';
+ };
+
+ return (
+
+
+
+ {collapsed ? 'EH' : import.meta.env.VITE_APP_TITLE}
+
+
+
+
+
+
+
+
+
+ {/* Резерв */}
+
+
+
+
+
+
+ );
+};
+
+export default AdminLayout;
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..eed14fc
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import dayjs from 'dayjs';
+import 'dayjs/locale/ru';
+import locale from 'antd/locale/ru_RU';
+import { ConfigProvider } from 'antd';
+import App from './App';
+
+dayjs.locale('ru');
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+);
\ No newline at end of file
diff --git a/src/pages/admins/AdminDetailPage.tsx b/src/pages/admins/AdminDetailPage.tsx
new file mode 100644
index 0000000..5fc30f4
--- /dev/null
+++ b/src/pages/admins/AdminDetailPage.tsx
@@ -0,0 +1,160 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Card, Descriptions, Spin, Button, Tag, Space, Modal, Form, Input, Select, message } from 'antd';
+import { EditOutlined } from '@ant-design/icons';
+import { useAdmin, useUpdateAdmin } from '../../hooks/useAdmins';
+import dayjs from 'dayjs';
+
+const AdminDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const { data: admin, isLoading } = useAdmin(id || '');
+ const updateAdmin = useUpdateAdmin();
+
+ const [editModal, setEditModal] = useState(false);
+ const [form] = Form.useForm();
+
+ // Очистка значения
+ const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
+
+ // Заполнение формы при открытии
+ useEffect(() => {
+ if (editModal && admin) {
+ setTimeout(() => {
+ form.setFieldsValue({
+ nickname: clean(admin.nickname),
+ email: clean(admin.email),
+ role: clean(admin.role),
+ status: clean(admin.status),
+ timezone: clean(admin.timezone),
+ language: clean(admin.language),
+ phone: clean(admin.phone),
+ });
+ }, 0);
+ }
+ }, [editModal, admin, form]);
+
+ const handleSave = () => {
+ form.validateFields().then((values) => {
+ const payload = Object.fromEntries(
+ Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null)
+ );
+ updateAdmin.mutate(
+ { id: id!, data: payload },
+ {
+ onSuccess: () => {
+ setEditModal(false);
+ message.success('Данные обновлены');
+ },
+ }
+ );
+ });
+ };
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ const roleColors: Record = {
+ superadmin: 'red',
+ admin: 'blue',
+ moderator: 'purple',
+ support: 'cyan',
+ };
+
+ if (isLoading) return ;
+ if (!admin) return Администратор не найден
;
+
+ return (
+ } onClick={() => setEditModal(true)}>
+ Редактировать
+
+ }
+ >
+
+ {admin.id}
+ {admin.email}
+ {isBadValue(admin.nickname) ? '-' : admin.nickname}
+
+ {admin.role}
+
+
+ {admin.status}
+
+ {isBadValue(admin.timezone) ? '-' : admin.timezone}
+ {isBadValue(admin.language) ? '-' : admin.language}
+ {isBadValue(admin.phone) ? '-' : admin.phone}
+ {isBadValue(admin.avatar_url) ? '-' : admin.avatar_url}
+ {isBadValue(admin.preferences) ? '-' : JSON.stringify(admin.preferences)}
+ {formatDate(admin.last_login)}
+ {formatDate(admin.created_at)}
+ {formatDate(admin.updated_at)}
+
+
+
+
+ setEditModal(false)}
+ onOk={handleSave}
+ confirmLoading={updateAdmin.isPending}
+ destroyOnHidden
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AdminDetailPage;
\ No newline at end of file
diff --git a/src/pages/admins/AdminListPage.tsx b/src/pages/admins/AdminListPage.tsx
new file mode 100644
index 0000000..8260c42
--- /dev/null
+++ b/src/pages/admins/AdminListPage.tsx
@@ -0,0 +1,305 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, Tag, Space, Modal, Form, Input, Select, Tooltip, Spin } from 'antd';
+import { InfoCircleOutlined, EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+import { useNavigate } from 'react-router-dom';
+import { useAdmins, useCreateAdmin, useUpdateAdmin, useDeleteAdmin, useAdmin } from '../../hooks/useAdmins';
+import { Admin, AdminListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+
+const AdminListPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'email', order: 'asc' });
+ const { data, isLoading } = useAdmins(params);
+ const createAdmin = useCreateAdmin();
+ const updateAdmin = useUpdateAdmin();
+ const deleteAdmin = useDeleteAdmin();
+
+ // Модальное окно создания
+ const [createModal, setCreateModal] = useState(false);
+ const [createForm] = Form.useForm();
+
+ // Модальное окно редактирования
+ const [editModal, setEditModal] = useState<{ open: boolean; adminId: string | null }>({
+ open: false,
+ adminId: null,
+ });
+ const { data: editingAdmin, isLoading: loadingAdmin } = useAdmin(editModal.adminId || '');
+ const [editForm] = Form.useForm();
+
+ // Очистка значения от "undefined" и "-"
+ const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
+
+ // Заполнение формы редактирования при загрузке данных
+ useEffect(() => {
+ if (editModal.open && editingAdmin) {
+ setTimeout(() => {
+ editForm.setFieldsValue({
+ nickname: clean(editingAdmin.nickname),
+ email: clean(editingAdmin.email),
+ role: clean(editingAdmin.role),
+ status: clean(editingAdmin.status),
+ timezone: clean(editingAdmin.timezone),
+ language: clean(editingAdmin.language),
+ phone: clean(editingAdmin.phone),
+ });
+ }, 0);
+ }
+ }, [editingAdmin, editModal.open, editForm]);
+
+ const handleCreate = () => {
+ createForm.validateFields().then((values) => {
+ createAdmin.mutate(values, {
+ onSuccess: () => {
+ setCreateModal(false);
+ createForm.resetFields();
+ },
+ });
+ });
+ };
+
+ const handleEdit = (admin: Admin) => {
+ // Не сбрасываем форму вручную, destroyOnHidden очистит её после предыдущего закрытия
+ setEditModal({ open: true, adminId: admin.id });
+ };
+
+ const handleUpdate = () => {
+ editForm.validateFields().then((values) => {
+ if (!editModal.adminId) return;
+ const payload = Object.fromEntries(
+ Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null)
+ );
+ updateAdmin.mutate(
+ { id: editModal.adminId, data: payload },
+ { onSuccess: () => setEditModal({ open: false, adminId: null }) }
+ );
+ });
+ };
+
+ const handleDelete = (id: string) => {
+ Modal.confirm({
+ title: 'Удалить администратора?',
+ okText: 'Удалить',
+ okType: 'danger',
+ cancelText: 'Отмена',
+ onOk: () => deleteAdmin.mutate(id),
+ });
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const roleColors: Record = {
+ superadmin: 'red',
+ admin: 'blue',
+ moderator: 'purple',
+ support: 'cyan',
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: ,
+ key: 'detail',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ type="link"
+ onClick={() => navigate(`/admins/${record.id}`)}
+ />
+
+ ),
+ },
+ {
+ title: 'Email',
+ dataIndex: 'email',
+ key: 'email',
+ sorter: true,
+ ellipsis: true,
+ },
+ {
+ title: 'Ник',
+ dataIndex: 'nickname',
+ key: 'nickname',
+ sorter: true,
+ ellipsis: true,
+ },
+ {
+ title: 'Роль',
+ dataIndex: 'role',
+ key: 'role',
+ width: 120,
+ sorter: true,
+ render: (role: string) => (
+ {role}
+ ),
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => (
+ {status}
+ ),
+ },
+ {
+ title: 'Последний вход',
+ dataIndex: 'last_login',
+ key: 'last_login',
+ sorter: true,
+ },
+ {
+ title: 'Действия',
+ key: 'actions',
+ width: 80,
+ render: (_, record) => (
+
+
+ }
+ size="small"
+ onClick={() => handleEdit(record)}
+ />
+
+
+ }
+ size="small"
+ danger
+ onClick={() => handleDelete(record.id)}
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+ );
+};
+
+export default AdminListPage;
\ No newline at end of file
diff --git a/src/pages/audit/AuditPage.tsx b/src/pages/audit/AuditPage.tsx
new file mode 100644
index 0000000..8eb0144
--- /dev/null
+++ b/src/pages/audit/AuditPage.tsx
@@ -0,0 +1,244 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Tag, Space, Select, DatePicker, Spin } from 'antd';
+import { Link } from 'react-router-dom';
+import { useAudit } from '../../hooks/useAudit';
+import { useAdmin, useAdmins } from '../../hooks/useAdmins';
+import { useUser } from '../../hooks/useUsers';
+import { useEvent } from '../../hooks/useEvents';
+import { useReview } from '../../hooks/useReviews';
+import { AuditRecord, AuditListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+
+const { RangePicker } = DatePicker;
+
+const AuditPage: React.FC = () => {
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'timestamp', order: 'desc' });
+ const { data, isLoading } = useAudit(params);
+
+ const { data: admins, isLoading: loadingAdmins } = useAdmins({ limit: 1000, offset: 0 });
+
+ const [uniqueActions, setUniqueActions] = useState([]);
+
+ useEffect(() => {
+ if (data?.data) {
+ const actions = new Set(data.data.map(record => record.action).filter(Boolean));
+ setUniqueActions(prev => {
+ const merged = new Set([...prev, ...actions]);
+ return Array.from(merged).sort();
+ });
+ }
+ }, [data]);
+
+ const AdminCell: React.FC<{ adminId: string }> = ({ adminId }) => {
+ const { data: admin, isLoading: loading } = useAdmin(adminId);
+ if (loading) return ;
+ if (!admin) return {adminId};
+ const name = admin.nickname && admin.nickname !== '-' ? admin.nickname : admin.email;
+ return {name || admin.id};
+ };
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const EntityNameCell: React.FC<{ entityType: string; entityId: string }> = ({ entityType, entityId }) => {
+ const { data: user, isLoading: loadingUser } = useUser(entityType === 'user' ? entityId : '');
+ const { data: event, isLoading: loadingEvent } = useEvent(entityType === 'event' ? entityId : '');
+ const { data: review, isLoading: loadingReview } = useReview(entityType === 'review' ? entityId : '');
+
+ if (entityType === 'user') {
+ if (loadingUser) return ;
+ if (!user) return {entityId};
+ const name = !isBadValue(user.nickname) ? user.nickname : user.email;
+ return {!isBadValue(name) ? name : user.id};
+ }
+
+ if (entityType === 'event') {
+ if (loadingEvent) return ;
+ if (!event) return {entityId};
+ const name = !isBadValue(event.title) ? event.title : event.id;
+ return {name};
+ }
+
+ if (entityType === 'review') {
+ if (loadingReview) return ;
+ // Для отзыва показываем ссылку на страницу отзыва
+ return {entityId};
+ }
+
+ // Для остальных типов (calendar, report, ticket, subscription, admin) пока просто ID
+ return {entityId};
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const handleDateChange = (dates: any) => {
+ if (dates) {
+ setParams({
+ ...params,
+ date_from: dates[0]?.toISOString(),
+ date_to: dates[1]?.toISOString(),
+ offset: 0,
+ });
+ } else {
+ setParams({ ...params, date_from: undefined, date_to: undefined, offset: 0 });
+ }
+ };
+
+ const roleColors: Record = {
+ superadmin: 'red',
+ admin: 'blue',
+ moderator: 'purple',
+ support: 'cyan',
+ };
+
+ const actionColors: Record = {
+ create: 'green',
+ update: 'blue',
+ delete: 'red',
+ freeze: 'orange',
+ unfreeze: 'cyan',
+ block: 'orange',
+ unblock: 'cyan',
+ hide: 'orange',
+ unhide: 'cyan',
+ login: 'geekblue',
+ logout: 'default',
+ };
+
+ const entityTypeColors: Record = {
+ user: 'blue',
+ event: 'green',
+ calendar: 'orange',
+ review: 'purple',
+ report: 'red',
+ ticket: 'default',
+ subscription: 'cyan',
+ admin: 'magenta',
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: 'Админ',
+ key: 'admin',
+ render: (_, record) => ,
+ },
+ {
+ title: 'Роль',
+ dataIndex: 'role',
+ key: 'role',
+ width: 120,
+ sorter: true,
+ render: (role: string) => (
+ {role}
+ ),
+ },
+ {
+ title: 'Действие',
+ dataIndex: 'action',
+ key: 'action',
+ width: 120,
+ sorter: true,
+ render: (action: string) => (
+ {action}
+ ),
+ },
+ {
+ title: 'Тип',
+ dataIndex: 'entity_type',
+ key: 'entity_type',
+ width: 120,
+ sorter: true,
+ render: (type: string) => (
+ {type}
+ ),
+ },
+ {
+ title: 'Наименование',
+ key: 'entity_name',
+ render: (_, record) => (
+
+ ),
+ },
+ {
+ title: 'Дата',
+ dataIndex: 'timestamp',
+ key: 'timestamp',
+ sorter: true,
+ },
+ { title: 'IP', dataIndex: 'ip', key: 'ip', width: 130 },
+ {
+ title: 'Причина',
+ dataIndex: 'reason',
+ key: 'reason',
+ ellipsis: true,
+ },
+ ];
+
+ return (
+
+
Аудит
+
+
+
+
+
+
+
+ );
+};
+
+export default AuditPage;
\ No newline at end of file
diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx
new file mode 100644
index 0000000..fea9d0c
--- /dev/null
+++ b/src/pages/auth/LoginPage.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { Form, Input, Button, Card, message } from 'antd';
+import { useNavigate } from 'react-router-dom';
+import { useAuthStore } from '../../store/authStore';
+
+const LoginPage: React.FC = () => {
+ const navigate = useNavigate();
+ const login = useAuthStore((s) => s.login);
+
+ const onFinish = async (values: { email: string; password: string }) => {
+ console.log('Form submitted', values);
+ try {
+ await login(values.email, values.password);
+ navigate('/dashboard');
+ } catch (error) {
+ message.error('Ошибка входа');
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default LoginPage;
\ No newline at end of file
diff --git a/src/pages/banned-words/BannedWordsPage.tsx b/src/pages/banned-words/BannedWordsPage.tsx
new file mode 100644
index 0000000..60589e9
--- /dev/null
+++ b/src/pages/banned-words/BannedWordsPage.tsx
@@ -0,0 +1,133 @@
+import React, { useState } from 'react';
+import { Table, Button, Input, Space, Popconfirm, Tooltip, Spin } from 'antd';
+import { SearchOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Link } from 'react-router-dom';
+import { useBannedWords, useAddBannedWord, useRemoveBannedWord, BannedWordListParams } from '../../hooks/useBannedWords';
+import { useAdmin } from '../../hooks/useAdmins';
+import { BannedWord } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table';
+import dayjs from 'dayjs';
+
+const BannedWordsPage: React.FC = () => {
+ const [params, setParams] = useState({ limit: 20, offset: 0 });
+ const { data, isLoading } = useBannedWords(params);
+ const addWord = useAddBannedWord();
+ const removeWord = useRemoveBannedWord();
+ const [newWord, setNewWord] = useState('');
+
+ const handleAdd = () => {
+ if (newWord.trim()) {
+ addWord.mutate(newWord.trim(), {
+ onSuccess: () => setNewWord(''),
+ });
+ }
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ // Компонент для отображения "Кем добавлено"
+ const AddedByCell: React.FC<{ adminId: string | null }> = ({ adminId }) => {
+ const { data: admin, isLoading: loadingAdmin } = useAdmin(adminId || '');
+ if (!adminId || adminId === '-' || adminId === 'undefined') return -;
+ if (loadingAdmin) return ;
+ if (!admin) return {adminId};
+ const name = admin.nickname && admin.nickname !== '-' ? admin.nickname : admin.email;
+ return {name || admin.id};
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: 'Слово',
+ dataIndex: 'word',
+ key: 'word',
+ sorter: true,
+ },
+ {
+ title: 'Кем добавлено',
+ dataIndex: 'added_by',
+ key: 'added_by',
+ render: (addedBy: string) => ,
+ },
+ {
+ title: 'Дата добавления',
+ dataIndex: 'added_at',
+ key: 'added_at',
+ sorter: true,
+ render: (date: string) => {
+ if (!date || date === '-' || date === 'undefined') return '-';
+ return dayjs(date).format('DD.MM.YYYY HH:mm');
+ },
+ },
+ {
+ title: 'Действия',
+ key: 'actions',
+ width: 80,
+ render: (_, record) => (
+ removeWord.mutate(record.word)}
+ >
+
+ }
+ size="small"
+ danger
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
Бан-слова
+
+ setNewWord(e.target.value)}
+ onPressEnter={handleAdd}
+ style={{ width: 200 }}
+ />
+
+ setParams(prev => ({ ...prev, q: value || undefined, offset: 0 }))}
+ style={{ width: 200 }}
+ allowClear
+ />
+
+
+
+ );
+};
+
+export default BannedWordsPage;
\ No newline at end of file
diff --git a/src/pages/dashboard/DashboardPage.tsx b/src/pages/dashboard/DashboardPage.tsx
new file mode 100644
index 0000000..c2d174b
--- /dev/null
+++ b/src/pages/dashboard/DashboardPage.tsx
@@ -0,0 +1,238 @@
+import React from 'react';
+import { Card, Col, Row, Statistic, Spin, Alert, Table, Tag } from 'antd';
+import {
+ UserOutlined,
+ CalendarOutlined,
+ StarOutlined,
+ TeamOutlined,
+ WarningOutlined,
+ BugOutlined,
+ ClockCircleOutlined,
+} from '@ant-design/icons';
+import { useDashboardStats } from '../../hooks/useDashboard';
+import type { ColumnsType } from 'antd/es/table';
+import { AdminActivity } from '../../types/api';
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Legend,
+ ResponsiveContainer,
+ AreaChart,
+ Area,
+} from 'recharts';
+
+const DashboardPage: React.FC = () => {
+ const { data, isLoading, error } = useDashboardStats();
+
+ if (isLoading) return ;
+ if (error) return ;
+ if (!data) return null;
+
+ const roleColors: Record = {
+ superadmin: 'red',
+ admin: 'blue',
+ moderator: 'purple',
+ support: 'cyan',
+ };
+
+ const adminColumns: ColumnsType = [
+ { title: 'Email', dataIndex: 'email', key: 'email' },
+ { title: 'Ник', dataIndex: 'nickname', key: 'nickname' },
+ {
+ title: 'Роль',
+ dataIndex: 'role',
+ key: 'role',
+ render: (role: string) => (
+ {role}
+ ),
+ },
+ { title: 'Действий', dataIndex: 'actions', key: 'actions' },
+ { title: 'Последний вход', dataIndex: 'last_login', key: 'last_login' },
+ ];
+
+ const eventsChartData = data.events_by_day?.map(item => ({
+ date: item.date,
+ events: item.count,
+ })) || [];
+
+ const registrationsChartData = data.registrations_by_day?.map(item => ({
+ date: item.date,
+ registrations: item.count,
+ })) || [];
+
+ return (
+
+
Общая статистика
+
+ {/* Пользователи с мини-графиком */}
+
+
+
+
}
+ style={{ flex: '0 0 auto', marginRight: 16 }}
+ />
+ {registrationsChartData.length > 0 && (
+
+
+
+
+
+
+
+
+ `Дата: ${label}`} />
+
+
+ )}
+
+
+
+
+ {/* События с мини-графиком */}
+
+
+
+
}
+ style={{ flex: '0 0 auto', marginRight: 16 }}
+ />
+ {eventsChartData.length > 0 && (
+
+
+
+
+
+
+
+
+ `Дата: ${label}`} />
+
+
+ )}
+
+
+
+
+
+
+ } />
+
+
+
+
+
+ } />
+
+
+
+
+
+ } />
+
+
+
+
+ } />
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Активность администраторов
+
+
+ );
+};
+
+export default DashboardPage;
\ No newline at end of file
diff --git a/src/pages/events/EventDetailPage.tsx b/src/pages/events/EventDetailPage.tsx
new file mode 100644
index 0000000..515e4b5
--- /dev/null
+++ b/src/pages/events/EventDetailPage.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Card, Descriptions, Spin, Button, Tag, Space } from 'antd';
+import { Link } from 'react-router-dom';
+import { useEvent } from '../../hooks/useEvents';
+import dayjs from 'dayjs';
+
+const EventDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const { data: event, isLoading } = useEvent(id || '');
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const displayValue = (val: any) => isBadValue(val) ? '-' : val;
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ if (isLoading) return ;
+ if (!event) return Событие не найдено
;
+
+ // Подготовка сложных полей
+ const tagsStr = Array.isArray(event.tags) && event.tags.length > 0 ? event.tags.join(', ') : '-';
+ const recurrenceStr = event.recurrence ? `${(event.recurrence as any).freq || ''} (интервал: ${(event.recurrence as any).interval || ''})` : '-';
+ const locationStr = event.location ? JSON.stringify(event.location) : '-';
+ const attachmentsStr = !isBadValue(event.attachments) ? JSON.stringify(event.attachments) : '-';
+ const editHistoryStr = !isBadValue(event.edit_history) ? JSON.stringify(event.edit_history) : '-';
+
+ // ID-ссылки
+ const specialistLink = !isBadValue(event.specialist_id) ? (
+ {event.specialist_id}
+ ) : '-';
+ const calendarLink = !isBadValue(event.calendar_id) ? (
+ // Пока нет страницы календаря, просто ID, но можно обернуть в ссылку, если появится
+ {event.calendar_id}
+ ) : '-';
+ const masterLink = !isBadValue(event.master_id) ? (
+ {event.master_id}
+ ) : '-';
+
+ return (
+
+
+ {event.id}
+ {event.title}
+ {displayValue(event.description) || '-'}
+
+
+ {event.event_type}
+
+
+
+
+ {event.status}
+
+
+ {displayValue(event.reason)}
+ {formatDate(event.start_time)}
+ {event.duration ?? '-'}
+ {displayValue(event.capacity)}
+ {specialistLink}
+ {calendarLink}
+ {masterLink}
+ {displayValue(event.online_link)}
+ {tagsStr}
+ {event.rating_avg} ({event.rating_count} оценок)
+ {attachmentsStr}
+ {editHistoryStr}
+ {recurrenceStr}
+ {locationStr}
+ {event.is_instance ? 'Да' : 'Нет'}
+ {formatDate(event.created_at)}
+ {formatDate(event.updated_at)}
+
+
+
+ );
+};
+
+export default EventDetailPage;
\ No newline at end of file
diff --git a/src/pages/events/EventListPage.tsx b/src/pages/events/EventListPage.tsx
new file mode 100644
index 0000000..9810c54
--- /dev/null
+++ b/src/pages/events/EventListPage.tsx
@@ -0,0 +1,291 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Button, Tag, Space, Modal, Form, Select, message, Spin, Input, Tooltip, DatePicker, InputNumber } from 'antd';
+import { InfoCircleOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Link, useNavigate } from 'react-router-dom';
+import { useEvents, useUpdateEvent, useDeleteEvent, useEvent } from '../../hooks/useEvents';
+import { Event, EventListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+import dayjs from 'dayjs';
+
+const EventListPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'id', order: 'asc' });
+ const { data, isLoading } = useEvents(params);
+ const updateEvent = useUpdateEvent();
+ const deleteEvent = useDeleteEvent();
+
+ const [editModal, setEditModal] = useState<{ open: boolean; eventId: string | null }>({
+ open: false,
+ eventId: null,
+ });
+ const { data: editingEvent, isLoading: loadingEvent } = useEvent(editModal.eventId || '');
+ const [editForm] = Form.useForm();
+ const [editHasErrors, setEditHasErrors] = useState(true);
+ const originalEventRef = useRef(null);
+
+ const handleEdit = (id: string) => {
+ setEditModal({ open: true, eventId: id });
+ };
+
+ const handleSaveEdit = () => {
+ editForm.validateFields().then((values) => {
+ if (!editModal.eventId || !originalEventRef.current) return;
+ const original = originalEventRef.current;
+ const cleanedValues: Record = {};
+
+ const allKeys = new Set([...Object.keys(values), ...Object.keys(original)]);
+ allKeys.forEach((key) => {
+ const newVal = values[key];
+ if (newVal === '' || newVal === undefined || newVal === null) {
+ const origVal = (original as any)[key];
+ if (origVal && origVal !== '-' && origVal !== '' && origVal !== undefined && origVal !== null) {
+ cleanedValues[key] = null;
+ }
+ return;
+ }
+ if (newVal === '-') return;
+ if (dayjs.isDayjs(newVal)) {
+ cleanedValues[key] = newVal.toISOString();
+ } else {
+ cleanedValues[key] = newVal;
+ }
+ });
+
+ const payload = Object.fromEntries(
+ Object.entries(cleanedValues).filter(([key, v]) => {
+ const origVal = (original as any)[key];
+ return JSON.stringify(v) !== JSON.stringify(origVal === '-' ? undefined : origVal);
+ })
+ );
+
+ updateEvent.mutate(
+ { id: editModal.eventId, data: payload },
+ { onSuccess: () => setEditModal({ open: false, eventId: null }) }
+ );
+ });
+ };
+
+ useEffect(() => {
+ if (editingEvent && editModal.open) {
+ originalEventRef.current = editingEvent;
+ const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
+ setTimeout(() => {
+ editForm.setFieldsValue({
+ title: clean(editingEvent.title),
+ description: clean(editingEvent.description),
+ event_type: clean(editingEvent.event_type),
+ status: clean(editingEvent.status),
+ start_time: clean(editingEvent.start_time) ? dayjs(editingEvent.start_time) : null,
+ duration: editingEvent.duration,
+ capacity: editingEvent.capacity,
+ specialist_id: clean(editingEvent.specialist_id),
+ calendar_id: clean(editingEvent.calendar_id),
+ online_link: clean(editingEvent.online_link),
+ tags: editingEvent.tags,
+ });
+ validateEditForm();
+ }, 0);
+ }
+ }, [editingEvent, editModal.open, editForm]);
+
+ const validateEditForm = () => {
+ const title = editForm.getFieldValue('title');
+ const status = editForm.getFieldValue('status');
+ setEditHasErrors(!title || !status);
+ };
+
+ const handleDelete = (id: string) => {
+ Modal.confirm({
+ title: 'Удалить событие?',
+ okText: 'Удалить',
+ okType: 'danger',
+ cancelText: 'Отмена',
+ onOk: () => deleteEvent.mutate(id),
+ });
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const typeColors: Record = {
+ single: 'blue',
+ recurring: 'purple',
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: ,
+ key: 'detail',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ type="link"
+ onClick={() => navigate(`/events/${record.id}`)}
+ />
+
+ ),
+ },
+ {
+ title: 'Название',
+ dataIndex: 'title',
+ key: 'title',
+ ellipsis: true,
+ sorter: true,
+ },
+ {
+ title: 'Тип',
+ dataIndex: 'event_type',
+ key: 'event_type',
+ width: 100,
+ sorter: true,
+ render: (type: string) => (
+ {type}
+ ),
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => (
+
+ {status}
+
+ ),
+ },
+ {
+ title: 'Начало',
+ dataIndex: 'start_time',
+ key: 'start_time',
+ width: 130,
+ sorter: true,
+ render: (time: string) => time ? dayjs(time).format('DD.MM.YYYY HH:mm') : '-',
+ },
+ {
+ title: 'Рейтинг',
+ key: 'rating',
+ width: 90,
+ render: (_, record) => `${record.rating_avg} (${record.rating_count})`,
+ },
+ {
+ title: 'Действия',
+ key: 'actions',
+ width: 100,
+ render: (_, record) => (
+
+
+ }
+ size="small"
+ onClick={() => handleEdit(record.id)}
+ />
+
+
+ }
+ size="small"
+ danger
+ onClick={() => handleDelete(record.id)}
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+ );
+};
+
+export default EventListPage;
\ No newline at end of file
diff --git a/src/pages/profile/ProfilePage.tsx b/src/pages/profile/ProfilePage.tsx
new file mode 100644
index 0000000..3d9b7eb
--- /dev/null
+++ b/src/pages/profile/ProfilePage.tsx
@@ -0,0 +1,201 @@
+import React, { useState } from 'react';
+import { Card, Descriptions, Spin, Button, Form, Input, Select, Tag, message, Space, Avatar } from 'antd';
+import { UserOutlined } from '@ant-design/icons';
+import { useAuthStore } from '../../store/authStore';
+import { useUpdateAdmin } from '../../hooks/useAdmins';
+import dayjs from 'dayjs';
+
+const ProfilePage: React.FC = () => {
+ const { user } = useAuthStore();
+ const updateAdmin = useUpdateAdmin();
+ const [editing, setEditing] = useState(false);
+ const [form] = Form.useForm();
+
+ // Проверка, является ли текущий пользователь superadmin
+ const isSuperadmin = user?.role === 'superadmin';
+
+ // Заполняем форму при входе в режим редактирования
+ const startEditing = () => {
+ if (!user) return;
+ const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
+ form.setFieldsValue({
+ nickname: clean(user.nickname),
+ email: clean(user.email),
+ timezone: clean(user.timezone),
+ language: clean(user.language),
+ phone: clean(user.phone),
+ avatar_url: clean(user.avatar_url),
+ preferences: user.preferences && !isBadValue(user.preferences) ? JSON.stringify(user.preferences) : '',
+ });
+ setEditing(true);
+ };
+
+ const handleSave = () => {
+ if (!user || !isSuperadmin) return;
+ form.validateFields().then((values) => {
+ const payload = Object.fromEntries(
+ Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null)
+ );
+ if (payload.preferences) {
+ try {
+ payload.preferences = JSON.parse(payload.preferences);
+ } catch {
+ message.error('Поле «Настройки» должно быть валидным JSON');
+ return;
+ }
+ }
+ updateAdmin.mutate(
+ { id: user.id, data: payload },
+ {
+ onSuccess: () => {
+ message.success('Профиль обновлён');
+ setEditing(false);
+ },
+ }
+ );
+ });
+ };
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ if (!user) return ;
+
+ return (
+
+ {!editing ? (
+ <>
+
+
} src={isBadValue(user.avatar_url) ? undefined : user.avatar_url} />
+
+
{!isBadValue(user.nickname) ? user.nickname : user.email}
+
+ {user.role}
+
+
+
+
+ {user.email}
+ {isBadValue(user.nickname) ? '-' : user.nickname}
+
+
+ {user.role}
+
+
+
+ {user.status}
+
+ {isBadValue(user.timezone) ? '-' : user.timezone}
+ {isBadValue(user.language) ? '-' : user.language}
+ {isBadValue(user.phone) ? '-' : user.phone}
+
+ {isBadValue(user.avatar_url) ? '-' : {user.avatar_url}}
+
+
+ {isBadValue(user.preferences) ? '-' : {JSON.stringify(user.preferences, null, 2)}}
+
+ {formatDate(user.last_login)}
+
+ {isSuperadmin && (
+
+ )}
+ >
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ if (value && value.trim().length > 0) {
+ try {
+ JSON.parse(value);
+ } catch {
+ return Promise.reject(new Error('Невалидный JSON'));
+ }
+ }
+ return Promise.resolve();
+ },
+ },
+ ]}
+ >
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ProfilePage;
\ No newline at end of file
diff --git a/src/pages/reports/ReportDetailPage.tsx b/src/pages/reports/ReportDetailPage.tsx
new file mode 100644
index 0000000..1536a0b
--- /dev/null
+++ b/src/pages/reports/ReportDetailPage.tsx
@@ -0,0 +1,98 @@
+import React from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Card, Descriptions, Spin, Button, Tag, Space, Modal } from 'antd';
+import { Link } from 'react-router-dom';
+import { useReport, useUpdateReport } from '../../hooks/useReports';
+import { useUser } from '../../hooks/useUsers';
+import { useEvent } from '../../hooks/useEvents';
+import { useAdmin } from '../../hooks/useAdmins';
+
+const ReportDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const { data: report, isLoading: reportLoading } = useReport(id || '');
+ const updateReport = useUpdateReport();
+
+ const reporterId = report?.reporter_id;
+ const resolvedById = report?.resolved_by;
+ const targetType = report?.target_type;
+ const targetId = report?.target_id;
+
+ const { data: reporter, isLoading: loadingReporter } = useUser(reporterId || '');
+ const { data: resolvedAdmin, isLoading: loadingResolver } = useAdmin(resolvedById || '');
+ const { data: targetEvent, isLoading: loadingEvent } = useEvent(targetId || '');
+
+ const handleStatusChange = (status: 'reviewed' | 'dismissed') => {
+ Modal.confirm({
+ title: `Отметить как «${status === 'reviewed' ? 'Рассмотрено' : 'Отклонено'}»?`,
+ okText: 'Да',
+ cancelText: 'Отмена',
+ onOk: () => updateReport.mutate({ id: id!, data: { status } }),
+ });
+ };
+
+ const isBadValue = (val: any) => val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const getLink = (entity: any, type: 'user' | 'admin' | 'event') => {
+ if (!entity) return ;
+ let name = '';
+ if (type === 'event') {
+ name = !isBadValue(entity.title) ? entity.title : entity.id;
+ } else {
+ const nick = entity.nickname;
+ const email = entity.email;
+ if (!isBadValue(nick)) name = nick;
+ else if (!isBadValue(email)) name = email;
+ else name = entity.id;
+ }
+ const to = type === 'user' ? `/users/${entity.id}` : type === 'admin' ? `/admins/${entity.id}` : `/events/${entity.id}`;
+ return {name};
+ };
+
+ const displayId = (val?: string | null) => (!val || isBadValue(val)) ? '-' : val;
+ const isValidId = (val?: string | null) => val && !isBadValue(val);
+
+ if (reportLoading) return ;
+ if (!report) return Жалоба не найдена
;
+
+ return (
+
+
+ {report.id}
+
+ {loadingReporter && isValidId(reporterId) ? : (
+ reporter ? getLink(reporter, 'user') : displayId(reporterId)
+ )}
+
+ {report.target_type}
+
+ {targetType === 'event' && loadingEvent && isValidId(targetId) ? : (
+ targetType === 'event' && targetEvent ? getLink(targetEvent, 'event') : displayId(targetId)
+ )}
+
+ {report.reason}
+
+
+ {report.status}
+
+
+ {report.created_at}
+ {report.resolved_at || '-'}
+
+ {loadingResolver && isValidId(resolvedById) ? : (
+ resolvedAdmin ? getLink(resolvedAdmin, 'admin') : displayId(resolvedById)
+ )}
+
+
+ {report.status === 'pending' && (
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default ReportDetailPage;
\ No newline at end of file
diff --git a/src/pages/reports/ReportListPage.tsx b/src/pages/reports/ReportListPage.tsx
new file mode 100644
index 0000000..7c498e2
--- /dev/null
+++ b/src/pages/reports/ReportListPage.tsx
@@ -0,0 +1,293 @@
+import React, { useState } from 'react';
+import { Table, Button, Tag, Modal, Descriptions, Tooltip, Spin, Space } from 'antd';
+import { InfoCircleOutlined, EyeOutlined } from '@ant-design/icons';
+import { Link, useNavigate } from 'react-router-dom';
+import { useReports, useUpdateReport } from '../../hooks/useReports';
+import { useUser } from '../../hooks/useUsers';
+import { useEvent } from '../../hooks/useEvents';
+import { useAdmin } from '../../hooks/useAdmins';
+import { Report, ReportListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+
+const ReportListPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'created_at', order: 'desc' });
+ const { data, isLoading } = useReports(params);
+ const updateReport = useUpdateReport();
+
+ const [detailModal, setDetailModal] = useState<{ open: boolean; report: Report | null }>({
+ open: false,
+ report: null,
+ });
+
+ const reporterId = detailModal.report?.reporter_id;
+ const resolvedById = detailModal.report?.resolved_by;
+ const targetType = detailModal.report?.target_type;
+ const targetId = detailModal.report?.target_id;
+
+ const { data: reporter, isLoading: loadingReporter } = useUser(reporterId || '');
+ const { data: resolvedAdmin, isLoading: loadingResolver } = useAdmin(resolvedById || '');
+ const { data: targetEvent, isLoading: loadingEvent } = useEvent(targetId || '');
+
+ const handleViewDetails = (report: Report) => {
+ setDetailModal({ open: true, report });
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ // Цвета для типа цели
+ const targetTypeColors: Record = {
+ event: 'blue',
+ calendar: 'green',
+ review: 'purple',
+ };
+
+ // Резолвер отправителя
+ const ReporterCell: React.FC<{ reporterId: string }> = ({ reporterId }) => {
+ const { data: user, isLoading: loading } = useUser(reporterId);
+ if (loading) return ;
+ if (!user) return {reporterId};
+ // Используем ту же логику, что и в renderLink
+ const nick = user.nickname;
+ const email = user.email;
+ let name = '';
+ if (!isBadValue(nick)) {
+ name = nick;
+ } else if (!isBadValue(email)) {
+ name = email;
+ } else {
+ name = user.id;
+ }
+ return {name};
+ };
+
+ // Резолвер цели
+ const TargetCell: React.FC<{ targetType: string; targetId: string }> = ({ targetType, targetId }) => {
+ const { data: event, isLoading: loading } = useEvent(targetType === 'event' ? targetId : '');
+ if (targetType === 'event') {
+ if (loading) return ;
+ if (event) {
+ const name = event.title || event.id;
+ return {name};
+ }
+ return {targetId};
+ }
+ return {targetId};
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: ,
+ key: 'detail',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ type="link"
+ onClick={() => navigate(`/reports/${record.id}`)}
+ />
+
+ ),
+ },
+ {
+ title: 'Отправитель',
+ key: 'reporter',
+ ellipsis: true,
+ render: (_, record) => ,
+ },
+ {
+ title: 'Тип',
+ dataIndex: 'target_type',
+ key: 'target_type',
+ width: 100,
+ sorter: true,
+ render: (type: string) => (
+ {type}
+ ),
+ },
+ {
+ title: 'Цель',
+ key: 'target',
+ ellipsis: true,
+ render: (_, record) => ,
+ },
+ {
+ title: 'Причина',
+ dataIndex: 'reason',
+ key: 'reason',
+ ellipsis: true,
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => (
+
+ {status}
+
+ ),
+ },
+ {
+ title: 'Создано',
+ dataIndex: 'created_at',
+ key: 'created_at',
+ width: 120,
+ sorter: true,
+ },
+ {
+ title: ,
+ key: 'view',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ onClick={() => handleViewDetails(record)}
+ />
+
+ ),
+ },
+ ];
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const renderLink = (entity: any, type: 'user' | 'admin' | 'event') => {
+ if (!entity) return ;
+ let name = '';
+ if (type === 'event') {
+ name = !isBadValue(entity.title) ? entity.title : entity.id;
+ } else {
+ const nick = entity.nickname;
+ const email = entity.email;
+ if (!isBadValue(nick)) {
+ name = nick;
+ } else if (!isBadValue(email)) {
+ name = email;
+ } else {
+ name = entity.id;
+ }
+ }
+ const to =
+ type === 'user' ? `/users/${entity.id}` :
+ type === 'admin' ? `/admins/${entity.id}` :
+ `/events/${entity.id}`;
+ return {name};
+ };
+
+ const displayId = (id?: string | null) => {
+ if (!id || isBadValue(id)) return '-';
+ return id;
+ };
+
+ const isValidId = (id?: string | null) => id && !isBadValue(id);
+
+ return (
+
+
Жалобы
+
+
+
setDetailModal({ open: false, report: null })}
+ footer={null}
+ width={600}
+ >
+ {detailModal.report && (
+ <>
+
+ {detailModal.report.id}
+
+ {loadingReporter && isValidId(reporterId) ? : (
+ reporter ? renderLink(reporter, 'user') : displayId(reporterId)
+ )}
+
+ {detailModal.report.target_type}
+
+ {targetType === 'event' && loadingEvent && isValidId(targetId) ? : (
+ targetType === 'event' && targetEvent ? renderLink(targetEvent, 'event') : displayId(targetId)
+ )}
+
+ {detailModal.report.reason}
+
+
+ {detailModal.report.status}
+
+
+ {detailModal.report.created_at}
+ {detailModal.report.resolved_at || '-'}
+
+ {loadingResolver && isValidId(resolvedById) ? : (
+ resolvedAdmin ? renderLink(resolvedAdmin, 'admin') : displayId(resolvedById)
+ )}
+
+
+ {detailModal.report.status === 'pending' && (
+
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+};
+
+export default ReportListPage;
\ No newline at end of file
diff --git a/src/pages/reviews/ReviewDetailPage.tsx b/src/pages/reviews/ReviewDetailPage.tsx
new file mode 100644
index 0000000..356f88d
--- /dev/null
+++ b/src/pages/reviews/ReviewDetailPage.tsx
@@ -0,0 +1,144 @@
+import React, { useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Card, Descriptions, Spin, Button, Tag, Space, Modal, Form, Select, Input, message } from 'antd';
+import { Link } from 'react-router-dom';
+import { useReview, useUpdateReview } from '../../hooks/useReviews';
+import { useUser } from '../../hooks/useUsers';
+import { useEvent } from '../../hooks/useEvents';
+import dayjs from 'dayjs';
+
+const ReviewDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const { data: review, isLoading: reviewLoading } = useReview(id || '');
+ const updateReview = useUpdateReview();
+
+ const userId = review?.user_id;
+ const targetType = review?.target_type;
+ const targetId = review?.target_id;
+
+ const { data: user, isLoading: loadingUser } = useUser(userId || '');
+ const { data: targetEvent, isLoading: loadingEvent } = useEvent(targetType === 'event' ? targetId || '' : '');
+
+ const [statusModal, setStatusModal] = useState<{
+ open: boolean;
+ newStatus: string;
+ }>({ open: false, newStatus: 'visible' });
+ const [statusForm] = Form.useForm();
+
+ const isBadValue = (val: any) => val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+ const displayValue = (val: any) => isBadValue(val) ? '-' : val;
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ const getUserLink = () => {
+ if (loadingUser) return ;
+ if (!user) return displayValue(userId);
+ const name = !isBadValue(user.nickname) ? user.nickname : user.email || user.id;
+ return {name};
+ };
+
+ const getTargetLink = () => {
+ if (targetType === 'event') {
+ if (loadingEvent) return ;
+ if (targetEvent) {
+ const name = targetEvent.title || targetEvent.id;
+ return {name};
+ }
+ return displayValue(targetId);
+ }
+ return displayValue(targetId);
+ };
+
+ // Открытие модалки смены статуса
+ const openStatusModal = (newStatus: string) => {
+ setStatusModal({ open: true, newStatus });
+ statusForm.resetFields();
+ // Предзаполняем причину, если была
+ const currentReason = review?.reason && !isBadValue(review.reason) ? review.reason : '';
+ statusForm.setFieldsValue({ reason: currentReason });
+ };
+
+ const handleStatusSubmit = () => {
+ statusForm.validateFields().then((values) => {
+ if (!review) return;
+ const payload: any = { status: statusModal.newStatus };
+ if (values.reason && values.reason.trim() !== '') {
+ payload.reason = values.reason;
+ }
+ updateReview.mutate(
+ { id: review.id, data: payload },
+ {
+ onSuccess: () => {
+ setStatusModal({ open: false, newStatus: 'visible' });
+ message.success('Статус обновлён');
+ }
+ }
+ );
+ });
+ };
+
+ if (reviewLoading) return ;
+ if (!review) return Отзыв не найден
;
+
+ return (
+
+
+ {review.id}
+ {getUserLink()}
+
+ {review.target_type}
+
+ {getTargetLink()}
+ {'⭐'.repeat(review.rating)} ({review.rating})
+ {displayValue(review.comment)}
+
+
+ {review.status}
+
+
+ {displayValue(review.reason)}
+ {isBadValue(review.likes) ? 0 : review.likes}
+ {isBadValue(review.dislikes) ? 0 : review.dislikes}
+ {formatDate(review.created_at)}
+ {formatDate(review.updated_at)}
+
+
+
+
+
+
+
+
+ setStatusModal({ open: false, newStatus: 'visible' })}
+ onOk={handleStatusSubmit}
+ confirmLoading={updateReview.isPending}
+ destroyOnHidden
+ >
+
+
+
+
+
+
+ );
+};
+
+export default ReviewDetailPage;
\ No newline at end of file
diff --git a/src/pages/reviews/ReviewListPage.tsx b/src/pages/reviews/ReviewListPage.tsx
new file mode 100644
index 0000000..a3d7686
--- /dev/null
+++ b/src/pages/reviews/ReviewListPage.tsx
@@ -0,0 +1,345 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, Tag, Space, Modal, Form, Select, InputNumber, message, Tooltip, Input, Spin } from 'antd';
+import { InfoCircleOutlined, EditOutlined } from '@ant-design/icons';
+import { Link, useNavigate } from 'react-router-dom';
+import { useReviews, useUpdateReview, useBulkUpdateReviews, useReview } from '../../hooks/useReviews';
+import { useUser } from '../../hooks/useUsers';
+import { useEvent } from '../../hooks/useEvents';
+import { Review, ReviewListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+
+const ReviewListPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'created_at', order: 'desc' });
+ const { data, isLoading } = useReviews(params);
+ const updateReview = useUpdateReview();
+ const bulkUpdate = useBulkUpdateReviews();
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+
+ // Модальное окно для индивидуального редактирования
+ const [editModal, setEditModal] = useState<{ open: boolean; reviewId: string | null }>({
+ open: false,
+ reviewId: null,
+ });
+ const [editForm] = Form.useForm();
+ const { data: editingReview, isLoading: loadingReview } = useReview(editModal.reviewId || '');
+
+ // Модальное окно для массового изменения статуса с причиной
+ const [bulkStatusModal, setBulkStatusModal] = useState<{
+ open: boolean;
+ status: string;
+ }>({ open: false, status: 'hidden' });
+ const [bulkStatusForm] = Form.useForm();
+
+ // ---- Индивидуальное редактирование ----
+ const handleEdit = (id: string) => {
+ setEditModal({ open: true, reviewId: id });
+ };
+
+ const handleSaveEdit = () => {
+ editForm.validateFields().then((values) => {
+ if (!editModal.reviewId) return;
+ const payload = Object.fromEntries(
+ Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null)
+ );
+ updateReview.mutate(
+ { id: editModal.reviewId, data: payload },
+ { onSuccess: () => setEditModal({ open: false, reviewId: null }) }
+ );
+ });
+ };
+
+ useEffect(() => {
+ if (editingReview && editModal.open) {
+ setTimeout(() => {
+ editForm.setFieldsValue({
+ status: editingReview.status,
+ reason: editingReview.reason,
+ comment: editingReview.comment,
+ rating: editingReview.rating,
+ });
+ }, 0);
+ }
+ }, [editingReview, editModal.open, editForm]);
+
+ // ---- Массовое изменение статуса ----
+ const openBulkStatusModal = (status: string) => {
+ if (selectedRowKeys.length === 0) {
+ message.warning('Выберите отзывы');
+ return;
+ }
+ setBulkStatusModal({ open: true, status });
+ bulkStatusForm.resetFields();
+ };
+
+ const handleBulkStatusSubmit = () => {
+ bulkStatusForm.validateFields().then((values) => {
+ const reason = values.reason ? values.reason.trim() : '';
+ const updates = selectedRowKeys.map(id => ({
+ id: id as string,
+ status: bulkStatusModal.status,
+ reason: reason || undefined, // если пусто, не передаем
+ }));
+ bulkUpdate.mutate(updates, {
+ onSuccess: () => {
+ setBulkStatusModal({ open: false, status: 'hidden' });
+ setSelectedRowKeys([]);
+ },
+ });
+ });
+ };
+
+ // ---- Таблица ----
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const isBadValue = (val: any) => val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const UserCell: React.FC<{ userId: string }> = ({ userId }) => {
+ const { data: user, isLoading: loading } = useUser(userId);
+ if (loading) return ;
+ if (!user) return {userId};
+ const nick = user.nickname;
+ const email = user.email;
+ let name = '';
+ if (!isBadValue(nick)) {
+ name = nick;
+ } else if (!isBadValue(email)) {
+ name = email;
+ } else {
+ name = user.id;
+ }
+ return {name};
+ };
+
+ const TargetCell: React.FC<{ targetType: string; targetId: string }> = ({ targetType, targetId }) => {
+ const { data: event, isLoading: loading } = useEvent(targetType === 'event' ? targetId : '');
+ if (targetType === 'event') {
+ if (loading) return ;
+ if (event) {
+ const name = event.title || event.id;
+ return {name};
+ }
+ return {targetId};
+ }
+ return {targetId};
+ };
+
+ const typeColors: Record = {
+ calendar: 'green',
+ event: 'blue',
+ review: 'purple',
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: ,
+ key: 'detail',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ type="link"
+ onClick={() => navigate(`/reviews/${record.id}`)}
+ />
+
+ ),
+ },
+ {
+ title: 'Пользователь',
+ key: 'user',
+ ellipsis: true,
+ render: (_, record) => ,
+ },
+ {
+ title: 'Тип',
+ dataIndex: 'target_type',
+ key: 'target_type',
+ width: 100,
+ sorter: true,
+ render: (type: string) => (
+ {type}
+ ),
+ },
+ {
+ title: 'Цель',
+ key: 'target',
+ ellipsis: true,
+ render: (_, record) => ,
+ },
+ {
+ title: 'Оценка',
+ dataIndex: 'rating',
+ key: 'rating',
+ width: 140,
+ sorter: true,
+ render: (rating: number) => '⭐'.repeat(rating),
+ },
+ {
+ title: 'Комментарий',
+ dataIndex: 'comment',
+ key: 'comment',
+ ellipsis: true,
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => (
+
+ {status}
+
+ ),
+ },
+ {
+ title: 'Лайки / Дизлайки',
+ key: 'likes',
+ width: 120,
+ render: (_, record) => {
+ const likes = isBadValue(record.likes) ? 0 : record.likes;
+ const dislikes = isBadValue(record.dislikes) ? 0 : record.dislikes;
+ return `${likes} / ${dislikes}`;
+ },
+ },
+ {
+ title: '',
+ key: 'actions',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ onClick={() => handleEdit(record.id)}
+ />
+
+ ),
+ },
+ ];
+
+ return (
+
+
Отзывы
+
+
+
+
+
+
setSelectedRowKeys(keys),
+ }}
+ columns={columns}
+ dataSource={data?.data}
+ rowKey="id"
+ loading={isLoading}
+ onChange={handleTableChange}
+ pagination={{
+ total: data?.total,
+ current: (params.offset || 0) / (params.limit || 20) + 1,
+ pageSize: params.limit || 20,
+ showSizeChanger: false,
+ }}
+ />
+
+ {/* Модальное окно индивидуального редактирования */}
+ setEditModal({ open: false, reviewId: null })}
+ onOk={handleSaveEdit}
+ confirmLoading={updateReview.isPending}
+ destroyOnHidden
+ >
+ {loadingReview ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value) {
+ const status = getFieldValue('status');
+ if ((status === 'hidden' || status === 'deleted') && (!value || value.trim() === '')) {
+ return Promise.reject(new Error('Укажите причину изменения статуса'));
+ }
+ return Promise.resolve();
+ },
+ }),
+ ]}
+ >
+
+
+
+ )}
+
+
+ {/* Модальное окно для массового изменения с причиной */}
+ setBulkStatusModal({ open: false, status: 'hidden' })}
+ onOk={handleBulkStatusSubmit}
+ confirmLoading={bulkUpdate.isPending}
+ destroyOnHidden
+ >
+
+
+
+
+
+
+ );
+};
+
+export default ReviewListPage;
\ No newline at end of file
diff --git a/src/pages/subscriptions/SubscriptionListPage.tsx b/src/pages/subscriptions/SubscriptionListPage.tsx
new file mode 100644
index 0000000..4723b5a
--- /dev/null
+++ b/src/pages/subscriptions/SubscriptionListPage.tsx
@@ -0,0 +1,263 @@
+import React, { useState, useEffect } from 'react';
+import { Table, Button, Tag, Space, Modal, Form, Select, DatePicker, Tooltip, Spin } from 'antd';
+import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Link } from 'react-router-dom';
+import { useSubscriptions, useUpdateSubscription, useDeleteSubscription, useSubscription } from '../../hooks/useSubscriptions';
+import { useUser } from '../../hooks/useUsers';
+import { Subscription, SubscriptionListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+import dayjs from 'dayjs';
+
+const SubscriptionListPage: React.FC = () => {
+ const [params, setParams] = useState({ limit: 20, offset: 0 });
+ const { data, isLoading } = useSubscriptions(params);
+ const updateSubscription = useUpdateSubscription();
+ const deleteSubscription = useDeleteSubscription();
+
+ const [editModal, setEditModal] = useState<{ open: boolean; subscriptionId: string | null }>({
+ open: false,
+ subscriptionId: null,
+ });
+ const { data: editingSubscription, isLoading: loadingSubscription } = useSubscription(editModal.subscriptionId || '');
+ const [form] = Form.useForm();
+
+ // Заполняем форму с задержкой, когда данные загружены
+ useEffect(() => {
+ if (editingSubscription && editModal.open) {
+ setTimeout(() => {
+ form.setFieldsValue({
+ plan: editingSubscription.plan,
+ status: editingSubscription.status,
+ trial_used: editingSubscription.trial_used,
+ expires_at: editingSubscription.expires_at ? dayjs(editingSubscription.expires_at) : null,
+ });
+ }, 0);
+ }
+ }, [editingSubscription, editModal.open, form]);
+
+ const handleEdit = (sub: Subscription) => {
+ // Сбрасываем форму перед открытием
+ form.resetFields();
+ setEditModal({ open: true, subscriptionId: sub.id });
+ };
+
+ const handleSave = () => {
+ form.validateFields().then((values) => {
+ if (!editModal.subscriptionId) return;
+ const payload = Object.fromEntries(
+ Object.entries(values)
+ .filter(([_, v]) => v !== '' && v !== undefined && v !== null)
+ .map(([key, val]) => {
+ if (dayjs.isDayjs(val)) return [key, val.toISOString()];
+ return [key, val];
+ })
+ );
+ updateSubscription.mutate(
+ { id: editModal.subscriptionId, data: payload },
+ { onSuccess: () => setEditModal({ open: false, subscriptionId: null }) }
+ );
+ });
+ };
+
+ const handleDelete = (id: string) => {
+ Modal.confirm({
+ title: 'Удалить подписку?',
+ content: 'Это действие нельзя отменить.',
+ okText: 'Удалить',
+ okType: 'danger',
+ cancelText: 'Отмена',
+ onOk: () => deleteSubscription.mutate(id),
+ });
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const isBadValue = (val: any) => val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ // Компонент для резолвинга пользователя
+ const UserCell: React.FC<{ userId: string }> = ({ userId }) => {
+ const { data: user, isLoading: loading } = useUser(userId);
+ if (loading) return ;
+ if (!user) return {userId};
+ const name = !isBadValue(user.nickname) ? user.nickname : user.email;
+ return {!isBadValue(name) ? name : user.id};
+ };
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ const planColors: Record = {
+ trial: 'cyan',
+ monthly: 'blue',
+ quarterly: 'green',
+ biannual: 'purple',
+ annual: 'orange',
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: 'Пользователь',
+ key: 'user',
+ ellipsis: true,
+ render: (_, record) => ,
+ },
+ {
+ title: 'План',
+ dataIndex: 'plan',
+ key: 'plan',
+ width: 100,
+ sorter: true,
+ render: (plan: string) => (
+ {plan}
+ ),
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => {
+ const color =
+ status === 'active' ? 'green' :
+ status === 'expired' ? 'orange' : 'red';
+ return {status};
+ },
+ },
+ {
+ title: 'Пробный',
+ dataIndex: 'trial_used',
+ key: 'trial_used',
+ width: 100,
+ render: (v: boolean) => (v ? 'Да' : 'Нет'),
+ },
+ { title: 'Начало', dataIndex: 'started_at', key: 'started_at', render: (val) => formatDate(val) },
+ { title: 'Окончание', dataIndex: 'expires_at', key: 'expires_at', render: (val) => formatDate(val) },
+ {
+ title: 'Действия',
+ key: 'actions',
+ width: 100,
+ render: (_, record) => (
+
+
+ }
+ size="small"
+ onClick={() => handleEdit(record)}
+ />
+
+
+ }
+ size="small"
+ danger
+ onClick={() => handleDelete(record.id)}
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
Подписки
+
+
+
+
+
+
+
setEditModal({ open: false, subscriptionId: null })}
+ onOk={handleSave}
+ confirmLoading={updateSubscription.isPending}
+ destroyOnHidden
+ >
+ {loadingSubscription ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default SubscriptionListPage;
\ No newline at end of file
diff --git a/src/pages/tickets/TicketDetailPage.tsx b/src/pages/tickets/TicketDetailPage.tsx
new file mode 100644
index 0000000..f8fd3ca
--- /dev/null
+++ b/src/pages/tickets/TicketDetailPage.tsx
@@ -0,0 +1,150 @@
+import React, { useEffect, useState } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Card, Descriptions, Spin, Button, Tag, Space, Form, Select, Input, message } from 'antd';
+import { Link } from 'react-router-dom';
+import { useTicket, useUpdateTicket } from '../../hooks/useTickets';
+import { useUser } from '../../hooks/useUsers';
+import { useAdmin, useAdmins } from '../../hooks/useAdmins';
+import dayjs from 'dayjs';
+
+const TicketDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const { data: ticket, isLoading } = useTicket(id || '');
+ const updateTicket = useUpdateTicket();
+ const [form] = Form.useForm();
+
+ const { data: admins, isLoading: loadingAdmins } = useAdmins({ limit: 1000, offset: 0 });
+
+ const reporterId = ticket?.reporter_id;
+ const { data: reporter, isLoading: loadingReporter } = useUser(reporterId || '');
+
+ const assignedId = ticket?.assigned_to;
+ const { data: assignedAdmin, isLoading: loadingAssigned } = useAdmin(assignedId || '');
+
+ useEffect(() => {
+ if (ticket) {
+ form.setFieldsValue({
+ status: ticket.status,
+ assigned_to: !isBadValue(ticket.assigned_to) ? ticket.assigned_to : undefined,
+ resolution_note: !isBadValue(ticket.resolution_note) ? ticket.resolution_note : '',
+ });
+ }
+ }, [ticket, form]);
+
+ const handleSave = () => {
+ form.validateFields().then((values) => {
+ const payload = Object.fromEntries(
+ Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null)
+ );
+ updateTicket.mutate(
+ { id: id!, data: payload },
+ { onSuccess: () => navigate('/tickets') }
+ );
+ });
+ };
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ const getUserLink = () => {
+ if (loadingReporter) return ;
+ if (!reporter) return reporterId || '-';
+ const name = reporter.nickname && !isBadValue(reporter.nickname)
+ ? reporter.nickname
+ : reporter.email;
+ return {name || reporter.id};
+ };
+
+ if (isLoading) return ;
+ if (!ticket) return Тикет не найден
;
+
+ return (
+
+
+ {ticket.id}
+ {getUserLink()}
+ {ticket.error_hash}
+ {ticket.error_message}
+
+
+ {isBadValue(ticket.stacktrace) ? '-' : ticket.stacktrace}
+
+
+
+ {isBadValue(ticket.context) ? '-' : ticket.context}
+
+ {ticket.count}
+ {formatDate(ticket.first_seen)}
+ {formatDate(ticket.last_seen)}
+
+
+ {ticket.status}
+
+
+
+ {loadingAssigned ? : (
+ assignedAdmin ? (
+
+ {!isBadValue(assignedAdmin.nickname) ? assignedAdmin.nickname : assignedAdmin.email || assignedAdmin.id}
+
+ ) : (isBadValue(ticket.assigned_to) ? '-' : ticket.assigned_to)
+ )}
+
+
+ {isBadValue(ticket.resolution_note) ? '-' : ticket.resolution_note}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default TicketDetailPage;
\ No newline at end of file
diff --git a/src/pages/tickets/TicketListPage.tsx b/src/pages/tickets/TicketListPage.tsx
new file mode 100644
index 0000000..b742009
--- /dev/null
+++ b/src/pages/tickets/TicketListPage.tsx
@@ -0,0 +1,158 @@
+import React, { useState } from 'react';
+import { Table, Button, Tag, Space, Popconfirm, Tooltip, Spin, Card, Col, Row, Statistic } from 'antd';
+import { InfoCircleOutlined, DeleteOutlined, BugOutlined, ClockCircleOutlined, CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
+import { Link, useNavigate } from 'react-router-dom';
+import { useTickets, useDeleteTicket, useTicketStats } from '../../hooks/useTickets';
+import { useAdmin } from '../../hooks/useAdmins';
+import { Ticket, TicketListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+
+const TicketListPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'last_seen', order: 'desc' });
+ const { data, isLoading } = useTickets(params);
+ const deleteTicket = useDeleteTicket();
+ const { data: stats } = useTicketStats();
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ // Резолвер администратора для колонки "Назначен"
+ const AssignedCell: React.FC<{ adminId: string | null }> = ({ adminId }) => {
+ const { data: admin, isLoading: loading } = useAdmin(adminId || '');
+ if (!adminId || adminId === '-' || adminId === 'undefined') return -;
+ if (loading) return ;
+ if (!admin) return {adminId};
+ const name = !isBadValue(admin.nickname) ? admin.nickname : admin.email;
+ return {!isBadValue(name) ? name : admin.id};
+ };
+
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: ,
+ key: 'detail',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ type="link"
+ onClick={() => navigate(`/tickets/${record.id}`)}
+ />
+
+ ),
+ },
+ {
+ title: 'Ошибка',
+ dataIndex: 'error_message',
+ key: 'error_message',
+ ellipsis: true,
+ sorter: true,
+ render: (text: string) => isBadValue(text) ? '-' : text,
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => {
+ const color =
+ status === 'open' ? 'red' :
+ status === 'in_progress' ? 'blue' :
+ status === 'resolved' ? 'green' : 'default';
+ return {status};
+ },
+ },
+ {
+ title: 'Назначен',
+ dataIndex: 'assigned_to',
+ key: 'assigned_to',
+ render: (assigned: string) => ,
+ },
+ { title: 'Повторов', dataIndex: 'count', key: 'count', width: 80, sorter: true },
+ { title: 'Первый раз', dataIndex: 'first_seen', key: 'first_seen', sorter: true },
+ { title: 'Последний', dataIndex: 'last_seen', key: 'last_seen', sorter: true },
+ {
+ title: 'Действия',
+ key: 'actions',
+ width: 80,
+ render: (_, record) => (
+ deleteTicket.mutate(record.id)}
+ >
+
+ }
+ size="small"
+ danger
+ />
+
+
+ ),
+ },
+ ];
+
+ return (
+
+
Тикеты
+ {stats && (
+
+
+
+ } styles={{ content: { color: 'red' } }} />
+
+
+
+
+ } styles={{ content: { color: 'blue' } }} />
+
+
+
+
+ } styles={{ content: { color: 'green' } }} />
+
+
+
+
+ } />
+
+
+
+ )}
+
+
+ );
+};
+
+export default TicketListPage;
\ No newline at end of file
diff --git a/src/pages/users/UserDetailPage.tsx b/src/pages/users/UserDetailPage.tsx
new file mode 100644
index 0000000..7bbfac9
--- /dev/null
+++ b/src/pages/users/UserDetailPage.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { Card, Descriptions, Spin, Button, Tag } from 'antd';
+import { useUser } from '../../hooks/useUsers';
+import dayjs from 'dayjs';
+
+const UserDetailPage: React.FC = () => {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+ const { data: user, isLoading } = useUser(id || '');
+
+ const isBadValue = (val: any) =>
+ val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
+
+ const displayValue = (val: any) => (isBadValue(val) ? '-' : val);
+
+ const formatDate = (dateStr: string) => {
+ if (isBadValue(dateStr)) return '-';
+ const d = dayjs(dateStr);
+ return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
+ };
+
+ if (isLoading) return ;
+ if (!user) return Пользователь не найден
;
+
+ return (
+
+
+ {user.id}
+ {displayValue(user.email)}
+ {displayValue(user.nickname)}
+
+ {user.role}
+
+
+
+ {user.status}
+
+
+ {displayValue(user.reason)}
+ {displayValue(user.phone)}
+ {displayValue(user.language)}
+ {displayValue(user.timezone)}
+ {displayValue(user.avatar_url)}
+ {displayValue(user.social_links)}
+ {displayValue(user.preferences)}
+ {formatDate(user.last_login)}
+ {formatDate(user.created_at)}
+ {formatDate(user.updated_at)}
+
+
+
+ );
+};
+
+export default UserDetailPage;
\ No newline at end of file
diff --git a/src/pages/users/UserListPage.tsx b/src/pages/users/UserListPage.tsx
new file mode 100644
index 0000000..a7e5294
--- /dev/null
+++ b/src/pages/users/UserListPage.tsx
@@ -0,0 +1,354 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Table, Button, Tag, Space, Modal, Form, Select, message, Spin, Input, Tooltip } from 'antd';
+import { InfoCircleOutlined, EditOutlined, LockOutlined, UnlockOutlined, DeleteOutlined } from '@ant-design/icons';
+import { useNavigate } from 'react-router-dom';
+import { useUsers, useUpdateUser, useDeleteUser, useUser } from '../../hooks/useUsers';
+import { User, UserListParams } from '../../types/api';
+import type { ColumnsType, SorterResult } from 'antd/es/table/interface';
+
+const UserListPage: React.FC = () => {
+ const navigate = useNavigate();
+ const [params, setParams] = useState({ limit: 20, offset: 0, sort: 'id', order: 'asc' });
+ const { data, isLoading } = useUsers(params);
+ const updateUser = useUpdateUser();
+ const deleteUser = useDeleteUser();
+
+ // Редактирование
+ const [editModal, setEditModal] = useState<{ open: boolean; userId: string | null }>({
+ open: false,
+ userId: null,
+ });
+ const { data: editingUser, isLoading: loadingUser } = useUser(editModal.userId || '');
+ const [editForm] = Form.useForm();
+ const [editHasErrors, setEditHasErrors] = useState(true);
+ const originalUserRef = useRef(null);
+
+ // Изменение статуса
+ const [statusModal, setStatusModal] = useState<{
+ open: boolean;
+ userId: string | null;
+ newStatus: string;
+ currentReason: string;
+ }>({
+ open: false,
+ userId: null,
+ newStatus: 'active',
+ currentReason: '',
+ });
+ const [statusForm] = Form.useForm();
+
+ // ==================== Редактирование ====================
+ const handleEdit = (id: string) => {
+ setEditModal({ open: true, userId: id });
+ };
+
+ const handleSaveEdit = () => {
+ editForm.validateFields().then((values) => {
+ if (!editModal.userId || !originalUserRef.current) return;
+ const original = originalUserRef.current;
+ const cleanedValues: Record = {};
+
+ const allKeys = new Set([...Object.keys(values), ...Object.keys(original)]);
+ allKeys.forEach((key) => {
+ const newVal = values[key];
+ if (newVal === '' || newVal === undefined || newVal === null) {
+ const origVal = (original as any)[key];
+ if (origVal && origVal !== '-' && origVal !== '' && origVal !== undefined && origVal !== null) {
+ cleanedValues[key] = null;
+ }
+ return;
+ }
+ if (newVal === '-') return;
+ cleanedValues[key] = newVal;
+ });
+
+ const payload = Object.fromEntries(
+ Object.entries(cleanedValues).filter(([key, v]) => {
+ const origVal = (original as any)[key];
+ return JSON.stringify(v) !== JSON.stringify(origVal === '-' ? undefined : origVal);
+ })
+ );
+
+ updateUser.mutate(
+ { id: editModal.userId, data: payload },
+ { onSuccess: () => setEditModal({ open: false, userId: null }) }
+ );
+ });
+ };
+
+ useEffect(() => {
+ if (editingUser && editModal.open) {
+ originalUserRef.current = editingUser;
+ const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
+ setTimeout(() => {
+ editForm.setFieldsValue({
+ email: clean(editingUser.email),
+ nickname: clean(editingUser.nickname),
+ role: clean(editingUser.role),
+ status: clean(editingUser.status),
+ reason: clean(editingUser.reason),
+ });
+ validateEditForm();
+ }, 0);
+ }
+ }, [editingUser, editModal.open, editForm]);
+
+ const validateEditForm = () => {
+ const role = editForm.getFieldValue('role');
+ const status = editForm.getFieldValue('status');
+ const hasErrors = !role || !status;
+ setEditHasErrors(hasErrors);
+ };
+
+ // ==================== Изменение статуса ====================
+ const handleStatusChange = (user: User) => {
+ if (user.status === 'deleted') return;
+ const newStatus = user.status === 'active' ? 'frozen' : 'active';
+ const currentReason = user.reason && user.reason !== '-' ? user.reason : '';
+ setStatusModal({
+ open: true,
+ userId: user.id,
+ newStatus,
+ currentReason,
+ });
+ };
+
+ useEffect(() => {
+ if (statusModal.open) {
+ setTimeout(() => {
+ statusForm.setFieldsValue({ reason: statusModal.currentReason });
+ }, 0);
+ }
+ }, [statusModal.open, statusModal.currentReason, statusForm]);
+
+ const handleStatusSave = () => {
+ statusForm.validateFields().then((values) => {
+ if (!statusModal.userId) return;
+ const payload: any = { status: statusModal.newStatus };
+ if (values.reason && values.reason.trim() !== '') {
+ payload.reason = values.reason;
+ }
+ updateUser.mutate(
+ { id: statusModal.userId, data: payload },
+ { onSuccess: () => setStatusModal({ open: false, userId: null, newStatus: 'active', currentReason: '' }) }
+ );
+ });
+ };
+
+ // ==================== Удаление ====================
+ const handleDelete = (id: string) => {
+ Modal.confirm({
+ title: 'Удалить пользователя?',
+ content: 'Это действие нельзя отменить. При необходимости предварительно укажите причину через редактирование.',
+ okText: 'Удалить',
+ okType: 'danger',
+ cancelText: 'Отмена',
+ onOk: () => deleteUser.mutate(id),
+ });
+ };
+
+ // ==================== Таблица ====================
+ const handleTableChange = (
+ pagination: any,
+ filters: any,
+ sorter: SorterResult | SorterResult[]
+ ) => {
+ const s = Array.isArray(sorter) ? sorter[0] : sorter;
+ setParams(prev => ({
+ ...prev,
+ sort: s.field as string,
+ order: s.order === 'ascend' ? 'asc' : s.order === 'descend' ? 'desc' : undefined,
+ offset: ((pagination.current - 1) * pagination.pageSize) || 0,
+ limit: pagination.pageSize || prev.limit,
+ }));
+ };
+
+ const columns: ColumnsType = [
+ {
+ title: ,
+ key: 'detail',
+ width: 48,
+ align: 'center',
+ render: (_, record) => (
+
+ }
+ size="small"
+ type="link"
+ onClick={() => navigate(`/users/${record.id}`)}
+ />
+
+ ),
+ },
+ { title: 'Email', dataIndex: 'email', key: 'email', sorter: true, ellipsis: true },
+ { title: 'Ник', dataIndex: 'nickname', key: 'nickname', sorter: true, ellipsis: true },
+ {
+ title: 'Роль',
+ dataIndex: 'role',
+ key: 'role',
+ width: 100,
+ sorter: true,
+ render: (role: string) => {
+ const color = role === 'user' ? 'blue' : role === 'bot' ? 'purple' : 'default';
+ return {role};
+ },
+ },
+ {
+ title: 'Статус',
+ dataIndex: 'status',
+ key: 'status',
+ width: 100,
+ sorter: true,
+ render: (status: string) => (
+
+ {status}
+
+ ),
+ },
+ { title: 'Последний вход', dataIndex: 'last_login', key: 'last_login', sorter: true },
+ {
+ title: 'Действия',
+ key: 'actions',
+ width: 120,
+ render: (_, record) => {
+ const isDeleted = record.status === 'deleted';
+ return (
+
+
+ }
+ size="small"
+ onClick={() => handleEdit(record.id)}
+ />
+
+
+ : }
+ size="small"
+ onClick={() => handleStatusChange(record)}
+ disabled={isDeleted}
+ />
+
+
+ }
+ size="small"
+ danger
+ onClick={() => handleDelete(record.id)}
+ disabled={isDeleted}
+ />
+
+
+ );
+ },
+ },
+ ];
+
+ return (
+
+
Пользователи
+
+
+ {/* Модальное окно редактирования */}
+
setEditModal({ open: false, userId: null })}
+ onOk={handleSaveEdit}
+ confirmLoading={updateUser.isPending}
+ destroyOnHidden
+ okButtonProps={{ disabled: editHasErrors }}
+ >
+ {loadingUser ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value) {
+ const status = getFieldValue('status');
+ if (status && status !== 'active' && (!value || value.trim() === '')) {
+ return Promise.reject(new Error('Укажите причину изменения статуса'));
+ }
+ return Promise.resolve();
+ },
+ }),
+ ]}
+ >
+
+
+
+ )}
+
+
+ {/* Модальное окно изменения статуса */}
+
setStatusModal({ open: false, userId: null, newStatus: 'active', currentReason: '' })}
+ onOk={handleStatusSave}
+ confirmLoading={updateUser.isPending}
+ destroyOnHidden
+ >
+
+
+
+
+
+
+ );
+};
+
+export default UserListPage;
\ No newline at end of file
diff --git a/src/store/authStore.ts b/src/store/authStore.ts
new file mode 100644
index 0000000..ab27286
--- /dev/null
+++ b/src/store/authStore.ts
@@ -0,0 +1,70 @@
+import { create } from 'zustand';
+import { Admin } from '../types/api';
+import { authApi } from '../api/authApi';
+import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../utils/constants';
+
+interface AuthState {
+ user: Admin | null;
+ accessToken: string | null;
+ refreshToken: string | null;
+ isAuthenticated: boolean;
+ isInitialized: boolean;
+ login: (email: string, password: string) => Promise;
+ logout: () => Promise;
+ checkAuth: () => Promise;
+}
+
+export const useAuthStore = create((set) => ({
+ user: null,
+ accessToken: localStorage.getItem(ACCESS_TOKEN_KEY),
+ refreshToken: localStorage.getItem(REFRESH_TOKEN_KEY),
+ isAuthenticated: false,
+ isInitialized: false,
+
+ login: async (email: string, password: string) => {
+ const { token, refresh_token, user } = await authApi.login(email, password);
+ localStorage.setItem(ACCESS_TOKEN_KEY, token);
+ localStorage.setItem(REFRESH_TOKEN_KEY, refresh_token);
+ set({
+ accessToken: token,
+ refreshToken: refresh_token,
+ user,
+ isAuthenticated: true,
+ isInitialized: true,
+ });
+ },
+
+ logout: async () => {
+ localStorage.removeItem(ACCESS_TOKEN_KEY);
+ localStorage.removeItem(REFRESH_TOKEN_KEY);
+ set({
+ user: null,
+ accessToken: null,
+ refreshToken: null,
+ isAuthenticated: false,
+ isInitialized: true,
+ });
+ },
+
+ checkAuth: async () => {
+ const token = localStorage.getItem(ACCESS_TOKEN_KEY);
+ if (!token) {
+ set({ isInitialized: true });
+ return;
+ }
+ try {
+ const user = await authApi.getMe();
+ set({ user, isAuthenticated: true, isInitialized: true });
+ } catch {
+ localStorage.removeItem(ACCESS_TOKEN_KEY);
+ localStorage.removeItem(REFRESH_TOKEN_KEY);
+ set({
+ user: null,
+ accessToken: null,
+ refreshToken: null,
+ isAuthenticated: false,
+ isInitialized: true,
+ });
+ }
+ },
+}));
\ No newline at end of file
diff --git a/src/types/api.ts b/src/types/api.ts
new file mode 100644
index 0000000..62fcddd
--- /dev/null
+++ b/src/types/api.ts
@@ -0,0 +1,299 @@
+// ===================== Event (Событие) =====================
+export interface Event {
+ id: string;
+ calendar_id: string;
+ title: string;
+ description: string;
+ event_type: 'single' | 'recurring';
+ start_time: string; // ISO8601
+ duration: number;
+ recurrence: object | null;
+ master_id: string | null;
+ is_instance: boolean;
+ specialist_id: string | null;
+ location: object | null;
+ tags: string[];
+ capacity: number | null;
+ online_link: string | null;
+ status: 'active' | 'cancelled' | 'completed';
+ reason: string | null;
+ rating_avg: number;
+ rating_count: number;
+ attachments: string[] | null;
+ edit_history: object[] | null;
+ created_at: string; // ISO8601
+ updated_at: string; // ISO8601
+}
+
+export interface EventListParams {
+ from?: string;
+ to?: string;
+ status?: 'active' | 'cancelled' | 'completed';
+ calendar_id?: string;
+ title?: string;
+ q?: string;
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== User (Пользователь) =====================
+export interface User {
+ id: string;
+ email: string;
+ role: 'user' | 'bot';
+ status: 'active' | 'frozen' | 'deleted';
+ reason: string | null;
+ nickname: string | null;
+ avatar_url: string | null;
+ timezone: string | null;
+ language: string | null;
+ social_links: string[] | null;
+ phone: string | null;
+ preferences: object | null;
+ last_login: string; // ISO8601
+ created_at: string; // ISO8601
+ updated_at: string; // ISO8601
+}
+
+export interface UserListParams {
+ role?: 'user' | 'bot';
+ status?: 'active' | 'frozen' | 'deleted';
+ q?: string;
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== Report (Жалоба) =====================
+export interface Report {
+ id: string;
+ reporter_id: string;
+ target_type: 'calendar' | 'event' | 'review';
+ target_id: string;
+ reason: string;
+ status: 'pending' | 'reviewed' | 'dismissed';
+ created_at: string;
+ resolved_at: string | null;
+ resolved_by: string | null;
+}
+
+export interface ReportListParams {
+ status?: 'pending' | 'reviewed' | 'dismissed';
+ target_type?: 'calendar' | 'event' | 'review';
+ q?: string;
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== Review (Отзыв) =====================
+export interface Review {
+ id: string;
+ user_id: string;
+ target_type: 'calendar' | 'event';
+ target_id: string;
+ rating: number; // 1-5
+ comment: string;
+ status: 'visible' | 'hidden' | 'deleted';
+ reason: string | null;
+ likes: number;
+ dislikes: number;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface ReviewListParams {
+ target_type?: 'calendar' | 'event';
+ target_id?: string;
+ user_id?: string;
+ status?: 'visible' | 'hidden' | 'deleted';
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== Banned Word (Бан-слово) =====================
+export interface BannedWord {
+ id: string;
+ word: string;
+ added_by: string | null;
+ added_at: string | null;
+}
+
+// ===================== Ticket (Тикет баг-трекера) =====================
+export interface Ticket {
+ id: string;
+ reporter_id: string;
+ error_hash: string;
+ error_message: string;
+ stacktrace: string;
+ context: string;
+ count: number;
+ first_seen: string;
+ last_seen: string;
+ status: 'open' | 'in_progress' | 'resolved' | 'closed';
+ assigned_to: string | null;
+ resolution_note: string | null;
+}
+
+export interface TicketListParams {
+ status?: 'open' | 'in_progress' | 'resolved' | 'closed';
+ assigned_to?: string;
+ q?: string;
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+export interface TicketStats {
+ open: number;
+ in_progress: number;
+ resolved: number;
+ closed: number;
+ total: number;
+}
+
+// ===================== Subscription (Подписка) =====================
+export interface Subscription {
+ id: string;
+ user_id: string;
+ plan: 'monthly' | 'quarterly' | 'biannual' | 'annual';
+ status: 'active' | 'expired' | 'cancelled';
+ trial_used: boolean;
+ started_at: string;
+ expires_at: string;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface SubscriptionListParams {
+ plan?: 'monthly' | 'quarterly' | 'biannual' | 'annual';
+ status?: 'active' | 'expired' | 'cancelled';
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== Admin (Администратор) =====================
+export interface Admin {
+ id: string;
+ email: string;
+ role: 'superadmin' | 'admin' | 'moderator' | 'support';
+ status: 'active' | 'blocked';
+ nickname: string | null;
+ avatar_url: string | null;
+ timezone: string | null;
+ language: string | null;
+ phone: string | null;
+ preferences: object | null;
+ last_login: string;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface AdminListParams {
+ role?: 'superadmin' | 'admin' | 'moderator' | 'support';
+ status?: 'active' | 'blocked';
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== Audit (Запись аудита) =====================
+export interface AuditRecord {
+ id: string;
+ admin_id: string;
+ email: string;
+ role: string;
+ action: string;
+ entity_type: string;
+ entity_id: string;
+ timestamp: string;
+ ip: string;
+ reason: string | null;
+}
+
+export interface AuditListParams {
+ admin_id?: string;
+ action?: string;
+ date_from?: string;
+ date_to?: string;
+ limit?: number;
+ offset?: number;
+ sort?: string;
+ order?: 'asc' | 'desc';
+}
+
+// ===================== Dashboard Stats =====================
+export interface DashboardStats {
+ users_total: number;
+ events_total: number;
+ reviews_total: number;
+ calendars_total: number;
+ reports_total: number;
+ tickets_total: number;
+ tickets_open: number;
+ avg_ticket_resolution_h: number;
+ admin_activity: AdminActivity[];
+ events_by_day: DayCount[];
+ registrations_by_day: DayCount[];
+}
+
+export interface AdminActivity {
+ admin_id: string;
+ email: string;
+ role: string;
+ last_login: string;
+ nickname: string;
+ actions: number;
+}
+
+export interface DayCount {
+ date: string;
+ count: number;
+}
+
+// ===================== Moderation =====================
+export type TargetType = 'calendar' | 'event' | 'review' | 'user';
+
+export type ModerationAction = {
+ calendar: 'freeze' | 'unfreeze';
+ event: 'freeze' | 'unfreeze';
+ review: 'hide' | 'unhide';
+ user: 'block' | 'unblock';
+};
+
+export interface ModerationPayload {
+ action: string;
+ reason?: string;
+}
+
+// ===================== Auth =====================
+export interface LoginRequest {
+ email: string;
+ password: string;
+}
+
+export interface LoginResponse {
+ access_token: string;
+ refresh_token: string;
+}
+
+export interface RefreshResponse {
+ access_token: string;
+ refresh_token: string;
+}
+
+// ===================== Pagination =====================
+export interface PaginatedResponse {
+ data: T[];
+ total: number;
+}
\ No newline at end of file
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
new file mode 100644
index 0000000..d3eba91
--- /dev/null
+++ b/src/utils/constants.ts
@@ -0,0 +1,2 @@
+export const ACCESS_TOKEN_KEY = 'access_token';
+export const REFRESH_TOKEN_KEY = 'refresh_token';
\ No newline at end of file
diff --git a/src/utils/normalize.ts b/src/utils/normalize.ts
new file mode 100644
index 0000000..19929bd
--- /dev/null
+++ b/src/utils/normalize.ts
@@ -0,0 +1,48 @@
+/**
+ * Заменяет все "некрасивые" значения в объекте или массиве на дефис.
+ * - null
+ * - "undefined" (строка)
+ * - undefined
+ * - пустая строка ""
+ */
+import dayjs from 'dayjs';
+
+const isDateKey = (key: string): boolean => {
+ // Покрываем: _at, _date, _time, _seen, timestamp, last_login, date, time и т.д.
+ const datePatterns = /_(at|date|time|seen)$|^(date|time|timestamp|last_login)$/;
+ return datePatterns.test(key);
+};
+
+const formatDateValue = (value: unknown): string => {
+ if (typeof value !== 'string' || value === '-' || value === '') return value as string;
+ const d = dayjs(value);
+ if (!d.isValid()) return value;
+ if (value.includes('T') || value.length > 10) {
+ return d.format('DD.MM.YYYY HH:mm');
+ }
+ return d.format('DD.MM.YYYY');
+};
+
+export const normalizeData = (data: T): T => {
+ if (data === null || data === undefined || data === 'undefined' || data === '') {
+ return '-' as unknown as T;
+ }
+
+ if (Array.isArray(data)) {
+ return data.map(item => normalizeData(item)) as unknown as T;
+ }
+
+ if (typeof data === 'object' && data !== null) {
+ const result: Record = {};
+ for (const [key, value] of Object.entries(data)) {
+ let normalized = normalizeData(value);
+ if (isDateKey(key) && typeof normalized === 'string' && normalized !== '-') {
+ normalized = formatDateValue(normalized);
+ }
+ result[key] = normalized;
+ }
+ return result as T;
+ }
+
+ return data;
+};
\ No newline at end of file
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000..5d098a2
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "es2023",
+ "lib": ["ES2023", "DOM"],
+ "module": "esnext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+// "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..9f14144
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "es2023",
+ "lib": ["ES2023"],
+ "module": "esnext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+// "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..01a5496
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ proxy: {
+ '/v1': {
+ target: 'https://admin-api.eventhub.local',
+ changeOrigin: true,
+ secure: false,
+ },
+ '/admin/ws': {
+ target: 'wss://admin-ws.eventhub.local',
+ ws: true,
+ changeOrigin: true,
+ secure: false,
+ },
+ },
+ },
+});
\ No newline at end of file