diff --git a/components.json b/components.json deleted file mode 100644 index 5c23ec4..0000000 --- a/components.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "radix-nova", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/index.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "rtl": false, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "menuColor": "default", - "menuAccent": "subtle", - "registries": {} -} diff --git a/docs/asset-ui-migration.md b/docs/asset-ui-migration.md index 417cafb..71e9b06 100644 --- a/docs/asset-ui-migration.md +++ b/docs/asset-ui-migration.md @@ -1,4 +1,6 @@ -# 资产管理组件库迁移 +# 资产管理组件库迁移(历史记录) + +> 2026-09-23:本次组件库与视觉改造已按用户要求撤销。资产页面恢复到 9b0980e 的布局和样式,保留 AI 问数入口及库存统计、权限和下钻逻辑修复。以下为撤销前的实施与验收记录。 ## 范围与约束 diff --git a/package-lock.json b/package-lock.json index 58a326b..d0947d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,15 +9,11 @@ "version": "1.1.17", "dependencies": { "@amap/amap-jsapi-loader": "^1.0.1", - "@fontsource-variable/geist": "^5.3.0", "@hono/node-server": "^1.13.0", "@types/jsonwebtoken": "^9.0.10", - "ag-grid-community": "^36.2.0", - "ag-grid-react": "^36.2.0", "ali-oss": "^6.23.0", "class-variance-authority": "^0.7.1", "cn": "^0.4.0", - "date-fns": "^4.4.0", "dotenv": "^16.4.0", "echarts": "^6.1.0", "hono": "^4.7.0", @@ -28,12 +24,9 @@ "pg": "^8.22.0", "radix-ui": "^1.6.7", "react": "^19.0.0", - "react-day-picker": "^10.0.1", "react-dom": "^19.0.0", "recharts": "^3.8.1", - "shadcn": "^4.21.0", "tsx": "^4.21.0", - "tw-animate-css": "^1.4.0", "xlsx": "^0.18.5" }, "devDependencies": { @@ -60,6 +53,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -74,6 +68,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -83,6 +78,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -113,6 +109,7 @@ "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", @@ -125,22 +122,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", @@ -153,53 +139,21 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/traverse": "^7.29.7", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -213,6 +167,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -226,61 +181,21 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -290,6 +205,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -299,6 +215,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -308,6 +225,7 @@ "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", @@ -321,6 +239,7 @@ "version": "7.29.9", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.8" @@ -332,52 +251,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", @@ -410,48 +283,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.29.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.9.tgz", - "integrity": "sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-syntax-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", - "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/plugin-transform-modules-commonjs": "^7.29.7", - "@babel/plugin-transform-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/template": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -466,6 +302,7 @@ "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -484,6 +321,7 @@ "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -493,205 +331,6 @@ "node": ">=6.9.0" } }, - "node_modules/@date-fns/tz": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.5.0.tgz", - "integrity": "sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==", - "license": "MIT" - }, - "node_modules/@dotenvx/dotenvx": { - "version": "1.75.1", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", - "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@dotenvx/primitives": "^0.8.0", - "commander": "^11.1.0", - "conf": "^10.2.0", - "dotenv": "^17.2.1", - "enquirer": "^2.4.1", - "env-paths": "^2.2.1", - "execa": "^5.1.1", - "fdir": "^6.2.0", - "ignore": "^5.3.0", - "object-treeify": "1.1.33", - "open": "^8.4.2", - "picomatch": "^4.0.4", - "systeminformation": "^5.22.11", - "undici": "^7.11.0", - "which": "^4.0.0", - "yocto-spinner": "^1.1.0" - }, - "bin": { - "dotenvx": "src/cli/dotenvx.js" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@dotenvx/primitives": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@dotenvx/primitives/-/primitives-0.8.0.tgz", - "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==", - "license": "BSD-3-Clause" - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -1146,15 +785,6 @@ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, - "node_modules/@fontsource-variable/geist": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.3.0.tgz", - "integrity": "sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, "node_modules/@hono/node-server": { "version": "1.19.11", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", @@ -1171,6 +801,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1181,6 +812,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1191,6 +823,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1200,93 +833,20 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", - "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", - "license": "MIT", - "dependencies": { - "@hono/node-server": "^1.19.9 || ^2.0.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.2.1", - "express-rate-limit": "^8.2.1", - "hono": "^4.11.4", - "jose": "^6.1.3", - "json-schema-typed": "^8.0.2", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.25 || ^4.0", - "zod-to-json-schema": "^3.25.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - }, - "zod": { - "optional": false - } - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@radix-ui/number": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", @@ -3176,24 +2736,6 @@ "win32" ] }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT" - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3478,17 +3020,6 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, - "node_modules/@ts-morph/common": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", - "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.3", - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1" - } - }, "node_modules/@types/ali-oss": { "version": "6.23.3", "resolved": "https://registry.npmjs.org/@types/ali-oss/-/ali-oss-6.23.3.tgz", @@ -3674,12 +3205,6 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, - "node_modules/@types/validate-npm-package-name": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", - "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", - "license": "MIT" - }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -3701,19 +3226,6 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/address": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", @@ -3732,42 +3244,6 @@ "node": ">=0.8" } }, - "node_modules/ag-charts-types": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-14.2.0.tgz", - "integrity": "sha512-4pDV6/4zbzPN7t607Gr1o6DO9RhmPafQGr+hIpTJBaerbaMoC+WOO3v/wkxFbvCNrHKOexEKSfNTH7PaAR4UzA==", - "license": "MIT" - }, - "node_modules/ag-grid-community": { - "version": "36.2.0", - "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-36.2.0.tgz", - "integrity": "sha512-tK0Toj7t0fV/OMLPnDc0xlYKAqDbnCT3iJodYSn2TJ541caU8jPdYwWhMdW+AP2+pUNwFiJVgc04eJNfvBPsWw==", - "license": "MIT", - "dependencies": { - "ag-charts-types": "14.2.0", - "ag-stack": "36.2.0" - } - }, - "node_modules/ag-grid-react": { - "version": "36.2.0", - "resolved": "https://registry.npmjs.org/ag-grid-react/-/ag-grid-react-36.2.0.tgz", - "integrity": "sha512-7dsaR4Ga8/bJAOov7XJqxsDc1JAjUU5Bmh9+s4U9+/TKIdSsaVo53SuZb2yEFSB1elv3eh6aZP8a1fqGZehXwA==", - "license": "MIT", - "dependencies": { - "ag-grid-community": "36.2.0", - "prop-types": "^15.8.1" - }, - "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/ag-stack": { - "version": "36.2.0", - "resolved": "https://registry.npmjs.org/ag-stack/-/ag-stack-36.2.0.tgz", - "integrity": "sha512-f3XT2htFs9dUPcHni2z9ak69QNta5ODEpooHuuNvbjN98JkokpEqRQ9YLQHQJFdCcgi9Ybs/j3zkkvmNRamIPg==", - "license": "MIT" - }, "node_modules/agentkeepalive": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.3.tgz", @@ -3780,39 +3256,6 @@ "node": ">= 4.0.0" } }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/ali-oss": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/ali-oss/-/ali-oss-6.23.0.tgz", @@ -3849,19 +3292,11 @@ "node": ">=8" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3889,12 +3324,6 @@ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -3907,27 +3336,6 @@ "node": ">=10" } }, - "node_modules/ast-types": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.3.tgz", - "integrity": "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/atomically": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", - "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", - "license": "MIT", - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -3937,19 +3345,11 @@ "node": ">= 6.0.0" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/baseline-browser-mapping": { "version": "2.10.10", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz", "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==", + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -3958,77 +3358,17 @@ "node": ">=6.0.0" } }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/bowser": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==", "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4070,30 +3410,6 @@ "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", "license": "MIT" }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -4123,19 +3439,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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", - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001781", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4207,33 +3515,6 @@ "url": "https://polar.sh/cva" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4270,12 +3551,6 @@ "node": ">=20" } }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "license": "MIT" - }, "node_modules/codepage": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", @@ -4305,15 +3580,6 @@ "dev": true, "license": "MIT" }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/concurrently": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", @@ -4339,78 +3605,6 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/conf": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", - "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", - "license": "MIT", - "dependencies": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", - "atomically": "^1.7.0", - "debounce-fn": "^4.0.0", - "dot-prop": "^6.0.1", - "env-paths": "^2.2.1", - "json-schema-typed": "^7.0.3", - "onetime": "^5.1.2", - "pkg-up": "^3.1.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conf/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/conf/node_modules/json-schema-typed": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", - "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", - "license": "BSD-2-Clause" - }, - "node_modules/conf/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -4424,26 +3618,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/copy-to": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", @@ -4456,49 +3633,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -4511,53 +3645,6 @@ "node": ">=0.8" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -4686,16 +3773,6 @@ "node": ">=12" } }, - "node_modules/date-fns": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", - "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, "node_modules/dateformat": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", @@ -4705,21 +3782,6 @@ "node": "*" } }, - "node_modules/debounce-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", - "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4743,57 +3805,6 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-browser": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", - "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/default-user-agent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-user-agent/-/default-user-agent-1.0.0.tgz", @@ -4806,18 +3817,6 @@ "node": ">= 0.10.0" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -4827,15 +3826,6 @@ "node": ">=0.10" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -4862,15 +3852,6 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, - "node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/digest-header": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/digest-header/-/digest-header-1.1.0.tgz", @@ -4880,21 +3861,6 @@ "node": ">= 8.0.0" } }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -4956,6 +3922,7 @@ "version": "1.5.325", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.325.tgz", "integrity": "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA==", + "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { @@ -4965,15 +3932,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -5006,37 +3964,6 @@ "node": ">=10.13.0" } }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -5122,6 +4049,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5133,164 +4061,12 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.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/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", - "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", - "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/express/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -5303,57 +4079,11 @@ "node": ">=0.10.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -5367,75 +4097,6 @@ } } }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/formstream": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/formstream/-/formstream-1.5.2.tgz", @@ -5448,15 +4109,6 @@ "pause-stream": "~0.0.11" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/frac": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", @@ -5493,29 +4145,6 @@ } } }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-extra": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", - "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5539,12 +4168,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fuzzysort": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", - "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", - "license": "MIT" - }, "node_modules/generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -5558,6 +4181,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -5573,18 +4197,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-east-asian-width": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", - "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -5618,18 +4230,6 @@ "node": ">=6" } }, - "node_modules/get-own-enumerable-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", - "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -5649,22 +4249,6 @@ "integrity": "sha512-mFXCZPJIlcYcth+N8267+mghfYN9h3EhsDa6JSnbA3Wrhh/XFpuowviFcsDeYZtKspQyWyJqfs4O6P8CHeTwzw==", "license": "MIT" }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-tsconfig": { "version": "4.13.7", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", @@ -5677,18 +4261,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -5705,6 +4277,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -5750,44 +4323,6 @@ "node": ">=16.9.0" } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -5813,15 +4348,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -5832,22 +4358,6 @@ "url": "https://opencollective.com/immer" } }, - "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", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -5863,51 +4373,12 @@ "node": ">=12" } }, - "node_modules/ip-address": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", - "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "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" - }, "node_modules/is-class-hotfix": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==", "license": "MIT" }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -5917,15 +4388,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -5936,126 +4398,12 @@ "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-in-ssh": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", - "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "license": "MIT" }, - "node_modules/is-regexp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-type-of": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.4.0.tgz", @@ -6067,48 +4415,12 @@ "isstream": "~0.1.2" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, - "node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -6125,15 +4437,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/jose": { - "version": "6.2.12", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", - "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-base64": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", @@ -6144,34 +4447,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -6180,28 +4463,11 @@ "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" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-schema-typed": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -6210,18 +4476,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -6283,15 +4537,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -6553,25 +4798,6 @@ "url": "https://opencollective.com/parcel" } }, - "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" - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -6620,68 +4846,17 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, - "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/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -6730,19 +4905,6 @@ "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -6752,46 +4914,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -6804,67 +4926,6 @@ "node": ">=4.0.0" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/mimic-fn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", - "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -6982,6 +5043,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -6996,35 +5058,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", - "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", - "license": "MIT", - "dependencies": { - "content-type": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/negotiator/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/node-hex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/node-hex/-/node-hex-1.0.1.tgz", @@ -7038,36 +5071,9 @@ "version": "2.0.36", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, "license": "MIT" }, - "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7089,27 +5095,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-treeify": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", - "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7119,135 +5104,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/onetime/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/open": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.4.tgz", - "integrity": "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.5.1", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.2.1", - "wsl-utils": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/ora/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/os-name": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/os-name/-/os-name-1.0.3.tgz", @@ -7279,127 +5135,6 @@ "node": ">=0.10.0" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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", - "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", - "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/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -7505,12 +5240,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7519,27 +5256,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkce-challenge": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/platform": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", @@ -7550,6 +5266,7 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -7574,19 +5291,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -7626,95 +5330,12 @@ "node": ">=0.10.0" } }, - "node_modules/powershell-utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.1.tgz", - "integrity": "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pretty-ms": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", - "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "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/proxy-addr": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", - "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -7741,26 +5362,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/radix-ui": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.7.tgz", @@ -7838,34 +5439,6 @@ } } }, - "node_modules/range-parser": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", - "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -7875,32 +5448,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-day-picker": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-10.0.1.tgz", - "integrity": "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==", - "license": "MIT", - "dependencies": { - "@date-fns/tz": "^1.4.1", - "date-fns": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/gpbl" - }, - "peerDependencies": { - "@types/react": ">=16.8.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", @@ -8043,22 +5590,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/recast": { - "version": "0.23.21", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz", - "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/recharts": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", @@ -8114,30 +5645,12 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "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-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", - "engines": { - "node": ">=4" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -8147,47 +5660,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", @@ -8233,57 +5705,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -8348,151 +5769,12 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shadcn": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.21.0.tgz", - "integrity": "sha512-UU2mFNusW8C5rvadKdH69vERYZqUlOOlXBcf0MYhYLdTGP6DPti7X4qovCu+RTfCqsAgq/T+YfE0Vnttxh9aiw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/parser": "^7.28.0", - "@babel/plugin-transform-typescript": "^7.28.0", - "@babel/preset-typescript": "^7.27.1", - "@dotenvx/dotenvx": "^1.48.4", - "@modelcontextprotocol/sdk": "^1.26.0", - "@types/validate-npm-package-name": "^4.0.2", - "browserslist": "^4.26.2", - "cn": "^0.2.4", - "commander": "^14.0.0", - "cosmiconfig": "^9.0.0", - "dedent": "^1.6.0", - "deepmerge": "^4.3.1", - "diff": "^8.0.2", - "execa": "^9.6.0", - "fast-glob": "^3.3.3", - "fs-extra": "^11.3.1", - "fuzzysort": "^3.1.0", - "kleur": "^4.1.5", - "open": "^11.0.0", - "ora": "^8.2.0", - "postcss": "^8.5.6", - "postcss-selector-parser": "^7.1.0", - "prompts": "^2.4.2", - "recast": "^0.23.11", - "socks": "^2.8.8", - "stringify-object": "^5.0.0", - "ts-morph": "^26.0.0", - "tsconfig-paths": "^4.2.0", - "undici": "^7.27.2", - "validate-npm-package-name": "^7.0.1", - "zod": "^3.24.1", - "zod-to-json-schema": "^3.24.6" - }, - "bin": { - "shadcn": "dist/index.js" - }, - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/shadcn/node_modules/cn": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/cn/-/cn-0.2.6.tgz", - "integrity": "sha512-+i4L0zUGgRcEnhsxueVrP7iBGxBx5iD0WOTYg1MwFEu2ZyCmH5Ov2V1cul2Ht5UchRQQgftCf4be/RxspuW6QQ==", - "license": "MIT", - "bin": { - "cn": "bin/cn.mjs" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/shell-quote": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", @@ -8578,61 +5860,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", - "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "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", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -8683,18 +5915,6 @@ "node": ">= 0.6" } }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stream-http": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.2.tgz", @@ -8747,39 +5967,11 @@ "node": ">=8" } }, - "node_modules/stringify-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", - "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-keys": "^1.0.0", - "is-obj": "^3.0.0", - "is-regexp": "^3.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/stringify-object?sponsor=1" - } - }, - "node_modules/stringify-object/node_modules/is-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", - "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -8788,27 +5980,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -8825,32 +5996,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/systeminformation": { - "version": "5.33.13", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.13.tgz", - "integrity": "sha512-GtyakkchTQKymM1WeAB652zi3rM1q00RKdc+jZe9KGstuc/mWUwAnQGN/RE5gVYvHIR8xYOUIUD9irVKDtA0wA==", - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32", - "freebsd", - "openbsd", - "netbsd", - "sunos", - "android" - ], - "bin": { - "systeminformation": "lib/cli.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "funding": { - "type": "Buy me a coffee", - "url": "https://www.buymeacoffee.com/systeminfo" - } - }, "node_modules/tailwindcss": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", @@ -8928,27 +6073,6 @@ "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", "license": "MIT" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -8959,30 +6083,6 @@ "tree-kill": "cli.js" } }, - "node_modules/ts-morph": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", - "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.27.0", - "code-block-writer": "^13.0.3" - } - }, - "node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -9008,51 +6108,11 @@ "fsevents": "~2.3.3" } }, - "node_modules/tw-animate-css": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", - "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Wombosvideo" - } - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", - "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9062,15 +6122,6 @@ "node": ">=14.17" } }, - "node_modules/undici": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", - "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -9089,40 +6140,11 @@ "node": ">=0.10.0" } }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -9266,24 +6288,6 @@ "node": ">= 0.12.0" } }, - "node_modules/validate-npm-package-name": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", - "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/victory-vendor": { "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", @@ -9865,21 +6869,6 @@ "@esbuild/win32-x64": "0.25.12" } }, - "node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, "node_modules/win-release": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", @@ -9943,34 +6932,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/wsl-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz", - "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==", - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wsl-utils/node_modules/powershell-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", - "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/xlsx": { "version": "0.18.5", "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", @@ -10037,6 +6998,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yargs": { @@ -10068,51 +7030,6 @@ "node": ">=12" } }, - "node_modules/yocto-spinner": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.2.tgz", - "integrity": "sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", - "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, "node_modules/zrender": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", diff --git a/package.json b/package.json index cf21e07..94e4088 100644 --- a/package.json +++ b/package.json @@ -15,15 +15,11 @@ }, "dependencies": { "@amap/amap-jsapi-loader": "^1.0.1", - "@fontsource-variable/geist": "^5.3.0", "@hono/node-server": "^1.13.0", "@types/jsonwebtoken": "^9.0.10", - "ag-grid-community": "^36.2.0", - "ag-grid-react": "^36.2.0", "ali-oss": "^6.23.0", "class-variance-authority": "^0.7.1", "cn": "^0.4.0", - "date-fns": "^4.4.0", "dotenv": "^16.4.0", "echarts": "^6.1.0", "hono": "^4.7.0", @@ -34,12 +30,9 @@ "pg": "^8.22.0", "radix-ui": "^1.6.7", "react": "^19.0.0", - "react-day-picker": "^10.0.1", "react-dom": "^19.0.0", "recharts": "^3.8.1", - "shadcn": "^4.21.0", "tsx": "^4.21.0", - "tw-animate-css": "^1.4.0", "xlsx": "^0.18.5" }, "devDependencies": { diff --git a/src/components/SearchSelect.tsx b/src/components/SearchSelect.tsx index 619871a..fc59a12 100644 --- a/src/components/SearchSelect.tsx +++ b/src/components/SearchSelect.tsx @@ -1,33 +1,71 @@ -import { useState, useEffect, useMemo, useRef, useId } from 'react'; +import { useState, useEffect, useMemo, useRef } from 'react'; import { ChevronDown } from 'lucide-react'; export function SearchSelect({ value, onChange, options, placeholder, className }: { - value: string; onChange: (value: string) => void; options: string[]; placeholder: string; className?: string; + value: string; + onChange: (v: string) => void; + options: string[]; + placeholder: string; + className?: string; }) { - const [open,setOpen]=useState(false); - const [query,setQuery]=useState(''); - const [active,setActive]=useState(0); - const ref=useRef(null); - const inputRef=useRef(null); - const id=useId(); - const filtered=useMemo(()=>options.filter(option=>option.toLowerCase().includes(query.toLowerCase())),[options,query]); - const choices=['',...filtered]; - const choose=(option:string)=>{onChange(option);setQuery('');setOpen(false);}; - useEffect(()=>{const outside=(event:MouseEvent)=>{if(ref.current && !ref.current.contains(event.target as Node))setOpen(false);};document.addEventListener('mousedown',outside);return()=>document.removeEventListener('mousedown',outside);},[]); - useEffect(()=>{if(open)document.getElementById(`${id}-${active}`)?.scrollIntoView({block:'nearest'});},[open,active,id]); - return
-
- setOpen(true)} onFocus={()=>{setOpen(true);setQuery('');setActive(0);}} - onChange={e=>{setQuery(e.target.value);setActive(0);setOpen(true);}} - onKeyDown={e=>{if(e.key==='Escape'){e.stopPropagation();setOpen(false);}else if(e.key==='ArrowDown'||e.key==='ArrowUp'){e.preventDefault();setOpen(true);setActive(i=>(i+(e.key==='ArrowDown'?1:-1)+choices.length)%choices.length);}else if(e.key==='Enter'&&open){e.preventDefault();choose(choices[active]??'');}else if(e.key==='Tab')setOpen(false);}} - /> - + const [open, setOpen] = useState(false); + const [query, setQuery] = useState(''); + const ref = useRef(null); + + useEffect(() => { + const handler = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, []); + + const filtered = useMemo(() => { + if (!query) return options; + const q = query.toLowerCase(); + return options.filter((o) => o.toLowerCase().includes(q)); + }, [options, query]); + + const displayValue = value || ''; + + return ( +
+
setOpen(!open)} + > + { setQuery(e.target.value); if (!open) setOpen(true); }} + onFocus={() => { setOpen(true); setQuery(''); }} + /> + +
+ {open && ( +
+
{ onChange(''); setQuery(''); setOpen(false); }} + > + {placeholder} +
+ {filtered.map((o) => ( +
{ onChange(o); setQuery(''); setOpen(false); }} + > + {o} +
+ ))} + {filtered.length === 0 && ( +
无匹配项
+ )} +
+ )}
- {open &&
- {choices.map((option,index)=>)} - {filtered.length===0 &&

无匹配项

} -
} -
; + ); } diff --git a/src/components/Shell.tsx b/src/components/Shell.tsx index 6a04e7e..fde3a0c 100644 --- a/src/components/Shell.tsx +++ b/src/components/Shell.tsx @@ -83,17 +83,11 @@ export function Shell({ modules }: { modules: ModuleConfig[] }) { const { user } = useAuth(); const activeLabel = flatModules.find((module) => module.id === activeModule)?.label ?? '业务看板'; - // 水印时间戳随会话推进刷新,避免长时间停留后时间失真。 - const [watermarkNow, setWatermarkNow] = useState(() => Date.now()); - useEffect(() => { - const timer = window.setInterval(() => setWatermarkNow(Date.now()), 30000); - return () => window.clearInterval(timer); - }, []); const watermarkText = useMemo(() => { const name = user?.userName || '未登录'; - const time = new Date(watermarkNow).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-'); + const time = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-'); return `${name}-${time}`; - }, [user, watermarkNow]); + }, [user]); return ( @@ -157,13 +151,17 @@ export function Shell({ modules }: { modules: ModuleConfig[] }) {
-
{user?.userName || '未登录'}
+
{user?.userName || '未登录'}
{/* 内容区 */}
- +
+ 羚牛氢能 BI + / + {activeLabel} +
}> {ActiveComponent && } diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx deleted file mode 100644 index bfa8eba..0000000 --- a/src/components/ui/accordion.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from "react" -import { cn } from "cn" -import { Accordion as AccordionPrimitive } from "radix-ui" -import { ChevronDownIcon, ChevronUpIcon } from "lucide-react" - -function Accordion({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function AccordionItem({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function AccordionTrigger({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - - - {children} - - - - - ) -} - -function AccordionContent({ - className, - children, - ...props -}: React.ComponentProps) { - return ( - -
- {children} -
-
- ) -} - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx deleted file mode 100644 index 17ba728..0000000 --- a/src/components/ui/alert.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "cn" - -const alertVariants = cva( - "group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4", - { - variants: { - variant: { - default: "bg-card text-card-foreground", - destructive: - "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function Alert({ - className, - variant, - ...props -}: React.ComponentProps<"div"> & VariantProps) { - return ( -
- ) -} - -function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground", - className - )} - {...props} - /> - ) -} - -function AlertDescription({ - className, - ...props -}: React.ComponentProps<"div">) { - return ( -
- ) -} - -function AlertAction({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -export { Alert, AlertTitle, AlertDescription, AlertAction } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx deleted file mode 100644 index 837196c..0000000 --- a/src/components/ui/badge.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "cn" -import { Slot } from "radix-ui" - -const badgeVariants = cva( - "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", - secondary: - "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", - destructive: - "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", - outline: - "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", - ghost: - "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", - link: "text-primary underline-offset-4 hover:underline", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function Badge({ - className, - variant = "default", - asChild = false, - ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot.Root : "span" - - return ( - - ) -} - -export { Badge, badgeVariants } diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx deleted file mode 100644 index a501f87..0000000 --- a/src/components/ui/calendar.tsx +++ /dev/null @@ -1,222 +0,0 @@ -"use client" - -import * as React from "react" -import { cn } from "cn" -import { - DayPicker, - getDefaultClassNames, - type DayButton, - type Locale, -} from "react-day-picker" - -import { Button, buttonVariants } from "@/components/ui/button" -import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react" - -function Calendar({ - className, - classNames, - showOutsideDays = true, - captionLayout = "label", - buttonVariant = "ghost", - locale, - formatters, - components, - ...props -}: React.ComponentProps & { - buttonVariant?: React.ComponentProps["variant"] -}) { - const defaultClassNames = getDefaultClassNames() - - return ( - svg]:rotate-180`, - String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, - className - )} - captionLayout={captionLayout} - locale={locale} - formatters={{ - formatMonthDropdown: (date) => - date.toLocaleString(locale?.code, { month: "short" }), - ...formatters, - }} - classNames={{ - root: cn("w-fit", defaultClassNames.root), - months: cn( - "relative flex flex-col gap-4 md:flex-row", - defaultClassNames.months - ), - month: cn("flex w-full flex-col gap-4", defaultClassNames.month), - nav: cn( - "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1", - defaultClassNames.nav - ), - button_previous: cn( - buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) p-0 select-none aria-disabled:opacity-50", - defaultClassNames.button_previous - ), - button_next: cn( - buttonVariants({ variant: buttonVariant }), - "size-(--cell-size) p-0 select-none aria-disabled:opacity-50", - defaultClassNames.button_next - ), - month_caption: cn( - "flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)", - defaultClassNames.month_caption - ), - dropdowns: cn( - "flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium", - defaultClassNames.dropdowns - ), - dropdown_root: cn( - "relative rounded-(--cell-radius)", - defaultClassNames.dropdown_root - ), - dropdown: cn( - "absolute inset-0 bg-popover opacity-0", - defaultClassNames.dropdown - ), - caption_label: cn( - "font-medium select-none", - captionLayout === "label" - ? "text-sm" - : "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground", - defaultClassNames.caption_label - ), - month_grid: cn("w-full border-collapse", defaultClassNames.month_grid), - weekdays: cn("flex", defaultClassNames.weekdays), - weekday: cn( - "flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none", - defaultClassNames.weekday - ), - week: cn("mt-2 flex w-full", defaultClassNames.week), - week_number_header: cn( - "w-(--cell-size) select-none", - defaultClassNames.week_number_header - ), - week_number: cn( - "text-[0.8rem] text-muted-foreground select-none", - defaultClassNames.week_number - ), - day: cn( - "group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)", - props.showWeekNumber - ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)" - : "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)", - defaultClassNames.day - ), - range_start: cn( - "relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted", - defaultClassNames.range_start - ), - range_middle: cn("rounded-none", defaultClassNames.range_middle), - range_end: cn( - "relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted", - defaultClassNames.range_end - ), - today: cn( - "rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none", - defaultClassNames.today - ), - outside: cn( - "text-muted-foreground aria-selected:text-muted-foreground", - defaultClassNames.outside - ), - disabled: cn( - "text-muted-foreground opacity-50", - defaultClassNames.disabled - ), - hidden: cn("invisible", defaultClassNames.hidden), - ...classNames, - }} - components={{ - Root: ({ className, rootRef, ...props }) => { - return ( -
- ) - }, - Chevron: ({ className, orientation, ...props }) => { - if (orientation === "left") { - return ( - - ) - } - - if (orientation === "right") { - return ( - - ) - } - - return ( - - ) - }, - DayButton: ({ ...props }) => ( - - ), - WeekNumber: ({ children, ...props }) => { - return ( - -
- {children} -
- - ) - }, - ...components, - }} - {...props} - /> - ) -} - -function CalendarDayButton({ - className, - day, - modifiers, - locale, - ...props -}: React.ComponentProps & { locale?: Partial }) { - const defaultClassNames = getDefaultClassNames() - - const ref = React.useRef(null) - React.useEffect(() => { - if (modifiers.focused) ref.current?.focus() - }, [modifiers.focused]) - - return ( - - - )} - - - ) -} - -function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) -} - -function SheetTitle({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function SheetDescription({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { - Sheet, - SheetTrigger, - SheetClose, - SheetContent, - SheetHeader, - SheetFooter, - SheetTitle, - SheetDescription, -} diff --git a/src/components/ui/surface.tsx b/src/components/ui/surface.tsx index bdade23..26134c3 100644 --- a/src/components/ui/surface.tsx +++ b/src/components/ui/surface.tsx @@ -1,4 +1,4 @@ -import { useId, useState, type ComponentType, type ReactNode } from 'react'; +import { useState, type ComponentType, type ReactNode } from 'react'; import { AlertCircle, Info, Loader2, SearchX, X } from 'lucide-react'; import { AnimatePresence, motion } from 'motion/react'; import { cn } from '../../lib/cn'; @@ -31,14 +31,14 @@ export function PageFrame({ const [infoOpen, setInfoOpen] = useState(false); return ( -
+
{!hideHeader ? @@ -129,7 +129,7 @@ export function SurfaceCard({ className?: string; }) { return ( -
+
{(title || subtitle || actions) && (
@@ -173,7 +173,7 @@ export function MetricTile({ return (
@@ -210,9 +210,8 @@ export function SegmentedNav({ onChange: (id: T) => void; className?: string; }) { - const indicatorId = useId(); return ( -
+
{tabs.map(({ id, label, icon: Icon }) => { const isActive = active === id; @@ -228,7 +227,7 @@ export function SegmentedNav({ > {isActive ? ( diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx deleted file mode 100644 index 288efdd..0000000 --- a/src/components/ui/tabs.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "cn" -import { Tabs as TabsPrimitive } from "radix-ui" - -function Tabs({ - className, - orientation = "horizontal", - ...props -}: React.ComponentProps) { - return ( - - ) -} - -const tabsListVariants = cva( - "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none", - { - variants: { - variant: { - default: "bg-muted", - line: "gap-1 bg-transparent", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function TabsList({ - className, - variant = "default", - ...props -}: React.ComponentProps & - VariantProps) { - return ( - - ) -} - -function TabsTrigger({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function TabsContent({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants } diff --git a/src/index.css b/src/index.css index 15d1d8e..87124dc 100644 --- a/src/index.css +++ b/src/index.css @@ -1,51 +1,12 @@ @import "tailwindcss"; -@import "tw-animate-css"; -@import "shadcn/tailwind.css"; -@import "@fontsource-variable/geist"; -@import "./vendor/apple-design/tokens.css"; - -@custom-variant dark (&:is(.dark *)); :root { --app-bg: #f4f7fb; --panel-bg: rgba(255, 255, 255, 0.9); --hairline: rgba(148, 163, 184, 0.18); - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: var(--blue); - --primary-foreground: var(--bg); - --secondary: var(--gray-6); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: var(--blue); - --chart-1: var(--blue); - --chart-2: var(--indigo); - --chart-3: var(--teal); - --chart-4: var(--orange); - --chart-5: var(--green); - --radius: 1rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); } -html, -body { +html, body { overscroll-behavior: none; -webkit-overflow-scrolling: touch; background: var(--app-bg); @@ -61,11 +22,10 @@ body { height: 100%; color: #0f172a; background: #fff; + font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } -button, -input, -select { +button, input, select { font-family: inherit; } @@ -89,32 +49,18 @@ select { } @keyframes marquee { - from { - transform: translateX(0); - } - to { - transform: translateX(-50%); - } + from { transform: translateX(0); } + to { transform: translateX(-50%); } } @keyframes shimmer { - 0% { - transform: translateX(-100%); - } - 100% { - transform: translateX(100%); - } + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } } @keyframes floatUp { - from { - opacity: 0; - transform: translateY(8px); - } - to { - opacity: 1; - transform: translateY(0); - } + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } } @utility animate-marquee { @@ -124,9 +70,7 @@ select { @utility no-scrollbar { scrollbar-width: none; -ms-overflow-style: none; - &::-webkit-scrollbar { - display: none; - } + &::-webkit-scrollbar { display: none; } } .shimmer { @@ -137,27 +81,14 @@ select { content: ""; position: absolute; inset: 0; - background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.72), - transparent - ); + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.72), transparent); animation: shimmer 1.4s infinite; } .enterprise-grid-bg { background: - radial-gradient( - circle at 16% 0%, - rgba(59, 130, 246, 0.08), - transparent 28% - ), - radial-gradient( - circle at 90% 12%, - rgba(20, 184, 166, 0.08), - transparent 26% - ), + radial-gradient(circle at 16% 0%, rgba(59, 130, 246, 0.08), transparent 28%), + radial-gradient(circle at 90% 12%, rgba(20, 184, 166, 0.08), transparent 26%), linear-gradient(180deg, #f8fbff 0%, var(--app-bg) 42%, #f7f9fc 100%); } @@ -165,170 +96,3 @@ select { cursor: pointer; opacity: 0; } - -/* AG Grid 数字列:列头 label 是 flex,需要单独右对齐;单元格由 cellStyle 控制。 */ -.ag-numeric-header .ag-header-cell-label { - justify-content: flex-end; -} - -/* Desktop information density: shared page primitives only; touch layouts stay intact. */ -@media (min-width: 1024px) { - .desktop-page-frame { - padding: 16px 20px; - } - .desktop-page-frame > div { - gap: 12px; - } - .desktop-page-header { - border-radius: 12px; - box-shadow: 0 1px 3px rgb(15 23 42 / 4%); - } - .desktop-page-header > div.absolute { - display: none; - } - .desktop-page-header > div:not(.absolute) { - padding: 12px 16px; - gap: 12px; - } - .desktop-page-header h1 { - font-size: 20px; - font-weight: 650; - } - .desktop-page-header p { - margin-top: 4px; - font-size: 12px; - font-weight: 400; - } - .desktop-surface-card { - border-radius: 12px; - box-shadow: none; - } - .desktop-surface-card > div:first-child { - gap: 8px; - } - .desktop-metric-tile { - padding: 12px 14px; - border-radius: 12px; - box-shadow: none; - } - .desktop-metric-tile:hover { - transform: none; - } - .desktop-metric-tile > div:nth-child(2) { - margin-top: 8px; - } - .desktop-metric-tile > div:nth-child(2) > span:first-child { - font-size: 24px; - font-weight: 650; - } - .desktop-segmented { - width: fit-content; - max-width: 100%; - border-radius: 10px; - box-shadow: none; - } - .desktop-segmented > div { - display: flex; - flex-wrap: wrap; - } - .desktop-segmented button { - min-height: 32px; - padding: 5px 18px; - font-weight: 600; - border-radius: 7px; - } - .desktop-segmented button > span.absolute { - border-radius: 7px; - } -} - -@theme inline { - --font-heading: var(--font-sans); - --font-sans: - -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", - "Helvetica Neue", "Segoe UI", system-ui, sans-serif; - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --color-foreground: var(--foreground); - --color-background: var(--background); - --radius-sm: calc(var(--radius) * 0.6); - --radius-md: calc(var(--radius) * 0.8); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) * 1.4); - --radius-2xl: calc(var(--radius) * 1.8); - --radius-3xl: calc(var(--radius) * 2.2); - --radius-4xl: calc(var(--radius) * 2.6); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } - html { - @apply font-sans; - } -} diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index f815ded..0000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1 +0,0 @@ -export { cn } from "cn" diff --git a/src/modules/assets/AssetsModule.tsx b/src/modules/assets/AssetsModule.tsx index 3b6d125..7b9fb8b 100644 --- a/src/modules/assets/AssetsModule.tsx +++ b/src/modules/assets/AssetsModule.tsx @@ -1,316 +1,1710 @@ -import "./library/asset-ui.css"; -import "./library/inventory-matrix.css"; -import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react"; -import { Button } from "@/components/ui/button"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { Skeleton } from "@/components/ui/skeleton"; -import { ArrowUpRight, Moon, RefreshCw, Sparkles, Sun, Truck } from "lucide-react"; +import React, { lazy, Suspense, useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { - Picker, - Failure, - baseSelection, - useCountUp, - sectionTabsTriggerClass, -} from "./library/shared"; + Truck, + ChevronDown, + ChevronRight, + Loader2, + Filter, + ArrowRightLeft, + MapPin, +} from 'lucide-react'; +import { motion, AnimatePresence } from 'motion/react'; +import * as XLSX from 'xlsx'; import { - fetchSummary, - fetchByType, - fetchDeptStats, - fetchRegionStats, - fetchCustomerStats, - fetchInventoryStats, - fetchSubjects, -} from "./api"; -import type { SubjectOption } from "./api"; -import type { - SummaryData, - TypeSummary, - DeptGroup, - RegionGroup, - CustomerStats, - RegionalInventoryStats, -} from "./types"; -import { formatLocalDateTime, type VehicleModalSelection } from "./model"; + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + PieChart, + Pie, + Cell, + LabelList, +} from 'recharts'; +import type { SummaryData, TypeSummary, VehicleListItem, DeptGroup, RegionGroup, CustomerStats, RegionalInventoryStats } from './types'; +import { fetchSummary, fetchByType, fetchVehicleList, fetchWeeklyDetail, fetchDeptStats, fetchRegionStats, fetchCustomerStats, fetchInventoryStats, fetchRegionChart, fetchSubjects, fetchFlowStats, type SubjectOption } from './api'; +import type { FlowDetailItem, FlowStatsResponse, FlowType, WeeklyDetailItem } from './api'; import { - useAssetHash, - type AssetSection, - type AssetTab, -} from "./library/useAssetHash"; -import { AssetOverview } from "./library/AssetOverview"; -import { - DepartmentView, - RegionView, - CustomerView, -} from "./library/BusinessViews"; -import { VehicleDetails } from "./library/VehicleDetails"; + buildCustomerPieData, + buildVehicleModalRequest, + deriveCustomerView, + deriveDepartmentView, + deriveInventoryView, + deriveModalVehicleView, + formatLocalDateTime, + getRecentFlowRange, + selectFlowDetails, + type VehicleModalSelection, + type FlowSelection, +} from './model'; +import { SearchSelect } from '../../components/SearchSelect'; +import { MultiSearchSelect } from '../../components/MultiSearchSelect'; +import Blur from '../../components/Blur'; +import RotatingFooterHint from '../../components/RotatingFooterHint'; +import { ErrorState, LoadingState, PageFrame, SkeletonBlock, SurfaceCard } from '../../components/ui/surface'; +import { AssetsHeader } from './components/AssetsHeader'; +import { FlowDetailModal } from './components/FlowDetailModal'; +import { VehicleDetailModal } from './components/VehicleDetailModal'; +import { OverviewView } from './components/OverviewView'; + + + const AssetAssistant = lazy(() => import('./assistant/AssetAssistant')); -interface Data { - summary: SummaryData; - types: TypeSummary[]; - departments: DeptGroup[]; - regions: RegionGroup[]; - customers: CustomerStats[]; - inventory: RegionalInventoryStats[]; -} export default function AssetsModule() { const [assistantOpen, setAssistantOpen] = useState(false); const assistantTrigger = useRef(null); - const [appearance, setAppearance] = useState<"light" | "dark">(() => { - const saved = window.localStorage.getItem("asset-appearance"); - return saved === "light" || saved === "dark" - ? saved - : window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light"; - }); - const [subject, setSubject] = useState(null); - const [subjects, setSubjects] = useState([]); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); - const [updated, setUpdated] = useState(""); - const [{ tab, section }, setHash] = useAssetHash(); - const [selection, setSelection] = useState( - null, - ); - const requestId = useRef(0); - const load = useCallback(async () => { - const id = ++requestId.current; - setLoading(true); - try { - const [summary, types, departments, regions, customers, inventory] = - await Promise.all([ - fetchSummary(subject), - fetchByType(subject), - fetchDeptStats(subject), - fetchRegionStats(undefined, subject), - fetchCustomerStats(subject), - fetchInventoryStats(subject), - ]); - if (id !== requestId.current) return; - setData({ summary, types, departments, regions, customers, inventory }); - setUpdated(formatLocalDateTime(new Date())); - setError(""); - } catch (e) { - if (id === requestId.current) - setError(e instanceof Error ? e.message : "数据加载失败"); - } finally { - if (id === requestId.current) setLoading(false); + const [activeTab, setActiveTab] = useState<'overview' | 'department' | 'region' | 'customer'>('overview'); + const [tabReady, setTabReady] = useState(true); + const prevTabRef = useRef(activeTab); + useEffect(() => { + if (prevTabRef.current !== activeTab) { + setTabReady(false); + prevTabRef.current = activeTab; + const id = requestAnimationFrame(() => { setTabReady(true); }); + return () => cancelAnimationFrame(id); } - }, [subject]); + }, [activeTab]); + const [theme, setTheme] = useState<'soft' | 'minimal' | 'vibrant'>('soft'); + + // 所属公司(归属主体)筛选 —— 影响全页聚合 + const [selectedSubject, setSelectedSubject] = useState(null); + const [subjects, setSubjects] = useState([]); + const [subjectDropdownOpen, setSubjectDropdownOpen] = useState(false); + const [subjectSearch, setSubjectSearch] = useState(''); + const subjectDropdownRef = useRef(null); + const [expandedModels, setExpandedModels] = useState>(new Set()); + const [expandedAssetTypes, setExpandedAssetTypes] = useState>(new Set()); + const [showPlateNumbers, setShowPlateNumbers] = useState(null); + + // Data state + const [summary, setSummary] = useState(null); + const [processedData, setProcessedData] = useState([]); + const [modalVehicles, setModalVehicles] = useState([]); + const [modalWeeklyDetail, setModalWeeklyDetail] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [lastUpdate, setLastUpdate] = useState(() => formatLocalDateTime(new Date())); + const [modalLoading, setModalLoading] = useState(false); + const [flowRange, setFlowRange] = useState(() => getRecentFlowRange()); + const [flowStats, setFlowStats] = useState(null); + const [flowLoading, setFlowLoading] = useState(false); + const [flowDailyExpanded, setFlowDailyExpanded] = useState(false); + const [selectedFlow, setSelectedFlow] = useState(null); + + // Dept/Region/Customer data + const [deptData, setDeptData] = useState([]); + const [regionData, setRegionData] = useState([]); + const [customerData, setCustomerData] = useState([]); + + // Dept section state + const [deptViewMode, setDeptViewMode] = useState<'department' | 'manager'>('department'); + const [expandedDepts, setExpandedDepts] = useState>(new Set()); + const [expandedManagerDetails, setExpandedManagerDetails] = useState>(new Set()); + const [selectedManager, setSelectedManager] = useState('All'); + + // Region section state + const [expandedRegions, setExpandedRegions] = useState>(new Set()); + const [expandedRegionCities, setExpandedRegionCities] = useState>(new Set()); + const [regionFilters, setRegionFilters] = useState({ region: '', city: '', customer: '' }); + const [isRegionFilterOpen, setIsRegionFilterOpen] = useState(false); + const [draftRegionFilters, setDraftRegionFilters] = useState({ region: '', city: '', customer: '' }); + + // Customer section state + const [expandedCustomers, setExpandedCustomers] = useState>(new Set()); + const [customerFilters, setCustomerFilters] = useState({ customer: [] as string[], brand: '', department: '', manager: '', region: '' }); + const [isCustomerFilterOpen, setIsCustomerFilterOpen] = useState(false); + const [draftCustomerFilters, setDraftCustomerFilters] = useState({ customer: [] as string[], brand: '', department: '', manager: '', region: '' }); + + // Inventory statistics section state + const [inventoryData, setInventoryData] = useState([]); + const [inventoryTab, setInventoryTab] = useState<'region' | 'model'>('region'); + const [expandedInventoryRegions, setExpandedInventoryRegions] = useState>(new Set()); + const [expandedInventoryTypes, setExpandedInventoryTypes] = useState>(new Set(['4.5T普货'])); + const [inventoryFilters, setInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' }); + const [isInventoryFilterOpen, setIsInventoryFilterOpen] = useState(false); + const [draftInventoryFilters, setDraftInventoryFilters] = useState({ region: '', city: '', brand: '', type: '', model: '' }); + + // Chart view states + const [customerChartView, setCustomerChartView] = useState<'region' | 'province'>('region'); + const [regionChartView, setRegionChartView] = useState<'region' | 'province'>('region'); + const [regionChartData, setRegionChartData] = useState<{ name: string; value: number }[]>([]); + + // Modal filter state + const [modalFilters, setModalFilters] = useState({ plateNumber: '', model: '', brand: '', location: '' }); + const [isModalFilterExpanded, setIsModalFilterExpanded] = useState(false); + + // Reset modal filters when modal opens useEffect(() => { - setData(null); - void load(); - let timer: number | undefined; - const start = () => { - if (timer === undefined) timer = window.setInterval(load, 30000); - }; - const stop = () => { - if (timer !== undefined) { - window.clearInterval(timer); - timer = undefined; - } - }; - // 页面不可见时暂停轮询,回到前台立即刷新一次,避免后台空跑接口。 - const onVisibility = () => { - if (document.hidden) { - stop(); - } else { - void load(); - start(); - } - }; - start(); - document.addEventListener("visibilitychange", onVisibility); + if (showPlateNumbers) { + setModalFilters({ plateNumber: '', model: '', brand: '', location: '' }); + } + }, [showPlateNumbers]); + + const loadData = useCallback(async () => { + try { + setLoading(true); + setError(null); + const [s, byType, dept, region, cust, inv] = await Promise.all([ + fetchSummary(selectedSubject), + fetchByType(selectedSubject), + fetchDeptStats(selectedSubject), + fetchRegionStats(undefined, selectedSubject), + fetchCustomerStats(selectedSubject), + fetchInventoryStats(selectedSubject), + ]); + setSummary(s); + setProcessedData(byType); + setDeptData(dept); + setRegionData(region); + setCustomerData(cust); + setInventoryData(inv); + setLastUpdate(formatLocalDateTime(new Date())); + } catch (e) { + setError(e instanceof Error ? e.message : '数据加载失败'); + } finally { + setLoading(false); + } + }, [selectedSubject]); + + useEffect(() => { + loadData(); + const interval = setInterval(loadData, 5 * 1000); + return () => clearInterval(interval); + }, [loadData]); + + useEffect(() => { + let cancelled = false; + setFlowLoading(true); + fetchFlowStats({ start: flowRange.start, end: flowRange.end, subject: selectedSubject }) + .then((data) => { + if (!cancelled) setFlowStats(data); + }) + .catch(() => { + if (!cancelled) setFlowStats(null); + }) + .finally(() => { + if (!cancelled) setFlowLoading(false); + }); return () => { - stop(); - document.removeEventListener("visibilitychange", onVisibility); - requestId.current++; + cancelled = true; }; - }, [load]); + }, [flowRange.start, flowRange.end, selectedSubject]); + + // 归属公司列表(仅首次加载,公司集合相对稳定) useEffect(() => { - fetchSubjects() - .then(setSubjects) - .catch(() => {}); + fetchSubjects().then(setSubjects).catch(() => setSubjects([])); }, []); + + // 点击外部关闭归属公司下拉 useEffect(() => { - const root = document.documentElement; - const previous = root.getAttribute("data-theme"); - root.setAttribute("data-theme", appearance); - return () => { - if (previous === null) root.removeAttribute("data-theme"); - else root.setAttribute("data-theme", previous); + if (!subjectDropdownOpen) return; + const handler = (e: MouseEvent) => { + if (subjectDropdownRef.current && !subjectDropdownRef.current.contains(e.target as Node)) { + setSubjectDropdownOpen(false); + } }; - }, [appearance]); - const toggleAppearance = () => - setAppearance((current) => { - const next = current === "dark" ? "light" : "dark"; - window.localStorage.setItem("asset-appearance", next); - return next; - }); - const totalAssets = useCountUp(data?.summary.totalAssets ?? 0); + document.addEventListener('mousedown', handler); + return () => document.removeEventListener('mousedown', handler); + }, [subjectDropdownOpen]); + + // Re-fetch region data when filters change + useEffect(() => { + const hasFilter = regionFilters.customer || regionFilters.city || regionFilters.region; + if (hasFilter) { + fetchRegionStats( + { customer: regionFilters.customer || undefined, city: regionFilters.city || undefined, region: regionFilters.region || undefined }, + selectedSubject, + ).then(setRegionData).catch(() => {}); + } else { + // No filters: use data from the main loadData cycle + fetchRegionStats(undefined, selectedSubject).then(setRegionData).catch(() => {}); + } + }, [regionFilters, selectedSubject]); + + // Fetch region chart data when view changes + useEffect(() => { + fetchRegionChart(regionChartView, regionChartView === 'province' ? 5 : 8, 'realtime', selectedSubject) + .then(setRegionChartData) + .catch(() => setRegionChartData([])); + }, [regionChartView, selectedSubject]); + + // Load modal vehicles + useEffect(() => { + if (!showPlateNumbers) { + setModalVehicles([]); + setModalWeeklyDetail([]); + return; + } + setModalLoading(true); + const request = buildVehicleModalRequest(showPlateNumbers, selectedSubject); + if (request.kind === 'weekly') { + setModalVehicles([]); + fetchWeeklyDetail(request.type, { ...request.filters, subject: selectedSubject }) + .then(setModalWeeklyDetail) + .catch(() => setModalWeeklyDetail([])) + .finally(() => setModalLoading(false)); + return; + } + + setModalWeeklyDetail([]); + fetchVehicleList(request.params) + .then(setModalVehicles) + .catch(() => setModalVehicles([])) + .finally(() => setModalLoading(false)); + }, [showPlateNumbers, selectedSubject]); + + const allTypesExpanded = processedData.length > 0 && processedData.every((t) => expandedAssetTypes.has(t.type)); + + const toggleAllAssetTypes = () => { + if (allTypesExpanded) { + setExpandedAssetTypes(new Set()); + } else { + setExpandedAssetTypes(new Set(processedData.map((t) => t.type))); + } + }; + + const toggleAssetType = (type: string) => { + const newSet = new Set(expandedAssetTypes); + if (newSet.has(type)) newSet.delete(type); + else newSet.add(type); + setExpandedAssetTypes(newSet); + }; + + const toggleModel = (model: string) => { + const newSet = new Set(expandedModels); + if (newSet.has(model)) newSet.delete(model); + else newSet.add(model); + setExpandedModels(newSet); + }; + + const toggleDept = (dept: string) => { + const newSet = new Set(expandedDepts); + if (newSet.has(dept)) newSet.delete(dept); + else newSet.add(dept); + setExpandedDepts(newSet); + }; + + const toggleManagerDetails = (manager: string) => { + const newSet = new Set(expandedManagerDetails); + if (newSet.has(manager)) newSet.delete(manager); + else newSet.add(manager); + setExpandedManagerDetails(newSet); + }; + + const toggleRegion = (region: string) => { + const newSet = new Set(expandedRegions); + if (newSet.has(region)) newSet.delete(region); + else newSet.add(region); + setExpandedRegions(newSet); + }; + + const toggleRegionCity = (key: string) => { + const newSet = new Set(expandedRegionCities); + if (newSet.has(key)) newSet.delete(key); + else newSet.add(key); + setExpandedRegionCities(newSet); + }; + + const toggleCustomer = (customer: string) => { + const newSet = new Set(expandedCustomers); + if (newSet.has(customer)) newSet.delete(customer); + else newSet.add(customer); + setExpandedCustomers(newSet); + }; + + const toggleInventoryRegion = (region: string) => { + const newSet = new Set(expandedInventoryRegions); + if (newSet.has(region)) newSet.delete(region); + else newSet.add(region); + setExpandedInventoryRegions(newSet); + }; + + const toggleInventoryType = (type: string) => { + const newSet = new Set(expandedInventoryTypes); + if (newSet.has(type)) newSet.delete(type); + else newSet.add(type); + setExpandedInventoryTypes(newSet); + }; + + const inventoryTypeFilter = isInventoryFilterOpen + ? draftInventoryFilters.type + : inventoryFilters.type; + const { + filtered: filteredInventoryStats, + brands: uniqueInventoryBrands, + regions: uniqueInventoryRegions, + cities: uniqueInventoryCities, + types: uniqueInventoryTypes, + modelsForType: uniqueInventoryModelsForType, + byRegion: inventoryByRegion, + byModel: inventoryByModel, + } = useMemo( + () => deriveInventoryView(inventoryData, inventoryFilters, inventoryTypeFilter), + [inventoryData, inventoryFilters, inventoryTypeFilter], + ); + + const { + managers: allManagersList, + groupedManagers: managersGroupedByDept, + managerStats, + } = useMemo( + () => deriveDepartmentView(deptData, selectedManager), + [deptData, selectedManager], + ); + + const { + filtered: filteredCustomerStats, + brands: uniqueBrands, + departments: uniqueDepts, + regions: uniqueRegions, + cities: uniqueCities, + customerNames: uniqueCustomerNames, + managersByDepartment: customerManagersGroupedByDept, + } = useMemo( + () => deriveCustomerView(customerData, deptData, customerFilters), + [customerData, deptData, customerFilters], + ); + + const { + plates: uniqueModalPlates, + models: uniqueModalModels, + brands: uniqueModalBrands, + locations: uniqueModalLocations, + filteredVehicles: filteredModalVehicles, + filteredWeeklyDetails: filteredModalWeeklyDetail, + } = useMemo( + () => deriveModalVehicleView(modalVehicles, modalWeeklyDetail, modalFilters), + [modalVehicles, modalWeeklyDetail, modalFilters], + ); + + const selectedFlowDetails = useMemo( + () => selectFlowDetails(flowStats, selectedFlow), + [flowStats, selectedFlow], + ); + + const exportFlowDetails = useCallback((rows?: FlowDetailItem[], title = '资产流转明细') => { + const source = rows ?? flowStats?.details ?? []; + if (source.length === 0) return; + const table = source.map((item) => ({ + 业务日期: item.date, + 类型: item.typeLabel, + 车牌: item.plateNumber, + 运维签章时间: item.eventTime || '', + 建单时间: item.submitTime || '', + 部门: item.department || '', + 业务负责人: item.manager || '', + 客户: item.customerName || '', + })); + const ws = XLSX.utils.json_to_sheet(table); + ws['!cols'] = [ + { wch: 14 }, + { wch: 8 }, + { wch: 14 }, + { wch: 20 }, + { wch: 20 }, + { wch: 16 }, + { wch: 14 }, + { wch: 24 }, + ]; + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, '明细'); + XLSX.writeFile(wb, `${title}-${flowRange.start}-${flowRange.end}.xlsx`); + }, [flowRange.end, flowRange.start, flowStats]); + + const [customerProvinceData, setCustomerProvinceData] = useState<{ name: string; value: number }[]>([]); + useEffect(() => { + if (customerChartView === 'province') { + fetchRegionChart('province', 5, 'vehicle', selectedSubject).then(setCustomerProvinceData).catch(() => setCustomerProvinceData([])); + } + }, [customerChartView, selectedSubject]); + + const customerPieData = useMemo( + () => buildCustomerPieData(customerData, customerChartView, customerProvinceData), + [customerData, customerChartView, customerProvinceData], + ); + + + if (loading && !summary) { + return ( + + +
+ {[0, 1, 2, 3].map(item => ( + + ))} +
+
+ +
+
+
+ ); + } + + if (error && !summary) { + return ( + + +
+ + +
+
+
+ ); + } + + const SUMMARY = summary!; + const operatingRate = SUMMARY.totalAssets > 0 ? SUMMARY.operating.total / SUMMARY.totalAssets * 100 : 0; + const inventoryRate = SUMMARY.totalAssets > 0 ? SUMMARY.inventory.total / SUMMARY.totalAssets * 100 : 0; + const pendingRate = SUMMARY.totalAssets > 0 ? SUMMARY.pendingDelivery / SUMMARY.totalAssets * 100 : 0; + return ( -
-
-
-
-
-
-

- 资产管理 -

-

- 资产全景 · 运营与库存分析 -

-
-
-
- - 资产总数 - - {data ? ( - - ) : ( - - )} -
-
-
-
- s.name)} - change={(value) => { - setSubject(value || null); - setSelection(null); - }} - /> -
-
- - - {updated - ? `更新于 ${updated.slice(11, 16)} · 台账截至 17:00` - : "正在加载数据"} - - - -
-
-

- {updated - ? `更新于 ${updated.slice(11, 16)} · 台账截至 17:00` - : "正在加载数据"} -

- {error && ( - - )} - {!data ? ( - loading ? ( - - ) : null - ) : ( - setHash({ tab: value as AssetTab })} - > - - - 总览 - - - 按部门 - - - 按区域 - - - 按客户 - - - - - setHash({ section: next }) - } - open={setSelection} - /> - - - - - - - - - - - - )} - setSelection(null)} +
+ setAssistantOpen(true)} + activeTab={activeTab} + setActiveTab={setActiveTab} + theme={theme} + setTheme={setTheme} + lastUpdate={lastUpdate} + loading={loading} + selectedSubject={selectedSubject} + setSelectedSubject={setSelectedSubject} + subjects={subjects} + subjectDropdownOpen={subjectDropdownOpen} + setSubjectDropdownOpen={setSubjectDropdownOpen} + subjectSearch={subjectSearch} + setSubjectSearch={setSubjectSearch} + subjectDropdownRef={subjectDropdownRef} /> - {assistantOpen && 正在打开 AI 问数…
}> { setAssistantOpen(false); requestAnimationFrame(() => assistantTrigger.current?.focus()); }} />} + + {/* Main Content Area */} +
+ + {!tabReady && ( +
+ +
+ )} + + {tabReady && activeTab === 'overview' && ( + + )} + + {tabReady && activeTab === 'department' && ( +
+
+ {/* Overall Total Summary (Compact) - Moved to Top */} +
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'department', title: '部门运营统计' })}> + 总运营车辆 + {deptData.reduce((s, d) => s + d.totalAssets, 0)} +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', attendance: 'active', source: 'department', title: '部门运营统计 - 出勤车辆' })}> + 出勤车辆 + + {deptData.reduce((acc, d) => acc + d.operatingCount, 0)} + +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', attendance: 'idle', source: 'department', title: '部门运营统计 - 闲置车辆' })}> + 闲置车辆 + + {deptData.reduce((acc, d) => acc + d.idleCount, 0)} + +
+
+ 平均出勤 + + {deptData.length > 0 ? (deptData.reduce((acc, d) => acc + d.attendanceRate, 0) / deptData.length).toFixed(1) : 0}% + +
+
+
+ +
+ *说明:当天里程>0即为出勤。 +
+ + {/* Controls Row: Toggles Left, Filter Right */} +
+
+ + +
+ +
+ {deptViewMode === 'manager' && ( +
+ + + +
+ )} +
+
+ + {/* Desktop Table View */} +
+ + + + + {deptViewMode === 'manager' && } + + {deptViewMode === 'department' && ( + <> + + + + + )} + {deptViewMode === 'manager' && ( + <> + + + + + + + + )} + + + + + {deptViewMode === 'department' ? ( + deptData.map((dept) => { + const isExpanded = expandedDepts.has(dept.department); + return ( + + toggleDept(dept.department)} + > + + + + + + + + {isExpanded && ( + + + + )} + + ); + }) + ) : ( + managerStats.map((m) => { + const isManagerExpanded = expandedManagerDetails.has(m.manager); + return ( + + toggleManagerDetails(m.manager)} + > + + + + + + + + + + + + {isManagerExpanded && ( + + + + )} + + ); + }) + )} + +
{deptViewMode === 'department' ? '部门名称' : '业务负责人'}所属部门{deptViewMode === 'department' ? '出勤率' : '合计资产'}总运营车辆出勤车辆闲置车辆4.5T冷链18T49T挂车其他详情
+ {dept.department} + + + {dept.attendanceRate}% + + + + + + + + + {isExpanded ? : } +
+
+ {dept.managers.map(m => { + const isManagerExpanded = expandedManagerDetails.has(m.manager); + return ( +
+
toggleManagerDetails(m.manager)} + > +
+ {isManagerExpanded ? : } + {m.manager} +
+ +
+ + {isManagerExpanded && ( +
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })} + > +
4.5T
+
{m.t4_5}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })} + > +
冷链
+
{m.t4_5c}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })} + > +
18T
+
{m.t18}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })} + > +
49T
+
{m.t49}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })} + > +
挂车
+
{m.trailer}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })} + > +
其他
+
{m.other}
+
+
+
+ )} +
+ ); + })} +
+
+ {isManagerExpanded ? : } + {m.manager} + {m.department} { + e.stopPropagation(); + setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 正在运营` }); + }} + > + {m.total} + ------ + +
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })}> + 4.5T + {m.t4_5} +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })}> + 冷链 + {m.t4_5c} +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })}> + 18T + {m.t18} +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })}> + 49T + {m.t49} +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })}> + 挂车 + {m.trailer} +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })}> + 其他 + {m.other} +
+
+
+
+ + {/* Mobile Card View */} +
+ {deptViewMode === 'department' ? ( + deptData.map((dept) => { + const isExpanded = expandedDepts.has(dept.department); + return ( +
+
toggleDept(dept.department)} + > +
+

{dept.department}

+ + 出勤率: {dept.attendanceRate}% + +
+
+
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, category: 'Operating', source: 'department', title: `部门运营统计 - ${dept.department}` }); }}> +
总运营
+
{dept.totalAssets}
+
+
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'active', source: 'department', title: `部门运营统计 - ${dept.department} - 出勤车辆` }); }}> +
出勤
+
{dept.operatingCount}
+
+
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', department: dept.department, attendance: 'idle', source: 'department', title: `部门运营统计 - ${dept.department} - 闲置车辆` }); }}> +
闲置
+
{dept.idleCount}
+
+
+
+ {isExpanded ? : } +
+
+ {isExpanded && ( +
+ {dept.managers.map(m => { + const isManagerExpanded = expandedManagerDetails.has(m.manager); + return ( +
+
toggleManagerDetails(m.manager)} + > +
+ {isManagerExpanded ? : } + {m.manager} +
+ +
+ + {isManagerExpanded && ( +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })} + > +
4.5T
+
{m.t4_5}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })} + > +
冷链
+
{m.t4_5c}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })} + > +
18T
+
{m.t18}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })} + > +
49T
+
{m.t49}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })} + > +
挂车
+
{m.trailer}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })} + > +
其他
+
{m.other}
+
+
+ )} +
+ ); + })} +
+ )} +
+ ); + }) + ) : ( + managerStats.map((m) => { + const isManagerExpanded = expandedManagerDetails.has(m.manager); + return ( +
+
toggleManagerDetails(m.manager)} + > +
+ {isManagerExpanded ? : } +
+

{m.manager}

+ {m.department} +
{ + e.stopPropagation(); + setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 正在运营` }); + }} + > + 资产: {m.total} +
+
+
+ + +
+ + {isManagerExpanded && ( +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T` })} + > +
4.5T
+
{m.t4_5}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '4.5T', isColdChain: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 4.5T冷链` })} + > +
冷链
+
{m.t4_5c}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '18T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 18T` })} + > +
18T
+
{m.t18}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '49T', category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 49T` })} + > +
49T
+
{m.t49}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: true, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 挂车` })} + > +
挂车
+
{m.trailer}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', manager: m.manager, type: '其他车型', isTrailer: false, category: 'Operating', source: 'department', title: `部门运营统计 - ${m.manager} - 其他` })} + > +
其他
+
{m.other}
+
+
+ )} +
+ ); + }) + )} +
+
+
+ )} + + {tabReady && activeTab === 'region' && ( +
+ {/* Region Distribution Chart */} +
+
+
+
+

区域资产分布概览

+
+
+ + +
+
+
+ + + + + + + + + + + +
+
+ + {/* Region - Vehicle - Customer Section */} +
+
+
+
+
+

区域运营统计

+

*按区域—车型—客户维度统计

+
+
+ +
+ + + + {isRegionFilterOpen && ( + <> +
+ +
+

区域筛选

+ +
+ +
+
+ + setDraftRegionFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" /> +
+ +
+
+ + +
+
+ + +
+
+
+ +
+ + )} + +
+
+ + {Object.values(regionFilters).some(v => v !== '') && ( +
+ {regionFilters.customer && ( + + 客户: {regionFilters.customer} + + + )} + {regionFilters.region && ( + + 区域: {regionFilters.region} + + + )} + {regionFilters.city && ( + + 城市: {regionFilters.city} + + + )} + +
+ )} + +
+
+ + + + + + + + + + + + {regionData.map((r) => { + const isExpanded = expandedRegions.has(r.region); + return ( + + toggleRegion(r.region)} + > + + + + + + + {isExpanded && r.cities.map((city) => { + const cityKey = `${r.region}-${city.city}`; + const isCityExpanded = expandedRegionCities.has(cityKey); + return ( + + toggleRegionCity(cityKey)} + > + + + + + + + {isCityExpanded && city.typeBreakdown.map(tb => ( + + + + + + + + ))} + + ); + })} + + ); + })} + +
区域 / 车型 / 客户资产总数运营中待交车主要客户
+ {isExpanded ? : } + + {r.region}区域 + { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, source: 'region', title: `区域运营统计 - ${r.region}` }); }}>{r.totalAssets} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Operating', source: 'region', title: `区域运营统计 - ${r.region} - 正在运营` }); }}>{r.operatingCount} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Pending', source: 'region', title: `区域运营统计 - ${r.region} - 待交车` }); }}>{r.pendingCount}{r.customers.slice(0, 2).join(', ')}
+ {isCityExpanded ? : } + + {city.city} + { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, source: 'region', title: `区域运营统计 - ${city.city}` }); }}>{city.totalAssets} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, category: 'Operating', source: 'region', title: `区域运营统计 - ${city.city} - 正在运营` }); }}>{city.operatingCount} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, category: 'Pending', source: 'region', title: `区域运营统计 - ${city.city} - 待交车` }); }}>{city.pendingCount}{city.customers.slice(0, 2).join(', ')}
+
+ {tb.type} 车型 +
setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, vehicleType: tb.type === '其他' ? '区域其他' : tb.type, source: 'region', title: `区域运营统计 - ${city.city} - ${tb.type}` })}>{tb.total} setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, vehicleType: tb.type === '其他' ? '区域其他' : tb.type, category: 'Operating', source: 'region', title: `区域运营统计 - ${city.city} - ${tb.type} - 正在运营` })}>{tb.operating} { setShowPlateNumbers({ batch: 'All', model: 'All', location: city.city, vehicleType: tb.type === '其他' ? '区域其他' : tb.type, category: 'Pending', source: 'region', title: `区域运营统计 - ${city.city} - ${tb.type} - 待交车` }); }}>{tb.pending}{tb.customers.slice(0, 2).join(', ')}
+
+ + {/* Mobile View (Region) */} +
+ {regionData.map((r) => { + const isExpanded = expandedRegions.has(r.region); + return ( +
+
toggleRegion(r.region)} + > +
+ {isExpanded ? : } + + {r.region}区域 +
+
资产: {r.totalAssets}
+
+ {isExpanded && ( + <> +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Operating', source: 'region', title: `区域运营统计 - ${r.region} - 正在运营` })} + > +
运营中
+
{r.operatingCount}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, category: 'Pending', source: 'region', title: `区域运营统计 - ${r.region} - 待交车` })} + > +
待交车
+
{r.pendingCount}
+
+
+
+ {r.typeBreakdown.map(tb => ( +
+ {tb.type} 车型 +
+ setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, vehicleType: tb.type === '其他' ? '区域其他' : tb.type, category: 'Operating', source: 'region', title: `区域运营统计 - ${r.region} - ${tb.type} - 正在运营` })} + > + 运:{tb.operating} + + setShowPlateNumbers({ batch: 'All', model: 'All', location: r.region, vehicleType: tb.type === '其他' ? '区域其他' : tb.type, category: 'Pending', source: 'region', title: `区域运营统计 - ${r.region} - ${tb.type} - 待交车` })} + > + 待:{tb.pending} + +
+
+ ))} +
+ + )} +
+ ); + })} +
+
+
+
+ )} + + {tabReady && activeTab === 'customer' && ( +
+ + {/* Customer Operations Statistics Section */} +
+
+
+
+
+

客户运营统计

+

*按客户维度统计资产分布

+
+
+ +
+ + + + {isCustomerFilterOpen && ( + <> + {/* Backdrop */} +
+ + {/* Popover Content */} + +
+

数据筛选

+
+ +
+
+ +
+
+ + setDraftCustomerFilters(prev => ({ ...prev, customer: v }))} options={uniqueCustomerNames} placeholder="所有客户" className="text-xs py-2 px-2" /> +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + )} + +
+
+ + {Object.values(customerFilters).some(v => Array.isArray(v) ? v.length > 0 : v !== '') && ( +
+ {customerFilters.customer.length > 0 && ( + + 客户: {customerFilters.customer.join(', ')} + + + )} + {customerFilters.manager && ( + + 负责人: {customerFilters.manager} + + + )} + {customerFilters.brand && ( + + 品牌: {customerFilters.brand} + + + )} + {customerFilters.department && ( + + 部门: {customerFilters.department} + + + )} + {customerFilters.region && ( + + 区域: {customerFilters.region} + + + )} + +
+ )} + +
+ {/* Desktop Table View (Customer) */} +
+ + + + + + + + + + + + + + + + + {filteredCustomerStats.map((cust) => { + const isExpanded = expandedCustomers.has(cust.customer); + return ( + + toggleCustomer(cust.customer)} + > + + + + + + + + + + + + {isExpanded && ( + + + + )} + + ); + })} + +
客户名称所在区域关联业务负责人4.5T4.5T冷链18T49T挂车其他合计
+ {isExpanded ? : } + {cust.customer} + + {cust.region} + {cust.manager} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: false, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T` }); }}>{cust.t4_5} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: true, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T冷链` }); }}>{cust.t4_5c} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '18T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 18T` }); }}>{cust.t18} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '49T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 49T` }); }}>{cust.t49} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 挂车` }); }}>{cust.trailer} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 其他` }); }}>{cust.other} { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, source: 'customer', title: `客户运营统计 - ${cust.customer}` }); }}>{cust.total}
+
+
+
客户详情
+
{cust.customer}
+
+
+
主要车型
+
+ {cust.t49 > cust.t18 ? '49T 重卡' : (cust.t18 > cust.t4_5c ? '18T 货车' : '4.5T 轻卡')} +
+
+
+
业务经理
+
{cust.manager}
+
+
+
资产占比
+
+ {((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}% +
+
+
+
+
+ + {/* Mobile Card View (Customer) */} +
+ {filteredCustomerStats.map((cust) => { + const isExpanded = expandedCustomers.has(cust.customer); + return ( +
+
toggleCustomer(cust.customer)} + > +
+ {isExpanded ? : } +
+ {cust.customer} + {cust.region}区域 +
+
+
{ e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, source: 'customer', title: `客户运营统计 - ${cust.customer}` }); }} + > + 合计: {cust.total} +
+
+ + {isExpanded && ( +
+ {/* Details Cards for Mobile */} +
+
+
客户详情
+
{cust.customer}
+
+
+
主要车型
+
+ {cust.t49 > cust.t18 ? '49T 重卡' : (cust.t18 > cust.t4_5c ? '18T 货车' : '4.5T 轻卡')} +
+
+
+
业务经理
+
{cust.manager}
+
+
+
资产占比
+
+ {((cust.total / deptData.reduce((s, d) => s + d.totalAssets, 0)) * 100).toFixed(1)}% +
+
+
+ +
+
车型分布
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: false, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T` })} + > +
4.5T
+
{cust.t4_5}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '4.5T', isColdChain: true, source: 'customer', title: `客户运营统计 - ${cust.customer} - 4.5T冷链` })} + > +
冷链
+
{cust.t4_5c}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '18T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 18T` })} + > +
18T
+
{cust.t18}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '49T', source: 'customer', title: `客户运营统计 - ${cust.customer} - 49T` })} + > +
49T
+
{cust.t49}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 挂车` })} + > +
挂车
+
{cust.trailer}
+
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', customer: cust.customer, type: '其他车型', source: 'customer', title: `客户运营统计 - ${cust.customer} - 其他` })} + > +
其他
+
{cust.other}
+
+
+
+
+ )} +
+ ); + })} +
+
+
+
+ )} + + + + + + +
+ + {assistantOpen && 正在打开 AI 问数…
}> { setAssistantOpen(false); requestAnimationFrame(() => assistantTrigger.current?.focus()); }} />}
); } diff --git a/src/modules/assets/library/Chart.tsx b/src/modules/assets/assistant/Chart.tsx similarity index 100% rename from src/modules/assets/library/Chart.tsx rename to src/modules/assets/assistant/Chart.tsx diff --git a/src/modules/assets/library/EChartImpl.tsx b/src/modules/assets/assistant/EChartImpl.tsx similarity index 100% rename from src/modules/assets/library/EChartImpl.tsx rename to src/modules/assets/assistant/EChartImpl.tsx diff --git a/src/modules/assets/assistant/ResultView.tsx b/src/modules/assets/assistant/ResultView.tsx index 67bc113..8056d3b 100644 --- a/src/modules/assets/assistant/ResultView.tsx +++ b/src/modules/assets/assistant/ResultView.tsx @@ -16,7 +16,7 @@ import type { AssistantRow, } from "../../../shared/assets/assistant"; import { GROUP_LABELS } from "../../../shared/assets/assistant"; -import { Chart } from "../library/Chart"; +import { Chart } from "./Chart"; function downloadCsv(rows: string[][], title: string) { const content = rows diff --git a/src/modules/assets/components/AbnormalVehicleDetails.tsx b/src/modules/assets/components/AbnormalVehicleDetails.tsx new file mode 100644 index 0000000..74dc0c7 --- /dev/null +++ b/src/modules/assets/components/AbnormalVehicleDetails.tsx @@ -0,0 +1,52 @@ +import Blur from '../../../components/Blur'; +import type { VehicleListItem } from '../types'; + +type DetailsProps = { vehicles: VehicleListItem[] }; + +function InventoryVehicleDetails({ vehicles, kind }: DetailsProps & { kind: 'abnormal' | 'pending' }) { + const pending = kind === 'pending'; + const labels = pending ? ['合同编号', '客户名称(已脱敏)'] : ['异动类型', '预计结束时间']; + const details = (vehicle: VehicleListItem) => pending + ? [vehicle.pendingDelivery?.contractNo || '未提供', vehicle.pendingDelivery?.customerName || '未提供'] + : [vehicle.abnormalMove?.type || '未提供', vehicle.abnormalMove?.targetCompletionTime || '未提供']; + + if (vehicles.length === 0) return
暂无符合筛选条件的{pending ? '待交车' : '异动车辆'}
; + + return <> +

{pending ? '展示当前待交车任务的合同信息,客户名称已脱敏。' : '展示当前异动类型与预计结束时间。'}

+
+ {vehicles.map(vehicle =>
+

{vehicle.plateNumber || vehicle.vin || '—'}

+

{vehicle.brandLabel || '—'} · {vehicle.type} · {vehicle.location === '其他' ? '对接中' : vehicle.location}

+
+ {details(vehicle).map((value, index) =>
+
{labels[index]}
+
{value}
+
)} +
+
)} +
+
+ + + {['车牌', '品牌', '车型', '所在地', ...labels].map(label => )} + + {vehicles.map(vehicle => + + + + + {details(vehicle).map((value, index) => )} + )} +
{label}
{vehicle.plateNumber || vehicle.vin || '—'}{vehicle.brandLabel || '—'}{vehicle.type}{vehicle.location === '其他' ? '对接中' : vehicle.location}{value}
+
+ ; +} + +export function AbnormalVehicleDetails(props: DetailsProps) { + return ; +} + +export function PendingVehicleDetails(props: DetailsProps) { + return ; +} diff --git a/src/modules/assets/components/AssetsHeader.tsx b/src/modules/assets/components/AssetsHeader.tsx new file mode 100644 index 0000000..50eef42 --- /dev/null +++ b/src/modules/assets/components/AssetsHeader.tsx @@ -0,0 +1,234 @@ +import React from 'react'; +import { ChevronDown, Filter, Loader2, Search, Sparkles } from 'lucide-react'; +import { motion } from 'motion/react'; +import type { SubjectOption } from '../api'; + +export type AssetsTab = 'overview' | 'department' | 'region' | 'customer'; +export type AssetsTheme = 'soft' | 'minimal' | 'vibrant'; + +interface AssetsHeaderProps { + onOpenAssistant: () => void; + assistantTrigger: React.RefObject; + activeTab: AssetsTab; + setActiveTab: React.Dispatch>; + theme: AssetsTheme; + setTheme: React.Dispatch>; + lastUpdate: string; + loading: boolean; + selectedSubject: string | null; + setSelectedSubject: React.Dispatch>; + subjects: SubjectOption[]; + subjectDropdownOpen: boolean; + setSubjectDropdownOpen: React.Dispatch>; + subjectSearch: string; + setSubjectSearch: React.Dispatch>; + subjectDropdownRef: React.RefObject; +} +// --- Constants --- +const TABS = [ + { id: 'overview', label: '总览' }, + { id: 'department', label: '按部门' }, + { id: 'region', label: '按区域' }, + { id: 'customer', label: '按客户' }, +]; + +export function AssetsHeader({ + onOpenAssistant, + assistantTrigger, + activeTab, + setActiveTab, + theme, + setTheme, + lastUpdate, + loading, + selectedSubject, + setSelectedSubject, + subjects, + subjectDropdownOpen, + setSubjectDropdownOpen, + subjectSearch, + setSubjectSearch, + subjectDropdownRef, +}: AssetsHeaderProps) { + return ( + <> + {/* Compact Header Bar */} +
+ {/* Title row */} +
+

羚牛氢能-资产BI

+ {/* Right: status + theme */} +
+
+ + {lastUpdate} +
+ {loading && ( +
+ +
+ )} +
+ {(['soft','minimal','vibrant'] as const).map((t) => ( + + ))} +
+
+
+
+ +
+ {/* 归属公司作用域筛选 (Scope Chip) */} +
+
+ + {subjectDropdownOpen && ( +
+
+
+ + setSubjectSearch(e.target.value)} + placeholder="搜索公司名" + className="w-full h-7 pl-6 pr-2 text-[11px] bg-gray-50 border border-gray-100 rounded focus:outline-none focus:border-blue-300 focus:bg-white" + /> +
+
+
+ +
+ {subjects + .filter((s) => !subjectSearch || s.name.toLowerCase().includes(subjectSearch.toLowerCase())) + .map((s) => { + const active = selectedSubject === s.name; + return ( + + ); + })} + {subjects.filter((s) => !subjectSearch || s.name.toLowerCase().includes(subjectSearch.toLowerCase())).length === 0 && ( +
未找到匹配公司
+ )} +
+
+ )} +
+
+ + {/* Tab row */} +
+ {TABS.map(tab => ( + + ))} +
+ {/* Status row */} +
+
+ + 数据更新时间:{lastUpdate.slice(0, 16)} +
+ + 运维台账每日统计口径:当日17:00 +
+
+ + + ); +} diff --git a/src/modules/assets/components/FlowDetailModal.tsx b/src/modules/assets/components/FlowDetailModal.tsx new file mode 100644 index 0000000..5935a00 --- /dev/null +++ b/src/modules/assets/components/FlowDetailModal.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { Download, X } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import type { FlowDetailItem, FlowType } from '../api'; +import type { FlowSelection } from '../model'; + +export const FLOW_META: Record = { + delivered: { label: '交车', tone: 'text-blue-600 bg-blue-50 border-blue-100', chip: 'bg-blue-50 text-blue-700 border-blue-100' }, + returned: { label: '还车', tone: 'text-orange-600 bg-orange-50 border-orange-100', chip: 'bg-orange-50 text-orange-700 border-orange-100' }, + replaced: { label: '替换交车', tone: 'text-violet-600 bg-violet-50 border-violet-100', chip: 'bg-violet-50 text-violet-700 border-violet-100' }, +}; + +interface FlowDetailModalProps { + selectedFlow: FlowSelection | null; + selectedFlowDetails: FlowDetailItem[]; + setSelectedFlow: React.Dispatch>; + exportFlowDetails: (rows?: FlowDetailItem[], title?: string) => void; +} +export function FlowDetailModal({ + selectedFlow, + selectedFlowDetails, + setSelectedFlow, + exportFlowDetails, +}: FlowDetailModalProps) { + const dateLabel = selectedFlow + ? ('date' in selectedFlow ? selectedFlow.date : `${selectedFlow.start} 至 ${selectedFlow.end}`) + : ''; + return ( + <> + {/* Flow Detail Modal */} + + {selectedFlow && ( +
+ +
+
+
+
运维签章时间口径 · 单位:车次
+

+ {dateLabel} · {FLOW_META[selectedFlow.type].label}明细 +

+
+ 共 {selectedFlowDetails.length} 车次,包含车牌、运维签章时间、建单时间、部门、负责人和客户 +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + {selectedFlowDetails.map((item) => ( + + + + + + + + + ))} + +
车牌运维签章时间建单时间部门业务负责人客户
{item.plateNumber}{item.type === 'replaced' && 替换交车}{item.eventTime || '-'}{item.submitTime || '-'}{item.department || '-'}{item.manager || '-'}{item.customerName || '-'}
+
+ +
+ {selectedFlowDetails.map((item) => ( +
+
+
+
{item.plateNumber}
+
+ {item.typeLabel} +
+
+
+
建单
+
{item.submitTime?.slice(5, 16) || '-'}
+
+
+
+
+
运维签章时间
+
{item.eventTime || '-'}
+
+
+
负责人
+
{item.manager || '-'}
+
+
+
部门
+
{item.department || '-'}
+
+
+
客户
+
{item.customerName || '-'}
+
+
+
+ ))} +
+ + {selectedFlowDetails.length === 0 && ( +
当前日期暂无明细
+ )} +
+
+
+ )} +
+ + ); +} diff --git a/src/modules/assets/components/OverviewView.tsx b/src/modules/assets/components/OverviewView.tsx new file mode 100644 index 0000000..a9f2189 --- /dev/null +++ b/src/modules/assets/components/OverviewView.tsx @@ -0,0 +1,64 @@ +import { AssetSummarySection } from './overview/AssetSummarySection'; +import { FlowStatisticsCard } from './overview/FlowStatisticsCard'; +import { InventoryStatisticsSection } from './overview/InventoryStatisticsSection'; +import { SummaryMetrics } from './overview/SummaryMetrics'; +import type { OverviewViewProps } from './overview/types'; + +export function OverviewView(props: OverviewViewProps) { + return ( + <> + + + + + + + + + ); +} diff --git a/src/modules/assets/components/VehicleDetailModal.tsx b/src/modules/assets/components/VehicleDetailModal.tsx new file mode 100644 index 0000000..fb95497 --- /dev/null +++ b/src/modules/assets/components/VehicleDetailModal.tsx @@ -0,0 +1,292 @@ +import React, { useCallback, useRef } from 'react'; +import { AbnormalVehicleDetails, PendingVehicleDetails } from './AbnormalVehicleDetails'; +import { useDialogFocus } from './useDialogFocus'; +import { ChevronDown, Filter, Loader2, PlusCircle, Truck } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import Blur from '../../../components/Blur'; +import { SearchSelect } from '../../../components/SearchSelect'; +import type { WeeklyDetailItem } from '../api'; +import type { ModalVehicleFilters, VehicleModalSelection } from '../model'; +import type { VehicleListItem } from '../types'; + +interface VehicleDetailModalProps { + selection: VehicleModalSelection | null; + setSelection: React.Dispatch>; + isFilterExpanded: boolean; + setIsFilterExpanded: React.Dispatch>; + filters: ModalVehicleFilters; + setFilters: React.Dispatch>; + plates: string[]; + models: string[]; + brands: string[]; + locations: string[]; + loading: boolean; + weeklyDetails: WeeklyDetailItem[]; + filteredWeeklyDetails: WeeklyDetailItem[]; + filteredVehicles: VehicleListItem[]; +} +export function VehicleDetailModal({ + selection: showPlateNumbers, + setSelection: setShowPlateNumbers, + isFilterExpanded: isModalFilterExpanded, + setIsFilterExpanded: setIsModalFilterExpanded, + filters: modalFilters, + setFilters: setModalFilters, + plates: uniqueModalPlates, + models: uniqueModalModels, + brands: uniqueModalBrands, + locations: uniqueModalLocations, + loading: modalLoading, + weeklyDetails: modalWeeklyDetail, + filteredWeeklyDetails: filteredModalWeeklyDetail, + filteredVehicles: filteredModalVehicles, +}: VehicleDetailModalProps) { + const dialogRef = useRef(null); + const close = useCallback(() => setShowPlateNumbers(null), [setShowPlateNumbers]); + useDialogFocus(Boolean(showPlateNumbers), dialogRef, close); + return ( + <> + {/* Vehicle Detail Modal */} + + {showPlateNumbers && ( +
+ +
+
+

+ + {showPlateNumbers.title || ( + (showPlateNumbers.manager ? `${showPlateNumbers.manager} 的${showPlateNumbers.type || ''}车辆` : + showPlateNumbers.customer ? `${showPlateNumbers.customer} 的${showPlateNumbers.type || ''}车辆` : + showPlateNumbers.batch === 'All' ? '全量批次' : `${showPlateNumbers.batch} 批次`) + ' - 运营明细' + )} +

+

+ {showPlateNumbers.model === 'All' ? '全量型号' : showPlateNumbers.model} | + {showPlateNumbers.category === 'Pending' ? '待交车' : + showPlateNumbers.category === 'Delivered' ? '本周已交车' : + showPlateNumbers.category === 'Returned' ? '已还车' : + showPlateNumbers.category === 'Replaced' ? '已替换' : + showPlateNumbers.category === 'Abnormal' ? '异动' : + showPlateNumbers.category === 'Inventory' ? `${showPlateNumbers.location}库存` : + showPlateNumbers.category === 'Operating' ? '正在运营' : '全部状态'} +

+
+ +
+ + {/* Modal Filters */} +
+
setIsModalFilterExpanded(!isModalFilterExpanded)} + > +
+ + 筛选条件 +
+
+ {/* Quick Search always visible when collapsed */} + {!isModalFilterExpanded && ( +
e.stopPropagation()}> + setModalFilters({...modalFilters, plateNumber: v})} options={uniqueModalPlates} placeholder="快速搜索车牌..." className="text-[11px] py-1 px-2" /> +
+ )} + + + +
+
+ + + {isModalFilterExpanded && ( + +
+
+ + setModalFilters({...modalFilters, plateNumber: v})} options={uniqueModalPlates} placeholder="全部车牌" className="text-[11px] py-1.5 px-2" /> +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ )} +
+
+ +
+ {modalLoading ? ( +
+ +
+ ) : showPlateNumbers.category === 'Abnormal' ? ( + + ) : showPlateNumbers.category === 'Pending' ? ( + + ) : modalWeeklyDetail.length > 0 ? ( +
+ + + + + + + + + + {filteredModalWeeklyDetail.map((v, i) => ( + + + + + + ))} + +
车牌客户名称日期
{v.plate_number}{v.customer_name || '—'}{v.handover_date ? v.handover_date.slice(0, 10) : '—'}
+
+ ) : ( +
+ + + + {showPlateNumbers.source === 'customer' ? ( + <> + + + + + + + + + + + + + + + ) : ( + <> + + {showPlateNumbers.source !== 'asset' && showPlateNumbers.category !== 'Inventory' && ( + + )} + + + + + )} + + + + {filteredModalVehicles.map((v, idx) => ( + + {showPlateNumbers.source === 'customer' ? ( + <> + + + + + + + + + + + + + + + ) : ( + <> + + {showPlateNumbers.source !== 'asset' && showPlateNumbers.category !== 'Inventory' && ( + + )} + + + + + )} + + ))} + {filteredModalVehicles.length === 0 && ( + + + + )} + +
业务部门业务负责人品牌车型资产归属客户名称车牌状态提车时间合同到期时间运营区域离到期签约公司车牌客户名称品牌车型所在地
{v.departmentName || '—'}{v.customerManager || '—'}{v.brandLabel || '—'}{v.type}{v.subjectOrg || '—'}{v.customerName || '—'}{v.plateNumber || v.vin || '—'} + + {v.status === 'Operating' ? '在租' : v.status === 'Inventory' ? '库存' : '异常'} + + {'—'}{'—'}{v.location === '其他' ? '对接中' : v.location}{'—'}{v.orgName || '—'}{v.plateNumber || v.vin || '—'}{v.customerName || '—'}{v.brandLabel || '—'}{v.type}{v.location === '其他' ? '对接中' : v.location}
+ 暂无符合条件的车辆数据 +
+
+ )} +
+ +
+
+ 共计 {filteredModalWeeklyDetail.length > 0 ? filteredModalWeeklyDetail.length : filteredModalVehicles.length} 台车辆 +
+ +
+
+
+ )} +
+ + ); +} diff --git a/src/modules/assets/components/overview/AssetSummaryDesktopTable.tsx b/src/modules/assets/components/overview/AssetSummaryDesktopTable.tsx new file mode 100644 index 0000000..800ce42 --- /dev/null +++ b/src/modules/assets/components/overview/AssetSummaryDesktopTable.tsx @@ -0,0 +1,203 @@ +import React from 'react'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import type { AssetSummarySectionProps } from './types'; + +export function AssetSummaryDesktopTable({ + processedData, + theme, + expandedAssetTypes, + toggleAssetType, + expandedModels, + toggleModel, + setVehicleSelection: setShowPlateNumbers, +}: AssetSummarySectionProps) { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + {processedData.map((typeGroup) => ( + + {/* Category Header Row */} + toggleAssetType(typeGroup.type)}> + + + + + {expandedAssetTypes.has(typeGroup.type) && typeGroup.models.map((model) => ( + + toggleModel(model.model)} + > + + + + + {['嘉兴', '广东', '北京', '新疆', '其他'].map(reg => ( + + ))} + + + + + + + + + ))} + + + ))} + +
车型品牌型号车辆总资产库存总数库存-江浙沪库存-广东库存-北京库存-新疆库存-其他异动待交车当前在运营本周交车本周还车本周替换
+
+
+ {expandedAssetTypes.has(typeGroup.type) ? + : + + } + {typeGroup.type} +
+
+ 资产 {typeGroup.totalAssets} + 库存 {typeGroup.totalInventory} + 异动 {typeGroup.totalAbnormal} + 运营 {typeGroup.totalOperating} +
+
+
{typeGroup.type} +
+ {expandedModels.has(model.model) ? : } + {model.model} +
+
+ 资产 {model.total} + 库存 {model.inventory} + 运营 {model.operating} +
+
+ + + + + {model.inventoryRegions[reg] > 0 ? ( + + ) : ''} + + + + {model.pending > 0 ? ( + + ) : model.pending} + + + + {model.weeklyDelivered > 0 ? ( + + ) : model.weeklyDelivered} + + {model.weeklyReturned > 0 ? ( + + ) : model.weeklyReturned} + + {model.weeklyReplaced > 0 ? ( + + ) : model.weeklyReplaced} +
+
+ ); +} diff --git a/src/modules/assets/components/overview/AssetSummaryMobileCards.tsx b/src/modules/assets/components/overview/AssetSummaryMobileCards.tsx new file mode 100644 index 0000000..9b8e031 --- /dev/null +++ b/src/modules/assets/components/overview/AssetSummaryMobileCards.tsx @@ -0,0 +1,129 @@ +import { ChevronDown, ChevronRight } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import type { AssetSummarySectionProps } from './types'; + +export function AssetSummaryMobileCards({ + processedData, + theme, + expandedAssetTypes, + toggleAssetType, + expandedModels, + toggleModel, + setVehicleSelection: setShowPlateNumbers, +}: AssetSummarySectionProps) { + return ( +
+ {processedData.map((typeGroup) => ( +
+
toggleAssetType(typeGroup.type)} + > +
+ {expandedAssetTypes.has(typeGroup.type) ? + : + + } + {typeGroup.type} +
+
+ 资产 {typeGroup.totalAssets} + 库存 {typeGroup.totalInventory} + 异动 {typeGroup.totalAbnormal} + 运营 {typeGroup.totalOperating} +
+
+ + + {expandedAssetTypes.has(typeGroup.type) && ( + + {typeGroup.models.map((model) => ( +
+
toggleModel(model.model)} + > +
+ {expandedModels.has(model.model) ? : } + {model.model} +
+
+ { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', source: 'asset', title: model.model }); }}>资产 {model.total} + { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Inventory', source: 'asset', title: `${model.model} - 库存` }); }}>库存 {model.inventory} + + { e.stopPropagation(); setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Operating', source: 'asset', title: `${model.model} - 在运营` }); }}>运营 {model.operating} +
+
+ + {expandedModels.has(model.model) && ( +
+
+
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Inventory', source: 'asset', title: `${model.model} - 库存` })}> + 总库存 + {model.inventory} +
+
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Pending', source: 'asset', title: `${model.model} - 待交车` })}> + 待交车 + {model.pending} +
+
+ {['嘉兴', '广东', '北京', '新疆', '其他'].map(reg => ( +
+
{reg === '嘉兴' ? '浙' : reg === '广东' ? '粤' : reg === '北京' ? '京' : reg === '新疆' ? '新' : '其'}
+ {model.inventoryRegions[reg] > 0 ? ( + + ) : ( +
-
+ )} +
+ ))} +
+
+
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Delivered', source: 'asset', title: `${model.model} - 本周交车` })}> + 本周已交车 + {model.weeklyDelivered} +
+
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Returned', source: 'asset', title: `${model.model} - 本周还车` })}> + 已还车 + {model.weeklyReturned} +
+
setShowPlateNumbers({ batch: 'All', model: model.model, location: 'All', category: 'Replaced', source: 'asset', title: `${model.model} - 本周替换` })}> + 已替换 + {model.weeklyReplaced} +
+
+
+
+ )} +
+ ))} +
+ )} +
+
+ ))} +
+ ); +} diff --git a/src/modules/assets/components/overview/AssetSummarySection.tsx b/src/modules/assets/components/overview/AssetSummarySection.tsx new file mode 100644 index 0000000..4f754cd --- /dev/null +++ b/src/modules/assets/components/overview/AssetSummarySection.tsx @@ -0,0 +1,26 @@ +import { Info } from 'lucide-react'; +import { AssetSummaryDesktopTable } from './AssetSummaryDesktopTable'; +import { AssetSummaryMobileCards } from './AssetSummaryMobileCards'; +import type { AssetSummarySectionProps } from './types'; + +export function AssetSummarySection(props: AssetSummarySectionProps) { + return ( +
+
+
+

资产数据实时汇总

+
+ + 点击型号展开详情 +
+
+
+ + {/* Desktop View Table */} + + + {/* Mobile View Cards for Asset Summary */} + +
+ ); +} diff --git a/src/modules/assets/components/overview/FlowDateRangePicker.tsx b/src/modules/assets/components/overview/FlowDateRangePicker.tsx new file mode 100644 index 0000000..ed33dee --- /dev/null +++ b/src/modules/assets/components/overview/FlowDateRangePicker.tsx @@ -0,0 +1,118 @@ +import { useRef, useState } from 'react'; +import { ArrowRight, CalendarDays, Check, ChevronDown, ChevronLeft, ChevronRight, X } from 'lucide-react'; +import { getRecentFlowRange, getWeeklyFlowRange, type DateRange } from '../../model'; + +const weekdays = ['一', '二', '三', '四', '五', '六', '日']; +const ymd = (date: Date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +const parseDate = (value: string) => new Date(`${value}T12:00:00`); +const shiftDay = (date: Date, offset: number) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + offset); +const dayCount = (start: string, end: string) => Math.round((Date.parse(`${end}T00:00:00Z`) - Date.parse(`${start}T00:00:00Z`)) / 86400000) + 1; + +export function FlowDateRangePicker({ value, onChange }: { value: DateRange; onChange: (range: DateRange) => void }) { + const dialog = useRef(null); + const [draft, setDraft] = useState(value); + const [selecting, setSelecting] = useState<'start' | 'end'>('start'); + const [month, setMonth] = useState(() => parseDate(value.start)); + const [hovered, setHovered] = useState(''); + const today = ymd(new Date()); + const count = draft.end ? dayCount(draft.start, draft.end) : 0; + const valid = count > 0 && count <= 370; + const monthStart = new Date(month.getFullYear(), month.getMonth(), 1); + const previewEnd = draft.end || hovered || draft.start; + const rangeStart = draft.start < previewEnd ? draft.start : previewEnd; + const rangeEnd = draft.start > previewEnd ? draft.start : previewEnd; + const quickPicks = [ + { label: '今天', range: { start: today, end: today } }, + { label: '昨天', range: { start: ymd(shiftDay(new Date(), -1)), end: ymd(shiftDay(new Date(), -1)) } }, + { label: '业务周', range: getWeeklyFlowRange() }, + { label: '近 7 天', range: getRecentFlowRange() }, + { label: '近 30 天', range: { start: ymd(shiftDay(new Date(), -29)), end: today } }, + ]; + const open = () => { + setDraft(value); setSelecting('start'); setMonth(parseDate(value.start)); setHovered(''); + dialog.current?.showModal(); + }; + const choose = (date: string) => { + if (selecting === 'start') { + setDraft({ start: date, end: '' }); setSelecting('end'); + } else { + setDraft(date < draft.start ? { start: date, end: draft.start } : { start: draft.start, end: date }); + setSelecting('start'); + } + setHovered(''); + }; + const calendar = (offset: number) => { + const current = new Date(monthStart.getFullYear(), monthStart.getMonth() + offset, 1); + const year = current.getFullYear(), m = current.getMonth(); + const padding = (current.getDay() + 6) % 7; + const length = new Date(year, m + 1, 0).getDate(); + return
+
{year} 年 {m + 1} 月
+
+ {weekdays.map(day =>
{day}
)} + {Array.from({ length: padding }, (_, i) =>
)} + {Array.from({ length }, (_, i) => { + const date = ymd(new Date(year, m, i + 1)); + const endpoint = date === draft.start || date === draft.end; + const inRange = date >= rangeStart && date <= rangeEnd; + return ; + })} +
+
; + }; + return <> + + { if (e.target === e.currentTarget) dialog.current?.close(); }} + className="fixed inset-0 m-auto max-h-[90dvh] w-[calc(100%-24px)] max-w-[680px] overflow-y-auto rounded-3xl border-0 bg-white p-0 text-slate-700 shadow-2xl backdrop:bg-slate-950/30 backdrop:backdrop-blur-sm"> +
+
+

选择统计日期

先选开始日期,再选结束日期

+ +
+
+ {quickPicks.map(pick => )} +
+
+ {(['start', 'end'] as const).map(field => )} +
+
+ +
+ + +
+ +
+
setHovered('')}>{calendar(0)}{calendar(1)}
+
+
+ 370 ? 'text-red-500' : 'text-slate-400'}`}>{!draft.end ? '请选择结束日期' : count > 370 ? '最多选择 370 天' : `已选 ${count} 天(包含首尾)`} +
+ + +
+
+
+ ; +} diff --git a/src/modules/assets/components/overview/FlowStatisticsCard.tsx b/src/modules/assets/components/overview/FlowStatisticsCard.tsx new file mode 100644 index 0000000..181a85b --- /dev/null +++ b/src/modules/assets/components/overview/FlowStatisticsCard.tsx @@ -0,0 +1,129 @@ +import { CalendarDays, ChevronDown, Download, Loader2 } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import type { FlowType } from '../../api'; +import { FLOW_META } from '../FlowDetailModal'; +import type { FlowStatisticsCardProps } from './types'; +import { FlowDateRangePicker } from './FlowDateRangePicker'; + +const FLOW_TYPES: FlowType[] = ['delivered', 'returned', 'replaced']; + +export function FlowStatisticsCard({ + flowRange, + setFlowRange, + flowLoading, + flowStats, + exportFlowDetails, + flowDailyExpanded, + setFlowDailyExpanded, + setSelectedFlow, +}: FlowStatisticsCardProps) { + return ( +
+
+
+
+
+ + 交还车统计(车次) + {flowLoading && } +
+
+
+ +
+
+

按运维签章时间统计;交车包含替换交车,合计=交车+还车,同车多笔业务分别计次。

+ +
+
+
+
{flowStats?.totals.total ?? 0}
+
合计
+
+ {FLOW_TYPES.map((type) => ( + + ))} +
+
+ + + {flowDailyExpanded && ( + +
+ {flowLoading && ( +
正在加载交还车统计...
+ )} + {!flowLoading && flowStats?.daily.slice().sort((a, b) => b.date.localeCompare(a.date)).map((day) => ( +
+
+
{day.date}
+
运维签章时间
+
+
+
+
{day.total}
+
合计
+
+ {FLOW_TYPES.map((type) => { + const count = day[type]; + return ( + + ); + })} +
+
+ ))} + {!flowLoading && !flowStats?.daily.length && ( +
暂无交换车数据
+ )} +
+
+ )} +
+
+
+ ); +} diff --git a/src/modules/assets/components/overview/InventoryDesktopTable.tsx b/src/modules/assets/components/overview/InventoryDesktopTable.tsx new file mode 100644 index 0000000..601abb2 --- /dev/null +++ b/src/modules/assets/components/overview/InventoryDesktopTable.tsx @@ -0,0 +1,158 @@ +import { InventoryStatusCounts } from './InventoryStatusCounts'; +import React from 'react'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import type { InventoryStatisticsSectionProps } from './types'; + +type InventoryDesktopTableProps = Pick< + InventoryStatisticsSectionProps, + | 'inventoryTab' + | 'inventoryByRegion' + | 'expandedInventoryRegions' + | 'toggleInventoryRegion' + | 'inventoryByModel' + | 'expandedInventoryTypes' + | 'toggleInventoryType' + | 'setVehicleSelection' +>; + +export function InventoryDesktopTable({ + inventoryTab, + inventoryByRegion, + expandedInventoryRegions, + toggleInventoryRegion, + inventoryByModel, + expandedInventoryTypes, + toggleInventoryType, + setVehicleSelection: setShowPlateNumbers, +}: InventoryDesktopTableProps) { + return ( +
+ + + + + + + + + + + {inventoryTab === 'region' ? ( + Object.entries(inventoryByRegion).map(([region, cities]) => ( + + toggleInventoryRegion(region)} + > + + + + {expandedInventoryRegions.has(region) && Object.entries(cities).map(([city, stats]) => ( + + {stats.map((stat, idx) => ( + + + + + + + ))} + + ))} + + + )) + ) : ( + Object.entries(inventoryByModel).map(([type, models]) => ( + + toggleInventoryType(type)} + > + + + + {expandedInventoryTypes.has(type) && Object.entries(models).map(([model, stats]) => ( + + {stats.map((stat, idx) => ( + + + + + + + ))} + + ))} + + + )) + )} + +
{inventoryTab === 'region' ? '区域 / 城市' : '车型分类 / 型号'}{inventoryTab === 'region' ? '品牌' : '品牌'}{inventoryTab === 'region' ? '车型' : '所在区域/城市'}库存数量
+
+ {expandedInventoryRegions.has(region) ? : } + {region} + { + e.stopPropagation(); + setShowPlateNumbers({ batch: 'All', model: 'All', location: region, category: 'Inventory', source: 'asset', title: `库存统计 - ${region}` }); + }}> + (共 {Object.values(cities).flat().reduce((acc, s) => acc + s.quantity, 0)} 台) + + +
+
+ {idx === 0 ? {city} : ''} + {stat.brand}{stat.model} + +
+
+
+ {expandedInventoryTypes.has(type) ? : } + {type} + { + e.stopPropagation(); + setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', type: type, category: 'Inventory', source: 'asset', title: `库存统计 - ${type}` }); + }}> + (共 {Object.values(models).flat().reduce((acc, s) => acc + s.quantity, 0)} 台) + + +
+
+ {idx === 0 ? {model} : ''} + {stat.brand}{stat.region} / {stat.city} + +
+
+
+ ); +} diff --git a/src/modules/assets/components/overview/InventoryMobileList.tsx b/src/modules/assets/components/overview/InventoryMobileList.tsx new file mode 100644 index 0000000..3708293 --- /dev/null +++ b/src/modules/assets/components/overview/InventoryMobileList.tsx @@ -0,0 +1,122 @@ +import { InventoryStatusCounts } from './InventoryStatusCounts'; +import { ChevronDown, ChevronRight } from 'lucide-react'; +import type { InventoryStatisticsSectionProps } from './types'; + +type InventoryMobileListProps = Pick< + InventoryStatisticsSectionProps, + | 'inventoryTab' + | 'inventoryByRegion' + | 'expandedInventoryRegions' + | 'toggleInventoryRegion' + | 'inventoryByModel' + | 'expandedInventoryTypes' + | 'toggleInventoryType' + | 'setVehicleSelection' +>; + +export function InventoryMobileList({ + inventoryTab, + inventoryByRegion, + expandedInventoryRegions, + toggleInventoryRegion, + inventoryByModel, + expandedInventoryTypes, + toggleInventoryType, + setVehicleSelection: setShowPlateNumbers, +}: InventoryMobileListProps) { + return ( +
+ {inventoryTab === 'region' ? ( + Object.entries(inventoryByRegion).map(([region, cities]) => ( +
+
toggleInventoryRegion(region)} + > +
+ {expandedInventoryRegions.has(region) ? : } + {region} +
+ + {Object.values(cities).flat().reduce((acc, s) => acc + s.quantity, 0)} 台 + + +
+ + {expandedInventoryRegions.has(region) && ( +
+ {Object.entries(cities).map(([city, stats]) => ( +
+
{city}
+
+ {stats.map((stat, idx) => ( +
+
+ {stat.brand} + {stat.model} +
+ + {(stat.abnormal || stat.pending) ?
: null} +
+ ))} +
+
+ ))} +
+ )} +
+ )) + ) : ( + Object.entries(inventoryByModel).map(([type, models]) => ( +
+
toggleInventoryType(type)} + > +
+ {expandedInventoryTypes.has(type) ? : } + {type} +
+ + {Object.values(models).flat().reduce((acc, s) => acc + s.quantity, 0)} 台 + + +
+ + {expandedInventoryTypes.has(type) && ( +
+ {Object.entries(models).map(([model, stats]) => ( +
+
{model}
+
+ {stats.map((stat, idx) => ( +
+
+ {stat.brand} + {stat.region} / {stat.city} +
+ + {(stat.abnormal || stat.pending) ?
: null} +
+ ))} +
+
+ ))} +
+ )} +
+ )) + )} +
+ ); +} diff --git a/src/modules/assets/components/overview/InventoryStatisticsSection.tsx b/src/modules/assets/components/overview/InventoryStatisticsSection.tsx new file mode 100644 index 0000000..35b62ce --- /dev/null +++ b/src/modules/assets/components/overview/InventoryStatisticsSection.tsx @@ -0,0 +1,211 @@ +import { InventoryStatusCounts } from './InventoryStatusCounts'; +import { Filter } from 'lucide-react'; +import { AnimatePresence, motion } from 'motion/react'; +import { InventoryDesktopTable } from './InventoryDesktopTable'; +import { InventoryMobileList } from './InventoryMobileList'; +import type { InventoryStatisticsSectionProps } from './types'; + +export function InventoryStatisticsSection({ + inventoryFilters, + setInventoryFilters, + draftInventoryFilters, + setDraftInventoryFilters, + isInventoryFilterOpen, + setIsInventoryFilterOpen, + uniqueInventoryRegions, + uniqueInventoryCities, + uniqueInventoryBrands, + uniqueInventoryTypes, + uniqueInventoryModelsForType, + filteredInventoryStats, + inventoryTab, + setInventoryTab, + inventoryByRegion, + expandedInventoryRegions, + toggleInventoryRegion, + inventoryByModel, + expandedInventoryTypes, + toggleInventoryType, + setVehicleSelection: setShowPlateNumbers, +}: InventoryStatisticsSectionProps) { + const listProps = { + inventoryTab, + inventoryByRegion, + expandedInventoryRegions, + toggleInventoryRegion, + inventoryByModel, + expandedInventoryTypes, + toggleInventoryType, + setVehicleSelection: setShowPlateNumbers, + }; + + return ( +
+
+
+
+
+

库存统计

+ 实时更新库存数据 +
+
+ +
+
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory', source: 'asset', title: '库存总数' })}> + 总库存数据 + + {filteredInventoryStats.reduce((acc, s) => acc + s.quantity, 0)} + 台 + +
+ +
+ + + + {isInventoryFilterOpen && ( + <> + +
+

库存统计 - 数据筛选

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + )} +
+
+
+
+ + {filteredInventoryStats.some(row => (row.abnormal || 0) > 0 || (row.pending || 0) > 0) && ( +
+ + 已包含在库存总数中 +
+ )} + +
+
+ + +
+
+ + {/* Active Filters Bar */} + {(inventoryFilters.region || inventoryFilters.city || inventoryFilters.brand || inventoryFilters.type || inventoryFilters.model) && ( +
+ {inventoryFilters.region && ( + + 区域: {inventoryFilters.region} + + + )} + {inventoryFilters.city && ( + + 城市: {inventoryFilters.city} + + + )} + {inventoryFilters.brand && ( + + 品牌: {inventoryFilters.brand} + + + )} + {inventoryFilters.type && ( + + 车型: {inventoryFilters.type} + + + )} + {inventoryFilters.model && ( + + 批次: {inventoryFilters.model} + + + )} + +
+ )} + + {/* Desktop View Table */} + + + {/* Mobile View */} + +
+ ); +} diff --git a/src/modules/assets/components/overview/InventoryStatusCounts.tsx b/src/modules/assets/components/overview/InventoryStatusCounts.tsx new file mode 100644 index 0000000..cd8a791 --- /dev/null +++ b/src/modules/assets/components/overview/InventoryStatusCounts.tsx @@ -0,0 +1,12 @@ +import type { RegionalInventoryStats } from '../../types'; + +/** Status counts are subsets of these filtered inventory rows, never extra stock. */ +export function InventoryStatusCounts({ rows }: { rows: RegionalInventoryStats[] }) { + const abnormal = rows.reduce((sum, row) => sum + (row.abnormal ?? 0), 0); + const pending = rows.reduce((sum, row) => sum + (row.pending ?? 0), 0); + if (abnormal === 0 && pending === 0) return null; + return + {abnormal > 0 && 其中异动 {abnormal} 台} + {pending > 0 && 其中待交车 {pending} 台} + ; +} diff --git a/src/modules/assets/components/overview/SummaryMetrics.tsx b/src/modules/assets/components/overview/SummaryMetrics.tsx new file mode 100644 index 0000000..e87a2ea --- /dev/null +++ b/src/modules/assets/components/overview/SummaryMetrics.tsx @@ -0,0 +1,87 @@ +import { Activity, TriangleAlert, PlusCircle, Truck, Warehouse } from 'lucide-react'; +import type { SummaryMetricsProps } from './types'; + +export function SummaryMetrics({ + summary: SUMMARY, + operatingRate, + inventoryRate, + setVehicleSelection: setShowPlateNumbers, +}: SummaryMetricsProps) { + return ( + <> + {/* Header Summary - Ultra Compact */} +
+ {/* Total Assets */} +
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', source: 'asset', title: '资产概览' })}> +
+ +
+
+
资产总数
+
{SUMMARY.totalAssets.toLocaleString()}
+
+
+ + {/* Operating */} +
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Operating', source: 'asset', title: '正在运营' })}> +
+ +
+
+
总运营
+
+ {SUMMARY.operating.total} + 自{SUMMARY.operating.self} 租{SUMMARY.operating.leased}{SUMMARY.operating.hanging > 0 && ` 挂${SUMMARY.operating.hanging}`} +
+
+
+ + {/* Inventory */} +
setShowPlateNumbers({ batch: 'All', model: 'All', location: 'All', category: 'Inventory', source: 'asset', title: '库存总数' })}> +
+ +
+
+
总库存
+
+ {SUMMARY.inventory.total} +
+
+ + + +
+
+
+ + +
+ +
+
+
运营概览
+
+
+
运营率
+
{operatingRate.toFixed(1)}%
+
+
+
库存率
+
{inventoryRate.toFixed(1)}%
+
+ +
+
+
+ + ); +} diff --git a/src/modules/assets/components/overview/types.ts b/src/modules/assets/components/overview/types.ts new file mode 100644 index 0000000..a5d4f7b --- /dev/null +++ b/src/modules/assets/components/overview/types.ts @@ -0,0 +1,72 @@ +import type { Dispatch, SetStateAction } from 'react'; +import type { FlowDetailItem, FlowStatsResponse, FlowType } from '../../api'; +import type { + DateRange, + FlowSelection, + InventoryFilters, + VehicleModalSelection, +} from '../../model'; +import type { RegionalInventoryStats, SummaryData, TypeSummary } from '../../types'; +import type { AssetsTheme } from '../AssetsHeader'; + +export interface SummaryMetricsProps { + summary: SummaryData; + operatingRate: number; + inventoryRate: number; + pendingRate: number; + setVehicleSelection: Dispatch>; +} + +export interface FlowStatisticsCardProps { + flowRange: DateRange; + setFlowRange: Dispatch>; + flowLoading: boolean; + flowStats: FlowStatsResponse | null; + exportFlowDetails: (rows?: FlowDetailItem[], title?: string) => void; + flowDailyExpanded: boolean; + setFlowDailyExpanded: Dispatch>; + setSelectedFlow: Dispatch>; +} + +export interface AssetSummarySectionProps { + processedData: TypeSummary[]; + theme: AssetsTheme; + expandedAssetTypes: Set; + toggleAssetType: (type: string) => void; + expandedModels: Set; + toggleModel: (model: string) => void; + setVehicleSelection: Dispatch>; +} + +export interface InventoryStatisticsSectionProps { + inventoryFilters: InventoryFilters; + setInventoryFilters: Dispatch>; + draftInventoryFilters: InventoryFilters; + setDraftInventoryFilters: Dispatch>; + isInventoryFilterOpen: boolean; + setIsInventoryFilterOpen: Dispatch>; + uniqueInventoryRegions: string[]; + uniqueInventoryCities: string[]; + uniqueInventoryBrands: string[]; + uniqueInventoryTypes: string[]; + uniqueInventoryModelsForType: string[]; + filteredInventoryStats: RegionalInventoryStats[]; + inventoryTab: 'region' | 'model'; + setInventoryTab: Dispatch>; + inventoryByRegion: Record>; + expandedInventoryRegions: Set; + toggleInventoryRegion: (region: string) => void; + inventoryByModel: Record>; + expandedInventoryTypes: Set; + toggleInventoryType: (type: string) => void; + setVehicleSelection: Dispatch>; +} + +export type OverviewViewProps = SummaryMetricsProps + & FlowStatisticsCardProps + & AssetSummarySectionProps + & InventoryStatisticsSectionProps + & { + allTypesExpanded: boolean; + toggleAllAssetTypes: () => void; + }; diff --git a/src/modules/assets/components/useDialogFocus.ts b/src/modules/assets/components/useDialogFocus.ts new file mode 100644 index 0000000..07bd3c5 --- /dev/null +++ b/src/modules/assets/components/useDialogFocus.ts @@ -0,0 +1,22 @@ +import { useEffect, type RefObject } from 'react'; + +export function useDialogFocus(open: boolean, ref: RefObject, close: () => void) { + useEffect(() => { + if (!open) return; + const previous = document.activeElement as HTMLElement | null; + const overflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + const frame = requestAnimationFrame(() => ref.current?.focus()); + const onKey = (event: KeyboardEvent) => { + if (event.key === 'Escape') { event.preventDefault(); close(); } + if (event.key !== 'Tab') return; + const items = Array.from(ref.current?.querySelectorAll('button:not([disabled]), input, select, a[href], [tabindex="0"]') || []).filter(el=>el.getClientRects().length>0); + const first = items[0]; const last = items[items.length-1]; + if (!first) { event.preventDefault(); return; } + if (event.shiftKey && (document.activeElement === first || document.activeElement === ref.current)) {event.preventDefault();last.focus();} + else if (!event.shiftKey && (document.activeElement === last || document.activeElement === ref.current)) {event.preventDefault();first.focus();} + }; + document.addEventListener('keydown',onKey); + return () => {cancelAnimationFrame(frame);document.removeEventListener('keydown',onKey);document.body.style.overflow=overflow;previous?.focus();}; + }, [open, ref, close]); +} diff --git a/src/modules/assets/library/AssetOverview.tsx b/src/modules/assets/library/AssetOverview.tsx deleted file mode 100644 index cdcc305..0000000 --- a/src/modules/assets/library/AssetOverview.tsx +++ /dev/null @@ -1,424 +0,0 @@ -import { useState } from "react"; -import { InventoryAnalysis } from "./InventoryAnalysis"; -import { inventoryTotals, type InventoryScope } from "./inventory-view"; -import { InventorySection } from "./InventorySection"; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - CardAction, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { - Accordion, - AccordionItem, - AccordionTrigger, - AccordionContent, -} from "@/components/ui/accordion"; -import { - Package, - Truck, - ArrowLeftRight, - AlertTriangle, - Activity, - Warehouse, - ArrowUpRight, -} from "lucide-react"; -import type { - SummaryData, - TypeSummary, - RegionalInventoryStats, - ModelSummary, - BatchSummary, -} from "../types"; -import type { VehicleModalCategory, VehicleModalSelection } from "../model"; -import type { AssetSection } from "./useAssetHash"; -import { - baseSelection, - ProportionBar, - sectionTabsListClass, - sectionTabsTriggerClass, - type OpenVehicles, -} from "./shared"; -import { DataGrid, type Column } from "./DataGrid"; -import { ExportButton } from "./export"; -import { FlowStatistics } from "./FlowStatistics"; - -/** 运营率环形图(SVG donut)。 */ -function RateDonut({ rate, size = 48 }: { rate: number; size?: number }) { - const stroke = 6; - const r = (size - stroke) / 2; - const circumference = 2 * Math.PI * r; - const filled = (Math.min(100, Math.max(0, rate)) / 100) * circumference; - return ( -
- - -
- ); -} -export function AssetOverview({ - summary: s, - types, - inventory, - subject, - section, - onSectionChange, - open, -}: { - summary: SummaryData; - types: TypeSummary[]; - inventory: RegionalInventoryStats[]; - subject: string | null; - section: AssetSection; - onSectionChange: (section: AssetSection) => void; - open: OpenVehicles; -}) { - const [inventoryScope, setInventoryScope] = useState( - null, - ); - const inventoryCounts = inventoryTotals(inventory); - const operatingRate = s.totalAssets - ? (s.operating.total / s.totalAssets) * 100 - : 0; - const show = (category?: VehicleModalCategory) => { - if ( - category === "Inventory" || - category === "Abnormal" || - category === "Pending" - ) - setInventoryScope({ - rows: inventory, - label: "全部库存", - initialCategory: category === "Inventory" ? undefined : category, - }); - else open({ ...baseSelection, category }); - }; - return ( -
-
- - - - - -
- -
-

- 运营率 -

-

- {operatingRate.toFixed(1)}% -

-
-
-
- 租赁 + 自营 -
- - -
- - 租赁 {s.operating.leased} - - - 自营 {s.operating.self} - -
- -
-
- - - - - - 待运营 + 可运营;异动、待交车已包含在内 - - - - -
- - -
- -
-
-
- onSectionChange(value as AssetSection)} - > - - - - 库存统计 - - - - 车型资产 - - - - 交还车统计 - - - - - - - - - - 车型资产 - - - 按车型、型号与批次逐层查看;本周交还车按车辆去重。 - - - - - {types.map((type) => ( - - - {type.type} · {type.totalAssets} 台 · 库存{" "} - {type.totalInventory} - - - - - {type.models.map((model) => ( - - - {model.model} · 查看批次 - - - - - - ))} - - - - ))} - - - - - - - - - {inventoryScope && ( - setInventoryScope(null)} - /> - )} -
- ); -} -function ModelTable({ - rows, - open, - selection, - batch = false, -}: { - rows: (ModelSummary | BatchSummary)[]; - open: OpenVehicles; - selection: VehicleModalSelection; - batch?: boolean; -}) { - const data = rows.map((row) => ({ - ...row, - name: "model" in row ? row.model : row.batch, - })); - type Row = (typeof data)[number]; - const columns: Column[] = [ - { key: "name", label: batch ? "批次" : "型号", width: 190 }, - ...( - [ - ["total", "资产", undefined], - ["operating", "运营", "Operating"], - ["inventory", "库存", "Inventory"], - ["abnormal", "异动", "Abnormal"], - ["pending", "待交车", "Pending"], - ["weeklyDelivered", "本周交车", "Delivered"], - ["weeklyReturned", "本周还车", "Returned"], - ["weeklyReplaced", "替换交车", "Replaced"], - ] as const - ).map(([key, label, category]) => ({ - key, - label, - action: (row: Row) => - open({ - ...selection, - ...(batch ? { batch: row.name } : { model: row.name }), - category, - }), - })), - { - key: "inventoryRegions", - label: "库存分布", - width: 260, - format: (value) => formatInventoryRegions(value), - tooltip: (value) => formatInventoryRegions(value), - }, - ]; - return ( -
- - -
- ); -} - -/** 库存分布:只展示有库存的区域,避免 0 值噪声;截断时由 tooltip 展示完整内容。 */ -function formatInventoryRegions(value: unknown): string { - const parts = Object.entries(value as Record) - .filter(([, count]) => count > 0) - .map(([region, count]) => `${region} ${count}`); - return parts.join(" · ") || "—"; -} diff --git a/src/modules/assets/library/BusinessViews.tsx b/src/modules/assets/library/BusinessViews.tsx deleted file mode 100644 index 9afe795..0000000 --- a/src/modules/assets/library/BusinessViews.tsx +++ /dev/null @@ -1,695 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, -} from "@/components/ui/card"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { - Accordion, - AccordionItem, - AccordionTrigger, - AccordionContent, -} from "@/components/ui/accordion"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Checkbox } from "@/components/ui/checkbox"; -import { Button } from "@/components/ui/button"; -import { Field, FieldLabel } from "@/components/ui/field"; -import { Input } from "@/components/ui/input"; -import { Skeleton } from "@/components/ui/skeleton"; -import type { - DeptGroup, - ManagerStats, - RegionGroup, - RegionTypeBreakdown, - CustomerStats, -} from "../types"; -import { - deriveDepartmentView, - deriveCustomerView, - type CustomerFilters, -} from "../model"; -import { fetchRegionStats, fetchRegionChart } from "../api"; -const APPLE_CHART_COLORS = [ - "var(--indigo)", - "var(--blue)", - "var(--teal)", - "var(--orange)", - "var(--green)", -]; -import { - baseSelection, - FilterFields, - PrivateText, - unique, - Failure, - type OpenVehicles, -} from "./shared"; -import { DataGrid, type Column } from "./DataGrid"; -import { ExportButton } from "./export"; -import { Chart } from "./Chart"; -const typeFields = [ - ["t4_5", "4.5T普货"], - ["t4_5c", "4.5T冷链"], - ["t18", "18T"], - ["t49", "49T"], - ["trailer", "挂车"], - ["other", "其他"], -] as const; -export function DepartmentView({ - data, - open, -}: { - data: DeptGroup[]; - open: OpenVehicles; -}) { - const [mode, setMode] = useState("department"); - const [manager, setManager] = useState(""); - const view = deriveDepartmentView(data, manager || "All"); - const cols: Column[] = [ - { key: "department", label: "部门", width: 180 }, - { - key: "totalAssets", - label: "车辆总数", - action: (r) => - open({ - ...baseSelection, - source: "department", - category: "Operating", - department: r.department, - }), - }, - { - key: "operatingCount", - label: "出勤车辆", - action: (r) => - open({ - ...baseSelection, - source: "department", - category: "Operating", - department: r.department, - attendance: "active", - }), - }, - { - key: "idleCount", - label: "未出勤车辆", - action: (r) => - open({ - ...baseSelection, - source: "department", - category: "Operating", - department: r.department, - attendance: "idle", - }), - }, - { - key: "attendanceRate", - label: "出勤率", - numeric: true, - format: (v) => `${Number(v).toFixed(1)}%`, - }, - { - key: "avgMileage", - label: "平均里程(km)", - width: 160, - numeric: true, - format: (v) => Number(v).toFixed(1), - }, - ]; - const managerCols: Column[] = [ - { key: "manager", label: "业务负责人", private: true }, - { key: "department", label: "部门", width: 160 }, - ...typeFields.map(([key, type]) => ({ - key, - label: type, - action: (r: ManagerStats) => - open({ - ...baseSelection, - source: "department", - category: "Operating", - department: r.department, - manager: r.manager, - type, - }), - })), - { - key: "total", - label: "合计", - action: (r) => - open({ - ...baseSelection, - source: "department", - category: "Operating", - department: r.department, - manager: r.manager, - }), - }, - ]; - return ( - - - - 部门运营 - - 查看部门出勤表现与负责人车辆分布。 - - -
- - - 按部门 - 按负责人 - - - {mode === "department" ? ( - - ) : ( - - )} -
- {mode === "department" ? ( - <> - - - {data.map((d) => ( - - - {d.department} · 负责人明细 - - - - - - ))} - - - ) : ( - <> - setManager("")} - /> - - - )} -
-
- ); -} -export function RegionView({ - data, - subject, - open, -}: { - data: RegionGroup[]; - subject: string | null; - open: OpenVehicles; -}) { - const [chartView, setChartView] = useState("region"); - const [filters, setFilters] = useState({ - region: "", - city: "", - customer: "", - }); - const [filtered, setFiltered] = useState(data); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); - const [retry, setRetry] = useState(0); - useEffect(() => { - let active = true; - if (!filters.region && !filters.city && !filters.customer) { - setFiltered(data); - setLoading(false); - setError(""); - return; - } - setLoading(true); - setError(""); - fetchRegionStats(filters, subject) - .then((v) => { - if (active) setFiltered(v); - }) - .catch((e) => { - if (active) setError(String(e.message || e)); - }) - .finally(() => { - if (active) setLoading(false); - }); - return () => { - active = false; - }; - }, [filters, subject, data, retry]); - const option = useMemo( - () => ({ - color: APPLE_CHART_COLORS, - textStyle: { color: "var(--label-secondary)" }, - tooltip: { trigger: "axis" }, - grid: { left: 45, right: 15, top: 20, bottom: 45 }, - xAxis: { - type: "category", - data: filtered.map((r) => r.region), - axisLabel: { color: "var(--label-secondary)", interval: 0 }, - axisLine: { lineStyle: { color: "var(--separator)" } }, - }, - yAxis: { - type: "value", - minInterval: 1, - axisLabel: { color: "var(--label-secondary)" }, - splitLine: { lineStyle: { color: "var(--separator)" } }, - }, - series: [ - { - type: "bar", - name: "车辆", - data: filtered.map((r) => r.totalAssets), - barMaxWidth: 48, - itemStyle: { borderRadius: [4, 4, 0, 0] }, - label: { - show: true, - position: "top", - color: "var(--label-secondary)", - }, - }, - ], - }), - [filtered], - ); - const breakdown = (rows: RegionTypeBreakdown[], location: string) => ( - ({ - key, - label, - action: (r: RegionTypeBreakdown) => - open({ - ...baseSelection, - source: "region", - location: filters.city || location, - customer: filters.customer || undefined, - vehicleType: r.type === "其他" ? "区域其他" : r.type, - category, - }), - })), - ]} - /> - ); - return ( - - - - 区域分布 - - - 运营与待交车车辆,按区域 → 城市 → 车型查看。 - - - - r.region), - change: (v) => setFilters((f) => ({ ...f, region: v, city: "" })), - }, - { - label: "城市", - value: filters.city, - values: unique( - data - .filter((r) => !filters.region || r.region === filters.region) - .flatMap((r) => r.cities.map((c) => c.city)), - ), - change: (v) => setFilters((f) => ({ ...f, city: v })), - }, - { - label: "客户", - value: filters.customer, - values: unique(data.flatMap((r) => r.customers)), - change: (v) => setFilters((f) => ({ ...f, customer: v })), - }, - ]} - onReset={() => setFilters({ region: "", city: "", customer: "" })} - /> - {error ? ( - setRetry((n) => n + 1)} /> - ) : loading ? ( - - ) : ( - <> - - - 区域分布 - 实时省份 - - - {chartView === "region" ? ( - - ) : ( - - )} - - {filtered.map((r) => ( - - - {r.region} · {r.totalAssets} 台 - - - {breakdown(r.typeBreakdown, r.region)} - - {r.cities.map((city) => ( - - - {city.city} · {city.totalAssets} 台 - - - {breakdown(city.typeBreakdown, city.city)} - - - ))} - - - - ))} - - {!filtered.length && ( - 暂无符合条件的区域数据 - )} - - )} - - - ); -} -const initialCustomer: CustomerFilters = { - customer: [], - brand: "", - department: "", - manager: "", - region: "", -}; -export function CustomerView({ - data, - departments, - subject, - open, -}: { - data: CustomerStats[]; - subject: string | null; - departments: DeptGroup[]; - open: OpenVehicles; -}) { - const [filters, setFilters] = useState(initialCustomer); - const [customerSearch, setCustomerSearch] = useState(""); - const [chartGroup, setChartGroup] = useState("region"); - const view = deriveCustomerView(data, departments, filters); - const option = useMemo(() => { - const counts = new Map(); - for (const row of view.filtered) { - const name = chartGroup === "region" ? row.region : row.city; - counts.set( - name || "未归属", - (counts.get(name || "未归属") || 0) + row.total, - ); - } - return { - color: APPLE_CHART_COLORS, - textStyle: { color: "var(--label-secondary)" }, - tooltip: { trigger: "item" }, - legend: { - type: "scroll", - bottom: 0, - textStyle: { color: "var(--label-secondary)" }, - }, - series: [ - { - type: "pie", - radius: ["35%", "65%"], - label: { show: false }, - data: [...counts].map(([name, value]) => ({ name, value })), - }, - ], - }; - }, [view.filtered, chartGroup]); - const cols: Column[] = [ - { key: "customer", label: "客户", width: 220, private: true }, - { key: "manager", label: "负责人", private: true }, - { key: "department", label: "部门", width: 150 }, - { key: "brand", label: "品牌" }, - { key: "region", label: "区域" }, - { key: "city", label: "城市" }, - ...typeFields.map(([key, type]) => ({ - key, - label: type, - action: (r: CustomerStats) => - open({ - ...baseSelection, - source: "customer", - category: "Operating", - customer: r.customer, - type, - }), - })), - { - key: "total", - label: "合计", - action: (r) => - open({ - ...baseSelection, - source: "customer", - category: "Operating", - customer: r.customer, - }), - }, - ]; - return ( - - - - 客户资产 - - - 按客户、负责人、品牌与区域查看车辆分布。 - - - -
- - - - - - setCustomerSearch(e.target.value)} - /> -
- {view.customerNames - .filter((c) => c.includes(customerSearch)) - .map((c) => ( - - - setFilters((f) => ({ - ...f, - customer: checked - ? [...f.customer, c] - : f.customer.filter((x) => x !== c), - })) - } - /> - - {c} - - - ))} -
-
-
- -
- setFilters((f) => ({ ...f, brand: v })), - }, - { - label: "部门", - value: filters.department, - values: view.departments, - change: (v) => - setFilters((f) => ({ ...f, department: v, manager: "" })), - }, - { - label: "负责人", - value: filters.manager, - values: unique( - view.managersByDepartment - .filter( - (d) => - !filters.department || - d.department === filters.department, - ) - .flatMap((d) => d.managers), - ), - change: (v) => setFilters((f) => ({ ...f, manager: v })), - }, - { - label: "区域", - value: filters.region, - values: view.regions, - change: (v) => setFilters((f) => ({ ...f, region: v })), - }, - ]} - onReset={() => setFilters(initialCustomer)} - /> - - - 区域分布 - 城市分布 - 省份分布 - - - {chartGroup === "province" ? ( - - ) : ( - - )} - -
-
- ); -} - -function ProvinceChart({ - subject, - source, -}: { - subject: string | null; - source: "vehicle" | "realtime"; -}) { - const [rows, setRows] = useState<{ name: string; value: number }[] | null>( - null, - ); - const [error, setError] = useState(""); - const [retry, setRetry] = useState(0); - useEffect(() => { - let active = true; - setRows(null); - setError(""); - fetchRegionChart("province", 5, source, subject) - .then((v) => { - if (active) setRows(v); - }) - .catch((e) => { - if (active) setError(e.message || "加载失败"); - }); - return () => { - active = false; - }; - }, [subject, source, retry]); - const option = useMemo( - () => ({ - color: APPLE_CHART_COLORS, - textStyle: { color: "var(--label-secondary)" }, - tooltip: { trigger: "item" }, - legend: { - type: "scroll", - bottom: 0, - textStyle: { color: "var(--label-secondary)" }, - }, - series: [ - { - type: "pie", - radius: ["35%", "65%"], - label: { show: false }, - data: rows || [], - }, - ], - }), - [rows], - ); - return ( -
- - {source === "realtime" ? "实时定位" : "车辆档案"}省份分布 · - 当前公司范围,不受表格筛选影响 - - {error ? ( - setRetry((n) => n + 1)} /> - ) : rows ? ( - - ) : ( - - )} -
- ); -} diff --git a/src/modules/assets/library/DataGrid.tsx b/src/modules/assets/library/DataGrid.tsx deleted file mode 100644 index ca607b4..0000000 --- a/src/modules/assets/library/DataGrid.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import { useMemo, useState, useEffect, lazy, Suspense, useRef } from "react"; -import { ArrowUpRight, Search, SearchX, X } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { useDemoMode } from "@/components/Blur"; -import { Count, useMobile } from "./shared"; -import { - Accordion, - AccordionItem, - AccordionTrigger, - AccordionContent, -} from "@/components/ui/accordion"; -import { Skeleton } from "@/components/ui/skeleton"; -const DesktopGrid = lazy( - () => import("./DesktopGrid"), -) as typeof import("./DesktopGrid").default; -export type Column = { - key: keyof T & string; - label: string; - width?: number; - private?: boolean; - primary?: boolean; - action?: (row: T) => void; - format?: (value: unknown, row: T) => string; - numeric?: boolean; - tooltip?: (value: unknown, row: T) => string; -}; -export function DataGrid({ - rows, - columns, - label = "明细", - search = true, -}: { - rows: T[]; - columns: Column[]; - label?: string; - search?: boolean; -}) { - const mobile = useMobile(); - const demo = useDemoMode(); - const [query, setQuery] = useState(""); - const [page, setPage] = useState(0); - const [compact, setCompact] = useState(true); - const top = useRef(null); - const pageSize = 20; - const display = (row: T, col: Column) => - col.private && demo - ? "••••••" - : col.format - ? col.format(row[col.key], row) - : String(row[col.key] ?? "—"); - const filtered = useMemo(() => { - const term = query.trim().toLocaleLowerCase(); - return rows.filter( - (row) => - !term || - columns.some((col) => - display(row, col).toLocaleLowerCase().includes(term), - ), - ); - }, [rows, columns, query, demo]); - useEffect(() => setPage(0), [query, rows.length]); - const pages = Math.max(1, Math.ceil(filtered.length / pageSize)); - const currentPage = Math.min(page, pages - 1); - const renderValue = (row: T, col: Column) => { - if (col.private && demo) return "••••••"; - if (col.action) - return ( - col.action!(row)} - /> - ); - const tooltip = col.tooltip?.(row[col.key], row); - return tooltip ? ( - {display(row, col)} - ) : ( - display(row, col) - ); - }; - const metrics = columns.slice(1).filter((col) => col.action || col.numeric); - const textColumns = columns.slice(1).filter((col) => !metrics.includes(col)); - const primaryColumns = textColumns.filter( - (col, index) => index < 4 || col.primary, - ); - const moreColumns = textColumns.filter( - (col) => !primaryColumns.includes(col), - ); - const fields = (row: T, list: Column[]) => ( -
- {list.map((col) => ( -
-
{col.label}
-
{renderValue(row, col)}
-
- ))} -
- ); - const changePage = (next: number) => { - setPage(next); - top.current?.scrollIntoView({ block: "start", behavior: "instant" }); - }; - return ( -
-
-

- {query - ? `找到 ${filtered.length} / ${rows.length} 条记录` - : `共 ${filtered.length} 条记录`} - {columns.some((col) => col.action) && ( - 蓝色数量可查看明细 - )} -

-
- {search && (rows.length > 8 || query) && ( -
-
- )} - {!mobile && rows.length > 0 && ( - - )} -
-
- {!filtered.length ? ( -
-
- ) : mobile ? ( - <> - {filtered - .slice(currentPage * pageSize, (currentPage + 1) * pageSize) - .map((row, index) => ( -
-
-
-

- {columns[0].label} -

-

{renderValue(row, columns[0])}

-
- - {String(currentPage * pageSize + index + 1).padStart( - 2, - "0", - )} - -
- {primaryColumns.length > 0 && fields(row, primaryColumns)} - {metrics.length > 0 && ( -
- {metrics.map((col) => { - const value = Number(row[col.key]) || 0; - const content = ( - <> - - {col.label} - - - {display(row, col)} - {col.action && value > 0 && ( - - - ); - return col.action && - value > 0 && - !(col.private && demo) ? ( - - ) : ( -
- {content} -
- ); - })} -
- )} - {moreColumns.length > 0 && ( - - - - 更多车辆信息 · {moreColumns.length} 项 - - - {fields(row, moreColumns)} - - - - )} -
- ))} - {filtered.length > pageSize && ( - - )} - - ) : ( -
pageSize ? "h-[36rem] min-w-0" : "min-w-0" - } - role="region" - aria-label={label} - > - }> - - -
- )} -
- ); -} diff --git a/src/modules/assets/library/DesktopGrid.tsx b/src/modules/assets/library/DesktopGrid.tsx deleted file mode 100644 index 3bbaf2d..0000000 --- a/src/modules/assets/library/DesktopGrid.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { AgGridReact } from "ag-grid-react"; -import { - AllCommunityModule, - ModuleRegistry, - themeQuartz, - type ColDef, - type ICellRendererParams, -} from "ag-grid-community"; -import { useDemoMode } from "@/components/Blur"; -import { Count } from "./shared"; -import type { Column } from "./DataGrid"; -ModuleRegistry.registerModules([AllCommunityModule]); -const gridTheme = themeQuartz.withParams({ - fontFamily: "inherit", - fontSize: 14, - rowHeight: 48, - headerHeight: 36, - spacing: 6, - borderRadius: 8, - accentColor: "var(--blue)", - backgroundColor: "var(--bg-tertiary)", - foregroundColor: "var(--label)", - headerBackgroundColor: "var(--bg-secondary)", - borderColor: "var(--separator)", - oddRowBackgroundColor: "var(--bg-secondary)", -}); -const localeText = { - noRowsToShow: "暂无数据", - page: "页", - of: "/", - to: "–", - more: "更多", - nextPage: "下一页", - previousPage: "上一页", - firstPage: "首页", - lastPage: "末页", - pageSizeSelectorLabel: "每页", - loadingOoo: "加载中…", - filterOoo: "筛选…", - equals: "等于", - notEqual: "不等于", - contains: "包含", - notContains: "不包含", - startsWith: "开头是", - endsWith: "结尾是", - blank: "空白", - notBlank: "非空", - lessThan: "小于", - greaterThan: "大于", - inRange: "范围", - andCondition: "且", - orCondition: "或", -}; -export default function DesktopGrid({ - rows, - columns, - compact = false, -}: { - rows: T[]; - columns: Column[]; - compact?: boolean; -}) { - const demo = useDemoMode(); - const display = (row: T, col: Column) => - col.private && demo - ? "••••••" - : col.format - ? col.format(row[col.key], row) - : String(row[col.key] ?? "—"); - const defs: ColDef[] = columns.map((col, index) => { - const numeric = Boolean(col.numeric || col.action); - return { - colId: col.key, - pinned: index === 0 ? "left" : col.key === "total" ? "right" : undefined, - headerName: col.label, - minWidth: col.width || 110, - initialFlex: 1, - cellStyle: numeric ? { textAlign: "right" } : undefined, - cellClass: numeric ? "tabular-nums" : undefined, - headerClass: numeric ? "tabular-nums ag-numeric-header" : undefined, - valueGetter: ({ data }) => - data ? (demo && col.private ? "••••••" : data[col.key]) : "", - valueFormatter: ({ data }) => (data ? display(data, col) : ""), - cellRenderer: col.action - ? (p: ICellRendererParams) => - p.data && ( - col.action!(p.data!)} - /> - ) - : col.tooltip - ? (p: ICellRendererParams) => - p.data && ( - - {display(p.data, col)} - - ) - : undefined, - }; - }); - return ( - - theme={gridTheme} - rowHeight={compact ? 36 : 48} - tooltipShowDelay={350} - rowData={rows} - columnDefs={defs} - defaultColDef={{ sortable: true, filter: true, resizable: true }} - domLayout={rows.length <= 20 ? "autoHeight" : "normal"} - pagination={rows.length > 20} - paginationPageSize={20} - paginationPageSizeSelector={[20, 50, 100]} - localeText={localeText} - /> - ); -} diff --git a/src/modules/assets/library/FlowStatistics.tsx b/src/modules/assets/library/FlowStatistics.tsx deleted file mode 100644 index d77f79b..0000000 --- a/src/modules/assets/library/FlowStatistics.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import { useCallback, useEffect, useState, useMemo } from "react"; -import { zhCN } from "date-fns/locale"; -import { - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Calendar } from "@/components/ui/calendar"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, -} from "@/components/ui/dialog"; -import { Skeleton } from "@/components/ui/skeleton"; -import { CalendarDays, Download } from "lucide-react"; -import { - fetchFlowStats, - type FlowStatsResponse, - type FlowDetailItem, - type FlowDailyPoint, -} from "../api"; -import { - getRecentFlowRange, - getWeeklyFlowRange, - formatLocalDate, - selectFlowDetails, - type FlowSelection, -} from "../model"; -import { Failure, useMobile } from "./shared"; -import { DataGrid, type Column } from "./DataGrid"; -import { exportToXlsx } from "./export"; -import { Chart } from "./Chart"; -export function FlowStatistics({ subject }: { subject: string | null }) { - const [range, setRange] = useState(getRecentFlowRange); - const [calendarOpen, setCalendarOpen] = useState(false); - const [draft, setDraft] = useState<{ from: Date | undefined; to?: Date }>({ - from: new Date(`${range.start}T00:00:00`), - to: new Date(`${range.end}T00:00:00`), - }); - const [data, setData] = useState(null); - const [error, setError] = useState(""); - const [retry, setRetry] = useState(0); - const [selection, setSelection] = useState(null); - const mobile = useMobile(); - useEffect(() => { - let active = true; - setData(null); - setError(""); - fetchFlowStats({ ...range, subject }) - .then((v) => { - if (active) setData(v); - }) - .catch((e) => { - if (active) setError(String(e.message || e)); - }); - return () => { - active = false; - }; - }, [range, subject, retry]); - const details = data && selection ? selectFlowDetails(data, selection) : []; - const columns: Column[] = [ - { key: "plateNumber", label: "车牌", private: true }, - { key: "typeLabel", label: "类型" }, - { key: "date", label: "业务日期" }, - { key: "eventTime", label: "运维签章时间", width: 180 }, - { key: "submitTime", label: "建单时间", width: 180 }, - { key: "department", label: "部门" }, - { key: "manager", label: "负责人", private: true }, - { key: "customerName", label: "客户", private: true, width: 180 }, - ]; - const exportRows = (rows: FlowDetailItem[], filename: string) => - exportToXlsx(rows, columns, filename, "交还车明细"); - const option = useMemo( - () => ({ - color: ["var(--blue)", "var(--orange)", "var(--indigo)"], - textStyle: { color: "var(--label-secondary)" }, - tooltip: { trigger: "axis" }, - legend: { - data: ["交车", "还车", "替换交车"], - textStyle: { color: "var(--label-secondary)" }, - }, - grid: { left: 40, right: 20, bottom: 40, top: 40 }, - xAxis: { - type: "category", - data: data?.daily.map((d) => d.date.slice(5)) || [], - axisLabel: { color: "var(--label-secondary)" }, - axisLine: { lineStyle: { color: "var(--separator)" } }, - }, - yAxis: { - type: "value", - minInterval: 1, - axisLabel: { color: "var(--label-secondary)" }, - splitLine: { lineStyle: { color: "var(--separator)" } }, - }, - series: ["delivered", "returned", "replaced"].map((key, i) => ({ - name: ["交车", "还车", "替换交车"][i], - type: "line", - data: data?.daily.map((d) => d[key as "delivered"]) || [], - })), - }), - [data], - ); - return ( - - - - 交还车统计(车次) - - - 按运维签章时间统计;交车含替换交车,合计 = 交车 + 还车。 - - - -
- { - setCalendarOpen(v); - if (v) - setDraft({ - from: new Date(`${range.start}T00:00:00`), - to: new Date(`${range.end}T00:00:00`), - }); - }} - > - - - - - setDraft(v || { from: undefined })} - numberOfMonths={mobile ? 1 : 2} - disabled={{ after: new Date() }} - /> - - - - - - -
- {error ? ( - setRetry((n) => n + 1)} /> - ) : !data ? ( - - ) : ( - <> -
- {( - [ - ["total", "合计"], - ["delivered", "交车"], - ["returned", "还车"], - ["replaced", "替换交车"], - ] as const - ).map(([key, title]) => ( - - - {title} - - {key === "total" ? ( - data.totals[key] - ) : ( - - )} - - - - ))} -
- - ({ - key, - label: ["交车", "还车", "替换交车"][i], - action: (row: FlowDailyPoint) => - setSelection({ date: row.date, type: key }), - }), - ), - ]} - /> - - )} -
- !v && setSelection(null)}> - - - 交还车明细 - - {details.length} 车次 ·{" "} - {selection && - ("date" in selection - ? selection.date - : `${selection.start} — ${selection.end}`)} - - - - - - -
- ); -} diff --git a/src/modules/assets/library/InventoryAnalysis.tsx b/src/modules/assets/library/InventoryAnalysis.tsx deleted file mode 100644 index ba4b14d..0000000 --- a/src/modules/assets/library/InventoryAnalysis.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import { useMemo, useRef, useState } from "react"; -import { ArrowLeft, ArrowUpRight, X, AlertTriangle, Truck } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - inventoryModel, - inventoryTotals, - scopedInventorySelection, - type InventoryScope, -} from "./inventory-view"; -import { VehicleDetails } from "./VehicleDetails"; -import type { VehicleModalSelection } from "../model"; -import type { RegionalInventoryStats } from "../types"; -import { emptyInventoryCounts } from "../../../shared/assets/inventory"; - -export function InventoryAnalysis({ - scope, - subject, - close, -}: { - scope: InventoryScope; - subject: string | null; - close: () => void; -}) { - const [selection, setSelection] = useState( - () => - scope.initialCategory - ? scopedInventorySelection(scope, { category: scope.initialCategory }) - : null, - ); - const [query, setQuery] = useState(""); - const originFocus = useRef( - document.activeElement instanceof HTMLElement - ? document.activeElement - : null, - ); - const scrollTop = useRef(0); - const total = inventoryTotals(scope.rows); - const open = (extra: Partial = {}) => - setSelection(scopedInventorySelection(scope, extra)); - const segments = [ - { - key: "available" as const, - label: "可运营", - count: total.available, - color: "var(--blue)", - }, - { - key: "awaiting" as const, - label: "待运营", - count: total.awaiting, - color: "var(--inventory-awaiting)", - }, - ...(total.unclassified - ? [ - { - key: "unclassified" as const, - label: "待核实状态", - count: total.unclassified, - color: "var(--label-secondary)", - }, - ] - : []), - ]; - const variants = useMemo(() => { - const groups = new Map< - string, - { - rows: RegionalInventoryStats[]; - color: string; - count: number; - name: string; - brand: string; - operator: string; - } - >(); - for (const row of scope.rows) { - const model = inventoryModel(row.model, row.brand); - const colors = row.variants || { - 未标注颜色: { ...emptyInventoryCounts(), quantity: row.quantity }, - }; - for (const [color, counts] of Object.entries(colors)) { - const key = JSON.stringify([row.brand, row.model, color]); - const value = groups.get(key) || { - rows: [], - color, - count: 0, - name: model.name, - brand: row.brand, - operator: model.operator, - }; - value.rows.push(row); - value.count += counts.quantity; - groups.set(key, value); - } - } - return [...groups.entries()].sort((a, b) => b[1].count - a[1].count); - }, [scope.rows]); - const shown = variants.filter(([, item]) => - [item.name, item.brand, item.operator, item.color] - .join(" ") - .toLowerCase() - .includes(query.trim().toLowerCase()), - ); - let offset = 0; - return ( - <> - !value && close()}> - { - event.preventDefault(); - if (!selection) originFocus.current?.focus({ preventScroll: true }); - }} - > - -
- -
-

库存分析

- 库存构成 -
- -
- - {subject || "全部公司"} · {scope.label} - -
-
{ - if (node) node.scrollTop = scrollTop.current; - }} - onScroll={(event) => { - scrollTop.current = event.currentTarget.scrollTop; - }} - > -
-
-
- - - {segments - .filter((segment) => segment.count > 0) - .map((segment) => { - const proportion = segment.count / total.quantity; - const dashOffset = -offset; - offset += proportion * 100; - return ( - - open({ - inventoryOperation: segment.key, - title: `${segment.label}车辆`, - }) - } - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - open({ - inventoryOperation: segment.key, - title: `${segment.label}车辆`, - }); - } - }} - > - - {segment.label} {segment.count} 台 ·{" "} - {(proportion * 100).toFixed(1)}% - - - ); - })} - -
- 总库存 - {total.quantity.toLocaleString()} - 台 -
-
-
-

运营状态

- {segments.map((segment) => ( - - ))} -

- {total.unclassified - ? "运营状态按原始字段分别统计" - : "可运营 + 待运营 = 总库存"} -

- {total.unclassified > 0 && ( -

- 存在旧状态,单独列示待核实,未计入可运营或待运营。 -

- )} -
-
-
-
-

业务状态

- 已包含在总库存内 -
- -
- {Object.entries(total.abnormalTypes).map(([type, count]) => ( - - ))} - {!total.abnormal && 当前范围无异动车辆} -
- -
-
-
-
-
-

型号明细

-

品牌 · 所属公司 · 颜色

-
- -
- setQuery(event.target.value)} - /> -
- {shown.map(([key, item]) => ( - - ))} - {!shown.length && ( -

没有匹配的型号

- )} -
-
-
-
- 点击数量查看当前范围的车辆明细 - -
-
-
- setSelection(null)} - returnLabel="返回库存构成" - /> - - ); -} diff --git a/src/modules/assets/library/InventoryExplorer.tsx b/src/modules/assets/library/InventoryExplorer.tsx deleted file mode 100644 index 2601fd0..0000000 --- a/src/modules/assets/library/InventoryExplorer.tsx +++ /dev/null @@ -1,475 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import { - ArrowLeft, - ArrowRight, - ChevronRight, - MapPin, - Search, - Truck, - X, -} from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import type { RegionalInventoryStats } from "../types"; -import { Picker, unique } from "./shared"; -import { VehicleDetails } from "./VehicleDetails"; -import { - INVENTORY_TYPES, - inventoryModel, - inventoryTotals, - scopedInventorySelection, -} from "./inventory-view"; -import { - inventoryBrowseRows, - inventoryBrowseUrl, - inventoryModels, - inventoryPlaces, - inventoryTypeRows, - readInventoryBrowse, - type InventoryBrowseState, -} from "./inventory-explorer"; - -const fmt = (value: number) => value.toLocaleString(); -const regionName = (value: string | null) => value || "未归属地区"; -const cityName = (value: string | null) => value || "未归属城市"; - -export function InventoryExplorer({ - inventory, - subject, -}: { - inventory: RegionalInventoryStats[]; - subject: string | null; -}) { - const [state, setState] = useState(() => - readInventoryBrowse(window.location.search), - ); - const sectionRef = useRef(null); - const headingRef = useRef(null); - const positions = useRef( - new Map< - string, - { scroll: [Element, number][]; y: number; focus?: string } - >(), - ); - const currentUrl = useRef(window.location.href); - const locationKey = JSON.stringify([state.region, state.city]); - const lastLocation = useRef(locationKey); - - const remember = () => { - const scroll: [Element, number][] = []; - let element = sectionRef.current?.parentElement; - while (element) { - if (element.scrollHeight > element.clientHeight) - scroll.push([element, element.scrollTop]); - element = element.parentElement; - } - positions.current.set(currentUrl.current, { - scroll, - y: window.scrollY, - focus: (document.activeElement as HTMLElement | null)?.dataset - .inventoryKey, - }); - }; - useEffect(() => { - const onBack = () => { - currentUrl.current = window.location.href; - setState(readInventoryBrowse(window.location.search)); - }; - window.addEventListener("popstate", onBack); - return () => window.removeEventListener("popstate", onBack); - }, []); - useEffect(() => { - if (lastLocation.current === locationKey) return; - lastLocation.current = locationKey; - const saved = positions.current.get(currentUrl.current); - if (saved) { - const target = saved.focus - ? Array.from( - sectionRef.current?.querySelectorAll( - "[data-inventory-key]", - ) || [], - ).find((element) => element.dataset.inventoryKey === saved.focus) - : null; - (target || headingRef.current)?.focus({ preventScroll: true }); - saved.scroll.forEach(([element, top]) => { - element.scrollTop = top; - }); - window.scrollTo({ top: saved.y, behavior: "instant" }); - } else { - headingRef.current?.focus({ preventScroll: true }); - headingRef.current?.scrollIntoView({ - block: "nearest", - behavior: "instant", - }); - } - }, [locationKey]); - - const navigate = (change: Partial, replace = false) => { - remember(); - const next = { ...state, ...change }; - const url = inventoryBrowseUrl(window.location.href, next); - if (url === window.location.href) return; - if (replace) window.history.replaceState(window.history.state, "", url); - else - window.history.pushState( - { ...window.history.state, inventoryParent: window.location.href }, - "", - url, - ); - currentUrl.current = url; - setState(next); - }; - const goBack = () => { - const next = - state.model !== null - ? { model: null } - : state.city !== null - ? { city: null, model: null } - : { region: null, city: null, model: null }; - const url = inventoryBrowseUrl(window.location.href, { ...state, ...next }); - if (window.history.state?.inventoryParent === url) window.history.back(); - else navigate(next); - }; - - const scoped = useMemo( - () => inventoryBrowseRows(inventory, state), - [inventory, state], - ); - const rows = useMemo( - () => inventoryTypeRows(scoped, state.type), - [scoped, state.type], - ); - const total = inventoryTotals(rows); - const places = inventoryPlaces( - scoped, - state.region === null ? "region" : "city", - state.type, - ); - const models = useMemo(() => inventoryModels(rows), [rows]); - const chosenModel = models.find((model) => model.key === state.model); - const label = [ - state.region === null ? "全部地区" : regionName(state.region), - state.city === null ? "" : cityName(state.city), - state.type || "全部车型", - ] - .filter(Boolean) - .join(" / "); - const selection = useMemo( - () => - chosenModel - ? scopedInventorySelection({ - rows: chosenModel.rows, - label: `${label} / ${chosenModel.brand || "未标注品牌"} / ${chosenModel.model}`, - }) - : null, - [chosenModel, label], - ); - const level = state.region === null ? 0 : state.city === null ? 1 : 2; - const hasFilters = Boolean(state.query || state.brand); - - return ( -
-
-
-

库存找车

-

选地区、选城市,快速找到对应车辆

-
-
-
    - {["选地区", "选城市", "查车辆"].map((title, index) => ( -
  1. - {index + 1} - {title} - {index < 2 &&
  2. - ))} -
- -
- {level > 0 && ( - - )} -
-

- {state.city !== null - ? cityName(state.city) - : state.region !== null - ? regionName(state.region) - : "地区库存"} -

-

- {state.type || "全部车型"} - {hasFilters ? " · 已筛选" : ""} ·{" "} - {state.city !== null - ? `${models.length} 种型号` - : `${places.filter((place) => place.total.quantity > 0).length} 个${level === 0 ? "地区" : "城市"}有库存`} -

-
-
- {fmt(total.quantity)} - 库存 / 台 -
-
-
- 找什么车型 - 数量为当前范围库存 -
-
- {["", ...INVENTORY_TYPES].map((type) => ( - - ))} -
-
- - row.brand))} - change={(brand) => navigate({ brand, model: null })} - /> -
-
- - {level === 0 - ? "选择地区" - : level === 1 - ? "选择城市" - : "选择型号查看车辆"} - - {hasFilters ? ( - - ) : ( - {level < 2 ? "按库存从多到少" : "点击进入车辆明细"} - )} -
- {total.quantity === 0 && ( -
-
- )} - {level < 2 ? ( -
- {places.map((place) => ( - - ))} -
- ) : ( -
- {models.map((model) => ( - - ))} -
- )} - {state.model !== null && !chosenModel && ( -
- 该型号已不在当前库存范围 - -
- )} -
- 库存包含待交车、异动车辆,不代表均可立即交付。 - 「其他」包含挂车及未单列车型。 -
- -
- ); -} diff --git a/src/modules/assets/library/InventorySection.tsx b/src/modules/assets/library/InventorySection.tsx deleted file mode 100644 index e864dfb..0000000 --- a/src/modules/assets/library/InventorySection.tsx +++ /dev/null @@ -1,460 +0,0 @@ -import { Fragment, useMemo, useState } from "react"; -import { - ChevronDown, - ChevronRight, - ChevronsDownUp, - ListFilter, - Search, - X, - ArrowUpRight, -} from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import type { RegionalInventoryStats } from "../types"; -import { Picker, unique, useMobile } from "./shared"; -import { ExportButton } from "./export"; -import { InventoryExplorer } from "./InventoryExplorer"; -import { - groupInventory, - INVENTORY_TYPES, - inventoryColumn, - inventoryTotals, - type InventoryScope, -} from "./inventory-view"; - -type Inspect = (scope: InventoryScope) => void; -export function InventoryNumber({ - rows, - label, - inspect, -}: { - rows: RegionalInventoryStats[]; - label: string; - inspect: Inspect; -}) { - const total = inventoryTotals(rows); - if (!total.quantity) return 0; - return ( -
- - {(total.pending > 0 || total.abnormal > 0) && ( -
- {total.pending > 0 && ( - - )} - {total.abnormal > 0 && ( - - )} -
- )} -
- ); -} -export function InventorySection({ - inventory, - inspect, - subject, -}: { - subject: string | null; - inventory: RegionalInventoryStats[]; - inspect: Inspect; -}) { - const mobile = useMobile(); - const [query, setQuery] = useState(""); - const [filters, setFilters] = useState({ region: "", city: "", brand: "" }); - const [filterOpen, setFilterOpen] = useState(false); - const [expanded, setExpanded] = useState>(new Set()); - const [expandedType, setExpandedType] = useState(""); - const filtered = useMemo( - () => - inventory.filter( - (row) => - Object.entries(filters).every( - ([key, value]) => - !value || row[key as keyof typeof filters] === value, - ) && - (!query.trim() || - [ - row.region, - row.city, - row.brand, - row.type, - row.model, - inventoryColumn(row.type), - ] - .join(" ") - .toLowerCase() - .includes(query.trim().toLowerCase())), - ), - [inventory, filters, query], - ); - const rows = filtered; - const groups = groupInventory(rows, "region"); - const brands = unique( - filtered - .filter((row) => inventoryColumn(row.type) === expandedType) - .map((row) => row.brand), - ); - const hasBrandColumns = Boolean(expandedType) && brands.length > 0; - const columns = INVENTORY_TYPES.flatMap((type) => [ - { - key: type, - title: expandedType === type && brands.length ? "小计" : type, - type, - brand: "", - }, - ...(expandedType === type - ? brands.map((brand) => ({ - key: `${type}-${brand}`, - title: brand, - type, - brand, - })) - : []), - ]); - const activeCount = Object.values(filters).filter(Boolean).length; - const total = inventoryTotals(rows); - const toggleRegion = (region: string) => - setExpanded((old) => { - const next = new Set(old); - if (next.has(region)) next.delete(region); - else next.add(region); - return next; - }); - const cellRows = ( - records: RegionalInventoryStats[], - type: string, - brand: string, - ) => - records.filter( - (row) => - inventoryColumn(row.type) === type && (!brand || row.brand === brand), - ); - const renderCells = (records: RegionalInventoryStats[], label: string) => ( - <> - - - - {columns.map((col) => ( - - - - ))} - - ); - if (mobile) - return ; - return ( -
-
-
-

库存统计

-

地区 × 车型总览,展开城市核对库存

-
-
-
- {query || activeCount ? "筛选库存" : "总库存"} - -
- -
-
-
- -
- - inventoryColumn(String(value)), - }, - { key: "model", label: "原始型号" }, - { key: "quantity", label: "库存" }, - { key: "available", label: "可运营" }, - { key: "awaiting", label: "待运营" }, - { key: "abnormal", label: "异动" }, - { key: "pending", label: "待交车" }, - ]} - filename="库存统计" - sheetName="库存" - /> -
-
- {filterOpen && ( -
- row.region))} - change={(region) => - setFilters((old) => ({ ...old, region, city: "" })) - } - /> - !filters.region || row.region === filters.region, - ) - .map((row) => row.city), - )} - change={(city) => setFilters((old) => ({ ...old, city }))} - /> - row.brand))} - change={(brand) => setFilters((old) => ({ ...old, brand }))} - /> - -
- )} - {activeCount > 0 && ( -
- {Object.values(filters).filter(Boolean).join(" / ")} - -
- )} -
- {INVENTORY_TYPES.map((type) => { - const counts = inventoryTotals(cellRows(rows, type, "")); - return ( - - ); - })} -
-
- 地区合计与车型合计均为当前筛选范围 - 点击车型展开品牌 · 点击数字查看明细 -
- {!rows.length ? ( -
- 暂无匹配库存 -

尝试切换车型或调整筛选条件

- -
- ) : ( -
- - - - - - - {INVENTORY_TYPES.map((type) => ( - - ))} - - {hasBrandColumns && ( - - - {brands.map((brand) => ( - - ))} - - )} - - - {groups.map((group) => ( - - - - {renderCells( - group.cities.flatMap((city) => city.rows), - group.name, - )} - - {expanded.has(group.name) && - group.cities.map((city) => ( - - - {renderCells(city.rows, `${group.name} / ${city.name}`)} - - ))} - - ))} - - - - - {renderCells(rows, "当前库存合计")} - - -
- 区域展开城市,车型展开品牌,点击库存数量查看构成与车辆明细 -
- 区域 / 城市 - - 总库存 - - -
小计 - {brand} -
- -
- {city.name} -
- 当前范围合计 -
-
- )} -
- - {groups.length} 个区域 ·{" "} - {groups.reduce((sum, group) => sum + group.cities.length, 0)} 个城市 ·{" "} - {total.quantity} 台 - - 异动、待交车已包含在库存中 ·「其他」包含挂车及未单列车型 -
-
- ); -} diff --git a/src/modules/assets/library/VehicleDetails.tsx b/src/modules/assets/library/VehicleDetails.tsx deleted file mode 100644 index 15f81de..0000000 --- a/src/modules/assets/library/VehicleDetails.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import { inventoryColor } from "../../../shared/assets/inventory"; -import { matchesInventorySelection, inventoryModel } from "./inventory-view"; -import { ArrowLeft, X } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { useEffect, useRef, useState } from "react"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogDescription, -} from "@/components/ui/dialog"; -import { Skeleton } from "@/components/ui/skeleton"; -import { - fetchVehicleList, - fetchWeeklyDetail, - type WeeklyDetailItem, -} from "../api"; -import { buildVehicleModalRequest, type VehicleModalSelection } from "../model"; -import type { VehicleListItem } from "../types"; -import { DataGrid, type Column } from "./DataGrid"; -import { ExportButton } from "./export"; -import { Failure, FilterFields, PrivateText, unique } from "./shared"; -const titles = { - Inventory: "库存车辆", - Operating: "运营车辆", - Abnormal: "异动车辆", - Pending: "待交车车辆", - Delivered: "本周交车", - Returned: "本周还车", - Replaced: "本周替换交车", -}; -export function VehicleDetails({ - selection, - subject, - close, - returnLabel = "返回统计", -}: { - selection: VehicleModalSelection | null; - subject: string | null; - close: () => void; - returnLabel?: string; -}) { - const returnFocus = useRef(null); - const [vehicles, setVehicles] = useState([]); - const [weekly, setWeekly] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); - const [retry, setRetry] = useState(0); - const [filters, setFilters] = useState({ - model: "", - brandLabel: "", - location: "", - }); - useEffect(() => { - let active = true; - setFilters({ model: "", brandLabel: "", location: "" }); - setVehicles([]); - setWeekly([]); - setError(""); - if (!selection) return; - setLoading(true); - const request = buildVehicleModalRequest(selection, subject); - const run = async () => { - try { - if (request.kind === "weekly") { - const rows = await fetchWeeklyDetail(request.type, { - ...request.filters, - subject, - }); - if (active) setWeekly(rows); - } else { - const rows = await fetchVehicleList(request.params); - if (active) - setVehicles( - rows.filter((row) => matchesInventorySelection(row, selection)), - ); - } - } catch (e) { - if (active) setError(e instanceof Error ? e.message : "加载失败"); - } finally { - if (active) setLoading(false); - } - }; - void run(); - return () => { - active = false; - }; - }, [selection, subject, retry]); - const rows = vehicles - .filter((r) => - Object.entries(filters).every( - ([k, v]) => !v || r[k as keyof VehicleListItem] === v, - ), - ) - .map((r) => ({ - ...r, - color: inventoryColor(r.color), - displayModel: inventoryModel(r.model, r.brandLabel || "").name, - operator: inventoryModel(r.model, r.brandLabel || "").operator || "—", - pendingContract: r.pendingDelivery?.contractNo || "—", - pendingCustomer: r.pendingDelivery?.customerName || "—", - moveType: r.abnormalMove?.type || "—", - work: r.abnormalMove?.work || "—", - target: r.abnormalMove?.targetCompletionTime || "—", - actual: r.abnormalMove?.actualCompletionTime || "—", - initiating: r.abnormalMove?.initiatingDepartment || "—", - executing: r.abnormalMove?.executingDepartment || "—", - responsible: r.abnormalMove?.responsiblePerson || "—", - driver: r.abnormalMove?.driver || "—", - })); - type Row = (typeof rows)[number]; - const columns: Column[] = [ - { key: "plateNumber", label: "车牌", private: true, width: 140 }, - { key: "brandLabel", label: "品牌" }, - { key: "type", label: "车型" }, - { key: "displayModel", label: "型号", width: 160 }, - { key: "operator", label: "所属公司" }, - { key: "color", label: "颜色" }, - { key: "location", label: "所在地" }, - ]; - if (selection?.category === "Pending") - columns.push( - { - key: "pendingContract", - primary: true, - label: "待交车合同", - width: 180, - }, - { - key: "pendingCustomer", - primary: true, - label: "客户(已脱敏)", - width: 180, - private: true, - }, - ); - if (selection?.category === "Abnormal") - columns.push( - ...([ - { key: "moveType", primary: true, label: "异动类型" }, - { key: "target", primary: true, label: "预计结束时间", width: 180 }, - { key: "work", label: "具体工作", width: 220 }, - { key: "initiating", label: "发起部门" }, - { key: "executing", label: "执行部门" }, - { key: "responsible", label: "负责人", private: true }, - { key: "driver", label: "操作司机", private: true }, - { key: "actual", label: "实际完成时间", width: 180 }, - ] as Column[]), - ); - columns.push( - { - key: "operationStatus", - label: "运营状态", - primary: true, - format: (value) => - ({ "1": "租赁", "2": "自营", "3": "可运营", "4": "待运营" })[ - String(value) - ] || String(value || "未标注"), - }, - { - key: "status", - label: "业务状态", - primary: true, - format: (value) => - ({ - Inventory: "库存", - Operating: "运营", - Abnormal: "异动", - Pending: "待交车", - })[String(value)] || String(value), - }, - { key: "departmentName", label: "部门" }, - { key: "customerManager", label: "客户经理", private: true }, - { key: "vin", label: "车架号", private: true, width: 200 }, - ); - const isWeekly = - selection && - ["Delivered", "Returned", "Replaced"].includes(selection.category || ""); - const weeklyColumns: Column[] = [ - { key: "plate_number", label: "车牌", private: true }, - { key: "handover_date", label: "签章日期", width: 180 }, - { key: "contract_type", label: "合同类型" }, - { key: "customer_name", label: "客户", private: true }, - ]; - const title = - selection?.title || - (selection?.category ? titles[selection.category] : "车辆明细"); - const context = [ - ["城市 / 区域", selection?.location], - ["品牌", selection?.brand], - ["车型", selection?.type || selection?.vehicleType], - ["型号", selection?.model], - ["批次", selection?.batch], - ["部门", selection?.department], - ].filter(([, value]) => value && value !== "All"); - return ( - !v && close()}> - { - returnFocus.current = - document.activeElement instanceof HTMLElement - ? document.activeElement - : null; - }} - onCloseAutoFocus={(event) => { - if (returnFocus.current?.isConnected) { - event.preventDefault(); - returnFocus.current.focus({ preventScroll: true }); - } - }} - > - -
- -
-

- 资产管理 /{" "} - {selection?.source === "department" - ? "部门运营" - : selection?.source === "region" - ? "区域分布" - : selection?.source === "customer" - ? "客户资产" - : "资产统计"} -

- - {title} - -
- -
- - {subject || "全部公司"} ·{" "} - {loading - ? "正在加载车辆…" - : `${isWeekly ? weekly.length : vehicles.length} 条记录`} - - {selection?.scopeLabel && ( -

{selection.scopeLabel}

- )} - {context.length > 0 && ( -
- {context.map(([label, value]) => ( - - {label}:{value} - - ))} -
- )} - {selection?.customer && ( -

- 客户:{selection.customer} -

- )} - {selection?.manager && ( -

- 负责人:{selection.manager} -

- )} -
-
- {error ? ( - setRetry((n) => n + 1)} /> - ) : loading ? ( - - ) : isWeekly ? ( - <> - - - - ) : ( - <> - ({ - label: ["型号", "品牌", "所在地"][i], - value: filters[key], - values: unique(vehicles.map((r) => r[key])), - change: (value) => - setFilters((f) => ({ ...f, [key]: value })), - }), - )} - onReset={() => - setFilters({ model: "", brandLabel: "", location: "" }) - } - /> - - - - )} -
-
- 关闭后返回原来的筛选与浏览位置 - -
-
-
- ); -} diff --git a/src/modules/assets/library/asset-ui.css b/src/modules/assets/library/asset-ui.css deleted file mode 100644 index ccd252d..0000000 --- a/src/modules/assets/library/asset-ui.css +++ /dev/null @@ -1,1040 +0,0 @@ -/* Asset BI only: shared by the page and portalled drill-down panels. */ -.asset-ui { - --font-sans: - "Geist Variable", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", - system-ui, sans-serif; - --font-heading: var(--font-sans); - --radius: 0.625rem; - --blue: #2563eb; - --label: #172b4d; - --label-secondary: #5e6e85; - --bg-secondary: #f3f6fa; - --bg-tertiary: #fff; - --background: #fff; - --foreground: #172b4d; - --card-foreground: #172b4d; - --muted: #edf2f7; - --muted-foreground: #5e6e85; - --primary: #2563eb; - --border: #dde5ef; - --input: #cbd5e1; - --separator: #dde5ef; - --asset-soft: #eff5ff; - --asset-hover: #e7efff; - --shadow-1: 0 1px 2px rgb(23 43 77 / 3%); - --shadow-2: var(--shadow-1); - --shadow-3: 0 2px 8px rgb(23 43 77 / 6%); - font-family: var(--font-sans); - font-size: 14px; - line-height: 1.5; - font-variant-numeric: tabular-nums; - -webkit-font-smoothing: antialiased; - color: var(--label); -} -[data-theme="dark"] .asset-ui { - --label: #e2eaf5; - --label-secondary: #a4b3c8; - --bg-secondary: #111b2b; - --bg-tertiary: #19263a; - --background: #19263a; - --foreground: #e2eaf5; - --card-foreground: #e2eaf5; - --muted: #23324a; - --muted-foreground: #a4b3c8; - --primary: #8bb6ff; - --blue: #8bb6ff; - --border: #34445c; - --input: #52647e; - --separator: #34445c; - --asset-soft: #1d3050; - --asset-hover: #253c60; -} -.asset-ui [data-slot="card"] { - border: 1px solid var(--border); - border-radius: 12px; - --card-spacing: 12px; -} -.asset-ui [data-slot="card-title"] { - letter-spacing: 0; -} -.asset-ui [data-slot="card-description"] { - line-height: 1.6; -} -.asset-ui [data-slot="button"], -.asset-ui [data-slot="select-trigger"], -.asset-ui [data-slot="input"] { - border-radius: 8px; -} -.asset-ui button:not(:disabled), -.asset-ui [role="tab"] { - cursor: pointer; -} -.asset-ui button:active { - translate: none; -} -.asset-ui :focus-visible { - outline: 2px solid var(--blue); - outline-offset: 3px; -} -.asset-ui [data-slot="tabs-list"] { - height: auto; - min-height: 36px; -} -.asset-ui [data-slot="tabs-trigger"] { - min-height: 36px; - padding: 6px 12px; - color: var(--label-secondary); -} -.asset-ui [data-slot="tabs-trigger"][data-state="active"] { - background: var(--bg-tertiary); - color: var(--blue); - box-shadow: 0 1px 4px rgb(23 43 77 / 8%); -} -.asset-ui .asset-main-tabs { - background: transparent; - border-bottom: 1px solid var(--border); - border-radius: 0; - width: 100%; - justify-content: flex-start; - gap: 24px; - padding: 0; - margin-bottom: 4px; -} -.asset-ui .asset-main-tabs [role="tab"] { - flex: none; - border-radius: 0; - min-height: 36px; - padding-inline: 4px; - border-bottom: 2px solid transparent; -} -.asset-ui .asset-main-tabs [role="tab"][data-state="active"] { - box-shadow: none; - background: transparent; - border-bottom-color: var(--blue); -} -.asset-ui .asset-page-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; -} -.asset-ui .asset-title-icon { - color: var(--blue); - background: var(--asset-soft); - border: 1px solid var(--border); - border-radius: 10px; - padding: 8px; -} -.asset-ui .asset-total { - display: flex; - align-items: center; - gap: 12px; - padding-left: 16px; - border-left: 1px solid var(--border); -} -.asset-ui .asset-total button { - font-size: 26px; - font-weight: 650; - color: var(--label); - min-height: 44px; -} -.asset-ui .asset-toolbar { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 12px; - border-bottom: 1px solid var(--border); - padding-bottom: 8px; -} -.asset-ui .asset-toolbar [data-slot="field"] { - display: flex; - flex-direction: row; - align-items: center; - gap: 12px; -} -.asset-ui .asset-toolbar [data-slot="field-label"] { - white-space: nowrap; - color: var(--label-secondary); -} -.asset-ui .asset-toolbar [data-slot="select-trigger"] { - min-width: 180px; -} -.asset-ui .asset-kpi { - background: var(--bg-tertiary); - border-top: 3px solid var(--blue); -} -.asset-ui .asset-kpi.inventory { - border-top-color: #64748b; -} -.asset-ui .asset-kpi [data-slot="card-header"] { - min-height: 40px; -} -.asset-ui .asset-kpi [data-slot="card-content"] { - gap: 8px; -} -.asset-ui [data-slot="card-content"].grid { - row-gap: 12px; -} -@media (min-width: 768px) { - .asset-ui .asset-kpi { - row-gap: 8px; - } - .asset-ui .asset-kpi-number { - min-height: 36px; - } - .asset-ui .asset-toolbar [data-slot="button"], - .asset-ui .asset-toolbar [data-slot="select-trigger"] { - min-height: 36px; - height: 36px; - } - .asset-ui .asset-toolbar [data-size="icon"] { - min-width: 36px; - width: 36px; - } -} -@media (min-width: 1100px) { - .asset-ui .asset-kpi { - display: grid; - grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); - align-items: center; - column-gap: 0; - } - .asset-ui .asset-kpi [data-slot="card-content"] { - padding-left: 8px; - } -} -.asset-ui .asset-kpi [data-slot="card-title"] svg { - width: 18px; - height: 18px; - color: var(--label-secondary); -} -.asset-ui .asset-kpi-number { - min-height: 44px; - padding: 0; - gap: 8px; - font-size: 28px; - line-height: 1.1; - font-weight: 650; - letter-spacing: -1px; - color: var(--blue); -} -.asset-ui .asset-kpi-number .asset-kpi-unit { - font-size: 13px; - font-weight: 400; - letter-spacing: 0; - color: var(--label-secondary); -} -.asset-ui .asset-kpi-number svg { - width: 18px; - height: 18px; - margin-left: 6px; - opacity: 0.65; -} -.asset-ui .asset-kpi-number:hover { - background: transparent; - text-decoration: underline; - text-underline-offset: 6px; -} -.asset-ui .asset-kpi.inventory .asset-kpi-number { - color: var(--label); -} -.asset-ui .asset-group { - border: 1px solid var(--border); - border-radius: 10px; - margin-top: 10px; - overflow: hidden; -} -.asset-ui .asset-group > h3 { - background: var(--bg-secondary); -} -.asset-ui .asset-group > h3 button { - padding: 8px 12px; - min-height: 40px; - text-decoration: none; -} -.asset-ui .asset-group > [role="region"] > div { - padding: 12px; -} -.asset-ui .asset-group-name { - font-size: 14px; - font-weight: 600; -} -.asset-ui .asset-count { - display: inline-flex; - align-items: center; - justify-content: flex-end; - gap: 5px; - min-height: 32px; - min-width: 44px; - padding: 0 6px; - color: var(--blue); - font-size: 14px; - font-weight: 600; - border-radius: 5px; -} -.asset-ui .asset-count:hover { - background: var(--asset-hover); - text-decoration: none; -} -.asset-ui .asset-count svg { - width: 12px; - height: 12px; - opacity: 0.65; -} -.asset-ui .asset-zero { - color: var(--label-secondary); - display: inline-block; - padding-right: 23px; - font-weight: 400; -} -.asset-ui .ag-numeric-header .ag-header-cell-label { - justify-content: flex-end; -} -.asset-ui .ag-cell.ag-cell-last-left-pinned { - font-weight: 550; -} -.asset-ui .asset-grid-toolbar { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.asset-ui .asset-grid-search { - position: relative; - width: min(100%, 280px); -} -.asset-ui .asset-grid-search input { - height: 38px; - padding-left: 34px; - padding-right: 36px; - background: var(--bg-tertiary); -} -.asset-ui .asset-grid-search > svg { - position: absolute; - left: 10px; - top: 11px; - width: 16px; - height: 16px; - color: var(--label-secondary); -} -.asset-ui .asset-grid-search button { - position: absolute; - right: 0; - top: 0; - height: 38px; - width: 36px; -} -.asset-ui .asset-record { - background: var(--bg-tertiary); - border: 1px solid var(--border); - border-radius: 10px; - overflow: hidden; - box-shadow: var(--shadow-1); -} -.asset-ui .asset-record-head { - padding: 10px 12px; - display: flex; - align-items: start; - justify-content: space-between; - gap: 12px; - border-bottom: 1px solid var(--border); -} -.asset-ui .asset-record-head h4 { - font-size: 15px; - font-weight: 600; - overflow-wrap: anywhere; -} -.asset-ui .asset-record-fields { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 8px 12px; - padding: 10px 12px; -} -.asset-ui .asset-record-fields dt { - font-size: 12px; - color: var(--label-secondary); - margin-bottom: 4px; -} -.asset-ui .asset-record-fields dd { - font-size: 14px; - overflow-wrap: anywhere; -} -.asset-ui .asset-record-metrics { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 1px; - background: var(--border); - border-top: 1px solid var(--border); -} -.asset-ui .asset-metric-cell { - min-width: 0; - background: var(--asset-soft); - padding: 10px 12px; -} -.asset-ui .asset-metric-cell dt { - font-size: 12px; - color: var(--label-secondary); -} -.asset-ui button.asset-metric-cell { - display: flex; - flex-direction: column; - align-items: start; - text-align: left; - min-height: 60px; - gap: 4px; -} -.asset-ui button.asset-metric-cell:hover { - background: var(--asset-hover); -} -.asset-ui .asset-metric-value { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - font-size: 20px; - font-weight: 600; - color: var(--blue); -} -.asset-ui .asset-metric-value svg { - width: 14px; - height: 14px; -} -.asset-ui .asset-metric-cell.asset-metric-empty { - background: var(--bg-secondary); -} -.asset-ui .asset-metric-empty .asset-metric-value { - color: var(--label-secondary); - font-weight: 400; -} -.asset-ui.asset-detail { - display: flex; - flex-direction: column; - gap: 0; - padding: 0; - width: calc(100% - 64px); - max-width: 1280px; - max-height: 90dvh; - overflow: hidden; - border: 1px solid var(--border); - background: var(--bg-tertiary); - color: var(--label); - border-radius: 14px; -} -.asset-ui .asset-detail-header { - flex-shrink: 0; - padding: 12px 16px; - border-bottom: 1px solid var(--border); - background: var(--bg-tertiary); -} -.asset-ui .asset-detail-body { - overflow: auto; - overscroll-behavior: contain; - min-height: 0; - padding: 12px 16px; - display: grid; - gap: 16px; -} -.asset-ui .asset-detail-footer { - border-top: 1px solid var(--border); - padding: 8px 16px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - color: var(--label-secondary); - font-size: 12px; -} -.asset-ui .asset-context { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 12px; -} -.asset-ui .asset-context span { - font-size: 12px; - padding: 3px 8px; - border: 1px solid var(--border); - background: var(--bg-secondary); - border-radius: 5px; - overflow-wrap: anywhere; -} -@media (max-width: 767px) { - .asset-ui { - font-size: 14px; - } - .asset-ui [data-slot="card"] { - --card-spacing: 12px; - border-radius: 10px; - } - .asset-ui .asset-page-header { - gap: 12px; - } - .asset-ui .asset-title-icon { - display: none; - } - .asset-ui .asset-total { - display: block; - padding-left: 12px; - text-align: right; - } - .asset-ui .asset-total button { - font-size: 26px; - padding-right: 0; - } - .asset-ui .asset-toolbar { - gap: 12px; - padding-bottom: 12px; - } - .asset-ui .asset-toolbar [data-slot="field"] { - flex-direction: column; - align-items: start; - gap: 6px; - } - .asset-ui .asset-toolbar [data-slot="select-trigger"] { - min-width: 0; - width: 100%; - } - .asset-ui .asset-main-tabs { - gap: 0; - margin-bottom: 8px; - } - .asset-ui .asset-main-tabs [role="tab"] { - min-height: 44px; - flex: 1; - } - .asset-ui [data-slot="tabs-trigger"] { - padding-inline: 10px; - min-height: 44px; - } - .asset-ui [data-slot="button"]:not(.asset-kpi-number), - .asset-ui [data-slot="select-trigger"] { - min-height: 44px; - } - .asset-ui [data-slot="button"][data-size^="icon"] { - min-width: 44px; - } - .asset-ui .asset-kpi [data-slot="card-header"] { - min-height: 0; - } - .asset-ui .asset-kpi-number { - font-size: 28px; - } - .asset-ui .asset-kpi [data-slot="card-action"] { - align-self: center; - } - .asset-ui .asset-group > h3 button { - padding: 12px; - } - .asset-ui .asset-group > [role="region"] > div { - padding: 12px; - } - .asset-ui .asset-grid-search { - width: 100%; - } - .asset-ui .asset-grid-search input { - height: 44px; - font-size: 16px; - } - .asset-ui .asset-grid-search > svg { - top: 14px; - } - .asset-ui .asset-grid-search button { - height: 44px; - width: 44px; - } - .asset-ui.asset-detail { - top: 0; - left: 0; - transform: none; - translate: none; - width: 100%; - max-width: 100%; - height: 100dvh; - max-height: 100dvh; - border: 0; - border-radius: 0; - } - .asset-ui .asset-detail-header { - padding: max(12px, env(safe-area-inset-top)) 16px 12px; - } - .asset-ui .asset-detail-body { - padding: 16px; - flex: 1; - align-content: start; - background: var(--bg-secondary); - } - .asset-ui .asset-detail-footer { - padding: 10px 16px max(10px, env(safe-area-inset-bottom)); - } -} -@media (prefers-reduced-motion: reduce) { - .asset-ui *, - .asset-ui *::before, - .asset-ui *::after { - animation-duration: 0.01ms !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} -.asset-ui .inventory-panel { - min-width: 0; - background: var(--bg-tertiary); - border: 1px solid var(--border); - border-radius: 12px; - overflow: hidden; - box-shadow: var(--shadow-1); -} -.asset-ui .inventory-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 12px 16px; - border-bottom: 1px solid var(--border); -} -.asset-ui .inventory-heading-title { - position: relative; - padding-left: 16px; -} -.asset-ui .inventory-heading-title::before { - content: ""; - position: absolute; - left: 0; - top: 3px; - width: 4px; - height: 36px; - border-radius: 3px; - background: var(--blue); -} -.asset-ui .inventory-heading h2 { - font-size: 18px; - font-weight: 650; -} -.asset-ui .inventory-heading p { - font-size: 12px; - color: var(--label-secondary); - margin-top: 4px; -} -.asset-ui .inventory-total { - display: grid; - text-align: right; - gap: 0; - white-space: nowrap; -} -.asset-ui .inventory-total > span { - font-size: 12px; - color: var(--label-secondary); -} -.asset-ui .inventory-total strong { - font-size: 28px; - line-height: 1.2; - font-weight: 650; - letter-spacing: -0.5px; -} -.asset-ui .inventory-total small { - margin-left: 6px; - font-size: 12px; - font-weight: 400; - color: var(--label-secondary); -} -.asset-ui .inventory-filter-count { - background: var(--blue); - color: var(--background); - min-width: 20px; - border-radius: 4px; - font-size: 12px; -} -.asset-ui .inventory-filters { - padding: 16px 24px; - background: var(--bg-secondary); - border-bottom: 1px solid var(--border); -} -.asset-ui .inventory-filter-chips { - display: flex; - flex-wrap: wrap; - gap: 8px; - padding: 12px 24px 0; -} -.asset-ui .inventory-filter-chips button { - white-space: normal; - height: auto; - text-align: left; -} -.asset-ui .inventory-toolbar { - display: flex; - align-items: center; - gap: 12px; - padding: 16px 24px 12px; -} -.asset-ui .inventory-search { - position: relative; - margin-left: auto; - width: 260px; -} -.asset-ui .inventory-search input { - height: 40px; - padding-left: 34px; - padding-right: 36px; - background: var(--bg-tertiary); -} -.asset-ui .inventory-search > svg { - width: 16px; - height: 16px; - position: absolute; - left: 10px; - top: 12px; - color: var(--label-secondary); -} -.asset-ui .inventory-search button { - position: absolute; - right: 0; - top: 0; - height: 40px; - width: 36px; -} -.asset-ui .inventory-summary { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - gap: 8px; - padding: 0 24px 12px; - font-size: 12px; - color: var(--label-secondary); -} -.asset-ui .inventory-table-scroll { - overflow: auto; - max-height: 660px; - scrollbar-gutter: stable; -} -.asset-ui .inventory-table { - width: 100%; - border-collapse: separate; - border-spacing: 0; - table-layout: fixed; - font-size: 14px; -} -.asset-ui .inventory-table thead { - position: sticky; - top: 0; - z-index: 2; -} -.asset-ui .inventory-table thead th { - background: var(--bg-secondary); - color: var(--label-secondary); - font-size: 12px; - font-weight: 600; - padding: 12px 20px; - text-align: left; - border-block: 1px solid var(--border); -} -.asset-ui .inventory-table td, -.asset-ui .inventory-table th.inventory-model-cell { - padding: 10px 20px; - border-bottom: 1px solid var(--border); - overflow-wrap: anywhere; -} -.asset-ui .inventory-table tr:hover td, -.asset-ui .inventory-table tr:hover th.inventory-model-cell { - background: var(--asset-soft); -} -.asset-ui .inventory-table .inventory-numeric { - text-align: right; -} -.asset-ui .inventory-numeric > button:not(.asset-count) { - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 24px; -} -.asset-ui .inventory-numeric svg { - width: 14px; - height: 14px; -} -.asset-ui .inventory-group-row th { - padding: 0; - border-bottom: 1px solid var(--border); -} -.asset-ui .inventory-group-toggle { - width: 100%; - display: flex; - align-items: center; - gap: 10px; - min-height: 48px; - text-align: left; - padding: 10px 20px; - background: var(--bg-secondary); - font-size: 14px; -} -.asset-ui .inventory-group-toggle > svg { - width: 16px; - height: 16px; - color: var(--label-secondary); - transition: transform 160ms ease; -} -.asset-ui .inventory-group-toggle:hover { - background: var(--asset-hover); -} -.asset-ui .inventory-group-meta { - font-size: 12px; - color: var(--label-secondary); - font-weight: 400; -} -.asset-ui .inventory-group-total { - margin-left: auto; - font-size: 12px; - font-weight: 400; - color: var(--label-secondary); -} -.asset-ui .inventory-group-total b { - font-size: 15px; - font-weight: 600; - color: var(--label); -} -.asset-ui .inventory-table .inventory-city-cell { - vertical-align: top; - text-align: left; - padding: 16px 20px 16px 46px; - border-right: 1px solid var(--border); - border-bottom: 1px solid var(--border); -} -.asset-ui .inventory-city-cell strong { - font-size: 14px; - font-weight: 600; -} -.asset-ui .inventory-city-cell span { - display: block; - margin-top: 4px; - font-size: 12px; - font-weight: 400; - color: var(--label-secondary); -} -.asset-ui .inventory-model-cell { - text-align: left; -} -.asset-ui .inventory-model-cell strong { - font-weight: 500; -} -.asset-ui .inventory-model-cell span { - margin-left: 10px; - padding: 2px 6px; - background: var(--bg-secondary); - border-radius: 4px; - color: var(--label-secondary); - font-size: 12px; - font-weight: 400; - white-space: nowrap; -} -.asset-ui .inventory-footer { - display: flex; - justify-content: space-between; - gap: 12px; - padding: 8px 16px; - font-size: 12px; - line-height: 1.6; - color: var(--label-secondary); - border-top: 1px solid var(--border); -} -.asset-ui .inventory-empty { - display: grid; - justify-items: center; - gap: 12px; - padding: 48px 16px; - text-align: center; - color: var(--label-secondary); -} -.asset-ui .inventory-empty > svg { - width: 32px; - height: 32px; -} -.asset-ui .inventory-empty strong { - font-size: 16px; - color: var(--label); -} -.asset-ui .inventory-mobile { - background: var(--bg-secondary); - padding: 0 12px 12px; -} -.asset-ui .inventory-mobile-sort { - width: 100%; - justify-content: flex-end; - color: var(--label-secondary); - font-size: 12px; -} -.asset-ui .inventory-mobile-group { - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - margin-bottom: 4px; - background: var(--bg-tertiary); -} -.asset-ui .inventory-city { - padding: 12px; - border-top: 1px solid var(--border); -} -.asset-ui .inventory-city-title { - display: flex; - align-items: center; - gap: 6px; - font-size: 13px; - margin-bottom: 10px; -} -.asset-ui .inventory-city-title svg { - width: 14px; - height: 14px; - color: var(--label-secondary); -} -.asset-ui .inventory-city-title > span { - margin-left: auto; - font-size: 12px; - color: var(--label-secondary); -} -.asset-ui .inventory-mobile-record { - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - margin-top: 8px; -} -.asset-ui .inventory-record-main { - display: flex; - align-items: center; - gap: 12px; - padding: 12px; -} -.asset-ui .inventory-record-main h3 { - font-size: 14px; - font-weight: 600; - overflow-wrap: anywhere; - line-height: 1.5; -} -.asset-ui .inventory-record-main p { - color: var(--label-secondary); - font-size: 12px; - margin-top: 6px; - overflow-wrap: anywhere; -} -.asset-ui .inventory-record-main p > span { - margin-inline: 6px; -} -.asset-ui .inventory-mobile-quantity { - margin-left: auto; - display: grid; - grid-template-columns: auto auto; - align-items: baseline; - justify-content: center; - column-gap: 4px; - flex-shrink: 0; - min-width: 74px; - min-height: 64px; - padding: 6px 8px; - border-radius: 6px; - background: var(--asset-soft); - color: var(--blue); -} -.asset-ui .inventory-mobile-quantity strong { - font-size: 24px; - font-weight: 650; -} -.asset-ui .inventory-mobile-quantity > span { - display: flex; - gap: 3px; - font-size: 12px; -} -.asset-ui .inventory-mobile-quantity svg { - width: 12px; - height: 12px; -} -.asset-ui .inventory-mobile-quantity small { - grid-column: span 2; - font-size: 11px; -} -.asset-ui .inventory-mobile-quantity:hover { - background: var(--asset-hover); -} -.asset-ui .inventory-record-status { - border-top: 1px solid var(--border); - display: flex; - gap: 8px; - padding-inline: 8px; - background: var(--bg-secondary); -} -@media (max-width: 767px) { - .asset-ui .inventory-heading { - padding: 16px; - gap: 12px; - flex-wrap: wrap; - } - .asset-ui .inventory-heading-title { - width: 100%; - padding-left: 12px; - } - .asset-ui .inventory-heading-title h2 { - font-size: 17px; - } - .asset-ui .inventory-heading > div:last-child { - width: 100%; - justify-content: space-between; - } - .asset-ui .inventory-total { - display: flex; - align-items: baseline; - gap: 12px; - text-align: left; - } - .asset-ui .inventory-total strong { - font-size: 26px; - } - .asset-ui .inventory-toolbar { - padding: 12px 16px; - flex-wrap: wrap; - justify-content: space-between; - gap: 8px; - } - .asset-ui .inventory-toolbar [role="tab"] { - padding-inline: 12px; - } - .asset-ui .inventory-toolbar > div:last-child { - gap: 0; - } - .asset-ui .inventory-search { - order: 3; - margin: 0; - width: 100%; - } - .asset-ui .inventory-search input { - height: 44px; - font-size: 16px; - } - .asset-ui .inventory-search > svg { - top: 14px; - } - .asset-ui .inventory-search button { - height: 44px; - width: 44px; - } - .asset-ui .inventory-summary { - padding: 0 16px 12px; - } - .asset-ui .inventory-filters, - .asset-ui .inventory-filter-chips { - padding: 12px 16px; - } - .asset-ui .inventory-group-toggle { - padding: 12px; - } - .asset-ui .inventory-footer { - padding: 12px 16px; - } - .asset-ui .inventory-mobile-quantity small { - font-size: 12px; - } -} -.asset-ui .asset-toolbar [data-slot="field-label"] { - width: auto; - flex: none; -} -/* The global feedback launcher must not cover the modal's vehicle cards. */ -body:has(.asset-detail[data-state="open"]) button[aria-label="反馈"] { - visibility: hidden; -} diff --git a/src/modules/assets/library/export.tsx b/src/modules/assets/library/export.tsx deleted file mode 100644 index 4814afb..0000000 --- a/src/modules/assets/library/export.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Download } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import type { Column } from "./DataGrid"; - -function cellValue(row: T, col: Column): string | number { - const raw = row[col.key]; - if (col.format) return col.format(raw, row); - return (raw as string | number) ?? ""; -} - -/** 按表格列定义导出 xlsx;xlsx 按需加载,避免进入首屏包。 */ -export async function exportToXlsx( - rows: T[], - columns: Column[], - filename: string, - sheetName = "明细", -): Promise { - const XLSX = await import("xlsx"); - const sheet = XLSX.utils.json_to_sheet( - rows.map((row) => - Object.fromEntries(columns.map((col) => [col.label, cellValue(row, col)])), - ), - ); - const book = XLSX.utils.book_new(); - XLSX.utils.book_append_sheet(book, sheet, sheetName); - XLSX.writeFile(book, `${filename}.xlsx`); -} - -export function ExportButton({ - rows, - columns, - filename, - sheetName, - label = "导出", -}: { - rows: T[]; - columns: Column[]; - filename: string; - sheetName?: string; - label?: string; -}) { - return ( - - ); -} diff --git a/src/modules/assets/library/inventory-explorer.test.ts b/src/modules/assets/library/inventory-explorer.test.ts deleted file mode 100644 index de60b08..0000000 --- a/src/modules/assets/library/inventory-explorer.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; -import type { RegionalInventoryStats } from "../types"; -import { - inventoryBrowseRows, - inventoryBrowseUrl, - inventoryModels, - inventoryPlaces, - inventoryTypeRows, - readInventoryBrowse, -} from "./inventory-explorer"; -import { - INVENTORY_TYPES, - inventoryTotals, - scopedInventorySelection, -} from "./inventory-view"; -import { inventoryKey } from "../../../shared/assets/inventory"; - -const make = ( - changes: Partial = {}, -): RegionalInventoryStats => ({ - region: "华东", - city: "嘉兴市", - brand: "现代", - type: "4.5T冷链", - model: "冷链(公司甲)", - batch: "批次甲", - quantity: 5, - available: 3, - awaiting: 2, - pending: 1, - abnormal: 1, - ...changes, -}); - -test("region, city and type partitions reconcile without counting pending/abnormal twice", () => { - const rows = [ - make(), - make({ city: "杭州市", quantity: 3 }), - make({ region: "华南", city: "广州市", type: "49T", quantity: 7 }), - make({ type: "挂车", quantity: 2 }), - ]; - const total = inventoryTotals(rows).quantity; - assert.equal(total, 17); - assert.equal( - inventoryPlaces(rows, "region", "").reduce( - (n, place) => n + place.total.quantity, - 0, - ), - total, - ); - assert.equal( - INVENTORY_TYPES.reduce( - (n, type) => n + inventoryTotals(inventoryTypeRows(rows, type)).quantity, - 0, - ), - total, - ); - for (const region of inventoryPlaces(rows, "region", "")) { - assert.equal( - inventoryPlaces(region.rows, "city", "").reduce( - (n, city) => n + city.total.quantity, - 0, - ), - region.total.quantity, - ); - } -}); - -test("type selection keeps zero-stock locations visible and never uses other-type totals", () => { - const places = inventoryPlaces( - [ - make(), - make({ region: "华南", city: "广州市", type: "49T", quantity: 7 }), - ], - "region", - "49T", - ); - assert.deepEqual( - places.map((place) => [place.name, place.total.quantity]), - [ - ["华南", 7], - ["华东", 0], - ], - ); - assert.equal(places[1].rows.length, 0); -}); - -test("city drill-down intersects geography, brand, query and type before opening exact detail keys", () => { - const wanted = make(); - const rows = [ - wanted, - make({ region: "华南" }), - make({ city: "杭州市" }), - make({ brand: "另一品牌" }), - make({ model: "普货", type: "4.5T普货" }), - ]; - const state = { - ...readInventoryBrowse(""), - region: "华东", - city: "嘉兴市", - brand: "现代", - query: "冷链", - type: "4.5T冷链", - }; - const filtered = inventoryTypeRows( - inventoryBrowseRows(rows, state), - state.type, - ); - assert.deepEqual(filtered, [wanted]); - const model = inventoryModels(filtered)[0]; - assert.deepEqual( - scopedInventorySelection({ rows: model.rows, label: "测试范围" }) - .inventoryKeys, - [inventoryKey(wanted)], - ); -}); - -test("missing geography and duplicate city/model names retain exact identity", () => { - const rows = [ - make({ region: "", city: "" }), - make({ city: "" }), - make({ region: "华南", city: "" }), - ]; - assert.equal(inventoryPlaces(rows, "city", "").length, 3); - assert.deepEqual( - inventoryBrowseRows(rows, { - ...readInventoryBrowse(""), - region: "", - city: "", - }), - [rows[0]], - ); - const models = inventoryModels([ - make({ type: "挂车" }), - make({ type: "35T" }), - make({ brand: "另一品牌" }), - ]); - assert.equal(models.length, 3); - assert.equal(new Set(models.map((model) => model.key)).size, 3); -}); - -test("deep links round-trip filters, empty geography and model identity without changing the host route", () => { - const state = { - region: "", - city: "", - type: "其他", - model: '["现代","挂车","A/B"]', - query: "A/B & C", - brand: "现代", - }; - const url = new URL( - inventoryBrowseUrl( - "https://example.test/asset?existing=yes#assets/overview", - state, - ), - ); - assert.deepEqual(readInventoryBrowse(url.search), state); - assert.equal(url.hash, "#assets/overview"); - assert.equal(url.pathname, "/asset"); - assert.equal(url.searchParams.get("existing"), "yes"); - const cleared = new URL( - inventoryBrowseUrl(url.href, readInventoryBrowse("")), - ); - assert.equal(cleared.search, "?existing=yes"); -}); - -test("orphan child links and invalid type values cannot select an unrelated scope", () => { - assert.deepEqual( - readInventoryBrowse("?stockCity=嘉兴市&stockModel=x&stockType=invalid"), - readInventoryBrowse(""), - ); - const state = readInventoryBrowse("?stockRegion=华东&stockModel=x"); - assert.equal(state.model, null); - assert.equal(inventoryModels([]).length, 0); - assert.equal( - inventoryBrowseRows([make()], { ...state, query: "no matching model" }) - .length, - 0, - ); -}); diff --git a/src/modules/assets/library/inventory-explorer.ts b/src/modules/assets/library/inventory-explorer.ts deleted file mode 100644 index 1fafcb5..0000000 --- a/src/modules/assets/library/inventory-explorer.ts +++ /dev/null @@ -1,150 +0,0 @@ -import type { RegionalInventoryStats } from "../types"; -import { - INVENTORY_TYPES, - inventoryColumn, - inventoryTotals, -} from "./inventory-view"; - -export interface InventoryBrowseState { - region: string | null; - city: string | null; - type: string; - model: string | null; - query: string; - brand: string; -} - -export function readInventoryBrowse(search: string): InventoryBrowseState { - const params = new URLSearchParams(search); - const region = params.get("stockRegion"); - const city = region === null ? null : params.get("stockCity"); - const type = params.get("stockType") || ""; - return { - region, - city, - type: INVENTORY_TYPES.includes(type as (typeof INVENTORY_TYPES)[number]) - ? type - : "", - model: city === null ? null : params.get("stockModel"), - query: params.get("stockQuery") || "", - brand: params.get("stockBrand") || "", - }; -} - -/** Keep the application's route and unrelated URL parameters intact. */ -export function inventoryBrowseUrl(href: string, state: InventoryBrowseState) { - const url = new URL(href); - for (const [key, value] of Object.entries({ - stockRegion: state.region, - stockCity: state.region === null ? null : state.city, - stockType: state.type || null, - stockModel: - state.region === null || state.city === null ? null : state.model, - stockQuery: state.query || null, - stockBrand: state.brand || null, - })) { - if (value === null) url.searchParams.delete(key); - else url.searchParams.set(key, value); - } - return url.href; -} - -export function inventoryBrowseRows( - rows: RegionalInventoryStats[], - state: InventoryBrowseState, -) { - const query = state.query.trim().toLocaleLowerCase(); - return rows.filter( - (row) => - (state.region === null || row.region === state.region) && - (state.city === null || row.city === state.city) && - (!state.brand || row.brand === state.brand) && - (!query || - [ - row.region, - row.city, - row.brand, - row.type, - row.model, - inventoryColumn(row.type), - ] - .join(" ") - .toLocaleLowerCase() - .includes(query)), - ); -} - -export function inventoryTypeRows( - rows: RegionalInventoryStats[], - type: string, -) { - return type ? rows.filter((row) => inventoryColumn(row.type) === type) : rows; -} - -/** Build places before applying the type filter so no-stock places remain explicit. */ -export function inventoryPlaces( - rows: RegionalInventoryStats[], - level: "region" | "city", - type: string, -) { - const groups = new Map(); - for (const row of rows) { - const key = JSON.stringify( - level === "region" ? [row.region] : [row.region, row.city], - ); - const group = groups.get(key) || []; - group.push(row); - groups.set(key, group); - } - return [...groups] - .map(([key, records]) => { - const selected = inventoryTypeRows(records, type); - return { - key, - value: records[0][level], - name: - records[0][level] || - (level === "region" ? "未归属地区" : "未归属城市"), - rows: selected, - total: inventoryTotals(selected), - cities: new Set( - selected.filter((row) => row.quantity > 0).map((row) => row.city), - ).size, - types: INVENTORY_TYPES.map((name) => ({ - name, - quantity: inventoryTotals(inventoryTypeRows(records, name)).quantity, - })).filter((item) => item.quantity > 0), - }; - }) - .sort( - (a, b) => - b.total.quantity - a.total.quantity || - a.name.localeCompare(b.name, "zh-CN"), - ); -} - -/** Keep the exact type/model/brand identity even when displayed in the Other column. */ -export function inventoryModels(rows: RegionalInventoryStats[]) { - const groups = new Map(); - for (const row of rows) { - const key = JSON.stringify([row.brand, row.type, row.model]); - const group = groups.get(key) || []; - group.push(row); - groups.set(key, group); - } - return [...groups] - .map(([key, records]) => ({ - key, - rows: records, - brand: records[0].brand, - model: records[0].model, - type: records[0].type, - total: inventoryTotals(records), - })) - .filter((group) => group.total.quantity > 0) - .sort( - (a, b) => - b.total.quantity - a.total.quantity || - a.model.localeCompare(b.model, "zh-CN"), - ); -} diff --git a/src/modules/assets/library/inventory-matrix.css b/src/modules/assets/library/inventory-matrix.css deleted file mode 100644 index 4f04fb7..0000000 --- a/src/modules/assets/library/inventory-matrix.css +++ /dev/null @@ -1,1173 +0,0 @@ -/* Inventory surfaces use the app's vendored Apple tokens; data surfaces stay opaque. */ -.asset-ui { - --inventory-awaiting: #74829b; - --inventory-warning: #a85512; - font-family: - -apple-system, BlinkMacSystemFont, "Geist Variable", "PingFang SC", - "Microsoft YaHei", system-ui, sans-serif; - font-optical-sizing: auto; -} -[data-theme="dark"] .asset-ui { - --inventory-awaiting: #9aa9c0; - --inventory-warning: #fdba74; -} -.inventory-matrix { - overflow: hidden; - min-width: 0; - background: var(--bg-tertiary); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: var(--shadow-1); -} -.matrix-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 12px 16px; -} -.matrix-heading h2 { - font-size: 1rem; - font-weight: 600; - letter-spacing: var(--tracking-snug); - line-height: 1.25; -} -.matrix-heading p, -.matrix-header-total > span, -.analysis-eyebrow { - font-size: 0.8125rem; - color: var(--label-secondary); - margin-top: 5px; -} -.matrix-heading-actions { - display: flex; - align-items: center; - gap: 16px; -} -.matrix-header-total { - text-align: right; -} -.matrix-header-total .matrix-value { - font-size: 1.5rem; - letter-spacing: var(--tracking-tight); - font-weight: 650; -} -.matrix-header-total .matrix-number { - align-items: center; - flex-direction: row; - gap: 12px; -} -.matrix-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 0 16px 10px; -} -.matrix-search { - position: relative; - display: flex; - align-items: center; - width: 360px; - max-width: 100%; -} -.matrix-search > svg { - position: absolute; - left: 12px; - width: 16px; - color: var(--label-secondary); -} -.matrix-search input { - padding-left: 36px; - padding-right: 40px; - min-height: 44px; - background: var(--bg-secondary); - border-color: transparent; -} -.matrix-search > button { - position: absolute; - right: 0; - display: grid; - place-items: center; - min-width: 44px; - min-height: 44px; -} -.matrix-search > button svg { - width: 16px; -} -.matrix-tools { - display: flex; - align-items: center; - gap: 8px; -} -.matrix-filters { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)) auto; - align-items: end; - gap: 12px; - background: var(--bg-secondary); - padding: 12px 16px; - border-top: 1px solid var(--border); -} -.matrix-filter-summary { - display: flex; - gap: 16px; - align-items: center; - flex-wrap: wrap; - padding: 8px 24px; - background: var(--asset-soft); - font-size: 0.8125rem; -} -.matrix-filter-summary button { - color: var(--blue); - min-height: 44px; -} -.matrix-scroll { - overflow: auto; - max-height: 640px; - border-block: 1px solid var(--border); -} -.matrix-table { - width: 100%; - border-collapse: separate; - border-spacing: 0; - font-size: 0.875rem; -} -.matrix-table th, -.matrix-table td { - min-width: 104px; - padding: 4px 12px; - text-align: right; - border-bottom: 1px solid var(--border); - white-space: nowrap; -} -.matrix-table thead { - position: sticky; - top: 0; - z-index: 3; - background: var(--bg-secondary); -} -.matrix-table thead th { - font-size: 0.8125rem; - font-weight: 500; - color: var(--label-secondary); - background: var(--bg-secondary); - height: 36px; -} -.matrix-table .matrix-geography { - position: sticky; - left: 0; - z-index: 2; - min-width: 176px; - width: 176px; - max-width: 176px; - text-align: left; - background: var(--bg-tertiary); - border-right: 1px solid var(--border); -} -.matrix-table thead .matrix-geography { - z-index: 4; - background: var(--bg-secondary); -} -.matrix-table .matrix-total { - position: sticky; - left: 176px; - z-index: 1; - background: var(--asset-soft); - border-right: 1px solid var(--border); -} -.matrix-table thead .matrix-total { - z-index: 4; -} -.matrix-table .matrix-selected-column, -.matrix-table .matrix-region-row .matrix-selected-column { - background: var(--asset-soft); -} -.matrix-type-overview { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 8px; - padding: 0 16px 10px; -} -.matrix-type-overview > button { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: 4px 8px; - padding: 10px 12px; - border: 1px solid var(--border); - border-radius: 8px; - text-align: left; - background: var(--bg-tertiary); -} -.matrix-type-overview > button[aria-pressed="true"] { - border-color: var(--blue); - background: var(--asset-soft); -} -.matrix-type-overview > button > span { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - font-size: 0.8125rem; - color: var(--label-secondary); -} -.matrix-type-overview > button > span:first-child { - color: var(--label); - font-weight: 600; -} -.matrix-type-overview svg { - width: 14px; - height: 14px; -} -.matrix-type-overview > button > span:last-child { - grid-column: 1; - grid-row: 2; -} -@media (min-width: 768px) { - .matrix-search input, - .matrix-search > button { - min-height: 36px; - height: 36px; - } -} - -.matrix-type-overview strong { - font-size: 1.5rem; - line-height: 1.25; - font-weight: 650; - grid-column: 2; - grid-row: 1 / 3; - white-space: nowrap; -} -.matrix-type-overview small { - font-size: 0.75rem; - margin-left: 6px; - color: var(--label-secondary); - font-weight: 400; -} -@media (min-width: 768px) and (max-width: 1100px) { - .matrix-type-overview > button { - grid-template-columns: minmax(0, 1fr); - } - .matrix-type-overview strong, - .matrix-type-overview > button > span:last-child { - grid-column: auto; - grid-row: auto; - } -} -.matrix-reading-guide { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - gap: 8px; - padding: 0 16px 8px; - font-size: 0.75rem; - color: var(--label-secondary); -} -.matrix-table .matrix-region-row th, -.matrix-table .matrix-region-row td { - background: var(--bg-secondary); - font-weight: 600; -} -.matrix-table .matrix-region-row .matrix-total { - background: var(--asset-soft); -} -.matrix-table .matrix-city-row .matrix-geography span { - padding-left: 28px; - color: var(--label-secondary); - font-weight: 500; -} -.matrix-table tr:hover td, -.matrix-table .matrix-city-row:hover th { - background: var(--asset-hover); -} -.matrix-table .matrix-brand-cell { - border-left: 1px solid var(--border); -} -.matrix-table tfoot { - position: sticky; - bottom: 0; - z-index: 2; -} -.matrix-table tfoot th, -.matrix-table tfoot td { - background: var(--bg-secondary); - border-bottom: 0; - border-top: 1px solid var(--border); - font-weight: 600; -} -.matrix-expand { - display: flex; - align-items: center; - gap: 8px; - min-height: 32px; - text-align: left; - width: 100%; - font-weight: 600; -} -.matrix-expand > svg { - width: 16px; - height: 16px; - color: var(--label-secondary); - flex-shrink: 0; -} -.matrix-expand small { - font-size: 0.75rem; - font-weight: 400; - color: var(--label-secondary); - margin-left: 8px; -} -.matrix-column-toggle { - display: inline-flex; - align-items: center; - justify-content: flex-end; - gap: 8px; - min-height: 28px; - color: var(--label); - font-weight: 600; -} -.matrix-column-toggle svg { - width: 14px; - height: 14px; - color: var(--label-secondary); -} -.matrix-column-toggle[aria-expanded="true"] { - color: var(--blue); -} -.matrix-number { - display: flex; - flex-direction: column; - align-items: flex-end; -} -.matrix-value { - display: inline-flex; - align-items: center; - justify-content: flex-end; - gap: 4px; - min-height: 28px; - min-width: 44px; - color: var(--blue); - font-size: 0.9375rem; - font-weight: 600; - font-variant-numeric: tabular-nums; -} -.matrix-value svg { - width: 12px; - height: 12px; - opacity: 0.55; -} -.matrix-value:hover { - text-decoration: underline; - text-underline-offset: 4px; -} -.matrix-secondary { - display: flex; - flex-wrap: wrap; - gap: 4px 10px; - justify-content: flex-end; - font-size: 0.75rem; - color: var(--label-secondary); - font-weight: 400; -} -.matrix-secondary button { - min-height: 24px; - white-space: nowrap; -} -.matrix-warning { - color: var(--inventory-warning); -} -.matrix-zero { - color: var(--label-secondary); - font-size: 0.875rem; - font-weight: 400; -} -.matrix-footer { - display: flex; - justify-content: space-between; - gap: 12px; - flex-wrap: wrap; - font-size: 0.75rem; - color: var(--label-secondary); - padding: 10px 16px; -} -.matrix-empty { - display: grid; - justify-items: center; - gap: 12px; - padding: 48px 24px; - color: var(--label-secondary); - text-align: center; -} -.matrix-empty strong { - color: var(--label); - font-size: 1rem; -} -.matrix-mobile-types { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 6px; - padding: 0 16px 16px; -} -.matrix-mobile-types button { - display: flex; - align-items: center; - justify-content: space-between; - gap: 4px; - padding: 8px 10px; - border: 1px solid transparent; - background: var(--bg-secondary); - border-radius: 8px; - font-size: 0.75rem; - min-height: 44px; -} -.matrix-mobile-types button span { - font-size: 0.75rem; - color: var(--label-secondary); -} -.matrix-mobile-types button[aria-pressed="true"] { - background: var(--asset-soft); - color: var(--blue); - border-color: var(--blue); -} -.asset-ui.inventory-analysis { - max-width: 960px; -} -.analysis-overview { - display: grid; - grid-template-columns: 1.4fr 1fr; - gap: 20px; -} -.analysis-composition, -.analysis-workflow { - border: 1px solid var(--border); - border-radius: 12px; - padding: 20px; - background: var(--bg-tertiary); -} -.analysis-composition { - display: flex; - align-items: center; - gap: 16px; -} -.analysis-donut { - position: relative; - width: 180px; - flex-shrink: 0; -} -.analysis-donut svg { - width: 100%; - display: block; - overflow: visible; -} -.analysis-donut circle[role="button"] { - cursor: pointer; - pointer-events: stroke; -} -.analysis-donut circle[role="button"]:hover { - opacity: 0.8; -} -.analysis-donut-center { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - pointer-events: none; -} -.analysis-donut-center span, -.analysis-donut-center small { - font-size: 0.75rem; - color: var(--label-secondary); -} -.analysis-donut-center strong { - font-size: 2.125rem; - line-height: 1.2; - letter-spacing: var(--tracking-tight); - font-weight: 650; - margin: 4px 0; -} -.analysis-legend { - flex: 1; - min-width: 0; -} -.analysis-legend h3, -.analysis-workflow h3, -.analysis-variants h3 { - font-size: 0.9375rem; - font-weight: 600; -} -.analysis-legend button { - display: flex; - align-items: center; - gap: 8px; - min-height: 44px; - width: 100%; - font-size: 0.8125rem; - white-space: nowrap; -} -.analysis-legend button strong { - margin-left: auto; - font-weight: 600; -} -.analysis-legend button svg { - width: 12px; - flex-shrink: 0; - color: var(--blue); -} -.analysis-legend button > span:nth-last-child(2) { - color: var(--label-secondary); - font-size: 0.75rem; -} -.analysis-dot { - width: 8px; - height: 8px; - border-radius: 50%; - flex-shrink: 0; -} -.analysis-legend p { - font-size: 0.75rem; - color: var(--label-secondary); - margin-top: 8px; - line-height: 1.6; -} -.analysis-workflow-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} -.analysis-workflow-heading > span { - font-size: 0.75rem; - color: var(--label-secondary); -} -.analysis-workflow-total { - display: flex; - align-items: center; - justify-content: space-between; - min-height: 52px; - width: 100%; -} -.analysis-workflow-total > span, -.analysis-workflow-total > strong { - display: flex; - align-items: center; - gap: 8px; -} -.analysis-workflow-total svg { - width: 16px; - height: 16px; - color: var(--label-secondary); -} -.analysis-workflow-total strong { - font-size: 1.25rem; - color: var(--blue); - font-weight: 600; -} -.analysis-workflow-total small { - font-size: 0.75rem; - color: var(--label-secondary); - font-weight: 400; -} -.analysis-workflow-total:disabled strong { - color: var(--label-secondary); -} -.analysis-abnormal-types { - display: flex; - flex-wrap: wrap; - gap: 6px 12px; - padding-bottom: 8px; - border-bottom: 1px solid var(--border); - font-size: 0.75rem; - color: var(--label-secondary); -} -.analysis-abnormal-types button { - display: flex; - align-items: center; - gap: 12px; - min-height: 36px; - color: var(--inventory-warning); -} -.analysis-variants { - display: grid; - gap: 16px; -} -.analysis-variants-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.analysis-variants-heading p { - font-size: 0.75rem; - color: var(--label-secondary); - margin-top: 4px; -} -.analysis-variant-list { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; -} -.analysis-variant { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - text-align: left; - padding: 16px; - border: 1px solid var(--border); - border-radius: 10px; - background: var(--bg-tertiary); - min-width: 0; -} -.analysis-variant > div { - min-width: 0; -} -.analysis-variant:hover { - border-color: var(--blue); - background: var(--asset-soft); -} -.analysis-variant > div > strong { - font-size: 0.9375rem; - font-weight: 600; - overflow-wrap: anywhere; -} -.analysis-tags { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 8px; -} -.analysis-tags span { - font-size: 0.75rem; - line-height: 1.5; - color: var(--label-secondary); - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: 5px; - padding: 2px 6px; - overflow-wrap: anywhere; -} -.analysis-variant-count { - display: flex; - align-items: center; - gap: 6px; - color: var(--blue); - font-size: 1.25rem; - font-weight: 600; - white-space: nowrap; -} -.analysis-variant-count small { - font-size: 0.75rem; - font-weight: 400; - color: var(--label-secondary); -} -.analysis-variant-count svg { - width: 14px; - height: 14px; -} -.inventory-matrix button:active, -.inventory-analysis button:active { - opacity: 0.65; -} -@media (max-width: 1100px) { - .analysis-overview { - grid-template-columns: 1fr; - } - .analysis-composition { - justify-content: center; - } - .analysis-legend { - flex: 0 1 300px; - } -} -@media (max-width: 767px) { - .matrix-heading { - padding: 20px 16px; - gap: 12px; - align-items: flex-start; - } - .matrix-heading h2 { - font-size: 1.125rem; - } - .matrix-heading p { - font-size: 0.75rem; - max-width: 130px; - } - .matrix-heading-actions { - gap: 10px; - align-items: flex-end; - } - .matrix-header-total .matrix-value { - font-size: 1.5rem; - } - .matrix-heading-actions > [data-slot="button"] { - font-size: 0.75rem; - padding: 8px; - } - .matrix-toolbar { - padding: 0 16px 14px; - gap: 8px; - flex-wrap: wrap; - } - .matrix-search { - width: 100%; - } - .matrix-tools { - width: 100%; - justify-content: space-between; - } - .matrix-filters { - padding: 16px; - gap: 12px; - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - .matrix-secondary button { - min-height: 44px; - } - .matrix-secondary { - gap: 0 8px; - } - .matrix-footer { - padding: 12px 16px; - gap: 6px; - } - .matrix-number { - flex-shrink: 0; - } - .analysis-overview { - gap: 12px; - } - .analysis-composition { - padding: 12px; - gap: 8px; - } - .analysis-donut { - width: 140px; - } - .analysis-legend { - flex: 1; - } - .analysis-legend button { - gap: 5px; - font-size: 0.75rem; - } - .analysis-legend button svg { - display: none; - } - .analysis-workflow { - padding: 16px; - } - .analysis-workflow-heading > span { - font-size: 0.75rem; - } - .analysis-abnormal-types button { - min-height: 44px; - } - .analysis-variant-list { - grid-template-columns: 1fr; - } - .analysis-variant { - padding: 14px; - } - .analysis-variants-heading { - align-items: center; - } - .analysis-variants-heading > [data-slot="button"] { - font-size: 0.75rem; - padding: 8px; - } - .analysis-donut-center strong { - font-size: 1.75rem; - } - .asset-ui.inventory-analysis { - max-width: 100%; - } -} -@media (prefers-contrast: more) { - .inventory-matrix, - .analysis-variant, - .finder-place { - border-color: var(--label-secondary); - } -} -.asset-ui [data-slot="tabs"], -.asset-ui [data-slot="tabs-content"] { - min-width: 0; - max-width: 100%; -} -.inventory-explorer { - --finder-space: 12px; -} -.finder-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 12px var(--finder-space) 10px; -} -.finder-header h2 { - font-size: 1.125rem; - font-weight: 650; -} -.finder-header p { - color: var(--label-secondary); - font-size: 0.8125rem; - margin-top: 4px; -} -.finder-header > svg { - width: 24px; - height: 24px; - color: var(--blue); - flex-shrink: 0; -} -.finder-steps { - display: flex; - align-items: center; - justify-content: space-between; - margin: 0 var(--finder-space); - padding: 8px 10px; - background: var(--bg-secondary); - border-radius: 10px; - list-style: none; - gap: 8px; -} -.finder-steps li { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.75rem; - color: var(--label-secondary); -} -.finder-steps li > span { - display: grid; - place-items: center; - border: 1px solid var(--border); - border-radius: 50%; - width: 20px; - height: 20px; -} -.finder-steps li[aria-current] { - color: var(--blue); - font-weight: 650; -} -.finder-steps li[aria-current] > span { - background: var(--blue); - color: var(--bg-tertiary); - border-color: transparent; -} -.finder-steps svg { - width: 12px; - height: 12px; - color: var(--label-secondary); -} -.finder-breadcrumb { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0 8px; - padding: 0 var(--finder-space); - font-size: 0.8125rem; - min-height: 44px; -} -.finder-breadcrumb button { - min-width: 44px; - min-height: 44px; - color: var(--blue); -} -.finder-breadcrumb span { - color: var(--label-secondary); - overflow-wrap: anywhere; -} -.finder-breadcrumb svg { - width: 12px; - height: 12px; - color: var(--label-secondary); -} -.finder-scope { - display: flex; - align-items: center; - gap: 8px; - padding: 4px var(--finder-space) 12px; -} -.finder-back { - display: grid; - place-items: center; - width: 44px; - height: 44px; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 10px; - margin-right: 4px; -} -.finder-back svg { - width: 18px; - height: 18px; -} -.finder-scope > div:not(.finder-scope-total) { - min-width: 0; -} -.finder-scope h3 { - font-size: 1.25rem; - font-weight: 650; - overflow-wrap: anywhere; -} -.finder-scope p { - font-size: 0.75rem; - color: var(--label-secondary); - margin-top: 5px; -} -.finder-scope-total { - display: grid; - text-align: right; - margin-left: auto; - flex-shrink: 0; -} -.finder-scope-total strong { - font-size: 1.875rem; - font-weight: 650; - line-height: 1.2; - letter-spacing: -0.03em; -} -.finder-scope-total > span { - font-size: 0.75rem; - color: var(--label-secondary); - margin-top: 5px; -} -.finder-type-heading, -.finder-results-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 0 var(--finder-space) 10px; -} -.finder-type-heading strong, -.finder-results-heading strong { - font-size: 0.875rem; - font-weight: 600; -} -.finder-type-heading > span, -.finder-results-heading > span { - font-size: 0.75rem; - color: var(--label-secondary); -} -.finder-types { - gap: 8px; - padding: 0 var(--finder-space) 12px; -} -.finder-types button { - padding: 8px 10px; - border-color: var(--border); - font-size: 0.8125rem; - min-height: 56px; - white-space: normal; -} -.finder-types button[aria-pressed="true"] { - box-shadow: inset 0 0 0 1px var(--blue); -} -.finder-types button[aria-pressed="true"] span { - color: var(--blue); -} -.finder-search-row { - display: grid; - gap: 12px; - padding: 0 var(--finder-space) 12px; -} -.finder-search-row .matrix-search { - width: 100%; -} -.finder-search-row input { - font-size: 16px; -} -.finder-search-row [data-slot="field"] { - display: grid; - grid-template-columns: auto 1fr; - align-items: center; - gap: 12px; -} -.finder-search-row [data-slot="field-label"] { - font-size: 0.8125rem; - color: var(--label-secondary); -} -.finder-search-row [data-slot="select-trigger"] { - min-height: 44px; - width: 100%; -} -.finder-results-heading { - min-height: 48px; - padding-bottom: 4px; -} -.finder-results-heading button { - min-height: 44px; - font-size: 0.8125rem; - color: var(--blue); -} -.finder-list { - display: grid; - gap: 10px; - padding: 0 var(--finder-space) 16px; -} -.finder-place { - display: grid; - width: 100%; - gap: 8px; - min-width: 0; - padding: 12px; - text-align: left; - border: 1px solid var(--border); - border-radius: 12px; - background: var(--bg-tertiary); -} -.finder-place:disabled { - background: var(--bg-secondary); -} -.finder-place-top { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.finder-place-top > strong { - font-size: 1rem; - font-weight: 600; - overflow-wrap: anywhere; - min-width: 0; -} -.finder-place-count { - display: flex; - align-items: center; - gap: 6px; - font-size: 1.375rem; - font-weight: 650; - color: var(--blue); - white-space: nowrap; -} -.finder-place:disabled .finder-place-count { - color: var(--label-secondary); -} -.finder-place-count small { - font-size: 0.75rem; - font-weight: 400; - color: var(--label-secondary); -} -.finder-place-count svg { - width: 16px; - height: 16px; - margin-left: 4px; - color: var(--label-secondary); -} -.finder-place-status { - display: flex; - gap: 6px 12px; - flex-wrap: wrap; - color: var(--label-secondary); - font-size: 0.75rem; -} -.finder-type-tags { - display: flex; - flex-wrap: wrap; - gap: 6px; -} -.finder-type-tags > span { - padding: 4px 8px; - background: var(--bg-secondary); - border-radius: 5px; - color: var(--label-secondary); - font-size: 0.75rem; -} -.finder-type-tags b { - font-weight: 600; - margin-left: 8px; - color: var(--label); -} -.finder-model-meta { - font-size: 0.8125rem; - color: var(--label-secondary); - overflow-wrap: anywhere; -} -.finder-model-footer { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - border-top: 1px solid var(--border); - padding-top: 12px; - font-size: 0.75rem; -} -.finder-model-footer > span:first-child { - color: var(--inventory-warning); -} -.finder-model-footer > span:last-child { - display: flex; - align-items: center; - gap: 6px; - margin-left: auto; - color: var(--blue); - white-space: nowrap; -} -.finder-model-footer svg { - width: 14px; - height: 14px; -} -.finder-empty { - display: grid; - justify-items: center; - gap: 12px; - padding: 24px var(--finder-space); - text-align: center; -} -.finder-empty > svg { - width: 28px; - height: 28px; - color: var(--label-secondary); -} -.finder-empty strong { - font-size: 0.9375rem; -} -.finder-empty p { - font-size: 0.8125rem; - color: var(--label-secondary); - max-width: 32ch; -} -.inventory-explorer [data-slot="button"] { - min-height: 44px; -} -.inventory-explorer .matrix-footer { - background: var(--bg-secondary); - font-size: 0.75rem; - line-height: 1.6; -} -@media (hover: hover) { - .finder-place:not(:disabled):hover, - .matrix-type-overview > button:hover { - background: var(--asset-soft); - border-color: var(--blue); - } -} -@media (prefers-reduced-motion: no-preference) { - .inventory-explorer button, - .matrix-type-overview > button { - transition: - background-color 140ms ease, - border-color 140ms ease, - opacity 100ms ease; - } -} -@media (max-width: 767px) { - .matrix-mobile-types button { - flex-direction: column; - align-items: flex-start; - white-space: nowrap; - gap: 3px; - } - .matrix-mobile-types button span { - align-self: flex-end; - font-size: 0.9375rem; - font-weight: 600; - } - .matrix-heading p { - max-width: none; - } -} diff --git a/src/modules/assets/library/inventory-view.test.ts b/src/modules/assets/library/inventory-view.test.ts deleted file mode 100644 index 2716ccb..0000000 --- a/src/modules/assets/library/inventory-view.test.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { - inventoryKey, - inventoryOperation, - inventoryColor, -} from "../../../shared/assets/inventory"; -import { - INVENTORY_TYPES, - inventoryColumn, - inventoryTotals, - inventoryModel, - scopedInventorySelection, - matchesInventorySelection, -} from "./inventory-view"; -import type { VehicleListItem } from "../types"; -import test from "node:test"; -import assert from "node:assert/strict"; -import { groupInventory, inventorySelection } from "./inventory-view"; -import { buildVehicleModalRequest } from "../model"; -import type { RegionalInventoryStats } from "../types"; -const make = ( - change: Partial = {}, -): RegionalInventoryStats => ({ - region: "华东", - city: "嘉兴市", - brand: "明威", - type: "挂车", - model: "挂车", - batch: "挂车", - quantity: 2, - abnormal: 1, - pending: 1, - ...change, -}); -test("inventory grouping preserves totals and does not double-count abnormal/pending subsets", () => { - const rows = [ - make(), - make({ brand: "万风", quantity: 1 }), - make({ region: "华南", city: "广州市", quantity: 4 }), - ]; - const original = JSON.stringify(rows); - const groups = groupInventory(rows, "region"); - assert.deepEqual( - groups.map((group) => [group.name, group.total]), - [ - ["华南", 4], - ["华东", 3], - ], - ); - assert.equal(groups[1].cities[0].rows.length, 2); - assert.equal( - groups.reduce((sum, group) => sum + group.total, 0), - 7, - ); - assert.equal(JSON.stringify(rows), original); -}); -test("type grouping keeps same-name cities in different regions separate", () => { - const grouped = groupInventory( - [make(), make({ region: "华南", brand: "万风" })], - "type", - ); - assert.equal(grouped.length, 1); - assert.equal(grouped[0].cities.length, 2); - assert.notEqual(grouped[0].cities[0].key, grouped[0].cities[1].key); -}); -test("sorting reverses quantities within each city and preserves the source array", () => { - const rows = [make(), make({ quantity: 5, model: "另一型号" })]; - assert.deepEqual( - groupInventory(rows, "region")[0].cities[0].rows.map((row) => row.quantity), - [5, 2], - ); - assert.deepEqual( - groupInventory(rows, "region", true)[0].cities[0].rows.map( - (row) => row.quantity, - ), - [2, 5], - ); - assert.equal(rows[0].quantity, 2); - assert.deepEqual(groupInventory([], "region"), []); -}); -test("drill-down keeps brand, model, city, type, category and company; does not mistake model for contract batch", () => { - const request = buildVehicleModalRequest( - inventorySelection(make(), "Pending"), - "公司甲", - ); - assert.equal(request.kind, "vehicles"); - if (request.kind !== "vehicles") return; - assert.deepEqual(request.params, { - brand: "明威", - model: "挂车", - location: "嘉兴市", - source: "asset", - category: "Pending", - vehicleType: "挂车", - subject: "公司甲", - }); -}); - -test("matrix merges trailers into other while preserving all five column totals", () => { - const rows = [ - make(), - make({ type: "35T", quantity: 3 }), - make({ type: "4.5T普货", quantity: 4 }), - make({ type: "4.5T冷链", quantity: 5 }), - make({ type: "18T", quantity: 6 }), - make({ type: "49T", quantity: 7 }), - ]; - assert.deepEqual( - INVENTORY_TYPES.map( - (type) => - inventoryTotals( - rows.filter((row) => inventoryColumn(row.type) === type), - ).quantity, - ), - [4, 5, 6, 7, 5], - ); - assert.equal(inventoryTotals(rows).quantity, 27); -}); -test("composition uses independent operation status; abnormal and pending remain subsets", () => { - const total = inventoryTotals([ - make({ - quantity: 5, - available: 3, - awaiting: 2, - abnormal: 2, - pending: 1, - abnormalTypes: { 维修: 2 }, - }), - make({ - quantity: 2, - available: 0, - awaiting: 2, - abnormal: 1, - pending: 0, - abnormalTypes: { 保养: 1 }, - }), - ]); - assert.equal(total.available + total.awaiting, total.quantity); - assert.equal(total.quantity, 7); - assert.equal(total.abnormal, 3); - assert.deepEqual(total.abnormalTypes, { 维修: 2, 保养: 1 }); - assert.equal(inventoryOperation("在库"), "unclassified"); -}); -test("model tags separate operator and color while retaining a model-first name", () => { - assert.deepEqual(inventoryModel("现代4.5T普货(恒运)", "现代"), { - name: "4.5T普货", - operator: "恒运", - }); - assert.deepEqual(inventoryModel("现代18T双飞翼(羚牛)", "帕力安牌"), { - name: "18T双飞翼", - operator: "羚牛", - }); - assert.deepEqual(inventoryModel("49T宇通", "宇通"), { - name: "49T", - operator: "", - }); - assert.deepEqual(inventoryModel("飞驰18T(红车)", "飞驰"), { - name: "18T", - operator: "", - }); - assert.deepEqual(inventoryModel("49T飞驰(浙氢-红)", "飞驰"), { - name: "49T", - operator: "浙氢", - }); -}); -test("drill-down intersects exact geographic/model/brand keys with color and operational status", () => { - const row = make(); - const selection = scopedInventorySelection( - { rows: [row], label: "华东 / 嘉兴市 / 其他" }, - { inventoryColor: "白", inventoryOperation: "available" }, - ); - const vehicle = { - inventoryKey: inventoryKey(row), - color: "白", - operationStatus: "3", - } as VehicleListItem; - assert.equal(matchesInventorySelection(vehicle, selection), true); - assert.equal( - matchesInventorySelection({ ...vehicle, color: "红" }, selection), - false, - ); - assert.equal( - matchesInventorySelection({ ...vehicle, operationStatus: "4" }, selection), - false, - ); - assert.equal( - matchesInventorySelection( - { ...vehicle, inventoryKey: inventoryKey({ ...row, region: "华南" }) }, - selection, - ), - false, - ); - assert.equal( - matchesInventorySelection(vehicle, { ...selection, inventoryKeys: [] }), - false, - ); -}); - -test("known color aliases combine without merging distinct multicolor values", () => { - assert.equal(inventoryColor("红"), inventoryColor("红色")); - assert.equal(inventoryColor("白车"), "白色"); - assert.equal(inventoryColor("蓝白绿"), "蓝白绿"); - assert.equal(inventoryColor(null), "未标注颜色"); -}); diff --git a/src/modules/assets/library/inventory-view.ts b/src/modules/assets/library/inventory-view.ts deleted file mode 100644 index 3d2541c..0000000 --- a/src/modules/assets/library/inventory-view.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { - emptyInventoryCounts, - inventoryKey, - inventoryOperation, - inventoryColor, -} from "../../../shared/assets/inventory"; -import type { VehicleListItem } from "../types"; -import type { RegionalInventoryStats } from "../types"; -import type { VehicleModalCategory, VehicleModalSelection } from "../model"; - -export type InventoryGrouping = "region" | "type"; -export function groupInventory( - rows: RegionalInventoryStats[], - grouping: InventoryGrouping, - ascending = false, -) { - const groups = new Map(); - for (const row of rows) { - const key = row[grouping] || "未归属"; - if (!groups.has(key)) groups.set(key, []); - groups.get(key)!.push(row); - } - return [...groups] - .map(([name, items]) => { - const cities = new Map(); - for (const row of items) { - // A city may be missing or share a name with a city in another region. - const key = JSON.stringify([row.region, row.city]); - if (!cities.has(key)) cities.set(key, []); - cities.get(key)!.push(row); - } - return { - name, - total: items.reduce((sum, row) => sum + row.quantity, 0), - cities: [...cities] - .map(([key, records]) => ({ - key, - name: records[0].city || "未归属城市", - region: records[0].region || "未归属", - total: records.reduce((sum, row) => sum + row.quantity, 0), - rows: [...records].sort( - (a, b) => - (ascending ? 1 : -1) * (a.quantity - b.quantity) || - a.model.localeCompare(b.model, "zh-CN"), - ), - })) - .sort( - (a, b) => - b.total - a.total || a.name.localeCompare(b.name, "zh-CN"), - ), - }; - }) - .sort((a, b) => b.total - a.total || a.name.localeCompare(b.name, "zh-CN")); -} -export function inventorySelection( - row: RegionalInventoryStats, - category: VehicleModalCategory = "Inventory", -): VehicleModalSelection { - return { - batch: "All", - source: "asset", - category, - brand: row.brand, - model: row.model, - location: row.city, - type: row.type, - }; -} - -export const INVENTORY_TYPES = [ - "4.5T普货", - "4.5T冷链", - "18T", - "49T", - "其他", -] as const; -export function inventoryColumn(type: string) { - return INVENTORY_TYPES.includes(type as (typeof INVENTORY_TYPES)[number]) - ? type - : "其他"; -} -export function inventoryTotals(rows: RegionalInventoryStats[]) { - const result = emptyInventoryCounts(); - for (const row of rows) { - for (const key of [ - "quantity", - "available", - "awaiting", - "abnormal", - "pending", - ] as const) - result[key] += row[key] || 0; - result.unclassified += - row.unclassified ?? - Math.max(0, row.quantity - (row.available || 0) - (row.awaiting || 0)); - for (const [type, count] of Object.entries(row.abnormalTypes || {})) - result.abnormalTypes[type] = (result.abnormalTypes[type] || 0) + count; - } - return result; -} -/** Display labels never replace the original model used for exact drill-down. */ -export function inventoryModel(model: string, brand: string) { - const parentheses = [...model.matchAll(/[((]([^))]+)[))]/g)].map( - (match) => match[1], - ); - const colorOnly = /^(?:红|白|蓝|绿|灰|黑|黄|银|蓝白绿)(?:色|车)?$/; - const operator = parentheses - .filter((value) => !colorOnly.test(value)) - .map((value) => value.replace(/[--](?:红|白|蓝白绿)(?:色|车)?$/, "")) - .join(" · "); - let name = model.replace(/[((][^))]*[))]/g, "").trim(); - for (const label of [ - brand, - "现代", - "帕力安牌", - "宇通", - "飞驰", - "苏龙", - "跃进", - "楚风", - ]) { - if (label && name.startsWith(label)) - name = name.slice(label.length).replace(/^-/, ""); - if (label && name.endsWith(label)) name = name.slice(0, -label.length); - } - return { name: name || model, operator }; -} -export interface InventoryScope { - rows: RegionalInventoryStats[]; - label: string; - initialCategory?: "Abnormal" | "Pending"; -} -export function scopedInventorySelection( - scope: InventoryScope, - extra: Partial = {}, -): VehicleModalSelection { - return { - batch: "All", - model: "All", - location: "All", - source: "asset", - category: "Inventory", - inventoryKeys: scope.rows.map(inventoryKey), - scopeLabel: scope.label, - ...extra, - }; -} -export function matchesInventorySelection( - vehicle: VehicleListItem, - selection: VehicleModalSelection, -) { - return ( - (!selection.inventoryKeys || - selection.inventoryKeys.includes(vehicle.inventoryKey || "")) && - (!selection.inventoryOperation || - inventoryOperation(vehicle.operationStatus) === - selection.inventoryOperation) && - (!selection.inventoryColor || - inventoryColor(vehicle.color) === - inventoryColor(selection.inventoryColor)) && - (!selection.abnormalType || - (vehicle.abnormalMove?.type || "未提供") === selection.abnormalType) - ); -} diff --git a/src/modules/assets/library/shared.tsx b/src/modules/assets/library/shared.tsx deleted file mode 100644 index 3868bcc..0000000 --- a/src/modules/assets/library/shared.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import { useEffect, useRef, useState, type ReactNode } from "react"; -import { - Accordion, - AccordionItem, - AccordionTrigger, - AccordionContent, -} from "@/components/ui/accordion"; -import { Badge } from "@/components/ui/badge"; -import { ArrowUpRight, SlidersHorizontal } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Field, FieldLabel } from "@/components/ui/field"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { useDemoMode } from "../../../components/Blur"; -import type { VehicleModalSelection } from "../model"; -export type OpenVehicles = (selection: VehicleModalSelection) => void; -export const baseSelection: VehicleModalSelection = { - batch: "All", - model: "All", - location: "All", - source: "asset", -}; -export const unique = (values: (string | null | undefined)[]) => - [...new Set(values.filter((v): v is string => !!v))].sort(); - -/** 区块级 Tab 统一样式:模块级与卡片内嵌 Tab 共用,避免多套视觉。 */ -export const sectionTabsListClass = - "w-full min-h-11 bg-[var(--muted)] md:w-fit"; -export const sectionTabsTriggerClass = - "min-h-11 px-4 data-[state=active]:text-[var(--blue)] dark:data-[state=active]:text-[var(--cyan)]"; - -/** 数字滚动动画:目标值变化时从当前值缓动到新值;尊重 reduced-motion。 */ -export function useCountUp(target: number, duration = 800) { - const [value, setValue] = useState(0); - const valueRef = useRef(0); - const rafRef = useRef(0); - useEffect(() => { - const from = valueRef.current; - if (from === target) return undefined; - if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { - valueRef.current = target; - setValue(target); - return undefined; - } - const start = performance.now(); - const tick = (now: number) => { - const t = Math.min(1, (now - start) / duration); - const eased = 1 - Math.pow(1 - t, 3); - const current = Math.round(from + (target - from) * eased); - valueRef.current = current; - setValue(current); - if (t < 1) rafRef.current = requestAnimationFrame(tick); - }; - rafRef.current = requestAnimationFrame(tick); - return () => cancelAnimationFrame(rafRef.current); - }, [target, duration]); - return value; -} - -/** 占比条:用于 KPI 卡片内的构成占比(如 租赁/自营)。 */ -export function ProportionBar({ - segments, - label, -}: { - segments: { value: number; className: string; title: string }[]; - label: string; -}) { - const total = segments.reduce((n, s) => n + s.value, 0); - return ( -
- {total > 0 && - segments - .filter((s) => s.value > 0) - .map((s) => ( -
- ))} -
- ); -} -export function PrivateText({ children }: { children: ReactNode }) { - return <>{useDemoMode() ? "••••••" : children || "—"}; -} -export function Count({ - value, - onClick, - label, -}: { - value: number; - onClick: () => void; - label?: string; -}) { - if (!value) - return ( - - 0 - - ); - return ( - - ); -} -export function Picker({ - label, - value = "", - values, - change, -}: { - label: string; - value?: string; - values: string[]; - change: (value: string) => void; -}) { - return ( - - {label} - - - ); -} -export function FilterFields({ - fields, - onReset, -}: { - fields: Parameters[0][]; - onReset: () => void; -}) { - const active = fields.filter((field) => field.value); - return ( - - - - - - 筛选条件 - {active.length ? ( - active.map((field) => ( - - {field.label}:{field.value} - - )) - ) : ( - - 未设条件 - - )} - - - -
- {fields.map((field) => ( - - ))} - -
-
-
-
- ); -} -export function Failure({ - message, - retry, -}: { - message: string; - retry?: () => void; -}) { - return ( - - 数据加载失败 - - {message} - {retry && ( - - )} - - - ); -} -export function useMobile() { - const [mobile, setMobile] = useState( - () => window.matchMedia("(max-width: 767px)").matches, - ); - useEffect(() => { - const query = window.matchMedia("(max-width: 767px)"); - const update = () => setMobile(query.matches); - query.addEventListener("change", update); - return () => query.removeEventListener("change", update); - }, []); - return mobile; -} diff --git a/src/modules/assets/library/useAssetHash.ts b/src/modules/assets/library/useAssetHash.ts deleted file mode 100644 index 8091dbc..0000000 --- a/src/modules/assets/library/useAssetHash.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useState } from "react"; - -const TAB_IDS = ["overview", "department", "region", "customer"] as const; -const SECTION_IDS = ["inventory", "models", "flow"] as const; - -export type AssetTab = (typeof TAB_IDS)[number]; -export type AssetSection = (typeof SECTION_IDS)[number]; - -interface AssetHashState { - tab: AssetTab; - section: AssetSection; -} - -const DEFAULT_STATE: AssetHashState = { tab: "overview", section: "inventory" }; - -const isTab = (value: string | undefined): value is AssetTab => - Boolean(value) && (TAB_IDS as readonly string[]).includes(value as string); -const isSection = (value: string | undefined): value is AssetSection => - Boolean(value) && (SECTION_IDS as readonly string[]).includes(value as string); - -function parseHash(): AssetHashState { - const [first, second, third] = window.location.hash.slice(1).split("/"); - if (first !== "assets") return DEFAULT_STATE; - return { - tab: isTab(second) ? second : "overview", - section: isSection(third) ? third : "inventory", - }; -} - -function writeHash(tab: AssetTab, section: AssetSection) { - const parts = ["assets", tab]; - // 默认子页签不写入,保持 `#assets/` 简洁。 - if (tab === "overview" && section !== "inventory") parts.push(section); - const hash = `#${parts.join("/")}`; - window.history.replaceState( - null, - "", - `${window.location.pathname}${window.location.search}${hash}`, - ); -} - -/** - * 资产模块页签状态 ↔ URL hash 二级/三级段(`#assets/[/
]`)。 - * 默认值不写入 hash,刷新可恢复,链接可分享;与 Shell 的一级模块段兼容。 - */ -export function useAssetHash() { - const [state, setState] = useState(parseHash); - - useEffect(() => { - const onChange = () => setState(parseHash()); - window.addEventListener("hashchange", onChange); - return () => window.removeEventListener("hashchange", onChange); - }, []); - - const update = (next: Partial) => { - const merged = { ...state, ...next }; - writeHash(merged.tab, merged.section); - setState(merged); - }; - - return [state, update] as const; -} diff --git a/src/modules/energy/SubTabs.tsx b/src/modules/energy/SubTabs.tsx index 63904d9..ff10646 100644 --- a/src/modules/energy/SubTabs.tsx +++ b/src/modules/energy/SubTabs.tsx @@ -15,7 +15,7 @@ interface Props { export default function SubTabs({ tabs, active, onChange }: Props) { return ( -
+
); diff --git a/src/modules/mileage/MileageModule.tsx b/src/modules/mileage/MileageModule.tsx index 06a0a36..9466e5f 100644 --- a/src/modules/mileage/MileageModule.tsx +++ b/src/modules/mileage/MileageModule.tsx @@ -30,7 +30,7 @@ export default function MileageModule() { compactInfo hideHeader > -
+
diff --git a/src/vendor/apple-design/tokens.css b/src/vendor/apple-design/tokens.css deleted file mode 100644 index 8be959e..0000000 --- a/src/vendor/apple-design/tokens.css +++ /dev/null @@ -1,239 +0,0 @@ -/* ============================================================================ - Apple Design Tokens — drop-in theme layer for the web - ---------------------------------------------------------------------------- - Real Apple system values (colors, type, spacing, radii, materials, motion) - expressed as CSS custom properties. Import once at the root of your app: - - @import "tokens.css"; - - Everything below is light-mode default; dark mode is applied automatically - via prefers-color-scheme AND an explicit [data-theme="dark"] override so you - can force a theme. Semantic tokens (--label, --bg, --fill-*) are what you - should reference in components — never hardcode a hex. - ============================================================================ */ - -:root { - /* ---- Type: system font stacks -------------------------------------- */ - /* -apple-system/BlinkMacSystemFont pull SF Pro on Apple platforms; the - rest degrade gracefully. "SF Pro Display" vs "Text" is handled by the OS - via optical sizing — we opt in with font-optical-sizing below. */ - --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", - "Helvetica Neue", "Segoe UI", system-ui, sans-serif; - --font-rounded: "SF Pro Rounded", -apple-system, "Nunito", system-ui, sans-serif; - --font-serif: "New York", ui-serif, Georgia, "Times New Roman", serif; - --font-mono: "SF Mono", ui-monospace, "JetBrains Mono", Menlo, monospace; - - /* ---- Type scale: Apple's text styles, in rem (1rem = 16px base) ----- - Each style is a *set*: size + weight + tracking + leading. See - references/typography.md. Tracking tightens as size grows; leading loosens - as size shrinks. Values are Apple's, converted pt→rem at 16px root. */ - --text-large-title: 2.125rem; /* 34pt */ - --text-title-1: 1.75rem; /* 28pt */ - --text-title-2: 1.375rem; /* 22pt */ - --text-title-3: 1.25rem; /* 20pt */ - --text-headline: 1.0625rem; /* 17pt, semibold */ - --text-body: 1.0625rem; /* 17pt */ - --text-callout: 1rem; /* 16pt */ - --text-subhead: 0.9375rem; /* 15pt */ - --text-footnote: 0.8125rem; /* 13pt */ - --text-caption-1: 0.75rem; /* 12pt */ - --text-caption-2: 0.6875rem; /* 11pt */ - - --tracking-tight: -0.022em; /* large display text */ - --tracking-snug: -0.014em; /* titles */ - --tracking-normal: 0em; /* body */ - --tracking-wide: 0.01em; /* small caps / footnotes */ - - --leading-tight: 1.05; /* headlines */ - --leading-snug: 1.2; - --leading-normal: 1.35; /* body-ish */ - --leading-relaxed: 1.5; /* long-form reading */ - - --weight-regular: 400; - --weight-medium: 500; - --weight-semibold: 600; - --weight-bold: 700; - - /* ---- Spacing: 4/8pt grid ------------------------------------------- */ - --space-1: 0.125rem; /* 2 */ - --space-2: 0.25rem; /* 4 */ - --space-3: 0.5rem; /* 8 */ - --space-4: 0.75rem; /* 12 */ - --space-5: 1rem; /* 16 */ - --space-6: 1.25rem; /* 20 */ - --space-7: 1.5rem; /* 24 */ - --space-8: 2rem; /* 32 */ - --space-9: 2.5rem; /* 40 */ - --space-10: 3rem; /* 48 */ - --space-12: 4rem; /* 64 */ - - /* ---- Radii: Apple leans on generous, continuous corners ------------ - iOS uses "continuous" curvature (a squircle), not a plain arc. On the web - you approximate it with a slightly larger border-radius; for hero elements - use the squircle recipe in references/tokens.md. Capsule = fully rounded. */ - --radius-xs: 6px; - --radius-sm: 8px; - --radius-md: 12px; - --radius-lg: 16px; - --radius-xl: 22px; /* iOS app-icon / large card feel */ - --radius-2xl: 28px; - --radius-capsule: 999px; - - /* ---- Elevation: soft, layered, low-contrast shadows ---------------- - Apple shadows are diffuse and never harsh. Two-layer: a tight contact - shadow + a soft ambient one. Tune opacity down in dark mode. */ - --shadow-1: 0 1px 2px rgba(0,0,0,0.04), 0 1px 1px rgba(0,0,0,0.06); - --shadow-2: 0 2px 8px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.08); - --shadow-3: 0 8px 24px rgba(0,0,0,0.10), 0 2px 6px rgba(0,0,0,0.08); - --shadow-4: 0 16px 48px rgba(0,0,0,0.14), 0 4px 12px rgba(0,0,0,0.10); - --shadow-glass: 0 8px 32px rgba(0,0,0,0.18); /* floating glass chrome */ - - /* ---- Motion: easing + duration tokens ------------------------------ - Apple UI never uses the weak built-in ease-*. These curves are the ones - Apple's own interfaces and the best web recreations use. Springs cannot be - expressed in pure CSS — see references/motion-physics.md for the JS tokens - (damping/response) consumed by Motion and GSAP. NEVER use ease-in on UI: - it starts slow and feels laggy. */ - --ease-out: cubic-bezier(0.23, 1, 0.32, 1); /* default UI exit-of-motion */ - --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* symmetric moves */ - --ease-ios: cubic-bezier(0.32, 0.72, 0, 1); /* sheets/drawers (iOS) */ - --ease-snappy: cubic-bezier(0.4, 0, 0.2, 1); /* quick, neutral */ - - --duration-instant: 100ms; /* button press / tap feedback */ - --duration-fast: 200ms; /* tooltips, small popovers */ - --duration-base: 300ms; /* dropdowns, most UI (keep UI under 300ms) */ - --duration-slow: 450ms; /* modals, sheets, drawers */ - - /* ---- Semantic colors: LIGHT (defaults) ----------------------------- */ - /* Accents (Apple system colors) */ - --blue: #007AFF; --green: #34C759; --indigo: #5856D6; --orange: #FF9500; - --pink: #FF2D55; --purple: #AF52DE; --red: #FF3B30; --teal: #30B0C7; - --cyan: #32ADE6; --yellow: #FFCC00; --mint: #00C7BE; --brown: #A2845E; - --accent: var(--blue); - - /* Grays */ - --gray-1: #8E8E93; --gray-2: #AEAEB2; --gray-3: #C7C7CC; - --gray-4: #D1D1D6; --gray-5: #E5E5EA; --gray-6: #F2F2F7; - - /* Backgrounds (base → elevated) */ - --bg: #FFFFFF; - --bg-secondary: #F2F2F7; - --bg-tertiary: #FFFFFF; - --bg-grouped: #F2F2F7; /* settings-style grouped lists */ - - /* Labels (text) — Apple encodes hierarchy as opacity over a base ink */ - --label: rgba(0, 0, 0, 1); - --label-secondary: rgba(60, 60, 67, 0.60); - --label-tertiary: rgba(60, 60, 67, 0.30); - --label-quaternary: rgba(60, 60, 67, 0.18); - - /* Separators & fills */ - --separator: rgba(60, 60, 67, 0.29); - --separator-opaque: #C6C6C8; - --fill-1: rgba(120, 120, 128, 0.20); /* thick control fill */ - --fill-2: rgba(120, 120, 128, 0.16); - --fill-3: rgba(120, 120, 128, 0.12); - --fill-4: rgba(120, 120, 128, 0.08); /* thin, e.g. hover */ - - /* ---- Materials: the tint + blur pair per thickness ------------------ - A "material" is a translucent tint color paired with a backdrop blur. Use - --material-*-tint as background and --material-*-blur inside backdrop-filter. - See references/liquid-glass.md for the full layered recipe (specular + rim). */ - --material-ultrathin-tint: rgba(255, 255, 255, 0.44); - --material-thin-tint: rgba(255, 255, 255, 0.60); - --material-regular-tint: rgba(255, 255, 255, 0.72); - --material-thick-tint: rgba(255, 255, 255, 0.84); - --material-blur: 20px; - --material-blur-thin: 12px; - --material-saturate: 180%; - - color-scheme: light dark; -} - -/* ============================================================================ - DARK MODE — auto via prefers-color-scheme, plus [data-theme] overrides so - you can force light/dark regardless of OS setting. - ============================================================================ */ -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { - --blue: #0A84FF; --green: #30D158; --indigo: #5E5CE6; --orange: #FF9F0A; - --pink: #FF375F; --purple: #BF5AF2; --red: #FF453A; --teal: #40C8E0; - --cyan: #64D2FF; --yellow: #FFD60A; --mint: #63E6E2; --brown: #AC8E68; - - --gray-1: #8E8E93; --gray-2: #636366; --gray-3: #48484A; - --gray-4: #3A3A3C; --gray-5: #2C2C2E; --gray-6: #1C1C1E; - - --bg: #000000; - --bg-secondary: #1C1C1E; - --bg-tertiary: #2C2C2E; - --bg-grouped: #000000; - - --label: rgba(255, 255, 255, 1); - --label-secondary: rgba(235, 235, 245, 0.60); - --label-tertiary: rgba(235, 235, 245, 0.30); - --label-quaternary: rgba(235, 235, 245, 0.16); - - --separator: rgba(84, 84, 88, 0.60); - --separator-opaque: #38383A; - --fill-1: rgba(120, 120, 128, 0.36); - --fill-2: rgba(120, 120, 128, 0.32); - --fill-3: rgba(120, 120, 128, 0.24); - --fill-4: rgba(120, 120, 128, 0.18); - - --material-ultrathin-tint: rgba(30, 30, 32, 0.44); - --material-thin-tint: rgba(30, 30, 32, 0.62); - --material-regular-tint: rgba(30, 30, 32, 0.74); - --material-thick-tint: rgba(30, 30, 32, 0.86); - - --shadow-1: 0 1px 2px rgba(0,0,0,0.30); - --shadow-2: 0 2px 8px rgba(0,0,0,0.36); - --shadow-3: 0 8px 24px rgba(0,0,0,0.44); - --shadow-4: 0 16px 48px rgba(0,0,0,0.52); - --shadow-glass: 0 8px 32px rgba(0,0,0,0.50); - } -} - -/* Explicit dark override (force dark even in a light OS) */ -[data-theme="dark"] { - --blue:#0A84FF; --green:#30D158; --indigo:#5E5CE6; --orange:#FF9F0A; - --pink:#FF375F; --purple:#BF5AF2; --red:#FF453A; --teal:#40C8E0; - --cyan:#64D2FF; --yellow:#FFD60A; --mint:#63E6E2; --brown:#AC8E68; - --gray-1:#8E8E93; --gray-2:#636366; --gray-3:#48484A; - --gray-4:#3A3A3C; --gray-5:#2C2C2E; --gray-6:#1C1C1E; - --bg:#000; --bg-secondary:#1C1C1E; --bg-tertiary:#2C2C2E; --bg-grouped:#000; - --label:rgba(255,255,255,1); --label-secondary:rgba(235,235,245,0.6); - --label-tertiary:rgba(235,235,245,0.3); --label-quaternary:rgba(235,235,245,0.16); - --separator:rgba(84,84,88,0.6); --separator-opaque:#38383A; - --fill-1:rgba(120,120,128,0.36); --fill-2:rgba(120,120,128,0.32); - --fill-3:rgba(120,120,128,0.24); --fill-4:rgba(120,120,128,0.18); - --material-ultrathin-tint:rgba(30,30,32,0.44); --material-thin-tint:rgba(30,30,32,0.62); - --material-regular-tint:rgba(30,30,32,0.74); --material-thick-tint:rgba(30,30,32,0.86); -} - -/* ============================================================================ - BASE — sensible defaults so the tokens feel Apple-native immediately. - ============================================================================ */ -html { -webkit-text-size-adjust: 100%; } - -body { - margin: 0; - font-family: var(--font-sans); - font-optical-sizing: auto; /* SF switches Text↔Display by size */ - -webkit-font-smoothing: antialiased; /* thinner, Apple-like rendering */ - text-rendering: optimizeLegibility; - font-size: var(--text-body); - line-height: var(--leading-normal); - letter-spacing: var(--tracking-normal); - color: var(--label); - background: var(--bg); -} - -/* Accessibility: respect the three reduced-* preferences everywhere. */ -@media (prefers-reduced-motion: reduce) { - *, *::before, *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -}