From a7874d79b54343ca9b29fe07673ac2a265940684 Mon Sep 17 00:00:00 2001 From: freedakgmail Date: Sun, 26 Jul 2026 22:48:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=EF=BC=9A=E8=BF=9E?= =?UTF-8?q?=E9=94=81=E9=A4=90=E9=A5=AE=E6=95=B0=E5=AD=97=E5=8C=96=E8=BF=90?= =?UTF-8?q?=E8=90=A5=E7=AE=A1=E7=90=86=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 10 + README.md | 126 + client/index.html | 12 + client/package-lock.json | 3487 ++++++++++++++++++ client/package.json | 34 + client/postcss.config.js | 6 + client/src/App.tsx | 88 + client/src/components/Badge.tsx | 25 + client/src/components/CollapsibleSection.tsx | 33 + client/src/components/DataTable.tsx | 68 + client/src/components/Layout.tsx | 143 + client/src/components/LoadingSpinner.tsx | 10 + client/src/components/MetricCard.tsx | 44 + client/src/components/Pagination.tsx | 76 + client/src/components/Skeleton.tsx | 37 + client/src/index.css | 56 + client/src/lib/api.ts | 27 + client/src/lib/utils.ts | 56 + client/src/main.tsx | 10 + client/src/pages/CostPage.tsx | 200 + client/src/pages/DashboardPage.tsx | 246 ++ client/src/pages/DataQualityPage.tsx | 118 + client/src/pages/IndicatorsPage.tsx | 38 + client/src/pages/LoginPage.tsx | 64 + client/src/pages/MemberPage.tsx | 153 + client/src/pages/MonthlyReviewPage.tsx | 124 + client/src/pages/OntologyPage.tsx | 393 ++ client/src/pages/PlatformPage.tsx | 147 + client/src/pages/RegionalPage.tsx | 117 + client/src/pages/RiskPage.tsx | 168 + client/src/pages/SKUPage.tsx | 170 + client/src/pages/StoreDetailPage.tsx | 103 + client/src/pages/StorePage.tsx | 319 ++ client/src/pages/TaskDetailPage.tsx | 323 ++ client/src/pages/TasksPage.tsx | 119 + client/src/pages/TimePage.tsx | 167 + client/tailwind.config.ts | 45 + client/tsconfig.json | 22 + client/tsconfig.node.json | 10 + client/vite.config.ts | 21 + db/ontology_standard.sql | 826 +++++ package-lock.json | 372 ++ package.json | 15 + run.md | 1 + server/package-lock.json | 1840 +++++++++ server/package.json | 27 + server/sql/01_create_tables.sql | 181 + server/sql/02_create_functions.sql | 199 + server/sql/03_create_views.sql | 225 ++ server/sql/04_init_data.sql | 38 + server/sql/05_backfill_task_values.sql | 54 + server/sql/06_backfill_task_values_2.sql | 47 + server/sql/07_simulate_monthly_review.sql | 78 + server/sql/08_fix_review_result.sql | 31 + server/src/config/database.ts | 48 + server/src/index.ts | 42 + server/src/middleware/auth.ts | 63 + server/src/middleware/error.ts | 43 + server/src/routes/auth.ts | 36 + server/src/routes/data.ts | 376 ++ server/src/routes/tasks.ts | 571 +++ server/src/types/index.ts | 154 + server/tsconfig.json | 16 + shared/types.ts | 154 + task.md | 317 ++ 技术架构方案与实施计划.md | 521 +++ 连锁餐饮企业数字化运营管理体系.md | 701 ++++ 67 files changed, 14391 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client/index.html create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/postcss.config.js create mode 100644 client/src/App.tsx create mode 100644 client/src/components/Badge.tsx create mode 100644 client/src/components/CollapsibleSection.tsx create mode 100644 client/src/components/DataTable.tsx create mode 100644 client/src/components/Layout.tsx create mode 100644 client/src/components/LoadingSpinner.tsx create mode 100644 client/src/components/MetricCard.tsx create mode 100644 client/src/components/Pagination.tsx create mode 100644 client/src/components/Skeleton.tsx create mode 100644 client/src/index.css create mode 100644 client/src/lib/api.ts create mode 100644 client/src/lib/utils.ts create mode 100644 client/src/main.tsx create mode 100644 client/src/pages/CostPage.tsx create mode 100644 client/src/pages/DashboardPage.tsx create mode 100644 client/src/pages/DataQualityPage.tsx create mode 100644 client/src/pages/IndicatorsPage.tsx create mode 100644 client/src/pages/LoginPage.tsx create mode 100644 client/src/pages/MemberPage.tsx create mode 100644 client/src/pages/MonthlyReviewPage.tsx create mode 100644 client/src/pages/OntologyPage.tsx create mode 100644 client/src/pages/PlatformPage.tsx create mode 100644 client/src/pages/RegionalPage.tsx create mode 100644 client/src/pages/RiskPage.tsx create mode 100644 client/src/pages/SKUPage.tsx create mode 100644 client/src/pages/StoreDetailPage.tsx create mode 100644 client/src/pages/StorePage.tsx create mode 100644 client/src/pages/TaskDetailPage.tsx create mode 100644 client/src/pages/TasksPage.tsx create mode 100644 client/src/pages/TimePage.tsx create mode 100644 client/tailwind.config.ts create mode 100644 client/tsconfig.json create mode 100644 client/tsconfig.node.json create mode 100644 client/vite.config.ts create mode 100644 db/ontology_standard.sql create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 run.md create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/sql/01_create_tables.sql create mode 100644 server/sql/02_create_functions.sql create mode 100644 server/sql/03_create_views.sql create mode 100644 server/sql/04_init_data.sql create mode 100644 server/sql/05_backfill_task_values.sql create mode 100644 server/sql/06_backfill_task_values_2.sql create mode 100644 server/sql/07_simulate_monthly_review.sql create mode 100644 server/sql/08_fix_review_result.sql create mode 100644 server/src/config/database.ts create mode 100644 server/src/index.ts create mode 100644 server/src/middleware/auth.ts create mode 100644 server/src/middleware/error.ts create mode 100644 server/src/routes/auth.ts create mode 100644 server/src/routes/data.ts create mode 100644 server/src/routes/tasks.ts create mode 100644 server/src/types/index.ts create mode 100644 server/tsconfig.json create mode 100644 shared/types.ts create mode 100644 task.md create mode 100644 技术架构方案与实施计划.md create mode 100644 连锁餐饮企业数字化运营管理体系.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30606c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +.env +*.log +.DS_Store +*.png +马兰拉面数据分析/ +*.xlsx +*.zip +.playwright-mcp/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..52a6575 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# 马兰拉面数字化运营管理平台 + +连锁餐饮数字化运营管理系统 — 任务闭环驱动门店改善 + +## 闭环目标 + +数据发现问题 → 自动分级 → 生成门店任务 → 现场执行 → 周度检查 → 月度验收 → 有效经验标准化 + +## 技术栈 + +- **后端**: Node.js + Express + TypeScript + PostgreSQL (pg) +- **前端**: React 18 + TypeScript + Vite + TailwindCSS + Recharts + React Query +- **认证**: JWT (角色: hq / regional / store / dept) +- **数据库**: PostgreSQL 15, `bill_query` @ localhost:5432 + +## 项目结构 + +``` +SBrainCO/ +├── package.json # monorepo 根配置 +├── server/ # 后端 +│ ├── src/ +│ │ ├── config/database.ts +│ │ ├── middleware/auth.ts, error.ts +│ │ ├── routes/data.ts, tasks.ts, auth.ts +│ │ ├── types/index.ts +│ │ └── index.ts +│ ├── sql/ +│ │ ├── 01_create_tables.sql +│ │ ├── 02_create_functions.sql +│ │ ├── 03_create_views.sql +│ │ └── 04_init_data.sql +│ └── .env +├── client/ # 前端 +│ ├── src/ +│ │ ├── components/ # Layout, MetricCard, Badge, DataTable +│ │ ├── pages/ # Dashboard, Tasks, StoreDetail, MonthlyReview, Indicators, Regional, Store, Login +│ │ ├── lib/ # api.ts, utils.ts +│ │ ├── App.tsx +│ │ └── main.tsx +│ └── vite.config.ts +└── shared/types.ts +``` + +## 快速启动 + +### 1. 安装依赖 + +```bash +# 根目录安装 +npm run install:all +``` + +### 2. 初始化数据库 + +```bash +# 按顺序执行SQL脚本 +psql -d bill_query -f server/sql/01_create_tables.sql +psql -d bill_query -f server/sql/02_create_functions.sql +psql -d bill_query -f server/sql/03_create_views.sql +psql -d bill_query -f server/sql/04_init_data.sql +``` + +### 3. 启动开发服务器 + +```bash +# 根目录一键启动 +npm run dev + +# 或分别启动 +cd server && npm run dev # 后端 http://localhost:3001 +cd client && npm run dev # 前端 http://localhost:5173 +``` + +### 4. 访问系统 + +- 前端: http://localhost:5173 +- 后端API: http://localhost:3001/api +- 默认账号: + - 总部管理员 / 123 (hq) + - 区域经理 / 123 (regional) + - 潘家园店长 / 123 (store) + - 商品部 / 123 (dept) + +## API 接口 + +### 数据查询 (25个) +- `GET /api/overview` — 公司概览 +- `GET /api/overview/daily` — 日度趋势 +- `GET /api/stores` — 门店列表 +- `GET /api/stores/:code` — 门店详情 +- `GET /api/stores/risk` — 风险分级 +- `GET /api/stores/priority` — 整改优先级 +- `GET /api/stores/quadrant` — 经营象限 +- `GET /api/cost/*` — 成本分析 +- `GET /api/platform/economics` — 平台经济性 +- `GET /api/member/*` — 会员分析 +- `GET /api/sku/*` — SKU分析 +- `GET /api/risk/*` — 风险分析 +- `GET /api/time/*` — 时间规律 +- `GET /api/channel` — 渠道结构 + +### 任务闭环 (23个) +- `GET /api/tasks` — 任务列表 +- `GET /api/tasks/:id` — 任务详情 +- `POST /api/tasks` — 创建任务 +- `PUT /api/tasks/:id` — 更新任务 +- `PUT /api/tasks/:id/execute` — 执行反馈 +- `PUT /api/tasks/:id/weekly-check` — 周度检查 +- `PUT /api/tasks/:id/verify` — 月度验收 +- `POST /api/tasks/:id/rollback` — 任务回滚 +- `POST /api/tasks/auto-generate` — 自动生成任务 +- `GET /api/tasks/loop-health` — 闭环健康度 +- `GET|POST /api/tasks/practices` — 经验标准化 +- `GET|POST /api/tasks/indicators` — 指标字典 +- `GET /api/tasks/stores/:code/daily-card` — 店长日卡 + +## 闭环健康度 + +| 指标 | 当前值 | 目标 | +|---|---|---| +| 任务生成率 | 100% | ≥95% | +| 店长执行率 | 0% | ≥80% | +| 区域周检率 | 0% | ≥80% | +| 月度验收率 | 0% | ≥80% | +| 经验推广率 | 0% | ≥80% | diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..0d2350c --- /dev/null +++ b/client/index.html @@ -0,0 +1,12 @@ + + + + + + 马兰拉面数字化运营管理平台 + + +
+ + + diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 0000000..1575ca1 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,3487 @@ +{ + "name": "sbrain-client", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sbrain-client", + "version": "1.0.0", + "dependencies": { + "@tanstack/react-query": "^5.51.1", + "axios": "^1.7.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "lucide-react": "^0.408.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.24.1", + "recharts": "^2.12.7", + "tailwind-merge": "^2.4.0" + }, + "devDependencies": { + "@types/node": "^26.1.1", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.39", + "tailwindcss": "^3.4.6", + "typescript": "^5.5.3", + "vite": "^5.3.4" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "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-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "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/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@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", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "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.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmmirror.com/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.4", + "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.3", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.3.tgz", + "integrity": "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.396", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "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-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.408.0", + "resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.408.0.tgz", + "integrity": "sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "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/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..f310162 --- /dev/null +++ b/client/package.json @@ -0,0 +1,34 @@ +{ + "name": "sbrain-client", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.51.1", + "axios": "^1.7.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "lucide-react": "^0.408.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.24.1", + "recharts": "^2.12.7", + "tailwind-merge": "^2.4.0" + }, + "devDependencies": { + "@types/node": "^26.1.1", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "postcss": "^8.4.39", + "tailwindcss": "^3.4.6", + "typescript": "^5.5.3", + "vite": "^5.3.4" + } +} diff --git a/client/postcss.config.js b/client/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/client/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..6a4467e --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,88 @@ +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { useState, useEffect, useCallback } from 'react' +import { Layout } from '@/components/Layout' +import { DashboardPage } from '@/pages/DashboardPage' +import { TasksPage } from '@/pages/TasksPage' +import { TaskDetailPage } from '@/pages/TaskDetailPage' +import { StoreDetailPage } from '@/pages/StoreDetailPage' +import { MonthlyReviewPage } from '@/pages/MonthlyReviewPage' +import { IndicatorsPage } from '@/pages/IndicatorsPage' +import { RegionalPage } from '@/pages/RegionalPage' +import { StorePage } from '@/pages/StorePage' +import { SKUPage } from '@/pages/SKUPage' +import { CostPage } from '@/pages/CostPage' +import { PlatformPage } from '@/pages/PlatformPage' +import { MemberPage } from '@/pages/MemberPage' +import { RiskPage } from '@/pages/RiskPage' +import { TimePage } from '@/pages/TimePage' +import { DataQualityPage } from '@/pages/DataQualityPage' +import { OntologyPage } from '@/pages/OntologyPage' +import { LoginPage } from '@/pages/LoginPage' + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { refetchOnWindowFocus: false, retry: 1 }, + }, +}) + +export default function App() { + const [user, setUser] = useState(null) + const [ready, setReady] = useState(false) + + useEffect(() => { + const stored = localStorage.getItem('user') + if (stored) setUser(JSON.parse(stored)) + setReady(true) + }, []) + + const handleLogout = useCallback(() => { + localStorage.removeItem('token') + localStorage.removeItem('user') + setUser(null) + }, []) + + const handleLogin = useCallback((u: any) => { + setUser(u) + }, []) + + if (!ready) return null + + const token = localStorage.getItem('token') + + return ( + + + {!token ? ( + + } /> + } /> + + ) : ( + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + )} + + + ) +} diff --git a/client/src/components/Badge.tsx b/client/src/components/Badge.tsx new file mode 100644 index 0000000..205dbfc --- /dev/null +++ b/client/src/components/Badge.tsx @@ -0,0 +1,25 @@ +import { cn, priorityColor, riskColor, statusColor, reviewResultColor } from '@/lib/utils' + +interface BadgeProps { + text: string + type?: 'priority' | 'risk' | 'status' | 'review' | 'default' + className?: string +} + +export function Badge({ text, type = 'default', className }: BadgeProps) { + const colorClass = type === 'priority' + ? priorityColor(text) + : type === 'risk' + ? riskColor(text) + : type === 'status' + ? statusColor(text) + : type === 'review' + ? reviewResultColor(text) + : 'bg-gray-100 text-gray-700 border border-gray-300' + + return ( + + {text} + + ) +} diff --git a/client/src/components/CollapsibleSection.tsx b/client/src/components/CollapsibleSection.tsx new file mode 100644 index 0000000..49f0cd1 --- /dev/null +++ b/client/src/components/CollapsibleSection.tsx @@ -0,0 +1,33 @@ +import { useState, type ReactNode } from 'react' +import { cn } from '@/lib/utils' + +interface CollapsibleSectionProps { + title: string + subtitle?: string + defaultOpen?: boolean + headerRight?: ReactNode + children: ReactNode +} + +export function CollapsibleSection({ title, subtitle, defaultOpen = true, headerRight, children }: CollapsibleSectionProps) { + const [open, setOpen] = useState(defaultOpen) + + return ( +
+ + {open &&
{children}
} +
+ ) +} diff --git a/client/src/components/DataTable.tsx b/client/src/components/DataTable.tsx new file mode 100644 index 0000000..62bfa30 --- /dev/null +++ b/client/src/components/DataTable.tsx @@ -0,0 +1,68 @@ +import { ReactNode } from 'react' +import { cn } from '@/lib/utils' + +interface DataTableProps { + columns: { key: string; label: string; align?: 'left' | 'right' | 'center'; render?: (row: any) => ReactNode }[] + data: any[] + onRowClick?: (row: any) => void + className?: string +} + +export function DataTable({ columns, data, onRowClick, className }: DataTableProps) { + return ( +
+ + + + {columns.map((col) => ( + + ))} + + + + {data.length === 0 ? ( + + + + ) : ( + data.map((row, i) => ( + onRowClick?.(row)} + className={cn( + 'border-t hover:bg-muted/30', + onRowClick && 'cursor-pointer' + )} + > + {columns.map((col) => ( + + ))} + + )) + )} + +
+ {col.label} +
+ 暂无数据 +
+ {col.render ? col.render(row) : row[col.key] ?? '-'} +
+
+ ) +} diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx new file mode 100644 index 0000000..72867a5 --- /dev/null +++ b/client/src/components/Layout.tsx @@ -0,0 +1,143 @@ +import { ReactNode, useState } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { LayoutDashboard, Store, ClipboardList, TrendingUp, Settings, LogOut, Menu, X, Package, DollarSign, ShoppingBag, Users, AlertTriangle, Clock, Database } from 'lucide-react' +import { cn } from '@/lib/utils' + +interface LayoutProps { + children: ReactNode + user: { name: string; role: string } | null + onLogout: () => void +} + +interface MenuItem { + path: string + label: string + icon: any + roles: string[] +} + +interface MenuGroup { + title: string + items: MenuItem[] +} + +const menuGroups: MenuGroup[] = [ + { + title: '经营管理', + items: [ + { path: '/', label: '总部驾驶舱', icon: LayoutDashboard, roles: ['hq', 'dept', 'regional', 'store'] }, + { path: '/regional', label: '区域经理', icon: Store, roles: ['hq', 'dept', 'regional', 'store'] }, + { path: '/store', label: '店长工作台', icon: ClipboardList, roles: ['hq', 'dept', 'regional', 'store'] }, + { path: '/tasks', label: '任务管理', icon: ClipboardList, roles: ['hq', 'regional', 'store', 'dept'] }, + { path: '/monthly-review', label: '月度验收', icon: TrendingUp, roles: ['hq', 'dept', 'regional', 'store'] }, + ], + }, + { + title: '业务模块', + items: [ + { path: '/sku', label: '商品SKU', icon: Package, roles: ['hq', 'dept'] }, + { path: '/cost', label: '成本库存', icon: DollarSign, roles: ['hq', 'dept'] }, + { path: '/platform', label: '平台优惠', icon: ShoppingBag, roles: ['hq', 'dept'] }, + { path: '/member', label: '会员复购', icon: Users, roles: ['hq', 'dept'] }, + { path: '/risk', label: '风险内控', icon: AlertTriangle, roles: ['hq', 'dept'] }, + { path: '/time', label: '时间分析', icon: Clock, roles: ['hq', 'dept'] }, + ], + }, + { + title: '系统管理', + items: [ + { path: '/data-quality', label: '数据质量', icon: Database, roles: ['hq', 'dept'] }, + { path: '/indicators', label: '指标字典', icon: Settings, roles: ['hq', 'dept', 'regional', 'store'] }, + { path: '/ontology', label: '本体标准', icon: Database, roles: ['hq', 'dept'] }, + ], + }, +] + +export function Layout({ children, user, onLogout }: LayoutProps) { + const location = useLocation() + const [sidebarOpen, setSidebarOpen] = useState(false) + + const visibleGroups = menuGroups.map(g => ({ + ...g, + items: g.items.filter((m) => !user || m.roles.includes(user.role)), + })).filter(g => g.items.length > 0) + + return ( +
+ {/* Mobile sidebar toggle */} + + + {/* Sidebar */} + + + {/* Main content */} +
+
+ {children} +
+
+ + {/* Overlay for mobile */} + {sidebarOpen && ( +
setSidebarOpen(false)} + /> + )} +
+ ) +} diff --git a/client/src/components/LoadingSpinner.tsx b/client/src/components/LoadingSpinner.tsx new file mode 100644 index 0000000..b237760 --- /dev/null +++ b/client/src/components/LoadingSpinner.tsx @@ -0,0 +1,10 @@ +import { Loader2 } from 'lucide-react' + +export function LoadingSpinner({ text = '加载中...' }: { text?: string }) { + return ( +
+ +

{text}

+
+ ) +} diff --git a/client/src/components/MetricCard.tsx b/client/src/components/MetricCard.tsx new file mode 100644 index 0000000..1d477db --- /dev/null +++ b/client/src/components/MetricCard.tsx @@ -0,0 +1,44 @@ +import { cn, formatNumber } from '@/lib/utils' + +interface MetricCardProps { + title: string + value: string | number | null | undefined + unit?: string + format?: 'number' | 'percent' | 'currency' + trend?: number + description?: string + className?: string +} + +export function MetricCard({ title, value, unit, format = 'number', trend, description, className }: MetricCardProps) { + const displayValue = format === 'percent' + ? value === null || value === undefined ? '-' : `${Number(value).toFixed(2)}%` + : format === 'currency' + ? value === null || value === undefined ? '-' : `¥${formatNumber(value)}` + : formatNumber(value) + + return ( +
+
+

{title}

+ {description && ( + + ? + + {description} + + + )} +
+
+ {displayValue} + {unit && {unit}} +
+ {trend !== undefined && ( +

0 ? 'text-green-600' : 'text-red-600')}> + {trend > 0 ? '↑' : '↓'} {Math.abs(trend).toFixed(1)}% 环比上周 +

+ )} +
+ ) +} diff --git a/client/src/components/Pagination.tsx b/client/src/components/Pagination.tsx new file mode 100644 index 0000000..fd32788 --- /dev/null +++ b/client/src/components/Pagination.tsx @@ -0,0 +1,76 @@ +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react' + +interface PaginationProps { + page: number + pageSize: number + total: number + onPageChange: (page: number) => void +} + +export function Pagination({ page, pageSize, total, onPageChange }: PaginationProps) { + const totalPages = Math.ceil(total / pageSize) + if (totalPages <= 1) return null + + const start = (page - 1) * pageSize + 1 + const end = Math.min(page * pageSize, total) + + return ( +
+ + 第 {start}-{end} 条,共 {total} 条 + +
+ + + {getPageNumbers(page, totalPages).map((p, idx) => + p === '...' ? ( + ... + ) : ( + + ) + )} + + +
+
+ ) +} + +function getPageNumbers(current: number, total: number): (number | string)[] { + if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1) + if (current <= 4) return [1, 2, 3, 4, 5, '...', total] + if (current >= total - 3) return [1, '...', total - 4, total - 3, total - 2, total - 1, total] + return [1, '...', current - 1, current, current + 1, '...', total] +} diff --git a/client/src/components/Skeleton.tsx b/client/src/components/Skeleton.tsx new file mode 100644 index 0000000..d05beda --- /dev/null +++ b/client/src/components/Skeleton.tsx @@ -0,0 +1,37 @@ +import { cn } from '@/lib/utils' + +export function Skeleton({ className, style }: { className?: string; style?: React.CSSProperties }) { + return
+} + +export function CardSkeleton() { + return ( +
+ + + +
+ ) +} + +export function ChartSkeleton({ height = 250 }: { height?: number }) { + return ( +
+ + +
+ ) +} + +export function TableSkeleton({ rows = 5 }: { rows?: number }) { + return ( +
+ +
+ {Array.from({ length: rows }).map((_, i) => ( + + ))} +
+
+ ) +} diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..60eba4c --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,56 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + } +} + +* { + border-color: hsl(var(--border)); +} + +body { + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; +} diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts new file mode 100644 index 0000000..d1b486b --- /dev/null +++ b/client/src/lib/api.ts @@ -0,0 +1,27 @@ +import axios from 'axios' + +const api = axios.create({ + baseURL: '/api', + timeout: 30000, +}) + +api.interceptors.request.use((config) => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +api.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('token') + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +export default api diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts new file mode 100644 index 0000000..524b3a5 --- /dev/null +++ b/client/src/lib/utils.ts @@ -0,0 +1,56 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +export function formatNumber(n: number | string | null | undefined): string { + if (n === null || n === undefined || n === '') return '-' + const num = typeof n === 'string' ? parseFloat(n) : n + if (isNaN(num)) return '-' + return num.toLocaleString('zh-CN', { maximumFractionDigits: 2 }) +} + +export function formatPercent(n: number | string | null | undefined): string { + if (n === null || n === undefined || n === '') return '-' + const num = typeof n === 'string' ? parseFloat(n) : n + if (isNaN(num)) return '-' + return `${num.toFixed(2)}%` +} + +export function formatCurrency(n: number | string | null | undefined): string { + if (n === null || n === undefined || n === '') return '-' + const num = typeof n === 'string' ? parseFloat(n) : n + if (isNaN(num)) return '-' + return `¥${num.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}` +} + +export function priorityColor(priority: string): string { + if (priority?.startsWith('P0')) return 'bg-red-100 text-red-700 border-red-300' + if (priority?.startsWith('P1')) return 'bg-orange-100 text-orange-700 border-orange-300' + if (priority?.startsWith('P2')) return 'bg-yellow-100 text-yellow-700 border-yellow-300' + if (priority?.includes('标杆')) return 'bg-green-100 text-green-700 border-green-300' + return 'bg-gray-100 text-gray-700 border-gray-300' +} + +export function riskColor(level: string): string { + if (level === '红色') return 'bg-red-500 text-white' + if (level === '黄色') return 'bg-yellow-500 text-white' + return 'bg-green-500 text-white' +} + +export function statusColor(status: string): string { + if (status === '待启动') return 'bg-gray-100 text-gray-600' + if (status === '进行中') return 'bg-blue-100 text-blue-700' + if (status === '已验收') return 'bg-green-100 text-green-700' + if (status === '已回滚') return 'bg-red-100 text-red-700' + return 'bg-gray-100 text-gray-600' +} + +export function reviewResultColor(result: string): string { + if (result === '达标') return 'bg-green-100 text-green-700 border border-green-300' + if (result === '改善中') return 'bg-yellow-100 text-yellow-700 border border-yellow-300' + if (result === '未改善') return 'bg-red-100 text-red-700 border border-red-300' + return 'bg-gray-100 text-gray-600 border border-gray-300' +} diff --git a/client/src/main.tsx b/client/src/main.tsx new file mode 100644 index 0000000..4a1b150 --- /dev/null +++ b/client/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +) diff --git a/client/src/pages/CostPage.tsx b/client/src/pages/CostPage.tsx new file mode 100644 index 0000000..9c1ecf7 --- /dev/null +++ b/client/src/pages/CostPage.tsx @@ -0,0 +1,200 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { MetricCard } from '@/components/MetricCard' +import { Badge } from '@/components/Badge' +import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' +import { useState, useMemo } from 'react' +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ScatterChart, Scatter } from 'recharts' + +const PAGE_SIZE = 15 + +export function CostPage() { + const [costPage, setCostPage] = useState(1) + const [invPage, setInvPage] = useState(1) + const [catPage, setCatPage] = useState(1) + + const { data: costData, isLoading: costLoading } = useQuery({ + queryKey: ['cost/comparison'], + queryFn: () => api.get('/cost/comparison'), + }) + + const { data: invData, isLoading: invLoading } = useQuery({ + queryKey: ['cost/inventory'], + queryFn: () => api.get('/cost/inventory'), + }) + + const { data: catData, isLoading: catLoading } = useQuery({ + queryKey: ['cost/category-benchmark'], + queryFn: () => api.get('/cost/category-benchmark'), + }) + + const costRows = (costData as any)?.data || [] + const invRows = (invData as any)?.data || [] + const catRows = (catData as any)?.data || [] + + const pageLoading = costLoading || invLoading || catLoading + + const validCostRows = costRows.filter((r: any) => !r.variance_level?.includes('口径异常')) + const abnormalRows = costRows.filter((r: any) => r.variance_level?.includes('口径异常')) + + const avgTheoreticalRate = validCostRows.length > 0 + ? validCostRows.reduce((s: number, r: any) => s + Number(r.theoretical_cost_rate_pct || 0), 0) / validCostRows.length + : 0 + const avgActualRate = validCostRows.length > 0 + ? validCostRows.reduce((s: number, r: any) => s + Number(r.actual_food_cost_rate_pct || 0), 0) / validCostRows.length + : 0 + const highVarianceCount = validCostRows.filter((r: any) => Number(r.variance_to_theoretical_pct || 0) > 20).length + const abnormalInvCount = invRows.filter((r: any) => Number(r.estimated_inventory_days) > 7 || Number(r.negative_item_lines) > 0).length + + const pagedCost = useMemo(() => costRows.slice((costPage - 1) * PAGE_SIZE, costPage * PAGE_SIZE), [costRows, costPage]) + const pagedInv = useMemo(() => invRows.slice((invPage - 1) * PAGE_SIZE, invPage * PAGE_SIZE), [invRows, invPage]) + const pagedCat = useMemo(() => catRows.slice((catPage - 1) * PAGE_SIZE, catPage * PAGE_SIZE), [catRows, catPage]) + + const scatterData = validCostRows.map((r: any) => ({ + name: r.store_name, + theoretical: Number(r.theoretical_cost_rate_pct || 0), + actual: Number(r.actual_food_cost_rate_pct || 0), + variance: Number(r.variance_to_theoretical_pct || 0), + })) + + if (pageLoading) { + return + } + + return ( +
+
+

成本与库存管理

+

理论vs实际成本 · 原料分类差异 · 库存效率 · 2026年4月

+
+ + {/* 概览指标 */} + +
+ + + + +
+ {abnormalRows.length > 0 && ( +
+

+ 口径异常门店({abnormalRows.length}家): + {abnormalRows.map((r: any) => r.store_name).join('、')} + — 理论成本数据缺失或口径不一致,需先修复数据再纳入对比 +

+
+ )} +
+ + {/* 成本散点图 */} + + + + + + + { + if (active && payload && payload.length) { + const d = payload[0].payload + return ( +
+

{d.name}

+

理论: {d.theoretical.toFixed(1)}%

+

实际: {d.actual.toFixed(1)}%

+

偏差: {d.variance > 0 ? '+' : ''}{d.variance.toFixed(1)}%

+
+ ) + } + return null + }} + /> + +
+
+
+ + {/* 成本对比表 */} + + +
+ formatPercent(r.theoretical_cost_rate_pct) }, + { key: 'actual_food_cost_rate_pct', label: '实际成本率', align: 'right', render: (r) => formatPercent(r.actual_food_cost_rate_pct) }, + { key: 'variance_to_theoretical_pct', label: '偏差', align: 'right', render: (r) => { + const v = Number(r.variance_to_theoretical_pct || 0) + return 20 ? 'font-medium text-red-600' : v > 10 ? 'text-yellow-600' : 'text-green-600'}>{v > 0 ? '+' : ''}{v.toFixed(1)}% + }}, + { key: 'variance_level', label: '偏差等级', render: (r) => ( + {r.variance_level || '-'} + )}, + { key: 'actual_food_cost', label: '实际食材成本', align: 'right', render: (r) => formatCurrency(r.actual_food_cost) }, + ]} + data={pagedCost} + /> +
+
+ + {/* 库存效率 */} + + +
+ { + const d = Number(r.estimated_inventory_days || 0) + return 7 ? 'font-medium text-red-600' : d > 4 ? 'text-yellow-600' : 'text-green-600'}>{d.toFixed(1)}天 + }}, + { key: 'ending_inventory_amount', label: '期末库存', align: 'right', render: (r) => formatCurrency(r.ending_inventory_amount) }, + { key: 'negative_item_lines', label: '负耗用行数', align: 'center', render: (r) => { + const n = Number(r.negative_item_lines || 0) + return n > 0 ? {n} : 0 + }}, + { key: 'abnormal_item_lines', label: '异常货品行', align: 'center', render: (r) => { + const n = Number(r.abnormal_item_lines || 0) + return n > 0 ? {n} : 0 + }}, + ]} + data={pagedInv} + /> +
+
+ + {/* 原料分类差异 */} + + +
+ formatCurrency(r.actual_category_cost) }, + { key: 'cost_per_10k_sales', label: '每万元成本', align: 'right', render: (r) => formatCurrency(r.cost_per_10k_sales) }, + { key: 'peer_median_cost_per_10k', label: '同业中位', align: 'right', render: (r) => formatCurrency(r.peer_median_cost_per_10k) }, + { key: 'excess_vs_peer_per_10k', label: '超出同业', align: 'right', render: (r) => { + const v = Number(r.excess_vs_peer_per_10k || 0) + return v > 0 ? +{formatCurrency(v)} : {formatCurrency(v)} + }}, + ]} + data={pagedCat} + /> +
+
+
+ ) +} diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..3bb4642 --- /dev/null +++ b/client/src/pages/DashboardPage.tsx @@ -0,0 +1,246 @@ +import { useQuery } from '@tanstack/react-query' +import { useNavigate } from 'react-router-dom' +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar, PieChart, Pie, Cell, Legend } from 'recharts' +import api from '@/lib/api' +import { MetricCard } from '@/components/MetricCard' +import { Badge } from '@/components/Badge' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { formatNumber, formatCurrency, formatPercent } from '@/lib/utils' +import { useState, useMemo } from 'react' + +const RISK_COLORS = { '红色': '#ef4444', '黄色': '#eab308', '绿色': '#22c55e' } +const PAGE_SIZE = 10 + +export function DashboardPage() { + const navigate = useNavigate() + const [priorityPage, setPriorityPage] = useState(1) + + const { data: overview, isLoading: odLoading } = useQuery({ + queryKey: ['overview'], + queryFn: () => api.get('/overview'), + }) + + const { data: daily, isLoading: dailyLoading } = useQuery({ + queryKey: ['overview/daily'], + queryFn: () => api.get('/overview/daily'), + }) + + const { data: riskData, isLoading: riskLoading } = useQuery({ + queryKey: ['stores/risk'], + queryFn: () => api.get('/stores/risk'), + }) + + const { data: priorityData, isLoading: priorityLoading } = useQuery({ + queryKey: ['stores/priority'], + queryFn: () => api.get('/stores/priority'), + }) + + const { data: loopHealth, isLoading: loopLoading } = useQuery({ + queryKey: ['loop-health'], + queryFn: () => api.get('/tasks/loop-health'), + }) + + const pageLoading = odLoading || dailyLoading || riskLoading || priorityLoading || loopLoading + + const od = (overview as any)?.data + const riskRows = (riskData as any)?.data || [] + const priorityRows = (priorityData as any)?.data || [] + const dailyRows = (daily as any)?.data || [] + const lh = (loopHealth as any)?.data + + const riskSummary = riskRows.reduce((acc: any, r: any) => { + acc[r.risk_level] = (acc[r.risk_level] || 0) + 1 + return acc + }, {}) + + const totalStores = riskRows.length + const riskPieData = Object.entries(riskSummary).map(([name, value]) => ({ + name, + value: value as number, + pct: totalStores > 0 ? Math.round((value as number / totalStores) * 100) : 0, + })) + + const prioritySummary = priorityRows.reduce((acc: any, r: any) => { + const key = r.action_priority + acc[key] = (acc[key] || 0) + 1 + return acc + }, {}) + + const p0p1Stores = priorityRows.filter((s: any) => s.action_priority?.startsWith('P0') || s.action_priority?.startsWith('P1')) + const pagedP0P1 = useMemo(() => p0p1Stores.slice((priorityPage - 1) * PAGE_SIZE, priorityPage * PAGE_SIZE), [p0p1Stores, priorityPage]) + + // 闭环健康度综合得分 + const loopScore = lh ? ( + (parseFloat(lh.task_generation_rate) + + parseFloat(lh.store_execution_rate) + + parseFloat(lh.weekly_check_rate) + + parseFloat(lh.monthly_review_rate) + + parseFloat(lh.practice_promotion_rate)) / 5 + ) : 0 + + // 计算环比趋势:最后7天 vs 前7天 + const last7 = dailyRows.slice(-7) + const prev7 = dailyRows.slice(-14, -7) + const sumKey = (arr: any[], key: string) => arr.reduce((s, r) => s + Number(r[key] || 0), 0) + const trend = (curr: number, prev: number) => prev > 0 ? Math.round((curr - prev) / prev * 1000) / 10 : 0 + + const trendReceived = trend(sumKey(last7, 'received'), sumKey(prev7, 'received')) + const trendBills = trend(sumKey(last7, 'bill_count'), sumKey(prev7, 'bill_count')) + const trendAvgBill = trend( + last7.length > 0 ? sumKey(last7, 'received') / sumKey(last7, 'bill_count') : 0, + prev7.length > 0 ? sumKey(prev7, 'received') / sumKey(prev7, 'bill_count') : 0, + ) + + if (pageLoading) { + return + } + + return ( +
+
+
+

总部驾驶舱

+

全部门店经营总览 · 2026年4月

+
+
+ {totalStores} 家门店 + 红色 {riskSummary['红色'] || 0} + 黄色 {riskSummary['黄色'] || 0} + 绿色 {riskSummary['绿色'] || 0} +
+
+ + {/* 经营指标 */} + +
+ + + + +
+
+ + {/* 运营指标 */} + +
+ + + +
+
+ + {/* 闭环健康度 */} + {lh && ( + + 综合得分 + = 80 ? 'text-green-600' : loopScore >= 60 ? 'text-yellow-600' : 'text-red-600'}`}> + {loopScore.toFixed(1)}% + +
+ } + > +
+ {[ + { label: '任务生成率', value: lh.task_generation_rate, desc: '系统自动生成的整改任务占应生成任务的比例' }, + { label: '店长执行率', value: lh.store_execution_rate, desc: '店长已提交反馈的任务占分配任务的比例' }, + { label: '区域周检率', value: lh.weekly_check_rate, desc: '区域经理完成周度检查的比例' }, + { label: '月度验收率', value: lh.monthly_review_rate, desc: '月度完成验收的任务比例' }, + { label: '经验推广率', value: lh.practice_promotion_rate, desc: '达标经验被推广到其他门店的比例' }, + ].map((item) => { + const num = parseFloat(item.value) + const isLow = num < 60 + const isMid = num >= 60 && num < 80 + return ( +
+
+

{item.label}

+ ? +
+

{num.toFixed(1)}%

+
+
+
+
+ ) + })} +
+ + )} + + {/* 图表区 */} +
+ + + + + v?.substring(5, 10)} tick={{ fontSize: 10 }} /> + v >= 10000 ? `${(v / 10000).toFixed(0)}万` : v} tick={{ fontSize: 10 }} /> + v?.substring(0, 10)} + formatter={(v: any) => formatCurrency(v)} + /> + + + + + + + + + `${name} ${value}家 (${pct}%)`} + > + {riskPieData.map((entry) => ( + + ))} + + + + + + +
+ + {/* P0/P1 门店列表 */} + + {Object.entries(prioritySummary).filter(([k]) => k?.startsWith('P0') || k?.startsWith('P1')).map(([k, v]) => ( + {k}: {v as number}家 + ))} +
+ } + > + +
+ }, + { key: 'problem_count', label: '问题数', align: 'center' }, + { key: 'problem_combination', label: '问题组合' }, + { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, + ]} + data={pagedP0P1} + onRowClick={(r) => navigate(`/stores/${r.store_code}`)} + /> +
+ +
+ ) +} diff --git a/client/src/pages/DataQualityPage.tsx b/client/src/pages/DataQualityPage.tsx new file mode 100644 index 0000000..f4ee285 --- /dev/null +++ b/client/src/pages/DataQualityPage.tsx @@ -0,0 +1,118 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { MetricCard } from '@/components/MetricCard' +import { formatNumber } from '@/lib/utils' + +export function DataQualityPage() { + const { data: qualityData, isLoading } = useQuery({ + queryKey: ['data-quality'], + queryFn: () => api.get('/data-quality'), + }) + + const dq = (qualityData as any)?.data || {} + + if (isLoading) { + return + } + + const totalBills = Number(dq.total_bills || 0) + const missingBillNo = Number(dq.missing_bill_no || 0) + const missingStoreCode = Number(dq.missing_store_code || 0) + const zeroConsumption = Number(dq.zero_consumption || 0) + const negativeReceived = Number(dq.negative_received || 0) + const storeCount = Number(dq.store_count || 0) + const totalDishRecords = Number(dq.total_dish_records || 0) + const dishMissingStore = Number(dq.dish_missing_store || 0) + const dishMissingDish = Number(dq.dish_missing_dish || 0) + + const billQualityStatus = missingBillNo === 0 && missingStoreCode === 0 ? '正常' : '异常' + const dishQualityStatus = dishMissingStore === 0 && dishMissingDish === 0 ? '正常' : '异常' + + return ( +
+
+

数据质量看板

+

账单与菜品明细数据质量检查 · {dq.min_date?.substring(0, 10)} ~ {dq.max_date?.substring(0, 10)}

+
+ + {/* 账单数据质量 */} + +
+ + + + +
+
+ + +
+

账单质量状态

+

+ {billQualityStatus} +

+
+
+ {missingBillNo > 0 && ( +
+

+ 提示: + {missingBillNo} 条账单缺失账单号,可能导致重复统计或关联失败,需排查源文件 +

+
+ )} + {missingStoreCode > 0 && ( +
+

+ 提示: + {missingStoreCode} 条账单缺失门店编码,无法归属门店,需修复映射 +

+
+ )} +
+ + {/* 菜品明细数据质量 */} + +
+ + + +
+

菜品质量状态

+

+ {dishQualityStatus} +

+
+
+ {dishMissingStore > 0 && ( +
+

+ 提示: + {dishMissingStore} 条菜品明细缺失门店编码,无法进行门店级SKU分析 +

+
+ )} +
+ + {/* 数据治理建议 */} + +
+
+

统一编码

+

门店、SKU、原料、会员、员工、渠道、活动必须使用稳定编码。名称只能用于展示,不能作为唯一关联键

+
+
+

每次导入自动检查

+

文件齐全性、日期完整性、行数金额对账、映射失败数量、重复账单、空值检查、异常金额、账单与明细对账、销售与成本单位匹配

+
+
+

口径变更管理

+

口径变更必须保留版本和生效日期,不能直接覆盖历史结果。月报出具后如需更正,应记录更正原因和影响金额

+
+
+
+
+ ) +} diff --git a/client/src/pages/IndicatorsPage.tsx b/client/src/pages/IndicatorsPage.tsx new file mode 100644 index 0000000..89a6772 --- /dev/null +++ b/client/src/pages/IndicatorsPage.tsx @@ -0,0 +1,38 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { useState, useMemo } from 'react' + +const PAGE_SIZE = 10 + +export function IndicatorsPage() { + const [page, setPage] = useState(1) + const { data } = useQuery({ + queryKey: ['indicators'], + queryFn: () => api.get('/tasks/indicators'), + }) + + const indicators = (data as any)?.data || [] + const pagedIndicators = useMemo(() => indicators.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [indicators, page]) + + return ( +
+

指标字典

+ + {r.business_definition} }, + { key: 'formula', label: '计算公式', render: (r) => {r.formula} }, + { key: 'update_frequency', label: '更新频率', align: 'center' }, + { key: 'owner', label: '负责人' }, + { key: 'yellow_threshold', label: '黄线', align: 'right' }, + { key: 'red_threshold', label: '红线', align: 'right' }, + { key: 'version_date', label: '版本日期', render: (r) => r.version_date?.substring(0, 10) }, + ]} + data={pagedIndicators} + /> +
+ ) +} diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx new file mode 100644 index 0000000..a3a5140 --- /dev/null +++ b/client/src/pages/LoginPage.tsx @@ -0,0 +1,64 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import api from '@/lib/api' + +interface LoginPageProps { + onLogin?: (user: any) => void +} + +export function LoginPage({ onLogin }: LoginPageProps) { + const navigate = useNavigate() + const [username, setUsername] = useState('总部管理员') + const [password, setPassword] = useState('123') + const [error, setError] = useState('') + + const handleLogin = async () => { + try { + const res: any = await api.post('/auth/login', { username, password }) + localStorage.setItem('token', res.data.token) + localStorage.setItem('user', JSON.stringify(res.data.user)) + onLogin?.(res.data.user) + navigate('/') + } catch { + setError('登录失败,请重试') + } + } + + return ( +
+
+

马兰拉面数字化运营平台

+
+
+ + setUsername(e.target.value)} + className="mt-1 w-full rounded-md border px-3 py-2 text-sm" + /> +
+
+ + setPassword(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleLogin()} + className="mt-1 w-full rounded-md border px-3 py-2 text-sm" + /> +
+ {error &&

{error}

} + +

+ 可选用户:总部管理员 / 区域经理 / 潘家园店长 / 商品部 +

+
+
+
+ ) +} diff --git a/client/src/pages/MemberPage.tsx b/client/src/pages/MemberPage.tsx new file mode 100644 index 0000000..48ceab2 --- /dev/null +++ b/client/src/pages/MemberPage.tsx @@ -0,0 +1,153 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { MetricCard } from '@/components/MetricCard' +import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' +import { useState, useMemo } from 'react' +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts' + +const PAGE_SIZE = 15 + +export function MemberPage() { + const [repeatPage, setRepeatPage] = useState(1) + + const { data: comparisonData, isLoading: cmpLoading } = useQuery({ + queryKey: ['member/comparison'], + queryFn: () => api.get('/member/comparison'), + }) + + const { data: repeatData, isLoading: repeatLoading } = useQuery({ + queryKey: ['member/repeat'], + queryFn: () => api.get('/member/repeat'), + }) + + const cmpRows = (comparisonData as any)?.data || [] + const repeatRows = (repeatData as any)?.data || [] + + const pageLoading = cmpLoading || repeatLoading + + const memberRow = cmpRows.find((r: any) => r.customer_type === '会员') || {} + const nonMemberRow = cmpRows.find((r: any) => r.customer_type === '非会员') || {} + + const totalBills = Number(memberRow.bill_count || 0) + Number(nonMemberRow.bill_count || 0) + const memberBillShare = totalBills > 0 ? (Number(memberRow.bill_count || 0) / totalBills * 100) : 0 + const totalReceived = Number(memberRow.received || 0) + Number(nonMemberRow.received || 0) + const memberRevenueShare = totalReceived > 0 ? (Number(memberRow.received || 0) / totalReceived * 100) : 0 + + const avgRepeatRate = repeatRows.length > 0 + ? repeatRows.reduce((s: number, r: any) => s + Number(r.repeat_rate_pct || 0), 0) / repeatRows.length + : 0 + const topRepeatStore = repeatRows.length > 0 + ? repeatRows.reduce((best: any, r: any) => Number(r.repeat_rate_pct || 0) > Number(best?.repeat_rate_pct || 0) ? r : best, null) + : null + const lowRepeatCount = repeatRows.filter((r: any) => Number(r.repeat_rate_pct || 0) < 30).length + + const cmpChartData = cmpRows.map((r: any) => ({ + name: r.customer_type, + 账单数: Number(r.bill_count || 0), + 实收: Number(r.received || 0), + 客单价: Number(r.avg_bill_value || 0), + })) + + const pieData = [ + { name: '会员账单', value: Number(memberRow.bill_count || 0) }, + { name: '非会员账单', value: Number(nonMemberRow.bill_count || 0) }, + ] + const PIE_COLORS = ['#3b82f6', '#e5e7eb'] + + const pagedRepeat = useMemo(() => repeatRows.slice((repeatPage - 1) * PAGE_SIZE, repeatPage * PAGE_SIZE), [repeatRows, repeatPage]) + + if (pageLoading) { + return + } + + return ( +
+
+

会员与复购管理

+

会员对比 · 复购率 · 沉睡分析 · 2026年4月

+
+ + {/* 概览指标 */} + +
+ + + + +
+
+ + {/* 会员对比图 */} +
+ + + + + + v >= 10000 ? `${(v / 10000).toFixed(0)}万` : v} tick={{ fontSize: 10 }} /> + + name === '实收' ? formatCurrency(v) : name === '客单价' ? formatCurrency(v) : formatNumber(v)} /> + + + + + + + + + + + + `${name}: ${formatNumber(value)}`}> + {pieData.map((_, i) => )} + + formatNumber(v)} /> + + + + +
+ + {/* 会员对比明细 */} + + formatNumber(r.bill_count) }, + { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, + { key: 'avg_bill_value', label: '客单价', align: 'right', render: (r) => formatCurrency(r.avg_bill_value) }, + { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => formatPercent(r.discount_rate_pct) }, + { key: 'theoretical_margin_pct', label: '理论毛利率', align: 'right', render: (r) => formatPercent(r.theoretical_margin_pct) }, + ]} + data={cmpRows} + /> + + + {/* 门店复购率 */} + + +
+ formatNumber(r.identified_members) }, + { key: 'repeat_members', label: '复购会员数', align: 'right', render: (r) => formatNumber(r.repeat_members) }, + { key: 'repeat_rate_pct', label: '复购率', align: 'right', render: (r) => { + const v = Number(r.repeat_rate_pct || 0) + return = 50 ? 'font-medium text-green-600' : v >= 30 ? 'text-yellow-600' : 'font-medium text-red-600'}>{v.toFixed(1)}% + }}, + { key: 'avg_orders', label: '人均消费次数', align: 'right', render: (r) => Number(r.avg_orders || 0).toFixed(2) }, + { key: 'repeat_revenue_share_pct', label: '复购收入占比', align: 'right', render: (r) => `${Number(r.repeat_revenue_share_pct || 0).toFixed(1)}%` }, + ]} + data={pagedRepeat} + /> +
+
+
+ ) +} diff --git a/client/src/pages/MonthlyReviewPage.tsx b/client/src/pages/MonthlyReviewPage.tsx new file mode 100644 index 0000000..d116b67 --- /dev/null +++ b/client/src/pages/MonthlyReviewPage.tsx @@ -0,0 +1,124 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts' +import { DataTable } from '@/components/DataTable' +import { Badge } from '@/components/Badge' +import { MetricCard } from '@/components/MetricCard' +import { Pagination } from '@/components/Pagination' +import { useState, useMemo } from 'react' + +const REVIEW_COLORS = { '达标': '#22c55e', '改善中': '#eab308', '未改善': '#ef4444' } +const PAGE_SIZE = 10 + +export function MonthlyReviewPage() { + const [month, setMonth] = useState('2026-05') + const [page, setPage] = useState(1) + + const { data } = useQuery({ + queryKey: ['monthly-review', month], + queryFn: () => api.get('/tasks/monthly-review', { params: { month } }), + }) + + const rd = (data as any)?.data + const summary = rd?.summary || { total: 0, passed: 0, improving: 0, failed: 0 } + const details = rd?.details || [] + const pagedDetails = useMemo(() => details.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [details, page]) + + const pieData = [ + { name: '达标', value: summary.passed }, + { name: '改善中', value: summary.improving }, + { name: '未改善', value: summary.failed }, + ].filter((d) => d.value > 0) + + return ( +
+
+

月度验收

+ setMonth(e.target.value)} + className="rounded-md border px-3 py-1.5 text-sm" + /> +
+ + {/* 汇总卡片 */} +
+ + + + +
+ +
+ {/* 饼图 */} +
+

验收结果分布

+ {pieData.length > 0 ? ( + + + `${name}: ${value}`}> + {pieData.map((d) => )} + + + + + ) :
暂无验收数据
} +
+ + {/* 经验标准化 */} +
+

经验标准化

+

达标门店的有效经验可标准化推广

+ +
+
+ + {/* 明细表 */} +
+

验收明细

+ +
+ }, + ]} + data={pagedDetails} + /> +
+
+
+ ) +} + +function PracticeSection() { + const { data } = useQuery({ + queryKey: ['practices'], + queryFn: () => api.get('/tasks/practices'), + }) + + const practices = (data as any)?.data || [] + + return ( +
+ {practices.length === 0 ? ( +

暂无标准化经验

+ ) : practices.map((p: any) => ( +
+
+ {p.practice_module} + +
+

标杆:{p.benchmark_store_name}

+

{p.key_actions}

+
+ ))} +
+ ) +} diff --git a/client/src/pages/OntologyPage.tsx b/client/src/pages/OntologyPage.tsx new file mode 100644 index 0000000..0152e43 --- /dev/null +++ b/client/src/pages/OntologyPage.tsx @@ -0,0 +1,393 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { DataTable } from '@/components/DataTable' +import { useState } from 'react' +import { Database, Table, Layers, Settings, ChevronRight, Box, ArrowRight } from 'lucide-react' +import { cn } from '@/lib/utils' + +type TabType = 'overview' | 'dimensions' | 'facts' | 'enums' | 'metrics' + +const TABLE_LABELS: Record = { + dim_store: '门店主数据', + dim_sku: 'SKU主数据', + dim_material: '原料主数据', + dim_member: '会员主数据', + dim_employee: '员工主数据', + dim_channel: '渠道主数据', + dim_promotion: '活动主数据', + dim_calendar: '日历主数据', + dim_supplier: '供应商主数据', + fact_bill: '账单事实表', + fact_bill_item: '账单明细事实表', + fact_payment: '支付事实表', + fact_promotion_usage: '优惠使用事实表', + fact_platform_order: '平台订单损益表', + fact_inventory_snapshot: '库存快照事实表', + fact_purchase_receipt: '采购收货事实表', + fact_transfer: '调拨事实表', + fact_waste: '报损事实表', + fact_recipe_bom: 'BOM配方事实表', + fact_employee_shift: '员工排班事实表', + fact_customer_contact: '会员触达事实表', + fact_store_task: '门店任务事实表', + enum_risk_level: '风险等级', + enum_priority: '任务优先级', + enum_task_status: '任务状态', + enum_verification_result: '验收结果', + enum_sku_type: 'SKU类型', + enum_sku_status: 'SKU状态', + enum_business_type: '业态类型', + enum_channel_group: '渠道分组', + enum_metric_direction: '指标方向', + enum_member_status: '会员状态', + enum_waste_reason: '报损原因', + enum_management_quadrant: '管理象限', +} + +const labelOf = (name: string) => TABLE_LABELS[name] || name + +export function OntologyPage() { + const [tab, setTab] = useState('overview') + const [selectedTable, setSelectedTable] = useState(null) + const [metricFilter, setMetricFilter] = useState('全部') + + const { data: overview, isLoading: overviewLoading } = useQuery({ + queryKey: ['ontology/overview'], + queryFn: () => api.get('/tasks/ontology/overview'), + enabled: tab === 'overview', + }) + + const { data: dimData, isLoading: dimLoading } = useQuery({ + queryKey: ['ontology/dim', selectedTable], + queryFn: () => api.get(`/tasks/ontology/dim/${selectedTable}`), + enabled: tab === 'dimensions' && !!selectedTable, + }) + + const { data: factData, isLoading: factLoading } = useQuery({ + queryKey: ['ontology/fact', selectedTable], + queryFn: () => api.get(`/tasks/ontology/fact/${selectedTable}`), + enabled: tab === 'facts' && !!selectedTable, + }) + + const { data: enumData, isLoading: enumLoading } = useQuery({ + queryKey: ['ontology/enum', selectedTable], + queryFn: () => api.get(`/tasks/ontology/enum/${selectedTable}`), + enabled: tab === 'enums' && !!selectedTable, + }) + + const { data: metricsData, isLoading: metricsLoading } = useQuery({ + queryKey: ['ontology/metrics'], + queryFn: () => api.get('/tasks/ontology/metrics'), + enabled: tab === 'metrics', + }) + + const ov = (overview as any)?.data + const dim = (dimData as any)?.data + const fact = (factData as any)?.data + const enumRows = (enumData as any)?.data || [] + const metrics = (metricsData as any)?.data || [] + + if (tab === 'overview' && overviewLoading) return + if (tab === 'dimensions' && dimLoading) return + if (tab === 'facts' && factLoading) return + if (tab === 'enums' && enumLoading) return + if (tab === 'metrics' && metricsLoading) return + + const tabs: { key: TabType; label: string; icon: any }[] = [ + { key: 'overview', label: '概览', icon: Database }, + { key: 'dimensions', label: '维度主数据', icon: Box }, + { key: 'facts', label: '事实表', icon: Table }, + { key: 'enums', label: '枚举标准', icon: Layers }, + { key: 'metrics', label: '指标标准', icon: Settings }, + ] + + return ( +
+
+

本体标准

+
+ + {/* Tab bar */} +
+ {tabs.map(t => { + const Icon = t.icon + return ( + + ) + })} +
+ + {/* Overview */} + {tab === 'overview' && ov && ( +
+
+ + + + +
+ +
+ { setTab('dimensions'); setSelectedTable(name) }} /> + { setTab('facts'); setSelectedTable(name) }} /> +
+ +
+ { setTab('enums'); setSelectedTable(name) }} /> +
+

指标标准概览

+
+
+ 总指标数 + {ov.metrics?.total || 0} +
+
+ 已标准化 + {ov.metrics?.standardized || 0} +
+
+ 待标准化 + {(ov.metrics?.total || 0) - (ov.metrics?.standardized || 0)} +
+
+ 已启用 + {ov.metrics?.active || 0} +
+ +
+
+
+
+ )} + + {/* Dimensions */} + {tab === 'dimensions' && ( +
+ {!selectedTable ? ( +
+ {ov?.dimensions?.map((t: any) => ( + setSelectedTable(t.table_name)} /> + ))} +
+ ) : ( +
+ +

{labelOf(selectedTable)} {selectedTable}

+
+ ({ + key: c.column_name, + label: c.column_name, + render: (r: any) => { + const val = r[c.column_name] + if (val === null || val === undefined) return + if (typeof val === 'object') return {JSON.stringify(val)} + if (c.data_type === 'timestamp with time zone' || c.data_type === 'timestamp without time zone') return {String(val).substring(0, 19)} + return {String(val)} + } + })) || []} + data={dim?.rows || []} + /> +
+
字段定义:
+
+ + + + + + + + + + + {dim?.columns?.map((c: any) => ( + + + + + + + ))} + +
字段名类型可空默认值
{c.column_name}{c.data_type}{c.is_nullable === 'YES' ? '✓' : }{c.column_default || '—'}
+
+
+ )} +
+ )} + + {/* Facts */} + {tab === 'facts' && ( +
+ {!selectedTable ? ( +
+ {ov?.facts?.map((t: any) => ( + setSelectedTable(t.table_name)} /> + ))} +
+ ) : ( +
+ +

{labelOf(selectedTable)} {selectedTable}

+
+ 字段数: {fact?.columns?.length || 0} + 数据量: {Number(fact?.total || 0).toLocaleString()} +
+
+ + + + + + + + + + + {fact?.columns?.map((c: any) => ( + + + + + + + ))} + +
字段名类型可空默认值
{c.column_name}{c.data_type}{c.is_nullable === 'YES' ? '✓' : }{c.column_default || '—'}
+
+
+ )} +
+ )} + + {/* Enums */} + {tab === 'enums' && ( +
+ {!selectedTable ? ( +
+ {ov?.enums?.map((t: any) => ( + setSelectedTable(t.table_name)} /> + ))} +
+ ) : ( +
+ +

{labelOf(selectedTable)} {selectedTable}

+
+ ({ key: k, label: k }))} + data={enumRows} + /> +
+
+ )} +
+ )} + + {/* Metrics */} + {tab === 'metrics' && ( +
+
+ {['全部', '收入', '成本', '优惠', '会员', '商品', '库存', '风险'].map(cat => ( + + ))} +
+
+ {r.metric_code || '—'} }, + { key: 'indicator_name', label: '指标名称', render: (r: any) => {r.indicator_name} }, + { key: 'metric_category', label: '分类', render: (r: any) => r.metric_category ? {r.metric_category} : }, + { key: 'business_definition', label: '业务定义', render: (r: any) => {r.business_definition} }, + { key: 'formula', label: '计算公式', render: (r: any) => {r.formula} }, + { key: 'unit', label: '单位', align: 'center' as const }, + { key: 'direction', label: '方向', render: (r: any) => r.direction === 'higher_better' ? ↑越高越好 : r.direction === 'lower_better' ? ↓越低越好 : '—' }, + { key: 'owner', label: '负责人', align: 'center' as const }, + { key: 'yellow_threshold', label: '黄线', align: 'right' as const, render: (r: any) => r.yellow_threshold ?? '—' }, + { key: 'red_threshold', label: '红线', align: 'right' as const, render: (r: any) => r.red_threshold ?? '—' }, + { key: 'update_frequency', label: '频率', align: 'center' as const }, + { key: 'version', label: '版本', render: (r: any) => {r.version || '—'} }, + ]} + data={metricFilter === '全部' ? metrics : metrics.filter((m: any) => m.metric_category === metricFilter)} + /> +
+
+ )} +
+ ) +} + +function StatCard({ title, value, unit, icon: Icon, color }: { title: string; value: number; unit: string; icon: any; color: string }) { + return ( +
+
+

{title}

+ +
+

{value}

+

{unit}

+
+ ) +} + +function TableListCard({ title, tables, icon: Icon, onOpen }: { title: string; tables: any[]; icon: any; onOpen: (name: string) => void }) { + return ( +
+

{title}

+
+ {tables?.map((t) => ( + + ))} +
+
+ ) +} + +function TableCard({ name, label, columnCount, rowCount, onClick }: { name: string; label: string; columnCount: number; rowCount: number; onClick: () => void }) { + return ( + + ) +} diff --git a/client/src/pages/PlatformPage.tsx b/client/src/pages/PlatformPage.tsx new file mode 100644 index 0000000..a3e0c92 --- /dev/null +++ b/client/src/pages/PlatformPage.tsx @@ -0,0 +1,147 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { MetricCard } from '@/components/MetricCard' +import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' +import { useState, useMemo } from 'react' +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts' + +const PAGE_SIZE = 15 + +export function PlatformPage() { + const [platformPage, setPlatformPage] = useState(1) + const [marketingPage, setMarketingPage] = useState(1) + + const { data: platformData, isLoading: platformLoading } = useQuery({ + queryKey: ['platform/economics'], + queryFn: () => api.get('/platform/economics'), + }) + + const { data: marketingData, isLoading: marketingLoading } = useQuery({ + queryKey: ['marketing/plans'], + queryFn: () => api.get('/marketing/plans'), + }) + + const platformRows = (platformData as any)?.data || [] + const marketingRows = (marketingData as any)?.data || [] + + const pageLoading = platformLoading || marketingLoading + + const storesWithPlatform = platformRows.filter((r: any) => Number(r.meituan_received) > 0 || Number(r.taobao_received) > 0 || Number(r.jd_received) > 0) + + const totalMeituan = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_received || 0), 0) + const totalTaobao = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_received || 0), 0) + const totalJd = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_received || 0), 0) + const totalMeituanCost = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_discount || 0) + Number(r.meituan_commission || 0), 0) + const totalTaobaoCost = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_discount || 0) + Number(r.taobao_commission || 0), 0) + const totalJdCost = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_discount || 0) + Number(r.jd_commission || 0), 0) + + const totalPlatformReceived = totalMeituan + totalTaobao + totalJd + const totalPlatformCost = totalMeituanCost + totalTaobaoCost + totalJdCost + const blendedCostRate = totalPlatformReceived > 0 ? (totalPlatformCost / totalPlatformReceived) * 100 : 0 + + const highCostStores = storesWithPlatform.filter((r: any) => { + const rates = [Number(r.meituan_cost_rate_pct || 0), Number(r.taobao_cost_rate_pct || 0), Number(r.jd_cost_rate_pct || 0)].filter((v: number) => v > 0) + return rates.some((v: number) => v >= 38) + }) + + const platformChartData = [ + { name: '美团', 实收: totalMeituan, 折扣: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_discount || 0), 0), 佣金: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_commission || 0), 0) }, + { name: '淘宝', 实收: totalTaobao, 折扣: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_discount || 0), 0), 佣金: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_commission || 0), 0) }, + { name: '京东', 实收: totalJd, 折扣: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_discount || 0), 0), 佣金: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_commission || 0), 0) }, + ] + + const pagedPlatform = useMemo(() => storesWithPlatform.slice((platformPage - 1) * PAGE_SIZE, platformPage * PAGE_SIZE), [storesWithPlatform, platformPage]) + const pagedMarketing = useMemo(() => marketingRows.slice((marketingPage - 1) * PAGE_SIZE, marketingPage * PAGE_SIZE), [marketingRows, marketingPage]) + + if (pageLoading) { + return + } + + return ( +
+
+

平台与优惠管理

+

三平台经济性 · 营销方案ROI · 优惠结构 · 2026年4月

+
+ + {/* 概览指标 */} + +
+ + + + +
+
+ + {/* 三平台对比图 */} + + + + + + v >= 10000 ? `${(v / 10000).toFixed(0)}万` : v} tick={{ fontSize: 10 }} /> + formatCurrency(v)} /> + + + + + + + + + {/* 门店平台经济性 */} + + +
+ formatCurrency(r.meituan_received) }, + { key: 'meituan_cost_rate_pct', label: '美团成本率', align: 'right', render: (r) => { + const v = Number(r.meituan_cost_rate_pct || 0) + return v > 0 ? = 38 ? 'font-medium text-red-600' : v >= 35 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% : - + }}, + { key: 'taobao_received', label: '淘宝实收', align: 'right', render: (r) => formatCurrency(r.taobao_received) }, + { key: 'taobao_cost_rate_pct', label: '淘宝成本率', align: 'right', render: (r) => { + const v = Number(r.taobao_cost_rate_pct || 0) + return v > 0 ? = 38 ? 'font-medium text-red-600' : v >= 35 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% : - + }}, + { key: 'jd_received', label: '京东实收', align: 'right', render: (r) => formatCurrency(r.jd_received) }, + { key: 'jd_cost_rate_pct', label: '京东成本率', align: 'right', render: (r) => { + const v = Number(r.jd_cost_rate_pct || 0) + return v > 0 ? = 38 ? 'font-medium text-red-600' : v >= 35 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% : - + }}, + ]} + data={pagedPlatform} + /> +
+
+ + {/* 营销方案ROI */} + + +
+ formatNumber(r.bill_count) }, + { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, + { key: 'avg_bill_value', label: '客单价', align: 'right', render: (r) => formatCurrency(r.avg_bill_value) }, + { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => { + const v = Number(r.discount_rate_pct || 0) + return = 25 ? 'font-medium text-red-600' : v >= 15 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% + }}, + { key: 'theoretical_margin_pct', label: '理论毛利率', align: 'right', render: (r) => formatPercent(r.theoretical_margin_pct) }, + ]} + data={pagedMarketing} + /> +
+
+
+ ) +} diff --git a/client/src/pages/RegionalPage.tsx b/client/src/pages/RegionalPage.tsx new file mode 100644 index 0000000..4b79245 --- /dev/null +++ b/client/src/pages/RegionalPage.tsx @@ -0,0 +1,117 @@ +import { useQuery } from '@tanstack/react-query' +import { useNavigate } from 'react-router-dom' +import api from '@/lib/api' +import { Badge } from '@/components/Badge' +import { DataTable } from '@/components/DataTable' +import { MetricCard } from '@/components/MetricCard' +import { Pagination } from '@/components/Pagination' +import { formatCurrency, formatPercent, riskColor } from '@/lib/utils' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { useState, useMemo } from 'react' + +const PAGE_SIZE = 10 + +export function RegionalPage() { + const navigate = useNavigate() + const [riskFilter, setRiskFilter] = useState('') + const [riskPage, setRiskPage] = useState(1) + const [checkPage, setCheckPage] = useState(1) + + const { data: riskData, isLoading: riskLoading } = useQuery({ + queryKey: ['stores/risk'], + queryFn: () => api.get('/stores/risk'), + }) + + const { data: weeklyData, isLoading: weeklyLoading } = useQuery({ + queryKey: ['tasks/weekly-check'], + queryFn: () => api.get('/tasks/weekly-check', { params: { month: '2026-05' } }), + }) + + const { data: priorityData, isLoading: priorityLoading } = useQuery({ + queryKey: ['stores/priority'], + queryFn: () => api.get('/stores/priority'), + }) + + const riskRows = ((riskData as any)?.data || []).filter((r: any) => !riskFilter || r.risk_level === riskFilter) + const weeklyRows = (weeklyData as any)?.data || [] + const priorityRows = (priorityData as any)?.data || [] + + const p0p1 = priorityRows.filter((s: any) => s.action_priority?.startsWith('P0') || s.action_priority?.startsWith('P1')) + const needCheck = weeklyRows.filter((w: any) => parseInt(w.consecutive_no_improve_weeks) >= 2) + + const pagedRiskRows = useMemo(() => riskRows.slice((riskPage - 1) * PAGE_SIZE, riskPage * PAGE_SIZE), [riskRows, riskPage]) + const pagedNeedCheck = useMemo(() => needCheck.slice((checkPage - 1) * PAGE_SIZE, checkPage * PAGE_SIZE), [needCheck, checkPage]) + + const pageLoading = riskLoading || weeklyLoading || priorityLoading + + if (pageLoading) { + return + } + + return ( +
+

区域经理工作台

+ + {/* 汇总卡片 */} +
+ r.risk_level !== '绿色').length} /> + + + !w.last_check_date).length} /> +
+ + {/* 风险筛选 */} +
+ {['', '红色', '黄色', '绿色'].map((r) => ( + + ))} +
+ + {/* 门店红黄绿列表 */} +
+

门店风险分级 ({riskRows.length})

+ +
+ }, + { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, + { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => formatPercent(r.discount_rate_pct) }, + { key: 'theoretical_margin_pct', label: '毛利率', align: 'right', render: (r) => formatPercent(r.theoretical_margin_pct) }, + { key: 'anomaly_rate_pct', label: '异常率', align: 'right', render: (r) => formatPercent(r.anomaly_rate_pct) }, + ]} + data={pagedRiskRows} + onRowClick={(r) => navigate(`/stores/${r.store_code}`)} + /> +
+
+ + {/* 需到店检查门店 */} + {needCheck.length > 0 && ( +
+

需到店检查门店(连续2周未改善)({needCheck.length})

+ +
+ }, + ]} + data={pagedNeedCheck} + onRowClick={(r) => navigate(`/tasks/${r.task_id}`)} + /> +
+
+ )} +
+ ) +} diff --git a/client/src/pages/RiskPage.tsx b/client/src/pages/RiskPage.tsx new file mode 100644 index 0000000..ef03dfe --- /dev/null +++ b/client/src/pages/RiskPage.tsx @@ -0,0 +1,168 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { MetricCard } from '@/components/MetricCard' +import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' +import { useState, useMemo } from 'react' +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' + +const PAGE_SIZE = 15 + +export function RiskPage() { + const [anomalyPage, setAnomalyPage] = useState(1) + const [cashierPage, setCashierPage] = useState(1) + const [zeroPage, setZeroPage] = useState(1) + const [riskTab, setRiskTab] = useState<'anomaly' | 'zero' | 'cashier'>('anomaly') + + const { data: anomalyData, isLoading: anomalyLoading } = useQuery({ + queryKey: ['risk/anomaly'], + queryFn: () => api.get('/risk/anomaly'), + }) + + const { data: zeroData, isLoading: zeroLoading } = useQuery({ + queryKey: ['risk/zero-received'], + queryFn: () => api.get('/risk/zero-received'), + }) + + const { data: cashierData, isLoading: cashierLoading } = useQuery({ + queryKey: ['risk/cashier'], + queryFn: () => api.get('/risk/cashier'), + }) + + const anomalyRows = (anomalyData as any)?.data || [] + const zeroRows = (zeroData as any)?.data || [] + const cashierRows = (cashierData as any)?.data || [] + + const pageLoading = anomalyLoading || zeroLoading || cashierLoading + + const totalAnomaly = anomalyRows.length + const totalZero = zeroRows.length + const highRiskCashiers = cashierRows.filter((r: any) => Number(r.anomaly_rate_pct || 0) >= 10).length + const avgAnomalyRate = cashierRows.length > 0 + ? cashierRows.reduce((s: number, r: any) => s + Number(r.anomaly_rate_pct || 0), 0) / cashierRows.length + : 0 + + const pagedAnomaly = useMemo(() => anomalyRows.slice((anomalyPage - 1) * PAGE_SIZE, anomalyPage * PAGE_SIZE), [anomalyRows, anomalyPage]) + const pagedZero = useMemo(() => zeroRows.slice((zeroPage - 1) * PAGE_SIZE, zeroPage * PAGE_SIZE), [zeroRows, zeroPage]) + const pagedCashier = useMemo(() => cashierRows.slice((cashierPage - 1) * PAGE_SIZE, cashierPage * PAGE_SIZE), [cashierRows, cashierPage]) + + const cashierChartData = cashierRows.slice(0, 15) + + if (pageLoading) { + return + } + + return ( +
+
+

风险与内控管理

+

异常账单 · 零实收 · 收银风险 · 2026年4月

+
+ + {/* 概览指标 */} + +
+ + + + +
+
+ + {/* 收银员风险图 */} + + + + + + + `${Number(v).toFixed(2)}%`} /> + + + + + + {/* 风险明细表 — Tab切换 */} + +
+ + + +
+ + {riskTab === 'anomaly' && ( + <> + +
+ formatCurrency(r.consumption) }, + { key: 'discount_total', label: '优惠', align: 'right', render: (r) => formatCurrency(r.discount_total) }, + { key: 'received_total', label: '实收', align: 'right', render: (r) => formatCurrency(r.received_total) }, + { key: 'anomaly_reason', label: '异常原因' }, + { key: 'closed_at', label: '结账时间', render: (r) => r.closed_at?.substring(0, 16) }, + ]} + data={pagedAnomaly} + /> +
+ + )} + + {riskTab === 'zero' && ( + <> + +
+ formatCurrency(r.consumption) }, + { key: 'discount_total', label: '优惠', align: 'right', render: (r) => formatCurrency(r.discount_total) }, + { key: 'zero_received_type', label: '零实收类型' }, + { key: 'cashier', label: '收银员' }, + { key: 'closed_at', label: '结账时间', render: (r) => r.closed_at?.substring(0, 16) }, + ]} + data={pagedZero} + /> +
+ + )} + + {riskTab === 'cashier' && ( + <> + +
+ formatNumber(r.bill_count) }, + { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, + { key: 'anomaly_bills', label: '异常账单数', align: 'right', render: (r) => formatNumber(r.anomaly_bills) }, + { key: 'anomaly_rate_pct', label: '异常率', align: 'right', render: (r) => { + const v = Number(r.anomaly_rate_pct || 0) + return = 10 ? 'font-medium text-red-600' : v >= 5 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(2)}% + }}, + { key: 'anomaly_consumption', label: '异常消费额', align: 'right', render: (r) => formatCurrency(r.anomaly_consumption) }, + ]} + data={pagedCashier} + /> +
+ + )} +
+
+ ) +} diff --git a/client/src/pages/SKUPage.tsx b/client/src/pages/SKUPage.tsx new file mode 100644 index 0000000..336667c --- /dev/null +++ b/client/src/pages/SKUPage.tsx @@ -0,0 +1,170 @@ +import { useQuery } from '@tanstack/react-query' +import api from '@/lib/api' +import { DataTable } from '@/components/DataTable' +import { Pagination } from '@/components/Pagination' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { CollapsibleSection } from '@/components/CollapsibleSection' +import { MetricCard } from '@/components/MetricCard' +import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' +import { useState, useMemo } from 'react' +import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts' + +const ABC_COLORS = { 'A-核心': '#22c55e', 'B-成长': '#3b82f6', 'C-长尾': '#ef4444' } +const PAGE_SIZE = 15 + +export function SKUPage() { + const [abcFilter, setAbcFilter] = useState('') + const [skuPage, setSkuPage] = useState(1) + + const { data: skuData, isLoading: skuLoading } = useQuery({ + queryKey: ['sku/abc'], + queryFn: () => api.get('/sku/abc'), + }) + + const { data: catData, isLoading: catLoading } = useQuery({ + queryKey: ['sku/category'], + queryFn: () => api.get('/sku/category'), + }) + + const skuRows = ((skuData as any)?.data || []).filter((r: any) => !abcFilter || r.abc_class === abcFilter) + const catRows = (catData as any)?.data || [] + + const abcSummary = (skuData as any)?.data?.reduce((acc: any, r: any) => { + const k = r.abc_class + if (!acc[k]) acc[k] = { sku_count: 0, received: 0, share: 0 } + acc[k].sku_count++ + acc[k].received += Number(r.received_amount || 0) + acc[k].share += Number(r.revenue_share_pct || 0) + return acc + }, {}) || {} + + const abcPieData = Object.entries(abcSummary).map(([name, v]: any) => ({ + name, + value: v.sku_count, + received: v.received, + share: v.share, + })) + + const totalSku = (skuData as any)?.data?.length || 0 + const totalReceived = Object.values(abcSummary).reduce((s: number, v: any) => s + v.received, 0) + const cClassCount = abcSummary['C-长尾']?.sku_count || 0 + const lowValueSku = ((skuData as any)?.data || []).filter((r: any) => Number(r.received_amount) < 1000).length + const singleStoreSku = ((skuData as any)?.data || []).filter((r: any) => Number(r.store_count) <= 1).length + + const pagedSku = useMemo(() => skuRows.slice((skuPage - 1) * PAGE_SIZE, skuPage * PAGE_SIZE), [skuRows, skuPage]) + + const pageLoading = skuLoading || catLoading + + if (pageLoading) { + return + } + + return ( +
+
+

商品SKU管理

+

SKU ABC分析 · 长尾治理 · 品类结构 · 2026年4月

+
+ + {/* 概览指标 */} + +
+ + + + +
+
+ + {/* ABC分布图 */} +
+ + + + `${name}: ${value}个 (${Number(share).toFixed(1)}%)`} + > + {abcPieData.map((entry) => ( + + ))} + + [`${v}个SKU, 实收¥${formatNumber(p.payload.received)}`, p.payload.name]} /> + + + + + + + + + + v >= 10000 ? `${(v / 10000).toFixed(0)}万` : v} /> + + formatCurrency(v)} /> + + + + +
+ + {/* SKU明细表 */} + +
+ {['', 'A-核心', 'B-成长', 'C-长尾'].map((f) => ( + + ))} +
+ +
+ ( + {r.abc_class} + )}, + { key: 'sales_quadrant', label: '象限' }, + { key: 'store_count', label: '覆盖门店', align: 'center' }, + { key: 'bill_count', label: '账单数', align: 'right', render: (r) => formatNumber(r.bill_count) }, + { key: 'received_amount', label: '实收', align: 'right', render: (r) => formatCurrency(r.received_amount) }, + { key: 'revenue_share_pct', label: '占比', align: 'right', render: (r) => `${Number(r.revenue_share_pct).toFixed(2)}%` }, + { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => `${Number(r.discount_rate_pct).toFixed(1)}%` }, + ]} + data={pagedSku} + /> +
+
+ + {/* 长尾治理建议 */} + +
+
+

首轮停用候选:约 {lowValueSku} 个SKU

+

月实收不足¥1,000的SKU,停用前需确认是否有区域特色或套餐组件

+
+
+

单店专属SKU:{singleStoreSku} 个

+

仅1家门店销售,评估是否为区域特色或可推广

+
+
+

合并/归档候选:约300个套餐/赠品/餐具类

+

转为技术SKU或合并为统一编码,目标有效SKU池800-900个

+
+
+
+
+ ) +} diff --git a/client/src/pages/StoreDetailPage.tsx b/client/src/pages/StoreDetailPage.tsx new file mode 100644 index 0000000..872faa5 --- /dev/null +++ b/client/src/pages/StoreDetailPage.tsx @@ -0,0 +1,103 @@ +import { useQuery } from '@tanstack/react-query' +import { useParams, useNavigate } from 'react-router-dom' +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' +import api from '@/lib/api' +import { MetricCard } from '@/components/MetricCard' +import { Badge } from '@/components/Badge' +import { DataTable } from '@/components/DataTable' +import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils' +import { ArrowLeft } from 'lucide-react' + +export function StoreDetailPage() { + const { code } = useParams() + const navigate = useNavigate() + + const { data: storeData } = useQuery({ + queryKey: ['store', code], + queryFn: () => api.get(`/stores/${code}`), + }) + + const { data: dailyData } = useQuery({ + queryKey: ['store', code, 'daily'], + queryFn: () => api.get(`/stores/${code}/daily`), + }) + + const { data: tasksData } = useQuery({ + queryKey: ['store', code, 'tasks'], + queryFn: () => api.get('/tasks', { params: { store_code: code, month: '2026-05', page_size: 50 } }), + }) + + const sd = (storeData as any)?.data + const daily = ((dailyData as any)?.data) || [] + const tasks = ((tasksData as any)?.data) || [] + + if (!sd) return
加载中...
+ const sc = sd.scorecard + + return ( +
+ + +
+

{sc.store_name}

+ {sd.risk && } +
+ + {/* 核心指标 */} +
+ + + + + + +
+ + {/* 日度趋势 */} +
+

日度实收趋势

+ + + + + + + + + + +
+ + {/* 平台经济性 */} + {sd.platform && ( +
+

平台经济性

+
+ + + + +
+
+ )} + + {/* 任务列表 */} +
+

门店任务 ({tasks.length})

+ }, + { key: 'action_required', label: '行动要求', render: (r) => {r.action_required} }, + { key: 'status', label: '状态', render: (r) => }, + { key: 'deadline', label: '截止日', render: (r) => r.deadline?.substring(0, 10) }, + ]} + data={tasks} + onRowClick={(r) => navigate(`/tasks/${r.task_id}`)} + /> +
+
+ ) +} diff --git a/client/src/pages/StorePage.tsx b/client/src/pages/StorePage.tsx new file mode 100644 index 0000000..4b75375 --- /dev/null +++ b/client/src/pages/StorePage.tsx @@ -0,0 +1,319 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import api from '@/lib/api' +import { Badge } from '@/components/Badge' +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' +import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils' +import { LoadingSpinner } from '@/components/LoadingSpinner' +import { useState } from 'react' + +export function StorePage() { + const queryClient = useQueryClient() + const [executeText, setExecuteText] = useState('') + const [activeTaskId, setActiveTaskId] = useState(null) + const [storeCode, setStoreCode] = useState('1111') + + const { data: storesData, isLoading: storesLoading } = useQuery({ + queryKey: ['stores-list'], + queryFn: () => api.get('/stores/risk'), + }) + const stores = ((storesData as any)?.data || []).map((s: any) => ({ code: s.store_code, name: s.store_name, risk: s.risk_level, issue: s.primary_issue })) + + const { data: priorityData } = useQuery({ + queryKey: ['stores-priority-detail'], + queryFn: () => api.get('/stores/priority'), + }) + + const { data: cardData, isLoading: cardLoading } = useQuery({ + queryKey: ['store', storeCode, 'daily-card'], + queryFn: () => api.get(`/tasks/stores/${storeCode}/daily-card`), + }) + + const { data: tasksData, isLoading: tasksLoading } = useQuery({ + queryKey: ['store', storeCode, 'tasks'], + queryFn: () => api.get('/tasks', { params: { store_code: storeCode, month: '2026-05', page_size: 50 } }), + }) + + const { data: dailyData, isLoading: dailyLoading } = useQuery({ + queryKey: ['store', storeCode, 'daily'], + queryFn: () => api.get(`/stores/${storeCode}/daily`, { params: { month: '2026-04' } }), + }) + + const { data: followupData, isLoading: followupLoading } = useQuery({ + queryKey: ['store', storeCode, 'followup'], + queryFn: () => api.get('/tasks/followup'), + }) + + const card = (cardData as any)?.data + const anomalies = card?.anomalies || [] + const tasks = (tasksData as any)?.data || [] + const dailyRows = (dailyData as any)?.data || [] + const followupRows = (followupData as any)?.data || [] + const followup = followupRows.find((f: any) => f.store_code === storeCode) + + const priorityRows = (priorityData as any)?.data || [] + const priorityInfo = priorityRows.find((p: any) => p.store_code === storeCode) + + const storeInfo = stores.find((s: any) => s.code === storeCode) + const storeName = storeInfo?.name || '' + const primaryIssue = storeInfo?.issue || priorityInfo?.problem_combination || '-' + + const todoTasks = tasks.filter((t: any) => t.status === '待启动' || t.status === '进行中') + const doneTasks = tasks.filter((t: any) => t.status === '已验收' || t.status === '已回滚') + const passCount = tasks.filter((t: any) => t.verification_result === '达标').length + const improvingCount = tasks.filter((t: any) => t.verification_result === '改善中').length + + const allAnomalyItems: { module: string; metric: string; value: number; baseline: number; is_anomaly: boolean }[] = [] + anomalies.forEach((a: any) => { + const list = typeof a.anomalies === 'string' ? JSON.parse(a.anomalies) : a.anomalies || [] + list.forEach((item: any) => { + allAnomalyItems.push({ module: a.module, metric: item.metric, value: item.value, baseline: item.baseline, is_anomaly: item.is_anomaly }) + }) + }) + const anomalyCount = allAnomalyItems.filter((i: any) => i.is_anomaly).length + + const executeMutation = useMutation({ + mutationFn: ({ id, text }: { id: number; text: string }) => api.put(`/tasks/${id}/execute`, { process_evidence: text }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['store', storeCode, 'tasks'] }) + setExecuteText('') + setActiveTaskId(null) + }, + }) + + const riskColor = (level: string) => { + if (level === '红色') return 'bg-red-100 text-red-700 border-red-300' + if (level === '黄色') return 'bg-yellow-100 text-yellow-700 border-yellow-300' + return 'bg-green-100 text-green-700 border-green-300' + } + + const pageLoading = storesLoading || cardLoading || tasksLoading || dailyLoading || followupLoading + + if (pageLoading) { + return + } + + return ( +
+
+

店长工作台

+ +
+ + {/* 门店概况 */} +
+
+

门店名称

+

{storeName}

+
+
+

风险等级

+
+ {storeInfo?.risk && {storeInfo.risk}} +
+
+
+

主要问题

+

{primaryIssue}

+
+
+

任务完成率

+

{tasks.length > 0 ? Math.round(doneTasks.length / tasks.length * 100) : 0}%

+

{doneTasks.length}/{tasks.length} 已完成

+
+
+ + {/* 经营概览 */} +
+
+

最新经营概览(5月1日)

+ {anomalyCount > 0 && ( + {anomalyCount} 项异常 + )} +
+ {allAnomalyItems.length === 0 ? ( +

暂无数据

+ ) : ( +
+ {allAnomalyItems.map((item, idx) => ( +
+

{item.metric}

+

+ {item.metric === '实收' ? formatCurrency(item.value) : formatNumber(item.value)} +

+

日基准: {item.metric === '实收' ? formatCurrency(item.baseline) : formatNumber(item.baseline)}{item.is_anomaly && }

+
+ ))} +
+ )} +
+ + {/* 日度趋势 + 月度指标对比 */} +
+ {/* 日度实收趋势 */} +
+

日度实收趋势

+ {dailyRows.length > 0 ? ( + + + + v?.substring(5, 10)} tick={{ fontSize: 10 }} /> + + v?.substring(0, 10)} /> + + + + ) :

暂无数据

} +
+ + {/* 月度指标对比 */} +
+

月度指标对比

+ {followup ? ( + + + + + + + + + + + + {[ + { label: '实收', baseline: Number(followup.baseline_received), target: Number(followup.target_received), actual: Number(followup.actual_received), lower: false, isCurrency: true }, + { label: '客单价', baseline: Number(followup.baseline_avg_bill), target: Number(followup.target_avg_bill), actual: Number(followup.actual_avg_bill), lower: false, isCurrency: true }, + { label: '优惠率(%)', baseline: Number(followup.baseline_discount_rate), target: Number(followup.target_discount_rate), actual: Number(followup.actual_discount_rate), lower: true, isCurrency: false }, + { label: '理论毛利率(%)', baseline: Number(followup.baseline_margin_rate), target: Number(followup.target_margin_rate), actual: Number(followup.actual_margin_rate), lower: false, isCurrency: false }, + { label: '异常率(%)', baseline: Number(followup.baseline_anomaly_rate), target: Number(followup.target_anomaly_rate), actual: Number(followup.actual_anomaly_rate), lower: true, isCurrency: false }, + { label: '会员占比(%)', baseline: Number(followup.baseline_member_share), target: Number(followup.target_member_share), actual: Number(followup.actual_member_share), lower: false, isCurrency: false }, + ].map((m) => { + const achieved = !isNaN(m.actual) && !isNaN(m.target) && (m.lower ? m.actual <= m.target : m.actual >= m.target) + const rate = !isNaN(m.actual) && !isNaN(m.baseline) && m.baseline !== 0 ? Math.round((m.actual / m.baseline) * 100) : null + const fmt = (v: number) => isNaN(v) ? '-' : m.isCurrency ? formatCurrency(v) : formatNumber(v) + return ( + + + + + + + + ) + })} + +
指标月基线目标实际达成率
{m.label}{fmt(m.baseline)}{fmt(m.target)}{fmt(m.actual)} + {rate !== null ? `${rate}%` : '-'} + {achieved ? ' ✓' : ' ✗'} +
+ ) :

暂无数据

} +
+
+ + {/* 工作重点和行动计划 */} + {followup && (followup.work_focus || followup.action_plan) && ( +
+ {followup.work_focus && ( +
+

工作重点

+

{followup.work_focus}

+
+ )} + {followup.action_plan && ( +
+

行动计划

+

{followup.action_plan}

+
+ )} +
+ )} + + {/* 待办任务 */} +
+

待办任务 ({todoTasks.length})

+
+ {todoTasks.length === 0 ? ( +

暂无待办任务

+ ) : todoTasks.map((t: any) => ( +
+
+
+ + {t.problem_indicator} +
+ +
+

{t.action_required}

+

截止日: {t.deadline?.substring(0, 10)}

+ + {activeTaskId === t.task_id ? ( +
+