diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a1fc2db --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +*.tsbuildinfo +*.local +.DS_Store diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..6fede69 --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1,4 @@ +# pnpm 10 no construye dependencias con scripts de instalación por defecto. +# esbuild (dep transitiva de vite) necesita su postinstall para descargar su +# binario; sin esto, `vite build` falla. Allowlist explícita y mínima. +enable-pre-post-scripts=true diff --git a/frontend/README.md b/frontend/README.md index bad36d3..812a5af 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,52 +1,74 @@ -# Frontend de osint_web (pendiente) +# Frontend de osint_web -Este directorio es un **placeholder**. El frontend lo montará un agente -posterior. El backend (`../server/main.py`) ya está completo y sirve todos los -endpoints que el frontend consumirá. +Frontend web local del explorador OSINT. Lee el backend FastAPI (`../server/main.py`, +escucha solo en `127.0.0.1:8470`) y ofrece cinco vistas de lectura sobre el vault +de Obsidian `osint` + la agenda/calendario del servidor Xandikos. -## Stack previsto +## Stack -- **React + Vite + Mantine v9 + `@fn_library`** (el sistema de UI del registry, - alias a `frontend/functions/ui/`). Componentes propios de `@fn_library` antes - que HTML nativo (regla `frontend_theming.md`): `Group`, `Stack`, `Table`, - `Paper`, `AppShell`, etc., e iconos de `@tabler/icons-react`. Theming con - `createTheme()` de `@mantine/core` (sin Tailwind, sin CSS variables custom). -- **Grafo**: `sigma.js` + `graphology` + `graphology-layout-forceatlas2` (las - dos únicas dependencias nuevas fuera de `@fn_library`; KISS). Color por - `tipo`, tamaño por grado, layout force-directed. Click en nodo → `NodeCard`. - Panel lateral con toggles de tipos visibles y caja de búsqueda. -- **Fechas** en formato europeo `DD/MM/AAAA` (memoria `formato-fecha-europeo`), - incluido el parseo de las fechas iCal (`YYYYMMDD[THHMMSS[Z]]`) que devuelve - `/api/calendar`. +- **React 19 + Vite 6 + TypeScript + Mantine v9**. Mantine v9 exige React 19 (con + React 18 compila pero no monta — error "s is not a function"); por eso el + `package.json` pina React 19. Iconos de `@tabler/icons-react`. Theming con + `createTheme()` (`src/theme.ts`), sin Tailwind ni CSS variables custom (regla + `frontend_theming.md`). +- **Grafo**: `sigma` (v3) + `graphology` + `graphology-layout-forceatlas2` (las + únicas deps fuera de Mantine; KISS). Layout force-directed en un **web worker** + (no bloquea la UI con 1199 nodos), pausable. +- **Markdown** de las fichas con `react-markdown`. **Calendario** con + `@mantine/dates` (que usa `dayjs`). +- Vite proxya `/api` → `http://127.0.0.1:8470` en dev (sobrescribible con + `VITE_API_BASE`). -## Vistas a construir +## Vistas -| Vista | Fuente (endpoint) | Qué muestra | -|---|---|---| -| `views/GraphView.tsx` | `GET /api/graph` | grafo sigma.js; nodos coloreados por `tipo` usando `counts` para la leyenda; nodos `dangling` atenuados con toggle; búsqueda con `GET /api/search?q=`. | -| `views/TablesView.tsx` | `GET /api/nodes?tipo=` | una pestaña/tabla Mantine por tipo (persona, organizacion, lugar, dominio, caso) con columnas del `frontmatter`, ordenable y filtrable. | -| `views/NodeCard.tsx` | `GET /api/node/` | ficha: `frontmatter` clave-valor + `body` Markdown + galería de `attachments` (imágenes con lightbox vía `GET /api/attachment?path=`, PDFs como enlace). | -| `views/ContactsView.tsx` | `GET /api/contacts`, `GET /api/contact/` | lista/tabla de contactos del addressbook Xandikos (nombre, teléfonos, emails, org, nota). | -| `views/CalendarView.tsx` | `GET /api/calendar?from=&to=` | eventos del calendario Xandikos en un rango (summary, fechas en formato europeo, lugar, descripción). | +| Vista | Archivo | Endpoint(s) | Qué muestra | +|---|---|---|---| +| **Grafo** | `src/views/GraphView.tsx` | `GET /api/graph`, `GET /api/search` | sigma.js force-directed; color por `tipo`, tamaño por grado; panel lateral con toggles de tipos + dangling + buscador (centra el nodo). Click en nodo → ficha. Layout pausable + reset de cámara. | +| **Tablas** | `src/views/TablesView.tsx` | `GET /api/graph`, `GET /api/nodes?tipo=` | una pestaña por tipo real; `Table` Mantine con columnas deducidas del frontmatter, ordenable y filtrable. Click en fila → ficha. | +| **Ficha** | `src/views/NodeCard.tsx` | `GET /api/node/`, `GET /api/attachment` | modal: frontmatter clave-valor (fechas europeas DD/MM/AAAA), cuerpo Markdown, galería de imágenes con lightbox, documentos/PDFs como enlace, wikilinks navegables. | +| **Contactos** | `src/views/ContactsView.tsx` | `GET /api/contacts` | agenda: lista + buscador (nombre/alias/tel/email); detalle con teléfonos, correos, bloque `osint` (dni/país/sexo…) y nota. | +| **Calendario** | `src/views/CalendarView.tsx` | `GET /api/calendar` | mini-calendario `@mantine/dates` con punto en días con eventos + lista de eventos del mes/día agrupados por fecha (hora local, lugar, descripción). | -## Cómo se montará (cuando se haga) +Botón global **Refrescar** (header) → `POST /api/refresh` + recarga de la vista activa. + +## Arrancar (dev) + +Necesitas backend + frontend a la vez: + +```bash +# Terminal 1 — backend (escucha solo en 127.0.0.1) +cd projects/osint/apps/osint_web +.venv/bin/python server/main.py --vault /home/enmanuel/Obsidian/osint --port 8470 + +# Terminal 2 — frontend +cd projects/osint/apps/osint_web/frontend +pnpm install # primera vez +pnpm dev # http://127.0.0.1:5173 +``` + +Abrir **http://127.0.0.1:5173**. El proxy de Vite reenvía `/api` al backend, así +que no hay que tocar CORS. Solo localhost (datos sensibles del vault: DNIs, fotos). + +## Build ```bash cd projects/osint/apps/osint_web/frontend -pnpm create vite . --template react-ts # o el scaffolder del registry -pnpm add @mantine/core @mantine/hooks @tabler/icons-react sigma graphology graphology-layout-forceatlas2 -# alias @fn_library -> ../../../../../frontend/functions/ui en vite.config.ts -pnpm dev # http://127.0.0.1:5173 (proxy /api -> http://127.0.0.1:8470) +pnpm install +pnpm build # tsc -b && vite build → dist/ ``` -Configurar el proxy de Vite (`server.proxy`) para reenviar `/api` al backend en -`http://127.0.0.1:8470`, o leer la URL del backend de una env var -(`VITE_API_BASE`). El backend ya emite CORS abierto solo en localhost, así que -ambos enfoques funcionan. +### Gotcha pnpm 10/11 (esbuild) -## Arrancar el backend (necesario para desarrollar el frontend) +pnpm bloquea por seguridad los scripts de build de dependencias. `esbuild` (el +bundler nativo de Vite) necesita su `postinstall`. El `pnpm-workspace.yaml` lo +permite con `allowBuilds: { esbuild: true }`. Si `pnpm build` falla con +"esbuild ... was not found", ejecuta `pnpm rebuild esbuild`. -```bash -cd projects/osint/apps/osint_web -.venv/bin/python server/main.py --vault ~/Obsidian/osint --port 8470 -``` +## Notas + +- **Grafo sin WebGL**: si el navegador no expone WebGL (headless sin GPU), la vista + Grafo muestra un aviso en vez de crashear; el resto de la app sigue funcionando. +- **Contactos/Calendario** dependen del servidor Xandikos: si no responde, esas dos + vistas muestran un aviso naranja y el grafo/tablas siguen operativos (offline). +- Las fechas se presentan en **europeo** (`src/format.ts`): ISO de Obsidian + `2026-06-07` → `07/06/2026`; iCal `20220829T133000Z` → hora local `15:30`. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..fd1924e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + osint · explorador + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..3dd2700 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,36 @@ +{ + "name": "osint-web-frontend", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@mantine/core": "^9.3.0", + "@mantine/dates": "^9.3.0", + "@mantine/hooks": "^9.3.0", + "@mantine/notifications": "^9.3.0", + "@tabler/icons-react": "^3.36.0", + "dayjs": "^1.11.13", + "graphology": "^0.26.0", + "graphology-layout-forceatlas2": "^0.10.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-markdown": "^10.1.0", + "sigma": "^3.0.2" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^4.3.4", + "postcss": "^8.4.49", + "postcss-preset-mantine": "^1.17.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "~5.6.3", + "vite": "^6.0.3" + } +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml new file mode 100644 index 0000000..e9bd557 --- /dev/null +++ b/frontend/pnpm-lock.yaml @@ -0,0 +1,2344 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@mantine/core': + specifier: ^9.3.0 + version: 9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/dates': + specifier: ^9.3.0 + version: 9.3.1(@mantine/core@9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': + specifier: ^9.3.0 + version: 9.3.1(react@19.2.7) + '@mantine/notifications': + specifier: ^9.3.0 + version: 9.3.1(@mantine/core@9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tabler/icons-react': + specifier: ^3.36.0 + version: 3.44.0(react@19.2.7) + dayjs: + specifier: ^1.11.13 + version: 1.11.21 + graphology: + specifier: ^0.26.0 + version: 0.26.0(graphology-types@0.24.8) + graphology-layout-forceatlas2: + specifier: ^0.10.1 + version: 0.10.1(graphology-types@0.24.8) + react: + specifier: ^19.2.0 + version: 19.2.7 + react-dom: + specifier: ^19.2.0 + version: 19.2.7(react@19.2.7) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.17)(react@19.2.7) + sigma: + specifier: ^3.0.2 + version: 3.0.3(graphology-types@0.24.8) + devDependencies: + '@types/node': + specifier: ^22.10.0 + version: 22.19.20 + '@types/react': + specifier: ^19.2.0 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.0 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.3(@types/node@22.19.20)(sugarss@5.0.1(postcss@8.5.15))) + postcss: + specifier: ^8.4.49 + version: 8.5.15 + postcss-preset-mantine: + specifier: ^1.17.0 + version: 1.18.0(postcss@8.5.15) + postcss-simple-vars: + specifier: ^7.0.1 + version: 7.0.1(postcss@8.5.15) + typescript: + specifier: ~5.6.3 + version: 5.6.3 + vite: + specifier: ^6.0.3 + version: 6.4.3(@types/node@22.19.20)(sugarss@5.0.1(postcss@8.5.15)) + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.27.19': + resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==} + peerDependencies: + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mantine/core@9.3.1': + resolution: {integrity: sha512-4rBoHpggSohayE+Os7lKdbsHTw7m/uZRKYjk9DN6cKBhQIjdiULdcG+b4b5CMVZSqZDHPgT70uWGI7yqkn8Ufw==} + peerDependencies: + '@mantine/hooks': 9.3.1 + react: ^19.2.0 + react-dom: ^19.2.0 + + '@mantine/dates@9.3.1': + resolution: {integrity: sha512-Nh4XQyyV1AM0XRnUJugb1kNfCm++nkticsO8aroKmFPZ7cpGV7cjdL+4MJNPu1atS1EUePXl2VLLpKnrclpqOQ==} + peerDependencies: + '@mantine/core': 9.3.1 + '@mantine/hooks': 9.3.1 + dayjs: '>=1.0.0' + react: ^19.2.0 + react-dom: ^19.2.0 + + '@mantine/hooks@9.3.1': + resolution: {integrity: sha512-zAOlxV59j5CDgAnExN+ypaR6dVW1vwMKDvKUxIlUVd/e52qxYnPyYRD+shJmyOLaZRuQmVF0R/7mJAjt2jw9cA==} + peerDependencies: + react: ^19.2.0 + + '@mantine/notifications@9.3.1': + resolution: {integrity: sha512-irkD9uszgVftthbCLd+4lS7M1jkhL4sVrZASv8+43aIkncW+oQkCFpBuaOAf6uRcMjmQ7XnKs2O8bXYzl/DqgQ==} + peerDependencies: + '@mantine/core': 9.3.1 + '@mantine/hooks': 9.3.1 + react: ^19.2.0 + react-dom: ^19.2.0 + + '@mantine/store@9.3.1': + resolution: {integrity: sha512-UZ8wGKfF/NFMHCDl1Rd0M8XtHdt4W1w3V4ohNpFSaP3nSjJ+Cu1FT+iIWtXwC0Ogha8XnuVQ7vm/dw2BUfpw+g==} + peerDependencies: + react: ^19.2.0 + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.61.1': + resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.61.1': + resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.61.1': + resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.61.1': + resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.61.1': + resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.61.1': + resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.61.1': + resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.61.1': + resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.61.1': + resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.61.1': + resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.61.1': + resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.61.1': + resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.61.1': + resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.61.1': + resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} + cpu: [x64] + os: [win32] + + '@tabler/icons-react@3.44.0': + resolution: {integrity: sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg==} + peerDependencies: + react: '>= 16' + + '@tabler/icons@3.44.0': + resolution: {integrity: sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.19.20': + resolution: {integrity: sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + baseline-browser-mapping@2.10.35: + resolution: {integrity: sha512-honAfLBde0HAFLdNyBEfuuENkF6zR+ozxqxa/2zJKHBe1qzLqyTSeRKpdPEHAP03rlDGyQOPnCSxnVpVqQo9Mg==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001797: + resolution: {integrity: sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + electron-to-chromium@1.5.371: + resolution: {integrity: sha512-e9htk9mAYL6AzmkEhSvVVw7IWGSBJ/Bqdn2eRyRLrj1g6sncN4WbFt5qnILYoCktktr45pyjIrOiRvBThQ808w==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + graphology-layout-forceatlas2@0.10.1: + resolution: {integrity: sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==} + peerDependencies: + graphology-types: '>=0.19.0' + + graphology-types@0.24.8: + resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==} + + graphology-utils@2.5.2: + resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==} + peerDependencies: + graphology-types: '>=0.23.0' + + graphology@0.26.0: + resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==} + peerDependencies: + graphology-types: '>=0.24.0' + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-mixins@12.1.2: + resolution: {integrity: sha512-90pSxmZVfbX9e5xCv7tI5RV1mnjdf16y89CJKbf/hD7GyOz1FCxcYMl8ZYA8Hc56dbApTKKmU9HfvgfWdCxlwg==} + engines: {node: ^20.0 || ^22.0 || >=24.0} + peerDependencies: + postcss: ^8.2.14 + + postcss-nested@7.0.2: + resolution: {integrity: sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-preset-mantine@1.18.0: + resolution: {integrity: sha512-sP6/s1oC7cOtBdl4mw/IRKmKvYTuzpRrH/vT6v9enMU/EQEQ31eQnHcWtFghOXLH87AAthjL/Q75rLmin1oZoA==} + peerDependencies: + postcss: '>=8.0.0' + + postcss-selector-parser@7.1.2: + resolution: {integrity: sha512-Wjvt4scRFouioIInHf51IFNP4ltJ2EngJM+cZPGiqbKetBfmP3vpdPV8ID2S6JS6/jdo74N8+aEYH9lQr2C6sA==} + engines: {node: '>=4'} + + postcss-simple-vars@7.0.1: + resolution: {integrity: sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==} + engines: {node: '>=14.0'} + peerDependencies: + postcss: ^8.2.1 + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-number-format@5.4.5: + resolution: {integrity: sha512-y8O2yHHj3w0aE9XO8d2BCcUOOdQTRSVq+WIuMlLVucAm5XNjJAy+BoOJiuQMldVYVOKTMyvVNfnbl2Oqp+YxGw==} + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + rollup@4.61.1: + resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + sigma@3.0.3: + resolution: {integrity: sha512-5H0zFlx6/NTQpqBg4Rm569ZOpnBOXMaS25UQThIWMU3XyzI5AhmorK/gnl87BvJBLhQd0tW4C0LIp3enWzMoNw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + sugarss@5.0.1: + resolution: {integrity: sha512-ctS5RYCBVvPoZAnzIaX5QSShK8ZiZxD5HUqSxlusvEMC+QZQIPCPOIJg6aceFX+K2rf4+SH89eu++h1Zmsr2nw==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.3.3 + + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@5.7.0: + resolution: {integrity: sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==} + engines: {node: '>=20'} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.3: + resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + 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 + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/react-dom@2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/dom': 1.7.6 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@floating-ui/react@0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@floating-ui/utils': 0.2.11 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + tabbable: 6.4.0 + + '@floating-ui/utils@0.2.11': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mantine/core@9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@floating-ui/react': 0.27.19(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.3.1(react@19.2.7) + clsx: 2.1.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-number-format: 5.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) + type-fest: 5.7.0 + transitivePeerDependencies: + - '@types/react' + + '@mantine/dates@9.3.1(@mantine/core@9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.1(react@19.2.7))(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@mantine/core': 9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.3.1(react@19.2.7) + clsx: 2.1.1 + dayjs: 1.11.21 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + '@mantine/hooks@9.3.1(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@mantine/notifications@9.3.1(@mantine/core@9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.3.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + dependencies: + '@mantine/core': 9.3.1(@mantine/hooks@9.3.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@mantine/hooks': 9.3.1(react@19.2.7) + '@mantine/store': 9.3.1(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + + '@mantine/store@9.3.1(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.61.1': + optional: true + + '@rollup/rollup-android-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.61.1': + optional: true + + '@rollup/rollup-darwin-x64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.61.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.61.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.61.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.61.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.61.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.61.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.61.1': + optional: true + + '@tabler/icons-react@3.44.0(react@19.2.7)': + dependencies: + '@tabler/icons': 3.44.0 + react: 19.2.7 + + '@tabler/icons@3.44.0': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.9 + + '@types/estree@1.0.9': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@22.19.20': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.1': {} + + '@vitejs/plugin-react@4.7.0(vite@6.4.3(@types/node@22.19.20)(sugarss@5.0.1(postcss@8.5.15)))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.3(@types/node@22.19.20)(sugarss@5.0.1(postcss@8.5.15)) + transitivePeerDependencies: + - supports-color + + bail@2.0.2: {} + + baseline-browser-mapping@2.10.35: {} + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.35 + caniuse-lite: 1.0.30001797 + electron-to-chromium: 1.5.371 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001797: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + clsx@2.1.1: {} + + comma-separated-tokens@2.0.3: {} + + convert-source-map@2.0.0: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + dayjs@1.11.21: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + + detect-node-es@1.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.29.7 + csstype: 3.2.3 + + electron-to-chromium@1.5.371: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + events@3.3.0: {} + + extend@3.0.2: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + graphology-layout-forceatlas2@0.10.1(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + graphology-utils: 2.5.2(graphology-types@0.24.8) + + graphology-types@0.24.8: {} + + graphology-utils@2.5.2(graphology-types@0.24.8): + dependencies: + graphology-types: 0.24.8 + + graphology@0.26.0(graphology-types@0.24.8): + dependencies: + events: 3.3.0 + graphology-types: 0.24.8 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.9 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + html-url-attributes@3.0.1: {} + + inline-style-parser@0.2.7: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + + is-hexadecimal@2.0.1: {} + + is-plain-obj@4.1.0: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.1 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + node-releases@2.0.47: {} + + object-assign@4.1.1: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss-js@4.1.0(postcss@8.5.15): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.15 + + postcss-mixins@12.1.2(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-js: 4.1.0(postcss@8.5.15) + postcss-simple-vars: 7.0.1(postcss@8.5.15) + sugarss: 5.0.1(postcss@8.5.15) + tinyglobby: 0.2.17 + + postcss-nested@7.0.2(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-selector-parser: 7.1.2 + + postcss-preset-mantine@1.18.0(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + postcss-mixins: 12.1.2(postcss@8.5.15) + postcss-nested: 7.0.2(postcss@8.5.15) + + postcss-selector-parser@7.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-simple-vars@7.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.2.0: {} + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.7): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.17 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.7 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-number-format@5.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7) + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7) + use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + + react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + get-nonce: 1.0.1 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + dependencies: + '@babel/runtime': 7.29.7 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + + react@19.2.7: {} + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + rollup@4.61.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.61.1 + '@rollup/rollup-android-arm64': 4.61.1 + '@rollup/rollup-darwin-arm64': 4.61.1 + '@rollup/rollup-darwin-x64': 4.61.1 + '@rollup/rollup-freebsd-arm64': 4.61.1 + '@rollup/rollup-freebsd-x64': 4.61.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 + '@rollup/rollup-linux-arm-musleabihf': 4.61.1 + '@rollup/rollup-linux-arm64-gnu': 4.61.1 + '@rollup/rollup-linux-arm64-musl': 4.61.1 + '@rollup/rollup-linux-loong64-gnu': 4.61.1 + '@rollup/rollup-linux-loong64-musl': 4.61.1 + '@rollup/rollup-linux-ppc64-gnu': 4.61.1 + '@rollup/rollup-linux-ppc64-musl': 4.61.1 + '@rollup/rollup-linux-riscv64-gnu': 4.61.1 + '@rollup/rollup-linux-riscv64-musl': 4.61.1 + '@rollup/rollup-linux-s390x-gnu': 4.61.1 + '@rollup/rollup-linux-x64-gnu': 4.61.1 + '@rollup/rollup-linux-x64-musl': 4.61.1 + '@rollup/rollup-openbsd-x64': 4.61.1 + '@rollup/rollup-openharmony-arm64': 4.61.1 + '@rollup/rollup-win32-arm64-msvc': 4.61.1 + '@rollup/rollup-win32-ia32-msvc': 4.61.1 + '@rollup/rollup-win32-x64-gnu': 4.61.1 + '@rollup/rollup-win32-x64-msvc': 4.61.1 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + sigma@3.0.3(graphology-types@0.24.8): + dependencies: + events: 3.3.0 + graphology-utils: 2.5.2(graphology-types@0.24.8) + transitivePeerDependencies: + - graphology-types + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + sugarss@5.0.1(postcss@8.5.15): + dependencies: + postcss: 8.5.15 + + tabbable@6.4.0: {} + + tagged-tag@1.0.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tslib@2.8.1: {} + + type-fest@5.7.0: + dependencies: + tagged-tag: 1.0.0 + + typescript@5.6.3: {} + + undici-types@6.21.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.7 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + util-deprecate@1.0.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.3(@types/node@22.19.20)(sugarss@5.0.1(postcss@8.5.15)): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.61.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.19.20 + fsevents: 2.3.3 + sugarss: 5.0.1(postcss@8.5.15) + + yallist@3.1.1: {} + + zwitch@2.0.4: {} diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml new file mode 100644 index 0000000..5ed0b5a --- /dev/null +++ b/frontend/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: true diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..e817f56 --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + "postcss-preset-mantine": {}, + "postcss-simple-vars": { + variables: { + "mantine-breakpoint-xs": "36em", + "mantine-breakpoint-sm": "48em", + "mantine-breakpoint-md": "62em", + "mantine-breakpoint-lg": "75em", + "mantine-breakpoint-xl": "88em", + }, + }, + }, +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..a851e52 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,125 @@ +import { useState } from "react"; +import { + ActionIcon, + AppShell, + Badge, + Group, + NavLink, + ScrollArea, + Text, + Title, + Tooltip, +} from "@mantine/core"; +import { notifications } from "@mantine/notifications"; +import { + IconAddressBook, + IconCalendarEvent, + IconGraph, + IconRefresh, + IconTable, +} from "@tabler/icons-react"; +import { refresh } from "./api"; +import { NodeCardProvider } from "./NodeCardContext"; +import { GraphView } from "./views/GraphView"; +import { TablesView } from "./views/TablesView"; +import { ContactsView } from "./views/ContactsView"; +import { CalendarView } from "./views/CalendarView"; + +type Section = "grafo" | "tablas" | "contactos" | "calendario"; + +const SECTIONS: { + key: Section; + label: string; + icon: typeof IconGraph; +}[] = [ + { key: "grafo", label: "Grafo", icon: IconGraph }, + { key: "tablas", label: "Tablas", icon: IconTable }, + { key: "contactos", label: "Contactos", icon: IconAddressBook }, + { key: "calendario", label: "Calendario", icon: IconCalendarEvent }, +]; + +export function App() { + const [section, setSection] = useState
("grafo"); + // Cambiar `reloadKey` fuerza el remontaje de la vista activa tras un refresh + // del backend, para que vuelva a pedir sus datos. + const [reloadKey, setReloadKey] = useState(0); + const [refreshing, setRefreshing] = useState(false); + + async function onRefresh() { + setRefreshing(true); + try { + const res = await refresh(); + notifications.show({ + color: "teal", + title: "Vault recargado", + message: `${res.nodes} nodos, ${res.edges} aristas`, + }); + setReloadKey((k) => k + 1); + } catch (err) { + notifications.show({ + color: "red", + title: "Error al refrescar", + message: String(err), + }); + } finally { + setRefreshing(false); + } + } + + return ( + + + + + + + osint · explorador + + solo lectura + + + + + + + + + + + + + {SECTIONS.map((s) => ( + } + onClick={() => setSection(s.key)} + /> + ))} + + + datos sensibles · 127.0.0.1 + + + + + {section === "grafo" && } + {section === "tablas" && } + {section === "contactos" && } + {section === "calendario" && } + + + + ); +} diff --git a/frontend/src/NodeCardContext.tsx b/frontend/src/NodeCardContext.tsx new file mode 100644 index 0000000..273a706 --- /dev/null +++ b/frontend/src/NodeCardContext.tsx @@ -0,0 +1,29 @@ +import { createContext, useContext, useState, type ReactNode } from "react"; +import { NodeCard } from "./views/NodeCard"; + +// Contexto global para abrir la ficha (NodeCard) de cualquier nodo desde +// cualquier vista (click en nodo del grafo, click en fila de tabla, click en +// resultado de búsqueda). La ficha es un modal único montado en la raíz. + +interface NodeCardCtx { + open: (slug: string) => void; +} + +const Ctx = createContext(null); + +export function NodeCardProvider({ children }: { children: ReactNode }) { + const [slug, setSlug] = useState(null); + + return ( + + {children} + setSlug(null)} onNavigate={setSlug} /> + + ); +} + +export function useNodeCard(): NodeCardCtx { + const ctx = useContext(Ctx); + if (!ctx) throw new Error("useNodeCard fuera de NodeCardProvider"); + return ctx; +} diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 0000000..38503c9 --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,172 @@ +// Cliente fino sobre el backend FastAPI de osint_web (127.0.0.1:8470, proxyado +// en dev por Vite bajo /api). Todas las rutas son relativas para que el mismo +// build sirva embebido o tras el proxy. Cada función devuelve JSON ya tipado. + +const BASE = "/api"; + +async function getJSON(path: string): Promise { + const res = await fetch(BASE + path, { headers: { Accept: "application/json" } }); + if (!res.ok) { + let detail = ""; + try { + const body = await res.json(); + detail = body?.detail || body?.error || ""; + } catch { + /* respuesta no JSON */ + } + throw new Error(`HTTP ${res.status}${detail ? ` — ${detail}` : ""}`); + } + return res.json() as Promise; +} + +// --- Tipos del backend ---------------------------------------------------- + +export type Frontmatter = Record; + +export interface GraphNode { + id: string; + tipo: string; + label: string; + frontmatter: Frontmatter; + dangling?: boolean; +} + +export interface GraphEdge { + source: string; + target: string; + kind: string; +} + +export interface GraphPayload { + nodes: GraphNode[]; + edges: GraphEdge[]; + counts: Record; + total_nodes: number; + total_edges: number; +} + +export interface NodeRow { + id: string; + label: string; + tipo: string; + frontmatter: Frontmatter; +} + +export interface NodesPayload { + tipo: string; + count: number; + rows: NodeRow[]; +} + +export type AttachmentKind = "image" | "pdf" | "other" | "missing"; + +export interface Attachment { + name: string; + path: string; + kind: AttachmentKind; +} + +export interface NodeDetail { + id: string; + tipo: string; + label: string; + frontmatter: Frontmatter; + body: string; + tags: string[]; + wikilinks: string[]; + attachments: Attachment[]; +} + +export interface SearchHit { + id: string; + label: string; + tipo: string; + matches: string[]; +} + +export interface SearchPayload { + query: string; + count: number; + results: SearchHit[]; +} + +export interface ContactPhone { + value: string; + type: string; +} + +export interface Contact { + uid: string | null; + fn: string | null; + nombre: string | null; + nickname: string | null; + alias: string | null; + org: string | null; + note: string | null; + nota: string | null; + phones: ContactPhone[]; + emails: ContactPhone[]; + telefonos: string[]; + correos: string[]; + osint: Record; + href?: string; + etag?: string; +} + +export interface ContactsPayload { + status: string; + count?: number; + contacts?: Contact[]; + error?: string; +} + +export interface CalendarEvent { + uid: string | null; + summary: string | null; + dtstart: string | null; + dtend: string | null; + location: string | null; + description: string | null; + href?: string; + etag?: string; +} + +export interface CalendarPayload { + status: string; + count?: number; + events?: CalendarEvent[]; + error?: string; +} + +// --- Endpoints ------------------------------------------------------------ + +export const fetchGraph = () => getJSON("/graph"); + +export const fetchNodes = (tipo: string) => + getJSON(`/nodes?tipo=${encodeURIComponent(tipo)}`); + +export const fetchNode = (slug: string) => + getJSON(`/node/${encodeURIComponent(slug)}`); + +export const fetchSearch = (q: string) => + getJSON(`/search?q=${encodeURIComponent(q)}`); + +export const fetchContacts = () => getJSON("/contacts"); + +export const fetchCalendar = (from = "", to = "") => { + const qs = new URLSearchParams(); + if (from) qs.set("from", from); + if (to) qs.set("to", to); + const tail = qs.toString(); + return getJSON(`/calendar${tail ? `?${tail}` : ""}`); +}; + +export const refresh = () => + fetch(`${BASE}/refresh`, { method: "POST" }).then((r) => { + if (!r.ok) throw new Error(`refresh falló: HTTP ${r.status}`); + return r.json(); + }); + +// URL del binario de un attachment (imagen / pdf) servido por el backend. +export const attachmentUrl = (path: string) => + `${BASE}/attachment?path=${encodeURIComponent(path)}`; diff --git a/frontend/src/format.ts b/frontend/src/format.ts new file mode 100644 index 0000000..886af2d --- /dev/null +++ b/frontend/src/format.ts @@ -0,0 +1,152 @@ +// Formateo de fechas en europeo DD/MM/AAAA (memoria formato-fecha-europeo). +// El backend devuelve dos formas de fecha: +// - ISO de Obsidian: "2026-06-07" (frontmatter `creado`, `fecha_nacimiento`). +// - iCal del calendario: "20260611T090000Z" o "20260611" (date / date-time). +// Estos helpers las normalizan a la presentación europea sin atar al locale. + +const MESES = [ + "ene", + "feb", + "mar", + "abr", + "may", + "jun", + "jul", + "ago", + "sep", + "oct", + "nov", + "dic", +]; + +/** "2026-06-07" → "07/06/2026". Devuelve el original si no matchea. */ +export function formatISODate(value: string): string { + const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(value); + if (!m) return value; + return `${m[3]}/${m[2]}/${m[1]}`; +} + +interface ICalParts { + year: string; + month: string; + day: string; + hour?: string; + minute?: string; + isUtc: boolean; + dateOnly: boolean; +} + +/** Parsea "20260611T090000Z" / "20260611" a sus componentes. null si no matchea. */ +export function parseICal(value: string): ICalParts | null { + const m = /^(\d{4})(\d{2})(\d{2})(?:T(\d{2})(\d{2})(\d{2})?(Z)?)?/.exec(value); + if (!m) return null; + return { + year: m[1], + month: m[2], + day: m[3], + hour: m[4], + minute: m[5], + isUtc: m[7] === "Z", + dateOnly: m[4] === undefined, + }; +} + +/** + * Fecha iCal a europeo. Las date-time vienen en UTC ("...Z"); las convertimos a + * hora local para mostrar la hora correcta del evento. "20260611T090000Z" en + * Europe/Madrid → "11/06/2026 11:00". + */ +export function formatICalDate(value: string | null | undefined): string { + if (!value) return ""; + const p = parseICal(value); + if (!p) return value; + if (p.dateOnly) { + return `${p.day}/${p.month}/${p.year}`; + } + if (p.isUtc) { + const d = new Date( + Date.UTC( + Number(p.year), + Number(p.month) - 1, + Number(p.day), + Number(p.hour ?? "0"), + Number(p.minute ?? "0"), + ), + ); + const dd = String(d.getDate()).padStart(2, "0"); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const hh = String(d.getHours()).padStart(2, "0"); + const min = String(d.getMinutes()).padStart(2, "0"); + return `${dd}/${mm}/${d.getFullYear()} ${hh}:${min}`; + } + return `${p.day}/${p.month}/${p.year} ${p.hour}:${p.minute}`; +} + +/** Solo la hora local "HH:MM" de una fecha iCal (para listas agrupadas por día). */ +export function formatICalTime(value: string | null | undefined): string { + if (!value) return ""; + const p = parseICal(value); + if (!p || p.dateOnly) return "todo el día"; + if (p.isUtc) { + const d = new Date( + Date.UTC( + Number(p.year), + Number(p.month) - 1, + Number(p.day), + Number(p.hour ?? "0"), + Number(p.minute ?? "0"), + ), + ); + return `${String(d.getHours()).padStart(2, "0")}:${String( + d.getMinutes(), + ).padStart(2, "0")}`; + } + return `${p.hour}:${p.minute}`; +} + +/** Clave de día local "AAAA-MM-DD" de una fecha iCal, para agrupar eventos. */ +export function icalDayKey(value: string | null | undefined): string { + if (!value) return ""; + const p = parseICal(value); + if (!p) return ""; + if (p.dateOnly || !p.isUtc) { + return `${p.year}-${p.month}-${p.day}`; + } + const d = new Date( + Date.UTC( + Number(p.year), + Number(p.month) - 1, + Number(p.day), + Number(p.hour ?? "0"), + Number(p.minute ?? "0"), + ), + ); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String( + d.getDate(), + ).padStart(2, "0")}`; +} + +/** Etiqueta legible de un día "AAAA-MM-DD" → "11 jun 2026". */ +export function dayLabel(key: string): string { + const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(key); + if (!m) return key; + const mes = MESES[Number(m[2]) - 1] ?? m[2]; + return `${Number(m[3])} ${mes} ${m[1]}`; +} + +/** + * Presenta un valor de frontmatter como texto. Aplana arrays y objetos, y + * formatea a europeo cualquier string que parezca fecha ISO. + */ +export function formatFrontmatterValue(value: unknown): string { + if (value == null) return ""; + if (Array.isArray(value)) { + return value.map((v) => formatFrontmatterValue(v)).join(", "); + } + if (typeof value === "object") { + return JSON.stringify(value); + } + const s = String(value); + if (/^\d{4}-\d{2}-\d{2}/.test(s)) return formatISODate(s); + return s; +} diff --git a/frontend/src/global.css b/frontend/src/global.css new file mode 100644 index 0000000..ec47729 --- /dev/null +++ b/frontend/src/global.css @@ -0,0 +1,36 @@ +html, +body, +#root { + height: 100%; + margin: 0; +} + +/* El contenedor del grafo sigma necesita una altura explícita para renderizar. */ +.sigma-container { + width: 100%; + height: 100%; +} + +/* Markdown del cuerpo de las fichas: márgenes contenidos, imágenes responsivas. */ +.node-body img { + max-width: 100%; + height: auto; + border-radius: 8px; +} + +.node-body pre { + overflow-x: auto; + padding: 8px; + background: var(--mantine-color-dark-8); + border-radius: 6px; +} + +.node-body table { + border-collapse: collapse; +} + +.node-body table td, +.node-body table th { + border: 1px solid var(--mantine-color-dark-4); + padding: 4px 8px; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..c499b76 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,19 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { MantineProvider } from "@mantine/core"; +import { Notifications } from "@mantine/notifications"; +import "@mantine/core/styles.css"; +import "@mantine/dates/styles.css"; +import "@mantine/notifications/styles.css"; +import "./global.css"; +import { theme } from "./theme"; +import { App } from "./App"; + +createRoot(document.getElementById("root")!).render( + + + + + + , +); diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts new file mode 100644 index 0000000..1319216 --- /dev/null +++ b/frontend/src/theme.ts @@ -0,0 +1,26 @@ +import { createTheme, type MantineColorsTuple } from "@mantine/core"; + +// Acento de marca del explorador OSINT: un cian-teal sobrio, distinto del resto +// de apps del ecosistema. Mantine genera sus propias CSS variables a partir de +// este tuple (sin CSS custom, regla frontend_theming.md). +const brand: MantineColorsTuple = [ + "#e0fbff", + "#cbf2ff", + "#9ae2ff", + "#64d2ff", + "#3cc5fe", + "#23bdfe", + "#09b9ff", + "#00a3e4", + "#0091cc", + "#007eb4", +]; + +export const theme = createTheme({ + primaryColor: "brand", + colors: { brand }, + fontFamily: + "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", + defaultRadius: "md", + headings: { fontWeight: "650" }, +}); diff --git a/frontend/src/tipos.ts b/frontend/src/tipos.ts new file mode 100644 index 0000000..5e6c657 --- /dev/null +++ b/frontend/src/tipos.ts @@ -0,0 +1,44 @@ +// Identidad visual por `tipo` de nodo, compartida entre el grafo (sigma), +// la leyenda, las tablas y las fichas. Los tipos provienen del frontmatter de +// las notas del vault osint; `dangling` no es un tipo sino un flag (nodo +// fantasma de un wikilink sin nota), con su propio estilo atenuado. + +export interface TipoStyle { + /** Color hex del nodo / badge. */ + color: string; + /** Etiqueta humana para leyenda y filtros. */ + label: string; +} + +const STYLES: Record = { + persona: { color: "#3cc5fe", label: "Persona" }, + organizacion: { color: "#f59f00", label: "Organización" }, + lugar: { color: "#51cf66", label: "Lugar" }, + documento: { color: "#cc5de8", label: "Documento" }, + caso: { color: "#ff6b6b", label: "Caso" }, + dominio: { color: "#20c997", label: "Dominio" }, + nota: { color: "#868e96", label: "Nota" }, + desconocido: { color: "#adb5bd", label: "Desconocido" }, +}; + +const FALLBACK: TipoStyle = { color: "#adb5bd", label: "Otro" }; + +/** Color/label de un tipo; cae a un gris neutro si el tipo es desconocido. */ +export function tipoStyle(tipo: string): TipoStyle { + return STYLES[tipo] ?? { ...FALLBACK, label: tipo || FALLBACK.label }; +} + +/** Color atenuado para nodos dangling (wikilink sin nota). */ +export const DANGLING_COLOR = "#495057"; + +/** Color de una arista según su `kind` (wikilink / relacion / documento). */ +export function edgeColor(kind: string): string { + switch (kind) { + case "relacion": + return "#5c7cfa"; + case "documento": + return "#cc5de8"; + default: + return "#343a40"; + } +} diff --git a/frontend/src/views/CalendarView.tsx b/frontend/src/views/CalendarView.tsx new file mode 100644 index 0000000..59a5397 --- /dev/null +++ b/frontend/src/views/CalendarView.tsx @@ -0,0 +1,220 @@ +import { useEffect, useMemo, useState } from "react"; +import { + Alert, + Badge, + Box, + Center, + Group, + Indicator, + Loader, + Paper, + ScrollArea, + Stack, + Text, + Title, +} from "@mantine/core"; +import { Calendar } from "@mantine/dates"; +import dayjs from "dayjs"; +import { IconClock, IconMapPin } from "@tabler/icons-react"; +import { fetchCalendar, type CalendarEvent } from "../api"; +import { + dayLabel, + formatICalTime, + icalDayKey, +} from "../format"; + +// Calendario: mini-calendario de @mantine/dates a la izquierda (con punto en +// los días que tienen eventos) y la lista de eventos a la derecha. Por defecto +// muestra el mes actual agrupado por día; al elegir un día se filtra a ese día. + +export function CalendarView() { + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + // Mantine v9 Calendar usa fechas como string "YYYY-MM-DD" (DateStringValue), + // no Date. `month` controla el mes mostrado; `selectedDay` filtra a un día. + const [month, setMonth] = useState(dayjs().format("YYYY-MM-DD")); + const [selectedDay, setSelectedDay] = useState(null); + + useEffect(() => { + let alive = true; + setLoading(true); + fetchCalendar() + .then((d) => { + if (!alive) return; + if (d.status !== "ok") { + setError(d.error || "Xandikos no respondió"); + return; + } + setEvents(d.events ?? []); + }) + .catch((e) => alive && setError(String(e))) + .finally(() => alive && setLoading(false)); + return () => { + alive = false; + }; + }, []); + + // Eventos indexados por día local "AAAA-MM-DD". + const byDay = useMemo(() => { + const map = new Map(); + for (const e of events) { + const key = icalDayKey(e.dtstart); + if (!key) continue; + const list = map.get(key) ?? []; + list.push(e); + map.set(key, list); + } + return map; + }, [events]); + + // Días visibles: si hay día seleccionado, solo ese; si no, todos los del mes + // mostrado, ordenados. + const visibleDays = useMemo(() => { + const monthPrefix = month.slice(0, 7); // "YYYY-MM" + let keys = [...byDay.keys()]; + if (selectedDay) { + keys = keys.filter((k) => k === selectedDay); + } else { + keys = keys.filter((k) => k.startsWith(monthPrefix)); + } + return keys.sort(); + }, [byDay, month, selectedDay]); + + if (error) { + return ( +
+ + {error} + + El calendario viene del servidor Xandikos. El resto de la app (grafo, + tablas) funciona sin él. + + +
+ ); + } + + if (loading) { + return ( +
+ +
+ ); + } + + return ( + + + + ({ + selected: selectedDay === date, + onClick: () => + setSelectedDay((prev) => (prev === date ? null : date)), + })} + renderDay={(date) => { + const has = byDay.has(date); + const day = Number(date.slice(8, 10)); + return ( + +
{day}
+
+ ); + }} + /> + + {events.length} eventos en total + + {selectedDay && ( + setSelectedDay(null)} + > + Ver todo el mes + + )} +
+
+ + + + + + {selectedDay + ? dayLabel(selectedDay) + : dayjs(month).format("MMMM YYYY")} + + + {visibleDays.length === 0 && ( + Sin eventos en este periodo. + )} + + {visibleDays.map((day) => ( + + {!selectedDay && ( + + {dayLabel(day)} + + )} + {(byDay.get(day) ?? []) + .sort((a, b) => + (a.dtstart ?? "").localeCompare(b.dtstart ?? ""), + ) + .map((ev, i) => ( + + ))} + + ))} + + + +
+ ); +} + +function EventRow({ ev }: { ev: CalendarEvent }) { + return ( + + + + + {ev.summary || "(sin título)"} + + {ev.location && ( + + + + {ev.location} + + + )} + {ev.description && ( + + {ev.description} + + )} + + + + + {formatICalTime(ev.dtstart)} + + + + + ); +} diff --git a/frontend/src/views/ContactsView.tsx b/frontend/src/views/ContactsView.tsx new file mode 100644 index 0000000..55634f2 --- /dev/null +++ b/frontend/src/views/ContactsView.tsx @@ -0,0 +1,281 @@ +import { useEffect, useMemo, useState } from "react"; +import { + Alert, + Badge, + Box, + Center, + Divider, + Group, + Loader, + Paper, + ScrollArea, + Stack, + Table, + Text, + TextInput, + Title, +} from "@mantine/core"; +import { useDebouncedValue } from "@mantine/hooks"; +import { + IconAt, + IconNote, + IconPhone, + IconSearch, + IconUser, +} from "@tabler/icons-react"; +import { fetchContacts, type Contact } from "../api"; + +// Agenda: lista de contactos del addressbook Xandikos a la izquierda (con +// buscador por nombre / alias / teléfono / email) y la ficha del contacto +// seleccionado a la derecha (todos los campos, incluido el bloque osint y nota). + +export function ContactsView() { + const [contacts, setContacts] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selected, setSelected] = useState(null); + const [query, setQuery] = useState(""); + const [debQuery] = useDebouncedValue(query, 200); + + useEffect(() => { + let alive = true; + setLoading(true); + fetchContacts() + .then((d) => { + if (!alive) return; + if (d.status !== "ok") { + setError(d.error || "Xandikos no respondió"); + return; + } + setContacts(d.contacts ?? []); + }) + .catch((e) => alive && setError(String(e))) + .finally(() => alive && setLoading(false)); + return () => { + alive = false; + }; + }, []); + + const filtered = useMemo(() => { + const q = debQuery.trim().toLowerCase(); + if (!q) return contacts; + return contacts.filter((c) => { + const hay = [ + c.nombre, + c.alias, + c.org, + ...(c.telefonos ?? []), + ...(c.correos ?? []), + ] + .filter(Boolean) + .join(" ") + .toLowerCase(); + return hay.includes(q); + }); + }, [contacts, debQuery]); + + if (error) { + return ( +
+ + {error} + + El calendario y los contactos vienen del servidor Xandikos. El resto + de la app (grafo, tablas) funciona sin él. + + +
+ ); + } + + return ( + + + + + } + value={query} + onChange={(e) => setQuery(e.currentTarget.value)} + /> + + {filtered.length} de {contacts.length} contactos + + + + {loading ? ( +
+ +
+ ) : ( + + + {filtered.map((c, i) => { + const key = c.uid || c.href || String(i); + const isSel = selected === c; + return ( + setSelected(c)} + style={{ + cursor: "pointer", + background: isSel + ? "var(--mantine-color-brand-light)" + : undefined, + borderBottom: "1px solid var(--mantine-color-dark-5)", + }} + > + + {c.nombre || c.alias || c.uid || "(sin nombre)"} + + {(c.telefonos?.[0] || c.correos?.[0]) && ( + + {c.telefonos?.[0] || c.correos?.[0]} + + )} + + ); + })} + + + )} +
+
+ + + + {selected ? ( + + ) : ( +
+ + + Selecciona un contacto + +
+ )} +
+
+
+ ); +} + +function ContactDetail({ contact }: { contact: Contact }) { + const osintEntries = Object.entries(contact.osint ?? {}).filter( + ([, v]) => v != null && v !== "", + ); + return ( + + + + {contact.nombre || contact.alias || contact.uid || "(sin nombre)"} + + {contact.alias && contact.alias !== contact.nombre && ( + + {contact.alias} + + )} + + + {contact.org && ( + + {contact.org} + + )} + + {(contact.telefonos?.length ?? 0) > 0 && ( + + + + + Teléfonos + + + {contact.phones.map((p, i) => ( + + {p.value} + {p.type && ( + + {p.type} + + )} + + ))} + + )} + + {(contact.correos?.length ?? 0) > 0 && ( + + + + + Correos + + + {contact.emails.map((e, i) => ( + + {e.value} + {e.type && ( + + {e.type} + + )} + + ))} + + )} + + {osintEntries.length > 0 && ( + + + OSINT + + + + {osintEntries.map(([k, v]) => ( + + + + {k.replace(/_/g, " ")} + + + + {v} + + + ))} + +
+
+ )} + + {contact.nota && ( + + + + + Nota + + + + {contact.nota} + + + )} + + {contact.uid && ( + + UID: {contact.uid} + + )} +
+ ); +} diff --git a/frontend/src/views/GraphView.tsx b/frontend/src/views/GraphView.tsx new file mode 100644 index 0000000..dd2be12 --- /dev/null +++ b/frontend/src/views/GraphView.tsx @@ -0,0 +1,399 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + ActionIcon, + Alert, + Badge, + Button, + Center, + Checkbox, + Group, + Loader, + Paper, + ScrollArea, + Stack, + Switch, + Text, + TextInput, + Tooltip, +} from "@mantine/core"; +import { useDebouncedValue } from "@mantine/hooks"; +import { + IconPlayerPause, + IconPlayerPlay, + IconSearch, + IconZoomReset, +} from "@tabler/icons-react"; +import Graph from "graphology"; +import forceAtlas2 from "graphology-layout-forceatlas2"; +import FA2Layout from "graphology-layout-forceatlas2/worker"; +import Sigma from "sigma"; +import { fetchGraph, fetchSearch, type GraphPayload } from "../api"; +import { useNodeCard } from "../NodeCardContext"; +import { DANGLING_COLOR, edgeColor, tipoStyle } from "../tipos"; + +// Layout: forceatlas2 en un web worker (no bloquea la UI con 1199 nodos). Se +// arranca al montar, se deja correr unos segundos y el usuario puede pausar / +// reanudar. Un pre-cálculo síncrono acotado da posiciones iniciales decentes. + +export function GraphView() { + const containerRef = useRef(null); + const sigmaRef = useRef(null); + const graphRef = useRef(null); + const layoutRef = useRef(null); + const { open } = useNodeCard(); + + const [payload, setPayload] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const [visibleTipos, setVisibleTipos] = useState>(new Set()); + const [showDangling, setShowDangling] = useState(true); + const [running, setRunning] = useState(true); + // Error de renderizado del propio canvas (ej. WebGL no disponible: navegador + // sin GPU / headless). Se aísla aquí para no tumbar la app entera. + const [renderError, setRenderError] = useState(null); + + const [search, setSearch] = useState(""); + const [debSearch] = useDebouncedValue(search, 300); + const [matches, setMatches] = useState<{ id: string; label: string; tipo: string }[]>( + [], + ); + + // --- carga de datos --- + useEffect(() => { + let alive = true; + setLoading(true); + fetchGraph() + .then((d) => { + if (!alive) return; + setPayload(d); + setVisibleTipos(new Set(Object.keys(d.counts))); + }) + .catch((e) => alive && setError(String(e))) + .finally(() => alive && setLoading(false)); + return () => { + alive = false; + }; + }, []); + + const tipos = useMemo( + () => (payload ? Object.keys(payload.counts).sort() : []), + [payload], + ); + + // --- construcción del grafo + sigma --- + useEffect(() => { + if (!payload || !containerRef.current) return; + + // Pre-chequeo de WebGL: sigma necesita un contexto WebGL. Si el navegador no + // lo expone (sin GPU, headless sin --use-gl), avisamos en vez de crashear. + const probe = document.createElement("canvas"); + const gl = + probe.getContext("webgl2") || probe.getContext("webgl"); + if (!gl) { + setRenderError( + "Este navegador no expone WebGL, necesario para dibujar el grafo. " + + "Las demás vistas (tablas, contactos, calendario) funcionan igual.", + ); + return; + } + + const graph = new Graph({ multi: true, type: "undirected" }); + const degree: Record = {}; + for (const e of payload.edges) { + degree[e.source] = (degree[e.source] ?? 0) + 1; + degree[e.target] = (degree[e.target] ?? 0) + 1; + } + + for (const n of payload.nodes) { + const deg = degree[n.id] ?? 0; + const size = Math.min(3 + Math.sqrt(deg) * 2, 18); + graph.addNode(n.id, { + label: n.label, + size, + color: n.dangling ? DANGLING_COLOR : tipoStyle(n.tipo).color, + x: Math.random(), + y: Math.random(), + tipo: n.tipo, + dangling: !!n.dangling, + }); + } + for (const e of payload.edges) { + if (graph.hasNode(e.source) && graph.hasNode(e.target)) { + graph.addEdge(e.source, e.target, { + color: edgeColor(e.kind), + size: 0.6, + kind: e.kind, + }); + } + } + + // Posiciones iniciales: pre-cálculo síncrono acotado (no bloquea mucho). + forceAtlas2.assign(graph, { + iterations: 50, + settings: forceAtlas2.inferSettings(graph), + }); + + graphRef.current = graph; + let sigma: Sigma; + try { + sigma = new Sigma(graph, containerRef.current, { + renderLabels: true, + labelDensity: 0.6, + labelGridCellSize: 80, + defaultEdgeColor: "#343a40", + labelColor: { color: "#c1c2c5" }, + labelFont: "Inter, sans-serif", + }); + } catch (err) { + setRenderError( + "No se pudo inicializar el lienzo del grafo (WebGL): " + String(err), + ); + graphRef.current = null; + return; + } + sigmaRef.current = sigma; + + sigma.on("clickNode", ({ node }) => open(node)); + + // Layout continuo en worker. + const layout = new FA2Layout(graph, { + settings: forceAtlas2.inferSettings(graph), + }); + layoutRef.current = layout; + layout.start(); + setRunning(true); + // Frenar el layout automáticamente a los 8s para que se estabilice. + const stopTimer = window.setTimeout(() => { + layout.stop(); + setRunning(false); + }, 8000); + + return () => { + window.clearTimeout(stopTimer); + layout.kill(); + sigma.kill(); + sigmaRef.current = null; + graphRef.current = null; + layoutRef.current = null; + }; + }, [payload, open]); + + // --- aplicar filtros de visibilidad (tipos + dangling) --- + useEffect(() => { + const graph = graphRef.current; + const sigma = sigmaRef.current; + if (!graph || !sigma) return; + graph.forEachNode((id, attrs) => { + const dangling = attrs.dangling as boolean; + const tipo = attrs.tipo as string; + const hidden = (dangling && !showDangling) || !visibleTipos.has(tipo); + graph.setNodeAttribute(id, "hidden", hidden); + }); + sigma.refresh(); + }, [visibleTipos, showDangling]); + + // --- búsqueda contra /api/search --- + useEffect(() => { + if (!debSearch.trim()) { + setMatches([]); + return; + } + let alive = true; + fetchSearch(debSearch) + .then((r) => { + if (!alive) return; + setMatches( + r.results + .filter((m) => graphRef.current?.hasNode(m.id)) + .slice(0, 20) + .map((m) => ({ id: m.id, label: m.label, tipo: m.tipo })), + ); + }) + .catch(() => alive && setMatches([])); + return () => { + alive = false; + }; + }, [debSearch]); + + const centerOn = useCallback((id: string) => { + const sigma = sigmaRef.current; + const graph = graphRef.current; + if (!sigma || !graph || !graph.hasNode(id)) return; + const nodePos = sigma.getNodeDisplayData(id); + if (!nodePos) return; + sigma.getCamera().animate( + { x: nodePos.x, y: nodePos.y, ratio: 0.25 }, + { duration: 500 }, + ); + }, []); + + function toggleTipo(tipo: string) { + setVisibleTipos((prev) => { + const next = new Set(prev); + if (next.has(tipo)) next.delete(tipo); + else next.add(tipo); + return next; + }); + } + + function toggleLayout() { + const layout = layoutRef.current; + if (!layout) return; + if (running) { + layout.stop(); + setRunning(false); + } else { + layout.start(); + setRunning(true); + } + } + + function resetCamera() { + sigmaRef.current?.getCamera().animatedReset(); + } + + if (error) { + return ( +
+ + {error} + +
+ ); + } + + if (renderError) { + return ( +
+ + {renderError} + +
+ ); + } + + return ( + + + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + {matches.length > 0 && ( + + + {matches.map((m) => ( + + ))} + + + )} + + + Tipos visibles + + + + {tipos.map((t) => { + const st = tipoStyle(t); + return ( + toggleTipo(t)} + color={st.color} + label={ + + + {" "} + + {st.label} + + {payload?.counts[t]} + + + } + /> + ); + })} + + + + setShowDangling(e.currentTarget.checked)} + label="Nodos fantasma (dangling)" + size="sm" + /> + + + + + {running ? ( + + ) : ( + + )} + + + + + + + + + {payload?.total_nodes} nodos · {payload?.total_edges} aristas + + + + + +
+ {loading && ( +
+ +
+ )} +
+
+ + ); +} diff --git a/frontend/src/views/Lightbox.tsx b/frontend/src/views/Lightbox.tsx new file mode 100644 index 0000000..9387ea0 --- /dev/null +++ b/frontend/src/views/Lightbox.tsx @@ -0,0 +1,36 @@ +import { Image, Modal } from "@mantine/core"; + +// Lightbox mínimo: un modal grande, fondo oscuro, con la imagen a tamaño +// completo. Usa el Modal de Mantine (no librería externa, KISS). + +export function Lightbox({ + src, + onClose, +}: { + src: string | null; + onClose: () => void; +}) { + return ( + + {src && ( + + )} + + ); +} diff --git a/frontend/src/views/NodeCard.tsx b/frontend/src/views/NodeCard.tsx new file mode 100644 index 0000000..d7928c2 --- /dev/null +++ b/frontend/src/views/NodeCard.tsx @@ -0,0 +1,248 @@ +import { useEffect, useState } from "react"; +import { + Alert, + Anchor, + Badge, + Center, + Group, + Image, + Loader, + Modal, + Paper, + ScrollArea, + SimpleGrid, + Stack, + Table, + Text, + Title, +} from "@mantine/core"; +import { IconExternalLink, IconFile, IconPhoto } from "@tabler/icons-react"; +import Markdown from "react-markdown"; +import { + attachmentUrl, + fetchNode, + type Attachment, + type NodeDetail, +} from "../api"; +import { formatFrontmatterValue } from "../format"; +import { tipoStyle } from "../tipos"; +import { Lightbox } from "./Lightbox"; + +interface Props { + slug: string | null; + onClose: () => void; + /** Navegar a otro nodo (click en un wikilink dentro de la ficha). */ + onNavigate: (slug: string) => void; +} + +export function NodeCard({ slug, onClose, onNavigate }: Props) { + const [detail, setDetail] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [lightbox, setLightbox] = useState(null); + + useEffect(() => { + if (!slug) { + setDetail(null); + setError(null); + return; + } + let alive = true; + setLoading(true); + setError(null); + fetchNode(slug) + .then((d) => alive && setDetail(d)) + .catch((e) => alive && setError(String(e))) + .finally(() => alive && setLoading(false)); + return () => { + alive = false; + }; + }, [slug]); + + const images = detail?.attachments.filter((a) => a.kind === "image") ?? []; + const docs = detail?.attachments.filter((a) => a.kind !== "image") ?? []; + + const style = detail ? tipoStyle(detail.tipo) : null; + + return ( + <> + + {detail.label} + {style && ( + + {style.label} + + )} + + ) : ( + "Ficha" + ) + } + > + {loading && ( +
+ +
+ )} + + {error && ( + + {error} + + )} + + {detail && !loading && ( + + + + {detail.tags.length > 0 && ( + + {detail.tags.map((t) => ( + + {t} + + ))} + + )} + + {images.length > 0 && ( + + + + Imágenes ({images.length}) + + + {images.map((a) => ( + {a.name} setLightbox(attachmentUrl(a.path))} + /> + ))} + + + )} + + {docs.length > 0 && ( + + + + Documentos ({docs.length}) + + + {docs.map((a) => ( + + ))} + + + )} + + {detail.body.trim() && ( + +
+ ( + + {children} + + ), + }} + > + {detail.body} + +
+
+ )} + + {detail.wikilinks.length > 0 && ( + + + Enlaces a otras notas + + + {detail.wikilinks.map((w) => ( + onNavigate(w)} + > + {w} + + ))} + + + )} +
+ )} +
+ + setLightbox(null)} /> + + ); +} + +function FrontmatterTable({ + frontmatter, +}: { + frontmatter: Record; +}) { + const entries = Object.entries(frontmatter).filter( + ([, v]) => v != null && v !== "", + ); + if (entries.length === 0) return null; + return ( + + + {entries.map(([k, v]) => ( + + + + {k.replace(/_/g, " ")} + + + + {formatFrontmatterValue(v)} + + + ))} + +
+ ); +} + +function DocLink({ att }: { att: Attachment }) { + if (att.kind === "missing") { + return ( + + ⚠ {att.name} (no encontrado) + + ); + } + return ( + + + + + {att.name.split("/").pop()} + + + {att.kind} + + + + ); +} diff --git a/frontend/src/views/TablesView.tsx b/frontend/src/views/TablesView.tsx new file mode 100644 index 0000000..0f28019 --- /dev/null +++ b/frontend/src/views/TablesView.tsx @@ -0,0 +1,289 @@ +import { useEffect, useMemo, useState } from "react"; +import { + Alert, + Box, + Center, + Group, + Loader, + ScrollArea, + Table, + Tabs, + Text, + TextInput, + UnstyledButton, +} from "@mantine/core"; +import { + IconChevronDown, + IconChevronUp, + IconSearch, + IconSelector, +} from "@tabler/icons-react"; +import { fetchGraph, fetchNodes, type NodeRow } from "../api"; +import { formatFrontmatterValue } from "../format"; +import { tipoStyle } from "../tipos"; +import { useNodeCard } from "../NodeCardContext"; + +// Una pestaña por tipo de nodo real (no fantasma). Cada pestaña carga +// perezosamente sus filas de /api/nodes?tipo= y las muestra en una tabla +// Mantine ordenable + filtrable. Las columnas se deducen de las claves de +// frontmatter más comunes del tipo. + +// Tipos que tienen tabla propia (los nodos reales del vault). Orden de aparición. +const TABLE_TIPOS = [ + "persona", + "organizacion", + "lugar", + "documento", + "caso", + "dominio", +]; + +export function TablesView() { + const [availableTipos, setAvailableTipos] = useState([]); + const [active, setActive] = useState("persona"); + const [error, setError] = useState(null); + + // Determinar qué tipos existen realmente en el vault (de los counts del grafo). + useEffect(() => { + let alive = true; + fetchGraph() + .then((d) => { + if (!alive) return; + const present = TABLE_TIPOS.filter((t) => (d.counts[t] ?? 0) > 0); + // Añadir cualquier otro tipo real con nodos no contemplado arriba. + const extra = Object.keys(d.counts).filter( + (t) => (d.counts[t] ?? 0) > 0 && !TABLE_TIPOS.includes(t) && t !== "nota", + ); + const all = [...present, ...extra]; + setAvailableTipos(all); + if (all.length > 0 && !all.includes(active)) setActive(all[0]); + }) + .catch((e) => alive && setError(String(e))); + return () => { + alive = false; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (error) { + return ( +
+ + {error} + +
+ ); + } + + if (availableTipos.length === 0) { + return ( +
+ +
+ ); + } + + return ( + v && setActive(v)} + keepMounted={false} + h="100%" + style={{ display: "flex", flexDirection: "column" }} + > + + {availableTipos.map((t) => ( + + } + > + {tipoStyle(t).label} + + ))} + + + {availableTipos.map((t) => ( + + {active === t && } + + ))} + + ); +} + +type SortDir = "asc" | "desc" | null; + +function TypeTable({ tipo }: { tipo: string }) { + const { open } = useNodeCard(); + const [rows, setRows] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [filter, setFilter] = useState(""); + const [sortCol, setSortCol] = useState("label"); + const [sortDir, setSortDir] = useState("asc"); + + useEffect(() => { + let alive = true; + setLoading(true); + fetchNodes(tipo) + .then((d) => alive && setRows(d.rows)) + .catch((e) => alive && setError(String(e))) + .finally(() => alive && setLoading(false)); + return () => { + alive = false; + }; + }, [tipo]); + + // Columnas: las claves de frontmatter más frecuentes de este conjunto, con + // `label` (nombre del nodo) siempre primero. Limitado para no desbordar. + const columns = useMemo(() => { + const freq: Record = {}; + for (const r of rows) { + for (const k of Object.keys(r.frontmatter)) { + if (k === "nombre" || k === "tipo") continue; // redundantes con label/tab + freq[k] = (freq[k] ?? 0) + 1; + } + } + const ranked = Object.entries(freq) + .sort((a, b) => b[1] - a[1]) + .slice(0, 6) + .map(([k]) => k); + return ["label", ...ranked]; + }, [rows]); + + const cellValue = (row: NodeRow, col: string): string => { + if (col === "label") return row.label; + return formatFrontmatterValue(row.frontmatter[col]); + }; + + const filtered = useMemo(() => { + const q = filter.trim().toLowerCase(); + let out = rows; + if (q) { + out = rows.filter((r) => + columns.some((c) => cellValue(r, c).toLowerCase().includes(q)), + ); + } + if (sortDir) { + out = [...out].sort((a, b) => { + const va = cellValue(a, sortCol).toLowerCase(); + const vb = cellValue(b, sortCol).toLowerCase(); + if (va < vb) return sortDir === "asc" ? -1 : 1; + if (va > vb) return sortDir === "asc" ? 1 : -1; + return 0; + }); + } + return out; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rows, filter, sortCol, sortDir, columns]); + + function toggleSort(col: string) { + if (sortCol !== col) { + setSortCol(col); + setSortDir("asc"); + } else { + setSortDir((d) => (d === "asc" ? "desc" : d === "desc" ? null : "asc")); + } + } + + if (error) { + return ( + + {error} + + ); + } + + return ( + + + } + value={filter} + onChange={(e) => setFilter(e.currentTarget.value)} + w={300} + /> + + {filtered.length} de {rows.length} + + + + {loading ? ( +
+ +
+ ) : ( + + + + + {columns.map((c) => ( + + toggleSort(c)}> + + + {c === "label" ? "nombre" : c.replace(/_/g, " ")} + + + + + + ))} + + + + {filtered.map((r) => ( + open(r.id)} + > + {columns.map((c) => ( + + + {cellValue(r, c)} + + + ))} + + ))} + {filtered.length === 0 && ( + + + + Sin resultados + + + + )} + +
+
+ )} +
+ ); +} + +function SortIcon({ active, dir }: { active: boolean; dir: SortDir }) { + if (!active || dir === null) return ; + return dir === "asc" ? ( + + ) : ( + + ); +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..c95ee7f --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..098fd5e --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "types": ["node"] + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..d224f75 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig, loadEnv } from "vite"; +import react from "@vitejs/plugin-react"; + +// El backend FastAPI escucha solo en 127.0.0.1:8470 (datos sensibles del vault). +// En dev, /api se proxya al backend para que `pnpm dev` consuma los handlers +// reales sin CORS. La URL del backend puede sobrescribirse con VITE_API_BASE. +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const apiBase = env.VITE_API_BASE || "http://127.0.0.1:8470"; + return { + plugins: [react()], + build: { outDir: "dist", emptyOutDir: true }, + server: { + host: "127.0.0.1", + port: 5173, + proxy: { + "/api": apiBase, + }, + }, + }; +});