Files
rmser/rmser-view/project_context.md
SERTY a536b3ff3c 2801-опция для перетаскивания строк в черновике.
пофиксил синк накладных
свайп убрал
внешний номер теперь ок
2026-01-28 03:58:43 +03:00

9735 lines
312 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ===================================================================
# Полный контекст React Typescript проекта
# Сгенерировано: 2026-01-28 02:48:31
# ===================================================================
Это полный дамп исходного кода React Typescript (Vite) проекта.
Каждый файл предваряется заголовком с путём к нему.
# ===================================================================
# Файл: Dockerfile
# ===================================================================
```
# Этап 1: Сборка (Build)
FROM node:24-alpine as builder
WORKDIR /app
# Копируем файлы зависимостей
COPY package*.json ./
# Устанавливаем зависимости
RUN npm install
# Копируем исходный код
COPY . .
# Собираем проект (результат будет в папке dist)
# Важно: Vite подставит VITE_API_URL во время сборки.
# Мы будем использовать относительный путь /api, чтобы работал прокси Nginx.
ENV VITE_API_URL=/api
RUN npm run build
# Этап 2: Запуск (Serve via Nginx)
FROM nginx:alpine
# Копируем конфиг nginx (создадим его на след. шаге)
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Копируем собранные файлы из этапа сборки
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
# ===================================================================
# Файл: eslint.config.js
# ===================================================================
```
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
```
# ===================================================================
# Файл: index.html
# ===================================================================
```
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RMSer App</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
```
# ===================================================================
# Файл: package-lock.json
# ===================================================================
```
{
"name": "rmser-view",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rmser-view",
"version": "0.0.0",
"dependencies": {
"@hello-pangea/dnd": "^18.0.1",
"@tanstack/react-query": "^5.90.12",
"@twa-dev/sdk": "^8.0.2",
"antd": "^6.1.0",
"axios": "^1.13.2",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.10.1",
"zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
},
"node_modules/@ant-design/colors": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.0.tgz",
"integrity": "sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==",
"license": "MIT",
"dependencies": {
"@ant-design/fast-color": "^3.0.0"
}
},
"node_modules/@ant-design/cssinjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.0.1.tgz",
"integrity": "sha512-Lw1Z4cUQxdMmTNir67gU0HCpTl5TtkKCJPZ6UBvCqzcOTl/QmMFB6qAEoj8qFl0CuZDX9qQYa3m9+rEKfaBSbA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.11.1",
"@emotion/hash": "^0.8.0",
"@emotion/unitless": "^0.7.5",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"stylis": "^4.3.4"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@ant-design/cssinjs-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.0.2.tgz",
"integrity": "sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==",
"license": "MIT",
"dependencies": {
"@ant-design/cssinjs": "^2.0.1",
"@babel/runtime": "^7.23.2",
"@rc-component/util": "^1.4.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/@ant-design/fast-color": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.0.tgz",
"integrity": "sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==",
"license": "MIT",
"engines": {
"node": ">=8.x"
}
},
"node_modules/@ant-design/icons": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.1.0.tgz",
"integrity": "sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^8.0.0",
"@ant-design/icons-svg": "^4.4.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@ant-design/icons-svg": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==",
"license": "MIT"
},
"node_modules/@ant-design/react-slick": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz",
"integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"clsx": "^2.1.1",
"json2mq": "^0.2.0",
"throttle-debounce": "^5.0.0"
},
"peerDependencies": {
"react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.27.1",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
"integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.28.3",
"@babel/helpers": "^7.28.4",
"@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5",
"@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.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@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.27.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.27.2",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
"integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
"integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
"integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.27.2",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
"@babel/types": "^7.28.5",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
"license": "MIT"
},
"node_modules/@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/config-array": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.17.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.1",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/js": {
"version": "9.39.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
"integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.17.0",
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@hello-pangea/dnd": {
"version": "18.0.1",
"resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz",
"integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.26.7",
"css-box-model": "^1.2.1",
"raf-schd": "^4.0.3",
"react-redux": "^9.2.0",
"redux": "^5.0.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=12.22"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/retry": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@rc-component/async-validator": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
"integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.4"
},
"engines": {
"node": ">=14.x"
}
},
"node_modules/@rc-component/cascader": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.9.0.tgz",
"integrity": "sha512-2jbthe1QZrMBgtCvNKkJFjZYC3uKl4N/aYm5SsMvO3T+F+qRT1CGsSM9bXnh1rLj7jDk/GK0natShWF/jinhWQ==",
"license": "MIT",
"dependencies": {
"@rc-component/select": "~1.3.0",
"@rc-component/tree": "~1.1.0",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/checkbox": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-1.0.1.tgz",
"integrity": "sha512-08yTH8m+bSm8TOqbybbJ9KiAuIATti6bDs2mVeSfu4QfEnyeF6X0enHVvD1NEAyuBWEAo56QtLe++MYs2D9XiQ==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/collapse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.1.2.tgz",
"integrity": "sha512-ilBYk1dLLJHu5Q74dF28vwtKUYQ42ZXIIDmqTuVy4rD8JQVvkXOs+KixVNbweyuIEtJYJ7+t+9GVD9dPc6N02w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.10.1",
"@rc-component/motion": "^1.1.4",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/color-picker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.0.3.tgz",
"integrity": "sha512-V7gFF9O7o5XwIWafdbOtqI4BUUkEUkgdBwp6favy3xajMX/2dDqytFaiXlcwrpq6aRyPLp5dKLAG5RFKLXMeGA==",
"license": "MIT",
"dependencies": {
"@ant-design/fast-color": "^3.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/context": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz",
"integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/dialog": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.5.1.tgz",
"integrity": "sha512-by4Sf/a3azcb89WayWuwG19/Y312xtu8N81HoVQQtnsBDylfs+dog98fTAvLinnpeoWG52m/M7QLRW6fXR3l1g==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.1.3",
"@rc-component/portal": "^2.0.0",
"@rc-component/util": "^1.0.1",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/drawer": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.3.0.tgz",
"integrity": "sha512-rE+sdXEmv2W25VBQ9daGbnb4J4hBIEKmdbj0b3xpY+K7TUmLXDIlSnoXraIbFZdGyek9WxxGKK887uRnFgI+pQ==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/portal": "^2.0.0",
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/dropdown": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz",
"integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==",
"license": "MIT",
"dependencies": {
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.11.0",
"react-dom": ">=16.11.0"
}
},
"node_modules/@rc-component/form": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.4.0.tgz",
"integrity": "sha512-C8MN/2wIaW9hSrCCtJmcgCkWTQNIspN7ARXLFA4F8PGr8Qxk39U5pS3kRK51/bUJNhb/fEtdFnaViLlISGKI2A==",
"license": "MIT",
"dependencies": {
"@rc-component/async-validator": "^5.0.3",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/image": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.5.3.tgz",
"integrity": "sha512-/NR7QW9uCN8Ugar+xsHZOPvzPySfEhcW2/vLcr7VPRM+THZMrllMRv7LAUgW7ikR+Z67Ab67cgPp5K5YftpJsQ==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.0.0",
"@rc-component/portal": "^2.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/input": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.1.2.tgz",
"integrity": "sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@rc-component/input-number": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz",
"integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==",
"license": "MIT",
"dependencies": {
"@rc-component/mini-decimal": "^1.0.1",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/mentions": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.6.0.tgz",
"integrity": "sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==",
"license": "MIT",
"dependencies": {
"@rc-component/input": "~1.1.0",
"@rc-component/menu": "~1.2.0",
"@rc-component/textarea": "~1.1.0",
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/menu": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.2.0.tgz",
"integrity": "sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/overflow": "^1.0.0",
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/mini-decimal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
"integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.0"
},
"engines": {
"node": ">=8.x"
}
},
"node_modules/@rc-component/motion": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.1.6.tgz",
"integrity": "sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.2.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/mutate-observer": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz",
"integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.2.0"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/notification": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-1.2.0.tgz",
"integrity": "sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/overflow": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.0.tgz",
"integrity": "sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.11.1",
"@rc-component/resize-observer": "^1.0.1",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/pagination": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz",
"integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/picker": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.8.0.tgz",
"integrity": "sha512-ek4efrIy+peC8WFJg6Lg7c+WNkykr+wUGQGBNoKmlF0K752aIJuaPcBj6p8CceT9vSJ9gOeeclQCBQIFWVDk1A==",
"license": "MIT",
"dependencies": {
"@rc-component/overflow": "^1.0.0",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/trigger": "^3.6.15",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=12.x"
},
"peerDependencies": {
"date-fns": ">= 2.x",
"dayjs": ">= 1.x",
"luxon": ">= 3.x",
"moment": ">= 2.x",
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
},
"peerDependenciesMeta": {
"date-fns": {
"optional": true
},
"dayjs": {
"optional": true
},
"luxon": {
"optional": true
},
"moment": {
"optional": true
}
}
},
"node_modules/@rc-component/portal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.0.1.tgz",
"integrity": "sha512-46KYuA7Udb1LAaLIdDrfmDz3wzyeEZxIURJCn+heoQVbhtW5PQkhBSQtRus+DUdsknmTFQulxSnqrbX3CI4yXw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=12.x"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/progress": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz",
"integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/qrcode": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz",
"integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.24.7"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/rate": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz",
"integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/resize-observer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.0.1.tgz",
"integrity": "sha512-r+w+Mz1EiueGk1IgjB3ptNXLYSLZ5vnEfKHH+gfgj7JMupftyzvUUl3fRcMZe5uMM04x0n8+G2o/c6nlO2+Wag==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.2.0"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/segmented": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.2.3.tgz",
"integrity": "sha512-L7G4S6zUpqHclOXK0wKKN2/VyqHa9tfDNxkoFjWOTPtQ0ROFaBwZhbf1+9sdZfIFkxJkpcShAmDOMEIBaFFqkw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.11.1",
"@rc-component/motion": "^1.1.4",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@rc-component/select": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.3.5.tgz",
"integrity": "sha512-A2QVOWDfRoLgHwPHrCGx1G42dYntOk+nsT6SX4ADCoagqu4bcxceJPbYvVKkfMYSIwgtfu+tDhPk3Z5gz8944g==",
"license": "MIT",
"dependencies": {
"@rc-component/overflow": "^1.0.0",
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.3.0",
"@rc-component/virtual-list": "^1.0.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
}
},
"node_modules/@rc-component/slider": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz",
"integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/steps": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz",
"integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/switch": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz",
"integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/table": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.9.0.tgz",
"integrity": "sha512-cq3P9FkD+F3eglkFYhBuNlHclg+r4jY8+ZIgK7zbEFo6IwpnA77YL/Gq4ensLw9oua3zFCTA6JDu6YgBei0TxA==",
"license": "MIT",
"dependencies": {
"@rc-component/context": "^2.0.1",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/util": "^1.1.0",
"@rc-component/virtual-list": "^1.0.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/tabs": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.7.0.tgz",
"integrity": "sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==",
"license": "MIT",
"dependencies": {
"@rc-component/dropdown": "~1.0.0",
"@rc-component/menu": "~1.2.0",
"@rc-component/motion": "^1.1.3",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/textarea": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@rc-component/textarea/-/textarea-1.1.2.tgz",
"integrity": "sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==",
"license": "MIT",
"dependencies": {
"@rc-component/input": "~1.1.0",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/tooltip": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz",
"integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==",
"license": "MIT",
"dependencies": {
"@rc-component/trigger": "^3.7.1",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/tour": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.2.1.tgz",
"integrity": "sha512-BUCrVikGJsXli38qlJ+h2WyDD6dYxzDA9dV3o0ij6gYhAq6ooT08SUMWOikva9v4KZ2BEuluGl5bPcsjrSoBgQ==",
"license": "MIT",
"dependencies": {
"@rc-component/portal": "^2.0.0",
"@rc-component/trigger": "^3.0.0",
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/tree": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.1.0.tgz",
"integrity": "sha512-HZs3aOlvFgQdgrmURRc/f4IujiNBf4DdEeXUlkS0lPoLlx9RoqsZcF0caXIAMVb+NaWqKtGQDnrH8hqLCN5zlA==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.0.0",
"@rc-component/util": "^1.2.1",
"@rc-component/virtual-list": "^1.0.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=10.x"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
}
},
"node_modules/@rc-component/tree-select": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.4.0.tgz",
"integrity": "sha512-I3UAlO2hNqy9CSKc8EBaESgnmKk2QaRzuZ2XHZGFCgsSMkGl06mdF97sVfROM02YIb64ocgLKefsjE0Ch4ocwQ==",
"license": "MIT",
"dependencies": {
"@rc-component/select": "~1.3.0",
"@rc-component/tree": "~1.1.0",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": "*",
"react-dom": "*"
}
},
"node_modules/@rc-component/trigger": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.7.1.tgz",
"integrity": "sha512-+YNP8FywxKJpdqzlAp6TN8UbSK6YsQtIs3kI13mHfm87qi3qUd5Q9AGW8Unfv76kXFUSu7U7D0FygRsGH+6MiA==",
"license": "MIT",
"dependencies": {
"@rc-component/motion": "^1.1.4",
"@rc-component/portal": "^2.0.0",
"@rc-component/resize-observer": "^1.0.0",
"@rc-component/util": "^1.2.1",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/upload": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz",
"integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==",
"license": "MIT",
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rc-component/util": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.6.0.tgz",
"integrity": "sha512-YbjuIVAm8InCnXVoA4n6G+uh31yESTxQ6fSY2frZ2/oMSvktoB+bumFUfNN7RKh7YeOkZgOvN2suGtEDhJSX0A==",
"license": "MIT",
"dependencies": {
"is-mobile": "^5.0.0",
"react-is": "^18.2.0"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/@rc-component/virtual-list": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz",
"integrity": "sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
"@rc-component/resize-observer": "^1.0.1",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1"
},
"engines": {
"node": ">=8.x"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.53",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
"integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
"integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
"integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
"integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
"integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
"integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
"integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
"integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
"integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
"integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
"integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
"integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
"integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
"integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
"integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
"integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
"integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
"integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
"integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
"integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
"integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
"integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
"integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@tanstack/query-core": {
"version": "5.90.12",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz",
"integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.90.12",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz",
"integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.90.12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@twa-dev/sdk": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@twa-dev/sdk/-/sdk-8.0.2.tgz",
"integrity": "sha512-Pp5GxnxP2blboVZFiM9aWjs4cb8IpW3x2jP3kLOMvIqy0jzNUTuFHkwHtx+zEvh/UcF2F+wmS8G6ebIA0XPXcg==",
"license": "MIT",
"dependencies": {
"@twa-dev/types": "^8.0.1"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
},
"node_modules/@twa-dev/types": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@twa-dev/types/-/types-8.0.2.tgz",
"integrity": "sha512-ICQ6n4NaUPPzV3/GzflVQS6Nnu5QX2vr9OlOG8ZkFf3rSJXzRKazrLAbZlVhCPPWkIW3MMuELPsE6tByrA49qA==",
"license": "MIT"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.2.tgz",
"integrity": "sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/@types/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.49.0.tgz",
"integrity": "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/type-utils": "8.49.0",
"@typescript-eslint/utils": "8.49.0",
"@typescript-eslint/visitor-keys": "8.49.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.49.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.49.0.tgz",
"integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
"@typescript-eslint/typescript-estree": "8.49.0",
"@typescript-eslint/visitor-keys": "8.49.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz",
"integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.49.0",
"@typescript-eslint/types": "^8.49.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.49.0.tgz",
"integrity": "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.49.0",
"@typescript-eslint/visitor-keys": "8.49.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz",
"integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.49.0.tgz",
"integrity": "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.49.0",
"@typescript-eslint/typescript-estree": "8.49.0",
"@typescript-eslint/utils": "8.49.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz",
"integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz",
"integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.49.0",
"@typescript-eslint/tsconfig-utils": "8.49.0",
"@typescript-eslint/types": "8.49.0",
"@typescript-eslint/visitor-keys": "8.49.0",
"debug": "^4.3.4",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.1.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.49.0.tgz",
"integrity": "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.49.0",
"@typescript-eslint/types": "8.49.0",
"@typescript-eslint/typescript-estree": "8.49.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz",
"integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.49.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz",
"integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.28.5",
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.53",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.18.0"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/antd": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/antd/-/antd-6.1.0.tgz",
"integrity": "sha512-RIe4W5saaL9SWgvqCcvz6LZta/KwT50B0YF7xYiWVZh0Gqfw2rJAsOMcp202Hxgm+YiyoSp4QqqvexKhuGGarw==",
"license": "MIT",
"dependencies": {
"@ant-design/colors": "^8.0.0",
"@ant-design/cssinjs": "^2.0.1",
"@ant-design/cssinjs-utils": "^2.0.2",
"@ant-design/fast-color": "^3.0.0",
"@ant-design/icons": "^6.1.0",
"@ant-design/react-slick": "~2.0.0",
"@babel/runtime": "^7.28.4",
"@rc-component/cascader": "~1.9.0",
"@rc-component/checkbox": "~1.0.1",
"@rc-component/collapse": "~1.1.2",
"@rc-component/color-picker": "~3.0.3",
"@rc-component/dialog": "~1.5.1",
"@rc-component/drawer": "~1.3.0",
"@rc-component/dropdown": "~1.0.2",
"@rc-component/form": "~1.4.0",
"@rc-component/image": "~1.5.2",
"@rc-component/input": "~1.1.2",
"@rc-component/input-number": "~1.6.2",
"@rc-component/mentions": "~1.6.0",
"@rc-component/menu": "~1.2.0",
"@rc-component/motion": "~1.1.6",
"@rc-component/mutate-observer": "^2.0.1",
"@rc-component/notification": "~1.2.0",
"@rc-component/pagination": "~1.2.0",
"@rc-component/picker": "~1.8.0",
"@rc-component/progress": "~1.0.2",
"@rc-component/qrcode": "~1.1.1",
"@rc-component/rate": "~1.0.1",
"@rc-component/resize-observer": "^1.0.1",
"@rc-component/segmented": "~1.2.3",
"@rc-component/select": "~1.3.2",
"@rc-component/slider": "~1.0.1",
"@rc-component/steps": "~1.2.2",
"@rc-component/switch": "~1.0.3",
"@rc-component/table": "~1.9.0",
"@rc-component/tabs": "~1.7.0",
"@rc-component/textarea": "~1.1.2",
"@rc-component/tooltip": "~1.4.0",
"@rc-component/tour": "~2.2.1",
"@rc-component/tree": "~1.1.0",
"@rc-component/tree-select": "~1.4.0",
"@rc-component/trigger": "^3.7.1",
"@rc-component/upload": "~1.1.0",
"@rc-component/util": "^1.4.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"scroll-into-view-if-needed": "^3.1.0",
"throttle-debounce": "^5.0.2"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ant-design"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.6",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz",
"integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"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",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
},
"bin": {
"browserslist": "cli.js"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001760",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
"integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
"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/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compute-scroll-into-view": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
"license": "MIT"
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"license": "MIT",
"dependencies": {
"tiny-invariant": "^1.0.6"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT",
"peer": true
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
"license": "MIT"
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.267",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
"dev": true,
"license": "ISC"
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint": {
"version": "9.39.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.1",
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.39.1",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.4.0",
"eslint-visitor-keys": "^4.2.1",
"espree": "^10.4.0",
"esquery": "^1.5.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^8.0.0",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"ignore": "^5.2.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
"bin": {
"eslint": "bin/eslint.js"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"jiti": "*"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
}
}
},
"node_modules/eslint-plugin-react-hooks": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
"integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.24.4",
"@babel/parser": "^7.24.4",
"hermes-parser": "^0.25.1",
"zod": "^3.25.0 || ^4.0.0",
"zod-validation-error": "^3.5.0 || ^4.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-react-refresh": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
"integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"eslint": ">=8.40"
}
},
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"estraverse": "^5.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"flat-cache": "^4.0.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/flat-cache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.4"
},
"engines": {
"node": ">=16"
}
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/globals": {
"version": "16.5.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
"integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
"dev": true,
"license": "MIT"
},
"node_modules/hermes-parser": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"hermes-estree": "0.25.1"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-mobile": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
"integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
"license": "MIT"
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/json2mq": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
"integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
"license": "MIT",
"dependencies": {
"string-convert": "^0.2.0"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.563.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz",
"integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==",
"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.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true,
"license": "MIT"
},
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"dev": true,
"license": "MIT"
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/raf-schd": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
"license": "MIT"
},
"node_modules/react": {
"version": "19.2.1",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.1"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-refresh": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz",
"integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz",
"integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==",
"license": "MIT",
"dependencies": {
"react-router": "7.10.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT",
"peer": true
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/rollup": {
"version": "4.53.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
"integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.53.3",
"@rollup/rollup-android-arm64": "4.53.3",
"@rollup/rollup-darwin-arm64": "4.53.3",
"@rollup/rollup-darwin-x64": "4.53.3",
"@rollup/rollup-freebsd-arm64": "4.53.3",
"@rollup/rollup-freebsd-x64": "4.53.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
"@rollup/rollup-linux-arm-musleabihf": "4.53.3",
"@rollup/rollup-linux-arm64-gnu": "4.53.3",
"@rollup/rollup-linux-arm64-musl": "4.53.3",
"@rollup/rollup-linux-loong64-gnu": "4.53.3",
"@rollup/rollup-linux-ppc64-gnu": "4.53.3",
"@rollup/rollup-linux-riscv64-gnu": "4.53.3",
"@rollup/rollup-linux-riscv64-musl": "4.53.3",
"@rollup/rollup-linux-s390x-gnu": "4.53.3",
"@rollup/rollup-linux-x64-gnu": "4.53.3",
"@rollup/rollup-linux-x64-musl": "4.53.3",
"@rollup/rollup-openharmony-arm64": "4.53.3",
"@rollup/rollup-win32-arm64-msvc": "4.53.3",
"@rollup/rollup-win32-ia32-msvc": "4.53.3",
"@rollup/rollup-win32-x64-gnu": "4.53.3",
"@rollup/rollup-win32-x64-msvc": "4.53.3",
"fsevents": "~2.3.2"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/scroll-into-view-if-needed": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
"license": "MIT",
"dependencies": {
"compute-scroll-into-view": "^3.0.2"
}
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
"integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
"license": "MIT"
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/stylis": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
"license": "MIT"
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/throttle-debounce": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
"license": "MIT",
"engines": {
"node": ">=12.22"
}
},
"node_modules/tiny-invariant": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.12"
},
"peerDependencies": {
"typescript": ">=4.8.4"
}
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"license": "MIT",
"dependencies": {
"prelude-ls": "^1.2.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.49.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.49.0.tgz",
"integrity": "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.49.0",
"@typescript-eslint/parser": "8.49.0",
"@typescript-eslint/typescript-estree": "8.49.0",
"@typescript-eslint/utils": "8.49.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz",
"integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/vite": {
"version": "7.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz",
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
"tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"lightningcss": "^1.21.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
"license": "ISC"
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-validation-error": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
"integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/zustand": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
"integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}
```
# ===================================================================
# Файл: package.json
# ===================================================================
```
{
"name": "rmser-view",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hello-pangea/dnd": "^18.0.1",
"@tanstack/react-query": "^5.90.12",
"@twa-dev/sdk": "^8.0.2",
"antd": "^6.1.0",
"axios": "^1.13.2",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.10.1",
"zustand": "^5.0.9"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}
```
# ===================================================================
# Файл: src/App.tsx
# ===================================================================
```
import { useEffect, useState } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { Result, Button } from "antd";
import { Providers } from "./components/layout/Providers";
import { AppLayout } from "./components/layout/AppLayout";
import { OcrLearning } from "./pages/OcrLearning";
import { InvoiceDraftPage } from "./pages/InvoiceDraftPage";
import { InvoiceViewPage } from "./pages/InvoiceViewPage";
import { DraftsList } from "./pages/DraftsList";
import { SettingsPage } from "./pages/SettingsPage";
import { UNAUTHORIZED_EVENT, MAINTENANCE_EVENT } from "./services/api";
import MaintenancePage from "./pages/MaintenancePage";
// Компонент-заглушка для внешних браузеров
const NotInTelegramScreen = () => (
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#fff",
padding: 20,
}}
>
<Result
status="warning"
title="Доступ ограничен"
subTitle="Пожалуйста, откройте это приложение через официального Telegram бота @RmserBot для корректной работы."
extra={
<Button type="primary" href="https://t.me/RmserBot" target="_blank">
Перейти в бота
</Button>
}
/>
</div>
);
function App() {
const [isUnauthorized, setIsUnauthorized] = useState(false);
const [isMaintenance, setIsMaintenance] = useState(false);
const tg = window.Telegram?.WebApp;
// Проверяем, есть ли данные от Telegram
const isInTelegram = !!tg?.initData;
useEffect(() => {
const handleUnauthorized = () => setIsUnauthorized(true);
const handleMaintenance = () => setIsMaintenance(true);
window.addEventListener(UNAUTHORIZED_EVENT, handleUnauthorized);
window.addEventListener(MAINTENANCE_EVENT, handleMaintenance);
if (tg) {
tg.expand(); // Расширяем приложение на все окно
}
return () => {
window.removeEventListener(UNAUTHORIZED_EVENT, handleUnauthorized);
window.removeEventListener(MAINTENANCE_EVENT, handleMaintenance);
};
}, [tg]);
// Если открыто не в Telegram — блокируем всё
if (!isInTelegram) {
return <NotInTelegramScreen />;
}
// Если бэкенд вернул 401
if (isUnauthorized) {
return (
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Result
status="403"
title="Ошибка доступа"
subTitle="Не удалось подтвердить вашу личность. Попробуйте обновить страницу внутри Telegram."
/>
</div>
);
}
// Если бэкенд вернул 503 (режим технического обслуживания)
if (isMaintenance) {
return <MaintenancePage />;
}
return (
<Providers>
<BrowserRouter>
<Routes>
<Route path="/" element={<AppLayout />}>
<Route index element={<Navigate to="/invoices" replace />} />
<Route path="ocr" element={<OcrLearning />} />
<Route path="invoices" element={<DraftsList />} />
<Route path="invoice/draft/:id" element={<InvoiceDraftPage />} />
<Route path="invoice/view/:id" element={<InvoiceViewPage />} />
<Route path="settings" element={<SettingsPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</BrowserRouter>
</Providers>
);
}
export default App;
```
# ===================================================================
# Файл: src/components/invoices/CreateContainerModal.tsx
# ===================================================================
```
import React, { useState } from 'react';
import { Modal, Form, Input, InputNumber, Button, message } from 'antd';
import { api } from '../../services/api';
import type { ProductContainer } from '../../services/types';
interface Props {
visible: boolean;
onCancel: () => void;
productId: string;
productBaseUnit: string;
// Callback возвращает уже полный объект с ID от сервера
onSuccess: (container: ProductContainer) => void;
}
export const CreateContainerModal: React.FC<Props> = ({
visible, onCancel, productId, productBaseUnit, onSuccess
}) => {
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const handleOk = async () => {
try {
const values = await form.validateFields();
setLoading(true);
// 1. Отправляем запрос на БЭКЕНД
const res = await api.createContainer({
product_id: productId,
name: values.name,
count: values.count
});
message.success('Фасовка создана');
// 2. БЭКЕНД вернул ID. Теперь мы собираем объект для UI
// Мы не придумываем ID сами, мы берем res.container_id
const newContainer: ProductContainer = {
id: res.container_id, // <--- ID от сервера
name: values.name,
count: values.count
};
// 3. Возвращаем полный объект родителю
onSuccess(newContainer);
form.resetFields();
} catch {
message.error('Ошибка создания фасовки');
} finally {
setLoading(false);
}
};
return (
<Modal
title="Новая фасовка"
open={visible}
onCancel={onCancel}
footer={[
<Button key="back" onClick={onCancel}>Отмена</Button>,
<Button key="submit" type="primary" loading={loading} onClick={handleOk}>
Создать
</Button>,
]}
>
<Form form={form} layout="vertical">
<Form.Item
name="name"
label="Название"
rules={[{ required: true, message: 'Введите название, например 0.5' }]}
>
<Input placeholder="Например: Бутылка 0.5" />
</Form.Item>
<Form.Item
name="count"
label={`Количество в базовых ед. (${productBaseUnit})`}
rules={[{ required: true, message: 'Введите коэффициент' }]}
>
<InputNumber style={{ width: '100%' }} step={0.001} placeholder="0.5" />
</Form.Item>
</Form>
</Modal>
);
};
```
# ===================================================================
# Файл: src/components/invoices/DraftItemRow.tsx
# ===================================================================
```
import React, { useMemo, useState, useEffect, useRef } from "react";
import { Draggable } from "@hello-pangea/dnd";
import {
Card,
Flex,
InputNumber,
Typography,
Select,
Tag,
Button,
Divider,
Modal,
Popconfirm,
} from "antd";
import {
SyncOutlined,
PlusOutlined,
WarningFilled,
DeleteOutlined,
} from "@ant-design/icons";
import { GripVertical } from "lucide-react";
import { CatalogSelect } from "../ocr/CatalogSelect";
import { CreateContainerModal } from "./CreateContainerModal";
import type {
DraftItem,
UpdateDraftItemRequest,
ProductSearchResult,
ProductContainer,
Recommendation,
} from "../../services/types";
const { Text } = Typography;
interface Props {
item: DraftItem;
index: number;
onUpdate: (itemId: string, changes: UpdateDraftItemRequest) => void;
onDelete: (itemId: string) => void;
isUpdating: boolean;
recommendations?: Recommendation[];
isReordering: boolean;
}
type FieldType = "quantity" | "price" | "sum";
export const DraftItemRow: React.FC<Props> = ({
item,
index,
onUpdate,
onDelete,
isUpdating,
recommendations = [],
isReordering,
}) => {
const [isModalOpen, setIsModalOpen] = useState(false);
// --- Локальное состояние значений (строки для удобства ввода) ---
const [localQty, setLocalQty] = useState<number | null>(item.quantity);
const [localPrice, setLocalPrice] = useState<number | null>(item.price);
const [localSum, setLocalSum] = useState<number | null>(item.sum);
// --- История редактирования (Stack) ---
// Храним 2 последних отредактированных поля.
// Инициализируем из пропсов или дефолтно ['quantity', 'price'], чтобы пересчитывалась сумма.
const editStack = useRef<FieldType[]>([
(item.last_edited_field_1 as FieldType) || "quantity",
(item.last_edited_field_2 as FieldType) || "price",
]);
// Храним ссылку на предыдущую версию item, чтобы сравнивать изменения
// --- Синхронизация с сервером ---
useEffect(() => {
// Если мы ждем ответа от сервера, не сбиваем локальный ввод
if (isUpdating) return;
// Обновляем локальные стейты только когда меняются конкретные поля в item
setLocalQty(item.quantity);
setLocalPrice(item.price);
setLocalSum(item.sum);
// Обновляем стек редактирования
if (item.last_edited_field_1 && item.last_edited_field_2) {
editStack.current = [
item.last_edited_field_1 as FieldType,
item.last_edited_field_2 as FieldType,
];
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
// Зависим ТОЛЬКО от примитивов. Если объект item изменится, но цифры те же - эффект не сработает.
item.quantity,
item.price,
item.sum,
item.last_edited_field_1,
item.last_edited_field_2,
isUpdating,
]);
// --- Логика пересчета (Треугольник) ---
const recalculateLocally = (changedField: FieldType, newVal: number) => {
// 1. Обновляем стек истории
// Удаляем поле, если оно уже было в стеке, и добавляем в начало (LIFO для важности)
const currentStack = editStack.current.filter((f) => f !== changedField);
currentStack.unshift(changedField);
// Оставляем только 2 последних
if (currentStack.length > 2) currentStack.pop();
editStack.current = currentStack;
// 2. Определяем, какое поле нужно пересчитать (то, которого НЕТ в стеке)
const allFields: FieldType[] = ["quantity", "price", "sum"];
const fieldToRecalc = allFields.find((f) => !currentStack.includes(f));
// 3. Выполняем расчет
let q = changedField === "quantity" ? newVal : localQty || 0;
let p = changedField === "price" ? newVal : localPrice || 0;
let s = changedField === "sum" ? newVal : localSum || 0;
switch (fieldToRecalc) {
case "sum":
s = q * p;
setLocalSum(s);
break;
case "quantity":
if (p !== 0) {
q = s / p;
setLocalQty(q);
} else {
setLocalQty(0);
}
break;
case "price":
if (q !== 0) {
p = s / q;
setLocalPrice(p);
} else {
setLocalPrice(0);
}
break;
}
};
// --- Обработчики ввода ---
const handleValueChange = (field: FieldType, val: number | null) => {
// Обновляем само поле
if (field === "quantity") setLocalQty(val);
if (field === "price") setLocalPrice(val);
if (field === "sum") setLocalSum(val);
if (val !== null) {
recalculateLocally(field, val);
}
};
const handleBlur = (field: FieldType) => {
// Отправляем на сервер только измененное поле + маркер edited_field.
// Сервер сам проведет пересчет и вернет точные данные.
// Важно: отправляем текущее локальное значение.
let val: number | null = null;
if (field === "quantity") val = localQty;
if (field === "price") val = localPrice;
if (field === "sum") val = localSum;
if (val === null) return;
// Сравниваем с текущим item, чтобы не спамить запросами, если число не поменялось
const serverVal = item[field];
// Используем эпсилон для сравнения float
if (Math.abs(val - serverVal) > 0.0001) {
onUpdate(item.id, {
[field]: val,
edited_field: field,
});
}
};
// --- Product & Container Logic (как было) ---
const [searchedProduct, setSearchedProduct] =
useState<ProductSearchResult | null>(null);
const [addedContainers, setAddedContainers] = useState<
Record<string, ProductContainer[]>
>({});
const activeProduct = useMemo(() => {
if (searchedProduct && searchedProduct.id === item.product_id)
return searchedProduct;
return item.product as unknown as ProductSearchResult | undefined;
}, [searchedProduct, item.product, item.product_id]);
const containers = useMemo(() => {
if (!activeProduct) return [];
const baseContainers = activeProduct.containers || [];
const manuallyAdded = addedContainers[activeProduct.id] || [];
const combined = [...baseContainers];
manuallyAdded.forEach((c) => {
if (!combined.find((existing) => existing.id === c.id)) combined.push(c);
});
return combined;
}, [activeProduct, addedContainers]);
const baseUom =
activeProduct?.main_unit?.name || activeProduct?.measure_unit || "ед.";
const containerOptions = useMemo(() => {
if (!activeProduct) return [];
const opts = [
{ value: "BASE_UNIT", label: `Базовая (${baseUom})` },
...containers.map((c) => ({
value: c.id,
label: `${c.name} (=${Number(c.count)} ${baseUom})`,
})),
];
if (
item.container_id &&
item.container &&
!containers.find((c) => c.id === item.container_id)
) {
opts.push({
value: item.container.id,
label: `${item.container.name} (=${Number(
item.container.count
)} ${baseUom})`,
});
}
return opts;
}, [activeProduct, containers, baseUom, item.container_id, item.container]);
// --- WARNING LOGIC ---
const activeWarning = useMemo(() => {
if (!item.product_id) return null;
return recommendations.find((r) => r.ProductID === item.product_id);
}, [item.product_id, recommendations]);
const showWarningModal = () => {
if (!activeWarning) return;
Modal.warning({
title: "Внимание: проблемный товар",
content: (
<div>
<p>
<b>{activeWarning.ProductName}</b>
</p>
<p>{activeWarning.Reason}</p>
<p>
<Tag color="orange">{activeWarning.Type}</Tag>
</p>
</div>
),
okText: "Понятно",
maskClosable: true,
});
};
// --- Handlers ---
const handleProductChange = (
prodId: string,
productObj?: ProductSearchResult
) => {
if (productObj) setSearchedProduct(productObj);
onUpdate(item.id, {
product_id: prodId,
container_id: null, // Сбрасываем фасовку
// При смене товара логично оставить Qty и Sum, пересчитав Price?
// Или оставить Qty и Price? Обычно цена меняется.
// Пока не трогаем числа, пусть остаются как были.
});
};
const handleContainerChange = (val: string) => {
// "" пустая строка приходит при выборе "Базовая" (мы так настроим value)
const newVal = val === "BASE_UNIT" ? "" : val;
onUpdate(item.id, { container_id: newVal });
};
const handleContainerCreated = (newContainer: ProductContainer) => {
setIsModalOpen(false);
if (activeProduct) {
setAddedContainers((prev) => ({
...prev,
[activeProduct.id]: [...(prev[activeProduct.id] || []), newContainer],
}));
}
onUpdate(item.id, { container_id: newContainer.id });
};
const cardBorderColor = !item.product_id
? "#ffa39e"
: item.is_matched
? "#b7eb8f"
: "#d9d9d9";
return (
<>
<Draggable draggableId={item.id} index={index} isDragDisabled={!isReordering}>
{(provided, snapshot) => {
const style = {
marginBottom: "8px",
backgroundColor: snapshot.isDragging ? "#e6f7ff" : "transparent",
boxShadow: snapshot.isDragging
? "0 4px 12px rgba(0, 0, 0, 0.15)"
: "none",
borderRadius: "4px",
transition: "background-color 0.2s ease, box-shadow 0.2s ease",
...provided.draggableProps.style,
};
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
style={style}
>
<Card
size="small"
style={{
display: "flex",
alignItems: "center",
padding: "12px 16px",
borderLeft: `4px solid ${cardBorderColor}`,
border: snapshot.isDragging
? "2px solid #1890ff"
: "1px solid #d9d9d9",
background: item.product_id ? "#fff" : "#fff1f0",
borderRadius: "4px",
}}
bodyStyle={{ padding: 0 }}
>
{/* Drag handle - иконка для перетаскивания (показываем только в режиме перетаскивания) */}
{isReordering && (
<div
{...provided.dragHandleProps}
style={{
cursor: "grab",
padding: "4px 8px 4px 0",
color: "#8c8c8c",
display: "flex",
alignItems: "center",
transition: "color 0.2s ease",
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = "#1890ff";
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = "#8c8c8c";
}}
>
<GripVertical size={20} />
</div>
)}
<Flex vertical gap={10} style={{ flex: 1 }}>
<Flex justify="space-between" align="start">
<div style={{ flex: 1 }}>
<Text
type="secondary"
style={{
fontSize: 12,
lineHeight: 1.2,
display: "block",
}}
>
{item.raw_name || "Новая позиция"}
</Text>
{item.raw_amount > 0 && (
<Text
type="secondary"
style={{ fontSize: 10, display: "block" }}
>
(чек: {item.raw_amount} x {item.raw_price})
</Text>
)}
</div>
<div
style={{
marginLeft: 8,
display: "flex",
alignItems: "center",
gap: 6,
}}
>
{isUpdating && (
<SyncOutlined spin style={{ color: "#1890ff" }} />
)}
{activeWarning && (
<WarningFilled
style={{
color: "#faad14",
fontSize: 16,
cursor: "pointer",
}}
onClick={showWarningModal}
/>
)}
{!item.product_id && (
<Tag color="error" style={{ margin: 0 }}>
?
</Tag>
)}
<Popconfirm
title="Удалить строку?"
onConfirm={() => onDelete(item.id)}
okText="Да"
cancelText="Нет"
placement="left"
>
<Button
type="text"
size="small"
icon={<DeleteOutlined />}
danger
style={{ marginLeft: 4 }}
/>
</Popconfirm>
</div>
</Flex>
<CatalogSelect
value={item.product_id || undefined}
onChange={handleProductChange}
initialProduct={activeProduct}
/>
{activeProduct && (
<Select
style={{ width: "100%" }}
placeholder="Выберите единицу измерения"
options={containerOptions}
value={item.container_id || "BASE_UNIT"}
onChange={handleContainerChange}
dropdownRender={(menu) => (
<>
{menu}
<Divider style={{ margin: "4px 0" }} />
<Button
type="text"
block
icon={<PlusOutlined />}
onClick={() => setIsModalOpen(true)}
style={{ textAlign: "left" }}
>
Добавить фасовку...
</Button>
</>
)}
/>
)}
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
background: "#fafafa",
margin: "0 -12px -12px -12px",
padding: "8px 12px",
borderTop: "1px solid #f0f0f0",
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
}}
>
<div
style={{
display: "flex",
gap: 8,
alignItems: "center",
flex: 1,
}}
>
<InputNumber
style={{ width: 70 }}
controls={false}
placeholder="Кол"
min={0}
value={localQty}
onChange={(val) => handleValueChange("quantity", val)}
onBlur={() => handleBlur("quantity")}
precision={3}
/>
<Text type="secondary">x</Text>
<InputNumber
style={{ width: 80 }}
controls={false}
placeholder="Цена"
min={0}
value={localPrice}
onChange={(val) => handleValueChange("price", val)}
onBlur={() => handleBlur("price")}
precision={2}
/>
</div>
<div
style={{ display: "flex", alignItems: "center", gap: 4 }}
>
<Text type="secondary">=</Text>
<InputNumber
style={{ width: 90, fontWeight: "bold" }}
controls={false}
placeholder="Сумма"
min={0}
value={localSum}
onChange={(val) => handleValueChange("sum", val)}
onBlur={() => handleBlur("sum")}
precision={2}
/>
</div>
</div>
</Flex>
</Card>
</div>
);
}}
</Draggable>
{activeProduct && (
<CreateContainerModal
visible={isModalOpen}
onCancel={() => setIsModalOpen(false)}
productId={activeProduct.id}
productBaseUnit={baseUom}
onSuccess={handleContainerCreated}
/>
)}
</>
);
};
```
# ===================================================================
# Файл: src/components/layout/AppLayout.tsx
# ===================================================================
```
import React from "react";
import { Layout, theme } from "antd";
import { Outlet, useNavigate, useLocation } from "react-router-dom";
import {
ScanOutlined,
FileTextOutlined,
SettingOutlined,
} from "@ant-design/icons";
const { Content } = Layout;
export const AppLayout: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const {
token: { colorBgContainer, colorPrimary, colorTextSecondary },
} = theme.useToken();
const path = location.pathname;
let activeKey = "invoices";
if (path.startsWith("/ocr")) activeKey = "ocr";
else if (path.startsWith("/settings")) activeKey = "settings";
const menuItems = [
{
key: "invoices",
icon: <FileTextOutlined style={{ fontSize: 20 }} />,
label: "Накладные",
path: "/invoices",
},
{
key: "ocr",
icon: <ScanOutlined style={{ fontSize: 20 }} />,
label: "Обучение",
path: "/ocr",
},
{
key: "settings",
icon: <SettingOutlined style={{ fontSize: 20 }} />,
label: "Настройки",
path: "/settings",
},
];
return (
<Layout
style={{ minHeight: "100vh", display: "flex", flexDirection: "column" }}
>
{/* Верхнюю шапку (Header) удалили для экономии места */}
<Content
style={{ padding: "0", flex: 1, overflowY: "auto", marginBottom: 60 }}
>
{/* Убрали лишние паддинги вокруг контента для мобилок */}
<div
style={{
background: colorBgContainer,
minHeight: "100%",
padding: "12px 12px 80px 12px", // Добавили отступ снизу, чтобы контент не перекрывался меню
borderRadius: 0, // На мобильных скругления углов всего экрана обычно не нужны
}}
>
<Outlet />
</div>
</Content>
{/* Нижний Таб-бар */}
<div
style={{
position: "fixed",
bottom: 0,
width: "100%",
zIndex: 1000,
background: "#fff",
borderTop: "1px solid #f0f0f0",
display: "flex",
justifyContent: "space-around",
alignItems: "center",
padding: "8px 0",
height: 60,
boxShadow: "0 -2px 8px rgba(0,0,0,0.05)",
}}
>
{menuItems.map((item) => {
const isActive = activeKey === item.key;
return (
<div
key={item.key}
onClick={() => navigate(item.path)}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "33%",
cursor: "pointer",
color: isActive ? colorPrimary : colorTextSecondary,
}}
>
{item.icon}
<span
style={{
fontSize: 10,
marginTop: 2,
fontWeight: isActive ? 500 : 400,
}}
>
{item.label}
</span>
</div>
);
})}
</div>
</Layout>
);
};
```
# ===================================================================
# Файл: src/components/layout/Providers.tsx
# ===================================================================
```
import React, { useEffect, useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import WebApp from '@twa-dev/sdk';
// Настройка клиента React Query
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false, // Не перезапрашивать при переключении вкладок
retry: 1,
},
},
});
interface ProvidersProps {
children: React.ReactNode;
}
export const Providers: React.FC<ProvidersProps> = ({ children }) => {
const [isReady, setIsReady] = useState(false);
useEffect(() => {
// Инициализация Telegram Mini App
WebApp.ready();
WebApp.expand(); // Разворачиваем на весь экран
// Подстраиваем цвет хедера под тему Telegram
WebApp.setHeaderColor('secondary_bg_color');
// eslint-disable-next-line react-hooks/set-state-in-effect
setIsReady(true);
}, []);
if (!isReady) {
return <div style={{ padding: 20, textAlign: 'center' }}>Loading Telegram SDK...</div>;
}
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
};
```
# ===================================================================
# Файл: src/components/ocr/AddMatchForm.tsx
# ===================================================================
```
import React, { useState, useMemo, useEffect } from "react";
import {
Card,
Button,
Flex,
AutoComplete,
Input,
InputNumber,
Typography,
Select,
Divider,
Popconfirm,
} from "antd";
import {
PlusOutlined,
DeleteOutlined,
EditOutlined,
CloseOutlined,
} from "@ant-design/icons";
import { CatalogSelect } from "./CatalogSelect";
import { CreateContainerModal } from "../invoices/CreateContainerModal";
import type {
CatalogItem,
UnmatchedItem,
ProductSearchResult,
ProductContainer,
ProductMatch,
} from "../../services/types";
const { Text } = Typography;
interface Props {
catalog: CatalogItem[];
unmatched?: UnmatchedItem[];
onSave: (
rawName: string,
productId: string,
quantity: number,
containerId?: string
) => void;
onDeleteUnmatched?: (rawName: string) => void;
isLoading: boolean;
initialValues?: ProductMatch; // Для редактирования
onCancelEdit?: () => void; // Для сброса режима редактирования
}
export const AddMatchForm: React.FC<Props> = ({
catalog,
unmatched = [],
onSave,
onDeleteUnmatched,
isLoading,
initialValues,
onCancelEdit,
}) => {
// --- Состояния ---
const [rawName, setRawName] = useState("");
const [selectedProduct, setSelectedProduct] = useState<string | undefined>(
undefined
);
// Храним полный объект товара, чтобы достать из него фасовки и имя для отображения
const [selectedProductData, setSelectedProductData] = useState<
ProductSearchResult | undefined
>(undefined);
const [quantity, setQuantity] = useState<number | null>(1);
const [selectedContainer, setSelectedContainer] = useState<string | null>(
null
);
const [isModalOpen, setIsModalOpen] = useState(false);
// --- Эффект для инициализации полей при редактировании ---
useEffect(() => {
if (initialValues) {
// eslint-disable-next-line
setRawName(initialValues.raw_name || "");
const prodId = initialValues.product?.id;
setSelectedProduct(prodId);
// Важно: восстанавливаем объект продукта из initialValues
// Приводим тип, так как DTO могут немного отличаться, но нам нужны containers и name
const prodData = initialValues.product as unknown as ProductSearchResult;
setSelectedProductData(prodData);
setQuantity(Number(initialValues.quantity) || 1);
setSelectedContainer(initialValues.container?.id || null);
} else {
// РЕЖИМ СОЗДАНИЯ (Сброс)
setRawName("");
setSelectedProduct(undefined);
setSelectedProductData(undefined);
setQuantity(1);
setSelectedContainer(null);
}
}, [initialValues]);
// --- Вычисляемые значения ---
const unmatchedOptions = useMemo(() => {
return unmatched.map((item) => ({
value: item.raw_name,
label: item.count ? `${item.raw_name} (${item.count} шт)` : item.raw_name,
}));
}, [unmatched]);
// Активный продукт: либо то, что выбрали в поиске, либо то, что пришло из редактирования
const activeProduct = useMemo(() => {
if (selectedProductData) return selectedProductData;
// Фоллбэк: пытаемся найти в общем каталоге (если он загружен полностью, что редко)
if (selectedProduct && catalog.length > 0) {
return catalog.find(
(item) => item.id === selectedProduct
) as unknown as ProductSearchResult;
}
return undefined;
}, [selectedProduct, selectedProductData, catalog]);
// Список контейнеров текущего товара
const containers = useMemo(() => {
return activeProduct?.containers || [];
}, [activeProduct]);
// Базовая единица
const baseUom =
activeProduct?.main_unit?.name || activeProduct?.measure_unit || "ед.";
// Текстовое отображение текущей единицы (для инпута количества)
const currentUomName = useMemo(() => {
if (selectedContainer) {
const cont = containers.find((c) => c.id === selectedContainer);
return cont ? cont.name : baseUom;
}
return baseUom;
}, [selectedContainer, containers, baseUom]);
const isButtonDisabled =
!rawName.trim() ||
!selectedProduct ||
quantity === null ||
quantity <= 0 ||
isLoading;
// --- Хендлеры ---
const handleProductChange = (
val: string,
productObj?: ProductSearchResult
) => {
setSelectedProduct(val);
if (productObj) {
setSelectedProductData(productObj);
}
// При смене товара сбрасываем фасовку
setSelectedContainer(null);
};
const handleSubmit = () => {
let quantityValue = quantity;
// Защита от null/строк
if (quantityValue === null || quantityValue === undefined) {
quantityValue = 1;
} else if (typeof quantityValue === "string") {
quantityValue = parseFloat(quantityValue);
}
if (isNaN(quantityValue) || quantityValue <= 0) {
quantityValue = 1;
}
if (rawName.trim() && selectedProduct) {
onSave(
rawName,
selectedProduct,
quantityValue,
selectedContainer || undefined
);
// Если это не редактирование, очищаем форму
if (!initialValues) {
setRawName("");
setSelectedProduct(undefined);
setSelectedProductData(undefined);
setQuantity(1);
setSelectedContainer(null);
}
}
};
const handleContainerCreated = (newContainer: ProductContainer) => {
setIsModalOpen(false);
// Добавляем созданную фасовку в локальный стейт продукта
if (selectedProductData) {
setSelectedProductData({
...selectedProductData,
containers: [...(selectedProductData.containers || []), newContainer],
});
} else if (activeProduct) {
setSelectedProductData({
...activeProduct,
containers: [...(activeProduct.containers || []), newContainer],
});
}
// Выбираем новую фасовку
setSelectedContainer(newContainer.id);
};
const handleDeleteUnmatched = () => {
if (onDeleteUnmatched && rawName.trim()) {
onDeleteUnmatched(rawName);
setRawName("");
}
};
// Кнопка "Сбросить" вызывает внешний обработчик отмены редактирования
const handleCancel = () => {
if (onCancelEdit) {
onCancelEdit();
}
};
return (
<Card
title={
initialValues ? "✏️ Редактирование связи" : " Добавить новую связь"
}
size="small"
style={{
marginBottom: 16,
borderColor: initialValues ? "#1890ff" : undefined, // Подсветка при редактировании
background: initialValues ? "#f0f5ff" : undefined,
}}
>
<Flex vertical gap="middle">
{/* Поле: Текст из чека */}
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: "#888" }}>
Текст из чека (Raw Name):
</div>
<div style={{ display: "flex", gap: 8 }}>
<AutoComplete
options={unmatchedOptions}
value={rawName}
onChange={setRawName}
filterOption={(inputValue, option) =>
!inputValue ||
(option?.value as string)
.toLowerCase()
.includes(inputValue.toLowerCase())
}
style={{ flex: 1 }}
>
<Input.TextArea
placeholder="Например: Масло слив. коробка"
autoSize={{ minRows: 1, maxRows: 4 }}
/>
</AutoComplete>
{onDeleteUnmatched && !initialValues && (
<Popconfirm
title="Удалить строку?"
description="Удалить из списка нераспознанных?"
onConfirm={handleDeleteUnmatched}
okText="Да"
cancelText="Нет"
>
<Button
danger
icon={<DeleteOutlined />}
disabled={!rawName.trim()}
title="Удалить мусорную строку"
/>
</Popconfirm>
)}
</div>
</div>
{/* Поле: Товар */}
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: "#888" }}>
Товар в iiko:
</div>
<CatalogSelect
value={selectedProduct}
onChange={handleProductChange}
disabled={isLoading}
initialProduct={activeProduct} // Передаем полный объект для правильного отображения!
/>
</div>
{/* Поле: Фасовка */}
{activeProduct && (
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: "#888" }}>
Единица измерения / Фасовка:
</div>
<Select
style={{ width: "100%" }}
value={selectedContainer}
onChange={setSelectedContainer}
placeholder="Выберите ед. измерения"
options={[
{ value: null, label: `Базовая единица (${baseUom})` },
...containers.map((c) => ({
value: c.id,
label: `${c.name} (=${Number(c.count)} ${baseUom})`,
})),
]}
dropdownRender={(menu) => (
<>
{menu}
<Divider style={{ margin: "4px 0" }} />
<Button
type="text"
block
icon={<PlusOutlined />}
onClick={() => setIsModalOpen(true)}
style={{ textAlign: "left" }}
>
Добавить фасовку
</Button>
</>
)}
/>
</div>
)}
{/* Поле: Количество */}
<div>
<div style={{ marginBottom: 4, fontSize: 12, color: "#888" }}>
Коэффициент (сколько товара в одной позиции чека):
</div>
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<InputNumber
min={0.001}
step={selectedContainer ? 1 : 0.1}
value={quantity}
onChange={(val) => setQuantity(Number(val))}
style={{ flex: 1 }}
placeholder="1"
/>
<Text strong>{currentUomName}</Text>
</div>
</div>
{/* Кнопки действий */}
<div style={{ display: "flex", gap: 8, marginTop: 4 }}>
<Button
type="primary"
icon={initialValues ? <EditOutlined /> : <PlusOutlined />}
onClick={handleSubmit}
loading={isLoading}
disabled={isButtonDisabled}
block
>
{initialValues ? "Сохранить изменения" : "Добавить связь"}
</Button>
{initialValues && (
<Button
onClick={handleCancel}
icon={<CloseOutlined />}
title="Отменить редактирование"
>
Отмена
</Button>
)}
</div>
</Flex>
{/* Модалка создания фасовки */}
{activeProduct && (
<CreateContainerModal
visible={isModalOpen}
onCancel={() => setIsModalOpen(false)}
productId={activeProduct.id}
productBaseUnit={baseUom}
onSuccess={handleContainerCreated}
/>
)}
</Card>
);
};
```
# ===================================================================
# Файл: src/components/ocr/CatalogSelect.tsx
# ===================================================================
```
import React, { useState, useEffect, useRef } from "react";
import { Select, Spin } from "antd";
import { api } from "../../services/api";
import type { CatalogItem, ProductSearchResult } from "../../services/types";
interface Props {
value?: string;
onChange?: (value: string, productObj?: ProductSearchResult) => void;
disabled?: boolean;
initialProduct?: CatalogItem | ProductSearchResult;
}
// Интерфейс для элемента выпадающего списка
interface SelectOption {
label: string;
value: string;
data: ProductSearchResult;
}
export const CatalogSelect: React.FC<Props> = ({
value,
onChange,
disabled,
initialProduct,
}) => {
const [options, setOptions] = useState<SelectOption[]>([]);
const [fetching, setFetching] = useState(false);
const [notFound, setNotFound] = useState(false);
const fetchRef = useRef<number | null>(null);
useEffect(() => {
if (initialProduct && initialProduct.id === value) {
const name = initialProduct.name;
const code = initialProduct.code;
setOptions([
{
label: code ? `${name} [${code}]` : name,
value: initialProduct.id,
data: initialProduct as ProductSearchResult,
},
]);
}
}, [initialProduct, value]);
const fetchProducts = async (search: string) => {
if (!search) {
setOptions([]);
setNotFound(false);
return;
}
setFetching(true);
// Не сбрасываем options сразу, чтобы не моргало
try {
const results = await api.searchProducts(search);
const newOptions = results.map((item) => ({
label: item.code ? `${item.name} [${item.code}]` : item.name,
value: item.id,
data: item,
}));
setOptions(newOptions);
// Показываем "Не найдено" если результатов нет
setNotFound(results.length === 0);
} catch (e) {
console.error(e);
setNotFound(true);
} finally {
setFetching(false);
}
};
const handleSearch = (val: string) => {
if (fetchRef.current !== null) {
window.clearTimeout(fetchRef.current);
}
// Сбрасываем notFound при новом поиске
setNotFound(false);
// Запускаем поиск только если введено хотя бы 2 символа
if (val.length < 2) {
return;
}
fetchRef.current = window.setTimeout(() => {
fetchProducts(val);
}, 500);
};
const handleChange = (
val: string,
option: SelectOption | SelectOption[] | undefined
) => {
if (onChange) {
const opt = Array.isArray(option) ? option[0] : option;
onChange(val, opt?.data);
}
};
return (
<Select
showSearch
placeholder="Начните вводить название товара..."
filterOption={false}
onSearch={handleSearch}
notFoundContent={
fetching ? (
<Spin size="small" />
) : notFound ? (
<div style={{ padding: "8px", color: "#999" }}>Товар не найден</div>
) : null
}
options={options}
value={value}
onChange={handleChange}
disabled={disabled}
style={{ width: "100%" }}
listHeight={256}
allowClear
// При очистке сбрасываем опции и notFound, чтобы при следующем клике не вылезал старый товар
onClear={() => {
setOptions([]);
setNotFound(false);
}}
// При клике (фокусе), если поле пустое - можно показать дефолтные опции или оставить пустым
onFocus={() => {
if (!value) setOptions([]);
}}
/>
);
};
```
# ===================================================================
# Файл: src/components/ocr/MatchList.tsx
# ===================================================================
```
import React from "react";
import { List, Typography, Tag, Input, Empty, Button, Popconfirm } from "antd";
import {
ArrowRightOutlined,
SearchOutlined,
DeleteOutlined,
EditOutlined,
} from "@ant-design/icons";
import type { ProductMatch } from "../../services/types";
const { Text } = Typography;
interface Props {
matches: ProductMatch[];
onDeleteMatch?: (rawName: string) => void;
onEditMatch?: (match: ProductMatch) => void;
isDeleting?: boolean;
}
export const MatchList: React.FC<Props> = ({
matches,
onDeleteMatch,
onEditMatch,
isDeleting = false,
}) => {
const [searchText, setSearchText] = React.useState("");
const filteredData = matches.filter((item) => {
const raw = (item.raw_name || "").toLowerCase();
const prod = item.product;
const prodName = (prod?.name || "").toLowerCase();
const search = searchText.toLowerCase();
return raw.includes(search) || prodName.includes(search);
});
return (
<div>
<Input
placeholder="Поиск по связям..."
prefix={<SearchOutlined style={{ color: "#ccc" }} />}
style={{ marginBottom: 12 }}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
allowClear
/>
<List
itemLayout="vertical"
dataSource={filteredData}
locale={{ emptyText: <Empty description="Нет данных" /> }}
pagination={{ pageSize: 10, size: "small", simple: true }}
renderItem={(item) => {
// Унификация полей (только snake_case)
const rawName = item.raw_name || "Без названия";
const product = item.product;
const productName = product?.name || "Товар не найден";
const qty = item.quantity || 1;
// Логика отображения Единицы или Фасовки
const container = item.container;
let displayUnit = "";
if (container) {
// Если есть фасовка: "Пачка 180г"
displayUnit = container.name;
} else {
// Иначе базовая ед.: "кг"
displayUnit = product?.measure_unit || "ед.";
}
return (
<List.Item
style={{
background: "#fff",
padding: 12,
marginBottom: 8,
borderRadius: 8,
display: "flex",
flexDirection: "column",
}}
>
<div style={{ flex: 1 }}>
<div style={{ marginBottom: 4 }}>
<Tag color="geekblue">Чек</Tag>
<Text strong>{rawName}</Text>
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
color: "#888",
}}
>
<ArrowRightOutlined />
<Text>
{productName}
<Text strong style={{ color: "#555", marginLeft: 6 }}>
x {qty} {displayUnit}
</Text>
</Text>
</div>
</div>
{(onDeleteMatch || onEditMatch) && (
<div
style={{ display: "flex", justifyContent: "space-between" }}
>
{onEditMatch && (
<Button
type="text"
icon={<EditOutlined />}
onClick={() => onEditMatch(item)}
size="small"
>
Редактировать
</Button>
)}
{onDeleteMatch && (
<Popconfirm
title="Удалить связь?"
description="Это действие нельзя отменить"
onConfirm={() => onDeleteMatch(rawName)}
okText="Да"
cancelText="Нет"
>
<Button
type="text"
danger
icon={<DeleteOutlined />}
loading={isDeleting}
size="small"
>
Удалить
</Button>
</Popconfirm>
)}
</div>
)}
</List.Item>
);
}}
/>
</div>
);
};
```
# ===================================================================
# Файл: src/components/recommendations/RecommendationCard.tsx
# ===================================================================
```
import React from 'react';
import { Card, Tag, Typography, Button } from 'antd';
import { WarningOutlined, InfoCircleOutlined } from '@ant-design/icons';
import type { Recommendation } from '../../services/types';
const { Text, Paragraph } = Typography;
interface Props {
item: Recommendation;
}
export const RecommendationCard: React.FC<Props> = ({ item }) => {
// Выбираем цвет тега в зависимости от типа проблемы
const getTagColor = (type: string) => {
switch (type) {
case 'UNUSED_IN_RECIPES': return 'volcano';
case 'NO_INCOMING': return 'gold';
default: return 'blue';
}
};
const getIcon = (type: string) => {
return type === 'UNUSED_IN_RECIPES' ? <WarningOutlined /> : <InfoCircleOutlined />;
};
return (
<Card
size="small"
title={
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{getIcon(item.Type)}
<Text strong ellipsis>{item.ProductName}</Text>
</div>
}
extra={<Tag color={getTagColor(item.Type)}>{item.Type}</Tag>}
style={{ marginBottom: 12, boxShadow: '0 2px 8px rgba(0,0,0,0.05)' }}
>
<Paragraph style={{ marginBottom: 8 }}>
{item.Reason}
</Paragraph>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Text type="secondary" style={{ fontSize: 12 }}>
{new Date(item.CreatedAt).toLocaleDateString()}
</Text>
{/* Кнопка действия (заглушка на будущее) */}
<Button size="small" type="link">Исправить</Button>
</div>
</Card>
);
};
```
# ===================================================================
# Файл: src/components/settings/PhotoStorageTab.tsx
# ===================================================================
```
import React, { useState } from "react";
import {
Card,
Image,
Button,
Popconfirm,
Tag,
Pagination,
Empty,
Spin,
message,
Tooltip,
} from "antd";
import {
DeleteOutlined,
ReloadOutlined,
FileImageOutlined,
CheckCircleOutlined,
FileTextOutlined,
} from "@ant-design/icons";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api, getStaticUrl } from "../../services/api";
import { AxiosError } from "axios";
import type { ReceiptPhoto, PhotoStatus } from "../../services/types";
export const PhotoStorageTab: React.FC = () => {
const [page, setPage] = useState(1);
const queryClient = useQueryClient();
const { data, isLoading, isError } = useQuery({
queryKey: ["photos", page],
queryFn: () => api.getPhotos(page, 18), // 18 - удобно делится на 2, 3, 6 колонок
});
const deleteMutation = useMutation({
mutationFn: ({ id, force }: { id: string; force: boolean }) =>
api.deletePhoto(id, force),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["photos"] });
message.success("Фото удалено");
},
// Исправленная типизация:
onError: (error: AxiosError<{ error: string }>) => {
if (error.response?.status === 409) {
message.warning(
"Это фото связано с черновиком. Используйте кнопку 'Удалить' с подтверждением."
);
} else {
message.error(error.response?.data?.error || "Ошибка удаления");
}
},
});
const regenerateMutation = useMutation({
mutationFn: (id: string) => api.regenerateDraftFromPhoto(id),
onSuccess: () => {
message.success("Черновик восстановлен");
// Можно редиректить, но пока просто обновим список
},
onError: () => {
message.error("Ошибка восстановления");
},
});
const getStatusTag = (status: PhotoStatus) => {
switch (status) {
case "ORPHAN":
return <Tag color="default">Без привязки</Tag>;
case "HAS_DRAFT":
return (
<Tag icon={<FileTextOutlined />} color="processing">
Черновик
</Tag>
);
case "HAS_INVOICE":
return (
<Tag icon={<CheckCircleOutlined />} color="success">
В iiko
</Tag>
);
default:
return <Tag>{status}</Tag>;
}
};
if (isLoading) {
return (
<div style={{ textAlign: "center", padding: 40 }}>
<Spin size="large" />
</div>
);
}
if (isError) {
return <Empty description="Ошибка загрузки фото" />;
}
if (!data?.photos?.length) {
return <Empty description="Нет загруженных фото" />;
}
return (
<div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))",
gap: 16,
marginBottom: 24,
}}
>
{data.photos.map((photo: ReceiptPhoto) => (
<Card
key={photo.id}
hoverable
size="small"
cover={
<div
style={{
height: 160,
overflow: "hidden",
position: "relative",
}}
>
<Image
src={getStaticUrl(photo.file_url)}
alt={photo.file_name}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
preview={{ mask: <FileImageOutlined /> }}
/>
</div>
}
actions={[
photo.can_regenerate ? (
<Tooltip title="Создать черновик заново">
<Button
type="text"
icon={<ReloadOutlined />}
onClick={() => regenerateMutation.mutate(photo.id)}
loading={regenerateMutation.isPending}
size="small"
/>
</Tooltip>
) : (
<span />
), // Placeholder для выравнивания
photo.can_delete ? (
<Popconfirm
title="Удалить фото?"
description={
photo.status === "HAS_DRAFT"
? "Внимание! Черновик тоже будет удален."
: "Восстановить будет невозможно."
}
onConfirm={() =>
deleteMutation.mutate({
id: photo.id,
force: photo.status === "HAS_DRAFT",
})
}
okText="Удалить"
cancelText="Отмена"
okButtonProps={{ danger: true }}
>
<Button
type="text"
danger
icon={<DeleteOutlined />}
size="small"
/>
</Popconfirm>
) : (
<Tooltip title="Нельзя удалить: накладная уже в iiko">
<Button
type="text"
disabled
icon={<DeleteOutlined />}
size="small"
/>
</Tooltip>
),
]}
>
<Card.Meta
title={
<div
style={{
fontSize: 12,
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{new Date(photo.created_at).toLocaleDateString()}
</div>
}
description={getStatusTag(photo.status)}
/>
</Card>
))}
</div>
<Pagination
current={page}
total={data.total}
pageSize={data.limit}
onChange={setPage}
showSizeChanger={false}
style={{ textAlign: "center" }}
simple
/>
</div>
);
};
```
# ===================================================================
# Файл: src/components/settings/TeamList.tsx
# ===================================================================
```
import React from "react";
import {
List,
Avatar,
Tag,
Button,
Select,
Popconfirm,
message,
Spin,
Alert,
Typography,
} from "antd";
import { DeleteOutlined, UserOutlined } from "@ant-design/icons";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "../../services/api";
import type { ServerUser, UserRole } from "../../services/types";
const { Text } = Typography;
interface Props {
currentUserRole: UserRole;
}
export const TeamList: React.FC<Props> = ({ currentUserRole }) => {
const queryClient = useQueryClient();
// Запрос списка пользователей
const {
data: users,
isLoading,
isError,
} = useQuery({
queryKey: ["serverUsers"],
queryFn: api.getUsers,
});
// Мутация изменения роли
const updateRoleMutation = useMutation({
mutationFn: ({ userId, newRole }: { userId: string; newRole: UserRole }) =>
api.updateUserRole(userId, newRole),
onSuccess: () => {
message.success("Роль пользователя обновлена");
queryClient.invalidateQueries({ queryKey: ["serverUsers"] });
},
onError: () => {
message.error("Не удалось изменить роль");
},
});
// Мутация удаления пользователя
const removeUserMutation = useMutation({
mutationFn: (userId: string) => api.removeUser(userId),
onSuccess: () => {
message.success("Пользователь удален из команды");
queryClient.invalidateQueries({ queryKey: ["serverUsers"] });
},
onError: () => {
message.error("Не удалось удалить пользователя");
},
});
// Хелперы для UI
const getRoleColor = (role: UserRole) => {
switch (role) {
case "OWNER":
return "gold";
case "ADMIN":
return "blue";
case "OPERATOR":
return "default";
default:
return "default";
}
};
const getRoleName = (role: UserRole) => {
switch (role) {
case "OWNER":
return "Владелец";
case "ADMIN":
return "Админ";
case "OPERATOR":
return "Оператор";
default:
return role;
}
};
// Проверка прав на удаление
const canDelete = (targetUser: ServerUser) => {
if (targetUser.is_me) return false; // Себя удалить нельзя
if (targetUser.role === "OWNER") return false; // Владельца удалить нельзя
if (currentUserRole === "ADMIN" && targetUser.role === "ADMIN")
return false; // Админ не может удалить админа
return true;
};
if (isLoading) {
return (
<div style={{ textAlign: "center", padding: 20 }}>
<Spin />
</div>
);
}
if (isError) {
return <Alert type="error" message="Не удалось загрузить список команды" />;
}
return (
<>
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message="Приглашение сотрудников"
description="Чтобы добавить сотрудника, отправьте ему ссылку-приглашение. Ссылку можно сгенерировать в Telegram-боте в меню «Управление сервером»."
/>
<List
itemLayout="horizontal"
dataSource={users || []}
renderItem={(user) => (
<List.Item
actions={[
// Селектор роли (только для Владельца и не для себя)
currentUserRole === "OWNER" && !user.is_me ? (
<Select
key="role-select"
defaultValue={user.role}
size="small"
style={{ width: 110 }}
disabled={updateRoleMutation.isPending}
onChange={(val) =>
updateRoleMutation.mutate({
userId: user.user_id,
newRole: val,
})
}
options={[
{ value: "ADMIN", label: "Админ" },
{ value: "OPERATOR", label: "Оператор" },
]}
/>
) : (
<Tag key="role-tag" color={getRoleColor(user.role)}>
{getRoleName(user.role)}
</Tag>
),
// Кнопка удаления
<Popconfirm
key="delete"
title="Закрыть доступ?"
description={`Вы уверены, что хотите удалить ${user.first_name}?`}
onConfirm={() => removeUserMutation.mutate(user.user_id)}
disabled={!canDelete(user)}
okText="Да"
cancelText="Нет"
>
<Button
danger
type="text"
icon={<DeleteOutlined />}
disabled={!canDelete(user) || removeUserMutation.isPending}
/>
</Popconfirm>,
]}
>
<List.Item.Meta
avatar={
<Avatar src={user.photo_url} icon={<UserOutlined />}>
{user.first_name?.[0]}
</Avatar>
}
title={
<span>
{user.first_name} {user.last_name}{" "}
{user.is_me && <Text type="secondary">(Вы)</Text>}
</span>
}
description={
user.username ? (
<a
href={`https://t.me/${user.username}`}
target="_blank"
rel="noopener noreferrer"
style={{ fontSize: 12 }}
>
@{user.username}
</a>
) : (
<Text type="secondary" style={{ fontSize: 12 }}>
Нет username
</Text>
)
}
/>
</List.Item>
)}
/>
</>
);
};
```
# ===================================================================
# Файл: src/hooks/useOcr.ts
# ===================================================================
```
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '../services/api';
import type { MatchRequest, ProductMatch, CatalogItem, UnmatchedItem } from '../services/types';
import { message } from 'antd';
export const useOcr = () => {
const queryClient = useQueryClient();
const catalogQuery = useQuery<CatalogItem[], Error>({
queryKey: ['catalog'],
queryFn: api.getCatalogItems,
staleTime: 1000 * 60 * 5,
});
const matchesQuery = useQuery<ProductMatch[], Error>({
queryKey: ['matches'],
queryFn: api.getMatches,
});
const unmatchedQuery = useQuery<UnmatchedItem[], Error>({
queryKey: ['unmatched'],
queryFn: api.getUnmatched,
staleTime: 0,
});
const createMatchMutation = useMutation({
// Теперь типы совпадают, any не нужен
mutationFn: (newMatch: MatchRequest) => api.createMatch(newMatch),
onSuccess: () => {
message.success('Связь сохранена');
queryClient.invalidateQueries({ queryKey: ['matches'] });
queryClient.invalidateQueries({ queryKey: ['unmatched'] });
},
onError: () => {
message.error('Ошибка при сохранении');
},
});
const deleteMatchMutation = useMutation({
mutationFn: (rawName: string) => api.deleteMatch(rawName),
onSuccess: () => {
message.success('Связь удалена');
queryClient.invalidateQueries({ queryKey: ['matches'] });
queryClient.invalidateQueries({ queryKey: ['unmatched'] });
},
onError: () => {
message.error('Ошибка при удалении связи');
},
});
const deleteUnmatchedMutation = useMutation({
mutationFn: (rawName: string) => api.deleteUnmatched(rawName),
onSuccess: () => {
message.success('Нераспознанная строка удалена');
queryClient.invalidateQueries({ queryKey: ['unmatched'] });
},
onError: () => {
message.error('Ошибка при удалении нераспознанной строки');
},
});
return {
catalog: catalogQuery.data || [],
matches: matchesQuery.data || [],
unmatched: unmatchedQuery.data || [],
isLoading: catalogQuery.isPending || matchesQuery.isPending,
isError: catalogQuery.isError || matchesQuery.isError,
createMatch: createMatchMutation.mutate,
isCreating: createMatchMutation.isPending,
deleteMatch: deleteMatchMutation.mutate,
isDeletingMatch: deleteMatchMutation.isPending,
deleteUnmatched: deleteUnmatchedMutation.mutate,
isDeletingUnmatched: deleteUnmatchedMutation.isPending,
};
};
```
# ===================================================================
# Файл: src/hooks/useRecommendations.ts
# ===================================================================
```
import { useQuery } from '@tanstack/react-query';
import { api } from '../services/api';
import type { Recommendation } from '../services/types';
export const useRecommendations = () => {
return useQuery<Recommendation[], Error>({
queryKey: ['recommendations'],
queryFn: api.getRecommendations,
// Обновлять данные каждые 30 секунд, если вкладка активна
refetchInterval: 30000,
});
};
```
# ===================================================================
# Файл: src/main.tsx
# ===================================================================
```
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
// Если есть глобальные стили, они подключаются тут.
// Если файла index.css нет, убери эту строку.
// import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
```
# ===================================================================
# Файл: src/pages/Dashboard.tsx
# ===================================================================
```
import React from 'react';
import { Typography, Row, Col, Statistic, Spin, Alert, Empty } from 'antd';
import { useRecommendations } from '../hooks/useRecommendations';
import { RecommendationCard } from '../components/recommendations/RecommendationCard';
const { Title } = Typography;
export const Dashboard: React.FC = () => {
const { data: recommendations, isPending, isError, error } = useRecommendations();
if (isPending) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}>
<Spin size="large" tip="Загрузка аналитики..." />
</div>
);
}
if (isError) {
return (
<Alert
message="Ошибка загрузки"
description={error?.message || 'Не удалось получить данные с сервера'}
type="error"
showIcon
/>
);
}
// Группировка для статистики
const unusedCount = recommendations?.filter(r => r.Type === 'UNUSED_IN_RECIPES').length || 0;
const noIncomingCount = recommendations?.filter(r => r.Type === 'NO_INCOMING').length || 0;
return (
<div>
<Title level={4} style={{ marginTop: 0 }}>Сводка проблем</Title>
{/* Блок статистики */}
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={12}>
<Statistic title="Без техкарт" value={unusedCount} valueStyle={{ color: '#cf1322' }} />
</Col>
<Col span={12}>
<Statistic title="Без закупок" value={noIncomingCount} valueStyle={{ color: '#d48806' }} />
</Col>
</Row>
<Title level={5}>Рекомендации ({recommendations?.length})</Title>
{recommendations && recommendations.length > 0 ? (
recommendations.map((rec) => (
<RecommendationCard key={rec.ID} item={rec} />
))
) : (
<Empty description="Проблем не обнаружено" />
)}
</div>
);
};
```
# ===================================================================
# Файл: src/pages/DraftsList.tsx
# ===================================================================
```
// src/pages/DraftsList.tsx
import React, { useState, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import {
List,
Typography,
Tag,
Spin,
Empty,
Flex,
Button,
Select,
DatePicker,
} from "antd";
import { useNavigate } from "react-router-dom";
import {
CheckCircleOutlined,
DeleteOutlined,
PlusOutlined,
ExclamationCircleOutlined,
LoadingOutlined,
CloseCircleOutlined,
StopOutlined,
SyncOutlined,
CloudServerOutlined,
} from "@ant-design/icons";
import dayjs from "dayjs";
import "dayjs/locale/ru";
import { api } from "../services/api";
import type { UnifiedInvoice } from "../services/types";
const { Title, Text } = Typography;
type FilterType = "ALL" | "DRAFT" | "SYNCED";
dayjs.locale("ru");
const DayDivider: React.FC<{ date: string }> = ({ date }) => {
const d = dayjs(date);
const dayOfWeek = d.format("dddd");
const formattedDate = d.format("D MMMM YYYY");
return (
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
padding: "16px 0 8px",
borderBottom: "1px solid #f0f0f0",
marginBottom: 8,
}}
>
<Text strong style={{ fontSize: 14, color: "#1890ff" }}>
{formattedDate}
</Text>
<Text type="secondary" style={{ fontSize: 12 }}>
{dayOfWeek}
</Text>
</div>
);
};
export const DraftsList: React.FC = () => {
const navigate = useNavigate();
const [syncLoading, setSyncLoading] = useState(false);
const [filterType, setFilterType] = useState<FilterType>("ALL");
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [startDate, setStartDate] = useState<dayjs.Dayjs | null>(
dayjs().subtract(30, "day")
);
const [endDate, setEndDate] = useState<dayjs.Dayjs | null>(dayjs());
const {
data: invoices,
isLoading,
isError,
refetch,
} = useQuery({
queryKey: [
"drafts",
startDate?.format("YYYY-MM-DD"),
endDate?.format("YYYY-MM-DD"),
],
queryFn: () =>
api.getDrafts(
startDate?.format("YYYY-MM-DD"),
endDate?.format("YYYY-MM-DD")
),
staleTime: 0,
refetchOnMount: true,
refetchOnWindowFocus: true,
});
const handleSync = async () => {
setSyncLoading(true);
try {
await api.syncInvoices();
refetch();
} finally {
setSyncLoading(false);
}
};
const getStatusTag = (item: UnifiedInvoice) => {
switch (item.status) {
case "PROCESSING":
return (
<Tag icon={<LoadingOutlined />} color="blue">
Обработка
</Tag>
);
case "READY_TO_VERIFY":
return (
<Tag icon={<ExclamationCircleOutlined />} color="orange">
Проверка
</Tag>
);
case "COMPLETED":
return (
<Tag icon={<CheckCircleOutlined />} color="green">
Готово
</Tag>
);
case "ERROR":
return (
<Tag icon={<CloseCircleOutlined />} color="red">
Ошибка
</Tag>
);
case "CANCELED":
return (
<Tag icon={<StopOutlined />} color="default">
Отменен
</Tag>
);
case "NEW":
return (
<Tag icon={<PlusOutlined />} color="blue">
Новая
</Tag>
);
case "PROCESSED":
return (
<Tag icon={<CheckCircleOutlined />} color="green">
Проведена
</Tag>
);
case "DELETED":
return (
<Tag icon={<DeleteOutlined />} color="red">
Удалена
</Tag>
);
default:
return <Tag>{item.status}</Tag>;
}
};
const handleInvoiceClick = (item: UnifiedInvoice) => {
if (item.type === "DRAFT") {
navigate("/invoice/draft/" + item.id);
} else if (item.type === "SYNCED") {
navigate("/invoice/view/" + item.id);
}
};
const handleFilterChange = (value: FilterType) => {
setFilterType(value);
setCurrentPage(1);
};
const handlePageSizeChange = (value: number) => {
setPageSize(value);
setCurrentPage(1);
};
const getItemDate = (item: UnifiedInvoice) =>
item.type === "DRAFT" ? item.created_at : item.date_incoming;
const filteredAndSortedInvoices = useMemo(() => {
if (!invoices || invoices.length === 0) return [];
let result = [...invoices];
if (filterType !== "ALL") {
result = result.filter((item) => item.type === filterType);
}
result.sort((a, b) => {
const dateA = dayjs(getItemDate(a)).startOf("day");
const dateB = dayjs(getItemDate(b)).startOf("day");
// Сначала по дате DESC
if (!dateA.isSame(dateB)) {
return dateB.valueOf() - dateA.valueOf();
}
// Внутри дня: DRAFT < SYNCED
if (a.type !== b.type) {
return a.type === "DRAFT" ? -1 : 1;
}
// Внутри типа: по номеру DESC
return (b.document_number || "").localeCompare(
a.document_number || "",
"ru",
{ numeric: true }
);
});
return result;
}, [invoices, filterType]);
const paginatedInvoices = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
return filteredAndSortedInvoices.slice(startIndex, startIndex + pageSize);
}, [filteredAndSortedInvoices, currentPage, pageSize]);
const groupedInvoices = useMemo(() => {
const groups: { [key: string]: UnifiedInvoice[] } = {};
paginatedInvoices.forEach((item) => {
const dateKey = dayjs(getItemDate(item)).format("YYYY-MM-DD");
if (!groups[dateKey]) {
groups[dateKey] = [];
}
groups[dateKey].push(item);
});
return groups;
}, [paginatedInvoices]);
const filterCounts = useMemo(() => {
if (!invoices) return { all: 0, draft: 0, synced: 0 };
return {
all: invoices.length,
draft: invoices.filter((item) => item.type === "DRAFT").length,
synced: invoices.filter((item) => item.type === "SYNCED").length,
};
}, [invoices]);
const totalPages = Math.ceil(
(filteredAndSortedInvoices.length || 0) / pageSize
);
if (isError) {
return (
<div style={{ padding: 20 }}>
<Text type="danger">Ошибка загрузки списка накладных</Text>
</div>
);
}
return (
<div style={{ padding: "0 4px 20px" }}>
<Flex
align="center"
justify="space-between"
style={{ marginTop: 16, marginBottom: 16 }}
>
<Flex align="center" gap={8}>
<Title level={4} style={{ margin: 0 }}>
Накладные
</Title>
<Button
icon={<SyncOutlined />}
loading={syncLoading}
onClick={handleSync}
/>
</Flex>
<Select
value={filterType}
onChange={handleFilterChange}
options={[
{ label: `Все (${filterCounts.all})`, value: "ALL" },
{ label: `Черновики (${filterCounts.draft})`, value: "DRAFT" },
{ label: `Накладные (${filterCounts.synced})`, value: "SYNCED" },
]}
size="small"
style={{ width: 140 }}
/>
</Flex>
<div
style={{
marginBottom: 12,
background: "#fff",
padding: 12,
borderRadius: 8,
}}
>
<Flex align="center" gap={8}>
<Text style={{ fontSize: 13 }}>Период:</Text>
<DatePicker
value={startDate}
onChange={setStartDate}
format="DD.MM.YYYY"
size="small"
placeholder="Начало"
style={{ width: 110 }}
/>
<Text type="secondary">—</Text>
<DatePicker
value={endDate}
onChange={setEndDate}
format="DD.MM.YYYY"
size="small"
placeholder="Конец"
style={{ width: 110 }}
/>
</Flex>
</div>
{isLoading ? (
<div style={{ textAlign: "center", padding: 40 }}>
<Spin size="large" />
</div>
) : !invoices || invoices.length === 0 ? (
<Empty description="Нет данных" />
) : (
<>
<div>
{Object.entries(groupedInvoices).map(([dateKey, items]) => (
<div key={dateKey}>
<DayDivider date={dateKey} />
{items.map((item) => {
const isSynced = item.type === "SYNCED";
const displayDate =
item.type === "DRAFT"
? item.created_at
: item.date_incoming;
return (
<List.Item
key={item.id}
style={{
background: isSynced ? "#fafafa" : "#fff",
padding: 12,
marginBottom: 10,
borderRadius: 12,
cursor: "pointer",
border: isSynced
? "1px solid #f0f0f0"
: "1px solid #e6f7ff",
boxShadow: "0 2px 4px rgba(0,0,0,0.02)",
display: "block",
position: "relative",
}}
onClick={() => handleInvoiceClick(item)}
>
<div
style={{
position: "absolute",
top: 12,
right: 12,
}}
>
{getStatusTag(item)}
</div>
<Flex vertical gap={4}>
<Flex align="center" gap={8}>
<Text strong style={{ fontSize: 16 }}>
{item.document_number || "Без номера"}
</Text>
{item.type === "SYNCED" && (
<CloudServerOutlined style={{ color: "gray" }} />
)}
{item.is_app_created && (
<span title="Создано в RMSer">📱</span>
)}
</Flex>
<Flex vertical gap={2}>
<Text type="secondary" style={{ fontSize: 13 }}>
{item.items_count} поз.
</Text>
<Text type="secondary" style={{ fontSize: 13 }}>
{dayjs(displayDate).format("DD.MM.YYYY")}
</Text>
</Flex>
{item.incoming_number && (
<Text type="secondary" style={{ fontSize: 12 }}>
Вх. № {item.incoming_number}
</Text>
)}
<Flex justify="space-between" align="center">
<div></div>
{item.store_name && (
<Tag
style={{
margin: 0,
maxWidth: 120,
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{item.store_name}
</Tag>
)}
</Flex>
<Flex
justify="space-between"
align="center"
style={{ marginTop: 8 }}
>
<Text
strong
style={{
fontSize: 17,
color: isSynced ? "#595959" : "#1890ff",
}}
>
{item.total_sum.toLocaleString("ru-RU", {
style: "currency",
currency: "RUB",
maximumFractionDigits: 0,
})}
</Text>
{item.items_preview && (
<div
style={{
textAlign: "right",
maxWidth: 150,
}}
>
{item.items_preview
.split(", ")
.slice(0, 3)
.map((previewItem, idx) => (
<div
key={idx}
style={{
fontSize: 12,
color: "#666",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{previewItem}
</div>
))}
</div>
)}
</Flex>
</Flex>
</List.Item>
);
})}
</div>
))}
</div>
{totalPages > 1 && (
<Flex
justify="space-between"
align="center"
style={{
marginTop: 16,
padding: "8px 12px",
background: "#fff",
borderRadius: 8,
}}
>
<Flex align="center" gap={8}>
<Text style={{ fontSize: 12 }}>На странице:</Text>
<Select
value={pageSize}
onChange={handlePageSizeChange}
options={[
{ label: "10", value: 10 },
{ label: "20", value: 20 },
{ label: "50", value: 50 },
]}
size="small"
style={{ width: 70 }}
/>
</Flex>
<Flex align="center" gap={8}>
<Text style={{ fontSize: 13 }}>
Стр. {currentPage} из {totalPages}
</Text>
<Button
size="small"
disabled={currentPage === 1}
onClick={() => setCurrentPage((prev) => prev - 1)}
>
</Button>
<Button
size="small"
disabled={currentPage === totalPages}
onClick={() => setCurrentPage((prev) => prev + 1)}
>
</Button>
</Flex>
</Flex>
)}
</>
)}
</div>
);
};
```
# ===================================================================
# Файл: src/pages/InvoiceDraftPage.tsx
# ===================================================================
```
import React, { useEffect, useMemo, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import {
Spin,
Alert,
Button,
Form,
Select,
DatePicker,
Input,
Typography,
message,
Row,
Col,
Affix,
Modal,
Tag,
Image,
} from "antd";
import {
ArrowLeftOutlined,
CheckOutlined,
DeleteOutlined,
ExclamationCircleFilled,
RestOutlined,
PlusOutlined,
FileImageOutlined,
SwapOutlined,
} from "@ant-design/icons";
import dayjs from "dayjs";
import { api, getStaticUrl } from "../services/api";
import { DraftItemRow } from "../components/invoices/DraftItemRow";
import type {
UpdateDraftItemRequest,
CommitDraftRequest,
ReorderDraftItemsRequest,
} from "../services/types";
import { DragDropContext, Droppable, type DropResult } from "@hello-pangea/dnd";
const { Text } = Typography;
const { TextArea } = Input;
const { confirm } = Modal;
export const InvoiceDraftPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const queryClient = useQueryClient();
const [form] = Form.useForm();
const [updatingItems, setUpdatingItems] = useState<Set<string>>(new Set());
const [itemsOrder, setItemsOrder] = useState<Record<string, number>>({});
const [isDragging, setIsDragging] = useState(false);
const [isReordering, setIsReordering] = useState(false);
// Состояние для просмотра фото чека
const [previewVisible, setPreviewVisible] = useState(false);
// --- ЗАПРОСЫ ---
const dictQuery = useQuery({
queryKey: ["dictionaries"],
queryFn: api.getDictionaries,
staleTime: 1000 * 60 * 5,
});
const recommendationsQuery = useQuery({
queryKey: ["recommendations"],
queryFn: api.getRecommendations,
});
const draftQuery = useQuery({
queryKey: ["draft", id],
queryFn: () => api.getDraft(id!),
enabled: !!id,
refetchInterval: (query) => {
if (isDragging) return false;
const status = query.state.data?.status;
return status === "PROCESSING" ? 3000 : false;
},
});
const draft = draftQuery.data;
const stores = dictQuery.data?.stores || [];
const suppliers = dictQuery.data?.suppliers || [];
// --- МУТАЦИИ ---
const updateItemMutation = useMutation({
mutationFn: (vars: { itemId: string; payload: UpdateDraftItemRequest }) =>
api.updateDraftItem(id!, vars.itemId, vars.payload),
onMutate: async ({ itemId }) => {
setUpdatingItems((prev) => new Set(prev).add(itemId));
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["draft", id] });
},
onError: () => {
message.error("Не удалось сохранить строку");
},
onSettled: (_data, _err, vars) => {
setUpdatingItems((prev) => {
const next = new Set(prev);
next.delete(vars.itemId);
return next;
});
},
});
const addItemMutation = useMutation({
mutationFn: () => api.addDraftItem(id!),
onSuccess: () => {
message.success("Строка добавлена");
queryClient.invalidateQueries({ queryKey: ["draft", id] });
},
onError: () => {
message.error("Ошибка создания строки");
},
});
const deleteItemMutation = useMutation({
mutationFn: (itemId: string) => api.deleteDraftItem(id!, itemId),
onSuccess: () => {
message.success("Строка удалена");
queryClient.invalidateQueries({ queryKey: ["draft", id] });
},
onError: () => {
message.error("Ошибка удаления строки");
},
});
const commitMutation = useMutation({
mutationFn: (payload: CommitDraftRequest) => api.commitDraft(id!, payload),
onSuccess: (data) => {
message.success(`Накладная ${data.document_number} создана!`);
navigate("/invoices");
queryClient.invalidateQueries({ queryKey: ["drafts"] });
},
onError: () => {
message.error("Ошибка при создании накладной");
},
});
const deleteDraftMutation = useMutation({
mutationFn: () => api.deleteDraft(id!),
onSuccess: () => {
if (draft?.status === "CANCELED") {
message.info("Черновик удален окончательно");
navigate("/invoices");
} else {
message.warning("Черновик отменен");
queryClient.invalidateQueries({ queryKey: ["draft", id] });
}
},
onError: () => {
message.error("Ошибка при удалении");
},
});
const reorderItemsMutation = useMutation({
mutationFn: ({
draftId,
payload,
}: {
draftId: string;
payload: ReorderDraftItemsRequest;
}) => api.reorderDraftItems(draftId, payload),
onError: (error) => {
message.error("Не удалось изменить порядок элементов");
console.error("Reorder error:", error);
},
});
// --- ЭФФЕКТЫ ---
useEffect(() => {
if (draft) {
const currentValues = form.getFieldsValue();
if (!currentValues.store_id && draft.store_id)
form.setFieldValue("store_id", draft.store_id);
if (!currentValues.supplier_id && draft.supplier_id)
form.setFieldValue("supplier_id", draft.supplier_id);
if (!currentValues.comment && draft.comment)
form.setFieldValue("comment", draft.comment);
// Инициализация входящего номера
if (
!currentValues.incoming_document_number &&
draft.incoming_document_number
)
form.setFieldValue(
"incoming_document_number",
draft.incoming_document_number
);
if (!currentValues.date_incoming)
form.setFieldValue(
"date_incoming",
draft.date_incoming ? dayjs(draft.date_incoming) : dayjs()
);
}
}, [draft, form]);
// --- ХЕЛПЕРЫ ---
const totalSum = useMemo(() => {
return (
draft?.items.reduce(
(acc, item) => acc + Number(item.quantity) * Number(item.price),
0
) || 0
);
}, [draft?.items]);
const invalidItemsCount = useMemo(() => {
return draft?.items.filter((i) => !i.product_id).length || 0;
}, [draft?.items]);
const handleItemUpdate = (
itemId: string,
changes: UpdateDraftItemRequest
) => {
updateItemMutation.mutate({ itemId, payload: changes });
};
const handleCommit = async () => {
try {
const values = await form.validateFields();
if (invalidItemsCount > 0) {
message.warning(
`Осталось ${invalidItemsCount} нераспознанных товаров!`
);
return;
}
commitMutation.mutate({
date_incoming: values.date_incoming.format("YYYY-MM-DD"),
store_id: values.store_id,
supplier_id: values.supplier_id,
comment: values.comment || "",
incoming_document_number: values.incoming_document_number || "",
});
} catch {
message.error("Заполните обязательные поля (Склад, Поставщик)");
}
};
const isCanceled = draft?.status === "CANCELED";
const handleDelete = () => {
confirm({
title: isCanceled ? "Удалить окончательно?" : "Отменить черновик?",
icon: <ExclamationCircleFilled style={{ color: "red" }} />,
content: isCanceled
? "Черновик пропадет из списка навсегда."
: 'Черновик получит статус "Отменен", но останется в списке.',
okText: isCanceled ? "Удалить навсегда" : "Отменить",
okType: "danger",
cancelText: "Назад",
onOk() {
deleteDraftMutation.mutate();
},
});
};
const handleDragStart = () => {
setIsDragging(true);
};
const handleDragEnd = async (result: DropResult) => {
setIsDragging(false);
const { source, destination } = result;
// Если нет назначения или позиция не изменилась
if (
!destination ||
(source.droppableId === destination.droppableId &&
source.index === destination.index)
) {
return;
}
if (!draft) return;
// Сохраняем предыдущее состояние для отката
const previousItems = [...draft.items];
const previousOrder = { ...itemsOrder };
// Создаём новый массив с изменённым порядком
const newItems = [...draft.items];
const [removed] = newItems.splice(source.index, 1);
newItems.splice(destination.index, 0, removed);
// Обновляем локальное состояние немедленно для быстрого UI
queryClient.setQueryData(["draft", id], {
...draft,
items: newItems,
});
// Подготавливаем payload для API
const reorderPayload: ReorderDraftItemsRequest = {
items: newItems.map((item, index) => ({
id: item.id,
order: index,
})),
};
// Отправляем запрос на сервер
try {
await reorderItemsMutation.mutateAsync({
draftId: draft.id,
payload: reorderPayload,
});
} catch {
// При ошибке откатываем локальное состояние
queryClient.setQueryData(["draft", id], {
...draft,
items: previousItems,
});
setItemsOrder(previousOrder);
}
};
// --- RENDER ---
const showSpinner =
draftQuery.isLoading ||
(draft?.status === "PROCESSING" &&
(!draft?.items || draft.items.length === 0));
if (showSpinner) {
return (
<div style={{ textAlign: "center", padding: 50 }}>
<Spin size="large" />
</div>
);
}
if (draftQuery.isError || !draft) {
return <Alert type="error" message="Ошибка загрузки черновика" />;
}
return (
<div style={{ paddingBottom: 60 }}>
{/* Header */}
<div
style={{
marginBottom: 12,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
flex: 1,
minWidth: 0,
}}
>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate("/invoices")}
size="small"
/>
<div
style={{
display: "flex",
alignItems: "center",
gap: 6,
flexWrap: "wrap",
}}
>
<span
style={{ fontSize: 18, fontWeight: "bold", whiteSpace: "nowrap" }}
>
{draft.document_number ? `№${draft.document_number}` : "Черновик"}
</span>
{draft.status === "PROCESSING" && <Spin size="small" />}
{isCanceled && (
<Tag color="red" style={{ margin: 0 }}>
ОТМЕНЕН
</Tag>
)}
</div>
</div>
{/* Правая часть хедера: Кнопка чека, Кнопка перетаскивания и Кнопка удаления */}
<div style={{ display: "flex", gap: 8 }}>
{/* Кнопка просмотра чека (только если есть URL) */}
{draft.photo_url && (
<Button
icon={<FileImageOutlined />}
onClick={() => setPreviewVisible(true)}
size="small"
>
Чек
</Button>
)}
{/* Кнопка переключения режима перетаскивания */}
<Button
type={isReordering ? "primary" : "default"}
icon={<SwapOutlined rotate={90} />}
onClick={() => setIsReordering(!isReordering)}
size="small"
>
{isReordering ? "Ок" : ""}
</Button>
<Button
danger={isCanceled}
type={isCanceled ? "primary" : "default"}
icon={isCanceled ? <DeleteOutlined /> : <RestOutlined />}
onClick={handleDelete}
loading={deleteDraftMutation.isPending}
size="small"
>
{isCanceled ? "Удалить" : "Отмена"}
</Button>
</div>
</div>
{/* Form: Склады и Поставщики */}
<div
style={{
background: "#fff",
padding: 12,
borderRadius: 8,
marginBottom: 12,
opacity: isCanceled ? 0.6 : 1,
}}
>
<Form
form={form}
layout="vertical"
initialValues={{ date_incoming: dayjs() }}
>
<Row gutter={10}>
<Col span={12}>
<Form.Item
label="Дата"
name="date_incoming"
rules={[{ required: true }]}
style={{ marginBottom: 8 }}
>
<DatePicker
style={{ width: "100%" }}
format="DD.MM.YYYY"
size="middle"
/>
</Form.Item>
</Col>
<Col span={12}>
{/* Входящий номер */}
<Form.Item
label="Входящий номер"
name="incoming_document_number"
style={{ marginBottom: 8 }}
>
<Input placeholder="№ Документа" size="middle" />
</Form.Item>
</Col>
</Row>
<Row gutter={10}>
<Col span={24}>
<Form.Item
label="Склад"
name="store_id"
rules={[{ required: true, message: "Выберите склад" }]}
style={{ marginBottom: 8 }}
>
<Select
placeholder="Куда?"
loading={dictQuery.isLoading}
options={stores.map((s) => ({ label: s.name, value: s.id }))}
size="middle"
/>
</Form.Item>
</Col>
</Row>
<Form.Item
label="Поставщик"
name="supplier_id"
rules={[{ required: true, message: "Выберите поставщика" }]}
style={{ marginBottom: 8 }}
>
<Select
placeholder="От кого?"
loading={dictQuery.isLoading}
options={suppliers.map((s) => ({ label: s.name, value: s.id }))}
size="middle"
showSearch
filterOption={(input, option) =>
(option?.label ?? "")
.toLowerCase()
.includes(input.toLowerCase())
}
/>
</Form.Item>
<Form.Item
label="Комментарий"
name="comment"
style={{ marginBottom: 0 }}
>
<TextArea
rows={1}
placeholder="Комментарий..."
style={{ fontSize: 13 }}
/>
</Form.Item>
</Form>
</div>
{/* Items Header */}
<div
style={{
marginBottom: 8,
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "0 4px",
}}
>
<Text strong>Позиции ({draft.items.length})</Text>
{invalidItemsCount > 0 && (
<Text type="danger" style={{ fontSize: 12 }}>
{invalidItemsCount} нераспознано
</Text>
)}
</div>
{/* Items List */}
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
<Droppable droppableId="draft-items">
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{
display: "flex",
flexDirection: "column",
gap: 8,
backgroundColor: snapshot.isDraggingOver
? "#f0f0f0"
: "transparent",
borderRadius: "4px",
padding: snapshot.isDraggingOver ? "8px" : "0",
transition: "background-color 0.2s ease",
}}
>
{draft.items.map((item, index) => (
<DraftItemRow
key={item.id}
item={item}
index={index}
onUpdate={handleItemUpdate}
onDelete={(itemId) => deleteItemMutation.mutate(itemId)}
isUpdating={updatingItems.has(item.id)}
recommendations={recommendationsQuery.data || []}
isReordering={isReordering}
/>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
{/* Кнопка добавления позиции */}
<Button
type="dashed"
block
icon={<PlusOutlined />}
style={{ marginTop: 12, marginBottom: 80, height: 48 }}
onClick={() => addItemMutation.mutate()}
loading={addItemMutation.isPending}
disabled={isCanceled}
>
Добавить товар
</Button>
{/* Footer Actions */}
<Affix offsetBottom={60}>
<div
style={{
background: "#fff",
padding: "8px 16px",
borderTop: "1px solid #eee",
boxShadow: "0 -2px 10px rgba(0,0,0,0.05)",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
borderRadius: "8px 8px 0 0",
}}
>
<div style={{ display: "flex", flexDirection: "column" }}>
<span style={{ fontSize: 11, color: "#888", lineHeight: 1 }}>
Итого:
</span>
<span
style={{
fontSize: 18,
fontWeight: "bold",
color: "#1890ff",
lineHeight: 1.2,
}}
>
{totalSum.toLocaleString("ru-RU", {
style: "currency",
currency: "RUB",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}
</span>
</div>
<Button
type="primary"
icon={<CheckOutlined />}
onClick={handleCommit}
loading={commitMutation.isPending}
disabled={invalidItemsCount > 0 || isCanceled}
style={{ height: 40, padding: "0 24px" }}
>
{isCanceled ? "Восстановить" : "Отправить"}
</Button>
</div>
</Affix>
{/* Скрытый компонент для просмотра изображения */}
{draft.photo_url && (
<div style={{ display: "none" }}>
<Image.PreviewGroup
preview={{
visible: previewVisible,
onVisibleChange: (vis) => setPreviewVisible(vis),
movable: true,
scaleStep: 0.5,
}}
>
<Image src={getStaticUrl(draft.photo_url)} />
</Image.PreviewGroup>
</div>
)}
</div>
);
};
```
# ===================================================================
# Файл: src/pages/InvoiceViewPage.tsx
# ===================================================================
```
// src/pages/InvoiceViewPage.tsx
import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { Spin, Alert, Button, Table, Typography, Tag, Image } from "antd";
import {
ArrowLeftOutlined,
FileImageOutlined,
HistoryOutlined,
} from "@ant-design/icons";
import { useNavigate } from "react-router-dom";
import { api, getStaticUrl } from "../services/api";
import type { DraftStatus } from "../services/types";
const { Title, Text } = Typography;
export const InvoiceViewPage: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
// Состояние для просмотра фото чека
const [previewVisible, setPreviewVisible] = useState(false);
// Запрос данных накладной
const {
data: invoice,
isLoading,
isError,
} = useQuery({
queryKey: ["invoice", id],
queryFn: () => api.getInvoice(id!),
enabled: !!id,
});
const getStatusTag = (status: DraftStatus) => {
switch (status) {
case "PROCESSING":
return <Tag color="blue">Обработка</Tag>;
case "READY_TO_VERIFY":
return <Tag color="orange">Проверка</Tag>;
case "COMPLETED":
return (
<Tag icon={<HistoryOutlined />} color="success">
Синхронизировано
</Tag>
);
case "ERROR":
return <Tag color="red">Ошибка</Tag>;
case "CANCELED":
return <Tag color="default">Отменен</Tag>;
default:
return <Tag>{status}</Tag>;
}
};
if (isLoading) {
return (
<div style={{ textAlign: "center", padding: 50 }}>
<Spin size="large" />
</div>
);
}
if (isError || !invoice) {
return <Alert type="error" message="Ошибка загрузки накладной" />;
}
const columns = [
{
title: "Товар",
dataIndex: "name",
key: "name",
},
{
title: "Кол-во",
dataIndex: "quantity",
key: "quantity",
align: "right" as const,
},
{
title: "Сумма",
dataIndex: "total",
key: "total",
align: "right" as const,
render: (total: number) =>
total.toLocaleString("ru-RU", {
style: "currency",
currency: "RUB",
}),
},
];
const totalSum = (invoice.items || []).reduce(
(acc, item) => acc + item.total,
0
);
return (
<div style={{ paddingBottom: 20 }}>
{/* Header */}
<div
style={{
marginBottom: 16,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 8,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
flex: 1,
minWidth: 0,
}}
>
<Button
icon={<ArrowLeftOutlined />}
onClick={() => navigate("/invoices")}
size="small"
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
}}
>
<Title level={4} style={{ margin: 0 }}>
№{invoice.number}
</Title>
<Text type="secondary" style={{ fontSize: 12 }}>
{invoice.date} • {invoice.supplier.name}
</Text>
</div>
</div>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
{getStatusTag(invoice.status)}
{/* Кнопка просмотра чека (только если есть URL) */}
{invoice.photo_url && (
<Button
icon={<FileImageOutlined />}
onClick={() => setPreviewVisible(true)}
size="small"
>
Чек
</Button>
)}
</div>
</div>
{/* Таблица товаров */}
<div
style={{
background: "#fff",
padding: 16,
borderRadius: 8,
marginBottom: 16,
}}
>
<Title level={5} style={{ marginBottom: 16 }}>
Товары ({(invoice.items || []).length} поз.)
</Title>
<Table
columns={columns}
dataSource={invoice.items || []}
pagination={false}
rowKey="name"
size="small"
summary={() => (
<Table.Summary.Row>
<Table.Summary.Cell index={0} colSpan={2}>
<Text strong>Итого:</Text>
</Table.Summary.Cell>
<Table.Summary.Cell index={2} align="right">
<Text strong>
{totalSum.toLocaleString("ru-RU", {
style: "currency",
currency: "RUB",
})}
</Text>
</Table.Summary.Cell>
</Table.Summary.Row>
)}
/>
</div>
{/* Скрытый компонент для просмотра изображения */}
{invoice.photo_url && (
<div style={{ display: "none" }}>
<Image.PreviewGroup
preview={{
visible: previewVisible,
onVisibleChange: (vis) => setPreviewVisible(vis),
movable: true,
scaleStep: 0.5,
}}
>
<Image src={getStaticUrl(invoice.photo_url)} />
</Image.PreviewGroup>
</div>
)}
</div>
);
};
```
# ===================================================================
# Файл: src/pages/MaintenancePage.tsx
# ===================================================================
```
import { Result, Button } from "antd";
// Страница-заглушка для режима технического обслуживания
const MaintenancePage = () => (
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#fff",
padding: 20,
}}
>
<Result
status="warning"
title="Сервис на техническом обслуживании"
subTitle="Мы скоро вернемся с новыми функциями!"
extra={
<Button type="primary" onClick={() => window.location.reload()}>
Попробовать снова
</Button>
}
/>
</div>
);
export default MaintenancePage;
```
# ===================================================================
# Файл: src/pages/OcrLearning.tsx
# ===================================================================
```
import React from "react";
import { Spin, Alert } from "antd";
import { useOcr } from "../hooks/useOcr";
import { AddMatchForm } from "../components/ocr/AddMatchForm";
import { MatchList } from "../components/ocr/MatchList";
export const OcrLearning: React.FC = () => {
const {
catalog,
matches,
unmatched,
isLoading,
isError,
createMatch,
isCreating,
deleteMatch,
isDeletingMatch,
deleteUnmatched,
} = useOcr();
// Состояние для редактирования
const [editingMatch, setEditingMatch] = React.useState<string | null>(null);
// Найти редактируемую связь
const currentEditingMatch = React.useMemo(() => {
if (!editingMatch) return undefined;
return matches.find((match) => match.raw_name === editingMatch);
}, [editingMatch, matches]);
if (isLoading && matches.length === 0) {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "50vh",
flexDirection: "column",
gap: 16,
}}
>
<Spin size="large" />
<span style={{ color: "#888" }}>Загрузка справочников...</span>
</div>
);
}
if (isError) {
return (
<Alert
message="Ошибка"
description="Не удалось загрузить данные."
type="error"
showIcon
style={{ margin: 16 }}
/>
);
}
return (
<div style={{ paddingBottom: 20 }}>
<AddMatchForm
catalog={catalog}
unmatched={unmatched}
// Передаем containerId
onSave={(raw, prodId, qty, contId) => {
if (currentEditingMatch) {
// Обновление существующей связи
createMatch({
raw_name: raw,
product_id: prodId,
quantity: qty,
container_id: contId,
});
setEditingMatch(null);
} else {
// Создание новой связи
createMatch({
raw_name: raw,
product_id: prodId,
quantity: qty,
container_id: contId,
});
}
}}
onDeleteUnmatched={deleteUnmatched}
isLoading={isCreating}
initialValues={currentEditingMatch}
onCancelEdit={() => setEditingMatch(null)}
/>
<h3 style={{ marginLeft: 4, marginBottom: 12 }}>
Обученные позиции ({matches.length})
</h3>
<MatchList
matches={matches}
onDeleteMatch={deleteMatch}
onEditMatch={(match) => {
setEditingMatch(match.raw_name);
}}
isDeleting={isDeletingMatch}
/>
</div>
);
};
```
# ===================================================================
# Файл: src/pages/SettingsPage.tsx
# ===================================================================
```
import React, { useEffect } from "react";
import {
Typography,
Card,
Form,
Select,
Switch,
Button,
Row,
Col,
Statistic,
TreeSelect,
Spin,
message,
Tabs,
Popconfirm,
} from "antd";
import {
SaveOutlined,
BarChartOutlined,
SettingOutlined,
FolderOpenOutlined,
TeamOutlined,
CameraOutlined,
} from "@ant-design/icons";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "../services/api";
import type { UserSettings } from "../services/types";
import { TeamList } from "../components/settings/TeamList";
import { PhotoStorageTab } from "../components/settings/PhotoStorageTab";
const { Title, Text } = Typography;
export const SettingsPage: React.FC = () => {
const queryClient = useQueryClient();
const [form] = Form.useForm();
// --- Запросы ---
const settingsQuery = useQuery({
queryKey: ["settings"],
queryFn: api.getSettings,
});
const statsQuery = useQuery({
queryKey: ["stats"],
queryFn: api.getStats,
});
const dictQuery = useQuery({
queryKey: ["dictionaries"],
queryFn: api.getDictionaries,
staleTime: 1000 * 60 * 5,
});
const groupsQuery = useQuery({
queryKey: ["productGroups"],
queryFn: api.getProductGroups,
staleTime: 1000 * 60 * 10,
});
// --- Мутации ---
const saveMutation = useMutation({
mutationFn: (vals: UserSettings) => api.updateSettings(vals),
onSuccess: () => {
message.success("Настройки сохранены");
queryClient.invalidateQueries({ queryKey: ["settings"] });
},
onError: () => {
message.error("Не удалось сохранить настройки");
},
});
const deleteAllDraftsMutation = useMutation({
mutationFn: () => api.deleteAllDrafts(),
onSuccess: (data) => {
message.success(`Удалено черновиков: ${data.count}`);
queryClient.invalidateQueries({ queryKey: ["stats"] });
},
onError: () => {
message.error("Не удалось удалить черновики");
},
});
// --- Эффекты ---
useEffect(() => {
if (settingsQuery.data) {
form.setFieldsValue(settingsQuery.data);
}
}, [settingsQuery.data, form]);
// --- Хендлеры ---
const handleSave = async () => {
try {
const values = await form.validateFields();
saveMutation.mutate(values);
} catch {
// Ошибки валидации
}
};
// --- Рендер ---
if (settingsQuery.isLoading) {
return (
<div style={{ textAlign: "center", padding: 50 }}>
<Spin size="large" />
</div>
);
}
// Определяем роль текущего пользователя
const currentUserRole = settingsQuery.data?.role || "OPERATOR";
const showTeamSettings =
currentUserRole === "ADMIN" || currentUserRole === "OWNER";
// Сохраняем JSX в переменную вместо создания вложенного компонента
const generalSettingsContent = (
<Form form={form} layout="vertical">
<Card size="small" style={{ marginBottom: 16 }}>
<Form.Item
label="Склад по умолчанию"
name="default_store_id"
tooltip="Этот склад будет выбираться автоматически при создании новой накладной"
>
<Select
placeholder="Не выбрано"
allowClear
loading={dictQuery.isLoading}
options={dictQuery.data?.stores.map((s) => ({
label: s.name,
value: s.id,
}))}
/>
</Form.Item>
<Form.Item
label="Корневая группа товаров"
name="root_group_id"
tooltip="Товары для распознавания будут искаться только внутри этой группы (и её подгрупп)."
>
<TreeSelect
showSearch
style={{ width: "100%" }}
dropdownStyle={{ maxHeight: 400, overflow: "auto" }}
placeholder="Выберите папку"
allowClear
treeDefaultExpandAll={false}
treeData={groupsQuery.data}
fieldNames={{
label: "title",
value: "value",
children: "children",
}}
treeNodeFilterProp="title"
suffixIcon={<FolderOpenOutlined />}
loading={groupsQuery.isLoading}
/>
</Form.Item>
<Form.Item
name="auto_conduct"
valuePropName="checked"
style={{ marginBottom: 0 }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<div>
<Text>Проводить накладные автоматически</Text>
<br />
<Text type="secondary" style={{ fontSize: 12 }}>
Если выключено, накладные в iiko будут создаваться как
"Непроведенные"
</Text>
</div>
<Switch />
</div>
</Form.Item>
</Card>
<Button
type="primary"
icon={<SaveOutlined />}
block
size="large"
onClick={handleSave}
loading={saveMutation.isPending}
>
Сохранить настройки
</Button>
{currentUserRole === "OWNER" && (
<Card
size="small"
style={{
marginTop: 24,
borderColor: "#ff4d4f",
borderWidth: 2,
}}
>
<Title level={5} style={{ color: "#ff4d4f", marginBottom: 16 }}>
Опасная зона
</Title>
<Popconfirm
title="Вы уверены?"
description="Это удалит ВСЕ черновики, которые еще не были отправлены в iiko. Это действие необратимо."
onConfirm={() => deleteAllDraftsMutation.mutate()}
okText="Удалить"
cancelText="Отмена"
okButtonProps={{ danger: true }}
>
<Button danger block loading={deleteAllDraftsMutation.isPending}>
Удалить все черновики
</Button>
</Popconfirm>
</Card>
)}
</Form>
);
const tabsItems = [
{
key: "general",
label: "Общие",
icon: <SettingOutlined />,
children: generalSettingsContent,
},
];
if (showTeamSettings) {
tabsItems.push({
key: "team",
label: "Команда",
icon: <TeamOutlined />,
children: (
<Card size="small">
<TeamList currentUserRole={currentUserRole} />
</Card>
),
});
}
// Добавляем вкладку с фото (доступна для OWNER)
if (currentUserRole === "OWNER") {
tabsItems.push({
key: "photos",
label: "Архив фото",
icon: <CameraOutlined />,
children: <PhotoStorageTab />,
});
}
return (
<div style={{ padding: "0 16px 80px" }}>
<Title level={4} style={{ marginTop: 16 }}>
<SettingOutlined /> Настройки
</Title>
{/* Статистика */}
<Card
size="small"
style={{
marginBottom: 16,
background: "#f0f5ff",
borderColor: "#d6e4ff",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 8,
marginBottom: 12,
}}
>
<BarChartOutlined style={{ color: "#1890ff" }} />
<Text strong>Статистика накладных</Text>
</div>
<Row gutter={16}>
<Col span={8}>
<Statistic
title="За 24ч"
value={statsQuery.data?.last_24h || 0}
valueStyle={{ fontSize: 18 }}
/>
</Col>
<Col span={8}>
<Statistic
title="Месяц"
value={statsQuery.data?.last_month || 0}
valueStyle={{ fontSize: 18 }}
/>
</Col>
<Col span={8}>
<Statistic
title="Всего"
value={statsQuery.data?.total || 0}
valueStyle={{ fontSize: 18 }}
/>
</Col>
</Row>
</Card>
{/* Табы настроек */}
<Tabs defaultActiveKey="general" items={tabsItems} />
</div>
);
};
```
# ===================================================================
# Файл: src/services/api.ts
# ===================================================================
```
import axios from 'axios';
import { notification } from 'antd';
import type {
CatalogItem,
CreateInvoiceRequest,
MatchRequest,
HealthResponse,
InvoiceResponse,
ProductMatch,
Recommendation,
UnmatchedItem,
UserSettings,
InvoiceStats,
ProductGroup,
Store,
Supplier,
DraftInvoice,
DraftItem,
UpdateDraftItemRequest,
CommitDraftRequest,
ReorderDraftItemsRequest,
ProductSearchResult,
AddContainerRequest,
AddContainerResponse,
DictionariesResponse,
UnifiedInvoice,
ServerUser,
UserRole,
InvoiceDetails,
GetPhotosResponse
} from './types';
// Базовый URL
export const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api';
// Хелпер для получения полного URL картинки (убирает /api если путь статики идет от корня, или добавляет как есть)
// В данном ТЗ сказано просто склеивать.
export const getStaticUrl = (path: string | null | undefined): string => {
if (!path) return '';
if (path.startsWith('http')) return path;
return `${API_BASE_URL}${path}`;
};
// Телеграм объект
const tg = window.Telegram?.WebApp;
// Событие для глобальной обработки 401
export const UNAUTHORIZED_EVENT = 'rms_unauthorized';
// Событие для режима технического обслуживания (503)
export const MAINTENANCE_EVENT = 'rms_maintenance';
const apiClient = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// --- Request Interceptor (Авторизация через initData) ---
apiClient.interceptors.request.use((config) => {
const initData = tg?.initData;
// Если initData пустая — мы не в Telegram. Блокируем запрос.
if (!initData) {
console.error('Запрос заблокирован: приложение запущено вне Telegram.');
return Promise.reject(new Error('MISSING_TELEGRAM_DATA'));
}
// Устанавливаем заголовок согласно новым требованиям
config.headers['Authorization'] = `Bearer ${initData}`;
return config;
});
// --- Response Interceptor (Обработка ошибок и уведомления) ---
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
// Глобальное уведомление об ошибке авторизации
notification.error({
message: 'Ошибка авторизации',
description: 'Ваша сессия в Telegram истекла или данные неверны. Попробуйте перезапустить бота.',
placement: 'top',
});
window.dispatchEvent(new Event(UNAUTHORIZED_EVENT));
}
if (error.response && error.response.status === 503) {
// Режим технического обслуживания
window.dispatchEvent(new Event(MAINTENANCE_EVENT));
}
// Если запрос был отменен нами (нет initData), не выводим стандартную ошибку API
if (error.message === 'MISSING_TELEGRAM_DATA') {
return Promise.reject(error);
}
console.error('API Error:', error);
return Promise.reject(error);
}
);
export const api = {
checkHealth: async (): Promise<HealthResponse> => {
const { data } = await apiClient.get<HealthResponse>('/health');
return data;
},
getRecommendations: async (): Promise<Recommendation[]> => {
const { data } = await apiClient.get<Recommendation[]>('/recommendations');
return data;
},
getCatalogItems: async (): Promise<CatalogItem[]> => {
const { data } = await apiClient.get<CatalogItem[]>('/ocr/catalog');
return data;
},
searchProducts: async (query: string): Promise<ProductSearchResult[]> => {
const { data } = await apiClient.get<ProductSearchResult[]>('/ocr/search', {
params: { q: query }
});
return data;
},
createContainer: async (payload: AddContainerRequest): Promise<AddContainerResponse> => {
const { data } = await apiClient.post<AddContainerResponse>('/drafts/container', payload);
return data;
},
getMatches: async (): Promise<ProductMatch[]> => {
const { data } = await apiClient.get<ProductMatch[]>('/ocr/matches');
return data;
},
getUnmatched: async (): Promise<UnmatchedItem[]> => {
const { data } = await apiClient.get<UnmatchedItem[]>('/ocr/unmatched');
return data;
},
createMatch: async (payload: MatchRequest): Promise<{ status: string }> => {
const { data } = await apiClient.post<{ status: string }>('/ocr/match', payload);
return data;
},
deleteMatch: async (rawName: string): Promise<{ status: string }> => {
const { data } = await apiClient.delete<{ status: string }>('/ocr/match', {
params: { raw_name: rawName }
});
return data;
},
deleteUnmatched: async (rawName: string): Promise<{ status: string }> => {
const { data } = await apiClient.delete<{ status: string }>('/ocr/unmatched', {
params: { raw_name: rawName }
});
return data;
},
createInvoice: async (payload: CreateInvoiceRequest): Promise<InvoiceResponse> => {
const { data } = await apiClient.post<InvoiceResponse>('/invoices/send', payload);
return data;
},
// --- НОВЫЙ МЕТОД: Получение всех справочников ---
getDictionaries: async (): Promise<DictionariesResponse> => {
const { data } = await apiClient.get<DictionariesResponse>('/dictionaries');
return data;
},
// Старые методы оставляем для совместимости, но они могут вызывать getDictionaries внутри или deprecated endpoint
getStores: async (): Promise<Store[]> => {
// Можно использовать новый эндпоинт и возвращать часть данных
const { data } = await apiClient.get<DictionariesResponse>('/dictionaries');
return data.stores;
},
getSuppliers: async (): Promise<Supplier[]> => {
// Реальный запрос вместо мока
const { data } = await apiClient.get<DictionariesResponse>('/dictionaries');
return data.suppliers;
},
// Обновленный метод получения списка накладных с фильтрацией
getDrafts: async (from?: string, to?: string): Promise<UnifiedInvoice[]> => {
const { data } = await apiClient.get<UnifiedInvoice[]>('/drafts', {
params: { from, to }
});
return data;
},
getDraft: async (id: string): Promise<DraftInvoice> => {
const { data } = await apiClient.get<DraftInvoice>(`/drafts/${id}`);
return data;
},
updateDraftItem: async (draftId: string, itemId: string, payload: UpdateDraftItemRequest): Promise<DraftInvoice> => {
const { data } = await apiClient.patch<DraftInvoice>(`/drafts/${draftId}/items/${itemId}`, payload);
return data;
},
addDraftItem: async (draftId: string): Promise<DraftItem> => {
const { data } = await apiClient.post<DraftItem>(`/drafts/${draftId}/items`, {});
return data;
},
deleteDraftItem: async (draftId: string, itemId: string): Promise<void> => {
await apiClient.delete(`/drafts/${draftId}/items/${itemId}`);
},
reorderDraftItems: async (draftId: string, payload: ReorderDraftItemsRequest): Promise<void> => {
await apiClient.post(`/drafts/${draftId}/reorder`, payload);
},
commitDraft: async (draftId: string, payload: CommitDraftRequest): Promise<{ document_number: string }> => {
const { data } = await apiClient.post<{ document_number: string }>(`/drafts/${draftId}/commit`, payload);
return data;
},
deleteDraft: async (id: string): Promise<void> => {
await apiClient.delete(`/drafts/${id}`);
},
deleteAllDrafts: async (): Promise<{ count: number }> => {
const { data } = await apiClient.delete<{ count: number }>('/drafts');
return data;
},
// --- Настройки и Статистика ---
getSettings: async (): Promise<UserSettings> => {
const { data } = await apiClient.get<UserSettings>('/settings');
return data;
},
updateSettings: async (payload: UserSettings): Promise<UserSettings> => {
const { data } = await apiClient.post<UserSettings>('/settings', payload);
return data;
},
getStats: async (): Promise<InvoiceStats> => {
const { data } = await apiClient.get<InvoiceStats>('/stats/invoices');
return data;
},
getProductGroups: async (): Promise<ProductGroup[]> => {
const { data } = await apiClient.get<ProductGroup[]>('/dictionaries/groups');
return data;
},
// --- Управление командой ---
getUsers: async (): Promise<ServerUser[]> => {
const { data } = await apiClient.get<ServerUser[]>('/settings/users');
return data;
},
updateUserRole: async (userId: string, newRole: UserRole): Promise<{ status: string }> => {
const { data } = await apiClient.patch<{ status: string }>(`/settings/users/${userId}`, { new_role: newRole });
return data;
},
removeUser: async (userId: string): Promise<{ status: string }> => {
const { data } = await apiClient.delete<{ status: string }>(`/settings/users/${userId}`);
return data;
},
getInvoice: async (id: string): Promise<InvoiceDetails> => {
const { data } = await apiClient.get<InvoiceDetails>(`/invoices/${id}`);
return data;
},
syncInvoices: async (): Promise<void> => {
await apiClient.post('/invoices/sync');
},
getPhotos: async (page = 1, limit = 20): Promise<GetPhotosResponse> => {
const { data } = await apiClient.get<GetPhotosResponse>('/photos', {
params: { page, limit }
});
return data;
},
deletePhoto: async (id: string, force = false): Promise<void> => {
await apiClient.delete(`/photos/${id}`, {
params: { force }
});
},
regenerateDraftFromPhoto: async (id: string): Promise<void> => {
await apiClient.post(`/photos/${id}/regenerate`);
},
};
```
# ===================================================================
# Файл: src/services/types.ts
# ===================================================================
```
// --- Общие типы ---
export type UUID = string;
// Добавляем типы ролей
export type UserRole = 'OWNER' | 'ADMIN' | 'OPERATOR';
// Интерфейс пользователя сервера
export interface ServerUser {
user_id: string;
username: string; // @username или пустая строка
first_name: string;
last_name: string;
photo_url: string; // URL картинки или пустая строка
role: UserRole;
is_me: boolean; // Флаг, является ли этот юзер текущим пользователем
}
// --- Каталог и Фасовки (API v2.0) ---
export interface ProductContainer {
id: UUID;
name: string; // "Пачка 180г"
count: number; // 0.180
}
// Запрос на создание фасовки
export interface AddContainerRequest {
product_id: UUID;
name: string; // "Бутылка 0.75"
count: number; // 0.75
}
// Ответ на создание фасовки
export interface AddContainerResponse {
status: string;
container_id: UUID;
}
// Результат поиска товара
export interface ProductSearchResult {
id: UUID;
name: string;
code: string;
num?: string;
// Обновляем структуру единицы измерения
main_unit?: MainUnit;
measure_unit?: string; // Оставим для совместимости, но брать будем из main_unit.name
containers: ProductContainer[];
}
// Совместимость с CatalogItem (чтобы не ломать старый код, если он где-то используется)
export interface CatalogItem extends ProductSearchResult {
// Fallback поля
ID?: UUID;
Name?: string;
Code?: string;
MeasureUnit?: string;
Containers?: ProductContainer[];
}
// --- Матчинг (Обучение) ---
export interface MatchRequest {
raw_name: string;
product_id: UUID;
quantity: number;
container_id?: UUID;
}
export interface ProductMatch {
raw_name: string;
product_id: UUID;
product?: CatalogItem;
quantity: number;
container_id?: UUID;
container?: ProductContainer;
updated_at: string;
}
// --- Нераспознанное ---
export interface UnmatchedItem {
raw_name: string;
count: number;
last_seen: string;
}
// --- Остальные типы ---
export interface Recommendation {
ID: UUID;
Type: string;
ProductID: UUID;
ProductName: string;
Reason: string;
CreatedAt: string;
}
export interface InvoiceItemRequest {
product_id: UUID;
amount: number;
price: number;
}
export interface CreateInvoiceRequest {
document_number: string;
date_incoming: string;
supplier_id: UUID;
store_id: UUID;
items: InvoiceItemRequest[];
}
export interface InvoiceResponse {
status: string;
created_number: string;
}
export interface HealthResponse {
status: string;
time: string;
}
// --- Справочники ---
export interface Store {
id: UUID;
name: string;
}
export interface Supplier {
id: UUID;
name: string;
}
export interface DictionariesResponse {
stores: Store[];
suppliers: Supplier[];
// product_groups?: ProductGroup[]; // пока не реализовано
}
// --- Настройки и Статистика ---
export interface UserSettings {
root_group_id: UUID | null;
default_store_id: UUID | null;
auto_conduct: boolean;
role: UserRole; // Добавляем поле роли в настройки текущего пользователя
}
export interface InvoiceStats {
last_month: number;
last_24h: number;
total: number;
}
// Интерфейс группы товаров (рекурсивный)
export interface ProductGroup {
key: string;
value: string;
title: string;
children?: ProductGroup[];
}
// --- Черновик Накладной (Draft) ---
export type DraftStatus = 'PROCESSING' | 'READY_TO_VERIFY' | 'COMPLETED' | 'ERROR' | 'CANCELED' | 'NEW' | 'PROCESSED' | 'DELETED';
export interface DraftItem {
id: UUID;
// Данные из OCR (Read-only)
raw_name: string;
raw_amount: number;
raw_price: number;
// Редактируемые данные
product_id: UUID | null;
container_id: UUID | null; // Фасовка
quantity: number;
price: number;
sum: number;
// Мета-данные
is_matched: boolean;
product?: CatalogItem;
container?: ProductContainer;
// Поля для синхронизации состояния (опционально, если бэкенд их отдает)
last_edited_field_1?: string;
last_edited_field_2?: string;
}
// --- Список Черновиков (Summary) ---
export interface DraftSummary {
id: UUID;
document_number: string;
date_incoming: string;
status: DraftStatus; // Используем существующий тип статуса
items_count: number;
total_sum: number;
store_name?: string;
created_at: string;
}
export interface DraftInvoice {
id: UUID;
status: DraftStatus;
document_number: string;
incoming_document_number?: string
date_incoming: string | null; // YYYY-MM-DD
store_id: UUID | null;
supplier_id: UUID | null;
comment: string;
items: DraftItem[];
created_at?: string;
photo_url?: string; // Добавлено поле фото чека
}
// DTO для обновления строки
export interface UpdateDraftItemRequest {
product_id?: UUID;
container_id?: UUID | null;
quantity?: number;
price?: number;
sum?: number;
edited_field?: string; // ('quantity' | 'price' | 'sum')
}
// DTO для коммита
export interface CommitDraftRequest {
date_incoming: string;
store_id: UUID;
supplier_id: UUID;
comment: string;
incoming_document_number?: string;
}
export interface ReorderDraftItemsRequest {
items: Array<{
id: UUID;
order: number;
}>;
}
export interface MainUnit {
id: UUID;
name: string; // "кг"
code: string;
}
export type InvoiceType = 'DRAFT' | 'SYNCED'; // Тип записи: Черновик или Синхронизировано из iiko
export interface UnifiedInvoice {
id: UUID;
type: InvoiceType; // Новый признак типа
document_number: string; // Внутренний номер iiko или ID черновика
incoming_number: string; // Входящий номер накладной от поставщика
date_incoming: string;
status: DraftStatus;
items_count: number;
total_sum: number;
store_name?: string;
created_at: string;
is_app_created: boolean; // Создано ли через наше приложение
items_preview: string; // Краткое содержание товаров
photo_url: string | null; // Ссылка на фото чека
}
export interface InvoiceDetails {
id: UUID;
number: string;
date: string;
status: DraftStatus;
supplier: Supplier;
items: {
name: string;
quantity: number;
price: number;
total: number;
}[];
photo_url: string | null;
}
export type PhotoStatus = 'ORPHAN' | 'HAS_DRAFT' | 'HAS_INVOICE';
export interface ReceiptPhoto {
id: string;
rms_server_id: string;
uploaded_by: string;
file_url: string;
file_name: string;
file_size: number;
draft_id?: string;
invoice_id?: string;
created_at: string;
status: PhotoStatus;
can_delete: boolean;
can_regenerate: boolean;
}
export interface GetPhotosResponse {
photos: ReceiptPhoto[];
total: number;
page: number;
limit: number;
}
```
# ===================================================================
# Файл: src/vite-env.d.ts
# ===================================================================
```
/// <reference types="vite/client" />
interface TelegramWebApp {
initData: string; // Сырая строка с параметрами и хешем
initDataUnsafe: {
user?: {
id: number;
first_name: string;
last_name?: string;
username?: string;
language_code?: string;
};
};
close: () => void;
expand: () => void;
}
interface Window {
Telegram?: {
WebApp: TelegramWebApp;
};
}
```
# ===================================================================
# Файл: tsconfig.app.json
# ===================================================================
```
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
```
# ===================================================================
# Файл: tsconfig.json
# ===================================================================
```
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
```
# ===================================================================
# Файл: tsconfig.node.json
# ===================================================================
```
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
```