diff --git a/next.config.ts b/next.config.ts
index e9ffa30..e9b7337 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,14 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ async rewrites() {
+ return [
+ {
+ source: "/api/:path*",
+ destination: "http://127.0.0.1:8000/:path*",
+ },
+ ];
+ },
};
export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index 7edce37..6158c96 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,16 @@
"name": "partner-next",
"version": "0.1.0",
"dependencies": {
+ "@mui/icons-material": "^7.1.0",
+ "@mui/material": "^7.1.0",
+ "@mui/x-data-grid": "^8.5.0",
+ "@types/react-datepicker": "^6.2.0",
+ "material-react-table": "^3.2.1",
"next": "15.3.3",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-datepicker": "^8.4.0",
+ "react-dom": "^19.0.0",
+ "recharts": "^2.15.3"
},
"devDependencies": {
"@types/node": "^20",
@@ -19,6 +26,145 @@
"typescript": "^5"
}
},
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz",
+ "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.27.3",
+ "@babel/types": "^7.27.3",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.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==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz",
+ "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz",
+ "integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
+ "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/parser": "^7.27.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
+ "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
@@ -29,6 +175,210 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+ "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
+ "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz",
+ "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.0.tgz",
+ "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.0",
+ "@floating-ui/utils": "^0.2.9"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.27.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.10.tgz",
+ "integrity": "sha512-2tQScvQgzeYAdReavy/ql0JimAruQ2qtDHQFlcXSv8WP/1S1/YaIftyvhGfHknIB6rns0K3P6CV/lw/Tjm5Oaw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.9",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0",
+ "react-dom": ">=17.0.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
+ "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
+ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
+ "license": "MIT"
+ },
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.2",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz",
@@ -425,6 +775,430 @@
"url": "https://opencollective.com/libvips"
}
},
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mui/core-downloads-tracker": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz",
+ "integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ }
+ },
+ "node_modules/@mui/icons-material": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz",
+ "integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^7.1.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz",
+ "integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/core-downloads-tracker": "^7.1.0",
+ "@mui/system": "^7.1.0",
+ "@mui/types": "^7.4.2",
+ "@mui/utils": "^7.1.0",
+ "@popperjs/core": "^2.11.8",
+ "@types/react-transition-group": "^4.4.12",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.1.0",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@mui/material-pigment-css": "^7.1.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@mui/material-pigment-css": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material/node_modules/react-is": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
+ "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
+ "license": "MIT"
+ },
+ "node_modules/@mui/private-theming": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
+ "integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/utils": "^7.1.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
+ "integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@emotion/cache": "^11.13.5",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/sheet": "^1.4.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/system": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz",
+ "integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/private-theming": "^7.1.0",
+ "@mui/styled-engine": "^7.1.0",
+ "@mui/types": "^7.4.2",
+ "@mui/utils": "^7.1.0",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/types": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz",
+ "integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
+ "integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/types": "^7.4.2",
+ "@types/prop-types": "^15.7.14",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.1.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils/node_modules/react-is": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
+ "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
+ "license": "MIT"
+ },
+ "node_modules/@mui/x-data-grid": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.5.0.tgz",
+ "integrity": "sha512-5rrMm9anFaLk9O5XRIw3J9tAAnaiE1GxFeocyqhDj23RUReMg0YSp3FYnCaFLAehRQVgT9pC4675XO571paxKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/utils": "^7.0.2",
+ "@mui/x-internals": "8.5.0",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "reselect": "^5.1.1",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/x-date-pickers": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.5.0.tgz",
+ "integrity": "sha512-pnivJhAopuu6C4uWbEEg7b8kDdPc7Ad0ANrlDzx4qZGUj9vFcc6n+hT7/kOFnn9uceszQmb07e3Ud+g/d8Z4vg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/utils": "^7.0.2",
+ "@mui/x-internals": "8.5.0",
+ "@types/react-transition-group": "^4.4.12",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0",
+ "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0",
+ "dayjs": "^1.10.7",
+ "luxon": "^3.0.2",
+ "moment": "^2.29.4",
+ "moment-hijri": "^2.1.2 || ^3.0.0",
+ "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "date-fns": {
+ "optional": true
+ },
+ "date-fns-jalali": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ },
+ "moment-hijri": {
+ "optional": true
+ },
+ "moment-jalaali": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/x-internals": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.0.tgz",
+ "integrity": "sha512-Ef4KJij1pBGk6/xILyVZHf76tcuRpJIX30k4Ghklsd5QJujZ9ENCGAjvd7aWRAFAs5p3ffn0H8UDESoIcroj1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.1",
+ "@mui/utils": "^7.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/@next/env": {
"version": "15.3.3",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.3.tgz",
@@ -559,6 +1333,16 @@
"node": ">= 10"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -574,6 +1358,145 @@
"tslib": "^2.8.0"
}
},
+ "node_modules/@tanstack/match-sorter-utils": {
+ "version": "8.19.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz",
+ "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==",
+ "license": "MIT",
+ "dependencies": {
+ "remove-accents": "0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-table": {
+ "version": "8.20.6",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz",
+ "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/table-core": "8.20.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.11.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz",
+ "integrity": "sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.11.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/table-core": {
+ "version": "8.20.5",
+ "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz",
+ "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.11.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz",
+ "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==",
+ "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.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "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/node": {
"version": "20.17.57",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz",
@@ -584,16 +1507,64 @@
"undici-types": "~6.19.2"
}
},
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+ "license": "MIT"
+ },
"node_modules/@types/react": {
"version": "19.1.6",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
"integrity": "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-datepicker": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.2.0.tgz",
+ "integrity": "sha512-+JtO4Fm97WLkJTH8j8/v3Ldh7JCNRwjMYjRaKh4KHH0M3jJoXtwiD3JBCsdlg3tsFIw9eQSqyAPeVDN2H2oM9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.26.2",
+ "@types/react": "*",
+ "date-fns": "^3.3.1"
+ }
+ },
+ "node_modules/@types/react-datepicker/node_modules/@floating-ui/react": {
+ "version": "0.26.28",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
+ "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.8",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@types/react-datepicker/node_modules/date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
"node_modules/@types/react-dom": {
"version": "19.1.5",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz",
@@ -604,6 +1575,31 @@
"@types/react": "^19.0.0"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -615,6 +1611,16 @@
"node": ">=10.16.0"
}
},
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001720",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
@@ -641,6 +1647,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "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/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -686,11 +1701,189 @@
"simple-swizzle": "^0.2.2"
}
},
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
+ "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.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "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/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "license": "MIT",
+ "peer": true,
+ "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/detect-libc": {
@@ -703,6 +1896,154 @@
"node": ">=8"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/error-ex/node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "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==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
+ "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/highlight-words": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-2.0.0.tgz",
+ "integrity": "sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20",
+ "npm": ">= 9"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
@@ -710,6 +2051,108 @@
"license": "MIT",
"optional": true
},
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/material-react-table": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-3.2.1.tgz",
+ "integrity": "sha512-sQtTf7bETpkPN2Hm5BVtz89wrfXCVQguz6XlwMChSnfKFO5QCKAJJC5aSIKnUc3S0AvTz/k/ILi00FnnY1Gixw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/match-sorter-utils": "8.19.4",
+ "@tanstack/react-table": "8.20.6",
+ "@tanstack/react-virtual": "3.11.2",
+ "highlight-words": "2.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kevinvandy"
+ },
+ "peerDependencies": {
+ "@emotion/react": ">=11.13",
+ "@emotion/styled": ">=11.13",
+ "@mui/icons-material": ">=6",
+ "@mui/material": ">=6",
+ "@mui/x-date-pickers": ">=7.15",
+ "react": ">=18.0",
+ "react-dom": ">=18.0"
+ }
+ },
+ "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",
+ "peer": true
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -782,6 +2225,64 @@
}
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -816,6 +2317,23 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -825,6 +2343,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-datepicker": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.4.0.tgz",
+ "integrity": "sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.27.3",
+ "clsx": "^2.1.1",
+ "date-fns": "^4.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
@@ -837,6 +2370,118 @@
"react": "^19.1.0"
}
},
+ "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-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.3",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.3.tgz",
+ "integrity": "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/remove-accents": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
+ "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==",
+ "license": "MIT"
+ },
+ "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/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@@ -908,6 +2553,16 @@
"is-arrayish": "^0.3.1"
}
},
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -948,6 +2603,37 @@
}
}
},
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "license": "MIT"
+ },
+ "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/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -974,6 +2660,47 @@
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT"
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "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/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "license": "ISC",
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
}
}
}
diff --git a/package.json b/package.json
index 2ef5c26..96fa416 100644
--- a/package.json
+++ b/package.json
@@ -9,14 +9,21 @@
"lint": "next lint"
},
"dependencies": {
+ "@mui/icons-material": "^7.1.0",
+ "@mui/material": "^7.1.0",
+ "@mui/x-data-grid": "^8.5.0",
+ "@types/react-datepicker": "^6.2.0",
+ "material-react-table": "^3.2.1",
+ "next": "15.3.3",
"react": "^19.0.0",
+ "react-datepicker": "^8.4.0",
"react-dom": "^19.0.0",
- "next": "15.3.3"
+ "recharts": "^2.15.3"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
- "@types/react-dom": "^19"
+ "@types/react-dom": "^19",
+ "typescript": "^5"
}
}
diff --git a/src/app/billing/page.tsx b/src/app/billing/page.tsx
new file mode 100644
index 0000000..dc97459
--- /dev/null
+++ b/src/app/billing/page.tsx
@@ -0,0 +1,64 @@
+"use client";
+import { useState, useEffect, useMemo } from "react";
+import MetricCard from "../../components/MetricCard";
+import styles from "../../styles/billing.module.css";
+import BillingPieChart from "../../components/BillingPieChart";
+import BillingMetricCards from "../../components/BillingMetricCards";
+import PayoutsTransactionsTable from "../../components/PayoutsTransactionsTable";
+import BillingStatChart from "../../components/BillingStatChart";
+import DateFilters from "../../components/DateFilters";
+
+export default function BillingPage() {
+ const [payoutForm, setPayoutForm] = useState({
+ amount: "",
+ method: "bank",
+ });
+ const [filters, setFilters] = useState({
+ dateStart: "",
+ dateEnd: "",
+ });
+ const [reloadKey, setReloadKey] = useState(0);
+
+ function formatCurrency(amount: number) {
+ return amount?.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ }) ?? "";
+ }
+
+ function handleApply() {
+ setReloadKey(k => k + 1);
+ }
+ function handleClear() {
+ setFilters(f => ({ ...f, dateStart: '', dateEnd: '' }));
+ setReloadKey(k => k + 1);
+ }
+
+ return (
+
+
Финансы
+
+
+
+
+
+
{
+ if (field === "dateStart") {
+ if (filters.dateEnd && value > filters.dateEnd) return;
+ }
+ if (field === "dateEnd") {
+ if (filters.dateStart && value < filters.dateStart) return;
+ }
+ setFilters(f => ({ ...f, [field]: value }));
+ }}
+ onApply={handleApply}
+ onClear={handleClear}
+ />
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 42fc323..b7dee54 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import Navigation from "../components/Navigation";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "RE:Premium Partner",
+ description: "Партнерская платформа",
};
export default function RootLayout({
@@ -23,9 +24,23 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
- {children}
+
+
+
+
+ {children}
+
);
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 84af2cb..94dffb1 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,95 +1,44 @@
-import Image from "next/image";
-import styles from "./page.module.css";
+import mockData from "../data/mockData";
+import MetricCards from "../components/MetricCards";
+import Table from "../components/Table";
+import StatCharts from "../components/StatCharts";
+import styles from "../styles/dashboard.module.css";
-export default function Home() {
+function formatCurrency(amount: number) {
+ return amount.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ });
+}
+
+export default function DashboardPage() {
return (
-
-
-
+ Дашборд
+
+
+ {/*
+
Последние продажи
+
(
+
+ | {sale.id} |
+ {sale.agent} |
+ {formatCurrency(sale.amount)} |
+ {formatCurrency(sale.commission)} |
+ {sale.date} |
+
+
+ {sale.status}
+
+ |
+
+ )}
/>
-
- -
- Get started by editing
src/app/page.tsx.
-
- - Save and see your changes instantly.
-
-
-
-
-
+ */}
);
}
diff --git a/src/app/stat/page.tsx b/src/app/stat/page.tsx
new file mode 100644
index 0000000..b6e7158
--- /dev/null
+++ b/src/app/stat/page.tsx
@@ -0,0 +1,84 @@
+"use client";
+import { useState } from "react";
+import mockData from "../../data/mockData";
+import AgentsTable from "../../components/AgentsTable";
+import ReferralsTable from "../../components/ReferralsTable";
+import SalesTable from "../../components/SalesTable";
+import styles from "../../styles/stat.module.css";
+import DateInput from "../../components/DateInput";
+import DateFilters from "../../components/DateFilters";
+
+const tabs = [
+ { id: "agents", label: "Агенты" },
+ { id: "referrals", label: "Рефералы" },
+ { id: "sales", label: "Продажи" },
+];
+
+export default function StatPage() {
+ const [activeTab, setActiveTab] = useState("agents");
+
+ const [filters, setFilters] = useState({
+ dateStart: "",
+ dateEnd: "",
+ agentStatus: "all",
+ });
+ const [reloadKey, setReloadKey] = useState(0);
+
+ function handleApply() {
+ setReloadKey(k => k + 1);
+ }
+ function handleClear() {
+ setFilters(f => ({ ...f, dateStart: '', dateEnd: '' }));
+ setReloadKey(k => k + 1);
+ }
+
+
+
+ return (
+
+
+
Статистика и аналитика
+ {/* */}
+
+
+ {tabs.map((tab) => (
+
+ ))}
+
+
+
{
+ if (field === "dateStart") {
+ if (filters.dateEnd && value > filters.dateEnd) return;
+ }
+ if (field === "dateEnd") {
+ if (filters.dateStart && value < filters.dateStart) return;
+ }
+ setFilters(f => ({ ...f, [field]: value }));
+ }}
+ onApply={handleApply}
+ onClear={handleClear}
+ />
+
+
+ {activeTab === "agents" && (
+
+ )}
+ {activeTab === "referrals" && (
+
+ )}
+ {activeTab === "sales" && (
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/AgentsBarChart.tsx b/src/components/AgentsBarChart.tsx
new file mode 100644
index 0000000..ceb795d
--- /dev/null
+++ b/src/components/AgentsBarChart.tsx
@@ -0,0 +1,98 @@
+"use client";
+import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, LabelList, Label } from "recharts";
+import { useEffect, useState } from "react";
+
+interface AgentBarData {
+ name: string;
+ count: number;
+ sum: number;
+}
+
+const formatCurrency = (amount: number) => {
+ return amount.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ });
+};
+
+const formatShort = (value: number) => {
+ if (value >= 1_000_000) return (value / 1_000_000).toFixed(1).replace('.0', '') + 'M ₽';
+ if (value >= 1_000) return (value / 1_000).toFixed(1).replace('.0', '') + 'K ₽';
+ return value + ' ₽';
+};
+
+const AgentsBarChart: React.FC = () => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetch("/api/dashboard/chart/agent")
+ .then((res) => {
+ if (!res.ok) throw new Error("Ошибка загрузки данных");
+ return res.json();
+ })
+ .then((data) => {
+ setData(data);
+ setLoading(false);
+ })
+ .catch((err) => {
+ setError(err.message);
+ setLoading(false);
+ });
+ }, []);
+
+ if (loading) return Загрузка графика...
;
+ if (error) return Ошибка: {error}
;
+
+ return (
+
+
+
+ {
+ let name = payload.value;
+ if (name.length > 10) {
+ name = name.slice(0, 10) + '...';
+ }
+ return (
+
+ {name}
+
+ );
+ }}
+ />
+
+
+
+
+
+
+ {
+ if (name === "sum" || name === "Сумма продаж") {
+ return [formatCurrency(Number(value)), "Сумма продаж"];
+ }
+ if (name === "count" || name === "Кол-во продаж") {
+ return [value, "Кол-во продаж"];
+ }
+ return value;
+ }} />
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AgentsBarChart;
\ No newline at end of file
diff --git a/src/components/AgentsTable.tsx b/src/components/AgentsTable.tsx
new file mode 100644
index 0000000..8c48934
--- /dev/null
+++ b/src/components/AgentsTable.tsx
@@ -0,0 +1,52 @@
+import { useEffect, useState, useMemo } from 'react';
+import { MaterialReactTable, MRT_ColumnDef } from 'material-react-table';
+import styles from "../styles/stat.module.css";
+
+function formatCurrency(amount: number) {
+ return amount?.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ }) ?? "";
+}
+
+export default function AgentsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) {
+ const [data, setData] = useState([]);
+
+ useEffect(() => {
+ const params = new URLSearchParams();
+ if (filters.dateStart) params.append('date_start', filters.dateStart);
+ if (filters.dateEnd) params.append('date_end', filters.dateEnd);
+ fetch(`/api/stat/agents?${params.toString()}`)
+ .then(res => res.json())
+ .then(setData)
+ .catch(() => setData([]));
+ }, [filters.dateStart, filters.dateEnd, reloadKey]);
+
+ const columns = useMemo[]>(
+ () => [
+ { accessorKey: 'name', header: 'Имя' },
+ { accessorKey: 'refCount', header: 'Кол-во рефов' },
+ { accessorKey: 'salesCount', header: 'Кол-во продаж' },
+ { accessorKey: 'salesSum', header: 'Сумма продаж',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ { accessorKey: 'crediting', header: 'Начислено',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ ],
+ []
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/BillingMetricCards.tsx b/src/components/BillingMetricCards.tsx
new file mode 100644
index 0000000..fe6ed44
--- /dev/null
+++ b/src/components/BillingMetricCards.tsx
@@ -0,0 +1,53 @@
+import MetricCard from "./MetricCard";
+import styles from "../styles/billing.module.css";
+import React, { useEffect, useState } from "react";
+
+interface BillingCardsData {
+ cost: number;
+ crediting: number;
+ pendingPayouts: number;
+}
+
+const BillingMetricCards: React.FC = () => {
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ function formatCurrency(amount: number) {
+ return amount?.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ }) ?? "";
+ }
+
+ useEffect(() => {
+ fetch("/api/billing/cards")
+ .then((res) => {
+ if (!res.ok) throw new Error("Ошибка загрузки данных");
+ return res.json();
+ })
+ .then((data) => {
+ setData(data);
+ setLoading(false);
+ })
+ .catch((err) => {
+ setError(err.message);
+ setLoading(false);
+ });
+ }, []);
+
+ if (loading) return Загрузка...
;
+ if (error) return Ошибка: {error}
;
+ if (!data) return null;
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default BillingMetricCards;
\ No newline at end of file
diff --git a/src/components/BillingPayoutsTable.tsx b/src/components/BillingPayoutsTable.tsx
new file mode 100644
index 0000000..fa44aeb
--- /dev/null
+++ b/src/components/BillingPayoutsTable.tsx
@@ -0,0 +1,51 @@
+import { useMemo } from 'react';
+import { MaterialReactTable, MRT_ColumnDef } from 'material-react-table';
+import mockData from '../data/mockData';
+
+function formatCurrency(amount: number) {
+ return amount?.toLocaleString('ru-RU', {
+ style: 'currency',
+ currency: 'RUB',
+ minimumFractionDigits: 0,
+ }) ?? '';
+}
+
+const statusColor: Record = {
+ 'Завершена': '#4caf50',
+ 'Ожидается': '#ff9800',
+ 'Ошибка': '#f44336',
+};
+
+export default function BillingPayoutsTable() {
+ const columns = useMemo[]>(
+ () => [
+ { accessorKey: 'id', header: 'ID' },
+ { accessorKey: 'amount', header: 'Сумма',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ { accessorKey: 'date', header: 'Дата' },
+ { accessorKey: 'status', header: 'Статус',
+ Cell: ({ cell }) => (
+
+ {cell.getValue() as string}
+
+ )
+ },
+ { accessorKey: 'method', header: 'Способ' },
+ ],
+ []
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/BillingPieChart.tsx b/src/components/BillingPieChart.tsx
new file mode 100644
index 0000000..c40ffe8
--- /dev/null
+++ b/src/components/BillingPieChart.tsx
@@ -0,0 +1,52 @@
+"use client";
+import React, { useEffect, useState } from "react";
+import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from "recharts";
+import styles from "../styles/billing.module.css";
+
+const STATUS_COLORS: Record = {
+ "done": "#10B981",
+ "waiting": "#F59E0B",
+ "error": "#EF4444",
+ "process": "#3B82F6",
+};
+
+const BillingPieChart: React.FC = () => {
+ const [data, setData] = useState<{ name: string; value: number; fill: string }[]>([]);
+ useEffect(() => {
+ fetch("api/billing/chart/pie")
+ .then((res) => res.json())
+ .then((apiData) => {
+ const mapped = apiData.map((item: { status: string; count: number }) => ({
+ name: item.status,
+ value: item.count,
+ fill: STATUS_COLORS[item.status] || "#A3A3A3",
+ }));
+ setData(mapped);
+ });
+ }, []);
+
+ return (
+
+
Статистика выплат
+
+
+ `${name}: ${value}`}
+ >
+ {data.map((entry, idx) => (
+ |
+ ))}
+
+
+
+
+
+ );
+};
+
+export default BillingPieChart;
\ No newline at end of file
diff --git a/src/components/BillingStatChart.tsx b/src/components/BillingStatChart.tsx
new file mode 100644
index 0000000..88fa916
--- /dev/null
+++ b/src/components/BillingStatChart.tsx
@@ -0,0 +1,107 @@
+"use client";
+import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Label } from "recharts";
+import { useEffect, useState } from "react";
+import styles from "../styles/billing.module.css";
+import { TooltipProps } from "recharts";
+import { ValueType, NameType } from "recharts/types/component/DefaultTooltipContent";
+
+const statusColors: Record = {
+ done: "#10B981",
+ process: "#3B82F6",
+ waiting: "#F59E42",
+ error: "#EF4444",
+};
+
+const BillingStatChart: React.FC = () => {
+ const [data, setData] = useState([]);
+ const [statuses, setStatuses] = useState([]);
+
+ useEffect(() => {
+ fetch("/api/billing/chart/stat")
+ .then(res => res.json())
+ .then((apiData) => {
+ // Собираем все уникальные даты и статусы
+ const allDates = new Set();
+ const allStatuses = new Set();
+ apiData.forEach((item: any) => {
+ allDates.add(item.date);
+ allStatuses.add(item.status);
+ });
+ const sortedDates = Array.from(allDates).sort((a, b) => a.localeCompare(b));
+ const statusesArr = Array.from(allStatuses);
+ // Группируем по дате, для каждой даты заполняем все статусы (если нет — 0)
+ const grouped: Record = {};
+ sortedDates.forEach(date => {
+ grouped[date] = { date };
+ statusesArr.forEach(status => {
+ grouped[date][status] = 0;
+ });
+ });
+ apiData.forEach((item: any) => {
+ grouped[item.date][item.status] = item.count;
+ });
+ const sorted = sortedDates.map(date => grouped[date]);
+ setData(sorted);
+ setStatuses(statusesArr);
+ })
+ .catch(() => setData([]));
+ }, []);
+
+ // Кастомный тултип
+ const CustomTooltip = ({ active, payload, label }: TooltipProps) => {
+ if (active && payload && payload.length) {
+ return (
+
+
{label}
+ {payload.map((entry, idx) => (
+
+ {entry.name} : {entry.value}
+
+ ))}
+
+ );
+ }
+ return null;
+ };
+
+ return (
+
+
Динамика выплат по статусам
+
+
+
+ new Date(d).toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" })}>
+
+
+
+
+
+ } />
+ {statuses.map(status => (
+
+ ))}
+
+
+ {/* Кастомная легенда под графиком */}
+
+ {statuses.map(status => (
+
+
+ {status}
+
+ ))}
+
+
+ );
+};
+
+export default BillingStatChart;
\ No newline at end of file
diff --git a/src/components/DateFilters.tsx b/src/components/DateFilters.tsx
new file mode 100644
index 0000000..bafdfd3
--- /dev/null
+++ b/src/components/DateFilters.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+import DateInput from "./DateInput";
+import styles from "../styles/stat.module.css";
+
+interface DateFiltersProps {
+ dateStart: string;
+ dateEnd: string;
+ onChange: (field: "dateStart" | "dateEnd", value: string) => void;
+ onApply: () => void;
+ onClear: () => void;
+}
+
+const DateFilters: React.FC = ({ dateStart, dateEnd, onChange, onApply, onClear }) => (
+
+ onChange("dateStart", e.target.value)}
+ max={dateEnd || undefined}
+ />
+ onChange("dateEnd", e.target.value)}
+ min={dateStart || undefined}
+ />
+
+
+
+);
+
+export default DateFilters;
\ No newline at end of file
diff --git a/src/components/DateInput.tsx b/src/components/DateInput.tsx
new file mode 100644
index 0000000..61a3a4b
--- /dev/null
+++ b/src/components/DateInput.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+
+interface DateInputProps {
+ label: string;
+ value: string;
+ onChange: (e: React.ChangeEvent) => void;
+ min?: string;
+ max?: string;
+}
+
+const DateInput: React.FC = ({ label, value, onChange, min, max }) => (
+
+
+
+
+);
+
+export default DateInput;
\ No newline at end of file
diff --git a/src/components/MetricCard.tsx b/src/components/MetricCard.tsx
new file mode 100644
index 0000000..8deb232
--- /dev/null
+++ b/src/components/MetricCard.tsx
@@ -0,0 +1,24 @@
+import styles from '../styles/metricCard.module.css';
+
+interface MetricCardProps {
+ title: string;
+ value: string | number;
+ change?: number;
+ isPositive?: boolean;
+}
+
+const MetricCard: React.FC = ({ title, value, change, isPositive = true }) => (
+
+
{title}
+
+
{value}
+ {change !== undefined && (
+
+ {isPositive ? '+' : ''}{change}%
+
+ )}
+
+
+);
+
+export default MetricCard;
\ No newline at end of file
diff --git a/src/components/MetricCards.tsx b/src/components/MetricCards.tsx
new file mode 100644
index 0000000..2249192
--- /dev/null
+++ b/src/components/MetricCards.tsx
@@ -0,0 +1,57 @@
+"use client";
+import MetricCard from "./MetricCard";
+import styles from "../styles/dashboard.module.css";
+import React, { useEffect, useState } from "react";
+
+function formatCurrency(amount: number) {
+ return amount.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ });
+}
+
+interface DashboardCardsData {
+ totalRevenue: number;
+ totalPayouts: number;
+ activeReferrals: number;
+ pendingPayouts: number;
+ totalSales: number;
+}
+
+export default function MetricCards() {
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetch("api/dashboard/cards")
+ .then((res) => {
+ if (!res.ok) throw new Error("Ошибка загрузки данных");
+ return res.json();
+ })
+ .then((data) => {
+ setData(data);
+ setLoading(false);
+ })
+ .catch((err) => {
+ setError(err.message);
+ setLoading(false);
+ });
+ }, []);
+
+ if (loading) return Загрузка...
;
+ if (error) return Ошибка: {error}
;
+ if (!data) return null;
+
+ return (
+
+
+
+
+
+ {/* */}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
new file mode 100644
index 0000000..14ec136
--- /dev/null
+++ b/src/components/Navigation.tsx
@@ -0,0 +1,46 @@
+"use client";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import styles from "../styles/navigation.module.css";
+
+interface NavItem {
+ id: string;
+ label: string;
+ href: string;
+}
+
+const navItems: NavItem[] = [
+ { id: "home", label: "Дашборд", href: "/" },
+ { id: "stat", label: "Статистика", href: "/stat" },
+ { id: "billing", label: "Финансы", href: "/billing" },
+];
+
+const Navigation: React.FC = () => {
+ const pathname = usePathname();
+ return (
+
+ );
+};
+
+export default Navigation;
\ No newline at end of file
diff --git a/src/components/PayoutsTransactionsTable.tsx b/src/components/PayoutsTransactionsTable.tsx
new file mode 100644
index 0000000..f4f9588
--- /dev/null
+++ b/src/components/PayoutsTransactionsTable.tsx
@@ -0,0 +1,72 @@
+import { useEffect, useState, useMemo } from "react";
+import { MaterialReactTable, MRT_ColumnDef } from "material-react-table";
+import styles from "../styles/billing.module.css";
+
+
+function formatCurrency(amount: number) {
+ return amount?.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ }) ?? "";
+}
+
+const statusColor: Record = {
+ 'done': '#4caf50',
+ 'waiting': '#ff9800',
+ 'process': '#2196f3',
+ 'error': '#f44336',
+};
+
+export default function PayoutsTransactionsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) {
+ const [data, setData] = useState([]);
+ useEffect(() => {
+ const params = new URLSearchParams();
+ if (filters.dateStart) params.append('date_start', filters.dateStart);
+ if (filters.dateEnd) params.append('date_end', filters.dateEnd);
+ fetch(`/api/billing/payouts/transactions?${params.toString()}`)
+ .then(res => res.json())
+ .then(setData)
+ .catch(() => setData([]));
+ }, [filters.dateStart, filters.dateEnd, reloadKey]);
+
+ const columns = useMemo[]>(
+ () => [
+ { accessorKey: 'id', header: 'ID' },
+ { accessorKey: 'sum', header: 'Сумма',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ { accessorKey: 'agent', header: 'Агент' },
+ { accessorKey: 'status', header: 'Статус',
+ Cell: ({ cell }) => (
+
+ {cell.getValue() as string}
+
+ )
+ },
+ { accessorKey: 'create_dttm', header: 'Создано',
+ Cell: ({ cell }) => new Date(cell.getValue() as string).toLocaleString('ru-RU') },
+ { accessorKey: 'update_dttm', header: 'Обновлено',
+ Cell: ({ cell }) => new Date(cell.getValue() as string).toLocaleString('ru-RU') },
+ ],
+ []
+ );
+
+ return (
+
+
+
История выплат
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/ReferralsTable.tsx b/src/components/ReferralsTable.tsx
new file mode 100644
index 0000000..8bb43d2
--- /dev/null
+++ b/src/components/ReferralsTable.tsx
@@ -0,0 +1,51 @@
+import { useEffect, useState, useMemo } from 'react';
+import { MaterialReactTable, MRT_ColumnDef } from 'material-react-table';
+import styles from "../styles/stat.module.css";
+
+function formatCurrency(amount: number) {
+ return amount?.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ }) ?? "";
+}
+
+export default function ReferralsTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) {
+ const [data, setData] = useState([]);
+
+ useEffect(() => {
+ const params = new URLSearchParams();
+ if (filters.dateStart) params.append('date_start', filters.dateStart);
+ if (filters.dateEnd) params.append('date_end', filters.dateEnd);
+ fetch(`/api/stat/referrals?${params.toString()}`)
+ .then(res => res.json())
+ .then(setData)
+ .catch(() => setData([]));
+ }, [filters.dateStart, filters.dateEnd, reloadKey]);
+
+ const columns = useMemo[]>(
+ () => [
+ { accessorKey: 'ref', header: 'Ref' },
+ { accessorKey: 'agent', header: 'Агент' },
+ { accessorKey: 'description', header: 'Описание' },
+ { accessorKey: 'salesCount', header: 'Кол-во продаж' },
+ { accessorKey: 'salesSum', header: 'Сумма продаж',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ ],
+ []
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/RevenueChart.tsx b/src/components/RevenueChart.tsx
new file mode 100644
index 0000000..4f8962e
--- /dev/null
+++ b/src/components/RevenueChart.tsx
@@ -0,0 +1,72 @@
+"use client";
+import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Label } from "recharts";
+import { useEffect, useState } from "react";
+
+const formatCurrency = (amount: number) => {
+ return amount.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ });
+};
+
+const formatShort = (value: number) => {
+ if (value >= 1_000_000) return (value / 1_000_000).toFixed(1).replace('.0', '') + 'M ₽';
+ if (value >= 1_000) return (value / 1_000).toFixed(1).replace('.0', '') + 'K ₽';
+ return value + ' ₽';
+};
+
+const RevenueChart: React.FC = () => {
+ const [data, setData] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetch("/api/dashboard/chart/total")
+ .then((res) => {
+ if (!res.ok) throw new Error("Ошибка загрузки данных");
+ return res.json();
+ })
+ .then((data) => {
+ setData(data);
+ setLoading(false);
+ })
+ .catch((err) => {
+ setError(err.message);
+ setLoading(false);
+ });
+ }, []);
+
+ if (loading) return Загрузка графика...
;
+ if (error) return Ошибка: {error}
;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ if (name === "revenue" || name === "Сумма продаж") {
+ return [formatCurrency(Number(value)), "Сумма продаж"];
+ }
+ if (name === "sales" || name === "Кол-во продаж") {
+ return [value, "Кол-во продаж"];
+ }
+ return value;
+ }} />
+
+
+
+
+ );
+};
+
+export default RevenueChart;
\ No newline at end of file
diff --git a/src/components/SalesTable.tsx b/src/components/SalesTable.tsx
new file mode 100644
index 0000000..fa1d5cc
--- /dev/null
+++ b/src/components/SalesTable.tsx
@@ -0,0 +1,52 @@
+import { useEffect, useState, useMemo } from 'react';
+import { MaterialReactTable, MRT_ColumnDef } from 'material-react-table';
+import styles from "../styles/stat.module.css";
+
+function formatCurrency(amount: number) {
+ return amount?.toLocaleString("ru-RU", {
+ style: "currency",
+ currency: "RUB",
+ minimumFractionDigits: 0,
+ }) ?? "";
+}
+
+export default function SalesTable({ filters, reloadKey }: { filters: { dateStart: string, dateEnd: string }, reloadKey: number }) {
+ const [data, setData] = useState([]);
+
+ useEffect(() => {
+ const params = new URLSearchParams();
+ if (filters.dateStart) params.append('date_start', filters.dateStart);
+ if (filters.dateEnd) params.append('date_end', filters.dateEnd);
+ fetch(`/api/stat/sales?${params.toString()}`)
+ .then(res => res.json())
+ .then(setData)
+ .catch(() => setData([]));
+ }, [filters.dateStart, filters.dateEnd, reloadKey]);
+
+ const columns = useMemo[]>(
+ () => [
+ { accessorKey: 'saleId', header: 'ID продажи' },
+ { accessorKey: 'cost', header: 'Сумма',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ { accessorKey: 'crediting', header: 'Начислено',
+ Cell: ({ cell }) => formatCurrency(cell.getValue() as number) },
+ { accessorKey: 'ref', header: 'Ref' },
+ { accessorKey: 'name', header: 'Агент' },
+ ],
+ []
+ );
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/StatCharts.tsx b/src/components/StatCharts.tsx
new file mode 100644
index 0000000..69a3a4f
--- /dev/null
+++ b/src/components/StatCharts.tsx
@@ -0,0 +1,16 @@
+import RevenueChart from "./RevenueChart";
+import AgentsBarChart from "./AgentsBarChart";
+import styles from "../styles/dashboard.module.css";
+
+const StatCharts: React.FC = () => (
+
+);
+
+export default StatCharts;
\ No newline at end of file
diff --git a/src/components/Table.tsx b/src/components/Table.tsx
new file mode 100644
index 0000000..a120475
--- /dev/null
+++ b/src/components/Table.tsx
@@ -0,0 +1,31 @@
+import styles from '../styles/table.module.css';
+import React from 'react';
+
+interface TableProps {
+ headers: string[];
+ data: T[];
+ renderRow: (item: T, index: number) => React.ReactNode;
+}
+
+function Table({ headers, data, renderRow }: TableProps) {
+ return (
+
+
+
+
+
+ {headers.map((header, index) => (
+ | {header} |
+ ))}
+
+
+
+ {data.map((item, index) => renderRow(item, index))}
+
+
+
+
+ );
+}
+
+export default Table;
\ No newline at end of file
diff --git a/src/data/mockData.js b/src/data/mockData.js
new file mode 100644
index 0000000..a1d288e
--- /dev/null
+++ b/src/data/mockData.js
@@ -0,0 +1,54 @@
+// Мок данные для Dashboard, Stat, Billing и других страниц
+const mockData = {
+ dashboard: {
+ totalRevenue: 245680,
+ totalPayouts: 189420,
+ activeReferrals: 1247,
+ conversion: 24.7,
+ pendingPayouts: 12890,
+ revenueChart: [
+ { date: '01.01', revenue: 12500, payouts: 8900 },
+ { date: '08.01', revenue: 18200, payouts: 12100 },
+ { date: '15.01', revenue: 22400, payouts: 15800 },
+ { date: '22.01', revenue: 19800, payouts: 13200 },
+ { date: '29.01', revenue: 25100, payouts: 18900 },
+ ],
+ recentSales: [
+ { id: 'REF-2024-001', agent: 'Алексей Петров', amount: 15000, commission: 2250, date: '2024-01-29', status: 'Выплачено' },
+ { id: 'REF-2024-002', agent: 'Мария Сидорова', amount: 8500, commission: 1275, date: '2024-01-28', status: 'Ожидает' },
+ { id: 'REF-2024-003', agent: 'Дмитрий Козлов', amount: 12300, commission: 1845, date: '2024-01-27', status: 'Выплачено' },
+ { id: 'REF-2024-004', agent: 'Елена Волкова', amount: 6700, commission: 1005, date: '2024-01-26', status: 'Ожидает' },
+ { id: 'REF-2024-005', agent: 'Игорь Смирнов', amount: 19200, commission: 2880, date: '2024-01-25', status: 'Выплачено' },
+ ]
+ },
+ agents: [
+ { id: 1, name: 'Алексей Петров', referrals: 45, sales: 12, conversion: 26.7, commission: 18500, status: 'Активен' },
+ { id: 2, name: 'Мария Сидорова', referrals: 38, sales: 8, conversion: 21.1, commission: 12300, status: 'Активен' },
+ { id: 3, name: 'Дмитрий Козлов', referrals: 52, sales: 15, conversion: 28.8, commission: 22100, status: 'Активен' },
+ { id: 4, name: 'Елена Волкова', referrals: 29, sales: 6, conversion: 20.7, commission: 8900, status: 'Неактивен' },
+ { id: 5, name: 'Игорь Смирнов', referrals: 67, sales: 18, conversion: 26.9, commission: 28700, status: 'Активен' },
+ ],
+ referrals: [
+ { id: 'REF-001', client: 'ООО "Альфа Строй"', date: '2024-01-15', status: 'Конвертирован', amount: 15000 },
+ { id: 'REF-002', client: 'ИП Иванов А.С.', date: '2024-01-18', status: 'Ожидает', amount: null },
+ { id: 'REF-003', client: 'ООО "БетаТех"', date: '2024-01-20', status: 'Конвертирован', amount: 8500 },
+ { id: 'REF-004', client: 'ООО "ГаммаПро"', date: '2024-01-22', status: 'Ожидает', amount: null },
+ { id: 'REF-005', client: 'ИП Петрова М.В.', date: '2024-01-25', status: 'Конвертирован', amount: 12300 },
+ ],
+ sales: [
+ { id: 'SALE-001', client: 'ООО "Альфа Строй"', agent: 'Алексей Петров', amount: 15000, commission: 15, status: 'Выплачено' },
+ { id: 'SALE-002', client: 'ООО "БетаТех"', agent: 'Мария Сидорова', amount: 8500, commission: 15, status: 'Ожидает' },
+ { id: 'SALE-003', client: 'ИП Петрова М.В.', agent: 'Дмитрий Козлов', amount: 12300, commission: 15, status: 'Выплачено' },
+ { id: 'SALE-004', client: 'ООО "ДельтаГрупп"', agent: 'Игорь Смирнов', amount: 19200, commission: 15, status: 'Выплачено' },
+ { id: 'SALE-005', client: 'ИП Сидоров В.П.', agent: 'Елена Волкова', amount: 6700, commission: 15, status: 'Ожидает' },
+ ],
+ payouts: [
+ { id: 'PAY-001', amount: 18500, date: '2024-01-25', status: 'Завершена', method: 'Банк' },
+ { id: 'PAY-002', amount: 12300, date: '2024-01-20', status: 'Завершена', method: 'Крипто' },
+ { id: 'PAY-003', amount: 8900, date: '2024-01-15', status: 'Ожидается', method: 'Банк' },
+ { id: 'PAY-004', amount: 22100, date: '2024-01-10', status: 'Завершена', method: 'Банк' },
+ { id: 'PAY-005', amount: 15600, date: '2024-01-05', status: 'Ошибка', method: 'Крипто' },
+ ]
+};
+
+export default mockData;
\ No newline at end of file
diff --git a/src/styles/billing.module.css b/src/styles/billing.module.css
new file mode 100644
index 0000000..8c5ac21
--- /dev/null
+++ b/src/styles/billing.module.css
@@ -0,0 +1,145 @@
+.billingPage {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+}
+.title {
+ font-size: 28px;
+ font-weight: bold;
+ color: #111827;
+}
+.metricsGrid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 20px;
+}
+@media (min-width: 768px) {
+ .metricsGrid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+.grid2 {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 20px;
+}
+@media (min-width: 1024px) {
+ .grid2 {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+.payoutFormBlock, .pieChartBlock {
+ background: #fff;
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ padding: 24px;
+}
+.blockTitle {
+ font-size: 18px;
+ font-weight: 500;
+ color: #111827;
+ margin-bottom: 16px;
+}
+.formGroup {
+ margin-bottom: 16px;
+}
+.formGroup label {
+ display: block;
+ font-size: 14px;
+ color: #374151;
+ margin-bottom: 6px;
+}
+.formGroup input,
+.formGroup select {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ font-size: 15px;
+ outline: none;
+ transition: border 0.2s;
+}
+.formGroup input:focus,
+.formGroup select:focus {
+ border-color: #2563eb;
+}
+.hint {
+ font-size: 12px;
+ color: #6b7280;
+ margin-top: 4px;
+}
+.payoutBtn {
+ width: 100%;
+ background: #2563eb;
+ color: #fff;
+ border: none;
+ border-radius: 8px;
+ padding: 12px 0;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ margin-top: 8px;
+ transition: background 0.2s;
+}
+.payoutBtn:hover {
+ background: #1d4ed8;
+}
+.tableBlock {
+ background: #fff;
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ margin-top: 16px;
+ padding-bottom: 8px;
+}
+.tableTitle {
+ font-size: 18px;
+ font-weight: 500;
+ color: #111827;
+ padding: 24px 24px 0 24px;
+}
+.statusDone {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #d1fae5;
+ color: #065f46;
+ font-size: 13px;
+ font-weight: 600;
+}
+.statusPending {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #fef3c7;
+ color: #92400e;
+ font-size: 13px;
+ font-weight: 600;
+}
+.statusError {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #fee2e2;
+ color: #991b1b;
+ font-size: 13px;
+ font-weight: 600;
+}
+@media (max-width: 600px) {
+ .metricsGrid {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ }
+ .grid2 {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ }
+ .payoutFormBlock, .pieChartBlock {
+ padding: 12px;
+ }
+ .tableTitle {
+ padding: 16px 8px 0 8px;
+ }
+ .tableBlock {
+ padding-bottom: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/dashboard.module.css b/src/styles/dashboard.module.css
new file mode 100644
index 0000000..9f96561
--- /dev/null
+++ b/src/styles/dashboard.module.css
@@ -0,0 +1,107 @@
+.dashboard {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+}
+.title {
+ font-size: 28px;
+ font-weight: bold;
+ color: #111827;
+ margin-bottom: 8px;
+}
+.metricsFlex {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+}
+@media (min-width: 768px) {
+ .metricsFlex {
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ }
+}
+@media (min-width: 1024px) {
+ .metricsFlex {
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ }
+}
+.metricCard {
+ min-width: 200px;
+}
+.chartsGrid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 20px;
+}
+@media (min-width: 1024px) {
+ .chartsGrid {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+.chartStub {
+ background: #fff;
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ min-height: 220px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #6b7280;
+ font-size: 18px;
+}
+.tableBlock {
+ background: #fff;
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ margin-top: 16px;
+ padding-bottom: 8px;
+}
+.tableTitle {
+ font-size: 18px;
+ font-weight: 500;
+ color: #111827;
+ padding: 24px 24px 0 24px;
+}
+.statusPaid {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #d1fae5;
+ color: #065f46;
+ font-size: 13px;
+ font-weight: 600;
+}
+.statusPending {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #fef3c7;
+ color: #92400e;
+ font-size: 13px;
+ font-weight: 600;
+}
+@media (max-width: 600px) {
+ .dashboard {
+ gap: 16px;
+ }
+ .metricsFlex {
+ flex-direction: column;
+ gap: 12px;
+ }
+ .chartsGrid {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ }
+ .chartStub {
+ min-height: 140px;
+ font-size: 15px;
+ }
+ .tableTitle {
+ padding: 16px 8px 0 8px;
+ font-size: 16px;
+ }
+ .tableBlock {
+ padding-bottom: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/metricCard.module.css b/src/styles/metricCard.module.css
new file mode 100644
index 0000000..eb52457
--- /dev/null
+++ b/src/styles/metricCard.module.css
@@ -0,0 +1,35 @@
+.card {
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 1px 2px rgba(16, 30, 54, 0.04);
+ border: 1px solid #e5e7eb;
+ padding: 24px;
+ min-width: 200px;
+}
+.title {
+ font-size: 14px;
+ font-weight: 500;
+ color: #6b7280;
+ margin-bottom: 8px;
+}
+.valueRow {
+ display: flex;
+ align-items: baseline;
+}
+.value {
+ font-size: 24px;
+ font-weight: bold;
+ color: #111827;
+}
+.positive {
+ margin-left: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ color: #16a34a;
+}
+.negative {
+ margin-left: 8px;
+ font-size: 14px;
+ font-weight: 500;
+ color: #dc2626;
+}
\ No newline at end of file
diff --git a/src/styles/navigation.module.css b/src/styles/navigation.module.css
new file mode 100644
index 0000000..a9062a4
--- /dev/null
+++ b/src/styles/navigation.module.css
@@ -0,0 +1,59 @@
+.nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: #fff;
+ border-bottom: 1px solid #e5e7eb;
+ height: 64px;
+ padding: 0 32px;
+ box-shadow: 0 1px 2px rgba(16, 30, 54, 0.04);
+}
+.logo {
+ font-size: 20px;
+ font-weight: bold;
+ color: #2563eb;
+}
+.links {
+ display: flex;
+ gap: 32px;
+}
+.link {
+ color: #6b7280;
+ font-size: 16px;
+ font-weight: 500;
+ text-decoration: none;
+ border-bottom: 2px solid transparent;
+ padding: 8px 0;
+ transition: color 0.2s, border 0.2s;
+}
+.link:hover {
+ color: #2563eb;
+ border-bottom: 2px solid #dbeafe;
+}
+.active {
+ color: #2563eb;
+ border-bottom: 2px solid #2563eb;
+ font-weight: 600;
+}
+.profile {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+.avatar {
+ width: 32px;
+ height: 32px;
+ background: #2563eb;
+ color: #fff;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+ font-size: 15px;
+}
+.profileName {
+ color: #374151;
+ font-size: 15px;
+ font-weight: 500;
+}
\ No newline at end of file
diff --git a/src/styles/stat.module.css b/src/styles/stat.module.css
new file mode 100644
index 0000000..b338651
--- /dev/null
+++ b/src/styles/stat.module.css
@@ -0,0 +1,199 @@
+.statPage {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+}
+.headerRow {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.title {
+ font-size: 28px;
+ font-weight: bold;
+ color: #111827;
+}
+.exportBtn {
+ background: #2563eb;
+ color: #fff;
+ border: none;
+ border-radius: 8px;
+ padding: 10px 24px;
+ font-size: 15px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+.exportBtn:hover {
+ background: #1d4ed8;
+}
+.tabs {
+ display: flex;
+ gap: 32px;
+ border-bottom: 1.5px solid #e5e7eb;
+}
+.tab {
+ background: none;
+ border: none;
+ font-size: 16px;
+ color: #6b7280;
+ font-weight: 500;
+ padding: 12px 0;
+ border-bottom: 2px solid transparent;
+ cursor: pointer;
+ transition: color 0.2s, border 0.2s;
+}
+.tab:hover {
+ color: #2563eb;
+ border-bottom: 2px solid #dbeafe;
+}
+.activeTab {
+ color: #2563eb;
+ border: none;
+ border-bottom: 2px solid #2563eb;
+ font-weight: 600;
+ background: none;
+}
+.filters {
+ display: grid;
+ grid-template-columns: repeat(1, 1fr);
+ gap: 16px;
+ background: #fff;
+ border-radius: 12px;
+ border: 1px solid #e5e7eb;
+ padding: 20px;
+}
+@media (min-width: 768px) {
+ .filters {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+.filters label {
+ display: block;
+ font-size: 14px;
+ color: #374151;
+ margin-bottom: 6px;
+}
+.filters input,
+.filters select {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid #d1d5db;
+ border-radius: 6px;
+ font-size: 15px;
+ outline: none;
+ transition: border 0.2s;
+ background: #fff !important;
+ color: #111827 !important;
+}
+.filters input:focus,
+.filters select:focus {
+ border-color: #2563eb;
+}
+.filters input:-webkit-autofill,
+.filters input:-webkit-autofill:focus,
+.filters input:-webkit-autofill:hover,
+.filters input:-webkit-autofill:active {
+ -webkit-box-shadow: 0 0 0 1000px #fff inset !important;
+ box-shadow: 0 0 0 1000px #fff inset !important;
+ -webkit-text-fill-color: #111827 !important;
+ color: #111827 !important;
+ transition: background-color 5000s ease-in-out 0s;
+}
+.filterBtnWrap {
+ display: flex;
+ align-items: end;
+}
+.filterBtn {
+ width: 100%;
+ background: #f3f4f6;
+ color: #374151;
+ border: none;
+ border-radius: 8px;
+ padding: 10px 0;
+ font-size: 15px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+.filterBtn:hover {
+ background: #e5e7eb;
+}
+.tabContent {
+ margin-top: 12px;
+}
+.statusActive {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #d1fae5;
+ color: #065f46;
+ font-size: 13px;
+ font-weight: 600;
+}
+.statusInactive {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #fee2e2;
+ color: #991b1b;
+ font-size: 13px;
+ font-weight: 600;
+}
+.statusPending {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ background: #fef3c7;
+ color: #92400e;
+ font-size: 13px;
+ font-weight: 600;
+}
+@media (max-width: 600px) {
+ .statPage {
+ gap: 16px;
+ }
+ .headerRow {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8px;
+ }
+ .tabs {
+ gap: 12px;
+ }
+ .filters {
+ grid-template-columns: 1fr;
+ gap: 12px;
+ padding: 12px;
+ }
+ .tabContent {
+ margin-top: 8px;
+ }
+ .tableTitle {
+ padding: 16px 8px 0 8px;
+ font-size: 16px;
+ }
+}
+
+/* Стили для иконки календаря в input[type='date'] */
+.filters input[type="date"]::-webkit-calendar-picker-indicator {
+ filter: invert(0.3) sepia(1) saturate(5) hue-rotate(180deg);
+ /* Делает иконку тёмной, можно скорректировать под нужный цвет */
+}
+.filters input[type="date"]::-webkit-input-placeholder {
+ color: #6b7280;
+}
+.filters input[type="date"]::-moz-placeholder {
+ color: #6b7280;
+}
+.filters input[type="date"]:-ms-input-placeholder {
+ color: #6b7280;
+}
+.filters input[type="date"]::placeholder {
+ color: #6b7280;
+}
+
+/* Для Firefox */
+.filters input[type="date"]::-moz-focus-inner {
+ color-scheme: dark;
+}
\ No newline at end of file
diff --git a/src/styles/table.module.css b/src/styles/table.module.css
new file mode 100644
index 0000000..fc7154c
--- /dev/null
+++ b/src/styles/table.module.css
@@ -0,0 +1,40 @@
+.tableWrapper {
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 1px 2px rgba(16, 30, 54, 0.04);
+ border: 1px solid #e5e7eb;
+ overflow: hidden;
+}
+.overflowX {
+ overflow-x: auto;
+}
+.table {
+ min-width: 100%;
+ border-collapse: collapse;
+}
+.thead {
+ background: #f9fafb;
+}
+.th {
+ padding: 12px 24px;
+ text-align: left;
+ font-size: 12px;
+ font-weight: 600;
+ color: #6b7280;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ border-bottom: 1px solid #e5e7eb;
+}
+.tbody tr {
+ transition: background 0.2s;
+}
+.tbody tr:hover {
+ background: #f3f4f6;
+}
+.tbody td {
+ padding: 12px 24px;
+ font-size: 14px;
+ color: #111827;
+ border-bottom: 1px solid #e5e7eb;
+ white-space: nowrap;
+}
\ No newline at end of file