From 0dac8027363992aa32d17848156e04875c75492e Mon Sep 17 00:00:00 2001 From: Kevin Rosales Date: Mon, 28 Jul 2025 11:00:25 -0600 Subject: [PATCH] Primera version de frontend --- .env | 4 + .gitignore | 24 + Dockerfile | 14 + Dockerfile.prod | 22 + docker-compose.prod.yml | 15 + eslint.config.js | 33 + index.html | 13 + nginx.conf | 29 + package-lock.json | 4418 ++++++++++++++++++++++++ package.json | 41 + postcss.config.cjs | 6 + postcss.config.js | 6 + public/debug.html | 0 public/vite.svg | 1 + src/App.jsx | 131 + src/api/auth.js | 25 + src/api/documentos.ts | 60 + src/api/documents.js | 102 + src/api/documents.ts | 0 src/api/expedientes.ts | 115 + src/api/notificaciones.ts | 68 + src/api/organizacion.js | 36 + src/api/organization.ts | 33 + src/api/pedimentoDocuments.ts | 44 + src/api/procesos.ts | 33 + src/api/users.js | 83 + src/api/users.ts | 172 + src/assets/organization-animations.css | 19 + src/assets/react.svg | 1 + src/components/Layout.jsx | 17 + src/components/LogoutButton.jsx | 28 + src/components/Navbar.jsx | 186 + src/components/NotificationBell.jsx | 222 ++ src/components/RequireAuth.jsx | 22 + src/components/Sidebar.jsx | 370 ++ src/components/Sidebar.jsx.bak | 211 ++ src/components/SidebarNew.jsx | 207 ++ src/components/SuccessModal.jsx | 25 + src/components/TestTailwind.jsx | 10 + src/components/card.jsx | 0 src/context/NotificationContext.jsx | 94 + src/context/UserContext.jsx | 90 + src/fetchWithAuth.js | 0 src/hooks/usePolling.js | 73 + src/hooks/useWebSocket.js | 49 + src/index.css | 21 + src/main.jsx | 19 + src/pages/Admin.jsx | 373 ++ src/pages/Debug.jsx | 13 + src/pages/Documents.jsx | 645 ++++ src/pages/Expedientes.jsx | 578 ++++ src/pages/ForgotPassword.jsx | 195 ++ src/pages/Importers.jsx | 427 +++ src/pages/Landing.jsx | 785 +++++ src/pages/LandingAnimated.jsx | 998 ++++++ src/pages/LandingNew.jsx | 756 ++++ src/pages/Login.jsx | 313 ++ src/pages/LoginBroken.jsx | 391 +++ src/pages/LoginFixed.jsx | 286 ++ src/pages/Notificaciones.jsx | 158 + src/pages/Organization.jsx | 300 ++ src/pages/PasswordResetConfirm.jsx | 212 ++ src/pages/PedimentoDetail.jsx | 930 +++++ src/pages/Procesos.jsx | 385 +++ src/pages/Reportes.jsx | 80 + src/pages/Reports.jsx | 316 ++ src/pages/Settings.jsx | 431 +++ src/pages/SettingsNew.jsx | 425 +++ src/pages/TableroAlmacenamiento.jsx | 326 ++ src/pages/Test.jsx | 20 + src/pages/Users.jsx | 1243 +++++++ src/pages/UsersNew.jsx | 723 ++++ src/pages/Vucem.jsx | 19 + src/theme.js | 93 + src/vite-env.d.ts | 4 + tailwind.config.cjs | 11 + tailwind.config.js | 111 + vite.config.js | 18 + 78 files changed, 18757 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Dockerfile.prod create mode 100644 docker-compose.prod.yml create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 nginx.conf create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.cjs create mode 100644 postcss.config.js create mode 100644 public/debug.html create mode 100644 public/vite.svg create mode 100644 src/App.jsx create mode 100644 src/api/auth.js create mode 100644 src/api/documentos.ts create mode 100644 src/api/documents.js create mode 100644 src/api/documents.ts create mode 100644 src/api/expedientes.ts create mode 100644 src/api/notificaciones.ts create mode 100644 src/api/organizacion.js create mode 100644 src/api/organization.ts create mode 100644 src/api/pedimentoDocuments.ts create mode 100644 src/api/procesos.ts create mode 100644 src/api/users.js create mode 100644 src/api/users.ts create mode 100644 src/assets/organization-animations.css create mode 100644 src/assets/react.svg create mode 100644 src/components/Layout.jsx create mode 100644 src/components/LogoutButton.jsx create mode 100644 src/components/Navbar.jsx create mode 100644 src/components/NotificationBell.jsx create mode 100644 src/components/RequireAuth.jsx create mode 100644 src/components/Sidebar.jsx create mode 100644 src/components/Sidebar.jsx.bak create mode 100644 src/components/SidebarNew.jsx create mode 100644 src/components/SuccessModal.jsx create mode 100644 src/components/TestTailwind.jsx create mode 100644 src/components/card.jsx create mode 100644 src/context/NotificationContext.jsx create mode 100644 src/context/UserContext.jsx create mode 100644 src/fetchWithAuth.js create mode 100644 src/hooks/usePolling.js create mode 100644 src/hooks/useWebSocket.js create mode 100644 src/index.css create mode 100644 src/main.jsx create mode 100644 src/pages/Admin.jsx create mode 100644 src/pages/Debug.jsx create mode 100644 src/pages/Documents.jsx create mode 100644 src/pages/Expedientes.jsx create mode 100644 src/pages/ForgotPassword.jsx create mode 100644 src/pages/Importers.jsx create mode 100644 src/pages/Landing.jsx create mode 100644 src/pages/LandingAnimated.jsx create mode 100644 src/pages/LandingNew.jsx create mode 100644 src/pages/Login.jsx create mode 100644 src/pages/LoginBroken.jsx create mode 100644 src/pages/LoginFixed.jsx create mode 100644 src/pages/Notificaciones.jsx create mode 100644 src/pages/Organization.jsx create mode 100644 src/pages/PasswordResetConfirm.jsx create mode 100644 src/pages/PedimentoDetail.jsx create mode 100644 src/pages/Procesos.jsx create mode 100644 src/pages/Reportes.jsx create mode 100644 src/pages/Reports.jsx create mode 100644 src/pages/Settings.jsx create mode 100644 src/pages/SettingsNew.jsx create mode 100644 src/pages/TableroAlmacenamiento.jsx create mode 100644 src/pages/Test.jsx create mode 100644 src/pages/Users.jsx create mode 100644 src/pages/UsersNew.jsx create mode 100644 src/pages/Vucem.jsx create mode 100644 src/theme.js create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.cjs create mode 100644 tailwind.config.js create mode 100644 vite.config.js diff --git a/.env b/.env new file mode 100644 index 0000000..7365a08 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +DEBUG_MODE=true + +VITE_EFC_API_URL=http://192.168.1.195:8000/api/v1 +VITE_EFC_MICROSERVICE_URL=http://192.168.1.195:8001/api/v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4e1835e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Etapa de desarrollo para Vite + React +FROM node:18-alpine + +WORKDIR /app + +COPY package*.json ./ +COPY vite.config.* ./ +RUN npm install + +COPY . . + +EXPOSE 5173 + +CMD ["npm", "run", "dev", "--", "--host"] \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..818f9af --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,22 @@ +# Dockerfile de producción para Vite + React +FROM node:18-alpine AS build + +WORKDIR /app + +COPY package*.json ./ +COPY vite.config.* ./ +RUN npm install --frozen-lockfile + +COPY . . +RUN npm run build + +# Etapa final: Nginx para servir archivos estáticos +FROM nginx:alpine + +# Copia los archivos estáticos generados a la carpeta de Nginx +COPY --from=build /app/dist /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..7d213b7 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,15 @@ +version: '3.8' +services: + frontend: + build: + context: . + dockerfile: Dockerfile.prod + image: efc_frontend_prod:latest + ports: + - "80:80" + restart: unless-stopped + environment: + - NODE_ENV=production + # Si necesitas montar archivos estáticos personalizados, descomenta y ajusta: + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf:ro diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/index.html b/index.html new file mode 100644 index 0000000..0c589ec --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + + +
+ + + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..3daf527 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,29 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c9d343a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4418 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource/roboto": "^5.2.6", + "@tanstack/react-query": "^5.62.7", + "chart.js": "^4.5.0", + "highlight.js": "^11.11.1", + "react": "^19.1.0", + "react-chartjs-2": "^5.3.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.2", + "react-window": "^1.8.11", + "socket.io-client": "^4.8.1", + "styled-components": "^6.1.19" + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "eslint": "^9.25.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "vite": "^6.3.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.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==", + "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.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", + "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/core/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 + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-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, + "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-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==", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.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, + "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==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dependencies": { + "@babel/types": "^7.27.7" + }, + "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, + "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, + "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.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "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==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "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, + "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.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "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.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "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.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "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.0", + "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, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", + "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", + "dev": true, + "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.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fontsource/roboto": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.6.tgz", + "integrity": "sha512-hzarG7yAhMoP418smNgfY4fO7UmuUEm5JUtbxCoCcFHT0hOJB+d/qAEyoNjz7YkPU5OjM2LM8rJnW8hfm0JLaA==", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "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, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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, + "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, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@tanstack/query-core": { + "version": "5.81.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.5.tgz", + "integrity": "sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.81.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.5.tgz", + "integrity": "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==", + "dependencies": { + "@tanstack/query-core": "5.81.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "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, + "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, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "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 + }, + "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 + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "dev": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", + "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.19", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.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, + "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, + "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, + "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-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "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 + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "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 + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "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" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "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" + } + ] + }, + "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, + "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/chart.js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "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, + "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 + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "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, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "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 + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "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, + "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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", + "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.30.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "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": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "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.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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 + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "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 + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "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, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "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, + "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, + "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 + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "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, + "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==", + "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, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "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, + "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==", + "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, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "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==", + "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 + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "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 + }, + "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 + }, + "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, + "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, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "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, + "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 + }, + "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, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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 + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "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, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "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==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.3.tgz", + "integrity": "sha512-zf45LZp5skDC6I3jDLXQUu0u26jtuP4lEGbc7BbdyxenBN1vJSTA18czM2D+h5qyMBuMrD+9uB+mU37HIoKGRA==", + "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.6.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.3.tgz", + "integrity": "sha512-DiWJm9qdUAmiJrVWaeJdu4TKu13+iB/8IEi0EW/XgaHCjW/vWGrwzup0GVvaMteuZjKnh5bEvJP/K0MDnzawHw==", + "dependencies": { + "react-router": "7.6.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-window": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + }, + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", + "dev": true, + "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.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" + }, + "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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/styled-components/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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" + } + ], + "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.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 + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1f5ff63 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource/roboto": "^5.2.6", + "@tanstack/react-query": "^5.62.7", + "chart.js": "^4.5.0", + "highlight.js": "^11.11.1", + "react": "^19.1.0", + "react-chartjs-2": "^5.3.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.2", + "react-window": "^1.8.11", + "socket.io-client": "^4.8.1", + "styled-components": "^6.1.19" + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "eslint": "^9.25.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "vite": "^6.3.5" + } +} diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/debug.html b/public/debug.html new file mode 100644 index 0000000..e69de29 diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..bedb887 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,131 @@ +import Documents from './pages/Documents'; +import Vucem from './pages/Vucem'; +import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'; +import { UserProvider } from './context/UserContext'; +import Navbar from './components/Navbar'; +import Layout from './components/Layout'; +import Login from './pages/Login'; +import Admin from './pages/Admin'; +import RequireAuth from './components/RequireAuth'; +import LandingAnimated from './pages/LandingAnimated'; +import Expedientes from './pages/Expedientes'; +import Organization from './pages/Organization'; +import Users from './pages/Users'; +import Reports from './pages/Reports'; +import Settings from './pages/Settings'; +import Importers from './pages/Importers'; +import PedimentoDetail from './pages/PedimentoDetail'; +import Procesos from './pages/Procesos'; +import TableroAlmacenamiento from './pages/TableroAlmacenamiento'; +import Notificaciones from './pages/Notificaciones'; +import ForgotPassword from './pages/ForgotPassword'; +import PasswordResetConfirm from './pages/PasswordResetConfirm'; + +// Componente para manejar el layout condicional +function AppContent() { + const location = useLocation(); + const isAuthPage = location.pathname === '/login' || location.pathname === '/' || location.pathname === '/forgot-password' || location.pathname.startsWith('/user/password-reset-confirm/'); + + console.log('🚀 AppContent renderizado'); + console.log('📍 Ubicación actual:', location.pathname); + console.log('🔐 Es página de auth:', isAuthPage); + console.log('🎫 Token en localStorage:', !!localStorage.getItem('access')); + + if (isAuthPage) { + return ( + <> + + } /> + } /> + } /> + } /> + + + ); + } + + return ( + + + + + + } /> + + + + } /> + + + + } /> + + + + + } /> + + + + } /> + + + + } /> + + + + } /> + + + + } /> + } /> + {/* Ruta para importadores */} + + + + } /> + {/* Ruta para procesos */} + + + + } /> + {/* Ruta para Uso de Almacenamiento */} + + + + } /> + {/* Ruta para Vucem */} + + + + } /> + + + ); +} + +function App() { + return ( + + + + + + ); +} + +export default App; \ No newline at end of file diff --git a/src/api/auth.js b/src/api/auth.js new file mode 100644 index 0000000..2be833c --- /dev/null +++ b/src/api/auth.js @@ -0,0 +1,25 @@ +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export async function login(username, password) { + const response = await fetch(`${API_URL}/token/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }), + }); + + console.log('API URL:', `${API_URL}/token/`); + if (!response.ok) { + throw new Error('Credenciales inválidas'); + } + return response.json(); // { access, refresh } +} + +export async function refreshToken(refresh) { + const res = await fetch(`${API_URL}/token/refresh/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ refresh }), + }); + if (!res.ok) throw new Error('SESSION_EXPIRED'); + return res.json(); // { access: '...' } +} \ No newline at end of file diff --git a/src/api/documentos.ts b/src/api/documentos.ts new file mode 100644 index 0000000..3da516e --- /dev/null +++ b/src/api/documentos.ts @@ -0,0 +1,60 @@ +// src/api/pedimentoDocuments.ts + +export interface PedimentoDocument { + id: string; + organizacion: string; + pedimento: string; + pedimento_numero:string; + archivo: string; + document_type: number; + size: number; + extension: string; + created_at: string; + updated_at: string; +} + +export interface PedimentoDocumentsResponse { + count: number; + next: string | null; + previous: string | null; + results: PedimentoDocument[]; +} + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export async function fetchPedimentoDocuments( + token: string, + pedimentoId: string = '', + page: number = 1, + pageSize: number = 10, + filters: { + pedimento_numero?: string; + extension?: string; + document_type?: string | number; + created_at?: string; + } = {} +): Promise { + const params = new URLSearchParams(); + params.append('page', String(page)); + params.append('page_size', String(pageSize)); + if (pedimentoId) params.append('pedimento', pedimentoId); + if (filters.pedimento_numero) params.append('pedimento_numero', filters.pedimento_numero); + if (filters.extension) params.append('extension', filters.extension); + if (filters.document_type) params.append('document_type', String(filters.document_type)); + if (filters.created_at) params.append('created_at', filters.created_at); + + const res = await fetch( + `${API_URL}/record/documents/?${params.toString()}`, + { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + } + ); + if (res.status === 401) { + throw new Error('SESSION_EXPIRED'); + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); +} diff --git a/src/api/documents.js b/src/api/documents.js new file mode 100644 index 0000000..d43c6e1 --- /dev/null +++ b/src/api/documents.js @@ -0,0 +1,102 @@ + +/** + * @typedef {Object} Document + * @property {string} id + * @property {string} organizacion + * @property {string} pedimento + * @property {string} archivo + * @property {number} document_type + * @property {number} size + * @property {string} extension + * @property {string} created_at + * @property {string} updated_at + */ + +/** + * @typedef {Object} DocumentsResponse + * @property {number} count + * @property {string|null} next + * @property {string|null} previous + * @property {Document[]} results + */ + +import { refreshToken } from './auth'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; +/** + * Obtiene la lista de documentos (pedimentos) + * @param {string} token + * @returns {Promise} + */ +export async function fetchDocuments(token, queryString = '') { + let url = `${API_URL}/customs/pedimentos/`; + if (queryString) { + url += `?${queryString}`; + } + let res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) { + // Intentar refrescar el token + const refresh = localStorage.getItem('refresh'); + if (refresh) { + try { + const data = await refreshToken(refresh); + localStorage.setItem('access', data.access); + // Reintenta la petición con el nuevo access token + res = await fetch(`${API_URL}/customs/pedimentos/`, { + headers: { + 'Authorization': `Bearer ${data.access}`, + 'Content-Type': 'application/json', + }, + }); + } catch (err) { + throw new Error('SESSION_EXPIRED'); + } + } else { + throw new Error('SESSION_EXPIRED'); + } + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); // Tipado por JSDoc: Promise +} +/** + * Obtiene los documentos por id de pedimento + * @param {string} token + * @param {string} id + * @returns {Promise} + */ +export async function fetchDocumentById(token, id) { + let res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) { + // Intentar refrescar el token + const refresh = localStorage.getItem('refresh'); + if (refresh) { + try { + const data = await refreshToken(refresh); + localStorage.setItem('access', data.access); + // Reintenta la petición con el nuevo access token + res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, { + headers: { + 'Authorization': `Bearer ${data.access}`, + 'Content-Type': 'application/json', + }, + }); + } catch (err) { + throw new Error('SESSION_EXPIRED'); + } + } else { + throw new Error('SESSION_EXPIRED'); + } + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); // Tipado por JSDoc: Promise +} diff --git a/src/api/documents.ts b/src/api/documents.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/api/expedientes.ts b/src/api/expedientes.ts new file mode 100644 index 0000000..d5692e3 --- /dev/null +++ b/src/api/expedientes.ts @@ -0,0 +1,115 @@ + + +export interface Document { + id: string; + organizacion: string; + pedimento: string; + archivo: string; + document_type: number; + size: number; + extension: string; + created_at: string; + updated_at: string; +} + +export interface DocumentsResponse { + count: number; + next: string | null; + previous: string | null; + results: Document[]; +} + +import { refreshToken } from './auth'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; +// Obtiene la lista de documentos (pedimentos) +export interface PedimentosFilters { + search?: string; + pedimento?: string; + existe_expediente?: string | boolean; + alerta?: string | boolean; + contribuyente?: string; + curp_apoderado?: string; + fecha_pago?: string; + patente?: string; + aduana?: string; + tipo_operacion?: string; + clave_pedimento?: string; +} + +export async function fetchDocuments( + token: string, + page: number = 1, + pageSize: number = 10, + filters: PedimentosFilters = {} +): Promise { + const params = new URLSearchParams(); + params.append('page', String(page)); + params.append('page_size', String(pageSize)); + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && value !== null && value !== '') { + params.append(key, String(value)); + } + }); + let res = await fetch(`${API_URL}/customs/pedimentos/?${params.toString()}`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) { + // Intentar refrescar el token + const refresh = localStorage.getItem('refresh'); + if (refresh) { + try { + const data = await refreshToken(refresh); + localStorage.setItem('access', data.access); + // Reintenta la petición con el nuevo access token + res = await fetch(`${API_URL}/customs/pedimentos/?page=${page}&page_size=${pageSize}`, { + headers: { + 'Authorization': `Bearer ${data.access}`, + 'Content-Type': 'application/json', + }, + }); + } catch (err) { + throw new Error('SESSION_EXPIRED'); + } + } else { + throw new Error('SESSION_EXPIRED'); + } + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); +} +// Obtiene los documentos por id de pedimento +export async function fetchDocumentById(token: string, id: string): Promise { + let res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) { + // Intentar refrescar el token + const refresh = localStorage.getItem('refresh'); + if (refresh) { + try { + const data = await refreshToken(refresh); + localStorage.setItem('access', data.access); + // Reintenta la petición con el nuevo access token + res = await fetch(`${API_URL}/record/documents/?page=1&page_size=10&pedimento=${id}/`, { + headers: { + 'Authorization': `Bearer ${data.access}`, + 'Content-Type': 'application/json', + }, + }); + } catch (err) { + throw new Error('SESSION_EXPIRED'); + } + } else { + throw new Error('SESSION_EXPIRED'); + } + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); +} diff --git a/src/api/notificaciones.ts b/src/api/notificaciones.ts new file mode 100644 index 0000000..fc2aa19 --- /dev/null +++ b/src/api/notificaciones.ts @@ -0,0 +1,68 @@ +// PUT para marcar una notificación como vista +export async function marcarNotificacionComoVista(id: number): Promise { + const token = localStorage.getItem('access'); + const url = `${API_URL}/notificaciones/notificaciones/${id}/`; + const headers = new Headers(); + if (token) headers.append('Authorization', `Bearer ${token}`); + headers.append('Content-Type', 'application/json'); + const res = await fetch(url, { + method: 'PUT', + headers, + body: JSON.stringify({ visto: true }) + }); + if (!res.ok) throw new Error('Error al actualizar notificación'); + return await res.json(); +} +// src/api/notificaciones.ts + +export interface TipoNotificacion { + id: number; + tipo: string; + descripcion: string; +} + +export interface Notificacion { + id: number; + tipo: TipoNotificacion; + dirigido: string; + mensaje: string; + fecha_envio: string; + created_at: string; + visto: boolean; +} + +export interface NotificacionesResponse { + count: number; + next: string | null; + previous: string | null; + results: Notificacion[]; +} + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export async function fetchNotificaciones({ page = 1, pageSize = 10, visto = false } = {}): Promise { + const token = localStorage.getItem('access'); + const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${pageSize}&visto=${visto}`; + const headers = new Headers(); + if (token) headers.append('Authorization', `Bearer ${token}`); + headers.append('Content-Type', 'application/json'); + const res = await fetch(url, { + headers, + }); + if (!res.ok) throw new Error('Error al obtener notificaciones'); + return await res.json(); +} + + +export async function fetchAllNotifications({page = 1, page_size=10}): Promise{ + const token = localStorage.getItem('access'); + const url = `${API_URL}/notificaciones/notificaciones/?page=${page}&page_size=${page_size}`; + const headers = new Headers(); + if (token) headers.append('Authorization', `Bearer ${token}`); + headers.append('Content-Type', 'application/json'); + const res = await fetch(url, { + headers, + }); + if (!res.ok) throw new Error('Error al obtener notificaciones'); + return await res.json(); +} \ No newline at end of file diff --git a/src/api/organizacion.js b/src/api/organizacion.js new file mode 100644 index 0000000..272c95b --- /dev/null +++ b/src/api/organizacion.js @@ -0,0 +1,36 @@ +import { refreshToken } from './auth'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export async function fetchOrganizationUsage(token) { + let res = await fetch(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) { + // Intentar refrescar el token + const refresh = localStorage.getItem('refresh'); + if (refresh) { + try { + const data = await refreshToken(refresh); + localStorage.setItem('access', data.access); + // Reintenta la petición con el nuevo access token + res = await fetch(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`, { + headers: { + 'Authorization': `Bearer ${data.access}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) throw new Error('SESSION_EXPIRED'); + } catch (err) { + throw new Error('SESSION_EXPIRED'); + } + } else { + throw new Error('SESSION_EXPIRED'); + } + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); +} diff --git a/src/api/organization.ts b/src/api/organization.ts new file mode 100644 index 0000000..62c60da --- /dev/null +++ b/src/api/organization.ts @@ -0,0 +1,33 @@ +// organization.ts +// Tipos para la respuesta del endpoint de uso de almacenamiento de organización + +export interface OrganizationUsage { + organizacion: string; + limite_almacenamiento_gb: number; + espacio_utilizado_bytes: number; + espacio_utilizado_gb: number; + espacio_disponible_bytes: number; + porcentaje_utilizado: number; + total_documentos: number; + total_pedimentos: number; + total_usuarios: number; +} + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +// Ejemplo de función para obtener la información tipada +export async function fetchOrganizationUsage(token: string): Promise { + const res = await fetch(`${API_URL}/organization/uso-almacenamiento/mi_organizacion/`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }); + if (res.status === 401) { + throw new Error('SESSION_EXPIRED'); + } + if (!res.ok) { + throw new Error('Error al obtener información de la organización'); + } + return res.json(); +} diff --git a/src/api/pedimentoDocuments.ts b/src/api/pedimentoDocuments.ts new file mode 100644 index 0000000..c6218c0 --- /dev/null +++ b/src/api/pedimentoDocuments.ts @@ -0,0 +1,44 @@ +// src/api/pedimentoDocuments.ts + +export interface PedimentoDocument { + id: string; + organizacion: string; + pedimento: string; + archivo: string; + document_type: number; + size: number; + extension: string; + created_at: string; + updated_at: string; +} + +export interface PedimentoDocumentsResponse { + count: number; + next: string | null; + previous: string | null; + results: PedimentoDocument[]; +} + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export async function fetchPedimentoDocuments( + token: string, + pedimentoId: string, + page: number = 1, + pageSize: number = 10 +): Promise { + const res = await fetch( + `${API_URL}/record/documents/?page=${page}&page_size=${pageSize}&pedimento=${pedimentoId}`, + { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + } + ); + if (res.status === 401) { + throw new Error('SESSION_EXPIRED'); + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); +} diff --git a/src/api/procesos.ts b/src/api/procesos.ts new file mode 100644 index 0000000..be42417 --- /dev/null +++ b/src/api/procesos.ts @@ -0,0 +1,33 @@ +// Tipos para la respuesta y registros +export interface ProcesamientoPedimento { + id: number; + created_at: string; + updated_at: string; + organizacion: string; + organizacion_name: string; + estado: number; + tipo_procesamiento: number; + pedimento: string; + servicio: number; +} + +export interface ProcesamientoPedimentosResponse { + count: number; + next: string | null; + previous: string | null; + results: ProcesamientoPedimento[]; +} + +// API para customs/procesamientopedimentos/ +export async function fetchProcesamientoPedimentos( + token: string | null, + page: number = 1, + pageSize: number = 20 +): Promise { + const API_URL = import.meta.env.VITE_EFC_API_URL; + const headers: Record = {}; + if (token) headers['Authorization'] = `Bearer ${token}`; + const res = await fetch(`${API_URL}/customs/procesamientopedimentos/?page=${page}&page_size=${pageSize}`, { headers }); + if (!res.ok) throw new Error('Error al obtener procesamiento de pedimentos'); + return await res.json(); +} diff --git a/src/api/users.js b/src/api/users.js new file mode 100644 index 0000000..cb30ca9 --- /dev/null +++ b/src/api/users.js @@ -0,0 +1,83 @@ +const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000'; + +async function handleResponse(response, operation = 'operación') { + if (response.status === 401) { + throw new Error('SESSION_EXPIRED'); + } + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + throw new Error('El servidor no devolvió JSON válido'); + } + return response.json(); +} + +export async function fetchUsers(token) { + const url = `${API_URL}/user/users/`; + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + return handleResponse(res, 'Fetch Users'); +} + +export async function createUser(token, userData) { + const url = `${API_URL}/user/users/`; + const res = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(userData), + }); + return handleResponse(res, 'Create User'); +} + +export async function updateUser(token, id, userData) { + const url = `${API_URL}/user/users/${id}/`; + const res = await fetch(url, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(userData), + }); + return handleResponse(res, 'Update User'); +} + +export async function deleteUser(token, id) { + const url = `${API_URL}/user/users/${id}/`; + const res = await fetch(url, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + if (res.status === 401) throw new Error('SESSION_EXPIRED'); + if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`); + return true; +} + +export async function getCurrentUser(token) { + const url = `${API_URL}/user/users/me/`; + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + return handleResponse(res, 'Get Current User'); +} diff --git a/src/api/users.ts b/src/api/users.ts new file mode 100644 index 0000000..760382e --- /dev/null +++ b/src/api/users.ts @@ -0,0 +1,172 @@ +const API_URL = import.meta.env.VITE_EFC_API_URL || 'http://localhost:8000'; + +// Función helper para manejar respuestas +async function handleResponse(response, operation = 'operación') { + console.log(`📡 ${operation} response:`, response.status, response.statusText); + + if (response.status === 401) { + console.error('❌ Unauthorized - session expired'); + throw new Error('SESSION_EXPIRED'); + } + + if (!response.ok) { + const errorText = await response.text(); + console.error(`❌ ${operation} error:`, response.status, errorText); + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + + // Verificar que la respuesta es JSON + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); + console.error('❌ Response is not JSON:', text.substring(0, 200)); + throw new Error('El servidor no devolvió JSON válido'); + } + + return response.json(); +} + +export async function fetchUsers(token) { + try { + const url = `${API_URL}/user/users/`; + console.log('👥 Fetching users from:', url); + + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + + const data = await handleResponse(res, 'Fetch Users'); + console.log('✅ Users data received'); + return data; + + } catch (error) { + console.error('❌ Error in fetchUsers:', error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('Error de conexión al servidor'); + } + throw error; + } +} + +export async function createUser(token, userData) { + try { + const url = `${API_URL}/user/users/`; + console.log('➕ Creating user at:', url); + + const res = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(userData), + }); + + const data = await handleResponse(res, 'Create User'); + console.log('✅ User created successfully'); + return data; + + } catch (error) { + console.error('❌ Error in createUser:', error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('Error de conexión al servidor'); + } + throw error; + } +} + +export async function updateUser(token, id, userData) { + try { + const url = `${API_URL}/user/users/${id}/`; + console.log('✏️ Updating user at:', url); + + const res = await fetch(url, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(userData), + }); + + const data = await handleResponse(res, 'Update User'); + console.log('✅ User updated successfully'); + return data; + + } catch (error) { + console.error('❌ Error in updateUser:', error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('Error de conexión al servidor'); + } + throw error; + } +} + +export async function deleteUser(token, id) { + try { + const url = `${API_URL}/user/users/${id}/`; + console.log('🗑️ Deleting user at:', url); + + const res = await fetch(url, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + + if (res.status === 401) { + console.error('❌ Unauthorized - session expired'); + throw new Error('SESSION_EXPIRED'); + } + + if (!res.ok) { + const errorText = await res.text(); + console.error('❌ Delete User error:', res.status, errorText); + throw new Error(`Error ${res.status}: ${res.statusText}`); + } + + console.log('✅ User deleted successfully'); + return true; // DELETE suele no devolver contenido + + } catch (error) { + console.error('❌ Error in deleteUser:', error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('Error de conexión al servidor'); + } + throw error; + } +} + +export async function getCurrentUser(token) { + try { + const url = `${API_URL}/user/users/me/`; + console.log('👤 Fetching current user from:', url); + + const res = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); + + const data = await handleResponse(res, 'Get Current User'); + console.log('✅ Current user data received:', data); + return data; + + } catch (error) { + console.error('❌ Error in getCurrentUser:', error); + if (error.name === 'TypeError' && error.message.includes('fetch')) { + throw new Error('Error de conexión al servidor'); + } + throw error; + } +} diff --git a/src/assets/organization-animations.css b/src/assets/organization-animations.css new file mode 100644 index 0000000..0137e14 --- /dev/null +++ b/src/assets/organization-animations.css @@ -0,0 +1,19 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer utilities { + @keyframes fadein-slideup { + 0% { + opacity: 0; + transform: translateY(24px); + } + 100% { + opacity: 1; + transform: translateY(0); + } + } + .animate-fadein-slideup { + animation: fadein-slideup 0.7s cubic-bezier(0.4,0,0.2,1) both; + } +} diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx new file mode 100644 index 0000000..21aed6f --- /dev/null +++ b/src/components/Layout.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Sidebar from './Sidebar'; +import NotificationBell from './NotificationBell'; + +export default function Layout({ children }) { + return ( +
+ +
+
+ {children} +
+
+ +
+ ); +} diff --git a/src/components/LogoutButton.jsx b/src/components/LogoutButton.jsx new file mode 100644 index 0000000..65c1380 --- /dev/null +++ b/src/components/LogoutButton.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +export default function LogoutButton() { + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + + // Disparar evento para actualizar el navbar + window.dispatchEvent(new CustomEvent('authStateChanged')); + + navigate('/login'); + }; + + return ( + + ); +} diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx new file mode 100644 index 0000000..e890e9c --- /dev/null +++ b/src/components/Navbar.jsx @@ -0,0 +1,186 @@ +import React, { useState, useEffect } from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { colors } from '../theme'; + +export default function Navbar() { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const location = useLocation(); + + useEffect(() => { + const checkAuthStatus = () => { + const token = localStorage.getItem('access'); + setIsLoggedIn(!!token); + }; + + checkAuthStatus(); + window.addEventListener('authStateChanged', checkAuthStatus); + return () => window.removeEventListener('authStateChanged', checkAuthStatus); + }, []); + + const logout = () => { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + window.dispatchEvent(new CustomEvent('authStateChanged')); + window.location.href = '/'; + }; + + const navLinks = [ + { path: '/', name: 'Inicio', public: true }, + { path: '/admin', name: 'Dashboard', public: false }, + { path: '/documents', name: 'Documentos', public: false }, + { path: '/mi-organizacion', name: 'Organización', public: false }, + { path: '/usuarios', name: 'Usuarios', public: false }, + { path: '/reportes', name: 'Reportes', public: false }, + ]; + + const isActiveLink = (path) => { + return location.pathname === path; + }; + + return ( + + ); +} diff --git a/src/components/NotificationBell.jsx b/src/components/NotificationBell.jsx new file mode 100644 index 0000000..4d4976c --- /dev/null +++ b/src/components/NotificationBell.jsx @@ -0,0 +1,222 @@ + +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { fetchNotificaciones, marcarNotificacionComoVista } from '../api/notificaciones'; + +export default function NotificationBell() { + const navigate = useNavigate(); + const [notifications, setNotifications] = useState([]); + const [showPanel, setShowPanel] = useState(false); + const [unreadCount, setUnreadCount] = useState(0); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + setLoading(true); + setError(null); + fetchNotificaciones({ page: 1, pageSize: 50 }) + .then(data => { + setNotifications(data.results); + setUnreadCount(data.results.filter(n => !n.visto).length); + }) + .catch(err => { + setError('Error al cargar notificaciones'); + }) + .finally(() => setLoading(false)); + }, []); + + // Opcional: aquí podrías hacer una petición PATCH/POST si es necesario + const markAsRead = async (notificationId) => { + try { + await marcarNotificacionComoVista(notificationId); + setNotifications(prev => + prev.map(notification => + notification.id === notificationId + ? { ...notification, visto: true } + : notification + ) + ); + setUnreadCount(prev => Math.max(0, prev - 1)); + } catch (e) { + // Opcional: mostrar error + } + }; + + const markAllAsRead = async () => { + const notVistas = notifications.filter(n => !n.visto); + try { + await Promise.all(notVistas.map(n => marcarNotificacionComoVista(n.id))); + setNotifications(prev => prev.map(notification => ({ ...notification, visto: true }))); + setUnreadCount(0); + } catch (e) { + // Opcional: mostrar error + } + }; + + const getNotificationIcon = (tipo) => { + switch (tipo) { + case 'success': + return ( +
+ + + +
+ ); + case 'warning': + return ( +
+ + + +
+ ); + default: + return ( +
+ + + +
+ ); + } + }; + + const formatTimestamp = (isoString) => { + const timestamp = new Date(isoString); + const now = new Date(); + const diff = now.getTime() - timestamp.getTime(); + const minutes = Math.floor(diff / (1000 * 60)); + const hours = Math.floor(diff / (1000 * 60 * 60)); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + if (minutes < 1) return 'Ahora'; + if (minutes < 60) return `${minutes}m`; + if (hours < 24) return `${hours}h`; + return `${days}d`; + }; + + return ( + <> + {/* Campanita flotante */} +
+ +
+ + {/* Panel de notificaciones */} + {showPanel && ( +
+ {/* Header del panel */} +
+

Notificaciones

+
+ {unreadCount > 0 && ( + + )} + +
+
+ + {/* Lista de notificaciones */} +
+ {loading ? ( +
Cargando notificaciones...
+ ) : error ? ( +
{error}
+ ) : notifications.length === 0 ? ( +
+ + + +

No hay notificaciones

+

Cuando tengas nuevas notificaciones aparecerán aquí.

+
+ ) : ( +
+ {notifications.map((notification) => ( +
markAsRead(notification.id)} + > +
+ {getNotificationIcon(notification.tipo?.tipo)} +
+
+

+ {notification.tipo?.descripcion || notification.tipo?.tipo || 'Notificación'} +

+ + {formatTimestamp(notification.fecha_envio || notification.created_at)} + +
+

{notification.mensaje}

+ {!notification.visto && ( +
+ +
+ )} +
+
+
+ ))} +
+ )} +
+ + {/* Footer del panel */} + {notifications.length > 0 && ( +
+ +
+ )} +
+ )} + + {/* Overlay para cerrar el panel al hacer clic fuera */} + {showPanel && ( +
setShowPanel(false)} + /> + )} + + ); +} diff --git a/src/components/RequireAuth.jsx b/src/components/RequireAuth.jsx new file mode 100644 index 0000000..a52b3a9 --- /dev/null +++ b/src/components/RequireAuth.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; + +// Esta función verifica si el usuario está autenticado (por ejemplo, si hay un token en localStorage) +function isAuthenticated() { + const token = localStorage.getItem('access'); + console.log('🔐 Verificando autenticación, token:', token ? 'presente' : 'ausente'); + return !!token; +} + +export default function RequireAuth({ children }) { + const authenticated = isAuthenticated(); + console.log('🛡️ RequireAuth - usuario autenticado:', authenticated); + + if (!authenticated) { + console.log('❌ No autenticado, redirigiendo a /login'); + return ; + } + + console.log('✅ Usuario autenticado, mostrando contenido'); + return children; +} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx new file mode 100644 index 0000000..58bc86f --- /dev/null +++ b/src/components/Sidebar.jsx @@ -0,0 +1,370 @@ +import React, { useState } from 'react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { useUser } from '../context/UserContext'; + +export default function Sidebar() { + // Leer si el usuario es importador desde localStorage + const isImportador = typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true'; + const [isCollapsed, setIsCollapsed] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + const { user: currentUser, loading } = useUser(); + + const handleLogout = () => { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + window.dispatchEvent(new CustomEvent('authStateChanged')); + navigate('/login'); + }; + + // El usuario y loading ahora vienen del contexto global + + // Definir todas las secciones + const allMenuSections = [ + { + title: 'Organización', + items: [ + { + name: 'Home', + path: '/admin', + icon: ( + + + + ) + }, + { + name: 'Mi Organización', + path: '/organization', + icon: ( + + + + ) + } + ] + }, + { + title: 'Servicios', + items: [ + { + name: 'Procesos', + path: '/procesos', + icon: ( + + + + ) + } + ] + }, + { + title: 'Documentación', + items: [ + { + name: 'Reportes', + path: '/reports', + icon: ( + + + + ) + }, + { + name: 'Expedientes', + path: '/expedientes', + icon: ( + + + + + ) + }, + { + name: 'Documentos', + path: '/documents', + icon: ( + + + + ) + } + ] + }, + // Nueva sección Tableros + { + title: 'Tableros', + items: [ + { + name: 'Uso de Almacenamiento', + path: '/tablero/almacenamiento', + icon: ( + + + + ) + } + ] + }, + { + title: 'Acceso a Usuarios', + items: [ + ...( + isImportador + ? [] + : [ + { + name: 'Usuarios', + path: '/users', + icon: ( + + + + ) + } + ] + ), + { + name: 'Vucem', + path: '/vucem', + icon: ( + + + + + ) + } + ] + } + ]; + + // Filtrar secciones según si es importador + // Modificar items según si es importador + const menuSections = allMenuSections + .map(section => { + if (section.title === 'Organización') { + return { + ...section, + items: section.items.filter(item => !(isImportador && item.name === 'Mi Organización')) + }; + } + // Para Acceso a Usuarios, no filtrar la sección, solo los items ya están condicionados arriba + if (section.title === 'Tableros' && isImportador) { + return null; + } + return section; + }) + .filter(Boolean); + + return ( +
+ {/* Header - Logo y colapsar */} +
+
+ {!isCollapsed && ( +
+ {/* Logo de la organización */} +
+ + + +
+

EFC Dashboard

+
+ )} + +
+
+ + {/* Navigation */} + + + {/* Configuración */} +
+ +
+ + + + +
+ {!isCollapsed && ( + Configuración + )} + +
+ + {/* Footer - Perfil del Usuario */} +
+ {!isCollapsed ? ( +
+ {/* Información del usuario */} +
+
+ {currentUser?.profile_picture ? ( + Avatar del usuario + ) : ( +
+ + + +
+ )} +
+
+ {loading ? ( +
+
+
+
+ ) : currentUser ? ( + <> +

+ {currentUser.first_name} {currentUser.last_name} +

+

+ {currentUser.username} +

+ + ) : ( + <> +

+ Usuario +

+

+ Sin datos +

+ + )} + {/* Debug temporal */} + {process.env.NODE_ENV === 'development' && ( +
+ Debug: {loading ? 'Cargando...' : currentUser ? 'Datos OK' : 'Sin datos'} +
+ )} +
+
+ + {/* Botón de logout más pequeño */} +
+ +
+
+ ) : ( +
+ {/* Avatar pequeño */} + {currentUser?.profile_picture ? ( + Avatar del usuario + ) : ( +
+ + + +
+ )} + {/* Botón de logout compacto */} + +
+ )} +
+
+ ); +} diff --git a/src/components/Sidebar.jsx.bak b/src/components/Sidebar.jsx.bak new file mode 100644 index 0000000..b5277ac --- /dev/null +++ b/src/components/Sidebar.jsx.bak @@ -0,0 +1,211 @@ +import React, { useState } from 'react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; + +export default function Sidebar() { + const [isCollapsed, setIsCollapsed] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + + // Disparar evento para actualizar el navbar + window.dispatchEvent(new CustomEvent('authStateChanged')); + + navigate('/login'); + }; + +export default function Sidebar() { + const [isCollapsed, setIsCollapsed] = useState(false); + const location = useLocation(); + + const menuItems = [ + { + name: 'Mi Organización', + path: '/organization', + icon: ( + + + + ) + }, + { + name: 'Importadores', + path: '/importers', + icon: ( + + + + ) + }, + { + name: 'Usuarios', + path: '/users', + icon: ( + + + + ) + }, + { + name: 'Reportes', + path: '/reports', + icon: ( + + + + ) + }, + { + name: 'Documentos', + path: '/documents', + icon: ( + + + + ) + } + ]; + + return ( +
+ {/* Header - Logo y colapsar */} +
+
+ {!isCollapsed && ( +
+ {/* Logo de la organización */} +
+ + + +
+

EFC Dashboard

+
+ )} + +
+
+ + {/* Navigation */} + + + {/* Configuración */} +
+ +
+ + + + +
+ {!isCollapsed && ( + Configuración + )} + +
+ + {/* Footer - Perfil del Usuario */} +
+ {!isCollapsed ? ( +
+ {/* Información del usuario */} +
+
+ Avatar del usuario +
+
+

+ Juan Pérez +

+

+ Administrador +

+
+
+ + {/* Botón de logout más pequeño */} +
+ +
+
+ ) : ( +
+ {/* Avatar pequeño */} + Avatar del usuario + {/* Botón de logout compacto */} + +
+ )} +
+
+ ); +} diff --git a/src/components/SidebarNew.jsx b/src/components/SidebarNew.jsx new file mode 100644 index 0000000..6a830a7 --- /dev/null +++ b/src/components/SidebarNew.jsx @@ -0,0 +1,207 @@ +import React, { useState } from 'react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; + +export default function Sidebar() { + const [isCollapsed, setIsCollapsed] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + + // Disparar evento para actualizar el navbar + window.dispatchEvent(new CustomEvent('authStateChanged')); + + navigate('/login'); + }; + + const menuItems = [ + { + name: 'Mi Organización', + path: '/organization', + icon: ( + + + + ) + }, + { + name: 'Importadores', + path: '/importers', + icon: ( + + + + ) + }, + { + name: 'Usuarios', + path: '/users', + icon: ( + + + + ) + }, + { + name: 'Reportes', + path: '/reports', + icon: ( + + + + ) + }, + { + name: 'Documentos', + path: '/documents', + icon: ( + + + + ) + } + ]; + + return ( +
+ {/* Header - Logo y colapsar */} +
+
+ {!isCollapsed && ( +
+ {/* Logo de la organización */} +
+ + + +
+

EFC Dashboard

+
+ )} + +
+
+ + {/* Navigation */} + + + {/* Configuración */} +
+ +
+ + + + +
+ {!isCollapsed && ( + Configuración + )} + +
+ + {/* Footer - Perfil del Usuario */} +
+ {!isCollapsed ? ( +
+ {/* Información del usuario */} +
+
+ Avatar del usuario +
+
+

+ Juan Pérez +

+

+ Administrador +

+
+
+ + {/* Botón de logout más pequeño */} +
+ +
+
+ ) : ( +
+ {/* Avatar pequeño */} + Avatar del usuario + {/* Botón de logout compacto */} + +
+ )} +
+
+ ); +} diff --git a/src/components/SuccessModal.jsx b/src/components/SuccessModal.jsx new file mode 100644 index 0000000..81f4d6e --- /dev/null +++ b/src/components/SuccessModal.jsx @@ -0,0 +1,25 @@ +import React from 'react'; + +export default function SuccessModal({ open, onClose, message = 'Descarga exitosa' }) { + if (!open) return null; + return ( +
+
+ + + +

{message}

+ +
+ +
+ ); +} diff --git a/src/components/TestTailwind.jsx b/src/components/TestTailwind.jsx new file mode 100644 index 0000000..f8ea275 --- /dev/null +++ b/src/components/TestTailwind.jsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export default function TestTailwind() { + return ( +
+

¡Tailwind CSS funciona!

+

Si ves este texto en azul con padding y sombra, Tailwind está trabajando correctamente.

+
+ ); +} diff --git a/src/components/card.jsx b/src/components/card.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/context/NotificationContext.jsx b/src/context/NotificationContext.jsx new file mode 100644 index 0000000..f2c8728 --- /dev/null +++ b/src/context/NotificationContext.jsx @@ -0,0 +1,94 @@ +import React, { createContext, useState, useContext } from 'react'; +import { colors } from '../theme'; + +const NotificationContext = createContext(); + +export function useNotification() { + return useContext(NotificationContext); +} + +export function NotificationProvider({ children }) { + const [message, setMessage] = useState(''); + const [type, setType] = useState('info'); // 'info', 'error', 'success', 'warning' + + const showMessage = (msg, msgType = 'info') => { + setMessage(msg); + setType(msgType); + setTimeout(() => setMessage(''), 4000); + }; + + const getNotificationStyles = (type) => { + const baseStyles = { + position: 'fixed', + top: 20, + left: '50%', + transform: 'translateX(-50%)', + color: '#FFFFFF', + padding: '12px 24px', + borderRadius: '12px', + zIndex: 9999, + boxShadow: '0 4px 16px rgba(0,0,0,0.15)', + fontWeight: '500', + fontSize: '14px', + maxWidth: '400px', + textAlign: 'center', + animation: 'slideInDown 0.3s ease-out' + }; + + const typeStyles = { + error: { background: '#C62828' }, + success: { background: '#2E7D32' }, + warning: { background: '#F57C00' }, + info: { background: '#4DA6FF' } + }; + + return { ...baseStyles, ...typeStyles[type] }; + }; + + return ( + + {children} + {message && ( + <> +
+
+ {type === 'success' && ( + + + + )} + {type === 'error' && ( + + + + )} + {type === 'warning' && ( + + + + )} + {type === 'info' && ( + + + + )} + {message} +
+
+ + + )} +
+ ); +} diff --git a/src/context/UserContext.jsx b/src/context/UserContext.jsx new file mode 100644 index 0000000..d5eb4b0 --- /dev/null +++ b/src/context/UserContext.jsx @@ -0,0 +1,90 @@ +import React, { createContext, useContext, useEffect, useState, useRef } from 'react'; +import { getCurrentUser } from '../api/users.ts'; +import { refreshToken } from '../api/auth.js'; + +const UserContext = createContext({ + user: null, + loading: true, + error: null, + refreshUser: () => {}, +}); + +export function UserProvider({ children }) { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const fetchedOnce = useRef(false); + + const fetchUser = async () => { + if (fetchedOnce.current && loading) return; + setLoading(true); + setError(null); + let token = localStorage.getItem('access'); + let triedRefresh = false; + while (true) { + try { + if (token) { + const userData = await getCurrentUser(token); + setUser(userData); + } else { + setUser(null); + } + break; + } catch (err) { + // Si el token expiró, intenta refrescarlo una vez + if (!triedRefresh && (err.message === 'SESSION_EXPIRED' || err.message.includes('401'))) { + triedRefresh = true; + const refresh = localStorage.getItem('refresh'); + if (refresh) { + try { + const data = await refreshToken(refresh); + if (data.access) { + localStorage.setItem('access', data.access); + token = data.access; + continue; // Reintenta con el nuevo token + } else { + throw new Error('No se pudo refrescar el token'); + } + } catch (refreshErr) { + setError(refreshErr); + setUser(null); + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + window.dispatchEvent(new CustomEvent('authStateChanged')); + break; + } + } else { + setUser(null); + break; + } + } else { + setError(err); + setUser(null); + break; + } + } + } + setLoading(false); + fetchedOnce.current = true; + }; + + useEffect(() => { + fetchUser(); + const handler = () => { + fetchedOnce.current = false; + fetchUser(); + }; + window.addEventListener('authStateChanged', handler); + return () => window.removeEventListener('authStateChanged', handler); + }, []); + + return ( + + {children} + + ); +} + +export function useUser() { + return useContext(UserContext); +} diff --git a/src/fetchWithAuth.js b/src/fetchWithAuth.js new file mode 100644 index 0000000..e69de29 diff --git a/src/hooks/usePolling.js b/src/hooks/usePolling.js new file mode 100644 index 0000000..35b5e30 --- /dev/null +++ b/src/hooks/usePolling.js @@ -0,0 +1,73 @@ +import { useState, useEffect, useRef } from 'react'; + +export function usePolling(fetchFunction, interval = 30000, dependencies = []) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const intervalRef = useRef(null); + const isActiveRef = useRef(true); + + const fetchData = async (showLoading = false) => { + if (showLoading) setLoading(true); + setError(null); + + try { + const result = await fetchFunction(); + if (isActiveRef.current) { + setData(result); + setLoading(false); + } + } catch (err) { + if (isActiveRef.current) { + setError(err); + setLoading(false); + } + } + }; + + const startPolling = () => { + if (intervalRef.current) return; // Ya está corriendo + + fetchData(true); // Fetch inicial + intervalRef.current = setInterval(() => { + if (isActiveRef.current) { + fetchData(false); // Fetch sin loading para no molestar al usuario + } + }, interval); + }; + + const stopPolling = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; + + const refetch = () => { + fetchData(true); + }; + + useEffect(() => { + isActiveRef.current = true; + startPolling(); + + // Parar polling cuando el componente se desmonta o la pestaña no está visible + const handleVisibilityChange = () => { + if (document.hidden) { + stopPolling(); + } else { + startPolling(); + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + + return () => { + isActiveRef.current = false; + stopPolling(); + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, dependencies); + + return { data, loading, error, refetch, startPolling, stopPolling }; +} diff --git a/src/hooks/useWebSocket.js b/src/hooks/useWebSocket.js new file mode 100644 index 0000000..568087d --- /dev/null +++ b/src/hooks/useWebSocket.js @@ -0,0 +1,49 @@ +import { useEffect, useState, useRef } from 'react'; +import { io } from 'socket.io-client'; + +export function useWebSocket(url, events = {}) { + const [isConnected, setIsConnected] = useState(false); + const socketRef = useRef(null); + + useEffect(() => { + // Conectar WebSocket + socketRef.current = io(url, { + transports: ['websocket'], + auth: { + token: localStorage.getItem('access') + } + }); + + const socket = socketRef.current; + + socket.on('connect', () => { + console.log('WebSocket conectado'); + setIsConnected(true); + }); + + socket.on('disconnect', () => { + console.log('WebSocket desconectado'); + setIsConnected(false); + }); + + // Registrar eventos personalizados + Object.entries(events).forEach(([eventName, handler]) => { + socket.on(eventName, handler); + }); + + return () => { + Object.keys(events).forEach(eventName => { + socket.off(eventName); + }); + socket.disconnect(); + }; + }, [url]); + + const emit = (eventName, data) => { + if (socketRef.current && isConnected) { + socketRef.current.emit(eventName, data); + } + }; + + return { isConnected, emit }; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..7450b59 --- /dev/null +++ b/src/index.css @@ -0,0 +1,21 @@ + +@tailwind base; +@tailwind components; +@tailwind utilities; + +button:hover { + border-color: #646cff; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..4a64a0d --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,19 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' +import { NotificationProvider } from './context/NotificationContext'; + +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; + + +createRoot(document.getElementById('root')).render( + + + + + , +) diff --git a/src/pages/Admin.jsx b/src/pages/Admin.jsx new file mode 100644 index 0000000..289bf2f --- /dev/null +++ b/src/pages/Admin.jsx @@ -0,0 +1,373 @@ +import React, { useEffect, useState } from 'react'; +// Animación fade-in/slide-up para cards +const fadeInSlideUp = `@keyframes fadein-slideup { + 0% { opacity: 0; transform: translateY(40px); } + 100% { opacity: 1; transform: translateY(0); } +}`; + +// Inyectar animación global si no existe +if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-admin')) { + const style = document.createElement('style'); + style.id = 'fadein-slideup-admin'; + style.innerHTML = fadeInSlideUp; + document.head.appendChild(style); +} +import TestTailwind from '../components/TestTailwind'; +import { colors } from '../theme'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export default function Admin() { + // Estado de servicios + const [services, setServices] = useState(null); + // Estado de descargas + const [downloads, setDownloads] = useState(null); + // Últimos documentos + const [latestDocs, setLatestDocs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + // Estado para análisis de actividad de usuario + const [userActivity, setUserActivity] = useState(null); + + useEffect(() => { + async function fetchData() { + setLoading(true); + setError(''); + try { + const token = localStorage.getItem('access'); + const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + + // Servicios + const resServices = await fetch(`${API_URL}/cards/services-util-information/`, { headers }); + if (!resServices.ok) throw new Error('Error al obtener estados de servicios'); + const dataServices = await resServices.json(); + setServices(dataServices); + + // Descargas + const resDownloads = await fetch(`${API_URL}/cards/document-util-information/`, { headers }); + if (!resDownloads.ok) throw new Error('Error al obtener información de descargas'); + const dataDownloads = await resDownloads.json(); + setDownloads(dataDownloads); + + // Últimos documentos + const resDocs = await fetch(`${API_URL}/cards/downloaded-documents/`, { headers }); + if (!resDocs.ok) throw new Error('Error al obtener últimos documentos'); + const dataDocs = await resDocs.json(); + setLatestDocs(dataDocs.documentos); + + // Análisis de actividad de usuario + const resUserActivity = await fetch(`${API_URL}/cards/user-activity-analysis/`, { headers }); + if (!resUserActivity.ok) throw new Error('Error al obtener análisis de actividad de usuario'); + const dataUserActivity = await resUserActivity.json(); + setUserActivity(dataUserActivity); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setLoading(false); + } + } + fetchData(); + }, []); + + // Helper para nombre de archivo + // Helper para nombre de archivo + function getFileName(path) { + return path.split('/').pop() || path; + } + + return ( +
+
+ {/* Header + Estado del Sistema alineados horizontalmente */} +
+ {/* Header principal mejorado */} +
+
+ + + +
+
+

+ Panel de Administración + {services && ( + + {services.en_espera} en espera + + )} +

+

+ {typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true' + ? 'Dashboard principal para gestión de Expediente electrónico' + : 'Dashboard principal para gestión de agencia aduanal'} +

+
+ {/* Efecto decorativo de fondo */} +
+ + + + + + + + + +
+ {/* Animación personalizada para el icono y contador */} + +
+ {/* Estado del Sistema card a la derecha */} +
+
+
+
+ + + +
+

Estado del Sistema

+
+
+
+ API Backend + + + Conectado + +
+
+ API Servicios + + + Conectado + +
+
+ Última Actualización + + Hace 2 min + +
+
+ {/* Efecto decorativo de fondo */} +
+ + + + + + + + + +
+ {/* Animación personalizada para el icono */} + +
+
+
+ + {/* Stats Cards con datos de endpoints */} +
+ {/* Estados de servicios */} +
+
+
+
+ + + +
+
+
+

Procesos en Espera

+

{services ? services.en_espera : '-'}

+

Total: {services ? services.procesos_filtrados : '-'}

+
+
+
+
+
+
+
+ + + +
+
+
+

En Proceso

+

{services ? services.en_proceso : '-'}

+

Finalizados: {services ? services.finalizados : '-'}

+
+
+
+
+
+
+
+ + + +
+
+
+

Con Error

+

{services ? services.con_error : '-'}

+

Finalizados: {services ? services.finalizados : '-'}

+
+
+
+ {/* Descargas */} +
+
+
+
+ + + +
+
+
+

Descargados 1 día

+

{downloads ? downloads.archivos_ultimas_1_dia : '-'}

+

7 días: {downloads ? downloads.archivos_ultimos_7_dias : '-'} | 30 días: {downloads ? downloads.archivos_ultimos_30_dias : '-'}

+
+
+
+
+ {/* Análisis de actividad de usuario */} + {!(typeof window !== 'undefined' && localStorage.getItem('user_is_importador') === 'true') && ( +
+

Actividad de Usuarios

+ {loading ? ( +
Cargando...
+ ) : error ? ( +
{error}
+ ) : userActivity ? ( +
+
+

Resumen de acciones

+
    + {Object.entries(userActivity.actions_count).map(([action, count]) => ( +
  • + {action} + {count} +
  • + ))} +
  • + Total actividades + {userActivity.actividades_filtradas} +
  • +
+
+
+

Top usuarios

+
    + {userActivity.top_users.map((user, idx) => ( +
  1. + {user.username} + {user.activity_count} +
  2. + ))} +
+
+
+ ) : null} +
+ )} + + {/* Tabla de últimos documentos */} +
+

Últimos documentos agregados

+ {loading ? ( +
Cargando...
+ ) : error ? ( +
{error}
+ ) : ( +
+ + + + + + + + + + + {latestDocs.map(doc => ( + + + + + + + ))} + +
ArchivoPedimentoOrganizaciónFecha
{getFileName(doc.archivo)}{doc.pedimento}{doc.organizacion}{new Date(doc.created_at).toLocaleString('es-MX')}
+
+ )} +
+ + +
+
+ ); +} diff --git a/src/pages/Debug.jsx b/src/pages/Debug.jsx new file mode 100644 index 0000000..ffbd936 --- /dev/null +++ b/src/pages/Debug.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export default function Debug() { + return ( +
+

🐛 Debug Page

+

Si ves esto, React está funcionando correctamente.

+

Fecha y hora: {new Date().toLocaleString()}

+

Token en localStorage: {localStorage.getItem('access') ? 'SÍ' : 'NO'}

+

URL actual: {window.location.href}

+
+ ); +} diff --git a/src/pages/Documents.jsx b/src/pages/Documents.jsx new file mode 100644 index 0000000..2ee2dbd --- /dev/null +++ b/src/pages/Documents.jsx @@ -0,0 +1,645 @@ +import React, { useEffect, useState, useLayoutEffect, useRef } from 'react'; +import SuccessModal from '../components/SuccessModal.jsx'; +// Animación fade-in/slide-up para bloques +const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`; +if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) { + const style = document.createElement('style'); + style.id = 'fadein-slideup-documents'; + style.innerHTML = fadeInSlideUp; + document.head.appendChild(style); +} +import { fetchPedimentoDocuments } from '../api/documentos.ts'; +import { useNotification } from '../context/NotificationContext'; +// import { usePolling } from '../hooks/usePolling'; +import { Link } from 'react-router-dom'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +// Descarga individual +const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => { + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (res.status === 401) { + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + return; + } + if (!res.ok) { + alert('No autorizado o error en la descarga'); + return; + } + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + if (setSuccess) setSuccess('Descarga exitosa'); +}; + +// Descarga masiva (bulk) +const downloadBulkZip = async (ids, showMessage, setSuccess, nombreZip = 'documentos') => { + if (!ids.length) { + showMessage('Selecciona al menos un documento.', 'error'); + return; + } + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}/record/documents/bulk-download/`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ document_ids: ids, pedimento_nombre: nombreZip }), + }); + if (res.status === 401) { + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + return; + } + if (!res.ok) { + showMessage('No autorizado o error en la descarga masiva', 'error'); + return; + } + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${nombreZip || 'documentos'}.zip`; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + if (setSuccess) setSuccess('Descarga(s) completada(s)'); +}; + +export default function Documents() { + const focusKeeperRef = useRef(null); + const [success, setSuccess] = useState(''); + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(10); + const [extensionFilter, setExtensionFilter] = useState(''); + const [documentTypeFilter, setDocumentTypeFilter] = useState(''); + const [createdAtFilter, setCreatedAtFilter] = useState(''); + const [pedimentoNumeroFilter, setPedimentoNumeroFilter] = useState(''); + const { showMessage } = useNotification(); + // Estado para controlar la animación de entrada + const [showAnimation, setShowAnimation] = useState(false); + const [hasAnimated, setHasAnimated] = useState(false); + useLayoutEffect(() => { + // Forzar un render antes de activar la animación + setShowAnimation(true); + }, []); + useEffect(() => { + if (showAnimation && !hasAnimated) { + const timeout = setTimeout(() => { + setHasAnimated(true); + setShowAnimation(false); + }, 700); // Duración igual a la animación + return () => clearTimeout(timeout); + } + }, [showAnimation, hasAnimated]); + + // Estado local para los datos, loading y error + const [docsData, setDocsData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + // Fetch de datos solo al cargar la página o cuando cambian los filtros/paginación + useEffect(() => { + let isMounted = true; + const fetchDocsData = async () => { + setLoading(true); + setError(null); + try { + const token = localStorage.getItem('access'); + const data = await fetchPedimentoDocuments(token, '', currentPage, itemsPerPage, { + pedimento_numero: pedimentoNumeroFilter, + extension: extensionFilter, + document_type: documentTypeFilter, + created_at: createdAtFilter, + }); + if (isMounted) setDocsData(data); + } catch (err) { + if (isMounted) setError(err); + } finally { + if (isMounted) setLoading(false); + } + }; + fetchDocsData(); + return () => { isMounted = false; }; + }, [currentPage, itemsPerPage, pedimentoNumeroFilter, extensionFilter, documentTypeFilter, createdAtFilter]); + + // Refetch manual (si se quiere usar en el futuro) + const refetch = () => { + setCurrentPage(1); // Esto forzará el useEffect a recargar + }; + + // Manejo de errores de sesión + useEffect(() => { + if (error && error.message === 'SESSION_EXPIRED') { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + } else if (error) { + showMessage(error.message, 'error'); + } + }, [error, showMessage]); + + + // Cálculos de paginación usando la estructura tipada + const documentsArray = docsData && docsData.results ? docsData.results : []; + const totalDocuments = docsData && typeof docsData.count === 'number' ? docsData.count : 0; + const totalPages = totalDocuments > 0 ? Math.ceil(totalDocuments / itemsPerPage) : 1; + const currentDocuments = documentsArray; + + // Selección de documentos + const [selectedDocs, setSelectedDocs] = useState([]); + // allSelected: todos los docs de la página actual están seleccionados + const allSelected = currentDocuments.length > 0 && selectedDocs.length === currentDocuments.length; + // someSelected: hay al menos uno seleccionado pero no todos + const someSelected = selectedDocs.length > 0 && selectedDocs.length < currentDocuments.length; + + // Handlers para selección + const handleSelectOne = (id) => { + setSelectedDocs(prev => prev.includes(id) ? prev.filter(d => d !== id) : [...prev, id]); + }; + const handleSelectAll = () => { + if (allSelected) { + setSelectedDocs([]); + } else { + setSelectedDocs(currentDocuments.map(doc => doc.id)); + } + }; + + + // Descargar seleccionados (bulk) con prompt para nombre del zip + const handleDownloadSelected = async () => { + const ids = currentDocuments.filter(doc => selectedDocs.includes(doc.id)).map(doc => doc.id); + if (ids.length === 1) { + // Si solo hay uno, descarga individual + const doc = currentDocuments.find(doc => doc.id === ids[0]); + await downloadFile(doc.id, doc.archivo ? doc.archivo.split('/').pop() : 'archivo', () => { + setSuccess('Descarga exitosa'); + setShowSuccessModal(true); + }, null, showMessage); + } else if (ids.length > 1) { + let nombreZip = window.prompt('¿Qué nombre quieres para el archivo zip?', 'documentos_seleccionados'); + if (!nombreZip) nombreZip = 'documentos_seleccionados'; + await downloadBulkZip(ids, showMessage, () => { + setSuccess('Descarga exitosa'); + setShowSuccessModal(true); + }, nombreZip); + } + }; + + // Descargar todos los de la página (bulk) con prompt para nombre del zip + const handleDownloadAll = async () => { + const ids = currentDocuments.map(doc => doc.id); + if (ids.length === 1) { + const doc = currentDocuments[0]; + await downloadFile(doc.id, doc.archivo ? doc.archivo.split('/').pop() : 'archivo', () => { + setSuccess('Descarga exitosa'); + setShowSuccessModal(true); + }, null, showMessage); + } else if (ids.length > 1) { + let nombreZip = window.prompt('¿Qué nombre quieres para el archivo zip?', 'documentos_pagina'); + if (!nombreZip) nombreZip = 'documentos_pagina'; + await downloadBulkZip(ids, showMessage, () => { + setSuccess('Descarga exitosa'); + setShowSuccessModal(true); + }, nombreZip); + } + }; + + // Limpiar selección al cambiar de página o documentos + useEffect(() => { + setSelectedDocs([]); + }, [currentPage, itemsPerPage, pedimentoNumeroFilter, extensionFilter, documentTypeFilter, createdAtFilter, docsData]); + + // Obtener lista única de contribuyentes para el combobox (de la página actual) + const contribuyentes = Array.from(new Set(currentDocuments.map(d => d.contribuyente).filter(Boolean))); + + // Refuerza la paginación SPA: nunca recarga la página, solo cambia el estado local + const handlePageChange = (newPage, e) => { + if (e && typeof e.preventDefault === 'function') e.preventDefault(); + if (e && typeof e.stopPropagation === 'function') e.stopPropagation(); + if (newPage < 1 || newPage > totalPages || newPage === currentPage) return; + setCurrentPage(newPage); + // Quitar el foco del botón activo para evitar salto de scroll + if (typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }; + + // Forzar foco al div invisible para evitar saltos por enfoque automático + useLayoutEffect(() => { + if (focusKeeperRef.current) { + focusKeeperRef.current.focus(); + } + }, [currentPage]); + + const handleItemsPerPageChange = (newItemsPerPage) => { + setItemsPerPage(newItemsPerPage); + setCurrentPage(1); // Reset a la primera página + }; + + + // El layout principal y la tabla siempre se renderizan, loader/error/empty solo dentro del área de la tabla + + return ( +
+ +
+ {/* Header mejorado y decorativo */} +
+
+ + + +
+
+

+ Documentos + {totalDocuments} +

+

Descarga los documentos de tus pedimentos.

+
+ {/* Efecto decorativo de fondo */} +
+ + + + + + + + + +
+
+ {/* Animación personalizada para el icono y contador */} + + +
+
+
+ {/* Header de Documentos Relacionados arriba de los filtros */} +
+

+ Todos los Documentos +

+
+
+ {/* Filtros de query parameters */} +
+ {/* Filtros avanzados */} +
+ {/* Pedimento Número */} +
+ + setPedimentoNumeroFilter(e.target.value)} + placeholder="Buscar por número de pedimento..." + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Extensión */} +
+ + +
+ {/* Tipo de documento */} +
+ + +
+ {/* Fecha de creación */} +
+ + setCreatedAtFilter(e.target.value)} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+
+ {/* Botón de actualizar eliminado por solicitud */} + setShowSuccessModal(false)} message={success || 'Descarga exitosa'} /> + {/* Botones de descarga */} + {currentDocuments.length > 0 && ( +
+ + +
+ )} +
+
6 ? 'auto' : 'hidden', position: 'relative' }}> + + + + + + + + + + + + + + {/* Loader/Error/Empty state dentro del área de la tabla, sin cambiar el layout */} + {loading ? ( + + + + ) : error ? ( + + + + ) : currentDocuments.length > 0 ? ( + <> + {currentDocuments.map(doc => ( + + + + + + + + + + ))} + {/* Rellenar con filas vacías si hay menos de 8 */} + {currentDocuments.length < 8 && !loading && !error && Array.from({length: 8 - currentDocuments.length}).map((_, idx) => ( + + + + ))} + + ) : ( + + + + )} + +
+ { if (el) el.indeterminate = someSelected; }} + onChange={handleSelectAll} + className="h-3.5 w-3.5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded align-middle" + style={{ minWidth: '14px', minHeight: '14px' }} + /> + PedimentoArchivoTipoTamañoExtensiónAcciones
+
+ Cargando documentos... +
+
+
+ Error: {error.message || 'Error al cargar documentos'} +
+
+ handleSelectOne(doc.id)} + className="h-3.5 w-3.5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded align-middle" + style={{ minWidth: '14px', minHeight: '14px' }} + /> + {doc.pedimento_numero}{doc.archivo ? doc.archivo.split('/').pop() : ''}{ + (() => { + switch (String(doc.document_type)) { + case '1': return 'Pedimento Partida'; + case '2': return 'Pedimento Completo'; + case '3': return 'Pedimento Remesas'; + case '4': return 'Pedimento Acuse'; + case '5': return 'Pedimento EDocument'; + case '6': return 'Estado Pedimento'; + default: return doc.document_type || ''; + } + })() + }{doc.size}{doc.extension} + +
+  
+
+
+ + + +
+

No hay pedimentos

+

Aún no tienes pedimentos registrados.

+
+
+
+
+ + {/* Botón de actualizar eliminado por solicitud */} + setShowSuccessModal(false)} message={success || 'Descarga exitosa'} /> +
+ +
+ + {/* Paginación con botones numerados y elipsis */} + {totalDocuments > 0 && ( +
+ {(() => { + const totalPages = Math.max(1, Math.ceil(totalDocuments / itemsPerPage)); + const maxPagesToShow = 5; + let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); + let endPage = startPage + maxPagesToShow - 1; + if (endPage > totalPages) { + endPage = totalPages; + startPage = Math.max(1, endPage - maxPagesToShow + 1); + } + const pageNumbers = []; + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push(i); + } + return ( +
+
+ + +
+
+ + + {pageNumbers.map(num => ( + + ))} + + + Página {currentPage} de {totalPages} +
+
+ ); + })()} +
+ )} +
+
+
+
+ ); +} diff --git a/src/pages/Expedientes.jsx b/src/pages/Expedientes.jsx new file mode 100644 index 0000000..bf3e33c --- /dev/null +++ b/src/pages/Expedientes.jsx @@ -0,0 +1,578 @@ +import React, { useEffect, useState, useLayoutEffect, useRef } from 'react'; +// Animación fade-in/slide-up para bloques +const fadeInSlideUp = `@keyframes fadein-slideup { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } }`; +if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-documents')) { + const style = document.createElement('style'); + style.id = 'fadein-slideup-documents'; + style.innerHTML = fadeInSlideUp; + document.head.appendChild(style); +} +import { fetchDocuments } from '../api/expedientes.ts'; +import { useNotification } from '../context/NotificationContext'; +import { usePolling } from '../hooks/usePolling'; +import { Link } from 'react-router-dom'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +const downloadFile = async (id, filename = 'archivo', setSuccess, setError, showMessage) => { + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (res.status === 401) { + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + return; + } + if (!res.ok) { + alert('No autorizado o error en la descarga'); + return; + } + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + if (setSuccess) setSuccess('Descarga exitosa');useEffect +}; + +export default function Documents() { + const focusKeeperRef = useRef(null); + const [success, setSuccess] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(10); + const [alertaFilter, setAlertaFilter] = useState('all'); // all, true, false + const [expedienteFilter, setExpedienteFilter] = useState('all'); // all, true, false + const [contribuyenteFilter, setContribuyenteFilter] = useState(''); + const [contribuyenteInput, setContribuyenteInput] = useState(''); + const [fechaPagoFilter, setFechaPagoFilter] = useState(''); + const [pedimentoFilter, setPedimentoFilter] = useState(''); + const [searchFilter, setSearchFilter] = useState(''); + const [curpApoderadoFilter, setCurpApoderadoFilter] = useState(''); + const [patenteFilter, setPatenteFilter] = useState(''); + const [aduanaFilter, setAduanaFilter] = useState(''); + const [tipoOperacionFilter, setTipoOperacionFilter] = useState(''); + const [clavePedimentoFilter, setClavePedimentoFilter] = useState(''); + const { showMessage } = useNotification(); + // Estado para controlar la animación de entrada + const [showAnimation, setShowAnimation] = useState(false); + const [hasAnimated, setHasAnimated] = useState(false); + useLayoutEffect(() => { + // Forzar un render antes de activar la animación + setShowAnimation(true); + }, []); + useEffect(() => { + if (showAnimation && !hasAnimated) { + const timeout = setTimeout(() => { + setHasAnimated(true); + setShowAnimation(false); + }, 700); // Duración igual a la animación + return () => clearTimeout(timeout); + } + }, [showAnimation, hasAnimated]); + + // Fetching usando la función tipada de TypeScript + const fetchPedimentosData = async (page = currentPage, pageSize = itemsPerPage) => { + const token = localStorage.getItem('access'); + // Construir objeto de filtros + const filters = { + search: searchFilter || undefined, + pedimento: pedimentoFilter || undefined, + existe_expediente: expedienteFilter === 'all' ? undefined : expedienteFilter, + alerta: alertaFilter === 'all' ? undefined : alertaFilter, + contribuyente: contribuyenteFilter || undefined, + curp_apoderado: curpApoderadoFilter || undefined, + fecha_pago: fechaPagoFilter || undefined, + patente: patenteFilter || undefined, + aduana: aduanaFilter || undefined, + tipo_operacion: tipoOperacionFilter || undefined, + clave_pedimento: clavePedimentoFilter || undefined, + }; + return await fetchDocuments(token, page, pageSize, filters); + }; + + // Hook de polling que se ejecuta cada 30 segundos + const { data: pedimentos, loading, error, refetch } = usePolling( + () => fetchPedimentosData(currentPage, itemsPerPage), + 30000, // 30 segundos + [currentPage, itemsPerPage, searchFilter, pedimentoFilter, expedienteFilter, alertaFilter, contribuyenteFilter, curpApoderadoFilter, fechaPagoFilter, patenteFilter, aduanaFilter, tipoOperacionFilter, clavePedimentoFilter] + ); + + // Manejo de errores de sesión + useEffect(() => { + if (error && error.message === 'SESSION_EXPIRED') { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + } else if (error) { + showMessage(error.message, 'error'); + } + }, [error, showMessage]); + + // Cálculos de paginación usando la estructura tipada + const documentsArray = pedimentos && pedimentos.results ? pedimentos.results : []; + const totalDocuments = pedimentos && typeof pedimentos.count === 'number' ? pedimentos.count : 0; + const totalPages = totalDocuments > 0 ? Math.ceil(totalDocuments / itemsPerPage) : 1; + const currentDocuments = documentsArray; + + // Obtener lista única de contribuyentes para el combobox (de la página actual) + const contribuyentes = Array.from(new Set(currentDocuments.map(d => d.contribuyente).filter(Boolean))); + + + // Refuerza la paginación SPA: nunca recarga la página, solo cambia el estado local + const handlePageChange = (newPage, e) => { + if (e && typeof e.preventDefault === 'function') e.preventDefault(); + if (e && typeof e.stopPropagation === 'function') e.stopPropagation(); + if (newPage < 1 || newPage > totalPages || newPage === currentPage) return; + setCurrentPage(newPage); + // Quitar el foco del botón activo para evitar salto de scroll + if (typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }; + + // Forzar foco al div invisible para evitar saltos por enfoque automático + useLayoutEffect(() => { + if (focusKeeperRef.current) { + focusKeeperRef.current.focus(); + } + }, [currentPage]); + + const handleItemsPerPageChange = (newItemsPerPage) => { + setItemsPerPage(newItemsPerPage); + setCurrentPage(1); // Reset a la primera página + }; + + + // El layout principal y la tabla siempre se renderizan, loader/error/empty solo dentro del área de la tabla + + return ( +
+ +
+ {/* Header mejorado y decorativo */} +
+
+ + + +
+
+

+ Expedientes + {totalDocuments} +

+

Gestiona y descarga los documentos de tus pedimentos.

+
+ {/* Efecto decorativo de fondo */} +
+ + + + + + + + + +
+
+ {/* Animación personalizada para el icono y contador */} + + +
+
+ {/* Filtros avanzados */} +
+ {/* Search global */} +
+ + setSearchFilter(e.target.value)} + placeholder="Buscar pedimento, contribuyente, agente aduanal..." + className="w-44 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Pedimento */} +
+ + setPedimentoFilter(e.target.value)} + placeholder="Buscar pedimento..." + className="w-36 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Alerta */} +
+ + +
+ {/* Expediente */} +
+ + +
+ {/* Contribuyente combobox */} +
+ + { + setContribuyenteInput(e.target.value); + setContribuyenteFilter(''); + }} + placeholder="Buscar o escribir..." + className="w-44 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + autoComplete="off" + /> + {/* Dropdown de sugerencias */} + {contribuyenteInput && ( +
+ {contribuyentes.filter(c => c.toLowerCase().includes(contribuyenteInput.toLowerCase())).length === 0 ? ( +
Sin coincidencias
+ ) : ( + contribuyentes.filter(c => c.toLowerCase().includes(contribuyenteInput.toLowerCase())).map(c => ( + + )) + )} +
+ )} +
+ {/* CURP Apoderado */} +
+ + setCurpApoderadoFilter(e.target.value)} + placeholder="CURP del apoderado..." + className="w-44 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Fecha de pago */} +
+ + setFechaPagoFilter(e.target.value)} + className="w-44 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" /> +
+ {/* Patente */} +
+ + setPatenteFilter(e.target.value)} + placeholder="Patente..." + className="w-36 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Aduana */} +
+ + setAduanaFilter(e.target.value)} + placeholder="Aduana..." + className="w-36 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Tipo de operación */} +
+ + setTipoOperacionFilter(e.target.value)} + placeholder="ID tipo operación..." + className="w-36 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+ {/* Clave pedimento */} +
+ + setClavePedimentoFilter(e.target.value)} + placeholder="Clave pedimento..." + className="w-36 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-gray-50" + /> +
+
+ +
+ + 🔄 Actualización automática cada 30 segundos + + + +
+ {success && ( +
+
+ + + +

{success}

+
+
+ )} +
+ +
+
+
8 ? 'auto' : 'hidden', position: 'relative' }}> + + + + + + + + + + + + + + + + {/* Loader/Error/Empty state dentro del área de la tabla, sin cambiar el layout */} + {loading ? ( + + + + ) : error ? ( + + + + ) : currentDocuments.length > 0 ? ( + <> + {currentDocuments.map(ped => ( + + + + + + + + + + + + ))} + {/* Rellenar con filas vacías si hay menos de 8 */} + {currentDocuments.length < 8 && !loading && !error && Array.from({length: 8 - currentDocuments.length}).map((_, idx) => ( + + + + ))} + + ) : ( + + + + )} + +
PedimentoFecha de pagoContribuyenteAlertaCURP ApoderadoImporte totalSaldo disponibleImporte pedimentoExpediente
+
+ Cargando documentos... +
+
+
+ Error: {error.message || 'Error al cargar documentos'} +
+
+ + {ped.pedimento} + + {ped.fechapago}{ped.contribuyente} + + {ped.alerta ? 'Sí' : 'No'} + + {ped.curp_apoderado}${ped.importe_total}${ped.saldo_disponible}${ped.importe_pedimento} + + {ped.existe_expediente ? 'Sí' : 'No'} + +
 
+
+
+ + + +
+

No hay pedimentos

+

Aún no tienes pedimentos registrados.

+
+
+
+
+ {/* Paginación con botones numerados y elipsis */} + {totalDocuments > 0 && ( +
+ {(() => { + const totalPages = Math.max(1, Math.ceil(totalDocuments / itemsPerPage)); + const maxPagesToShow = 5; + let startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); + let endPage = startPage + maxPagesToShow - 1; + if (endPage > totalPages) { + endPage = totalPages; + startPage = Math.max(1, endPage - maxPagesToShow + 1); + } + const pageNumbers = []; + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push(i); + } + return ( +
+
+ + +
+
+ + + {pageNumbers.map(num => ( + + ))} + + + Página {currentPage} de {totalPages} +
+
+ ); + })()} +
+ )} +
+
+
+
+ ); +} diff --git a/src/pages/ForgotPassword.jsx b/src/pages/ForgotPassword.jsx new file mode 100644 index 0000000..03e09ad --- /dev/null +++ b/src/pages/ForgotPassword.jsx @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +export default function ForgotPassword() { + const [email, setEmail] = useState(''); + const [username, setUsername] = useState(''); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(''); + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + setError(''); + setSuccess(false); + try { + const res = await fetch(`${API_URL}/user/password-reset/`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, email }), + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + setError(data.detail || 'No se pudo enviar el correo.'); + } else { + setSuccess(true); + } + } catch (err) { + setError('Error de red. Intenta de nuevo.'); + } + setLoading(false); + }; + + return ( +
+ {/* Background pattern */} +
+
+
+
+ {/* Main Card */} +
+ {/* Header with navy background */} +
+ +

+ Recuperar contraseña +

+

+ Ingresa tu usuario y correo para recibir el enlace de recuperación +

+
+ {/* Form */} +
+ {success ? ( +
+ Si el correo está registrado, recibirás un enlace para restablecer tu contraseña. +
+ ) : ( +
+
+ +
+
+ + + +
+ setUsername(e.target.value)} + onFocus={e => { + e.target.style.borderColor = 'transparent'; + e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; + }} + onBlur={e => { + e.target.style.borderColor = '#d1d5db'; + e.target.style.boxShadow = 'none'; + }} + onMouseEnter={e => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#4DA6FF'; + } + }} + onMouseLeave={e => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#d1d5db'; + } + }} + /> +
+
+
+ +
+
+ + + +
+ setEmail(e.target.value)} + onFocus={e => { + e.target.style.borderColor = 'transparent'; + e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; + }} + onBlur={e => { + e.target.style.borderColor = '#d1d5db'; + e.target.style.boxShadow = 'none'; + }} + onMouseEnter={e => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#4DA6FF'; + } + }} + onMouseLeave={e => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#d1d5db'; + } + }} + /> +
+
+ {error &&
{error}
} + +
+ )} + +
+
+ {/* Floating elements */} +
+
+
+
+ ); +} diff --git a/src/pages/Importers.jsx b/src/pages/Importers.jsx new file mode 100644 index 0000000..5211412 --- /dev/null +++ b/src/pages/Importers.jsx @@ -0,0 +1,427 @@ +import React, { useState, useEffect } from 'react'; + +export default function Importers() { + const [importers, setImporters] = useState([]); + const [loading, setLoading] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(10); + + // Datos dummy para mostrar + const dummyImporters = [ + { + id: 1, + name: 'Importadora ABC S.A.', + rfc: 'ABC123456789', + email: 'contacto@abc.com', + status: 'Activo', + lastActivity: '2024-01-15', + documentsCount: 45 + }, + { + id: 2, + name: 'Comercial XYZ Ltda.', + rfc: 'XYZ987654321', + email: 'info@xyz.com', + status: 'Activo', + lastActivity: '2024-01-14', + documentsCount: 23 + }, + { + id: 3, + name: 'Global Trade Corp.', + rfc: 'GTC555666777', + email: 'admin@globaltrade.com', + status: 'Inactivo', + lastActivity: '2024-01-10', + documentsCount: 12 + } + ]; + + useEffect(() => { + // Simular carga de datos + const timer = setTimeout(() => { + setImporters(dummyImporters); + setLoading(false); + }, 1000); + + return () => clearTimeout(timer); + }, []); + + const filteredImporters = importers.filter(importer => + importer.name.toLowerCase().includes(searchTerm.toLowerCase()) || + importer.rfc.toLowerCase().includes(searchTerm.toLowerCase()) || + importer.email.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Cálculos de paginación + const totalImporters = filteredImporters.length; + const totalPages = Math.ceil(totalImporters / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const currentImporters = filteredImporters.slice(startIndex, endIndex); + + // Reset página cuando cambia el filtro + useEffect(() => { + setCurrentPage(1); + }, [searchTerm]); + + const handlePageChange = (page) => { + setCurrentPage(page); + }; + + const handleItemsPerPageChange = (newItemsPerPage) => { + setItemsPerPage(newItemsPerPage); + setCurrentPage(1); // Reset a la primera página + }; + + const getStatusBadge = (status) => { + return status === 'Activo' + ? 'bg-success-100 text-success-800 border border-success-200' + : 'bg-danger-100 text-danger-800 border border-danger-200'; + }; + + if (loading) { + return ( +
+
+ + + + +

Cargando información de importadores...

+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+
+

+ Importadores +

+

Gestiona y supervisa las empresas importadoras registradas en el sistema.

+
+
+ + {/* Stats Cards */} +
+
+
+
+
+
+ + + +
+
+
+
+
Total Importadores
+
{importers.length}
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Activos
+
+ {importers.filter(i => i.status === 'Activo').length} +
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Inactivos
+
+ {importers.filter(i => i.status === 'Inactivo').length} +
+
+
+
+
+
+ +
+
+
+
+
+ + + +
+
+
+
+
Total Documentos
+
+ {importers.reduce((sum, i) => sum + i.documentsCount, 0)} +
+
+
+
+
+
+
+ + {/* Search and Actions */} +
+
+
+
+
+
+ + + +
+ setSearchTerm(e.target.value)} + /> +
+
+
+ +
+
+
+ + {/* Table */} +
+ + + + + + + + + + + + + {currentImporters.map((importer, index) => ( + + + + + + + + + ))} + +
+ Importador + + RFC + + Estado + + Documentos + + Última Actividad + + Acciones +
+
+
+
+ + + +
+
+
+
{importer.name}
+
{importer.email}
+
+
+
+ {importer.rfc} + + + {importer.status} + + +
+ {importer.documentsCount} + + docs + +
+
+ {new Date(importer.lastActivity).toLocaleDateString()} + +
+ + + +
+
+
+ + {/* Paginación */} + {totalImporters > 0 && ( +
+
+ + Mostrando {startIndex + 1} - {Math.min(endIndex, totalImporters)} de {totalImporters} importadores + + +
+ + {totalPages > 1 && ( +
+ + +
+ {[...Array(totalPages)].map((_, index) => { + const page = index + 1; + const isCurrentPage = page === currentPage; + const isNearCurrentPage = Math.abs(page - currentPage) <= 2; + const isFirstOrLast = page === 1 || page === totalPages; + + if (totalPages <= 7 || isNearCurrentPage || isFirstOrLast) { + return ( + + ); + } else if (page === currentPage - 3 || page === currentPage + 3) { + return ( + + ... + + ); + } + return null; + })} +
+ +
+ + Página {currentPage} de {totalPages} + +
+ + +
+ )} +
+ )} +
+ + {/* Empty state */} + {currentImporters.length === 0 && !loading && ( +
+
+
+ + + +
+

No se encontraron importadores

+

+ {searchTerm ? 'Intenta con otros términos de búsqueda.' : 'Comienza agregando un nuevo importador.'} +

+ {!searchTerm && ( + + )} +
+
+ )} +
+
+ ); +} diff --git a/src/pages/Landing.jsx b/src/pages/Landing.jsx new file mode 100644 index 0000000..e7ae626 --- /dev/null +++ b/src/pages/Landing.jsx @@ -0,0 +1,785 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; + +export default function Landing() { + const [isScrolled, setIsScrolled] = useState(false); + const [activeSection, setActiveSection] = useState('inicio'); + const [contactForm, setContactForm] = useState({ + name: '', + email: '', + company: '', + message: '' + }); + + // Efecto de scroll para navbar + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 50); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + // Smooth scroll para navegación + const scrollToSection = (sectionId) => { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + setActiveSection(sectionId); + } + }; + + const handleContactSubmit = (e) => { + e.preventDefault(); + alert('Gracias por tu mensaje. Nos pondremos en contacto contigo pronto.'); + setContactForm({ name: '', email: '', company: '', message: '' }); + }; + + // Estadísticas animadas + const stats = [ + { number: '500+', label: 'Agentes Aduanales', icon: '🏢' }, + { number: '15,000+', label: 'Pedimentos Procesados', icon: '📋' }, + { number: '99.9%', label: 'Uptime Garantizado', icon: '⚡' }, + { number: '24/7', label: 'Soporte Especializado', icon: '🛡️' } + ]; + + return ( +
+ {/* Navbar flotante con efectos */} +
+
+
+
+
+

+ + EFC + +

+
+ +
+
+ + Acceder + +
+
+
+
+ + {/* Hero Section con efectos */} +
+ {/* Efectos de fondo animados */} +
+
+
+
+
+ +
+
+

+ + EFC + +
+ + Para Agentes Aduanales e Importadores + +

+

+ La plataforma líder para agentes aduanales e importadores que buscan + digitalizar y revolucionar + {' '}sus procesos de comercio exterior. Gestiona pedimentos, documentación aduanal + y expedientes fiscales con total seguridad y eficiencia. +

+
+ +
+ + 🚀 + Acceder a la Plataforma + + + + + +
+ + {/* Estadísticas animadas */} +
+ {stats.map((stat, index) => ( +
+
{stat.icon}
+
{stat.number}
+
{stat.label}
+
+ ))} +
+
+ + {/* Scroll indicator */} +
+ +
+
+ + {/* Main content */} +
+
+ {/* Features Section */} +
+
+

+ Soluciones Especializadas para Comercio Exterior +

+

+ Herramientas diseñadas específicamente para las necesidades de agentes aduanales e importadores +

+
+ +
+ {/* Feature 1 */} +
+
+ + + +
+

+ Gestión de Pedimentos +

+

+ Administra pedimentos de importación y exportación, documentos aduanales, + clasificaciones arancelarias y toda la documentación requerida por el SAT. +

+
+ + {/* Feature 2 */} +
+
+ + + +
+

+ Control por Organización +

+

+ Gestiona múltiples clientes importadores con espacios de trabajo separados, + permisos granulares y control total sobre el acceso a la información. +

+
+ + {/* Feature 3 */} +
+
+ + + +
+

+ Reportes Aduanales +

+

+ Genera reportes especializados para auditorías, seguimiento de operaciones + aduanales, estadísticas de importación y cumplimiento normativo. +

+
+
+
+ + {/* Benefits Section */} +
+
+

+ ¿Por qué elegir EFC? +

+

+ Diseñado por expertos en comercio exterior para profesionales del sector +

+
+ +
+
+
+ + + +
+

Cumplimiento

+

Cumple con todas las regulaciones del SAT y normativas aduanales mexicanas

+
+ +
+
+ + + +
+

Eficiencia

+

Reduce hasta 70% el tiempo en gestión documental y procesos administrativos

+
+ +
+
+ + + +
+

Seguridad

+

Cifrado de extremo a extremo y controles de acceso empresariales

+
+ +
+
+ + + +
+

Soporte

+

Soporte especializado con conocimiento profundo en comercio exterior

+
+
+
+ + {/* Testimonios Section */} +
+
+

+ Lo que dicen nuestros clientes +

+

+ Agentes aduanales e importadores que ya transformaron su operación con EFC +

+
+ +
+
+
+
+ JM +
+
+

José María González

+

Agente Aduanal Patente 1234

+
+
+

+ "EFC ha revolucionado nuestra operación. La gestión de pedimentos es ahora 60% más rápida + y tenemos control total sobre todos nuestros clientes importadores." +

+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+
+ +
+
+
+ LR +
+
+

Laura Rodríguez

+

Directora de Comercio Exterior

+
+
+

+ "Como importador, necesitábamos una solución que nos diera visibilidad completa de nuestros procesos. + EFC nos permite colaborar eficientemente con nuestro agente aduanal." +

+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+
+ +
+
+
+ CT +
+
+

Carlos Torres

+

Agente Aduanal Patente 5678

+
+
+

+ "La seguridad y el cumplimiento normativo de EFC nos dan tranquilidad total. + Nuestros clientes valoran mucho la transparencia que ofrecemos ahora." +

+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+
+
+
+ + {/* Precios Section */} +
+
+

+ Planes diseñados para tu crecimiento +

+

+ Desde agencias pequeñas hasta grandes corporaciones +

+
+ +
+ {/* Plan Básico */} +
+
+

Básico

+

Para agencias pequeñas

+
+ $2,999 + /mes +
+
    +
  • + + + + Hasta 5 usuarios +
  • +
  • + + + + 50GB almacenamiento +
  • +
  • + + + + Gestión de pedimentos +
  • +
  • + + + + Soporte por email +
  • +
+ +
+
+ + {/* Plan Profesional */} +
+
+ + Más Popular + +
+
+

Profesional

+

Para agencias en crecimiento

+
+ $5,999 + /mes +
+
    +
  • + + + + Hasta 25 usuarios +
  • +
  • + + + + 200GB almacenamiento +
  • +
  • + + + + Todas las funciones básicas +
  • +
  • + + + + Reportes avanzados +
  • +
  • + + + + Soporte prioritario +
  • +
+ +
+
+ + {/* Plan Empresarial */} +
+
+

Empresarial

+

Para grandes corporaciones

+
+ $12,999 + /mes +
+
    +
  • + + + + Usuarios ilimitados +
  • +
  • + + + + 1TB almacenamiento +
  • +
  • + + + + Todas las funciones +
  • +
  • + + + + API personalizada +
  • +
  • + + + + Soporte 24/7 +
  • +
+ +
+
+
+
+ + {/* Contacto Section */} +
+
+
+

+ ¿Listo para transformar tu operación? +

+

+ Nuestro equipo de expertos en comercio exterior está aquí para ayudarte. + Contáctanos y descubre cómo EFC puede optimizar tus procesos aduanales. +

+ +
+
+
+ + + +
+
+

Teléfono

+

+52 (55) 1234-5678

+
+
+ +
+
+ + + +
+
+

Email

+

contacto@efc.com.mx

+
+
+ +
+
+ + + + +
+
+

Oficina

+

Ciudad de México, México

+
+
+
+
+ +
+
+
+ + setContactForm({...contactForm, name: e.target.value})} + className="w-full px-4 py-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="Tu nombre completo" + /> +
+ +
+ + setContactForm({...contactForm, email: e.target.value})} + className="w-full px-4 py-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="tu.email@empresa.com" + /> +
+ +
+ + setContactForm({...contactForm, company: e.target.value})} + className="w-full px-4 py-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + placeholder="Nombre de tu empresa" + /> +
+ +
+ + +
+ + +
+
+
+
+ + + {/* Footer */} +
+
+
+
+

+ + EFC + +

+

+ La plataforma líder para agentes aduanales e importadores, desarrollada por + @AduanaSoft con más de 10 años de experiencia. +

+
+ {['M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z', + 'M22.46 6c-.77.35-1.6.58-2.46.69.88-.53 1.56-1.37 1.88-2.38-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29 0 .34.04.67.11.98C8.28 9.09 5.11 7.38 3 4.79c-.37.63-.58 1.37-.58 2.15 0 1.49.75 2.81 1.91 3.56-.71 0-1.37-.2-1.95-.5v.03c0 2.08 1.48 3.82 3.44 4.21a4.22 4.22 0 0 1-1.93.07 4.28 4.28 0 0 0 4 2.98 8.521 8.521 0 0 1-5.33 1.84c-.34 0-.68-.02-1.02-.06C3.44 20.29 5.7 21 8.12 21 16 21 20.33 14.46 20.33 8.79c0-.19 0-.37-.01-.56.84-.6 1.56-1.36 2.14-2.23z', + 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'].map((path, idx) => ( + e.target.style.color = 'white'} + onMouseLeave={(e) => e.target.style.color = '#7A7A7A'}> + + + + + ))} +
+
+ +
+

Producto

+
    +
  • +
  • +
  • Integraciónes
  • +
  • API
  • +
+
+ +
+

Soporte

+ +
+
+ +
+

+ © 2025 EFC by @AduanaSoft. + Todos los derechos reservados. | Solución especializada para Agentes Aduanales e Importadores. +

+
+
+
+
+ ); +} diff --git a/src/pages/LandingNew.jsx b/src/pages/LandingNew.jsx new file mode 100644 index 0000000..b0109a6 --- /dev/null +++ b/src/pages/LandingNew.jsx @@ -0,0 +1,756 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; + +export default function Landing() { + const [isScrolled, setIsScrolled] = useState(false); + const [activeSection, setActiveSection] = useState('inicio'); + const [visibleElements, setVisibleElements] = useState(new Set()); + const [contactForm, setContactForm] = useState({ + name: '', + email: '', + company: '', + message: '' + }); + + // Efecto de scroll para navbar y detección de secciones activas + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 50); + + // Detectar sección activa + const sections = ['inicio', 'estadisticas', 'caracteristicas', 'testimonios', 'precios', 'contacto']; + const currentSection = sections.find(section => { + const element = document.getElementById(section); + if (element) { + const rect = element.getBoundingClientRect(); + return rect.top <= 100 && rect.bottom >= 100; + } + return false; + }); + + if (currentSection && currentSection !== activeSection) { + setActiveSection(currentSection); + } + + // Detectar elementos visibles para animaciones + const animatedElements = document.querySelectorAll('[data-animate]'); + const newVisibleElements = new Set(visibleElements); + + animatedElements.forEach((element) => { + const rect = element.getBoundingClientRect(); + const isVisible = rect.top < window.innerHeight && rect.bottom > 0; + + if (isVisible && !visibleElements.has(element.dataset.animate)) { + newVisibleElements.add(element.dataset.animate); + } + }); + + if (newVisibleElements.size !== visibleElements.size) { + setVisibleElements(newVisibleElements); + } + }; + + window.addEventListener('scroll', handleScroll); + // Trigger inicial + handleScroll(); + return () => window.removeEventListener('scroll', handleScroll); + }, [activeSection, visibleElements]); + + // Smooth scroll para navegación + const scrollToSection = (sectionId) => { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + setActiveSection(sectionId); + } + }; + + const handleContactSubmit = (e) => { + e.preventDefault(); + alert('Gracias por tu mensaje. Nos pondremos en contacto contigo pronto.'); + setContactForm({ name: '', email: '', company: '', message: '' }); + }; + + // Estadísticas animadas + const stats = [ + { number: '500+', label: 'Agentes Aduanales', icon: '🏢' }, + { number: '15,000+', label: 'Pedimentos Procesados', icon: '📋' }, + { number: '99.9%', label: 'Uptime Garantizado', icon: '⚡' }, + { number: '24/7', label: 'Soporte Especializado', icon: '🛡️' } + ]; + + return ( +
+ {/* Navbar flotante con efectos */} +
+
+
+
+
+

+ + EFC + +

+
+ +
+
+ + Acceder + +
+
+
+
+ + {/* Hero Section con efectos de gradiente animado */} +
+ {/* Background con gradientes animados */} +
+
+
+
+ +
+
+

+ + + EFC + + + + Para Agentes Aduanales + + + e Importadores + +

+ +

+ La plataforma líder desarrollada por + @AduanaSoft para + digitalizar y optimizar + {' '}todos tus procesos de comercio exterior con tecnología de vanguardia +

+ +
+ + Comenzar Ahora + + + + + +
+ + {/* Floating cards con efectos */} +
+ {[ + { icon: '🚀', title: 'Rápido', desc: 'Procesamiento instantáneo' }, + { icon: '🔒', title: 'Seguro', desc: 'Cifrado de nivel bancario' }, + { icon: '📊', title: 'Inteligente', desc: 'IA para optimización' } + ].map((feature, index) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.desc}

+
+ ))} +
+
+
+ + {/* Scroll indicator animado */} +
+ +
+
+ + {/* Sección de Estadísticas y Confianza */} +
+
+
+

+ Más de 500 empresas confían en nosotros +

+

+ Desarrollado por @AduanaSoft, + líderes en tecnología aduanal con más de 10 años de experiencia +

+
+ + {/* Stats con animaciones */} +
+ {stats.map((stat, index) => ( +
+
{stat.icon}
+
{stat.number}
+
{stat.label}
+
+ ))} +
+ + {/* AduanaSoft Info */} +
+
+
+

Acerca de AduanaSoft

+
+
+
+ + + +
+

10+ años especializados en software aduanal

+
+
+
+ + + +
+

Equipo experto en comercio exterior y tecnología

+
+
+
+ + + +
+

Certificación SAT y cumplimiento normativo total

+
+
+
+ + + +
+

Soporte 24/7 con especialistas aduanales

+
+
+
+
+
+
🏆
+

Líder del Mercado

+

+ Reconocidos como la mejor solución tecnológica para agentes aduanales en México +

+
+
+
+
+
+
+ + {/* Características con efectos interactivos */} +
+
+
+

+ Soluciones Especializadas para Comercio Exterior +

+

+ Herramientas diseñadas específicamente para las necesidades de agentes aduanales e importadores +

+
+ +
+ {[ + { + icon: '📋', + title: 'Gestión de Pedimentos', + description: 'Administra pedimentos de importación y exportación, documentos aduanales, clasificaciones arancelarias y toda la documentación requerida por el SAT.', + features: ['Validación automática SAT', 'Clasificación arancelaria', 'Cálculo de impuestos', 'Trazabilidad completa'] + }, + { + icon: '🏢', + title: 'Control por Organización', + description: 'Gestiona múltiples clientes importadores con espacios de trabajo separados, permisos granulares y control total sobre el acceso a la información.', + features: ['Multi-tenancy', 'Roles y permisos', 'Auditoría completa', 'Segregación de datos'] + }, + { + icon: '📊', + title: 'Reportes Aduanales', + description: 'Genera reportes especializados para auditorías, seguimiento de operaciones aduanales, estadísticas de importación y cumplimiento normativo.', + features: ['Dashboards en tiempo real', 'Exportación múltiple', 'KPIs personalizados', 'Alertas automáticas'] + } + ].map((feature, index) => ( +
+
+
+ {feature.icon} +
+

+ {feature.title} +

+

+ {feature.description} +

+
    + {feature.features.map((item, idx) => ( +
  • + + + + {item} +
  • + ))} +
+
+
+ +
+
+ ))} +
+
+
+ + {/* Testimonios */} +
+
+
+

+ Lo que dicen nuestros clientes +

+

+ Testimonios reales de agentes aduanales que han transformado su operación +

+
+ +
+ {[ + { + name: 'Carlos Mendoza', + company: 'Agente Aduanal 1234', + image: '👨‍💼', + testimonial: 'EFC revolucionó nuestra operación. Reducimos 70% el tiempo en procesar pedimentos y eliminamos errores manuales.', + rating: 5 + }, + { + name: 'María González', + company: 'Importadora Global SA', + image: '👩‍💼', + testimonial: 'La plataforma más completa del mercado. El soporte de AduanaSoft es excepcional, entienden perfectamente nuestras necesidades.', + rating: 5 + }, + { + name: 'Roberto Silva', + company: 'Comercio Exterior RSC', + image: '👨‍💻', + testimonial: 'Migramos de sistemas obsoletos a EFC y fue la mejor decisión. Ahora somos más eficientes y competitivos.', + rating: 5 + } + ].map((testimonial, index) => ( +
+
+
{testimonial.image}
+
+

{testimonial.name}

+

{testimonial.company}

+
+
+

"{testimonial.testimonial}"

+
+ {[...Array(testimonial.rating)].map((_, i) => ( + + + + ))} +
+
+ ))} +
+
+
+ + {/* Precios */} +
+
+
+

+ Planes diseñados para tu crecimiento +

+

+ Desde agentes independientes hasta grandes corporativos +

+
+ +
+ {[ + { + name: 'Starter', + price: '$2,999', + period: '/mes', + description: 'Perfecto para agentes aduanales independientes', + features: [ + 'Hasta 50 pedimentos/mes', + '5 GB almacenamiento', + 'Soporte por email', + 'Reportes básicos', + '2 usuarios' + ], + popular: false + }, + { + name: 'Professional', + price: '$5,999', + period: '/mes', + description: 'Ideal para agencias medianas', + features: [ + 'Hasta 200 pedimentos/mes', + '20 GB almacenamiento', + 'Soporte prioritario', + 'Reportes avanzados', + '10 usuarios', + 'API acceso', + 'Integraciones SAT' + ], + popular: true + }, + { + name: 'Enterprise', + price: 'Personalizado', + period: '', + description: 'Para grandes corporativos', + features: [ + 'Pedimentos ilimitados', + 'Almacenamiento ilimitado', + 'Soporte 24/7 dedicado', + 'Reportes personalizados', + 'Usuarios ilimitados', + 'API completa', + 'Implementación dedicada', + 'SLA garantizado' + ], + popular: false + } + ].map((plan, index) => ( +
+ {plan.popular && ( +
+ + Más Popular + +
+ )} + +
+

{plan.name}

+

{plan.description}

+
+ {plan.price} + {plan.period} +
+
+ +
    + {plan.features.map((feature, idx) => ( +
  • + + + + {feature} +
  • + ))} +
+ + +
+ ))} +
+
+
+ + {/* Contacto */} +
+
+
+
+

+ ¿Listo para transformar tu operación aduanal? +

+

+ Contáctanos y descubre cómo EFC puede optimizar tus procesos de comercio exterior +

+ +
+
+
+ + + +
+
+

Teléfono

+

+52 (55) 1234-5678

+
+
+ +
+
+ + + +
+
+

Email

+

contacto@aduanasoft.com

+
+
+ +
+
+ + + + +
+
+

Oficinas

+

Ciudad de México, México

+
+
+
+
+ +
+

Solicita una demostración

+
+
+ + setContactForm({...contactForm, name: e.target.value})} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all duration-200" + placeholder="Tu nombre" + /> +
+ +
+ + setContactForm({...contactForm, email: e.target.value})} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all duration-200" + placeholder="tu@empresa.com" + /> +
+ +
+ + setContactForm({...contactForm, company: e.target.value})} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all duration-200" + placeholder="Nombre de tu empresa" + /> +
+ +
+ + +
+ + +
+
+
+
+
+ + {/* Footer */} + + + {/* CSS personalizado para animaciones */} + +
+ ); +} diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx new file mode 100644 index 0000000..40ef1dc --- /dev/null +++ b/src/pages/Login.jsx @@ -0,0 +1,313 @@ +import React, { useState } from 'react'; +import { login } from '../api/auth'; +import { Link } from 'react-router-dom'; +import { colors } from '../theme'; + +export default function Login() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + setLoading(true); + try { + const data = await login(username, password); + localStorage.setItem('access', data.access); + localStorage.setItem('refresh', data.refresh); + + // Obtener y guardar la información del usuario autenticado + const apiUrl = import.meta.env.VITE_EFC_API_URL || ''; + const token = data.access; + try { + const res = await fetch(`${apiUrl}/user/users/me/`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + if (res.ok) { + const user = await res.json(); + if (user && user.username) { + localStorage.setItem('username', user.username); + if (user.email) localStorage.setItem('user_email', user.email); + if (user.id) localStorage.setItem('user_id', String(user.id)); + if (user.groups) localStorage.setItem('user_groups', JSON.stringify(user.groups)); + if (user.first_name) localStorage.setItem('user_first_name', user.first_name); + if (user.last_name) localStorage.setItem('user_last_name', user.last_name); + if (typeof user.is_importador !== 'undefined') localStorage.setItem('user_is_importador', String(user.is_importador)); + } + } + } catch (e) { + // Si falla, continuar igual + console.error('No se pudo guardar info de usuario en localStorage', e); + } + + // Disparar evento personalizado para que el navbar se actualice + window.dispatchEvent(new CustomEvent('authStateChanged')); + + // Redirigir al dashboard + window.location.href = '/admin'; + } catch (err) { + setError('Usuario o contraseña incorrectos'); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* Background pattern */} +
+
+
+ +
+ {/* Main Card */} +
+ {/* Header with navy background */} +
+
+ +

+ EFC +

+ +
+

+ Bienvenido de vuelta +

+

+ Inicia sesión para acceder a tu plataforma aduanal +

+
+ + {/* Form */} +
+
+ {/* Username Field */} +
+ +
+
+ + + +
+ setUsername(e.target.value)} + onFocus={(e) => { + e.target.style.borderColor = 'transparent'; + e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; + }} + onBlur={(e) => { + e.target.style.borderColor = '#d1d5db'; + e.target.style.boxShadow = 'none'; + }} + onMouseEnter={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#4DA6FF'; + } + }} + onMouseLeave={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#d1d5db'; + } + }} + /> +
+
+ + {/* Password Field */} +
+ +
+
+ + + +
+ setPassword(e.target.value)} + onFocus={(e) => { + e.target.style.borderColor = 'transparent'; + e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; + }} + onBlur={(e) => { + e.target.style.borderColor = '#d1d5db'; + e.target.style.boxShadow = 'none'; + }} + onMouseEnter={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#4DA6FF'; + } + }} + onMouseLeave={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#d1d5db'; + } + }} + /> + +
+
+ + {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

+ {error} +

+
+
+
+ )} + + {/* Login Button */} +
+ +
+ + {/* Additional Links */} +
+
+ e.target.style.color = '#1976D2'} + onMouseLeave={(e) => e.target.style.color = '#4DA6FF'} + > + ¿Olvidaste tu contraseña? + +
+
+ e.target.style.color = '#1976D2'} + onMouseLeave={(e) => e.target.style.color = '#4DA6FF'} + > + + + + Volver al inicio + +
+
+
+
+ + {/* Footer */} +
+
+

+ Desarrollado por @AduanaSoft +

+

+ Solución especializada para Agentes Aduanales +

+
+
+
+ + {/* Floating elements */} +
+
+
+
+ ); +} diff --git a/src/pages/LoginBroken.jsx b/src/pages/LoginBroken.jsx new file mode 100644 index 0000000..dfaacc3 --- /dev/null +++ b/src/pages/LoginBroken.jsx @@ -0,0 +1,391 @@ +import React, { useState } from 'react'; +import { login } from '../api/auth'; +import { Link } from 'react-router-dom'; +import { colors } from '../theme'; + +export default function Login() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + setLoading(true); + try { + const data = await login(username, password); + localStorage.setItem('access', data.access); + localStorage.setItem('refresh', data.refresh); + + // Disparar evento personalizado para que el navbar se actualice + window.dispatchEvent(new CustomEvent('authStateChanged')); + + // Redirigir al dashboard + window.location.href = '/admin'; + } catch (err) { + setError('Usuario o contraseña incorrectos'); + } finally { + setLoading(false); + } + }; + + return ( + <> +
+ {/* Background pattern */} +
+
+
+ +
+ {/* Main Card */} +
+ {/* Header with navy background */} +
+
+ +

+ EFC +

+ +
+

+ Bienvenido de vuelta +

+

+ Inicia sesión para acceder a tu plataforma aduanal +

+
+ + {/* Form */} +
+
+ {/* Username Field */} +
+ +
+
+ + + +
+ setUsername(e.target.value)} + /> +
+
+ + {/* Password Field */} +
+ +
+
+ + + +
+ setPassword(e.target.value)} + /> + +
+
+ + {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

+ {error} +

+
+
+
+ )} + + {/* Login Button */} +
+ +
+ + {/* Additional Links */} +
+ +
+ + + + + Volver al inicio + +
+
+
+
+ + {/* Footer */} +
+
+

+ Desarrollado por @AduanaSoft +

+

+ Solución especializada para Agentes Aduanales +

+
+
+
+ + {/* Floating elements with new colors */} +
+
+
+
+ + ); +} + +
+ {/* Main Card */} +
+ {/* Header with gradient */} +
+
+ +

+ EFC +

+ +
+

+ Bienvenido de vuelta +

+

+ Inicia sesión para acceder a tu plataforma aduanal +

+
+ + {/* Form */} +
+
+ {/* Username Field */} +
+ +
+
+ + + +
+ setUsername(e.target.value)} + /> +
+
+ + {/* Password Field */} +
+ +
+
+ + + +
+ setPassword(e.target.value)} + /> + +
+
+ + {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

+ {error} +

+
+
+
+ )} + + {/* Login Button */} +
+ +
+ + {/* Additional Links */} +
+ +
+ + + + + Volver al inicio + +
+
+
+
+ + {/* Footer */} +
+
+

+ Desarrollado por @AduanaSoft +

+

+ Solución especializada para Agentes Aduanales +

+
+
+ + {/* Floating elements */} +
+
+
+
+ + ); +} \ No newline at end of file diff --git a/src/pages/LoginFixed.jsx b/src/pages/LoginFixed.jsx new file mode 100644 index 0000000..59918f0 --- /dev/null +++ b/src/pages/LoginFixed.jsx @@ -0,0 +1,286 @@ +import React, { useState } from 'react'; +import { login } from '../api/auth'; +import { Link } from 'react-router-dom'; +import { colors } from '../theme'; + +export default function Login() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + setLoading(true); + try { + const data = await login(username, password); + localStorage.setItem('access', data.access); + localStorage.setItem('refresh', data.refresh); + + // Disparar evento personalizado para que el navbar se actualice + window.dispatchEvent(new CustomEvent('authStateChanged')); + + // Redirigir al dashboard + window.location.href = '/admin'; + } catch (err) { + setError('Usuario o contraseña incorrectos'); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* Background pattern */} +
+
+
+ +
+ {/* Main Card */} +
+ {/* Header with navy background */} +
+
+ +

+ EFC +

+ +
+

+ Bienvenido de vuelta +

+

+ Inicia sesión para acceder a tu plataforma aduanal +

+
+ + {/* Form */} +
+
+ {/* Username Field */} +
+ +
+
+ + + +
+ setUsername(e.target.value)} + onFocus={(e) => { + e.target.style.borderColor = 'transparent'; + e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; + }} + onBlur={(e) => { + e.target.style.borderColor = '#d1d5db'; + e.target.style.boxShadow = 'none'; + }} + onMouseEnter={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#4DA6FF'; + } + }} + onMouseLeave={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#d1d5db'; + } + }} + /> +
+
+ + {/* Password Field */} +
+ +
+
+ + + +
+ setPassword(e.target.value)} + onFocus={(e) => { + e.target.style.borderColor = 'transparent'; + e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; + }} + onBlur={(e) => { + e.target.style.borderColor = '#d1d5db'; + e.target.style.boxShadow = 'none'; + }} + onMouseEnter={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#4DA6FF'; + } + }} + onMouseLeave={(e) => { + if (document.activeElement !== e.target) { + e.target.style.borderColor = '#d1d5db'; + } + }} + /> + +
+
+ + {/* Error Message */} + {error && ( +
+
+
+ + + +
+
+

+ {error} +

+
+
+
+ )} + + {/* Login Button */} +
+ +
+ + {/* Additional Links */} +
+ +
+ e.target.style.color = '#1976D2'} + onMouseLeave={(e) => e.target.style.color = '#4DA6FF'} + > + + + + Volver al inicio + +
+
+
+
+ + {/* Footer */} +
+
+

+ Desarrollado por @AduanaSoft +

+

+ Solución especializada para Agentes Aduanales +

+
+
+
+ + {/* Floating elements */} +
+
+
+
+ ); +} diff --git a/src/pages/Notificaciones.jsx b/src/pages/Notificaciones.jsx new file mode 100644 index 0000000..c245b96 --- /dev/null +++ b/src/pages/Notificaciones.jsx @@ -0,0 +1,158 @@ +import React, { useEffect, useState } from 'react'; +import { fetchNotificaciones, fetchAllNotifications, marcarNotificacionComoVista } from '../api/notificaciones'; + +export default function Notificaciones() { + const [notificaciones, setNotificaciones] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [page, setPage] = useState(1); + const pageSize = 15; + const [count, setCount] = useState(0); + const [filtroVisto, setFiltroVisto] = useState('todos'); + + const fetchData = async () => { + setLoading(true); + setError(null); + try { + let data; + if (filtroVisto === 'todos') { + data = await fetchAllNotifications({ page, pageSize }); + } else { + const params = { page, pageSize }; + if (filtroVisto === 'visto') params.visto = true; + else if (filtroVisto === 'novisto') params.visto = false; + data = await fetchNotificaciones(params); + } + setNotificaciones(data.results); + setCount(data.count); + } catch (e) { + setError('Error al cargar notificaciones'); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + // eslint-disable-next-line + }, [page, filtroVisto]); + + const handleActualizarTodas = async () => { + setLoading(true); + try { + await Promise.all( + notificaciones.filter(n => !n.visto).map(n => marcarNotificacionComoVista(n.id)) + ); + fetchData(); + } catch (e) { + setError('Error al actualizar notificaciones'); + } finally { + setLoading(false); + } + }; + + const handleFiltroChange = (e) => { + setFiltroVisto(e.target.value); + setPage(1); + }; + + return ( +
+
+

Notificaciones

+
+ + +
+
+ {loading ? ( +
Cargando...
+ ) : error ? ( +
{error}
+ ) : ( +
+ + + + + + + + + + + + {Array.from({ length: pageSize }).map((_, idx) => { + const n = notificaciones[idx]; + if (n) { + return ( + + + + + + + + ); + } else { + return ( + + + + + + + + ); + } + })} + +
IDTipoMensajeFechaVisto
{n.id}{n.tipo?.descripcion || n.tipo?.tipo}{n.mensaje}{new Date(n.fecha_envio || n.created_at).toLocaleString()} + {n.visto ? ( + + ) : ( + No + )} +
-----
+
+ )} + {/* Paginación */} +
+
+ Página {page} de {Math.ceil(count / pageSize) || 1} +
+
+ + + 15 por página +
+
+
+ ); +} diff --git a/src/pages/Organization.jsx b/src/pages/Organization.jsx new file mode 100644 index 0000000..b75992e --- /dev/null +++ b/src/pages/Organization.jsx @@ -0,0 +1,300 @@ +import React, { useState, useEffect } from 'react'; +import { fetchOrganizationUsage } from '../api/organization'; +import { useNotification } from '../context/NotificationContext'; +import '../assets/organization-animations.css'; + +export default function Organization() { + const [info, setInfo] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const { showMessage } = useNotification(); + // Estado para animar el progress bar + const [animatedPercent, setAnimatedPercent] = useState(0); + + useEffect(() => { + const token = localStorage.getItem('access'); + if (!token) { + setError('No se encontró el token de acceso.'); + setLoading(false); + return; + } + fetchOrganizationUsage(token) + .then(data => { + setInfo(data); + setLoading(false); + }) + .catch(err => { + if (err.message === 'SESSION_EXPIRED') { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + } else { + setError(err.message); + } + setLoading(false); + }); + }, [showMessage]); + + // Animación del progress bar + useEffect(() => { + if (!info) return; + const used = info.espacio_utilizado_gb || 0; + const limit = info.limite_almacenamiento_gb || 1; + const percent = Math.min(100, (100 * used / limit)); + let start = 0; + // Si ya está en el valor correcto, no animar + if (animatedPercent === percent) return; + // Animar de 0 a percent + const step = () => { + setAnimatedPercent(prev => { + if (prev < percent) { + const next = Math.min(prev + 2, percent); // velocidad de animación + if (next < percent) { + setTimeout(step, 10); + } + return next; + } else { + return percent; + } + }); + }; + setAnimatedPercent(0); + setTimeout(step, 200); // pequeño delay para que se note la animación + // eslint-disable-next-line + }, [info]); + + if (loading) return ( +
+
+ + + + +

Cargando información de la organización...

+
+
+ ); + + if (error) return ( +
+
+
+ + + +

{error}

+
+
+
+ ); + + return ( +
+
+ {/* Header mejorado y decorativo */} +
+
+ + + +
+
+

+ Mi Organización + {info && ( + + {info.total_usuarios} usuarios + + )} +

+

Información y métricas de uso de tu organización

+
+ {/* Efecto decorativo de fondo */} +
+ + + + + + + + + +
+ {/* Animación personalizada para el icono y contador */} + +
+ + {/* Barra de almacenamiento con color y progress bar */} +
+

+ + + + Uso de Almacenamiento +

+
+ {/* Progress bar de color dinámico según porcentaje */} + {(() => { + const used = info?.espacio_utilizado_gb || 0; + const limit = info?.limite_almacenamiento_gb || 1; + const percent = Math.min(100, (100 * used / limit)); + let barColor = 'linear-gradient(90deg, #22c55e 0%, #16a34a 100%)'; // verde + if (animatedPercent >= 80) { + barColor = 'linear-gradient(90deg, #ef4444 0%, #b91c1c 100%)'; // rojo + } else if (animatedPercent >= 50) { + barColor = 'linear-gradient(90deg, #f59e42 0%, #d97706 100%)'; // naranja + } + return ( +
+ ); + })()} + {/* Etiquetas sobre la barra */} +
+ + + {info?.espacio_utilizado_gb?.toFixed(2)} GB usados + + {info?.espacio_disponible_bytes ? (info.espacio_disponible_bytes / (1024 * 1024 * 1024)).toFixed(2) : 0} GB libres + {info?.limite_almacenamiento_gb} GB límite +
+
+
+ +
+ {/* Tarjeta Organización */} +
+
+ + + +
+ Organización + {info?.organizacion} +
+ {/* Tarjeta Usuarios */} +
+
+ + + +
+ Usuarios + {info?.total_usuarios} +
+ {/* Tarjeta Pedimentos */} +
+
+ + + +
+ Pedimentos + {info?.total_pedimentos} +
+ {/* Tarjeta Documentos */} +
+
+ + + +
+ Documentos + {info?.total_documentos} +
+ {/* Tarjeta Límite de Almacenamiento */} +
+
+ + + +
+ Límite de Almacenamiento + {info?.limite_almacenamiento_gb} GB +
+ {/* Tarjeta Espacio Utilizado */} +
+
+ + + +
+ Espacio Utilizado + {info?.espacio_utilizado_gb?.toFixed(2)} GB +
+ {/* Tarjeta Espacio Disponible */} +
+
+ + + +
+ Espacio Disponible + {info?.espacio_disponible_bytes ? (info.espacio_disponible_bytes / (1024 * 1024 * 1024)).toFixed(2) : 0} GB +
+ {/* Tarjeta Porcentaje Utilizado */} +
+
+ + + + +
+ Porcentaje Utilizado + {info?.porcentaje_utilizado}% +
+
+ + + {/* Acciones */} +
+
+
+ + + +
+

Acciones

+
+ +
+ + + +
+
+
+
+ ); +} diff --git a/src/pages/PasswordResetConfirm.jsx b/src/pages/PasswordResetConfirm.jsx new file mode 100644 index 0000000..f742837 --- /dev/null +++ b/src/pages/PasswordResetConfirm.jsx @@ -0,0 +1,212 @@ +import React, { useState } from 'react'; +import logo from '../assets/react.svg'; +import { useParams, useNavigate } from 'react-router-dom'; + +export default function PasswordResetConfirm() { + const { uid, token } = useParams(); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(''); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + if (newPassword !== confirmPassword) { + setError('Las contraseñas no coinciden.'); + return; + } + setLoading(true); + try { + // POST a la ruta real del backend (URL absoluta) + const apiUrl = `http://192.168.1.195:8000/api/v1/user/password-reset-confirm/${uid}/${token}/`; + const res = await fetch(apiUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ password: newPassword }), + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + setError(data.detail || 'No se pudo cambiar la contraseña.'); + } else { + setSuccess(true); + setTimeout(() => navigate('/login'), 2500); + } + } catch (err) { + setError('Error de red. Intenta de nuevo.'); + } + setLoading(false); + }; + + return ( +
+ {/* Background pattern */} +
+
+
+
+
+ {/* Header navy */} +
+ {/* Logo eliminado para diseño limpio */} +

Nueva contraseña

+

+ Ingresa tu nueva contraseña y confírmala +

+
+
+ {success ? ( +
+ Contraseña cambiada correctamente. Redirigiendo al login... +
+ ) : ( +
+ {/* Nueva contraseña */} +
+ +
+
+ + + +
+ setNewPassword(e.target.value)} + onFocus={e => { e.target.style.borderColor = 'transparent'; e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; }} + onBlur={e => { e.target.style.borderColor = '#d1d5db'; e.target.style.boxShadow = 'none'; }} + onMouseEnter={e => { if (document.activeElement !== e.target) e.target.style.borderColor = '#4DA6FF'; }} + onMouseLeave={e => { if (document.activeElement !== e.target) e.target.style.borderColor = '#d1d5db'; }} + /> +
+
+ {/* Confirmar contraseña */} +
+ +
+
+ + + +
+ setConfirmPassword(e.target.value)} + onFocus={e => { e.target.style.borderColor = 'transparent'; e.target.style.boxShadow = '0 0 0 2px #4DA6FF'; }} + onBlur={e => { e.target.style.borderColor = '#d1d5db'; e.target.style.boxShadow = 'none'; }} + onMouseEnter={e => { if (document.activeElement !== e.target) e.target.style.borderColor = '#4DA6FF'; }} + onMouseLeave={e => { if (document.activeElement !== e.target) e.target.style.borderColor = '#d1d5db'; }} + /> +
+
+ {/* Error */} + {error && ( +
+
+
+ + + +
+
+

{error}

+
+
+
+ )} + {/* Botón */} +
+ +
+ {/* Link volver al login */} + +
+ )} +
+ {/* Footer */} +
+
+

+ Desarrollado por @AduanaSoft +

+

+ Solución especializada para Agentes Aduanales +

+
+
+
+ {/* Floating elements */} +
+
+
+
+ ); +} diff --git a/src/pages/PedimentoDetail.jsx b/src/pages/PedimentoDetail.jsx new file mode 100644 index 0000000..4cf7877 --- /dev/null +++ b/src/pages/PedimentoDetail.jsx @@ -0,0 +1,930 @@ +import React, { useEffect, useState } from 'react'; +// Animación fade-in/slide-up para bloques +const fadeInSlideUp = `@keyframes fadein-slideup { + 0% { opacity: 0; transform: translateY(40px); } + 100% { opacity: 1; transform: translateY(0); } +}`; +if (typeof document !== 'undefined' && !document.getElementById('fadein-slideup-pedimento')) { + const style = document.createElement('style'); + style.id = 'fadein-slideup-pedimento'; + style.innerHTML = fadeInSlideUp; + document.head.appendChild(style); +} +import hljs from 'highlight.js/lib/core'; +import xml from 'highlight.js/lib/languages/xml'; +import 'highlight.js/styles/github.css'; +hljs.registerLanguage('xml', xml); +// import type removed for JSX compatibility +import { fetchPedimentoDocuments } from '../api/pedimentoDocuments'; +import { useParams, Link } from 'react-router-dom'; +import { useNotification } from '../context/NotificationContext'; + +const API_URL = import.meta.env.VITE_EFC_API_URL; + +const downloadFile = async (id, filename = 'archivo', showMessage) => { + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}/record/documents/descargar/${id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (res.status === 401) { + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + return; + } + if (!res.ok) { + alert('No autorizado o error en la descarga'); + return; + } + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); +}; + +const downloadBulkZip = async (ids, showMessage, pedimentoNombre) => { + if (!ids.length) { + showMessage('Selecciona al menos un documento.', 'error'); + return; + } + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}/record/documents/bulk-download/`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ document_ids: ids, pedimento_nombre: pedimentoNombre }), + }); + if (res.status === 401) { + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + return; + } + if (!res.ok) { + showMessage('No autorizado o error en la descarga masiva', 'error'); + return; + } + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${pedimentoNombre || 'documentos'}.zip`; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); +}; + +import { useRef, useLayoutEffect } from 'react'; +export default function PedimentoDetail() { + // Función para formatear XML (pretty print) + function formatXml(xml) { + const PADDING = ' '; + const reg = /(>)(<)(\/*)/g; + let formatted = ''; + let pad = 0; + xml = xml.replace(reg, '$1\r\n$2$3'); + xml.split(/\r?\n/).forEach((node) => { + let indent = 0; + if (node.match(/.+<\/\w[^>]*>$/)) { + indent = 0; + } else if (node.match(/^<\/\w/)) { + if (pad !== 0) pad -= 1; + } else if (node.match(/^<\w[^>]*[^\/]>/)) { + indent = 1; + } + formatted += PADDING.repeat(pad) + node + '\r\n'; + pad += indent; + }); + return formatted.trim(); + } + // Helper para obtener el nombre legible del tipo de documento + const getDocumentTypeName = (type) => { + const found = documentTypeOptions.find(opt => String(opt.value) === String(type)); + return found ? found.label : 'Documento'; + }; + // Estado para modal de preview + const [previewOpen, setPreviewOpen] = useState(false); + const [previewUrl, setPreviewUrl] = useState(''); + const [previewType, setPreviewType] = useState(''); + const [previewLoading, setPreviewLoading] = useState(false); + const [previewError, setPreviewError] = useState(''); + const [previewXml, setPreviewXml] = useState(''); + const [previewXmlHtml, setPreviewXmlHtml] = useState(''); + // Filtros y ordenamiento + const [fileNameFilter, setFileNameFilter] = useState(''); + const [extensionFilter, setExtensionFilter] = useState(''); + const [dateFilter, setDateFilter] = useState(''); + const [orderBy, setOrderBy] = useState(''); + const [orderDir, setOrderDir] = useState('asc'); + const { id } = useParams(); + const [pedimento, setPedimento] = useState(null); + const [docsLoading, setDocsLoading] = useState(true); + const [docsError, setDocsError] = useState(''); +const [documents, setDocuments] = useState([]); + const [docsCount, setDocsCount] = useState(0); +const [docsNext, setDocsNext] = useState(null); +const [docsPrev, setDocsPrev] = useState(null); + // Refuerza la paginación SPA: nunca recarga la página, solo cambia el estado local + const [page, setPage] = useState(1); + // Ref para foco oculto (accesibilidad, opcional) + const focusKeeperRef = useRef(null); + + // Handler SPA para paginación + const handlePageChange = (newPage, e) => { + if (e && typeof e.preventDefault === 'function') e.preventDefault(); + if (e && typeof e.stopPropagation === 'function') e.stopPropagation(); + if (newPage < 1 || newPage > Math.max(1, Math.ceil(docsCount / pageSize)) || newPage === page) return; + setPage(newPage); + // Quitar el foco del botón activo para evitar salto de scroll + if (typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }; + + // Eliminado manejo manual de scroll para evitar saltos + const [pageSize, setPageSize] = useState(10); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [selected, setSelected] = useState([]); + const [downloading, setDownloading] = useState(false); + const [documentTypeFilter, setDocumentTypeFilter] = useState(''); + const documentTypeOptions = [ + { value: '', label: 'Todos' }, + { value: 1, label: 'Pedimento Partida' }, + { value: 2, label: 'Pedimento Completo' }, + { value: 3, label: 'Pedimento Remesas' }, + { value: 4, label: 'Pedimento Acuse' }, + { value: 5, label: 'Pedimento EDocument' }, + { value: 6, label: 'Estado Pedimento' }, + ]; + const { showMessage } = useNotification(); + + useEffect(() => { + const token = localStorage.getItem('access'); + fetch(`${API_URL}/customs/pedimentos/${id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + }) + .then(res => { + if (res.status === 401) { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + return null; + } + if (!res.ok) throw new Error('No autorizado o error en la petición'); + return res.json(); + }) + .then(data => { + setPedimento(data); + setLoading(false); + }) + .catch(err => { + setError(err.message); + setLoading(false); + }); + }, [id, showMessage]); + + // Fetch paginated documents + useEffect(() => { + if (!id) return; + const token = localStorage.getItem('access'); + setDocsLoading(true); + setDocsError(''); + fetchPedimentoDocuments(token, id, page, pageSize) + .then((data) => { + setDocuments(data.results); + setDocsCount(data.count); + setDocsNext(data.next); + setDocsPrev(data.previous); + setDocsLoading(false); + }) + .catch(err => { + if (err.message === 'SESSION_EXPIRED') { + localStorage.removeItem('access'); + localStorage.removeItem('refresh'); + showMessage('Tu sesión ha expirado, por favor inicia sesión de nuevo.', 'error'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + } else { + setDocsError(err.message); + } + setDocsLoading(false); + }); + }, [id, page, pageSize, showMessage]); + + if (loading) return ( +
+
+ + + + +

Cargando detalle de pedimento...

+
+
+ ); + + if (error) return ( +
+
+
+ + + +

{error}

+
+
+
+ ); + if (!pedimento) return null; + + const allDocIds = documents.map(doc => doc.id); + const allSelected = selected.length === allDocIds.length && allDocIds.length > 0; + + const handleSelect = (id) => { + setSelected(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); + }; + + const handleSelectAll = () => { + if (allSelected) setSelected([]); + else setSelected(allDocIds); + }; + + const handleBulkDownload = async (ids) => { + setDownloading(true); + await downloadBulkZip(ids, showMessage, pedimento?.pedimento); + setDownloading(false); + }; + + // Vista previa de documento + const handlePreview = async (doc) => { + setPreviewLoading(true); + setPreviewError(''); + setPreviewUrl(''); + setPreviewType(''); + setPreviewXml(''); + setPreviewOpen(true); + try { + const token = localStorage.getItem('access'); + const res = await fetch(`${API_URL}/record/documents/descargar/${doc.id}/`, { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + if (res.status === 401) { + setPreviewError('Tu sesión ha expirado, por favor inicia sesión de nuevo.'); + setPreviewLoading(false); + return; + } + if (!res.ok) { + setPreviewError('No autorizado o error en la descarga'); + setPreviewLoading(false); + return; + } + // Detectar tipo de archivo + let type = ''; + if (doc.extension) { + if (doc.extension.toLowerCase() === 'pdf') type = 'pdf'; + else if (["jpg","jpeg","png","gif","bmp","webp"].includes(doc.extension.toLowerCase())) type = 'img'; + else if (doc.extension.toLowerCase() === 'xml') type = 'xml'; + else type = 'other'; + } + setPreviewType(type); + if (type === 'xml') { + const text = await res.text(); + const prettyText = formatXml(text); + setPreviewXml(prettyText); + // Formatear y resaltar XML + try { + const highlighted = hljs.highlight(prettyText, { language: 'xml' }).value; + setPreviewXmlHtml(highlighted); + } catch (e) { + setPreviewXmlHtml(prettyText); + } + setPreviewLoading(false); + } else { + const blob = await res.blob(); + const url = window.URL.createObjectURL(blob); + setPreviewUrl(url); + setPreviewLoading(false); + } + } catch (err) { + setPreviewError('Error al obtener el archivo'); + setPreviewLoading(false); + } + }; + + // Cerrar modal y limpiar blob + const handleClosePreview = () => { + setPreviewOpen(false); + if (previewUrl) window.URL.revokeObjectURL(previewUrl); + setPreviewUrl(''); + setPreviewType(''); + setPreviewError(''); + setPreviewXml(''); + setPreviewXmlHtml(''); + }; + + return ( +
+ {/* Modal de vista previa resizable */} + {previewOpen && ( +
+
+ {/* Header mejorado del modal */} +
+
+
+ + + + +
+

Vista previa de documento

+
+ +
+ {/* Contenido del modal */} +
+ {previewLoading ? ( +
Cargando documento...
+ ) : previewError ? ( +
{previewError}
+ ) : previewType === 'pdf' ? ( +