Compare commits
7 Commits
f051e3f5aa
...
2ba25427de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ba25427de | ||
|
|
709e6d4238 | ||
|
|
1ced9ac1ea | ||
|
|
629451c13d | ||
|
|
d6ac1044fe | ||
|
|
752208da02 | ||
|
|
df1ab64c97 |
446
package-lock.json
generated
446
package-lock.json
generated
@@ -15,7 +15,9 @@
|
|||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
"mysql2": "^3.11.0",
|
"mysql2": "^3.11.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"recharts": "^3.8.1",
|
||||||
|
"tsx": "^4.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
@@ -25,7 +27,6 @@
|
|||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"concurrently": "^9.1.0",
|
"concurrently": "^9.1.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"tsx": "^4.21.0",
|
|
||||||
"typescript": "~5.8.2",
|
"typescript": "~5.8.2",
|
||||||
"vite": "^6.2.0"
|
"vite": "^6.2.0"
|
||||||
}
|
}
|
||||||
@@ -319,7 +320,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -336,7 +336,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -353,7 +352,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -370,7 +368,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -387,7 +384,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -404,7 +400,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -421,7 +416,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -438,7 +432,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -455,7 +448,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -472,7 +464,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -489,7 +480,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -506,7 +496,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -523,7 +512,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -540,7 +528,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -557,7 +544,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -574,7 +560,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -591,7 +576,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -608,7 +592,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -625,7 +608,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -642,7 +624,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -659,7 +640,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -676,7 +656,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -693,7 +672,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -710,7 +688,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -727,7 +704,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -744,7 +720,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -816,6 +791,42 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
|
||||||
|
"integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"@standard-schema/utils": "^0.3.0",
|
||||||
|
"immer": "^11.0.0",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||||
|
"version": "11.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
|
||||||
|
"integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-rc.3",
|
"version": "1.0.0-rc.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
|
||||||
@@ -1173,6 +1184,18 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/utils": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.2.2",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
|
||||||
@@ -1490,6 +1513,69 @@
|
|||||||
"@babel/types": "^7.28.2"
|
"@babel/types": "^7.28.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-array": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-color": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-ease": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-interpolate": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-color": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-path": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-scale": {
|
||||||
|
"version": "4.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||||
|
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-time": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-shape": {
|
||||||
|
"version": "3.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
|
||||||
|
"integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-path": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-time": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/d3-timer": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -1510,7 +1596,7 @@
|
|||||||
"version": "19.2.14",
|
"version": "19.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
@@ -1526,6 +1612,12 @@
|
|||||||
"@types/react": "^19.2.0"
|
"@types/react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-react": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
||||||
@@ -1695,6 +1787,15 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -1751,9 +1852,130 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-array": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"internmap": "1 - 2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-color": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-ease": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-format": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-interpolate": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-color": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-path": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-scale": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2.10.0 - 3",
|
||||||
|
"d3-format": "1 - 3",
|
||||||
|
"d3-interpolate": "1.2.0 - 3",
|
||||||
|
"d3-time": "2.1.1 - 3",
|
||||||
|
"d3-time-format": "2 - 4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-shape": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-path": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-array": "2 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-time-format": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"d3-time": "1 - 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/d3-timer": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
@@ -1772,6 +1994,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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/denque": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
@@ -1831,11 +2059,20 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-toolkit": {
|
||||||
|
"version": "1.45.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
|
||||||
|
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"docs",
|
||||||
|
"benchmarks"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.4",
|
"version": "0.27.4",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
||||||
"integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
|
"integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -1883,6 +2120,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
@@ -1932,7 +2175,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -1976,7 +2218,6 @@
|
|||||||
"version": "4.13.7",
|
"version": "4.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
|
||||||
"integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
|
"integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"resolve-pkg-maps": "^1.0.0"
|
"resolve-pkg-maps": "^1.0.0"
|
||||||
@@ -2027,6 +2268,25 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz",
|
||||||
|
"integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/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-fullwidth-code-point": {
|
"node_modules/is-fullwidth-code-point": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
@@ -2575,6 +2835,36 @@
|
|||||||
"react": "^19.2.4"
|
"react": "^19.2.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-is": {
|
||||||
|
"version": "19.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
|
||||||
|
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25 || ^19",
|
||||||
|
"react": "^18.0 || ^19",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
||||||
@@ -2585,6 +2875,51 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/recharts": {
|
||||||
|
"version": "3.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz",
|
||||||
|
"integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"www"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^1.9.0 || 2.x.x",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"decimal.js-light": "^2.5.1",
|
||||||
|
"es-toolkit": "^1.39.3",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"immer": "^10.1.1",
|
||||||
|
"react-redux": "8.x.x || 9.x.x",
|
||||||
|
"reselect": "5.1.1",
|
||||||
|
"tiny-invariant": "^1.3.3",
|
||||||
|
"use-sync-external-store": "^1.2.2",
|
||||||
|
"victory-vendor": "^37.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@@ -2595,11 +2930,16 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve-pkg-maps": {
|
"node_modules/resolve-pkg-maps": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||||
@@ -2785,6 +3125,12 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
@@ -2822,7 +3168,6 @@
|
|||||||
"version": "4.21.0",
|
"version": "4.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "~0.27.0",
|
"esbuild": "~0.27.0",
|
||||||
@@ -2889,6 +3234,37 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/victory-vendor": {
|
||||||
|
"version": "37.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||||
|
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
|
||||||
|
"license": "MIT AND ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/d3-array": "^3.0.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"@types/d3-interpolate": "^3.0.1",
|
||||||
|
"@types/d3-scale": "^4.0.2",
|
||||||
|
"@types/d3-shape": "^3.1.0",
|
||||||
|
"@types/d3-time": "^3.0.0",
|
||||||
|
"@types/d3-timer": "^3.0.0",
|
||||||
|
"d3-array": "^3.1.6",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"d3-interpolate": "^3.0.1",
|
||||||
|
"d3-scale": "^4.0.2",
|
||||||
|
"d3-shape": "^3.1.0",
|
||||||
|
"d3-time": "^3.0.0",
|
||||||
|
"d3-timer": "^3.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.4.1",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||||
|
|||||||
17
package.json
17
package.json
@@ -12,25 +12,26 @@
|
|||||||
"lint": "tsc --noEmit"
|
"lint": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.0.0",
|
"@hono/node-server": "^1.13.0",
|
||||||
"react-dom": "^19.0.0",
|
"dotenv": "^16.4.0",
|
||||||
|
"hono": "^4.7.0",
|
||||||
"lucide-react": "^0.546.0",
|
"lucide-react": "^0.546.0",
|
||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
"hono": "^4.7.0",
|
|
||||||
"@hono/node-server": "^1.13.0",
|
|
||||||
"mysql2": "^3.11.0",
|
"mysql2": "^3.11.0",
|
||||||
"dotenv": "^16.4.0",
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"recharts": "^3.8.1",
|
||||||
"tsx": "^4.21.0"
|
"tsx": "^4.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"concurrently": "^9.1.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"typescript": "~5.8.2",
|
"typescript": "~5.8.2",
|
||||||
"vite": "^6.2.0",
|
"vite": "^6.2.0"
|
||||||
"concurrently": "^9.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1745
src/App.tsx
1745
src/App.tsx
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import type {
|
|||||||
DeptGroup,
|
DeptGroup,
|
||||||
RegionGroup,
|
RegionGroup,
|
||||||
CustomerStats,
|
CustomerStats,
|
||||||
|
RegionalInventoryStats,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
const BASE = '/api/vehicles';
|
const BASE = '/api/vehicles';
|
||||||
@@ -69,6 +70,14 @@ export async function fetchCustomerStats(): Promise<CustomerStats[]> {
|
|||||||
return fetchJson<CustomerStats[]>(`${BASE}/customer-stats`);
|
return fetchJson<CustomerStats[]>(`${BASE}/customer-stats`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchInventoryStats(): Promise<RegionalInventoryStats[]> {
|
||||||
|
return fetchJson<RegionalInventoryStats[]>(`${BASE}/inventory-stats`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRegionChart(groupBy: string, top = 8): Promise<{ name: string; value: number }[]> {
|
||||||
|
return fetchJson<{ name: string; value: number }[]>(`${BASE}/region-chart?groupBy=${groupBy}&top=${top}`);
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchWeeklyDetail(type: string): Promise<WeeklyDetailItem[]> {
|
export async function fetchWeeklyDetail(type: string): Promise<WeeklyDetailItem[]> {
|
||||||
return fetchJson<WeeklyDetailItem[]>(`${BASE}/weekly-detail?type=${type}`);
|
return fetchJson<WeeklyDetailItem[]>(`${BASE}/weekly-detail?type=${type}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,15 +156,23 @@ function mapStatus(rentStatus: string | null): 'Operating' | 'Inventory' | 'Pend
|
|||||||
return 'Inventory';
|
return 'Inventory';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map ownership status
|
// Map ownership from truck_rent_status (rentStatusLabel)
|
||||||
// Actual DB values: 自有(0), 外租(1), 挂靠(2)
|
// DB values: 自营(1), 租赁(2), 挂靠(8) → these are the operating subtypes
|
||||||
function mapOwnership(ascriptionLabel: string | null): string {
|
function mapOwnership(rentStatusLabel: string | null): string {
|
||||||
if (!ascriptionLabel) return 'Self';
|
if (!rentStatusLabel) return 'Unknown';
|
||||||
const s = ascriptionLabel.trim();
|
const s = rentStatusLabel.trim();
|
||||||
if (s === '自有') return 'Self';
|
if (s === '自营') return 'Self';
|
||||||
if (s === '外租') return 'Leased';
|
if (s === '租赁') return 'Leased';
|
||||||
if (s === '挂靠') return 'Hanging';
|
if (s === '挂靠') return 'Hanging';
|
||||||
return 'Self';
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve city name: clean brackets, fallback to province for municipalities
|
||||||
|
function resolveCity(city: string | null, province: string | null): string {
|
||||||
|
const c = (city || '').replace(/[\[\]]/g, '').trim();
|
||||||
|
if (c) return c;
|
||||||
|
const p = (province || '').replace(/[\[\]]/g, '').trim();
|
||||||
|
return p || '其他';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive vehicle type category from model label
|
// Derive vehicle type category from model label
|
||||||
@@ -268,7 +276,7 @@ function transformRow(row: VehicleRow): Vehicle {
|
|||||||
province: row.省,
|
province: row.省,
|
||||||
city: row.市,
|
city: row.市,
|
||||||
status: mapStatus(row.车辆租赁状态Label),
|
status: mapStatus(row.车辆租赁状态Label),
|
||||||
ownership: mapOwnership(row.车辆归属状态Label),
|
ownership: mapOwnership(row.车辆租赁状态Label),
|
||||||
rentCompany: row.租赁公司 || '',
|
rentCompany: row.租赁公司 || '',
|
||||||
contractNo: row.合同编码,
|
contractNo: row.合同编码,
|
||||||
customerName: row.客户名称,
|
customerName: row.客户名称,
|
||||||
@@ -639,68 +647,168 @@ app.get('/inventory-analysis', async (c) => {
|
|||||||
return c.json(result);
|
return c.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/dept-stats — department & manager breakdown
|
// GET /api/vehicles/dept-stats — department & manager breakdown with mileage/attendance
|
||||||
app.get('/dept-stats', async (c) => {
|
app.get('/dept-stats', async (c) => {
|
||||||
const vehicles = await getVehicles();
|
const vehicles = await getVehicles();
|
||||||
const withManager = vehicles.filter((v) => v.customerManager);
|
const withManager = vehicles.filter((v) => v.status === 'Operating');
|
||||||
|
|
||||||
|
// Query mileage data: last 30 days attendance & avg mileage per plate
|
||||||
|
// + today's mileage for idle detection
|
||||||
|
const [[mileageRows], [todayRows]] = await Promise.all([
|
||||||
|
pool.query<any[]>(`
|
||||||
|
SELECT plateNumber,
|
||||||
|
COUNT(CASE WHEN dayMileage > 0 THEN 1 END) AS activeDays,
|
||||||
|
COUNT(*) AS totalDays,
|
||||||
|
AVG(dayMileage) AS avgMileage
|
||||||
|
FROM ln_vehicle_day_mileage
|
||||||
|
WHERE dates >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||||
|
GROUP BY plateNumber
|
||||||
|
`),
|
||||||
|
pool.query<any[]>(`
|
||||||
|
SELECT plateNumber, dayMileage
|
||||||
|
FROM ln_vehicle_day_mileage
|
||||||
|
WHERE dates = CURDATE()
|
||||||
|
`),
|
||||||
|
]);
|
||||||
|
const mileageMap = new Map<string, { activeDays: number; totalDays: number; avgMileage: number }>();
|
||||||
|
for (const row of mileageRows as any[]) {
|
||||||
|
mileageMap.set(row.plateNumber, {
|
||||||
|
activeDays: Number(row.activeDays),
|
||||||
|
totalDays: Number(row.totalDays),
|
||||||
|
avgMileage: Number(row.avgMileage),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const todayMileageMap = new Map<string, number>();
|
||||||
|
for (const row of todayRows as any[]) {
|
||||||
|
todayMileageMap.set(row.plateNumber, Number(row.dayMileage));
|
||||||
|
}
|
||||||
|
|
||||||
const deptMap = new Map<string, Map<string, Vehicle[]>>();
|
const deptMap = new Map<string, Map<string, Vehicle[]>>();
|
||||||
for (const v of withManager) {
|
for (const v of withManager) {
|
||||||
const dept = v.departmentName || '未分配';
|
const dept = v.departmentName || '未分配';
|
||||||
const mgr = v.customerManager!;
|
const mgr = v.customerManager || '未分配';
|
||||||
if (!deptMap.has(dept)) deptMap.set(dept, new Map());
|
if (!deptMap.has(dept)) deptMap.set(dept, new Map());
|
||||||
const mgrMap = deptMap.get(dept)!;
|
const mgrMap = deptMap.get(dept)!;
|
||||||
if (!mgrMap.has(mgr)) mgrMap.set(mgr, []);
|
if (!mgrMap.has(mgr)) mgrMap.set(mgr, []);
|
||||||
mgrMap.get(mgr)!.push(v);
|
mgrMap.get(mgr)!.push(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute attendance & mileage for a set of vehicles
|
||||||
|
const getMileageStats = (vList: Vehicle[]) => {
|
||||||
|
let totalActive = 0;
|
||||||
|
let totalDays = 0;
|
||||||
|
let totalMileage = 0;
|
||||||
|
let count = 0;
|
||||||
|
for (const v of vList) {
|
||||||
|
const m = mileageMap.get(v.plateNumber);
|
||||||
|
if (m) {
|
||||||
|
totalActive += m.activeDays;
|
||||||
|
totalDays += m.totalDays;
|
||||||
|
totalMileage += m.avgMileage;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
attendanceRate: totalDays > 0 ? Math.round((totalActive / totalDays) * 1000) / 10 : 0,
|
||||||
|
avgMileage: count > 0 ? Math.round(totalMileage / count) : 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const result = Array.from(deptMap.entries()).map(([department, mgrMap]) => {
|
const result = Array.from(deptMap.entries()).map(([department, mgrMap]) => {
|
||||||
const allDeptVehicles = Array.from(mgrMap.values()).flat();
|
const allDeptVehicles = Array.from(mgrMap.values()).flat();
|
||||||
|
const deptMileage = getMileageStats(allDeptVehicles);
|
||||||
const managers = Array.from(mgrMap.entries())
|
const managers = Array.from(mgrMap.entries())
|
||||||
.map(([manager, mvs]) => ({ manager, department, ...countByType(mvs) }))
|
.map(([manager, mvs]) => ({ manager, department, ...countByType(mvs) }))
|
||||||
.sort((a, b) => b.total - a.total);
|
.sort((a, b) => b.total - a.total);
|
||||||
return {
|
return {
|
||||||
department,
|
department,
|
||||||
totalAssets: allDeptVehicles.length,
|
totalAssets: allDeptVehicles.length,
|
||||||
operatingCount: allDeptVehicles.filter((v) => v.status === 'Operating').length,
|
operatingCount: allDeptVehicles.filter((v) => (todayMileageMap.get(v.plateNumber) || 0) > 0).length,
|
||||||
idleCount: allDeptVehicles.filter((v) => v.status !== 'Operating').length,
|
idleCount: allDeptVehicles.filter((v) => (todayMileageMap.get(v.plateNumber) || 0) === 0).length,
|
||||||
|
attendanceRate: deptMileage.attendanceRate,
|
||||||
|
avgMileage: deptMileage.avgMileage,
|
||||||
managers,
|
managers,
|
||||||
};
|
};
|
||||||
}).sort((a, b) => b.totalAssets - a.totalAssets);
|
}).sort((a, b) => {
|
||||||
|
// 按部门名中的数字排序(业务一部=1, 业务二部=2, ...)
|
||||||
|
const numMap: Record<string, number> = { '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10 };
|
||||||
|
const getNum = (name: string) => {
|
||||||
|
const m = name.match(/[一二三四五六七八九十]/);
|
||||||
|
return m ? (numMap[m[0]] || 99) : 99;
|
||||||
|
};
|
||||||
|
return getNum(a.department) - getNum(b.department);
|
||||||
|
});
|
||||||
|
|
||||||
return c.json(result);
|
return c.json(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/region-stats — macro-region breakdown for operating vehicles
|
// GET /api/vehicles/region-stats — macro-region with city drill-down
|
||||||
app.get('/region-stats', async (c) => {
|
app.get('/region-stats', async (c) => {
|
||||||
const vehicles = await getVehicles();
|
const vehicles = await getVehicles();
|
||||||
const operating = vehicles.filter((v) => v.status === 'Operating');
|
const operating = vehicles.filter((v) => v.status === 'Operating' || v.status === 'Pending');
|
||||||
|
|
||||||
const regionMap = new Map<string, Vehicle[]>();
|
const regionCityMap = new Map<string, Map<string, Vehicle[]>>();
|
||||||
for (const v of operating) {
|
for (const v of operating) {
|
||||||
const region = mapMacroRegion(v.province, v.city);
|
const region = mapMacroRegion(v.province, v.city);
|
||||||
if (!regionMap.has(region)) regionMap.set(region, []);
|
const city = resolveCity(v.city, v.province);
|
||||||
regionMap.get(region)!.push(v);
|
if (!regionCityMap.has(region)) regionCityMap.set(region, new Map());
|
||||||
|
const cityMap = regionCityMap.get(region)!;
|
||||||
|
if (!cityMap.has(city)) cityMap.set(city, []);
|
||||||
|
cityMap.get(city)!.push(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTypeBreakdown = (vList: Vehicle[]) =>
|
||||||
|
['4.5T', '18T', '49T'].map((type) => {
|
||||||
|
const tv = vList.filter((v) => v.type === type);
|
||||||
|
return { type, total: tv.length, operating: tv.filter((v) => v.status === 'Operating').length, inventory: tv.filter((v) => v.status === 'Inventory').length, customers: Array.from(new Set(tv.map((v) => v.customerName).filter(Boolean))) as string[] };
|
||||||
|
}).filter((t) => t.total > 0);
|
||||||
|
|
||||||
const regionOrder = ['华东', '华南', '华北', '华中', '西南', '西北', '其他'];
|
const regionOrder = ['华东', '华南', '华北', '华中', '西南', '西北', '其他'];
|
||||||
const result = regionOrder
|
const result = regionOrder
|
||||||
.filter((r) => regionMap.has(r))
|
.filter((r) => regionCityMap.has(r))
|
||||||
.map((region) => {
|
.map((region) => {
|
||||||
const rv = regionMap.get(region)!;
|
const cityMap = regionCityMap.get(region)!;
|
||||||
const customers = Array.from(new Set(rv.map((v) => v.customerName).filter(Boolean))) as string[];
|
const allVehicles = Array.from(cityMap.values()).flat();
|
||||||
const typeBreakdown = ['4.5T', '18T', '49T'].map((type) => {
|
const customers = Array.from(new Set(allVehicles.map((v) => v.customerName).filter(Boolean))) as string[];
|
||||||
const typeVehicles = rv.filter((v) => v.type === type);
|
const allCities = Array.from(cityMap.entries())
|
||||||
return {
|
.map(([city, cv]) => ({
|
||||||
type,
|
city,
|
||||||
total: typeVehicles.length,
|
totalAssets: cv.length,
|
||||||
operating: typeVehicles.filter((v) => v.status === 'Operating').length,
|
operatingCount: cv.filter((v) => v.status === 'Operating').length,
|
||||||
inventory: typeVehicles.filter((v) => v.status === 'Inventory').length,
|
pendingCount: cv.filter((v) => v.status === 'Pending').length,
|
||||||
customers: Array.from(new Set(typeVehicles.map((v) => v.customerName).filter(Boolean))) as string[],
|
customers: Array.from(new Set(cv.map((v) => v.customerName).filter(Boolean))) as string[],
|
||||||
};
|
typeBreakdown: getTypeBreakdown(cv),
|
||||||
}).filter((t) => t.total > 0);
|
}))
|
||||||
|
.sort((a, b) => b.totalAssets - a.totalAssets);
|
||||||
|
|
||||||
return { region, totalAssets: rv.length, operatingCount: rv.filter((v) => v.status === 'Operating').length, inventoryCount: rv.filter((v) => v.status === 'Inventory').length, customers, typeBreakdown };
|
// Top 8 cities + merge rest into "其他"
|
||||||
|
const topCities = allCities.slice(0, 8);
|
||||||
|
const restCities = allCities.slice(8);
|
||||||
|
if (restCities.length > 0) {
|
||||||
|
const restVehicles = restCities.flatMap((c) => {
|
||||||
|
const key = c.city;
|
||||||
|
return cityMap.get(key) || [];
|
||||||
|
});
|
||||||
|
topCities.push({
|
||||||
|
city: '其他',
|
||||||
|
totalAssets: restCities.reduce((s, c) => s + c.totalAssets, 0),
|
||||||
|
operatingCount: restCities.reduce((s, c) => s + c.operatingCount, 0),
|
||||||
|
pendingCount: restCities.reduce((s, c) => s + (c.pendingCount || 0), 0),
|
||||||
|
customers: Array.from(new Set(restVehicles.map((v) => v.customerName).filter(Boolean))) as string[],
|
||||||
|
typeBreakdown: getTypeBreakdown(restVehicles),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const cities = topCities;
|
||||||
|
|
||||||
|
return {
|
||||||
|
region,
|
||||||
|
totalAssets: allVehicles.length,
|
||||||
|
operatingCount: allVehicles.filter((v) => v.status === 'Operating').length,
|
||||||
|
pendingCount: allVehicles.filter((v) => v.status === 'Pending').length,
|
||||||
|
customers,
|
||||||
|
typeBreakdown: getTypeBreakdown(allVehicles),
|
||||||
|
cities,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return c.json(result);
|
return c.json(result);
|
||||||
@@ -709,11 +817,11 @@ app.get('/region-stats', async (c) => {
|
|||||||
// GET /api/vehicles/customer-stats — per-customer breakdown for operating vehicles
|
// GET /api/vehicles/customer-stats — per-customer breakdown for operating vehicles
|
||||||
app.get('/customer-stats', async (c) => {
|
app.get('/customer-stats', async (c) => {
|
||||||
const vehicles = await getVehicles();
|
const vehicles = await getVehicles();
|
||||||
const operating = vehicles.filter((v) => v.status === 'Operating' && v.customerName);
|
const operating = vehicles.filter((v) => v.status === 'Operating');
|
||||||
|
|
||||||
const custMap = new Map<string, Vehicle[]>();
|
const custMap = new Map<string, Vehicle[]>();
|
||||||
for (const v of operating) {
|
for (const v of operating) {
|
||||||
const cust = v.customerName!;
|
const cust = v.customerName || '未分配客户';
|
||||||
if (!custMap.has(cust)) custMap.set(cust, []);
|
if (!custMap.has(cust)) custMap.set(cust, []);
|
||||||
custMap.get(cust)!.push(v);
|
custMap.get(cust)!.push(v);
|
||||||
}
|
}
|
||||||
@@ -742,7 +850,8 @@ const VEHICLE_TYPE_FILTERS: Record<string, (v: Vehicle) => boolean> = {
|
|||||||
'4.5T冷链': (v) => v.type === '4.5T' && v.model.includes('冷链'),
|
'4.5T冷链': (v) => v.type === '4.5T' && v.model.includes('冷链'),
|
||||||
'18T': (v) => v.type === '18T',
|
'18T': (v) => v.type === '18T',
|
||||||
'49T': (v) => v.type === '49T',
|
'49T': (v) => v.type === '49T',
|
||||||
'其他': (v) => !['4.5T', '18T', '49T'].includes(v.type),
|
'挂车': (v) => v.type === '挂车' || v.model.includes('挂车'),
|
||||||
|
'其他': (v) => classifyVehicleType(v) === 'other',
|
||||||
};
|
};
|
||||||
|
|
||||||
// GET /api/vehicles/list — flat list with optional filters
|
// GET /api/vehicles/list — flat list with optional filters
|
||||||
@@ -761,10 +870,15 @@ app.get('/list', async (c) => {
|
|||||||
filtered = filtered.filter((v) => v.model === model);
|
filtered = filtered.filter((v) => v.model === model);
|
||||||
}
|
}
|
||||||
if (location && location !== 'All') {
|
if (location && location !== 'All') {
|
||||||
// Support both display region names and inventory region names
|
// Support: display regions (嘉兴/广东), inventory regions (江浙沪), cities (嘉兴市), macro regions (华东/华南)
|
||||||
|
const macroRegions = ['华东', '华南', '华北', '华中', '西南', '西北'];
|
||||||
|
if (macroRegions.includes(location) || location === '其他') {
|
||||||
|
filtered = filtered.filter((v) => mapMacroRegion(v.province, v.city) === location);
|
||||||
|
} else {
|
||||||
const inventoryRegionMap: Record<string, string> = { '江浙沪': '嘉兴', '其它': '其他' };
|
const inventoryRegionMap: Record<string, string> = { '江浙沪': '嘉兴', '其它': '其他' };
|
||||||
const mappedLocation = inventoryRegionMap[location] || location;
|
const mappedLocation = inventoryRegionMap[location] || location;
|
||||||
filtered = filtered.filter((v) => v.location === mappedLocation);
|
filtered = filtered.filter((v) => v.location === mappedLocation || v.city === location || resolveCity(v.city, v.province) === location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (status && status !== 'All') {
|
if (status && status !== 'All') {
|
||||||
filtered = filtered.filter((v) => v.status === status);
|
filtered = filtered.filter((v) => v.status === status);
|
||||||
@@ -777,10 +891,10 @@ app.get('/list', async (c) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (manager) {
|
if (manager) {
|
||||||
filtered = filtered.filter((v) => v.customerManager === manager);
|
filtered = filtered.filter((v) => manager === '未分配' ? !v.customerManager : v.customerManager === manager);
|
||||||
}
|
}
|
||||||
if (customer) {
|
if (customer) {
|
||||||
filtered = filtered.filter((v) => v.customerName === customer);
|
filtered = filtered.filter((v) => customer === '未分配客户' ? !v.customerName : v.customerName === customer);
|
||||||
}
|
}
|
||||||
if (isColdChain !== undefined) {
|
if (isColdChain !== undefined) {
|
||||||
const wantCold = isColdChain === 'true';
|
const wantCold = isColdChain === 'true';
|
||||||
@@ -795,6 +909,7 @@ app.get('/list', async (c) => {
|
|||||||
filtered.map((v) => ({
|
filtered.map((v) => ({
|
||||||
id: v.id,
|
id: v.id,
|
||||||
plateNumber: v.plateNumber,
|
plateNumber: v.plateNumber,
|
||||||
|
vin: v.vin,
|
||||||
type: v.type,
|
type: v.type,
|
||||||
model: v.model,
|
model: v.model,
|
||||||
location: v.location,
|
location: v.location,
|
||||||
@@ -805,10 +920,51 @@ app.get('/list', async (c) => {
|
|||||||
contractNo: v.contractNo,
|
contractNo: v.contractNo,
|
||||||
customerName: v.customerName,
|
customerName: v.customerName,
|
||||||
subjectOrg: v.subjectOrg,
|
subjectOrg: v.subjectOrg,
|
||||||
|
departmentName: v.departmentName,
|
||||||
|
customerManager: v.customerManager,
|
||||||
|
brandLabel: v.brandLabel,
|
||||||
|
orgName: v.orgName,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/vehicles/inventory-stats — grouped inventory stats for the inventory statistics section
|
||||||
|
app.get('/inventory-stats', async (c) => {
|
||||||
|
const vehicles = await getVehicles();
|
||||||
|
const inventory = vehicles.filter((v) => v.status === 'Inventory');
|
||||||
|
|
||||||
|
const TYPE_NAME_MAP: Record<string, string> = {
|
||||||
|
t4_5: '4.5T普货',
|
||||||
|
t4_5c: '4.5T冷链',
|
||||||
|
t18: '18T',
|
||||||
|
t49: '49T',
|
||||||
|
trailer: '挂车',
|
||||||
|
other: '其他',
|
||||||
|
};
|
||||||
|
|
||||||
|
const groups = new Map<string, number>();
|
||||||
|
for (const v of inventory) {
|
||||||
|
const typeCategory = classifyVehicleType(v);
|
||||||
|
const typeName = TYPE_NAME_MAP[typeCategory];
|
||||||
|
const region = mapMacroRegion(v.province, v.city);
|
||||||
|
const city = resolveCity(v.city, v.province);
|
||||||
|
const brand = v.brandLabel || '未知';
|
||||||
|
const model = v.model;
|
||||||
|
const batch = v.contractNo || 'N/A';
|
||||||
|
const key = `${region}|${city}|${brand}|${typeName}|${model}|${batch}`;
|
||||||
|
groups.set(key, (groups.get(key) || 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = Array.from(groups.entries())
|
||||||
|
.map(([key, quantity]) => {
|
||||||
|
const [region, city, brand, type, model, batch] = key.split('|');
|
||||||
|
return { region, city, brand, type, model, batch, quantity };
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.quantity - a.quantity);
|
||||||
|
|
||||||
|
return c.json(result);
|
||||||
|
});
|
||||||
|
|
||||||
// GET /api/vehicles/weekly-detail?type=delivered|returned|replaced|pending
|
// GET /api/vehicles/weekly-detail?type=delivered|returned|replaced|pending
|
||||||
app.get('/weekly-detail', async (c) => {
|
app.get('/weekly-detail', async (c) => {
|
||||||
const type = c.req.query('type');
|
const type = c.req.query('type');
|
||||||
@@ -866,4 +1022,31 @@ app.get('/debug', async (c) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/vehicles/region-chart — aggregated chart data with top N + "其他"
|
||||||
|
app.get('/region-chart', async (c) => {
|
||||||
|
const vehicles = await getVehicles();
|
||||||
|
const operating = vehicles.filter((v) => v.status === 'Operating');
|
||||||
|
const groupBy = c.req.query('groupBy') || 'region'; // 'region' | 'city'
|
||||||
|
const top = Number(c.req.query('top')) || 8;
|
||||||
|
|
||||||
|
const counts = new Map<string, number>();
|
||||||
|
for (const v of operating) {
|
||||||
|
const key = groupBy === 'city' ? resolveCity(v.city, v.province) : mapMacroRegion(v.province, v.city);
|
||||||
|
counts.set(key, (counts.get(key) || 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分离"其他",对非"其他"排序取 Top N,剩余全部合入"其他"
|
||||||
|
const otherCount = counts.get('其他') || 0;
|
||||||
|
counts.delete('其他');
|
||||||
|
|
||||||
|
const sorted = Array.from(counts.entries())
|
||||||
|
.map(([name, value]) => ({ name, value }))
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
|
||||||
|
const result = sorted.slice(0, top);
|
||||||
|
const restTotal = sorted.slice(top).reduce((s, item) => s + item.value, 0) + otherCount;
|
||||||
|
if (restTotal > 0) result.push({ name: '其他', value: restTotal });
|
||||||
|
return c.json(result);
|
||||||
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
39
src/types.ts
39
src/types.ts
@@ -98,6 +98,7 @@ export interface InventoryTypeSummary {
|
|||||||
export interface VehicleListItem {
|
export interface VehicleListItem {
|
||||||
id: number;
|
id: number;
|
||||||
plateNumber: string;
|
plateNumber: string;
|
||||||
|
vin: string;
|
||||||
type: string;
|
type: string;
|
||||||
model: string;
|
model: string;
|
||||||
location: string;
|
location: string;
|
||||||
@@ -108,6 +109,20 @@ export interface VehicleListItem {
|
|||||||
contractNo: string | null;
|
contractNo: string | null;
|
||||||
customerName: string | null;
|
customerName: string | null;
|
||||||
subjectOrg: string | null;
|
subjectOrg: string | null;
|
||||||
|
departmentName: string | null;
|
||||||
|
customerManager: string | null;
|
||||||
|
brandLabel: string | null;
|
||||||
|
orgName: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegionalInventoryStats {
|
||||||
|
region: string;
|
||||||
|
city: string;
|
||||||
|
brand: string;
|
||||||
|
type: string;
|
||||||
|
model: string;
|
||||||
|
batch: string;
|
||||||
|
quantity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ManagerStats {
|
export interface ManagerStats {
|
||||||
@@ -127,16 +142,36 @@ export interface DeptGroup {
|
|||||||
totalAssets: number;
|
totalAssets: number;
|
||||||
operatingCount: number;
|
operatingCount: number;
|
||||||
idleCount: number;
|
idleCount: number;
|
||||||
|
attendanceRate: number;
|
||||||
|
avgMileage: number;
|
||||||
managers: ManagerStats[];
|
managers: ManagerStats[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RegionTypeBreakdown {
|
||||||
|
type: string;
|
||||||
|
total: number;
|
||||||
|
operating: number;
|
||||||
|
inventory: number;
|
||||||
|
customers: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegionCityGroup {
|
||||||
|
city: string;
|
||||||
|
totalAssets: number;
|
||||||
|
operatingCount: number;
|
||||||
|
pendingCount: number;
|
||||||
|
customers: string[];
|
||||||
|
typeBreakdown: RegionTypeBreakdown[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface RegionGroup {
|
export interface RegionGroup {
|
||||||
region: string;
|
region: string;
|
||||||
totalAssets: number;
|
totalAssets: number;
|
||||||
operatingCount: number;
|
operatingCount: number;
|
||||||
inventoryCount: number;
|
pendingCount: number;
|
||||||
customers: string[];
|
customers: string[];
|
||||||
typeBreakdown: { type: string; total: number; operating: number; inventory: number; customers: string[] }[];
|
typeBreakdown: RegionTypeBreakdown[];
|
||||||
|
cities: RegionCityGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomerStats {
|
export interface CustomerStats {
|
||||||
|
|||||||
Reference in New Issue
Block a user