From 26b36ba7ebd1e7a5076a21dce205b455ad7947f7 Mon Sep 17 00:00:00 2001 From: markus Date: Fri, 24 Apr 2026 19:10:55 +0300 Subject: [PATCH 001/143] init filters --- .gitignore | 3 + AGENTS.md | 129 +- docs/Configuration/InlineEditing.md | 197 + docs/Export.md | 166 + docs/FeedExport.md | 187 + docs/Filters.md | 564 +++ docs/index.md | 1 + fixture/adminizerConfig.ts | 267 +- fixture/filters/customFilterHandlers.ts | 143 + fixture/helpers/seedDatabase.ts | 500 ++- fixture/index.ts | 16 +- fixture/models/Example.ts | 10 +- fixture/models/sequelize/Example.ts | 52 +- package-lock.json | 3685 ++++++++--------- package.json | 3 +- src/assets/js/components/add-form.tsx | 34 +- src/assets/js/components/add-user-form.tsx | 166 +- src/assets/js/components/list-table.tsx | 432 -- .../components/list-table/actions-column.tsx | 41 + .../list-table/filter-panel-saved-filters.tsx | 297 ++ .../js/components/list-table/filter-panel.tsx | 2837 +++++++++++++ .../list-table/group-visibility-selector.tsx | 94 + src/assets/js/components/list-table/index.ts | 6 + .../js/components/list-table/list-table.tsx | 195 + .../list-table/row-actions-dropdown.tsx | 108 + .../components/list-table/table-toolbar.tsx | 372 ++ src/assets/js/components/list-table/types.ts | 65 + .../list-table/use-filter-translations.ts | 35 + .../components/list-table/use-list-table.ts | 215 + .../media-manager/media-manager.tsx | 9 +- src/assets/js/components/nav-main.tsx | 14 +- src/assets/js/components/table/columns.tsx | 24 +- src/assets/js/components/table/data-table.tsx | 10 - .../components/table/inline-editable-cell.tsx | 245 ++ src/assets/js/components/ui/popover.tsx | 2 +- .../js/components/widgets/add-widgets.tsx | 16 +- .../js/layouts/app/app-sidebar-layout.tsx | 4 +- src/assets/js/pages/add.tsx | 2 +- src/assets/js/pages/list.tsx | 2 +- src/assets/js/pages/user-filters-list.tsx | 357 ++ src/assets/js/types/index.d.ts | 3 +- src/controllers/addUser.ts | 4 +- src/controllers/dashboard.ts | 3 +- src/controllers/exportData.ts | 403 ++ src/controllers/feed.ts | 85 + src/controllers/filter-fields/columns.ts | 145 + src/controllers/filter-fields/filterFields.ts | 196 + .../filter-fields/getAllUserFilters.ts | 147 + src/controllers/filter-fields/groups.ts | 37 + src/controllers/filter-fields/savedFilters.ts | 567 +++ src/controllers/initUser.ts | 4 +- src/controllers/inlineUpdate.ts | 168 + src/controllers/list.ts | 477 ++- src/controllers/listUserFilters.ts | 52 + src/controllers/register.ts | 4 +- src/controllers/userApiKey.ts | 56 + src/controllers/widgets/filterInfo.ts | 65 + src/helpers/accessRightsHelper.ts | 10 +- src/helpers/apiKeyHelper.ts | 9 + src/helpers/configHelper.ts | 8 + src/helpers/filterDatetimeHelper.ts | 114 + src/helpers/inertiaActionsHelper.ts | 3 +- src/helpers/inertiaAddHelper.ts | 34 + src/helpers/inertiaListHelper.ts | 32 +- src/helpers/inertiaUserHelper.ts | 7 +- src/helpers/menuHelper.ts | 9 + src/index.ts | 1 + src/interfaces/adminpanelConfig.ts | 109 + src/lib/Adminizer.ts | 26 +- src/lib/DataAccessor.ts | 4 + src/lib/datatable/NodeTable.ts | 263 -- src/lib/filters/ConditionValidator.ts | 668 +++ src/lib/filters/CustomFilterHandler.ts | 220 + src/lib/filters/FilterCustomFieldHandler.ts | 2 + src/lib/filters/FilterService.ts | 454 ++ src/lib/filters/index.ts | 12 + src/lib/history-actions/HistoryHandler.ts | 2 +- src/lib/media-manager/MediaManagerHandler.ts | 2 +- src/lib/model/AbstractModel.ts | 21 + src/lib/model/adapter/sequelize.ts | 445 +- .../AbstractNotificationService.ts | 2 - src/lib/query-builder/ModernQueryBuilder.ts | 949 +++++ src/lib/widgets/widgetHandler.ts | 122 +- src/models/FilterAP.ts | 172 + src/models/FilterColumnAP.ts | 33 + src/models/UserAP.ts | 5 + src/services/FeedService.ts | 444 ++ src/system/Router.ts | 82 + src/system/bindAuthorization.ts | 9 +- src/system/bindCustomFilterHandlers.ts | 6 + src/system/bindReqFunctions.ts | 1 + src/system/defaults.ts | 11 +- src/translations/en.json | 465 ++- src/translations/ru.json | 190 +- src/types/session.d.ts | 14 + test/boolean-debug.spec.ts | 289 ++ test/example-filters.spec.ts | 1144 +++++ test/search.spec.ts | 508 +++ 98 files changed, 17319 insertions(+), 3503 deletions(-) create mode 100644 docs/Configuration/InlineEditing.md create mode 100644 docs/Export.md create mode 100644 docs/FeedExport.md create mode 100644 docs/Filters.md create mode 100644 fixture/filters/customFilterHandlers.ts delete mode 100644 src/assets/js/components/list-table.tsx create mode 100644 src/assets/js/components/list-table/actions-column.tsx create mode 100644 src/assets/js/components/list-table/filter-panel-saved-filters.tsx create mode 100644 src/assets/js/components/list-table/filter-panel.tsx create mode 100644 src/assets/js/components/list-table/group-visibility-selector.tsx create mode 100644 src/assets/js/components/list-table/index.ts create mode 100644 src/assets/js/components/list-table/list-table.tsx create mode 100644 src/assets/js/components/list-table/row-actions-dropdown.tsx create mode 100644 src/assets/js/components/list-table/table-toolbar.tsx create mode 100644 src/assets/js/components/list-table/types.ts create mode 100644 src/assets/js/components/list-table/use-filter-translations.ts create mode 100644 src/assets/js/components/list-table/use-list-table.ts create mode 100644 src/assets/js/components/table/inline-editable-cell.tsx create mode 100644 src/assets/js/pages/user-filters-list.tsx create mode 100644 src/controllers/exportData.ts create mode 100644 src/controllers/feed.ts create mode 100644 src/controllers/filter-fields/columns.ts create mode 100644 src/controllers/filter-fields/filterFields.ts create mode 100644 src/controllers/filter-fields/getAllUserFilters.ts create mode 100644 src/controllers/filter-fields/groups.ts create mode 100644 src/controllers/filter-fields/savedFilters.ts create mode 100644 src/controllers/inlineUpdate.ts create mode 100644 src/controllers/listUserFilters.ts create mode 100644 src/controllers/userApiKey.ts create mode 100644 src/controllers/widgets/filterInfo.ts create mode 100644 src/helpers/apiKeyHelper.ts create mode 100644 src/helpers/filterDatetimeHelper.ts delete mode 100644 src/lib/datatable/NodeTable.ts create mode 100644 src/lib/filters/ConditionValidator.ts create mode 100644 src/lib/filters/CustomFilterHandler.ts create mode 100644 src/lib/filters/FilterCustomFieldHandler.ts create mode 100644 src/lib/filters/FilterService.ts create mode 100644 src/lib/filters/index.ts create mode 100644 src/lib/query-builder/ModernQueryBuilder.ts create mode 100644 src/models/FilterAP.ts create mode 100644 src/models/FilterColumnAP.ts create mode 100644 src/services/FeedService.ts create mode 100644 src/system/bindCustomFilterHandlers.ts create mode 100644 src/types/session.d.ts create mode 100644 test/boolean-debug.spec.ts create mode 100644 test/example-filters.spec.ts create mode 100644 test/search.spec.ts diff --git a/.gitignore b/.gitignore index a794e40a..963dbe99 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ logs .patches codex.patch .env +QWEN.md +.qwen/ +TODO diff --git a/AGENTS.md b/AGENTS.md index a27ce48f..f2f6dcd4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,121 @@ -### Agent Instructions - -* **Install Required Modules**: Ensure all necessary dependencies are installed before proceeding. -* **Run the Project**: Launch the development server to confirm that the application starts correctly. -* **Execute the Build Process**: Run the build to verify that everything compiles without errors. -* **Use TSX for Execution**: Follow the TSX-based startup method when running the project. -* **Write Documentation Immediately**: All relevant documentation must be added to the `docs/` directory as you work. -* **Follow Best Practices for Naming and Comments**: All variable names, function names, and comments must follow clean code principles and be written in English. +# Adminizer - Project Context + +## Project Overview + +Adminizer is a framework-agnostic admin panel for Node.js. +It includes a TypeScript backend and a React frontend, and can be integrated into server-side applications with different architectures. + +## Key Characteristics + +- Type: Node.js library/module +- Backend: TypeScript (ESM) +- Frontend: React + TypeScript + Vite +- UI stack: Radix UI, Tailwind CSS v4, shadcn/ui style components +- ORM support: Sequelize (active), Waterline (legacy) +- License: MIT + +## Current Development Focus + +- Primary ORM: Sequelize +- Waterline is legacy and should not receive new features +- For model adapter changes, prioritize `src/lib/model/adapter/sequelize.ts` + +## Directory Structure + +```text +adminizer/ +|-- src/ # Main source code +| |-- assets/ # Frontend (React/TSX/CSS) +| |-- controllers/ # Express controllers +| |-- helpers/ # Utilities +| |-- interfaces/ # Types/interfaces +| |-- lib/ # Core modules +| |-- migrations/ # DB migrations +| |-- models/ # Internal models +| |-- policies/ # Access policies +| |-- system/ # System wiring +| `-- index.ts # Main entry +|-- fixture/ # Demo app for local development +|-- modules/ # Extendable modules +|-- controls/ # Custom controls +|-- docs/ # Documentation +|-- test/ # Vitest tests +|-- translations/ # i18n resources +`-- dist/ # Build output +``` + +## Build and Run Commands + +### Development + +```bash +npm run dev +npm run dev:no-seed +npm run dev:waterline +npm run dev:cors +``` + +### Start + +```bash +npm run start +npm run start:seed +npm run start:waterline +``` + +### Build + +```bash +npm run build +npm run build:backend +npm run compile:backend +npm run compile:ui +npm run build:assets +npm run build:module +``` + +### Tests + +```bash +npm run test +npm run test:watch +``` + +## Environment Variables + +See `.env.example`. +Important variables include: + +- `OPENAI_API_KEY` +- `ADMIN_LOGIN` +- `ADMIN_PASS` +- `JWT_SECRET` +- `AP_PASSWORD_SALT` +- `ORM` +- `NO_SEED_DATA` +- `VITE_ENV` + +## Development Conventions + +- Use TypeScript and keep identifiers in English +- Keep comments concise and in English +- Follow existing project architecture and naming style +- Use axios for frontend HTTP requests (CSRF handling is already integrated) + +## Rules for AI Agents in This Repository + +1. Do not create commits unless the user explicitly asks. +2. Write commit messages in English when commits are requested. +3. Reply in Russian in chat communication. +4. Do not start the development server unless the user explicitly asks. +5. After code changes, run `npm run compile:backend` by default. +6. Update documentation only when the user explicitly asks. +7. Do not translate technical artifacts (paths, keys, code identifiers). + +## Completion Checklist + +Before finishing a coding task: + +1. Ensure changed code is consistent with Sequelize-first direction. +2. Run `npm run compile:backend` (unless user requested a different verification flow). +3. Report what was changed and any checks that were not run. diff --git a/docs/Configuration/InlineEditing.md b/docs/Configuration/InlineEditing.md new file mode 100644 index 00000000..fd74ec34 --- /dev/null +++ b/docs/Configuration/InlineEditing.md @@ -0,0 +1,197 @@ +# Inline Editing in List View + +Adminizer supports inline editing of fields directly in the list view. This allows administrators to quickly update values without navigating to the edit form. + +## Overview + +When inline editing is enabled for a field, users can double-click on a cell value in the table to edit it directly. Changes are saved via an API call and the table updates instantly without a full page reload. + +## Enabling Inline Editing + +To enable inline editing for a field, set the `inlineEditable` flag in the field configuration: + +```ts +models: { + Example: { + title: 'Example', + model: 'example', + fields: { + title: { + title: 'Title', + type: 'string', + inlineEditable: true + }, + isActive: { + title: 'Active', + type: 'boolean', + inlineEditable: true + }, + price: { + title: 'Price', + type: 'number', + inlineEditable: true, + inlineValidation: { + min: 0, + max: 99999 + } + } + } + } +} +``` + +## Supported Field Types + +Inline editing is only available for the following field types: + +| Type | Control | +|------|---------| +| `string` | Text input | +| `integer` | Number input | +| `number` | Number input | +| `float` | Number input | +| `email` | Email input | +| `range` | Number input with min/max | +| `boolean` | Checkbox | + +If `inlineEditable: true` is set on an unsupported field type (e.g., `text`, `date`, `json`), the flag is **ignored** and the field remains read-only. + +## Conditions for Editing + +A field is editable in the list view only when **all** of the following conditions are met: + +| Condition | Description | +|-----------|-------------| +| `inlineEditable: true` | The flag must be explicitly set | +| No `displayModifier` | Fields with `displayModifier` are always read-only (priority) | +| Supported field type | The field type must be in the allowed list above | + +## Validation + +You can define validation rules for inline editing using the `inlineValidation` option: + +```ts +fieldName: { + title: 'Field Name', + type: 'string', + inlineEditable: true, + inlineValidation: { + minLength: 3, + maxLength: 100, + pattern: '^[a-zA-Z]+$', + validate: (value) => value.trim().length > 0 || 'Value cannot be empty' + } +} +``` + +### Validation Options + +| Option | Description | Applicable Types | +|--------|-------------|------------------| +| `minLength` | Minimum string length | `string`, `email` | +| `maxLength` | Maximum string length | `string`, `email` | +| `min` | Minimum value | `integer`, `number`, `float`, `range` | +| `max` | Maximum value | `integer`, `number`, `float`, `range` | +| `pattern` | Regex pattern for validation | `string`, `email` | +| `validate` | Custom validation function | All types | + +## User Interaction + +### Text and Number Fields + +1. **Double-click** on the cell value to start editing +2. An input field appears with the current value selected +3. Press **Enter** or click outside to save +4. Press **Escape** to cancel + +### Boolean Fields + +1. **Double-click** on the cell to start editing +2. A checkbox appears +3. Toggle the checkbox to change the value +4. Click outside the checkbox to save +5. Press **Escape** to cancel + +## API Endpoint + +Inline editing uses the following API endpoint: + +``` +PATCH /adminizer/model/:name/inline/:id +``` + +**Request body:** +```json +{ + "field": "fieldName", + "value": "newValue" +} +``` + +**Response:** +```json +{ + "success": true, + "data": { + "id": "record-id", + "field": "fieldName", + "value": "newValue" + } +} +``` + +## Example Configuration + +```ts +import { AdminpanelConfig } from "adminizer"; + +const config: AdminpanelConfig = { + routePrefix: "/admin", + models: { + Product: { + title: 'Products', + model: 'product', + fields: { + name: { + title: 'Name', + type: 'string', + inlineEditable: true, + inlineValidation: { + minLength: 2, + maxLength: 200 + } + }, + price: { + title: 'Price', + type: 'number', + inlineEditable: true, + inlineValidation: { + min: 0, + max: 999999 + } + }, + active: { + title: 'Active', + type: 'boolean', + inlineEditable: true + }, + email: { + title: 'Contact Email', + type: 'email', + inlineEditable: true, + inlineValidation: { + pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$' + } + }, + // This field has displayModifier — inlineEditable will be ignored + displayName: { + title: 'Display Name', + type: 'string', + inlineEditable: true, // ← Ignored due to displayModifier + displayModifier: (v) => v?.toUpperCase() + } + } + } + } +}; +``` diff --git a/docs/Export.md b/docs/Export.md new file mode 100644 index 00000000..a2a27652 --- /dev/null +++ b/docs/Export.md @@ -0,0 +1,166 @@ +# Export Data + +Adminizer supports exporting filtered data from model list views in three formats: **JSON**, **CSV**, and **XLSX**. + +## Overview + +The export feature allows users to download all records (respecting the currently active filter) from any model list page. Export is available without pagination limits. + +## Supported Formats + +| Format | Description | Use Case | +|--------|-------------|----------| +| **JSON** | Raw JSON array of objects | API integration, data backup, programmatic processing | +| **CSV** | Comma-separated values with UTF-8 BOM | Opening in Excel, Google Sheets, data analysis | +| **XLSX** | Native Excel spreadsheet with auto-sized columns | Business reports, presentations | + +## Backend API + +### Endpoint + +``` +POST /adminizer/model/:modelName/export +``` + +### Request Body + +```json +{ + "format": "json|csv|xlsx", + "filterId": "optional-filter-id" +} +``` + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `format` | string | No | Export format: `json`, `csv`, or `xlsx`. Defaults to `json`. | +| `filterId` | string | No | ID of a saved filter to apply. If omitted, exports all records. Use `temporary` for session-based temporary filters. | +| `selectedFields` | string[] | No | Array of field names to include in export. If omitted, uses filter's column config or all fields. | + +### Response + +- **Success:** File download with appropriate `Content-Type` and `Content-Disposition` headers +- **400:** Invalid format or missing required data +- **401:** Unauthorized (if auth is enabled) +- **403:** Forbidden (insufficient permissions) +- **404:** Model not found +- **500:** Export failed (e.g., xlsx library not installed) + +### Response Headers + +``` +Content-Type: application/json (for JSON) +Content-Type: text/csv; charset=utf-8 (for CSV) +Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet (for XLSX) +Content-Disposition: attachment; filename="modelname_export_1234567890.json" +``` + +## Frontend Usage + +The export button appears automatically in the table toolbar when the page loads. + +### How to Export + +1. Navigate to any model list page +2. (Optional) Apply a saved filter to narrow down results +3. Click the **Экспорт** (Export) button in the toolbar +4. Select the desired format: JSON, CSV, or XLSX +5. The file downloads automatically + +### Filter Integration + +- If a **saved filter** is active, only filtered records are exported +- If a **temporary filter** is applied, it is used for the export +- If **no filter** is active, all records are exported + +## Implementation Details + +### Controller: `src/controllers/exportData.ts` + +The export controller: +1. Validates the user has `read-{modelName}-model` permission +2. Loads the saved filter (if `filterId` is provided) +3. Converts datetime conditions from HTML5 format to UTC +4. Applies custom column configuration from filter +5. Executes a query with no pagination (large limit) +6. Formats data based on field types (relationships, booleans, dates, JSON) +7. Generates the file and sends it as a download + +### Route Registration: `src/system/Router.ts` + +```typescript +import _exportData from "../controllers/exportData"; + +adminizer.app.post(`${baseRoute}/export`, adminizer.policyManager.bindPolicies(policies, _exportData)); +``` + +### Frontend: `src/assets/js/components/list-table/table-toolbar.tsx` + +The export dropdown: +- Uses `axios` with `responseType: 'blob'` for binary download +- Reads `filterId` from URL to preserve filter state +- Extracts filename from `Content-Disposition` header +- Shows toast notifications for success/error + +## Dependencies + +| Package | Purpose | Required | +|---------|---------|----------| +| `xlsx` | XLSX file generation | Yes (for XLSX export) | + +Install xlsx: +```bash +npm install xlsx +``` + +## CSV Encoding + +CSV files include a **UTF-8 BOM** (`\uFEFF`) prefix to ensure proper character encoding when opened in Microsoft Excel. + +## Data Formatting + +The export applies human-readable transformations: +- **Boolean:** `true` → `Yes`, `false` → `No` +- **Relationships:** Display field (title/name/id) is used +- **Has-Many:** Comma-separated list of display values +- **JSON:** Pretty-printed with indentation (2 spaces) +- **Dates:** ISO 8601 format (`YYYY-MM-DDTHH:mm:ss.sssZ`) +- **Arrays:** Comma-separated values + +## Security + +- Export respects the same **access rights** as the list view +- Only users with `read-{modelName}-model` permission can export +- Filter access control is enforced (private filters only accessible by owner) +- Datetime conditions are validated and converted to prevent injection + +## Extending + +To add a new export format: + +1. Add the format to the `ExportFormat` type in `exportData.ts` +2. Create a new `send{Format}Export` function +3. Add a case to the switch statement in the main controller +4. Add a menu item in `table-toolbar.tsx` + +Example for PDF export: + +```typescript +// In exportData.ts +async function sendPdfExport(res: ResType, data: Record[], modelName: string) { + // Generate PDF... + return res.send(buffer); +} + +// In the switch +case 'pdf': + return sendPdfExport(res, exportData, entity.name); +``` + +```tsx +// In table-toolbar.tsx + handleExport('pdf')}> + PDF + PDF документ + +``` diff --git a/docs/FeedExport.md b/docs/FeedExport.md new file mode 100644 index 00000000..7ab26da2 --- /dev/null +++ b/docs/FeedExport.md @@ -0,0 +1,187 @@ +# Feed Export API + +Публичный API для экспорта данных фильтров в форматах JSON и XML (Atom). Позволяет внешним системам получать отфильтрованные данные по ссылке без авторизации. + +## Обзор + +Фильтры с включённым API-доступом генерируют уникальный `apiKey`, который можно использовать для получения данных в формате: +- **JSON** — для программной интеграции +- **XML (Atom)** — для RSS-подобных фидов (Яндекс.Маркет, новостные агрегаторы и т.д.) + +## Как включить API для фильтра + +1. Откройте список записей нужной модели +2. Нажмите кнопку **«Фильтры»** → выберите существующий фильтр для редактирования +3. Внизу диалога найдите секцию **«API-доступ (фид)»** +4. Включите переключатель +5. Нажмите **«Показать»** для просмотра ссылок +6. Скопируйте URL для JSON или XML + +### Генерация ключа + +- При первом включении API автоматически генерируется `apiKey` (UUID v4) +- Кнопка **«Перегенерировать»** создаёт новый ключ (старая ссылка перестаёт работать) +- Ключ хранится в базе данных в модели `FilterAP` + +## Эндпоинты + +### JSON экспорт + +``` +GET /adminizer/api/feed/{apiKey}.json +``` + +**Ответ:** +```json +{ + "feed": { + "title": "Название фильтра", + "description": "Описание фильтра", + "modelName": "Example", + "generatedAt": "2026-04-06T12:00:00.000Z", + "totalItems": 42, + "apiKey": "550e8400-e29b-41d4-a716-446655440000", + "items": [ + { + "id": 1, + "name": "Запись 1", + "status": "active", + "createdAt": "2026-04-01T10:00:00.000Z" + } + ] + } +} +``` + +### XML (Atom) экспорт + +``` +GET /adminizer/api/feed/{apiKey}.xml +``` + +**Ответ:** +```xml + + + /adminizer/api/feed/550e8400-e29b-41d4-a716-446655440000 + Название фильтра + Описание фильтра + 2026-04-06T12:00:00.000Z + + Adminizer + + 1 + Запись 1 + 2026-04-01T10:00:00.000Z + Запись 1 + + + id1 + nameЗапись 1 + + ]]> + + {"id":1,"name":"Запись 1"} + + +``` + +## Примеры использования + +### curl + +```bash +# JSON +curl https://example.com/adminizer/api/feed/YOUR_API_KEY.json + +# XML +curl https://example.com/adminizer/api/feed/YOUR_API_KEY.xml +``` + +### JavaScript / Fetch + +```javascript +const response = await fetch('/adminizer/api/feed/YOUR_API_KEY.json'); +const data = await response.json(); +console.log(data.feed.items); +``` + +### Python + +```python +import requests + +url = "https://example.com/adminizer/api/feed/YOUR_API_KEY.json" +response = requests.get(url) +data = response.json() + +for item in data['feed']['items']: + print(item['name']) +``` + +### RSS-агрегатор + +Добавьте ссылку `https://example.com/adminizer/api/feed/YOUR_API_KEY.xml` в ваш RSS-ридер (Feedly, Inoreader и т.д.) + +## Безопасность + +- **apiKey** — это единственный секретный ключ. Любой, кто знает URL, может получить данные +- **Не используйте** для конфиденциальных данных без дополнительной защиты +- При компрометации ключа — перегенерируйте его в UI фильтра +- API не проверяет права доступа — данные отдаются всем, кто знает ключ + +## Ошибки + +| Код | Описание | +|-----|----------| +| `400` | Неверный формат. Поддерживаются только `json` и `xml` | +| `404` | Фильтр не найден или API-доступ отключён | +| `500` | Внутренняя ошибка сервера | + +**Пример ошибки:** +```json +{ + "error": "Filter not found or API access is disabled", + "message": "Check your apiKey or ensure API access is enabled for this filter" +} +``` + +## Архитектура + +### Файлы + +| Файл | Описание | +|------|----------| +| `src/services/FeedService.ts` | Сервис генерации фидов (JSON + XML) | +| `src/controllers/feed.ts` | HTTP-контроллер | +| `src/system/Router.ts` | Регистрация роутов | +| `src/controllers/filter-fields/savedFilters.ts` | Генерация apiKey при сохранении | +| `src/assets/js/components/list-table/filter-panel-save-dialog.tsx` | UI управления API | + +### Формат данных + +Данные форматируются так же, как в обычном экспорте (`exportData.ts`): +- Связи — по title field +- Даты — ISO 8601 +- Булевы — "Yes"/"No" +- Массивы — через запятую +- JSON — сериализованная строка + +## Кастомизация + +### Изменить структуру JSON + +Отредактируйте метод `generateJsonFeed()` в `FeedService.ts`: + +```typescript +async generateJsonFeed(filter: FilterAP): Promise { + const { records, fields, modelName } = await this.fetchFilterData(filter); + // Ваша кастомная логика + return { /* ваша структура */ }; +} +``` + +### Изменить XML формат + +Отредактируйте метод `buildAtomXml()` в `FeedService.ts`. diff --git a/docs/Filters.md b/docs/Filters.md new file mode 100644 index 00000000..94e6707c --- /dev/null +++ b/docs/Filters.md @@ -0,0 +1,564 @@ +# Filters in Adminizer + +The filtering system allows creating, saving, and applying filters to model data. Filters support private/public access, condition grouping, sorting, and column display customization. + +--- + +## Table of Contents + +1. [Architecture](#architecture) +2. [Data Models](#data-models) +3. [Filter Operators](#filter-operators) +4. [Filter Visibility](#filter-visibility) +5. [Backend API](#backend-api) +6. [Frontend Components](#frontend-components) +7. [Temporary Filters](#temporary-filters) +8. [Column Management](#column-management) +9. [Validation and Security](#validation-and-security) +10. [Filter Migration](#filter-migration) +11. [Custom Field Handlers](#custom-field-handlers) +12. [Public Feed API](#public-feed-api) +13. [Programmatic Filter Creation](#programmatic-filter-creation) + +--- + +## Architecture + +The filter system consists of the following components: + +| Component | Location | Description | +|-----------|----------|-------------| +| `FilterAP` | `src/models/FilterAP.ts` | Filter data model | +| `FilterColumnAP` | `src/models/FilterColumnAP.ts` | Column configuration model | +| `FilterService` | `src/lib/filters/FilterService.ts` | Main filtering service | +| `FilterBuilder` | `src/lib/filters/FilterBuilder.ts` | Fluent API for filter creation | +| `FilterMigrator` | `src/lib/filters/FilterMigrator.ts` | Filter schema migration | +| `ConditionValidator` | `src/lib/filters/ConditionValidator.ts` | Condition validation | +| `CustomFieldHandler` | `src/lib/filters/CustomFieldHandler.ts` | Complex field handlers | +| `ModernQueryBuilder` | `src/lib/query-builder/ModernQueryBuilder.ts` | Query building | + +--- + +## Data Models + +### FilterAP + +The main filter model: + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Filter UUID | +| `name` | `string` | Filter name | +| `description` | `string?` | Description | +| `modelName` | `string` | Model name | +| `slug` | `string` | Unique identifier | +| `conditions` | `FilterCondition[]` | Filter conditions | +| `sortField` | `string?` | Sort field | +| `sortDirection` | `'ASC' \| 'DESC'?` | Sort direction | +| `selectedFields` | `string[]?` | Selected display fields | +| `ownerId` | `number?` | Owner ID | +| `visibility` | `FilterVisibility` | Visibility type | +| `groupIds` | `number[]?` | Group IDs (for `groups`) | +| `apiKey` | `string?` | Public API key | +| `apiEnabled` | `boolean` | Public API enabled | +| `icon` | `string?` | Icon | +| `color` | `string?` | Color | +| `metadata` | `object?` | Additional metadata | +| `schemaVersion` | `number` | Schema version (current: `1`) | + +### FilterCondition + +A single filter condition structure: + +```typescript +interface FilterCondition { + id: string; // Condition UUID + field?: string; // Field name + operator?: FilterOperator; // Operator + value?: any; // Value + + // Nested conditions (groups) + logic?: 'AND' | 'OR' | 'NOT'; // Group logic + children?: FilterCondition[]; // Child conditions + + // For relations + relation?: string; // Relation name + relationField?: string; // Relation field + + // Custom handlers + customHandler?: string; // Handler ID + customHandlerParams?: any; // Handler parameters + + // Raw SQL + rawSQL?: string; // Raw SQL condition + rawSQLParams?: any[]; // SQL parameters +} +``` + +### Relation Conditions (Sequelize) + +Relation conditions are supported in the filter UI for Sequelize models. + +- Supported relation types: `association`, `association-many` +- Condition format: `relation + relationField + operator + value` +- Allowed operators for relation conditions: `eq`, `neq` +- Relation field picker includes only simple fields from the related model: + - included: string, text, number, integer, float, boolean, date, datetime, select, select-many, etc. + - excluded: `association`, `association-many`, `json`, `jsoneditor`, `object`, `array` +- Waterline relation filtering is not supported in filter conditions. + +### FilterColumnAP + +Column configuration for a filter: + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | UUID | +| `filterId` | `string` | Related filter ID | +| `columnConfigs` | `object` | Per-column configuration (visibility, order, width, editability) | + +--- + +## Filter Operators + +| Operator | Description | Applicable Types | +|----------|-------------|------------------| +| `eq` | Equals | All | +| `neq` | Not equals | All | +| `gt` | Greater than | number, integer, float, date, datetime | +| `gte` | Greater than or equal | number, integer, float, date, datetime | +| `lt` | Less than | number, integer, float, date, datetime | +| `lte` | Less than or equal | number, integer, float, date, datetime | +| `like` | LIKE %value% | string, text | +| `ilike` | Case-insensitive LIKE | string, text | +| `startsWith` | LIKE value% | string, text | +| `endsWith` | LIKE %value | string, text | +| `in` | IN (list) | All | +| `notIn` | NOT IN (list) | All | +| `between` | BETWEEN val1 AND val2 | number, integer, float, date, datetime | +| `isNull` | IS NULL | All | +| `isNotNull` | IS NOT NULL | All | +| `regex` | Regular expression | string, text | +| `custom` | Custom handler | Depends on handler | + +--- + +## Filter Visibility + +### Visibility Types + +| Type | Description | Who can see | +|------|-------------|-------------| +| `private` | Private filter | Owner only | + +--- + +## Recent UI Behavior Updates + +- Saved filters are now auto-applied immediately after a successful save in the filter dialog. +- Updated saved filters are also auto-applied immediately after saving on the edit slide. +- The auto-apply step reuses the same frontend flow as selecting a saved filter from the saved filters list. +- This keeps URL state (`filterId`) and filter panel state synchronized after save. +| `groups` | Group filter | Users in selected groups | +| `public` | Public filter | All model users | +| `system` | System filter | All (not editable) | + +### Permissions + +| Role | Creation | Edit | Delete | +|------|----------|------|--------| +| Admin (`isAdministrator: true`) | Any visibility | All filters | All filters | +| Regular user | `private` only | Own `private` only | Own `private` only | + +### Notes + +- When editing someone else's filter, visibility is preserved (cannot be changed) +- Group filters are visible to all group members, but only admins can edit them +- System filters are created programmatically and are not editable by users + +--- + +## Backend API + +### Get filter fields + +``` +GET /adminizer/model/:model/filter-fields +``` + +Returns available fields for filtering on a model. + +Notes for relation fields: + +- System fields (`id`, `ownerId`, `updatedAt`) are excluded. +- `modelFilters..excludeFromFilters` is applied before field exposure. +- If `includeInFilters` is set, it acts as a whitelist over the already filtered set. +- Relation filters are exposed only for Sequelize. +- Custom filters are exposed only for Sequelize fields with `customFilter` in field config. +- For custom fields, API returns additional metadata: + - `isCustomFilter: true` + - `customFilterHandlerId` + - `customFilterLabel` (optional) + +### Saved filters + +``` +GET /adminizer/model/:model/saved-filters +``` + +Returns the list of saved filters for a model, including owner info and result counts. + +### Create/update filter + +``` +POST /adminizer/model/:model/filter +``` + +**Request body:** +```json +{ + "name": "Name", + "description": "Description", + "conditions": [...], + "sortField": "title", + "sortDirection": "ASC", + "visibility": "private", + "groupIds": [1, 2], + "icon": "filter", + "color": "blue", + "apiKey": "optional-key", + "apiEnabled": false +} +``` + +### Delete filter + +``` +DELETE /adminizer/model/:model/filter/:id +``` + +### Apply temporary filter + +``` +POST /adminizer/model/:model/filter/apply +``` + +**Request body:** +```json +{ + "name": "Temporary filter", + "conditions": [...], + "temporary": true +} +``` + +Stores the filter in the session for temporary use. + +### User groups + +``` +GET /adminizer/groups +``` + +Returns all system groups (admin only). + +### Model columns + +``` +GET /adminizer/model/:model/columns +GET /adminizer/model/:model/filter/:filterId/columns +POST /adminizer/model/:model/filter/:filterId/columns +``` + +Manage column configuration for a filter. + +### Public feed + +``` +GET /adminizer/api/feed/:apiKey +``` + +Public API for exporting filter data in Atom/JSON format. + +--- + +## Frontend Components + +### Core Components + +| Component | File | Description | +|-----------|------|-------------| +| `FilterPanel` | `filter-panel.tsx` | Main filter panel component (~2375 lines). Manages dialogs for creating/editing/applying filters. | +| `SavedFiltersList` | `filter-panel-saved-filters.tsx` | Saved filters list with icons, visibility badges, and result counts. | +| `GroupVisibilitySelector` | `group-visibility-selector.tsx` | Group picker for group filters. | +| `TableToolbar` | `table-toolbar.tsx` | Table toolbar with filter dialog button. | + +### UI Flow + +1. **Open dialog** — "Filters" button in toolbar opens `FilterDialog` +2. **Select saved filter** — clicking a filter applies it +3. **Create new filter** — "Add filter" button → `FilterConditionsDialog` +4. **Configure conditions** — add/remove conditions, select fields/operators/values +5. **Apply** — filter is applied to data via temporary `filterId` +6. **Save** — "Save filter" button → `SaveFilterMetaDialog` → save to DB + +### Supported Value Types + +- **Strings** — text input +- **Numbers** — numeric input +- **Boolean** — Yes/No toggle +- **Date/Time** — date picker +- **Select** — dropdown with model options +- **Multi-select** — multiple selection +- **JSON fields** — via custom field handlers + +--- + +## Temporary Filters + +Temporary filters are not saved to the database; they are stored in the user's session: + +1. User creates a filter with conditions +2. Filter is applied to data (`filterId=temporary`) +3. Conditions are stored in `sessionStorage` for the backend +4. A "Save filter" button appears in the toolbar +5. Clicking opens the metadata save dialog (name, description, icon, color, visibility) +6. After saving, the filter appears in the saved filters list + +**Limitations:** +- Temporary filters do not persist between sessions +- You must click "Save filter" to persist the filter + +--- + +## Column Management + +Each saved filter can have its own column configuration: + +- **Visibility** — show/hide column +- **Order** — column display order +- **Width** — fixed column width +- **Editability** — inline cell editing enabled/disabled + +Configuration is stored in `FilterColumnAP` and applied when displaying the table. + +--- + +## Validation and Security + +### ConditionValidator + +Validates filter conditions: + +- Field existence in the model +- Operator validity per field type +- Value type and format +- Security limits: + - Maximum nesting depth + - Maximum conditions per group + - Maximum `IN` values + - Maximum string length +- SQL injection detection for raw SQL conditions + +### Security Limits + +| Parameter | Value | +|-----------|-------| +| Maximum nesting depth | 5 | +| Maximum conditions per group | 20 | +| Maximum `IN` values | 1000 | +| Maximum string length | 10000 | + +--- + +## Filter Migration + +`FilterMigrator` ensures backward compatibility when the filter schema changes: + +- Current schema version: `1` +- Automatic migration of old versions +- Post-migration condition validation +- Condition sanitization (remove invalid conditions) +- Check for removed model fields + +--- + +## Custom Field Handlers + +`CustomFieldHandler` allows registering handlers for complex fields: + +- JSON fields +- Computed fields +- Full-text search +- Geo-spatial queries + +### Config-based loading (recommended) + +Use `filters.customHandlersPath` in `adminConfig` to load one or many handler files on startup: + +```typescript +{ + filters: { + customHandlersPath: [ + 'fixture/filters/customFilterHandlers.ts' + ] + } +} +``` + +Each file must export a default function. Adminizer calls it during init. +If a file is missing or has invalid export, a warning is logged and startup continues. + +### Registering a handler + +```typescript +import { CustomFieldHandler } from 'adminizer'; + +CustomFieldHandler.register('myHandler', { + buildCondition: (operator, value, params) => { + // Returns criteria/rawSQL/inMemory + return { criteria: { title: value } }; + }, + validate: (value) => { + // Value validation, throw error if invalid + if (!value) throw new Error('Value required'); + } +}); +``` + +### Field-level config + +Enable custom filter mode on a field: + +```typescript +fields: { + json: { + type: 'jsoneditor', + customFilter: { + handlerId: 'Example.json', + label: 'Custom filtering' + } + } +} +``` + +UI behavior for custom filter fields: + +- Condition selector shows only one option (`Custom filtering`) +- A single value input is rendered +- Saved condition format: + +```json +{ + "id": "cond-1", + "customHandler": "Example.json", + "operator": "custom", + "value": "search text" +} +``` + +--- + +## Public Feed API + +Filters with `apiEnabled: true` and a configured `apiKey` are publicly accessible: + +- **Atom XML** — for RSS readers +- **JSON** — for programmatic access + +### Configuration + +```typescript +{ + name: "My public filter", + modelName: "Example", + conditions: [...], + apiKey: "my-secret-key", + apiEnabled: true +} +``` + +### Access + +``` +GET /adminizer/api/feed/my-secret-key +``` + +Public feeds bypass auth and use the `apiKey` for access control. + +--- + +## Programmatic Filter Creation + +### FilterBuilder (Fluent API) + +```typescript +import { FilterBuilder } from 'adminizer'; + +await FilterBuilder + .forModel('Example') + .named('Active records') + .where('sort', 'eq', true) + .andWhere('title', 'ilike', 'test') + .sortBy('createdAt', 'DESC') + .asPublic() + .save(user); +``` + +### Registration via config + +```typescript +import { FilterBuilder, FilterDefinition } from 'adminizer'; + +const filters: FilterDefinition[] = [ + { + name: 'Active', + modelName: 'Example', + description: 'Shows only active records', + conditions: [ + { id: '1', field: 'sort', operator: 'eq', value: true } + ], + visibility: 'system', + icon: 'check', + color: 'green' + } +]; + +FilterBuilder.registerFilters(filters); +``` + +### Lifecycle Hooks + +```typescript +FilterBuilder.on('beforeCreate', async (filter, context) => { + console.log('Creating filter:', filter.name); + return filter; +}); + +FilterBuilder.on('afterCreate', async (filter, context) => { + console.log('Created filter:', filter.id); +}); +``` + +Available hooks: `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate`, `beforeDelete`, `afterDelete`, `beforeExecute`, `afterExecute`. + +--- + +## Model Integration + +To enable filters in model configuration: + +```typescript +{ + models: { + Example: { + model: ExampleModel, + filtersEnabled: true, + modelFilters: { + excludeFromFilters: ['disabled_text', 'editor'], // Exclude fields + includeInFilters: ['title', 'sort'], // Whitelist + } + } + } +} +``` diff --git a/docs/index.md b/docs/index.md index fb63d75d..8bcf10d1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,6 +9,7 @@ * [General Settings](Configuration/General.md) * [Model Definitions](Configuration/Models.md) * [Field Options](Configuration/Fields.md) +* [Inline Editing](Configuration/InlineEditing.md) * [Custom Components](Configuration/CustomComponents.md) ## Other diff --git a/fixture/adminizerConfig.ts b/fixture/adminizerConfig.ts index d4331480..4f839d4b 100644 --- a/fixture/adminizerConfig.ts +++ b/fixture/adminizerConfig.ts @@ -7,7 +7,7 @@ const models: AdminpanelConfig["models"] = { title: 'Test model', model: 'test', displayName: 'title', - userAccessRelation: 'owner', + // userAccessRelation: 'owner', fields: { createdAt: false, updatedAt: false, @@ -46,37 +46,45 @@ const models: AdminpanelConfig["models"] = { title: 'All controls', model: 'example', displayName: 'description', - userAccessRelation: 'owner', - tools: [ - { - id: '1', - link: `https://google.com`, - type: 'blank', - title: 'Some new action', - icon: 'reorder', - }, - { - id: '2', - link: `${routePrefix}/form/global`, - type: 'self', - title: 'Form example', - icon: 'payment', - }, - { - id: '3', - link: 'https://google.com', - type: 'blank', - title: 'Form example from file Form example from file', - icon: 'touch_app', - } - ], + filters: { + enabled: true, + excludeFromFilters: ['createdAt', 'id', 'ownerId', 'testRelation', 'testRelationExample'], + }, + // userAccessRelation: 'owner', + // tools: [ + // { + // id: '1', + // link: `https://google.com`, + // type: 'blank', + // title: 'Some new action', + // icon: 'reorder', + // }, + // { + // id: '2', + // link: `${routePrefix}/form/global`, + // type: 'self', + // title: 'Form example', + // icon: 'payment', + // }, + // { + // id: '3', + // link: 'https://google.com', + // type: 'blank', + // title: 'Form example from file Form example from file', + // icon: 'touch_app', + // } + // ], fields: { - createdAt: false, + createdAt: { + title: 'created At', + type: 'string', + }, updatedAt: false, title: { title: 'Title', type: 'string', - required: true + required: true, + inlineEditable: true }, description: { title: 'Textarea', @@ -86,7 +94,8 @@ const models: AdminpanelConfig["models"] = { }, sort: { type: 'boolean', - title: 'Boolean' + title: 'Boolean', + inlineEditable: true }, disabled_text: { title: 'Disabled', @@ -100,7 +109,8 @@ const models: AdminpanelConfig["models"] = { options: { min: 10, max: 80 - } + }, + inlineEditable: true }, select: { title: 'Select', @@ -135,6 +145,7 @@ const models: AdminpanelConfig["models"] = { number: { title: 'Number', type: 'number', + inlineEditable: true }, color: { title: 'color', @@ -145,7 +156,11 @@ const models: AdminpanelConfig["models"] = { type: 'week', }, json: { - type: 'jsoneditor' + type: 'jsoneditor', + customFilter: { + handlerId: 'Example.json', + label: 'Custom filtering' + } }, tui: { type: 'tuieditor', @@ -173,6 +188,9 @@ const models: AdminpanelConfig["models"] = { datatable: { title: 'Price', type: 'table', + customFilter: { + handlerId: 'Example.datatable' + }, options: { config: { dataSchema: { name: null, footage: null, price: null }, @@ -190,10 +208,6 @@ const models: AdminpanelConfig["models"] = { isIn: ['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], type: 'select-many' }, - checkboxes: { - title: 'Checkboxes', - isIn: ['one', 'two', 'three'] - }, editor: { title: 'Editor', type: 'wysiwyg', @@ -227,7 +241,7 @@ const models: AdminpanelConfig["models"] = { } } }, - testRelation: { + testRelationExample: { title: 'Test one association', displayModifier: function (data) { return data?.title; @@ -236,96 +250,108 @@ const models: AdminpanelConfig["models"] = { }, tests: { title: 'One to many association', - displayModifier: function (data) { - return data?.title; + displayModifier: function (data: any) { + if (Array.isArray(data)) { + return data + .map((item: any) => item?.title) + .filter(Boolean) + .join(', '); + } + return data?.title || ''; + }, + disabled: false + }, + owner: { + title: 'Owner', + displayModifier: function (data: any) { + return data?.login || ''; }, disabled: false }, }, list: { fields: { - json: false, - tui: false, - geojson: false, - week: false, - color: false, - range: false, - date: false, - month: false, - selectMany: false, - select: false, - dateTime: false, - testRelation: false, - tests: false, - price: false, - code: false, - datatable: false - }, - actions: { - global: [ - { - id: "1", - link: 'https://google.com', - type: 'blank', - title: 'Google', - icon: 'insert_link' - }, { - id: "2", - link: 'https://google.com', - type: 'blank', - title: 'Google', - icon: 'insert_link' - }, { - id: "3", - link: 'https://google.com', - type: 'blank', - title: 'Google', - icon: 'insert_link' - }, { - id: "4", - link: 'https://google.com', - type: 'blank', - title: 'Google', - icon: 'insert_link' - }, { - id: "5", - link: `${routePrefix}/form/global`, - type: 'self', - title: 'Form', - icon: 'insert_link' - }, - ], - inline: [ - { - id: "1", - link: 'https://google.com', - type: 'blank', - title: 'Google', - icon: 'insert_link' - }, - { - id: "2", - link: 'https://google.com', - type: 'blank', - title: 'Google1', - icon: 'insert_link' - }, - { - id: "3", - link: 'https://google.com', - type: 'blank', - title: 'Google2', - icon: 'insert_link' - }, - { - id: "4", - link: `${routePrefix}/model/example/edit`, - type: 'self', - title: 'Test Edit', - icon: 'insert_link' - } - ] - } + // json: false, + // tui: false, + // geojson: false, + // week: false, + // color: false, + // range: false, + // date: false, + // month: false, + // selectMany: false, + // select: false, + // testRelation: false, + // tests: false, + // price: false, + // code: false, + // datatable: false + }, + // actions: { + // global: [ + // { + // id: "1", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google', + // icon: 'insert_link' + // }, { + // id: "2", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google', + // icon: 'insert_link' + // }, { + // id: "3", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google', + // icon: 'insert_link' + // }, { + // id: "4", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google', + // icon: 'insert_link' + // }, { + // id: "5", + // link: `${routePrefix}/form/global`, + // type: 'self', + // title: 'Form', + // icon: 'insert_link' + // }, + // ], + // inline: [ + // { + // id: "1", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google', + // icon: 'insert_link' + // }, + // { + // id: "2", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google1', + // icon: 'insert_link' + // }, + // { + // id: "3", + // link: 'https://google.com', + // type: 'blank', + // title: 'Google2', + // icon: 'insert_link' + // }, + // { + // id: "4", + // link: `${routePrefix}/model/example/edit`, + // type: 'self', + // title: 'Test Edit', + // icon: 'insert_link' + // } + // ] + // } }, icon: 'inbox' }, @@ -458,6 +484,9 @@ const models: AdminpanelConfig["models"] = { } const config: AdminpanelConfig = { + filters: { + enabled: true + }, mediamanager: { fileStoragePath: '.tmp/public', allowMIME: ['image/*', 'application/*', 'text/*', 'video/*'], diff --git a/fixture/filters/customFilterHandlers.ts b/fixture/filters/customFilterHandlers.ts new file mode 100644 index 00000000..c8092b87 --- /dev/null +++ b/fixture/filters/customFilterHandlers.ts @@ -0,0 +1,143 @@ +import { Sequelize } from 'sequelize'; +import { AbstractCustomFilter, FilterCustomFieldCondition } from '../../dist'; + +function normalizeSearchValue(value: unknown): string { + if (typeof value === 'string') { + return value.trim(); + } + + if (value === null || value === undefined) { + return ''; + } + + return JSON.stringify(value); +} + +function extractJsonPairFilters(rawValue: string): { key?: string; value?: string } { + const keyMatch = rawValue.match(/"key"\s*:\s*"([^"]+)"/i); + const valueMatch = rawValue.match(/"value"\s*:\s*"([^"]+)"/i); + + return { + key: keyMatch?.[1], + value: valueMatch?.[1], + }; +} + +function escapeSql(value: string): string { + return value.replace(/'/g, "''"); +} + +export class ExampleJsonCustomFilterHandler extends AbstractCustomFilter { + public readonly name = 'Example JSON text search'; + public readonly description = 'Search by substring in JSON text'; + public readonly inputConfig = { + query: { placeholder: 'JSON text', type: 'text' as const } + }; + + constructor() { + super('Example', 'json'); + } + + public buildCondition(value: unknown): FilterCustomFieldCondition { + const qualifiedJsonColumn = '`Example`.`json`'; + const valueFromInput = typeof value === 'object' && value !== null && !Array.isArray(value) + ? (value as Record).query + : value; + const originalValue = normalizeSearchValue(valueFromInput); + + if (!originalValue) { + return { criteria: {} }; + } + + const compactValue = originalValue.replace(/\s+/g, '').replace(/,+$/g, ''); + const pairFilters = extractJsonPairFilters(originalValue); + const sqlClauses: string[] = []; + + if (pairFilters.key) { + sqlClauses.push(`json_extract(${qualifiedJsonColumn}, '$.key') LIKE '%${escapeSql(pairFilters.key)}%'`); + } + + if (pairFilters.value) { + sqlClauses.push(`json_extract(${qualifiedJsonColumn}, '$.value') LIKE '%${escapeSql(pairFilters.value)}%'`); + } + + if (sqlClauses.length > 0) { + return { + criteria: Sequelize.literal(sqlClauses.join(' AND ')) + }; + } + + return { + criteria: Sequelize.literal( + `(CAST(${qualifiedJsonColumn} AS TEXT) LIKE '%${escapeSql(originalValue)}%' OR CAST(${qualifiedJsonColumn} AS TEXT) LIKE '%${escapeSql(compactValue)}%')` + ) + }; + } +} + +function parseOptionalNumber(rawValue: unknown): number | null { + if (rawValue === undefined || rawValue === null) { + return null; + } + + if (typeof rawValue === 'number') { + return Number.isFinite(rawValue) ? rawValue : null; + } + + const normalized = String(rawValue).trim().replace(',', '.'); + if (!normalized) { + return null; + } + + if (!/^-?\d+(\.\d+)?$/.test(normalized)) { + return null; + } + + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : null; +} + +export class ExampleDatatablePriceRangeFilterHandler extends AbstractCustomFilter { + public readonly name = 'Price range'; + public readonly description = 'Filter by price range inside datatable JSON array'; + public readonly inputConfig = { + from: { placeholder: 'Price from', type: 'number' as const }, + to: { placeholder: 'Price to', type: 'number' as const }, + }; + + constructor() { + super('Example', 'datatable'); + } + + public buildCondition(value: unknown): FilterCustomFieldCondition { + const qualifiedColumn = '`Example`.`datatable`'; + const valueObject = typeof value === 'object' && value !== null && !Array.isArray(value) + ? value as Record + : {}; + + let from = parseOptionalNumber(valueObject.from); + let to = parseOptionalNumber(valueObject.to); + + if (from !== null && to !== null && from > to) { + [from, to] = [to, from]; + } + + if (from === null && to === null) { + return { criteria: {} }; + } + + const clauses: string[] = []; + if (from !== null) { + clauses.push(`CAST(json_extract(price_item.value, '$.price') AS REAL) >= ${from}`); + } + if (to !== null) { + clauses.push(`CAST(json_extract(price_item.value, '$.price') AS REAL) <= ${to}`); + } + + const sql = `EXISTS (SELECT 1 FROM json_each(COALESCE(${qualifiedColumn}, '[]')) AS price_item WHERE ${clauses.join(' AND ')})`; + + return { + criteria: Sequelize.literal(sql) + }; + } +} diff --git a/fixture/helpers/seedDatabase.ts b/fixture/helpers/seedDatabase.ts index e8cb582f..07f991ab 100644 --- a/fixture/helpers/seedDatabase.ts +++ b/fixture/helpers/seedDatabase.ts @@ -1,229 +1,271 @@ -import { faker, tr } from '@faker-js/faker'; -import { generate } from 'password-hash'; -import { UserAP } from '../../src'; -import { Example } from '../models/sequelize/Example'; -export async function seedDatabase( - collections: Record, - count: number = 3 -) { - const getRandomTime = () => { - const hours = faker.number.int({ min: 0, max: 23 }).toString().padStart(2, '0'); - const minutes = faker.number.int({ min: 0, max: 59 }).toString().padStart(2, '0'); - return `${hours}:${minutes}`; - }; - - const exampleModel = collections.example ?? collections.Example; - const testModel = collections.test ?? collections.Test; - const userModel = collections.userap ?? collections.UserAP; - const groupModel = collections.groupap ?? collections.GroupAP; - - - const isSequelize = typeof exampleModel?.bulkCreate === 'function'; - const isWaterline = typeof exampleModel?.createEach === 'function'; - - if (!exampleModel || !userModel || !groupModel || (!isSequelize && !isWaterline)) { - throw new Error('Models should support the ORM interface (Sequelize or Waterline)'); - } - - // ------------------ Groups ------------------ // - const groupNames = [ - { name: 'Admins', description: 'System administrators', tokens: [ - 'ai-assistant-dummy', - 'ai-assistant-openai', - ] }, - { name: 'Users', description: 'Registered users', tokens: - [ - "create-test-model", - "read-test-model", - "update-test-model", - "delete-test-model", - - "create-example-model", - "read-example-model", - "update-example-model", - - "read-jsonschema-model", - "ai-assistant-dummy", - "ai-assistant-openai", - ] - }, - { name: 'Guests', description: 'Guest access' }, - ]; - - for (const group of groupNames) { - const exists = isSequelize - ? await groupModel.findOne({ where: { name: group.name } }) - : await groupModel.find({ name: group.name }); - - if (!exists || (Array.isArray(exists) && exists.length === 0)) { - if (isSequelize) { - await groupModel.create(group); - } else { - await groupModel.create(group).fetch(); - } - } - } - - // ------------------ Users ------------------ // - const users = [ - { login: 'pass', password: 'pass', fullName: 'User Pass' }, - { login: 'demo', password: 'demo', fullName: 'Administrator', isAdministrator: true }, - { login: 'user1', password: 'user1', fullName: 'User One' }, - { login: 'admin', password: 'admin', fullName: 'Admin User', isAdministrator: true }, - { login: 'user2', password: 'user2', fullName: 'User Two' }, - { login: 'user3', password: 'user3', fullName: 'User Three', isConfirmed: true }, - { login: 'openai', password: 'openai', fullName: 'OpenAI Agent', isAdministrator: true, isConfirmed: true }, - ]; - - for (const u of users) { - const exists = isSequelize - ? await userModel.findOne({ where: { login: u.login } }) - : await userModel.find({ login: u.login }); - - if (!exists || (Array.isArray(exists) && exists.length === 0)) { - const userData = { - login: u.login, - password: u.password, - passwordHashed: generate(u.login+u.password+process.env.AP_PASSWORD_SALT), - fullName: u.fullName, - isActive: true, - isAdministrator: u.isAdministrator || false, - isConfirmed: u.isConfirmed - }; - - const userInstance = isSequelize - ? await userModel.create(userData) - : await userModel.create(userData).fetch(); - - - // Linking to groups (simplified logic) - const groupName = u.isAdministrator ? 'Admins' : 'Users'; - - const group = isSequelize - ? await groupModel.findOne({ where: { name: groupName } }) - : (await groupModel.find({ name: groupName }))[0]; - - if (group && group.addUser) { - await group.addUser(userInstance); // Sequelize M:N - } else if (isWaterline && group && typeof groupModel.addToCollection === 'function') { - await groupModel.addToCollection(group.id, 'users', userInstance.id); - } - } - } - - - // ------------------ Example Records ------------------ // - const exampleCount = isSequelize - ? await exampleModel.count() - : await exampleModel.count({}); - - const allUsers: UserAP[] = isSequelize - ? await userModel.findAll() - : await userModel.find(); - - if (exampleCount === 0) { - const fakeExamples = Array.from({ length: count }, () => { - const randomUser = faker.helpers.arrayElement(allUsers); - if(isSequelize) { - return { - title: faker.lorem.word(), - description: faker.lorem.paragraph(), - sort: faker.datatype.boolean(), - time: getRandomTime(), - ownerId: randomUser.id, - number: faker.number.int(300), - editor: faker.lorem.text(), - } - } else { - return { - title: faker.lorem.word(), - description: faker.lorem.paragraph(), - sort: faker.datatype.boolean(), - time: getRandomTime(), - owner: randomUser.id, - ownerId: randomUser.id, - number: faker.number.int(300), - editor: faker.lorem.text(), - } - } - } - ); - - if (isSequelize) { - await exampleModel.bulkCreate(fakeExamples); - } else { - await exampleModel.createEach(fakeExamples).fetch(); - } - } - - // ------------------ Tests ------------------ // - const testCount = isSequelize - ? await testModel.count() - : await testModel.count({}); - - if (testCount === 0) { - const allUsers: UserAP[] = isSequelize - ? await userModel.findAll() - : await userModel.find(); - - const allExamples: Example[] = isSequelize - ? await exampleModel.findAll() - : await exampleModel.find(); - - const fakeTests = Array.from({ length: count }, () => { - const randomUser = faker.helpers.arrayElement(allUsers); - const randomExample = faker.helpers.arrayElement(allExamples); - - if(isSequelize) { - return { - title: faker.lorem.words(3), - ownerId: randomUser.id, - exampleId: randomExample?.id ?? null, - }; - } else { - return { - title: faker.lorem.words(3), - owner: randomUser.id, - example: randomExample?.id ?? null, - } - } - }); - - if (isSequelize) { - await testModel.bulkCreate(fakeTests); - } else { - await testModel.createEach(fakeTests).fetch(); - } - } - - - // ------------------ JsonSchemas ------------------ // - const jsonSchemaModel = collections.jsonschema ?? collections.JsonSchema; - - const schemaCount = isSequelize - ? await jsonSchemaModel.count() - : await jsonSchemaModel.count({}); - - if (schemaCount === 0) { - const fakeSchemas = Array.from({ length: 3 }, () => { - const schemaData = { - data: { - type: "object", - properties: { - name: { type: "string" }, - age: { type: "number" } - }, - required: ["name"] - }, - name: faker.internet.userName() - }; - return schemaData; - }); - - if (isSequelize) { - await jsonSchemaModel.bulkCreate(fakeSchemas); - } else { - await jsonSchemaModel.createEach(fakeSchemas).fetch(); - } - } -} +import { faker, tr } from '@faker-js/faker'; +import { generate } from 'password-hash'; +import { UserAP } from '../../src'; +import { Example } from '../models/sequelize/Example'; +export async function seedDatabase( + collections: Record, + count: number = 3 +) { + const getRandomTime = () => { + const hours = faker.number.int({ min: 0, max: 23 }).toString().padStart(2, '0'); + const minutes = faker.number.int({ min: 0, max: 59 }).toString().padStart(2, '0'); + return `${hours}:${minutes}`; + }; + + const exampleModel = collections.example ?? collections.Example; + const testModel = collections.test ?? collections.Test; + const userModel = collections.userap ?? collections.UserAP; + const groupModel = collections.groupap ?? collections.GroupAP; + + + const isSequelize = typeof exampleModel?.bulkCreate === 'function'; + const isWaterline = typeof exampleModel?.createEach === 'function'; + + if (!exampleModel || !userModel || !groupModel || (!isSequelize && !isWaterline)) { + throw new Error('Models should support the ORM interface (Sequelize or Waterline)'); + } + + // ------------------ Groups ------------------ // + const groupNames = [ + { name: 'Admins', description: 'System administrators', tokens: [ + 'ai-assistant-dummy', + 'ai-assistant-openai', + ] }, + { name: 'Users', description: 'Registered users', tokens: + [ + "create-test-model", + "read-test-model", + "update-test-model", + "delete-test-model", + + "create-example-model", + "read-example-model", + "update-example-model", + + "read-jsonschema-model", + "ai-assistant-dummy", + "ai-assistant-openai", + ] + }, + { name: 'Guests', description: 'Guest access' }, + ]; + + for (const group of groupNames) { + const exists = isSequelize + ? await groupModel.findOne({ where: { name: group.name } }) + : await groupModel.find({ name: group.name }); + + if (!exists || (Array.isArray(exists) && exists.length === 0)) { + if (isSequelize) { + await groupModel.create(group); + } else { + await groupModel.create(group).fetch(); + } + } + } + + // ------------------ Users ------------------ // + const users = [ + { login: 'pass', password: 'pass', fullName: 'User Pass' }, + { login: 'demo', password: 'demo', fullName: 'Administrator', isAdministrator: true }, + { login: 'user1', password: 'user1', fullName: 'User One' }, + { login: 'admin', password: 'admin', fullName: 'Admin User', isAdministrator: true }, + { login: 'user2', password: 'user2', fullName: 'User Two' }, + { login: 'user3', password: 'user3', fullName: 'User Three', isConfirmed: true }, + { login: 'openai', password: 'openai', fullName: 'OpenAI Agent', isAdministrator: true, isConfirmed: true }, + ]; + + for (const u of users) { + const exists = isSequelize + ? await userModel.findOne({ where: { login: u.login } }) + : await userModel.find({ login: u.login }); + + if (!exists || (Array.isArray(exists) && exists.length === 0)) { + const userData = { + login: u.login, + password: u.password, + passwordHashed: generate(u.login+u.password+process.env.AP_PASSWORD_SALT), + fullName: u.fullName, + isActive: true, + isAdministrator: u.isAdministrator || false, + isConfirmed: u.isConfirmed + }; + + const userInstance = isSequelize + ? await userModel.create(userData) + : await userModel.create(userData).fetch(); + + + // Linking to groups (simplified logic) + const groupName = u.isAdministrator ? 'Admins' : 'Users'; + + const group = isSequelize + ? await groupModel.findOne({ where: { name: groupName } }) + : (await groupModel.find({ name: groupName }))[0]; + + if (group && group.addUser) { + await group.addUser(userInstance); // Sequelize M:N + } else if (isWaterline && group && typeof groupModel.addToCollection === 'function') { + await groupModel.addToCollection(group.id, 'users', userInstance.id); + } + } + } + + + // ------------------ Example Records ------------------ // + const exampleCount = isSequelize + ? await exampleModel.count() + : await exampleModel.count({}); + + const allUsers: UserAP[] = isSequelize + ? await userModel.findAll() + : await userModel.find(); + + if (exampleCount === 0) { + // Helper function to get ISO week number (YYYY-Www format) + const getWeekNumber = (date: Date): string => { + const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); + const dayNum = d.getUTCDay() || 7; + d.setUTCDate(d.getUTCDate() + 4 - dayNum); + const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); + const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); + return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; + }; + + const fakeExamples = Array.from({ length: count }, () => { + const randomUser = faker.helpers.arrayElement(allUsers); + if(isSequelize) { + return { + title: faker.lorem.word(), + description: faker.lorem.paragraph(), + disabled_text: faker.lorem.sentence(), + sort: faker.datatype.boolean(), + time: getRandomTime(), + number: faker.number.int(300), + color: faker.color.rgb(), + range: faker.number.int({ min: 10, max: 80 }), + date: faker.date.past(), + month: faker.date.past().toISOString().slice(0, 7), + week: getWeekNumber(faker.date.past()), + datetime: faker.date.recent().toISOString().slice(0, 16), + code: faker.lorem.paragraphs(2), + editor: faker.lorem.paragraphs(3), + selectMany: faker.helpers.arrayElements(['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], 2), + select: faker.helpers.arrayElement(['decrease', 'increase', 'none']), + tui: faker.lorem.paragraphs(2), + datatable: [ + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) }, + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) } + ], + json: { key: faker.lorem.word(), value: faker.lorem.word(), count: faker.number.int(100) }, + ownerId: randomUser.id, + } + } else { + return { + title: faker.lorem.word(), + description: faker.lorem.paragraph(), + disabled_text: faker.lorem.sentence(), + sort: faker.datatype.boolean(), + time: getRandomTime(), + owner: randomUser.id, + ownerId: randomUser.id, + number: faker.number.int(300), + color: faker.color.rgb(), + range: faker.number.int({ min: 10, max: 80 }), + date: faker.date.past(), + month: faker.date.past().toISOString().slice(0, 7), + week: getWeekNumber(faker.date.past()), + datetime: faker.date.recent().toISOString().slice(0, 16), + code: faker.lorem.paragraphs(2), + editor: faker.lorem.paragraphs(3), + selectMany: faker.helpers.arrayElements(['Sone', 'Stwo', 'Sthree', 'Sfour', 'Sfive'], 2), + select: faker.helpers.arrayElement(['decrease', 'increase', 'none']), + tui: faker.lorem.paragraphs(2), + datatable: [ + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) }, + { name: faker.commerce.product(), footage: faker.number.int(100), price: faker.number.int(1000) } + ], + json: { key: faker.lorem.word(), value: faker.lorem.word(), count: faker.number.int(100) }, + } + } + } + ); + + if (isSequelize) { + await exampleModel.bulkCreate(fakeExamples); + } else { + await exampleModel.createEach(fakeExamples).fetch(); + } + } + + // ------------------ Tests ------------------ // + const testCount = isSequelize + ? await testModel.count() + : await testModel.count({}); + + if (testCount === 0) { + const allUsers: UserAP[] = isSequelize + ? await userModel.findAll() + : await userModel.find(); + + const allExamples: Example[] = isSequelize + ? await exampleModel.findAll() + : await exampleModel.find(); + + const fakeTests = Array.from({ length: count }, () => { + const randomUser = faker.helpers.arrayElement(allUsers); + const randomExample = faker.helpers.arrayElement(allExamples); + + if(isSequelize) { + return { + title: faker.lorem.words(3), + ownerId: randomUser.id, + exampleId: randomExample?.id ?? null, + }; + } else { + return { + title: faker.lorem.words(3), + owner: randomUser.id, + example: randomExample?.id ?? null, + } + } + }); + + if (isSequelize) { + await testModel.bulkCreate(fakeTests); + } else { + await testModel.createEach(fakeTests).fetch(); + } + } + + + // ------------------ JsonSchemas ------------------ // + const jsonSchemaModel = collections.jsonschema ?? collections.JsonSchema; + + const schemaCount = isSequelize + ? await jsonSchemaModel.count() + : await jsonSchemaModel.count({}); + + if (schemaCount === 0) { + const fakeSchemas = Array.from({ length: 3 }, () => { + const schemaData = { + data: { + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" } + }, + required: ["name"] + }, + name: faker.internet.username() + }; + return schemaData; + }); + + if (isSequelize) { + await jsonSchemaModel.bulkCreate(fakeSchemas); + } else { + await jsonSchemaModel.createEach(fakeSchemas).fetch(); + } + } +} diff --git a/fixture/index.ts b/fixture/index.ts index 56c92d10..6d6d3e34 100644 --- a/fixture/index.ts +++ b/fixture/index.ts @@ -43,6 +43,10 @@ import { InfoOne, Info4, Info3, InfoTwo } from "./widgets/Info"; import { CustomOne } from "./widgets/Custom"; import { ActionOne, ActionTwo } from "./widgets/Actions"; import { TestCatalog } from "./virtual-catalog/virtualCatalog"; +import { + ExampleDatatablePriceRangeFilterHandler, + ExampleJsonCustomFilterHandler +} from "./filters/customFilterHandlers"; import express from "express"; import cookieParser from "cookie-parser"; import { corsApi } from "./cors-api/api"; @@ -113,6 +117,9 @@ if (ormType === "waterline") { console.error("Error during database seeding:", seedErr); } } + + // Enable debug logging + Adminizer.logger.level = 'debug'; } // Finish @@ -185,9 +192,9 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { }); // add custom control wysiwyg - adminizer.emitter.on('adminizer:loaded', () => { - adminizer.controlsHandler.add(new ReactQuill(adminizer)) - }) + // adminizer.emitter.on('adminizer:loaded', () => { + // adminizer.controlsHandler.add(new ReactQuill(adminizer)) + // }) // Test cors adminizer.emitter.on('adminizer:loaded', () => { @@ -198,6 +205,9 @@ async function ormSharedFixtureLift(adminizer: Adminizer) { await adminizer.init(adminpanelConfig as unknown as AdminpanelConfig) + adminizer.customFilterHandler.add(new ExampleJsonCustomFilterHandler(), { force: true }); + adminizer.customFilterHandler.add(new ExampleDatatablePriceRangeFilterHandler(), { force: true }); + if (adminizer.config.aiAssistant?.enabled) { // Dynamic import to avoid loading OpenAI dependencies when AI assistant is disabled const { OpenAiDataAgentService } = await import("./helpers/ai/OpenAiDataAgentService"); diff --git a/fixture/models/Example.ts b/fixture/models/Example.ts index 27ed1897..f0c891e1 100644 --- a/fixture/models/Example.ts +++ b/fixture/models/Example.ts @@ -11,13 +11,13 @@ const Example = Waterline.Collection.extend({ description: { type: "string"}, disabled_text: { type: "string"}, sort: { type: "boolean" }, - time: { type: "string" }, + time: { type: "ref" }, number: { type: "number" }, color: { type: "string" }, range: { type: "string" }, - date: { type: "string" }, - month: { type: "string" }, - week: { type: "string" }, + date: { type: "ref" }, + month: { type: "number" }, + week: { type: "number" }, code: { type: "string" }, editor: { type: "string" }, selectMany: { type: "json" }, @@ -27,7 +27,7 @@ const Example = Waterline.Collection.extend({ datatable: { type: "json" }, json: { type: "json" }, tests: { collection: "Test", via: "example" }, - datetime: { type: "string" }, + datetime: { type: "ref" }, geojson: { type: "json" }, // Many-to-many relation with UserAP diff --git a/fixture/models/sequelize/Example.ts b/fixture/models/sequelize/Example.ts index 710572a5..5e4db3c1 100644 --- a/fixture/models/sequelize/Example.ts +++ b/fixture/models/sequelize/Example.ts @@ -39,62 +39,62 @@ import {UserAP} from "../../../src"; @Column(DataType.BOOLEAN) declare sort: boolean; - - @Column(DataType.STRING) + + @Column(DataType.TIME) declare time: string; - + @Column(DataType.INTEGER) declare number: number; - + @Column(DataType.STRING) declare color: string; - - @Column(DataType.STRING) - declare range: string; - - @Column(DataType.STRING) - declare date: string; - + + @Column(DataType.INTEGER) + declare range: number; + + @Column(DataType.DATEONLY) + declare date: Date; + @Column(DataType.STRING) declare month: string; - + @Column(DataType.STRING) declare week: string; - + @Column(DataType.STRING) declare code: string; - + @Column(DataType.STRING) declare editor: string; - + @Column(DataType.JSON) declare selectMany: object; - + @Column(DataType.STRING) declare select: string; - + @ForeignKey(() => Example) @Column(DataType.INTEGER) declare testRelation: number; - + @BelongsTo(() => Example) declare testRelationExample?: Example; - + @Column(DataType.STRING) declare tui: string; - + @Column(DataType.JSON) declare datatable: object; - + @Column(DataType.JSON) declare json: object; - + @HasMany(() => Test, 'exampleId') declare tests: Test[]; - - @Column(DataType.STRING) - declare datetime: string; - + + @Column(DataType.DATE) + declare datetime: Date; + @Column(DataType.JSON) declare geojson: object; diff --git a/package-lock.json b/package-lock.json index 4dc49bde..5bbdc1f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "adminizer", - "version": "4.5.0", + "version": "4.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "adminizer", - "version": "4.5.0", + "version": "4.6.0", "license": "MIT", "dependencies": { "@minoru/react-dnd-treeview": "^3.5.2", @@ -40,6 +40,7 @@ "uuid": "^11.0.5", "waterline": "^0.15.2", "winston": "^3.17.0", + "xlsx": "^0.18.5", "zod": "^4.1.11" }, "devDependencies": { @@ -278,9 +279,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", - "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "dev": true, "license": "MIT", "optional": true, @@ -540,69 +541,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-decorators": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", @@ -670,54 +608,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", @@ -736,21 +626,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -766,51 +641,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", @@ -826,42 +656,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", @@ -1691,28 +1485,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse--for-generate-function-map": { - "name": "@babel/traverse", - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", @@ -3550,226 +3322,50 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@expo/cli": { + "node_modules/@expo/code-signing-certificates": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", + "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-forge": "^1.3.3" + } + }, + "node_modules/@expo/config": { "version": "55.0.15", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.15.tgz", - "integrity": "sha512-Qd4aF2+wT9LtdV7G/gULbx/t8FJ/OVtwuNkLcZt1XlosQ5XX/C/3ywZXYl+/bYcHUmuO1TBD3Fg05bNlmL6vrw==", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.15.tgz", + "integrity": "sha512-lHc0ELIQ8126jYOMZpLv3WIuvordW98jFg5aT/J1/12n2ycuXu01XLZkJsdw0avO34cusUYb1It+MvY8JiMduA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/code-signing-certificates": "^0.0.6", - "@expo/config": "~55.0.8", - "@expo/config-plugins": "~55.0.6", - "@expo/devcert": "^1.2.1", - "@expo/env": "~2.1.1", - "@expo/image-utils": "^0.8.12", - "@expo/json-file": "^10.0.12", - "@expo/log-box": "55.0.7", - "@expo/metro": "~54.2.0", - "@expo/metro-config": "~55.0.9", - "@expo/osascript": "^2.4.2", - "@expo/package-manager": "^1.10.3", - "@expo/plist": "^0.5.2", - "@expo/prebuild-config": "^55.0.8", - "@expo/require-utils": "^55.0.2", - "@expo/router-server": "^55.0.9", - "@expo/schema-utils": "^55.0.2", - "@expo/spawn-async": "^1.7.2", - "@expo/ws-tunnel": "^1.0.1", - "@expo/xcpretty": "^4.4.0", - "@react-native/dev-middleware": "0.83.2", - "accepts": "^1.3.8", - "arg": "^5.0.2", - "better-opn": "~3.0.2", - "bplist-creator": "0.1.0", - "bplist-parser": "^0.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.3.0", - "compression": "^1.7.4", - "connect": "^3.7.0", - "debug": "^4.3.4", - "dnssd-advertise": "^1.1.3", - "expo-server": "^55.0.6", - "fetch-nodeshim": "^0.4.6", + "@expo/config-plugins": "~55.0.8", + "@expo/config-types": "^55.0.5", + "@expo/json-file": "^10.0.13", + "@expo/require-utils": "^55.0.4", + "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", - "lan-network": "^0.2.0", - "multitars": "^0.2.3", - "node-forge": "^1.3.3", - "npm-package-arg": "^11.0.0", - "ora": "^3.4.0", - "picomatch": "^4.0.3", - "pretty-format": "^29.7.0", - "progress": "^2.0.3", - "prompts": "^2.3.2", - "resolve-from": "^5.0.0", + "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", - "send": "^0.19.0", - "slugify": "^1.3.4", - "source-map-support": "~0.5.21", - "stacktrace-parser": "^0.1.10", - "structured-headers": "^0.4.1", - "terminal-link": "^2.1.1", - "toqr": "^0.1.1", - "wrap-ansi": "^7.0.0", - "ws": "^8.12.1", - "zod": "^3.25.76" - }, - "bin": { - "expo-internal": "build/bin/cli" - }, - "peerDependencies": { - "expo": "*", - "expo-router": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "expo-router": { - "optional": true - }, - "react-native": { - "optional": true - } + "slugify": "^1.3.4" } }, - "node_modules/@expo/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@expo/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@expo/cli/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@expo/cli/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@expo/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@expo/cli/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@expo/code-signing-certificates": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", - "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-forge": "^1.3.3" - } - }, - "node_modules/@expo/config": { - "version": "55.0.8", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.8.tgz", - "integrity": "sha512-D7RYYHfErCgEllGxNwdYdkgzLna7zkzUECBV3snbUpf7RvIpB5l1LpCgzuVoc5KVew5h7N1Tn4LnT/tBSUZsQg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@expo/config-plugins": "~55.0.6", - "@expo/config-types": "^55.0.5", - "@expo/json-file": "^10.0.12", - "@expo/require-utils": "^55.0.2", - "deepmerge": "^4.3.1", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "resolve-from": "^5.0.0", - "resolve-workspace-root": "^2.0.0", - "semver": "^7.6.0", - "slugify": "^1.3.4" - } - }, - "node_modules/@expo/config-plugins": { - "version": "55.0.6", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.6.tgz", - "integrity": "sha512-cIox6FjZlFaaX40rbQ3DvP9e87S5X85H9uw+BAxJE5timkMhuByy3GAlOsj1h96EyzSiol7Q6YIGgY1Jiz4M+A==", + "node_modules/@expo/config-plugins": { + "version": "55.0.8", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.8.tgz", + "integrity": "sha512-8WfWTRntTCcowfOS+tHdB0z98gKetTwktg4G5TWkCkXVa8Jt1NUnvzaaU4UHk2vbR2U4N84RyZJFizSwfF6C9g==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@expo/config-types": "^55.0.5", - "@expo/json-file": "~10.0.12", + "@expo/json-file": "~10.0.13", "@expo/plist": "^0.5.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", @@ -3871,18 +3467,6 @@ "optional": true, "peer": true }, - "node_modules/@expo/config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@expo/config/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -4000,9 +3584,9 @@ } }, "node_modules/@expo/dom-webview": { - "version": "55.0.3", - "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.3.tgz", - "integrity": "sha512-bY4/rfcZ0f43DvOtMn8/kmPlmo01tex5hRoc5hKbwBwQjqWQuQt0ACwu7akR9IHI4j0WNG48eL6cZB6dZUFrzg==", + "version": "55.0.5", + "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.5.tgz", + "integrity": "sha512-lt3uxYOCk3wmWvtOOvsC35CKGbDAOx5C2EaY8SH1JVSfBzqmF8Cs0Xp1MPxncDPMyxpMiWx5SvvV/iLF1rJU4A==", "dev": true, "license": "MIT", "optional": true, @@ -4083,9 +3667,9 @@ } }, "node_modules/@expo/fingerprint": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.5.tgz", - "integrity": "sha512-mLrcymtgkW9IJ/G1e8MH1Xt2VIb1MOS86ePY0ePcnV3nVyJqm7gfa/AXD1Hk+eZXvf8XhioYz6QZaamBdEzR3A==", + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.6.tgz", + "integrity": "sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ==", "dev": true, "license": "MIT", "optional": true, @@ -4199,20 +3783,20 @@ } }, "node_modules/@expo/image-utils": { - "version": "0.8.12", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.12.tgz", - "integrity": "sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==", + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.13.tgz", + "integrity": "sha512-1I//yBQeTY6p0u1ihqGNDAr35EbSG8uFEupFrIF0jd++h9EWH33521yZJU1yE+mwGlzCb61g3ehu78siMhXBlA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { + "@expo/require-utils": "^55.0.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", - "resolve-from": "^5.0.0", "semver": "^7.6.0" } }, @@ -4253,18 +3837,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@expo/image-utils/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@expo/image-utils/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -4296,9 +3868,9 @@ } }, "node_modules/@expo/json-file": { - "version": "10.0.12", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.12.tgz", - "integrity": "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==", + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.13.tgz", + "integrity": "sha512-pX/XjQn7tgNw6zuuV2ikmegmwe/S7uiwhrs2wXrANMkq7ozrA+JcZwgW9Q/8WZgciBzfAhNp5hnackHcrmapQA==", "dev": true, "license": "MIT", "optional": true, @@ -4309,15 +3881,15 @@ } }, "node_modules/@expo/local-build-cache-provider": { - "version": "55.0.6", - "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.6.tgz", - "integrity": "sha512-4kfdv48sKzokijMqi07fINYA9/XprshmPgSLf8i69XgzIv2YdRyBbb70SzrufB7PDneFoltz8N83icW8gOOj1g==", + "version": "55.0.11", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.11.tgz", + "integrity": "sha512-rJ4RTCrkeKaXaido/bVyhl90ZRtVTOEbj59F1PWVjIEIVgjdlfc1J3VD9v7hEsbf/+8Tbr/PgvWhT6Visi5sLQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/config": "~55.0.8", + "@expo/config": "~55.0.15", "chalk": "^4.1.2" } }, @@ -4374,54 +3946,54 @@ } }, "node_modules/@expo/log-box": { - "version": "55.0.7", - "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.7.tgz", - "integrity": "sha512-m7V1k2vlMp4NOj3fopjOg4zl/ANXyTRF3HMTMep2GZAKsPiDzgOQ41nm8CaU50/HlDIGXlCObss07gOn20UpHQ==", + "version": "55.0.10", + "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.10.tgz", + "integrity": "sha512-7jdikExgIrCIF5e3P1qMwcUZ2tcxrNdVqE9Y8kNMUHqZ+ipMlin+SiZwJKHM1+am4CYGjhdyrzbnIpvEcLDYcg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/dom-webview": "^55.0.3", + "@expo/dom-webview": "^55.0.5", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { - "@expo/dom-webview": "^55.0.3", + "@expo/dom-webview": "^55.0.5", "expo": "*", "react": "*", "react-native": "*" } }, "node_modules/@expo/metro": { - "version": "54.2.0", - "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz", - "integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==", + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-55.0.0.tgz", + "integrity": "sha512-wohGl+4y17rGHU+lq8UqC5neOXL/HOThorDYXTMbOcBL1jYwcK11MBc151gDMpjpgdVUzgHne0H5RfCIhIN4hA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "metro": "0.83.3", - "metro-babel-transformer": "0.83.3", - "metro-cache": "0.83.3", - "metro-cache-key": "0.83.3", - "metro-config": "0.83.3", - "metro-core": "0.83.3", - "metro-file-map": "0.83.3", - "metro-minify-terser": "0.83.3", - "metro-resolver": "0.83.3", - "metro-runtime": "0.83.3", - "metro-source-map": "0.83.3", - "metro-symbolicate": "0.83.3", - "metro-transform-plugins": "0.83.3", - "metro-transform-worker": "0.83.3" + "metro": "0.83.5", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-config": "0.83.5", + "metro-core": "0.83.5", + "metro-file-map": "0.83.5", + "metro-minify-terser": "0.83.5", + "metro-resolver": "0.83.5", + "metro-runtime": "0.83.5", + "metro-source-map": "0.83.5", + "metro-symbolicate": "0.83.5", + "metro-transform-plugins": "0.83.5", + "metro-transform-worker": "0.83.5" } }, "node_modules/@expo/metro-config": { - "version": "55.0.9", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.9.tgz", - "integrity": "sha512-ZJFEfat/+dLUhFyFFWrzMjAqAwwUaJ3RD42QNqR7jh+RVYkAf6XYLynb5qrKJTHI1EcOx4KoO1717yXYYRFDBA==", + "version": "55.0.16", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.16.tgz", + "integrity": "sha512-JaWDw0dmYZ5pOqA+3/Efvl8JzCVgWQVPogHFjTRC5azUgAsFV+T7moOaZTSgg4d+5TjFZjZbMZg4SUomE7LiGg==", "dev": true, "license": "MIT", "optional": true, @@ -4430,10 +4002,10 @@ "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", - "@expo/config": "~55.0.8", + "@expo/config": "~55.0.15", "@expo/env": "~2.1.1", - "@expo/json-file": "~10.0.12", - "@expo/metro": "~54.2.0", + "@expo/json-file": "~10.0.13", + "@expo/metro": "~55.0.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", @@ -4536,15 +4108,15 @@ } }, "node_modules/@expo/package-manager": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.10.3.tgz", - "integrity": "sha512-ZuXiK/9fCrIuLjPSe1VYmfp0Sa85kCMwd8QQpgyi5ufppYKRtLBg14QOgUqj8ZMbJTxE0xqzd0XR7kOs3vAK9A==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.10.4.tgz", + "integrity": "sha512-y9Mr4Kmpk4abAVZrNNPCdzOZr8nLLyi18p1SXr0RCVA8IfzqZX/eY4H+50a0HTmXqIsPZrQdcdb4I3ekMS9GvQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/json-file": "^10.0.12", + "@expo/json-file": "^10.0.13", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", @@ -4619,20 +4191,20 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "55.0.8", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.8.tgz", - "integrity": "sha512-VJNJiOmmZgyDnR7JMmc3B8Z0ZepZ17I8Wtw+wAH/2+UCUsFg588XU+bwgYcFGw+is28kwGjY46z43kfufpxOnA==", + "version": "55.0.15", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.15.tgz", + "integrity": "sha512-UcCzVhVBE42UbY5U3t/q1Rk2fSFW/B50LJpB6oFpXhImJfvLKu7ayOFU9XcHd38K89i4GqSia/xXuxQvu4RUBg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/config": "~55.0.8", - "@expo/config-plugins": "~55.0.6", + "@expo/config": "~55.0.15", + "@expo/config-plugins": "~55.0.8", "@expo/config-types": "^55.0.5", - "@expo/image-utils": "^0.8.12", - "@expo/json-file": "^10.0.12", - "@react-native/normalize-colors": "0.83.2", + "@expo/image-utils": "^0.8.13", + "@expo/json-file": "^10.0.13", + "@react-native/normalize-colors": "0.83.4", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", @@ -4642,6 +4214,15 @@ "expo": "*" } }, + "node_modules/@expo/prebuild-config/node_modules/@react-native/normalize-colors": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", + "integrity": "sha512-9ezxaHjxqTkTOLg62SGg7YhFaE+fxa/jlrWP0nwf7eGFHlGOiTAaRR2KUfiN3K05e+EMbEhgcH/c7bgaXeGyJw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@expo/prebuild-config/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -4670,9 +4251,9 @@ } }, "node_modules/@expo/require-utils": { - "version": "55.0.2", - "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.2.tgz", - "integrity": "sha512-dV5oCShQ1umKBKagMMT4B/N+SREsQe3lU4Zgmko5AO0rxKV0tynZT6xXs+e2JxuqT4Rz997atg7pki0BnZb4uw==", + "version": "55.0.4", + "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.4.tgz", + "integrity": "sha512-JAANvXqV7MOysWeVWgaiDzikoyDjJWOV/ulOW60Zb3kXJfrx2oZOtGtDXDFKD1mXuahQgoM5QOjuZhF7gFRNjA==", "dev": true, "license": "MIT", "optional": true, @@ -4691,47 +4272,10 @@ } } }, - "node_modules/@expo/router-server": { - "version": "55.0.9", - "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.9.tgz", - "integrity": "sha512-LcCFi+P1qfZOsw0DO4JwNKRxtWt4u2bjTYj0PUe4WVf9NVG/NfUetAXYRbBS6P+gupfM6SC+/bdzdqCWQh7j8g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "@expo/metro-runtime": "^55.0.6", - "expo": "*", - "expo-constants": "^55.0.7", - "expo-font": "^55.0.4", - "expo-router": "*", - "expo-server": "^55.0.6", - "react": "*", - "react-dom": "*", - "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" - }, - "peerDependenciesMeta": { - "@expo/metro-runtime": { - "optional": true - }, - "expo-router": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-server-dom-webpack": { - "optional": true - } - } - }, "node_modules/@expo/schema-utils": { - "version": "55.0.2", - "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.2.tgz", - "integrity": "sha512-QZ5WKbJOWkCrMq0/kfhV9ry8te/OaS34YgLVpG8u9y2gix96TlpRTbxM/YATjNcUR2s4fiQmPCOxkGtog4i37g==", + "version": "55.0.3", + "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.3.tgz", + "integrity": "sha512-l9KHVjTo6MvoeyvwNr6AjckGJm8NIcqZ3QSAh51cWozXW9v2AUjyCyqYtFtyntLWRZ0x/ByYJishpQo4ZQq45Q==", "dev": true, "license": "MIT", "optional": true, @@ -4794,9 +4338,9 @@ "peer": true }, "node_modules/@expo/xcpretty": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.1.tgz", - "integrity": "sha512-KZNxZvnGCtiM2aYYZ6Wz0Ix5r47dAvpNLApFtZWnSoERzAdOMzVBOPysBoM0JlF6FKWZ8GPqgn6qt3dV/8Zlpg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.3.tgz", + "integrity": "sha512-wC562eD3gS6vO2tWHToFhlFnmHKfKHgF1oyvojeSkLK/ZYop1bMU+7cOMiF9Sq70CzcsLy/EMRy/uRc76QmNRw==", "dev": true, "license": "BSD-3-Clause", "optional": true, @@ -5545,392 +5089,102 @@ "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, - "license": "ISC", + "license": "MIT", "optional": true, "peer": true, "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "@sinclair/typebox": "^0.27.8" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "p-locate": "^4.1.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/remapping": { @@ -8953,37 +8207,37 @@ } }, "node_modules/@react-native/assets-registry": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.84.1.tgz", - "integrity": "sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.85.1.tgz", + "integrity": "sha512-QODQ15teXThKaKdb7lnx4RifNUGnsGZ/NMKtkNBE89nJuK93+mPsb1ozp5xkGyLw7ZNVYO4Nkqsp4MsBOuAX8g==", "dev": true, "license": "MIT", "optional": true, "peer": true, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.2.tgz", - "integrity": "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg==", + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.4.tgz", + "integrity": "sha512-UFsK+c1rvT84XZfzpmwKePsc5nTr5LK7hh18TI0DooNlVcztDbMDsQZpDnhO/gmk7aTbWEqO5AB3HJ7tvGp+Jg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.83.2" + "@react-native/codegen": "0.83.4" }, "engines": { "node": ">= 20.19.4" } }, "node_modules/@react-native/babel-preset": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.2.tgz", - "integrity": "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA==", + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.4.tgz", + "integrity": "sha512-SXPFn3Jp4gOzlBDnDOKPzMfxQPKJMYJs05EmEeFB/6km46xZ9l+2YKXwAwxfNhHnmwNf98U/bnVndU95I0TMCw==", "dev": true, "license": "MIT", "optional": true, @@ -9030,7 +8284,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.83.2", + "@react-native/babel-plugin-codegen": "0.83.4", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" @@ -9088,9 +8342,9 @@ } }, "node_modules/@react-native/codegen": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz", - "integrity": "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA==", + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.4.tgz", + "integrity": "sha512-CJ7XutzIqJPz3Lp/5TOiRWlU/JAjTboMT1BHNLSXjYHXwTmgHM3iGEbpCOtBMjWvsojRTJyRO/G3ghInIIXEYg==", "dev": true, "license": "MIT", "optional": true, @@ -9121,9 +8375,9 @@ "peer": true }, "node_modules/@react-native/codegen/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "optional": true, @@ -9194,28 +8448,28 @@ } }, "node_modules/@react-native/community-cli-plugin": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.84.1.tgz", - "integrity": "sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.85.1.tgz", + "integrity": "sha512-vZtNEYv5qMYvbA9cTBMuZ3QkCqyJ7lDQgbxh4MpoZHZ0+62qjJpCXn9xzFM0Rm5ZG2hO8WDDxcFdI581BdASdg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@react-native/dev-middleware": "0.84.1", + "@react-native/dev-middleware": "0.85.1", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.83.3", - "metro-config": "^0.83.3", - "metro-core": "^0.83.3", + "metro": "^0.84.0", + "metro-config": "^0.84.0", + "metro-core": "^0.84.0", "semver": "^7.1.3" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { "@react-native-community/cli": "*", - "@react-native/metro-config": "*" + "@react-native/metro-config": "0.85.1" }, "peerDependenciesMeta": { "@react-native-community/cli": { @@ -9226,59 +8480,453 @@ } } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.84.1.tgz", - "integrity": "sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==", + "node_modules/@react-native/community-cli-plugin/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "optional": true, "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, "engines": { - "node": ">= 20.19.4" + "node": ">= 0.6" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-shell": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.84.1.tgz", - "integrity": "sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==", + "node_modules/@react-native/community-cli-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 20.19.4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.84.1.tgz", - "integrity": "sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==", + "node_modules/@react-native/community-cli-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.84.1", - "@react-native/debugger-shell": "0.84.1", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@react-native/community-cli-plugin/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.3.tgz", + "integrity": "sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.35.0", + "image-size": "^1.0.2", "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.84.3", + "metro-cache": "0.84.3", + "metro-cache-key": "0.84.3", + "metro-config": "0.84.3", + "metro-core": "0.84.3", + "metro-file-map": "0.84.3", + "metro-resolver": "0.84.3", + "metro-runtime": "0.84.3", + "metro-source-map": "0.84.3", + "metro-symbolicate": "0.84.3", + "metro-transform-plugins": "0.84.3", + "metro-transform-worker": "0.84.3", + "mime-types": "^3.0.1", "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^7.5.10" + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-babel-transformer": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.3.tgz", + "integrity": "sha512-svAA+yMLpeMiGcz/jKJs4oHpIGEx4nBqNEJ5AGj4CYIg1efvK+A0TjR6tgIuc6tKO5e8JmN/1lglpN2+f3/z/w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-cache": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.3.tgz", + "integrity": "sha512-0QElxwLaHqLZf+Xqio8QrjVbuXP/8sJfQBGSPiITlKDVXrVLefuzYVSH9Sj+QL6lrPj2gYZd/iwQh1yZuVKnLA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-cache-key": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.3.tgz", + "integrity": "sha512-TnSL1Fdvrw+2glTdBSRmA5TL8l/i16ECjsrUdf3E5HncA+sNx8KcwDG8r+3ct1UhfYcusJypzZqTN55FZZcwGg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-config": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.3.tgz", + "integrity": "sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.84.3", + "metro-cache": "0.84.3", + "metro-core": "0.84.3", + "metro-runtime": "0.84.3", + "yaml": "^2.6.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-core": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.3.tgz", + "integrity": "sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.84.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-file-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.3.tgz", + "integrity": "sha512-1cL4m4Jv1yRUt9RJExZQLfccscdlMNOcRG6LHLtmJhf3BG9j3MujPVc7CIpKYdFl+KUl+sdjge6oO3+meKCHQA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-minify-terser": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.3.tgz", + "integrity": "sha512-3ofrG2OQyJbO9RNhCfOcl8QU7EE2WrSsnN5dFkuZaJO5+4Imujr9bUXmspeNlXRsOVk0F/rVRbEFH98lFSCkBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-resolver": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.3.tgz", + "integrity": "sha512-pjEzGDtoM8DTHAIPK/9u9ZxszEiuRohYUVImWvgbnB91V4gqYJpQcoEYUugf2NIm1lrX5HNu0OvNqWmPBnGYjA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-runtime": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz", + "integrity": "sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-source-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz", + "integrity": "sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.3", + "nullthrows": "^1.1.1", + "ob1": "0.84.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-symbolicate": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.3.tgz", + "integrity": "sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-transform-plugins": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.3.tgz", + "integrity": "sha512-8S3baq2XhBaafHEH5Q8sJW6tmzsEJk80qKc3RU/nZV1MsnYq94RdjTUR6AyKjQd6Rfsk1BtBxhtiNnk7mgslCg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/metro-transform-worker": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.3.tgz", + "integrity": "sha512-Wjba7PyYktNRsHbPmkx2J2UX32rAzcDXjCu49zPHeF/viJlYJhwRaNePQcHaCRqQ+kmgQT4ThprsnJfDj71ZMA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.84.3", + "metro-babel-transformer": "0.84.3", + "metro-cache": "0.84.3", + "metro-cache-key": "0.84.3", + "metro-minify-terser": "0.84.3", + "metro-source-map": "0.84.3", + "metro-transform-plugins": "0.84.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ob1": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.3.tgz", + "integrity": "sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/community-cli-plugin/node_modules/semver": { @@ -9296,6 +8944,21 @@ "node": ">=10" } }, + "node_modules/@react-native/community-cli-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native/community-cli-plugin/node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", @@ -9321,47 +8984,48 @@ } }, "node_modules/@react-native/debugger-frontend": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.2.tgz", - "integrity": "sha512-t4fYfa7xopbUF5S4+ihNEwgaq4wLZLKLY0Ms8z72lkMteVd3bOX2Foxa8E2wTfRvdhPOkSpOsTeNDmD8ON4DoQ==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.85.1.tgz", + "integrity": "sha512-GUC2ZEy+J/Goc4l243XeeY/8NdNXVXPXoRTc6Yy14OiDcy7Yk87VyrMARbp23wCbzhnrz0dnYB8rxJ+AJvMzCg==", "dev": true, "license": "BSD-3-Clause", "optional": true, "peer": true, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/debugger-shell": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.2.tgz", - "integrity": "sha512-z9go6NJMsLSDJT5MW6VGugRsZHjYvUTwxtsVc3uLt4U9W6T3J6FWI2wHpXIzd2dUkXRfAiRQ3Zi8ZQQ8fRFg9A==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.85.1.tgz", + "integrity": "sha512-M/ogODh0uDFJ7xOlCc+v9nKUucUXGJwVOupl+zb3VT8tJnI2Cie/Fiv9NszAD/bzRQhJSrPZkJSAO6VW0XbWyA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "cross-spawn": "^7.0.6", + "debug": "^4.4.0", "fb-dotslash": "0.5.8" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/dev-middleware": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.2.tgz", - "integrity": "sha512-Zi4EVaAm28+icD19NN07Gh8Pqg/84QQu+jn4patfWKNkcToRFP5vPEbbp0eLOGWS+BVB1d1Fn5lvMrJsBbFcOg==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.85.1.tgz", + "integrity": "sha512-vJSIZP7yymZMnwOrdNjalVf8jqcAFtmi6zT3sC9MRMgJPGkDy05g8y5zgAkgTxpNtVsv+/q5pst8woYp7pgRkA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.83.2", - "@react-native/debugger-shell": "0.83.2", + "@react-native/debugger-frontend": "0.85.1", + "@react-native/debugger-shell": "0.85.1", "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.2.0", + "chromium-edge-launcher": "^0.3.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", @@ -9371,7 +9035,7 @@ "ws": "^7.5.10" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/dev-middleware/node_modules/ws": { @@ -9399,42 +9063,42 @@ } }, "node_modules/@react-native/gradle-plugin": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.84.1.tgz", - "integrity": "sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.85.1.tgz", + "integrity": "sha512-KeTntbnsH/NOdzZrSE8tgep+9jEMlEfklVDtgxnjjb5nDhhBD016judwyo9bsinZnuwXxmemXnOOqOfcEawxbg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/js-polyfills": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", - "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.1.tgz", + "integrity": "sha512-VseQZAKnDbmpZThLWviDIJ0NmuSiwiHA6vc2HNJTTVqTy2mQR0+858y9kDdDBQPYe0HH8+W1mYui2i4eUWGh4g==", "dev": true, "license": "MIT", "optional": true, "peer": true, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/@react-native/normalize-colors": { - "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.2.tgz", - "integrity": "sha512-gkZAb9LoVVzNuYzzOviH7DiPTXQoZPHuiTH2+O2+VWNtOkiznjgvqpwYAhg58a5zfRq5GXlbBdf5mzRj5+3Y5Q==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.1.tgz", + "integrity": "sha512-w+4ZZ2PvvtC0IODEmxizYOrHmeDgdzpM7CKhtTNWoNtDWZoi7/ZY3UmNntn9poPorUy5cwFbfYiP/8rJFEsFvQ==", "dev": true, "license": "MIT", "optional": true, "peer": true }, "node_modules/@react-native/virtualized-lists": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.84.1.tgz", - "integrity": "sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.85.1.tgz", + "integrity": "sha512-RLpoATkxeTaYxna5dDLIxEtoStp9UL7ryHIIOmKnE9NQW3ggR+U9DWQPXQkOfRc7/kPYba4ynKA2fIISGysVTg==", "dev": true, "license": "MIT", "optional": true, @@ -9444,12 +9108,12 @@ "nullthrows": "^1.1.1" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", - "react-native": "*" + "react-native": "0.85.1" }, "peerDependenciesMeta": { "@types/react": { @@ -9870,42 +9534,18 @@ "dependencies": { "minimist": "^1.2.6" }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "type-detect": "4.0.8" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } + "peer": true }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", @@ -10521,18 +10161,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/he": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", @@ -10738,15 +10366,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -10853,17 +10472,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3" }, "engines": { @@ -10875,7 +10494,158 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@typescript-eslint/project-service": { @@ -11241,9 +11011,9 @@ } }, "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", + "integrity": "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==", "dev": true, "license": "MIT", "optional": true, @@ -11311,6 +11081,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -11473,37 +11252,6 @@ "node": ">=14" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/app-root-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", @@ -11713,145 +11461,32 @@ "resolved": "https://registry.npmjs.org/b64-lite/-/b64-lite-1.4.0.tgz", "integrity": "sha512-aHe97M7DXt+dkpa8fHlCcm1CnskAHrJqEfMI0KN7dwqlzml/aUe1AGt6lk51HzrSfVD67xOso84sOpr+0wIe2w==", "dev": true, - "license": "MIT", - "dependencies": { - "base-64": "^0.1.0" - } - }, - "node_modules/b64u-lite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/b64u-lite/-/b64u-lite-1.1.0.tgz", - "integrity": "sha512-929qWGDVCRph7gQVTC6koHqQIpF4vtVaSbwLltFQo44B1bYUquALswZdBKFfrJCPEnsCOvWkJsPdQYZ/Ukhw8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "b64-lite": "^1.4.0" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" + "base-64": "^0.1.0" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "node_modules/b64u-lite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/b64u-lite/-/b64u-lite-1.1.0.tgz", + "integrity": "sha512-929qWGDVCRph7gQVTC6koHqQIpF4vtVaSbwLltFQo44B1bYUquALswZdBKFfrJCPEnsCOvWkJsPdQYZ/Ukhw8A==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "b64-lite": "^1.4.0" } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", - "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -11875,15 +11510,15 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", - "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -11932,39 +11567,10 @@ "@babel/plugin-syntax-flow": "^7.12.1" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" - } - }, "node_modules/babel-preset-expo": { - "version": "55.0.10", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.10.tgz", - "integrity": "sha512-aRtW7qJKohGU2V0LUJ6IeP7py3+kVUo9zcc8+v1Kix8jGGuIvqvpo9S6W1Fmn9VFP2DBwkFDLiyzkCZS85urVA==", + "version": "55.0.17", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.17.tgz", + "integrity": "sha512-voPAKycqeqOE+4g/nW6gGaNPMnj3MYCYbVEZlZDUlztGVxlKKkUD+xwlK0ZU/uy6HxAY+tjBEpvsabD5g6b2oQ==", "dev": true, "license": "MIT", "optional": true, @@ -11986,7 +11592,7 @@ "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.83.2", + "@react-native/babel-preset": "0.83.4", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.32.0", @@ -11997,7 +11603,7 @@ "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", - "expo-widgets": "^55.0.2", + "expo-widgets": "^55.0.13", "react-refresh": ">=0.14.0 <1.0.0" }, "peerDependenciesMeta": { @@ -12036,25 +11642,6 @@ "node": ">=8" } }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -12299,9 +11886,9 @@ } }, "node_modules/bplist-parser": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", - "integrity": "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", "dev": true, "license": "MIT", "optional": true, @@ -12679,6 +12266,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chai": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", @@ -12761,9 +12361,9 @@ } }, "node_modules/chromium-edge-launcher": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", - "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -12773,27 +12373,17 @@ "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "mkdirp": "^1.0.4" } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } + "peer": true }, "node_modules/ckeditor5": { "version": "45.2.2", @@ -12995,6 +12585,15 @@ "@codemirror/view": "^6.17.1" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -13375,9 +12974,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", - "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "dev": true, "license": "MIT", "optional": true, @@ -13413,6 +13012,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -13797,9 +13408,9 @@ } }, "node_modules/dnssd-advertise": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.3.tgz", - "integrity": "sha512-XENsHi3MBzWOCAXif3yZvU1Ah0l+nhJj1sjWL6TnOAYKvGiFhbTx32xHN7+wLMLUOCj7Nr0evADWG4R8JtqCDA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.4.tgz", + "integrity": "sha512-AmGyK9WpNf06WeP5TjHZq/wNzP76OuEeaiTlKr9E/EEelYLczywUKoqRz+DPRq/ErssjT4lU+/W7wzJW+7K/ZA==", "dev": true, "license": "MIT", "optional": true, @@ -14382,22 +13993,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -14630,34 +14225,34 @@ } }, "node_modules/expo": { - "version": "55.0.5", - "resolved": "https://registry.npmjs.org/expo/-/expo-55.0.5.tgz", - "integrity": "sha512-toVYbRU0gH50QSlIyrAswXD87RKi2pcJcHZpBDuqU3mIQZzJkTcWgRLWN/2R/wnd3kuJTtW5xlr5ndVG6xEWxQ==", + "version": "55.0.15", + "resolved": "https://registry.npmjs.org/expo/-/expo-55.0.15.tgz", + "integrity": "sha512-sHIvqG477UU1jZHhaexXbUgsU7y+xnYZqDW1HrUkEBYiuEb5lobvWLmwea76EBVkityQx46UDtepFtarpUJQqQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "55.0.15", - "@expo/config": "~55.0.8", - "@expo/config-plugins": "~55.0.6", + "@expo/cli": "55.0.24", + "@expo/config": "~55.0.15", + "@expo/config-plugins": "~55.0.8", "@expo/devtools": "55.0.2", - "@expo/fingerprint": "0.16.5", - "@expo/local-build-cache-provider": "55.0.6", - "@expo/log-box": "55.0.7", - "@expo/metro": "~54.2.0", - "@expo/metro-config": "55.0.9", + "@expo/fingerprint": "0.16.6", + "@expo/local-build-cache-provider": "55.0.11", + "@expo/log-box": "55.0.10", + "@expo/metro": "~55.0.0", + "@expo/metro-config": "55.0.16", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~55.0.10", - "expo-asset": "~55.0.8", - "expo-constants": "~55.0.7", - "expo-file-system": "~55.0.10", - "expo-font": "~55.0.4", - "expo-keep-awake": "~55.0.4", - "expo-modules-autolinking": "55.0.8", - "expo-modules-core": "55.0.14", + "babel-preset-expo": "~55.0.17", + "expo-asset": "~55.0.15", + "expo-constants": "~55.0.14", + "expo-file-system": "~55.0.16", + "expo-font": "~55.0.6", + "expo-keep-awake": "~55.0.6", + "expo-modules-autolinking": "55.0.17", + "expo-modules-core": "55.0.22", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" @@ -14687,16 +14282,16 @@ } }, "node_modules/expo-asset": { - "version": "55.0.8", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.8.tgz", - "integrity": "sha512-yEz2svDX67R0yiW2skx6dJmcE0q7sj9ECpGMcxBExMCbctc+nMoZCnjUuhzPl5vhClUsO5HFFXS5vIGmf1bgHQ==", + "version": "55.0.15", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.15.tgz", + "integrity": "sha512-d3FIpHJ6ZngYXxRItYWBGT5H8Wkk7/l4fMe8Mmd2xDyKrO0/CM7c8r/J5M71D+BJr5P3My8wertGYZXHSiZYxQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/image-utils": "^0.8.12", - "expo-constants": "~55.0.7" + "@expo/image-utils": "^0.8.13", + "expo-constants": "~55.0.14" }, "peerDependencies": { "expo": "*", @@ -14705,15 +14300,15 @@ } }, "node_modules/expo-constants": { - "version": "55.0.7", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.7.tgz", - "integrity": "sha512-kdcO4TsQRRqt0USvjaY5vgQMO9H52K3kBZ/ejC7F6rz70mv08GoowrZ1CYOr5O4JpPDRlIpQfZJUucaS/c+KWQ==", + "version": "55.0.14", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.14.tgz", + "integrity": "sha512-l23QVQCYBPKT5zbxxZdJeuhiunadvWdjcQ9+GC8h+02jCoLmWRk20064nCINnQTP3Hf+uLPteUiwYrJd0e446w==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/config": "~55.0.8", + "@expo/config": "~55.0.15", "@expo/env": "~2.1.1" }, "peerDependencies": { @@ -14722,9 +14317,9 @@ } }, "node_modules/expo-file-system": { - "version": "55.0.10", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.10.tgz", - "integrity": "sha512-ysFdVdUgtfj2ApY0Cn+pBg+yK4xp+SNwcaH8j2B91JJQ4OXJmnyCSmrNZYz7J4mdYVuv2GzxIP+N/IGlHQG3Yw==", + "version": "55.0.16", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.16.tgz", + "integrity": "sha512-EetQ/zVFK07Vmz4Yke0fvoES4xVwScTdd0PMoLekuMX7puE4op75pNnEdh1M0AeWzkqLrBoZIaU2ynSrKN5VZg==", "dev": true, "license": "MIT", "optional": true, @@ -14735,9 +14330,9 @@ } }, "node_modules/expo-font": { - "version": "55.0.4", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz", - "integrity": "sha512-ZKeGTFffPygvY5dM/9ATM2p7QDkhsaHopH7wFAWgP2lKzqUMS9B/RxCvw5CaObr9Ro7x9YptyeRKX2HmgmMfrg==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.6.tgz", + "integrity": "sha512-x9czUA3UQWjIwa0ZUEs/eWJNqB4mAue/m4ltESlNPLZhHL0nWWqIfsyHmklTLFH7mVfcHSJvew6k+pR2FE1zVw==", "dev": true, "license": "MIT", "optional": true, @@ -14752,9 +14347,9 @@ } }, "node_modules/expo-keep-awake": { - "version": "55.0.4", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.4.tgz", - "integrity": "sha512-vwfdMtMS5Fxaon8gC0AiE70SpxTsHJ+rjeoVJl8kdfdbxczF7OIaVmfjFJ5Gfigd/WZiLqxhfZk34VAkXF4PNg==", + "version": "55.0.6", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.6.tgz", + "integrity": "sha512-acJjeHqkNxMVckEcJhGQeIksqqsarscSHJtT559bNgyiM4r14dViQ66su7bb6qDVeBt0K7z3glXI1dHVck1Zgg==", "dev": true, "license": "MIT", "optional": true, @@ -14854,20 +14449,219 @@ "dependencies": { "base64-js": "^1.3.0" }, - "peerDependencies": { - "expo": "*" + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-server": { + "version": "55.0.7", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.7.tgz", + "integrity": "sha512-Cc1btFyPsD9P4DT2xd1pG/uR96TLVMx0W+dPm9Gjk1uDV9xuzvMcUsY7nf9bt4U5pGyWWkCXmPJcKwWfdl51Pw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/expo/node_modules/@expo/cli": { + "version": "55.0.24", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.24.tgz", + "integrity": "sha512-Z6Xh0WNTg1LvoZQ77zO3snF2cFiv1xf0VguDlwTL1Ql87oMOp30f7mjl9jeaSHqoWkgiAbmxgCKKIGjVX/keiA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~55.0.15", + "@expo/config-plugins": "~55.0.8", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.1.1", + "@expo/image-utils": "^0.8.13", + "@expo/json-file": "^10.0.13", + "@expo/log-box": "55.0.10", + "@expo/metro": "~55.0.0", + "@expo/metro-config": "~55.0.16", + "@expo/osascript": "^2.4.2", + "@expo/package-manager": "^1.10.4", + "@expo/plist": "^0.5.2", + "@expo/prebuild-config": "^55.0.15", + "@expo/require-utils": "^55.0.4", + "@expo/router-server": "^55.0.14", + "@expo/schema-utils": "^55.0.3", + "@expo/spawn-async": "^1.7.2", + "@expo/ws-tunnel": "^1.0.1", + "@expo/xcpretty": "^4.4.0", + "@react-native/dev-middleware": "0.83.4", + "accepts": "^1.3.8", + "arg": "^5.0.2", + "better-opn": "~3.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "dnssd-advertise": "^1.1.4", + "expo-server": "^55.0.7", + "fetch-nodeshim": "^0.4.10", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "multitars": "^0.2.3", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^4.0.3", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "resolve-from": "^5.0.0", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "source-map-support": "~0.5.21", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "terminal-link": "^2.1.1", + "toqr": "^0.1.1", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1", + "zod": "^3.25.76" + }, + "bin": { + "expo-internal": "build/bin/cli" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { + "version": "55.0.14", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.14.tgz", + "integrity": "sha512-YJjbeLMLp+ZjCnajHI+jEppNzXY372K0u4I4fLKGnA/loFX14aouDsg4tqZVGlZx6NUpnN8Bb3Tmw2BLTXT5Qw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "@expo/metro-runtime": "^55.0.9", + "expo": "*", + "expo-constants": "^55.0.13", + "expo-font": "^55.0.6", + "expo-router": "*", + "expo-server": "^55.0.7", + "react": "*", + "react-dom": "*", + "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + }, + "peerDependenciesMeta": { + "@expo/metro-runtime": { + "optional": true + }, + "expo-router": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@react-native/debugger-frontend": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.4.tgz", + "integrity": "sha512-mCE2s/S7SEjax3gZb6LFAraAI3x13gRVWJWqT0HIm71e4ITObENNTDuMw4mvZ/wr4Gz2wv4FcBH5/Nla9LXOcg==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/expo/node_modules/@react-native/debugger-shell": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.4.tgz", + "integrity": "sha512-FtAnrvXqy1xeZ+onwilvxEeeBsvBlhtfrHVIC2R/BOJAK9TbKEtFfjio0wsn3DQIm+UZq48DSa+p9jJZ2aJUww==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": ">= 20.19.4" + } + }, + "node_modules/expo/node_modules/@react-native/dev-middleware": { + "version": "0.83.4", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.4.tgz", + "integrity": "sha512-3s9nXZc/kj986nI2RPqxiIJeTS3o7pvZDxbHu7GE9WVIGX9YucA1l/tEiXd7BAm3TBFOfefDOT08xD46wH+R3Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.83.4", + "@react-native/debugger-shell": "0.83.4", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": ">= 20.19.4" } }, - "node_modules/expo-server": { - "version": "55.0.6", - "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.6.tgz", - "integrity": "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ==", + "node_modules/expo/node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "engines": { - "node": ">=20.16.0" + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/expo/node_modules/ansi-styles": { @@ -14907,16 +14701,51 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/expo/node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/expo/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/expo/node_modules/expo-modules-autolinking": { - "version": "55.0.8", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.8.tgz", - "integrity": "sha512-nrWB1pkNp7bR8ECUTgYUiJ2Pyh6AvxCBXZ+lyPlfl1TzEIGhwU1Yqr+d78eJDueXaW+9zKeE0HqrTZoLS3ve4A==", + "version": "55.0.17", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.17.tgz", + "integrity": "sha512-VhlEVGnP+xBjfSKDKNN7GAPKN2whIfV08jsZvNj7UGyJWpZYiO6Emx1FLP5xd1+JZVpIrt/kxR641kdcPo7Ehw==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@expo/require-utils": "^55.0.2", + "@expo/require-utils": "^55.0.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0" @@ -14926,9 +14755,9 @@ } }, "node_modules/expo/node_modules/expo-modules-core": { - "version": "55.0.14", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.14.tgz", - "integrity": "sha512-eAerOnnhbZitUAKbY7B61kIudiabAz/m/oMGINms2+GeY1DRhdvrm5aAkhkHHmykPrg58PPryXtmF14YAYWViw==", + "version": "55.0.22", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.22.tgz", + "integrity": "sha512-NC5GyvCHvnOvi5MtgLv68oUSrRP/0UORGzU/MX+7BIA8ctgBPxKSjPXPSfhwk3gMzj7eHBhYwlu0HJsIEnVd9A==", "dev": true, "license": "MIT", "optional": true, @@ -14938,7 +14767,13 @@ }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": "*", + "react-native-worklets": "^0.7.4 || ^0.8.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } } }, "node_modules/expo/node_modules/react-refresh": { @@ -14953,6 +14788,33 @@ "node": ">=0.10.0" } }, + "node_modules/expo/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/expo/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/expo/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -14968,6 +14830,18 @@ "node": ">=8" } }, + "node_modules/expo/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -15284,9 +15158,9 @@ "license": "MIT" }, "node_modules/fetch-nodeshim": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.9.tgz", - "integrity": "sha512-XIQWlB2A4RZ7NebXWGxS0uDMdvRHkiUDTghBVJKFg9yEOd45w/PP8cZANuPf2H08W6Cor3+2n7Q6TTZgAS3Fkw==", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", + "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", "dev": true, "license": "MIT", "optional": true, @@ -15519,6 +15393,15 @@ "node": ">= 0.6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/framer-motion": { "version": "11.18.2", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", @@ -15973,9 +15856,9 @@ } }, "node_modules/hermes-compiler": { - "version": "250829098.0.9", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.9.tgz", - "integrity": "sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==", + "version": "250829098.0.10", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.10.tgz", + "integrity": "sha512-TcRlZ0/TlyfJqquRFAWoyElVNnkdYRi/sEp4/Qy8/GYxjg8j2cS9D4MjuaQ+qimkmLN7AmO+44IznRf06mAr0w==", "dev": true, "license": "MIT", "optional": true, @@ -16459,290 +16342,107 @@ }, "node_modules/is-reference": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isomorphic-webcrypto": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/isomorphic-webcrypto/-/isomorphic-webcrypto-2.3.8.tgz", - "integrity": "sha512-XddQSI0WYlSCjxtm1AI8kWQOulf7hAN3k3DclF1sxDJZqOe0pcsOt675zvWW91cZH9hYs3nlA3Ev8QK5i80SxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@peculiar/webcrypto": "^1.0.22", - "asmcrypto.js": "^0.22.0", - "b64-lite": "^1.3.1", - "b64u-lite": "^1.0.1", - "msrcrypto": "^1.5.6", - "str2buf": "^1.3.0", - "webcrypto-shim": "^0.1.4" - }, - "optionalDependencies": { - "@unimodules/core": "*", - "@unimodules/react-native-adapter": "*", - "expo-random": "*", - "react-native-securerandom": "^0.1.1" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "@types/estree": "^1.0.6" } }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isomorphic-webcrypto": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/isomorphic-webcrypto/-/isomorphic-webcrypto-2.3.8.tgz", + "integrity": "sha512-XddQSI0WYlSCjxtm1AI8kWQOulf7hAN3k3DclF1sxDJZqOe0pcsOt675zvWW91cZH9hYs3nlA3Ev8QK5i80SxQ==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "@peculiar/webcrypto": "^1.0.22", + "asmcrypto.js": "^0.22.0", + "b64-lite": "^1.3.1", + "b64u-lite": "^1.0.1", + "msrcrypto": "^1.5.6", + "str2buf": "^1.3.0", + "webcrypto-shim": "^0.1.4" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@unimodules/core": "*", + "@unimodules/react-native-adapter": "*", + "expo-random": "*", + "react-native-securerandom": "^0.1.1" } }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jest-regex-util": { + "node_modules/jest-get-type": { "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "license": "MIT", "optional": true, @@ -16808,10 +16508,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "optional": true, @@ -17338,9 +17056,9 @@ "license": "MIT" }, "node_modules/lan-network": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.0.tgz", - "integrity": "sha512-EZgbsXMrGS+oK+Ta12mCjzBFse+SIewGdwrSTr5g+MSymnjpox2x05ceI20PQejJOFvOgzcXrfDk/SdY7dSCtw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.1.tgz", + "integrity": "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==", "dev": true, "license": "MIT", "optional": true, @@ -18377,22 +18095,22 @@ } }, "node_modules/metro": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz", - "integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", + "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@babel/code-frame": "^7.24.7", + "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.3", - "@babel/types": "^7.25.2", - "accepts": "^1.3.7", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", @@ -18400,25 +18118,25 @@ "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", - "hermes-parser": "0.32.0", + "hermes-parser": "0.33.3", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.83.3", - "metro-cache": "0.83.3", - "metro-cache-key": "0.83.3", - "metro-config": "0.83.3", - "metro-core": "0.83.3", - "metro-file-map": "0.83.3", - "metro-resolver": "0.83.3", - "metro-runtime": "0.83.3", - "metro-source-map": "0.83.3", - "metro-symbolicate": "0.83.3", - "metro-transform-plugins": "0.83.3", - "metro-transform-worker": "0.83.3", - "mime-types": "^2.1.27", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-config": "0.83.5", + "metro-core": "0.83.5", + "metro-file-map": "0.83.5", + "metro-resolver": "0.83.5", + "metro-runtime": "0.83.5", + "metro-source-map": "0.83.5", + "metro-symbolicate": "0.83.5", + "metro-transform-plugins": "0.83.5", + "metro-transform-worker": "0.83.5", + "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", @@ -18434,9 +18152,9 @@ } }, "node_modules/metro-babel-transformer": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz", - "integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", + "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", "dev": true, "license": "MIT", "optional": true, @@ -18444,7 +18162,7 @@ "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.32.0", + "hermes-parser": "0.33.3", "nullthrows": "^1.1.1" }, "engines": { @@ -18452,30 +18170,30 @@ } }, "node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "dev": true, "license": "MIT", "optional": true, "peer": true }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "hermes-estree": "0.32.0" + "hermes-estree": "0.33.3" } }, "node_modules/metro-cache": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz", - "integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", + "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", "dev": true, "license": "MIT", "optional": true, @@ -18484,16 +18202,16 @@ "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", - "metro-core": "0.83.3" + "metro-core": "0.83.5" }, "engines": { "node": ">=20.19.4" } }, "node_modules/metro-cache-key": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz", - "integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", + "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", "dev": true, "license": "MIT", "optional": true, @@ -18506,9 +18224,9 @@ } }, "node_modules/metro-config": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz", - "integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", + "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", "dev": true, "license": "MIT", "optional": true, @@ -18517,10 +18235,10 @@ "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", - "metro": "0.83.3", - "metro-cache": "0.83.3", - "metro-core": "0.83.3", - "metro-runtime": "0.83.3", + "metro": "0.83.5", + "metro-cache": "0.83.5", + "metro-core": "0.83.5", + "metro-runtime": "0.83.5", "yaml": "^2.6.1" }, "engines": { @@ -18528,9 +18246,9 @@ } }, "node_modules/metro-core": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz", - "integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", + "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", "dev": true, "license": "MIT", "optional": true, @@ -18538,16 +18256,16 @@ "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", - "metro-resolver": "0.83.3" + "metro-resolver": "0.83.5" }, "engines": { "node": ">=20.19.4" } }, "node_modules/metro-file-map": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz", - "integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", + "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", "dev": true, "license": "MIT", "optional": true, @@ -18568,9 +18286,9 @@ } }, "node_modules/metro-minify-terser": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz", - "integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", + "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", "dev": true, "license": "MIT", "optional": true, @@ -18584,9 +18302,9 @@ } }, "node_modules/metro-resolver": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz", - "integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", + "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", "dev": true, "license": "MIT", "optional": true, @@ -18599,9 +18317,9 @@ } }, "node_modules/metro-runtime": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz", - "integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", + "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", "dev": true, "license": "MIT", "optional": true, @@ -18615,22 +18333,21 @@ } }, "node_modules/metro-source-map": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz", - "integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", + "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@babel/traverse": "^7.25.3", - "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", - "@babel/types": "^7.25.2", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-symbolicate": "0.83.3", + "metro-symbolicate": "0.83.5", "nullthrows": "^1.1.1", - "ob1": "0.83.3", + "ob1": "0.83.5", "source-map": "^0.5.6", "vlq": "^1.0.0" }, @@ -18639,9 +18356,9 @@ } }, "node_modules/metro-symbolicate": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz", - "integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", + "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", "dev": true, "license": "MIT", "optional": true, @@ -18649,7 +18366,7 @@ "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", - "metro-source-map": "0.83.3", + "metro-source-map": "0.83.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" @@ -18662,18 +18379,18 @@ } }, "node_modules/metro-transform-plugins": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz", - "integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", + "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.3", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" }, @@ -18682,32 +18399,48 @@ } }, "node_modules/metro-transform-worker": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz", - "integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", + "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.25.2", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", - "@babel/types": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", - "metro": "0.83.3", - "metro-babel-transformer": "0.83.3", - "metro-cache": "0.83.3", - "metro-cache-key": "0.83.3", - "metro-minify-terser": "0.83.3", - "metro-source-map": "0.83.3", - "metro-transform-plugins": "0.83.3", + "metro": "0.83.5", + "metro-babel-transformer": "0.83.5", + "metro-cache": "0.83.5", + "metro-cache-key": "0.83.5", + "metro-minify-terser": "0.83.5", + "metro-source-map": "0.83.5", + "metro-transform-plugins": "0.83.5", "nullthrows": "^1.1.1" }, "engines": { "node": ">=20.19.4" } }, + "node_modules/metro/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/metro/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -18745,34 +18478,25 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/metro/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "dev": true, "license": "MIT", "optional": true, "peer": true }, "node_modules/metro/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "hermes-estree": "0.32.0" + "hermes-estree": "0.33.3" } }, "node_modules/metro/node_modules/image-size": { @@ -18793,6 +18517,37 @@ "node": ">=16.x" } }, + "node_modules/metro/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/metro/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/metro/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -19321,9 +19076,9 @@ } }, "node_modules/multitars": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/multitars/-/multitars-0.2.4.tgz", - "integrity": "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/multitars/-/multitars-0.2.5.tgz", + "integrity": "sha512-T/i4uZOzd4j2VnS28eAOJS0MgeAbcsFIijRPeLRhVv54hP9OqsC/FjYK0JmMTWxGhF2fv34oH1mtR6XLBKkNlw==", "dev": true, "license": "MIT", "optional": true, @@ -19435,9 +19190,9 @@ "license": "MIT" }, "node_modules/node-forge": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", - "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "optional": true, @@ -19576,18 +19331,6 @@ "node": ">=6" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-package-arg": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", @@ -19685,9 +19428,9 @@ } }, "node_modules/ob1": { - "version": "0.83.3", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz", - "integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==", + "version": "0.83.5", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", + "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", "dev": true, "license": "MIT", "optional": true, @@ -20005,22 +19748,10 @@ "aggregate-error": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json-from-dist": { @@ -20157,9 +19888,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", "dev": true, "license": "BlueOak-1.0.0", "optional": true, @@ -20310,18 +20041,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -21120,36 +20839,33 @@ } }, "node_modules/react-native": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.84.1.tgz", - "integrity": "sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.85.1.tgz", + "integrity": "sha512-1K2TIvu2M1C8gqkPevi/MuLan16mQvEdURiTlwHgrb6S2vvkDyik6TrkkXMlMMhz9hF5RT8wFyDUdlpGFlkpXg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.84.1", - "@react-native/codegen": "0.84.1", - "@react-native/community-cli-plugin": "0.84.1", - "@react-native/gradle-plugin": "0.84.1", - "@react-native/js-polyfills": "0.84.1", - "@react-native/normalize-colors": "0.84.1", - "@react-native/virtualized-lists": "0.84.1", + "@react-native/assets-registry": "0.85.1", + "@react-native/codegen": "0.85.1", + "@react-native/community-cli-plugin": "0.85.1", + "@react-native/gradle-plugin": "0.85.1", + "@react-native/js-polyfills": "0.85.1", + "@react-native/normalize-colors": "0.85.1", + "@react-native/virtualized-lists": "0.85.1", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", - "babel-jest": "^29.7.0", - "babel-plugin-syntax-hermes-parser": "0.32.0", + "babel-plugin-syntax-hermes-parser": "0.33.3", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", - "hermes-compiler": "250829098.0.9", + "hermes-compiler": "250829098.0.10", "invariant": "^2.2.4", - "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.83.3", - "metro-source-map": "^0.83.3", + "metro-runtime": "^0.84.0", + "metro-source-map": "^0.84.0", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", @@ -21168,13 +20884,17 @@ "react-native": "cli.js" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { + "@react-native/jest-preset": "0.85.1", "@types/react": "^19.1.1", "react": "^19.2.3" }, "peerDependenciesMeta": { + "@react-native/jest-preset": { + "optional": true + }, "@types/react": { "optional": true } @@ -21195,48 +20915,39 @@ } }, "node_modules/react-native/node_modules/@react-native/codegen": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", - "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.1.tgz", + "integrity": "sha512-Ge8F5VejnI7ng/NGObqBBovuLbItvmmZDFQ1Qwt/nBhHtk7l2tOffNMVNTta9Jt8TW0oXxVj6FG3hr6nx03JrQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "@babel/core": "^7.25.2", - "@babel/parser": "^7.25.3", - "hermes-parser": "0.32.0", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.33.3", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" }, "engines": { - "node": ">= 20.19.4" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { "@babel/core": "*" } }, - "node_modules/react-native/node_modules/@react-native/normalize-colors": { - "version": "0.84.1", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.84.1.tgz", - "integrity": "sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", - "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", + "integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "hermes-parser": "0.32.0" + "hermes-parser": "0.33.3" } }, "node_modules/react-native/node_modules/commander": { @@ -21252,24 +20963,101 @@ } }, "node_modules/react-native/node_modules/hermes-estree": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "dev": true, "license": "MIT", "optional": true, "peer": true }, "node_modules/react-native/node_modules/hermes-parser": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { - "hermes-estree": "0.32.0" + "hermes-estree": "0.33.3" + } + }, + "node_modules/react-native/node_modules/metro-runtime": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz", + "integrity": "sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/react-native/node_modules/metro-source-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz", + "integrity": "sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.3", + "nullthrows": "^1.1.1", + "ob1": "0.84.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/react-native/node_modules/metro-symbolicate": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.3.tgz", + "integrity": "sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/react-native/node_modules/ob1": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.3.tgz", + "integrity": "sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, "node_modules/react-native/node_modules/react-refresh": { @@ -21577,9 +21365,9 @@ "peer": true }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -21971,9 +21759,9 @@ } }, "node_modules/sax": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", - "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, "license": "BlueOak-1.0.0", "optional": true, @@ -22536,21 +22324,6 @@ "plist": "^3.0.5" } }, - "node_modules/simple-plist/node_modules/bplist-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", - "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "big-integer": "1.6.x" - }, - "engines": { - "node": ">= 5.10.0" - } - }, "node_modules/simple-swizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", @@ -22569,22 +22342,10 @@ "optional": true, "peer": true }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/slugify": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", - "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", + "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", "dev": true, "license": "MIT", "optional": true, @@ -22774,6 +22535,18 @@ } } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -22819,33 +22592,6 @@ "node": "*" } }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -23334,9 +23080,9 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -23363,84 +23109,6 @@ "optional": true, "peer": true }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -23631,9 +23299,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -23722,18 +23390,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/type-fest": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", @@ -25514,6 +25170,24 @@ "@types/node": "*" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -25619,22 +25293,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", @@ -25685,6 +25343,27 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xml2js": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", @@ -25751,9 +25430,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "dev": true, "license": "ISC", "optional": true, diff --git a/package.json b/package.json index 3a6039cf..78d8920a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "adminizer", "type": "module", - "version": "4.5.0", + "version": "4.6.0", "main": "index.js", "exports": { ".": "./index.js", @@ -77,6 +77,7 @@ "uuid": "^11.0.5", "waterline": "^0.15.2", "winston": "^3.17.0", + "xlsx": "^0.18.5", "zod": "^4.1.11" }, "devDependencies": { diff --git a/src/assets/js/components/add-form.tsx b/src/assets/js/components/add-form.tsx index 9c327326..398d02a5 100644 --- a/src/assets/js/components/add-form.tsx +++ b/src/assets/js/components/add-form.tsx @@ -193,24 +193,27 @@ const AddForm: FC<{ }; return ( -
+ <> {!catalog && -
- - +
+
+ + +
} -
+ }
+ ); }; diff --git a/src/assets/js/components/add-user-form.tsx b/src/assets/js/components/add-user-form.tsx index 9441734a..b802dd49 100644 --- a/src/assets/js/components/add-user-form.tsx +++ b/src/assets/js/components/add-user-form.tsx @@ -1,19 +1,19 @@ -import {type SharedData} from '@/types'; -import {Link, useForm, usePage} from "@inertiajs/react"; -import {Icon} from "@/components/icon.tsx"; -import {LoaderCircle, MoveLeft, Info} from "lucide-react"; -import {Button} from "@/components/ui/button.tsx"; -import {Label} from "@/components/ui/label.tsx"; -import {Input} from "@/components/ui/input.tsx"; +import { type SharedData } from '@/types'; +import { Link, useForm, usePage } from "@inertiajs/react"; +import { Icon } from "@/components/icon.tsx"; +import { LoaderCircle, MoveLeft, Info, RefreshCw, Copy, Key } from "lucide-react"; +import { Button } from "@/components/ui/button.tsx"; +import { Label } from "@/components/ui/label.tsx"; +import { Input } from "@/components/ui/input.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select" -import {FormEventHandler, useEffect, useState} from "react"; -import {Checkbox} from "@/components/ui/checkbox" +} from "@/components/ui/select"; +import { FormEventHandler, useEffect, useState } from "react"; +import { Checkbox } from "@/components/ui/checkbox" import { Tooltip, TooltipContent, @@ -23,18 +23,11 @@ import { import InputError from "@/components/input-error.tsx"; import MaterialIcon from "@/components/material-icon.tsx"; import axios from "axios"; +import { toast } from "sonner"; type value = string | boolean | Date | Record[] -interface Field { - label: string; - type: string; - name: string; - tooltip?: string; - value: value; -} - -interface AddUserProps extends SharedData { +interface AddUserFormProps extends SharedData { edit: boolean; view: boolean; btnBack: { @@ -55,13 +48,25 @@ interface AddUserProps extends SharedData { fields: Field[] groups: Field[] locales: Record[] + apiKey?: string; +} + +interface Field { + label: string; + type: string; + name: string; + tooltip?: string; + value: value; } export default function AddUserForm() { - const page = usePage() + const page = usePage() const [timezones, setTimezones] = useState[]>() - const {fields, groups, auth} = page.props; + const { fields, groups, auth } = page.props; + const [showApiKey, setShowApiKey] = useState(false); + const [currentApiKey, setCurrentApiKey] = useState(page.props.apiKey); + const [showRegenerateDialog, setShowRegenerateDialog] = useState(false); const { data, setData, @@ -101,18 +106,40 @@ export default function AddUserForm() { setData(fieldName, value); } + const handleRegenerateApiKey = async () => { + try { + const response = await axios.post('/adminizer/api/user-key/regenerate'); + setCurrentApiKey(response.data.apiKey); + toast.success('User API key regenerated'); + } catch (error) { + console.error('Failed to regenerate user API key:', error); + toast.error('Failed to regenerate user API key'); + } + setShowRegenerateDialog(false); + }; + + const copyApiKey = async () => { + if (!currentApiKey) return; + try { + await navigator.clipboard.writeText(currentApiKey); + toast.success('API key copied to clipboard'); + } catch (err) { + toast.error('Failed to copy'); + } + }; + return (
@@ -168,14 +195,14 @@ export default function AddUserForm() {
@@ -200,7 +227,7 @@ export default function AddUserForm() { {getField('isAdmin') && (
+ htmlFor={getField('isAdmin')?.name}>{getField('isAdmin')?.label} + htmlFor={getField('isConfirmed')?.name}>{getField('isConfirmed')?.label} @@ -248,7 +275,7 @@ export default function AddUserForm() { {auth.user.isAdministrator && ( @@ -258,7 +285,7 @@ export default function AddUserForm() { e.preventDefault()}> - +

{getField('userPassword')?.tooltip}

@@ -296,7 +323,7 @@ export default function AddUserForm() { disabled={processing || page.props.view} placeholder={getField('repeatUserPassword')?.label} /> - +
{page.props.groups.length > 0 && ( @@ -306,7 +333,7 @@ export default function AddUserForm() { {page.props.groups.map(group => (
+ htmlFor={group.name}>{group.label} )} + + {/* User API Key Section — only in edit mode */} + {page.props.edit && !page.props.view && ( + <> +

+ + User API Key (for feeds) +

+
+ {currentApiKey && ( +
+ + {showApiKey ? currentApiKey : '••••••••••••••••••••••••'} + + + {showApiKey && ( + + )} +
+ )} + +

+ Regenerating this key will invalidate all existing feed URLs. + You will need to update the ?userKey= parameter in all feed links. +

+
+ + )} + {/* Regenerate API Key Confirmation Dialog */} + {showRegenerateDialog && ( +
+
+

Regenerate User API Key

+

+ This will invalidate all existing feed URLs. You will need to update them with the new userKey. +

+
+ + +
+
+
+ )}
diff --git a/src/assets/js/components/list-table.tsx b/src/assets/js/components/list-table.tsx deleted file mode 100644 index 05c25e4b..00000000 --- a/src/assets/js/components/list-table.tsx +++ /dev/null @@ -1,432 +0,0 @@ -import {type Columns, SharedData} from '@/types'; -import {useTableColumns} from "@/components/table/columns" -import {DataTable} from "@/components/table/data-table" -import {Link, router, usePage} from "@inertiajs/react"; -import {ColumnDef} from "@tanstack/react-table"; -import {Button} from "@/components/ui/button.tsx"; -import {BetweenHorizontalStart, Eye, Pencil, SquarePlus, Search, RefreshCcw} from "lucide-react"; -import {Icon} from "@/components/icon.tsx"; -import MaterialIcon from "@/components/material-icon.tsx"; -import {DropdownMenu} from "@radix-ui/react-dropdown-menu"; -import { - DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, - DropdownMenuTrigger -} from "@/components/ui/dropdown-menu.tsx"; -import {useCallback, useEffect, useMemo, useRef, useState} from "react"; -import {toast} from "sonner"; -import {Toaster} from "@/components/ui/sonner" -import DeleteModal from "@/components/modals/del-modal.tsx"; -import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select.tsx"; -import {generatePagination} from "@/lib/pagination.ts"; -import PaginationRender from "@/components/pagination-render.tsx"; - -interface Action { - id: string, - title: string, - icon: string, - type: 'blank' | 'self', - link: string -} - -interface ExtendedSharedData extends SharedData { - data: { - data: any[], - recordsTotal: number - recordsFiltered: number - } - columns: Columns, - header: { - actions: Action[], - inlineActions: Action[], - thActionsTitle: string - crudActions: { - createTitle: string; - editTitle: string; - viewsTitle: string; - deleteTitle: string; - }, - entity: { - name: string; - uri: string - }, - notFoundContent: string, - searchBtn: string, - resetBtn: string - delModal: { - yes: string, - no: string - text: string - } - } -} - -const ListTable = () => { - const page = usePage() - const data = page.props.data - const [loading, setLoading] = useState(false) - // States for parameters - const [searchValue, setSearchValue] = useState('') - const [showSearch, setShowSearch] = useState(false) - const [currentPage, setCurrentPage] = useState(1) - const [count, setCount] = useState('5') - const [sortColumn, setSortColumn] = useState('1') - const [sortDirection, setSortDirection] = useState('desc') - - // Ref for storing search parameters by columns - const queryColumnsRef = useRef<{ key: string, value: string }[]>([]) - - // Initializing states from URL on load - useEffect(() => { - const searchParams = new URLSearchParams(window.location.search) - console.log(page.props) - setSearchValue(searchParams.get('globalSearch') || '') - setShowSearch(!!searchParams.get('globalSearch')) - setCurrentPage(parseInt(searchParams.get('page') || '1')) - setCount(searchParams.get('count') || '5') - setSortColumn(searchParams.get('column') || '1') - setSortDirection(searchParams.get('direction') || 'desc') - - // Initializing queryColumnsRef from URL - const searchColumns = searchParams.getAll('searchColumn') - const searchValues = searchParams.getAll('searchColumnValue') - queryColumnsRef.current = searchColumns.map((col, i) => ({ - key: col, - value: searchValues[i] - })) - }, []) - - useEffect(() => { - // fix menu after deletion and redirect - document.body.removeAttribute('style') - }, [data]) - - - const pagination = useMemo(() => { - return generatePagination( - data.recordsFiltered, - parseInt(count), - currentPage, - 5 - ) - }, [data.recordsFiltered, count, currentPage, searchValue]) - - useEffect(() => { - if (page.props.flash) { - if (page.props.flash.success) { - toast.success(page.props.flash.success) - } - if (page.props.flash.error) { - toast.error(page.props.flash.error) - } - } - }, [page.props.flash]) - - const changeCount = useCallback((newCount: string) => { - setCount(newCount) - setCurrentPage(1) - setLoading(true) - router.visit(`${page.props.header.entity.uri}?count=${newCount}&page=1`, { - preserveState: true, - only: ['data', 'columns', 'header'], - onSuccess: () => setLoading(false) - }) - }, [data]) - - const buildQueryString = useCallback( - (pageOverride?: number, initSortDirection?: string, initSortColumn?: string, initSearchValue?: string) => { - const pageToUse = pageOverride !== undefined ? pageOverride : currentPage - const initSortDir = initSortDirection !== undefined ? initSortDirection : sortDirection - const initSortCol = initSortColumn !== undefined ? initSortColumn : sortColumn - const initSearchVal = initSearchValue !== undefined ? initSearchValue : searchValue - - const baseParams = `count=${count}&page=${pageToUse}&column=${initSortCol}&direction=${initSortDir}` - const searchParam = initSearchVal ? `&globalSearch=${initSearchVal}` : '' - const columnParams = queryColumnsRef.current - .map(item => `&searchColumn=${item.key}&searchColumnValue=${item.value}`) - .join('') - - return `${baseParams}${searchParam}${columnParams}` - }, [data, count, currentPage, sortColumn, sortDirection, searchValue]) - - const handlePageChange = useCallback((value: number) => { - setCurrentPage(value) - setLoading(true) - router.visit(`${page.props.header.entity.uri}?${buildQueryString(value)}`, { - preserveState: true, - only: ['data', 'columns', 'header'], - onSuccess: () => setLoading(false) - }) - }, [buildQueryString, page.props.header.entity.uri]) - - const handleCustomSort = useCallback((column: string, sortDirection: string) => { - setSortColumn(column) - setSortDirection(sortDirection) - setLoading(true) - router.visit(`${page.props.header.entity.uri}?${buildQueryString(undefined, sortDirection, column)}`, { - preserveState: true, - only: ['data', 'columns', 'header'], - onSuccess: () => setLoading(false) - }) - }, [count, currentPage, searchValue]) - - const handleGlobalSearch = useCallback((value: string) => { - setSearchValue(value) - setCurrentPage(1) - }, []) - - const handleSearch = useCallback(() => { - setLoading(true) - router.visit(`${page.props.header.entity.uri}?${buildQueryString(1)}`, { - preserveState: true, - only: ['data', 'columns', 'header'], - onSuccess: () => setLoading(false) - }) - }, [page.props.header.entity.uri, buildQueryString]) - - const handleColumnSearch = useCallback((key: string, value: string) => { - setCurrentPage(1) - const existingIndex = queryColumnsRef.current.findIndex(item => item.key === key) - if (existingIndex >= 0) { - queryColumnsRef.current.splice(existingIndex, 1) - } - if (value) { - queryColumnsRef.current.push({key, value}) - } - }, [data]) - - const resetForm = () => { - setShowSearch(false) - setSearchValue('') - queryColumnsRef.current = [] - setCurrentPage(1) - setSortColumn('1') - setSortDirection('desc') - - router.visit(`${page.props.header.entity.uri}?count=${count}&page=1`, { - preserveState: true, - preserveScroll: true, - only: ['data', 'columns', 'header'], - onSuccess: () => setLoading(false) - }) - } - - const dynamicColumns = useTableColumns( - page.props.columns, - handleCustomSort, - handleColumnSearch, - handleSearch, - showSearch - ); - - const tableColumns = useMemo(() => { - const baseColumns: ColumnDef[] = [ - { - accessorKey: 'actions', - header: () => ( -
- {page.props.header.thActionsTitle} -
- ), - cell: ({row}) => { - return ( -
- {(['deleteTitle', 'viewsTitle', 'editTitle'] as const).some(key => - page.props.header.crudActions?.[key] && page.props.header.crudActions?.[key].trim() !== '' - ) && - - - - - - - {page.props.header.crudActions?.editTitle && ( - - { - localStorage.setItem('backUrl', window.location.pathname + window.location.search) - }}> - - {page.props.header.crudActions.editTitle} - - - )} - {page.props.header.crudActions?.viewsTitle && ( - - - - {page.props.header.crudActions.viewsTitle} - - - )} - {page.props.header.crudActions?.deleteTitle && ( - - - - )} - - {page.props.header.inlineActions && page.props.header.inlineActions.length > 0 && ( - <> - - {page.props.header.inlineActions.map((action: Action) => ( - - {action.type === 'blank' ? ( - - {action.icon && } - {action.title} - - ) : ( - - {action.icon && } - {action.title} - - )} - - - ))} - - )} - - - } -
- ) - } - } - ] - return [...baseColumns, ...dynamicColumns]; - }, [ - page.props.header.thActionsTitle, - page.props.header.crudActions, - page.props.header.inlineActions, - page.props.header.entity.uri, - page.props.header.entity.name, - page.props.header.delModal, - dynamicColumns - ]); - - - return ( - <> - -
-
- {page.props.header.crudActions?.createTitle && ( - - )} - - {page.props.header.actions.length > 0 && ( - <> -
- {page.props.header.actions.map((action) => ( - - )) || null} -
-
- - - - - - - {page.props.header.actions.map((action) => ( - - )) || null} - - - -
- - )} -
- -
-
-

Show {pagination.from} - {pagination.to} of {pagination.total}

-
- -
-
-
- {data.data.length > 0 && } -
-
-
- - ); -} -export default ListTable diff --git a/src/assets/js/components/list-table/actions-column.tsx b/src/assets/js/components/list-table/actions-column.tsx new file mode 100644 index 00000000..94cb4430 --- /dev/null +++ b/src/assets/js/components/list-table/actions-column.tsx @@ -0,0 +1,41 @@ +import {ColumnDef} from '@tanstack/react-table'; +import {RowActionsDropdown} from './row-actions-dropdown.tsx'; +import {HeaderConfig} from './types.ts'; + +interface ActionsColumnOptions { + thActionsTitle: string; + crudActions: HeaderConfig['crudActions']; + inlineActions: HeaderConfig['inlineActions']; + entityUri: string; + entityName: string; + delModal: HeaderConfig['delModal']; +} + +export function createActionsColumn(options: ActionsColumnOptions): ColumnDef { + return { + accessorKey: 'actions', + header: () => ( +
+ {options.thActionsTitle} +
+ ), + cell: ({row}) => { + return ( +
+ {(['deleteTitle', 'viewsTitle', 'editTitle'] as const).some(key => + options.crudActions?.[key] && options.crudActions?.[key].trim() !== '' + ) && + + } +
+ ); + } + }; +} diff --git a/src/assets/js/components/list-table/filter-panel-saved-filters.tsx b/src/assets/js/components/list-table/filter-panel-saved-filters.tsx new file mode 100644 index 00000000..bd06bb10 --- /dev/null +++ b/src/assets/js/components/list-table/filter-panel-saved-filters.tsx @@ -0,0 +1,297 @@ +import { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button.tsx'; +import { Badge } from '@/components/ui/badge.tsx'; +import { Play, Settings, Lock, Globe, Users, Filter } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import axios from 'axios'; +import DeleteModal from '@/components/modals/del-modal'; +import MaterialIcon from '@/components/material-icon.tsx'; +import { useFilterTranslations } from './use-filter-translations'; + +interface SavedFilter { + id: string; + name: string; + description?: string; + modelName: string; + conditions: any[]; + sortField?: string; + sortDirection?: 'ASC' | 'DESC'; + visibility: 'private' | 'public' | 'groups' | 'system'; + owner: number | any; + ownerId?: number; + ownerInfo?: { + id: number; + login: string; + fullName?: string; + }; + icon?: string; + color?: string; + resultCount?: number; + createdAt: string; + updatedAt: string; +} + +interface SavedFiltersListProps { + modelName: string; + onApplyFilter?: (filter: SavedFilter) => void; + onEditFilter?: (filter: SavedFilter) => void; + onDeleteFilter?: (filterId: string) => void; + activeFilterId?: string; + user?: { + id: number; + isAdministrator?: boolean; + groups?: Array<{ id: number; name: string }>; + }; +} + +export function SavedFiltersList({ + modelName, + onApplyFilter, + onEditFilter, + onDeleteFilter, + activeFilterId, + user +}: SavedFiltersListProps) { + const [filters, setFilters] = useState([]); + const [loading, setLoading] = useState(false); + const { t } = useFilterTranslations(modelName); + + useEffect(() => { + loadFilters(); + + // Подписка на событие обновления фильтров + const handleFilterChanged = () => { + loadFilters(); + }; + + window.addEventListener('adminizer:filterChanged', handleFilterChanged); + return () => { + window.removeEventListener('adminizer:filterChanged', handleFilterChanged); + }; + }, [modelName]); + + const loadFilters = async () => { + setLoading(true); + try { + const response = await axios.get(`/adminizer/model/${modelName}/saved-filters`); + setFilters(response.data.filters || []); + } catch (error: any) { + console.error('Error loading saved filters:', error); + + // Если 401 - редирект на логин + if (error.response?.status === 401) { + console.error('Session expired, redirecting to login...'); + window.location.href = '/adminizer/model/userap/login'; + return; + } + } finally { + setLoading(false); + } + }; + + const handleApply = (filter: SavedFilter) => { + onApplyFilter?.(filter); + }; + + const handleDelete = async (filter: SavedFilter) => { + try { + await axios.delete(`/adminizer/model/${modelName}/filter/${filter.id}`); + onDeleteFilter?.(filter.id); + loadFilters(); // Перезагружаем список + } catch (error: any) { + console.error('Error deleting filter:', error); + + // Если 401 - редирект на логин + if (error.response?.status === 401) { + console.error('Session expired, redirecting to login...'); + window.location.href = '/adminizer/model/userap/login'; + return; + } + + // Если 403 - нет прав + if (error.response?.status === 403) { + alert(t('No permission to delete filter')); + return; + } + } + }; + + const handleEdit = async (filter: SavedFilter) => { + try { + // Fetch fresh filter payload to avoid editing stale conditions from local list cache. + const response = await axios.get(`/adminizer/model/${modelName}/saved-filters`); + const savedFilters = response.data?.filters || []; + const freshFilter = savedFilters.find((f: SavedFilter) => String(f.id) === String(filter.id)); + onEditFilter?.(freshFilter || filter); + } catch (error) { + console.error('Error loading fresh filter for edit:', error); + onEditFilter?.(filter); + } + }; + + // Проверка прав на редактирование/удаление + const canEditOrDelete = (filter: SavedFilter): boolean => { + // Админ может всё + if (user?.isAdministrator) { + return true; + } + // Владелец может редактировать своё + if (filter.ownerId && user?.id && filter.ownerId === user.id) { + return true; + } + // Также проверяем owner (старое поле) + if (filter.owner && user?.id && filter.owner.id === user.id) { + return true; + } + // Также проверяем ownerInfo + if (filter.ownerInfo && user?.id && filter.ownerInfo.id === user.id) { + return true; + } + // Обычные пользователи могут редактировать ТОЛЬКО свои фильтры + // Групповые и публичные фильтры — только админ может редактировать + return false; + }; + + const isMaterialIconName = (iconName?: string): iconName is string => { + return Boolean(iconName) && /^[a-z0-9_]+$/i.test(iconName); + }; + + if (loading) { + return ( +
+ {t('Loading filters...')} +
+ ); + } + + if (filters.length === 0) { + return ( +
+ +

{t('No saved filters')}

+

{t('Save current filter for quick access')}

+
+ ); + } + + return ( +
+ {filters.map((filter) => { + const canEdit = canEditOrDelete(filter); + const isOwner = filter.ownerId === user?.id || + filter.owner?.id === user?.id || + filter.ownerInfo?.id === user?.id; + + return ( +
+ {/* Иконка и название */} +
handleApply(filter)} + > + {filter.icon && ( + + {isMaterialIconName(filter.icon) + ? + : {filter.icon}} + + )} +
+
+ {filter.name} + {/* Иконка видимости */} + {filter.visibility === 'private' && ( + + + + )} + {filter.visibility === 'groups' && ( + + + + )} + {filter.visibility === 'public' && ( + + + + )} +
+ {filter.description && ( +
+ {filter.description} +
+ )} + {/* Показываем владельца для чужих приватных фильтров */} + {!isOwner && filter.visibility === 'private' && filter.ownerInfo && ( +
+ {t('Owner')}: {filter.ownerInfo.fullName || filter.ownerInfo.login} +
+ )} +
+
+ + {/* Количество результатов */} + {filter.resultCount !== undefined && filter.resultCount >= 0 && ( + + {filter.resultCount} + + )} + + {/* Кнопки действий */} +
+ + {canEdit && ( + <> + + { + handleDelete(filter); + }} + isLink={false} + variant="ghost" + btnCLass="h-7 w-7 p-0 text-destructive hover:text-destructive" + /> + + )} +
+
+ ); + })} +
+ ); +} diff --git a/src/assets/js/components/list-table/filter-panel.tsx b/src/assets/js/components/list-table/filter-panel.tsx new file mode 100644 index 00000000..54443955 --- /dev/null +++ b/src/assets/js/components/list-table/filter-panel.tsx @@ -0,0 +1,2837 @@ +import {useState, useEffect, useRef, useCallback, useMemo} from 'react'; +import {router, usePage} from '@inertiajs/react'; +import axios from 'axios'; +import {format} from 'date-fns'; +import {ru} from 'date-fns/locale'; +import { + DialogStack, + DialogStackBody, + DialogStackContent, + DialogStackDescription, + DialogStackFooter, + DialogStackHeader, + DialogStackOverlay, + DialogStackTitle, +} from '@/components/ui/dialog-stack.tsx'; +import {Button} from '@/components/ui/button.tsx'; +import {Input} from '@/components/ui/input.tsx'; +import {Textarea} from '@/components/ui/textarea.tsx'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select.tsx'; +import {Popover, PopoverContent, PopoverTrigger} from '@/components/ui/popover.tsx'; +import {Calendar} from '@/components/ui/calendar.tsx'; +import {Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList} from '@/components/ui/command.tsx'; +import {Checkbox} from '@/components/ui/checkbox.tsx'; +import {Switch} from '@/components/ui/switch.tsx'; +import {SavedFiltersList} from './filter-panel-saved-filters'; +import {GroupVisibilitySelector} from './group-visibility-selector'; +import {Plus, X, Save, Trash2, Play, Settings, Columns3, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, Lock, Globe, Users} from 'lucide-react'; +import MaterialIcon from '@/components/material-icon.tsx'; +import {toast} from 'sonner'; +import {cn} from '@/lib/utils'; +import { useFilterTranslations } from './use-filter-translations'; + +export interface ColumnConfig { + fieldName: string; + label: string; + type: string; + order: number; +} + +export interface ActiveFilter { + id: string; + fieldId: string; + relationField?: string; + customHandler?: string; + enabled: boolean; + condition: string; + value: string | number | boolean | Date | Record | (string | number | Date)[]; +} + +export interface RelationFilterField { + id: string; + label: string; + type: string; + options?: {value: string | number; label: string}[]; +} + +export interface FilterField { + id: string; + label: string; + type: string; + options?: {value: string | number; label: string}[]; + isRelation?: boolean; + relationFields?: RelationFilterField[]; + isCustomFilter?: boolean; + customFilterHandlerId?: string; + customFilterConditionLabel?: string; + customFilterInputConfig?: Record; + required?: boolean; +} + +type FilterConditionOption = { value: string; label: string }; + +interface FilterPanelProps { + onApplyFilters?: (filters: ActiveFilter[]) => void; +} + +interface TemporaryFilterMeta { + name: string; + conditionCount: number; + isModified?: boolean; +} + +interface TemporaryFilterPayload { + name: string; + conditions: any[]; + columns?: Array<{ fieldName: string; order: number }>; +} + +const TEMPORARY_FILTER_ID = 'temporary'; + +const getRouteUrl = (url?: string) => { + if (url) { + return url; + } + + if (typeof window === 'undefined') { + return '/'; + } + + return `${window.location.pathname}${window.location.search}`; +}; + +const getUrlFromRoute = (url?: string) => { + const origin = typeof window !== 'undefined' ? window.location.origin : 'http://localhost'; + return new URL(getRouteUrl(url), origin); +}; + +const getModelNameFromPathname = (pathname: string) => { + const pathParts = pathname.split('/'); + const entityIndex = pathParts.findIndex(p => p === 'model'); + return entityIndex !== -1 && entityIndex + 1 < pathParts.length + ? pathParts[entityIndex + 1] + : ''; +}; + +const normalizeFilterId = (filterId: string | null) => { + if (!filterId || filterId === 'null' || filterId === 'undefined') { + return undefined; + } + + return filterId; +}; + +const useFilterRouteState = (pageUrl?: string) => { + const routeState = useMemo(() => { + const url = getUrlFromRoute(pageUrl); + return { + modelName: getModelNameFromPathname(url.pathname), + activeFilterId: normalizeFilterId(url.searchParams.get('filterId')), + pathname: url.pathname, + searchParams: url.searchParams, + }; + }, [pageUrl]); + + const navigateWithFilterId = useCallback((filterId?: string) => { + const url = getUrlFromRoute(pageUrl); + + if (filterId) { + url.searchParams.set('filterId', filterId); + } else { + url.searchParams.delete('filterId'); + } + + router.get(url.pathname, Object.fromEntries(url.searchParams), { + preserveState: true, + preserveScroll: true + }); + }, [pageUrl]); + + const clearActiveFilter = useCallback(() => { + navigateWithFilterId(undefined); + }, [navigateWithFilterId]); + + return { + modelName: routeState.modelName, + activeFilterId: routeState.activeFilterId, + setActiveFilterId: navigateWithFilterId, + clearActiveFilter, + }; +}; + +const getTemporaryFilterMeta = (data: any): TemporaryFilterMeta | null => { + if (!data || typeof data !== 'object') { + return null; + } + + return { + name: String(data.name || ''), + conditionCount: typeof data.conditionCount === 'number' + ? data.conditionCount + : (Array.isArray(data.conditions) ? data.conditions.length : 0), + isModified: Boolean(data.isModified), + }; +}; + +const getTemporaryFilterStorageKey = (modelName: string) => `temporaryFilter_${modelName}`; + +const FILTER_ICON_OPTIONS = [ + 'filter_alt', + 'bookmark', + 'star', + 'push_pin', + 'search', + 'bolt', + 'favorite', + 'local_offer', + 'list_alt', + 'tune', +] as const; + +export function FilterPanel({onApplyFilters}: FilterPanelProps) { + const page = usePage(); + const user = page.props?.auth?.user; + const activeFilterFromPage = page.props?.activeFilter; + const { + modelName, + activeFilterId, + setActiveFilterId, + clearActiveFilter, + } = useFilterRouteState(page.url); + const { t } = useFilterTranslations(modelName); + const [activeFilters, setActiveFilters] = useState([]); + const [availableFields, setAvailableFields] = useState([]); + const [tempFilterName, setTempFilterName] = useState(''); + const [filterForEdit, setFilterForEdit] = useState(null); + const [selectValue, setSelectValue] = useState('placeholder'); + const [hasTemporaryFilter, setHasTemporaryFilter] = useState(false); + const [temporaryFilterData, setTemporaryFilterData] = useState(null); + + // Meta fields for saving filter + const [saveMeta, setSaveMeta] = useState({ + name: '', + description: '', + icon: 'bookmark', + color: '#3b82f6' + }); + const [isNameDirty, setIsNameDirty] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [saveError, setSaveError] = useState(''); + // API access state + const [userKey, setUserKey] = useState(undefined); + const [showUserKeyRequiredDialog, setShowUserKeyRequiredDialog] = useState(false); + const [filterColumns, setFilterColumns] = useState([]); + const [columnsLoading, setColumnsLoading] = useState(false); + const [columnsAvailable, setColumnsAvailable] = useState([]); + const [columnsVisible, setColumnsVisible] = useState([]); + const [columnsError, setColumnsError] = useState(''); + + // Ref для основного DialogStack + const filterDialogRef = useRef(null); + + // Meta fields for editing saved filter + const [editMeta, setEditMeta] = useState({ + name: '', + description: '', + icon: 'bookmark', + color: '#3b82f6' + }); + // Edit API access state + const [editApiEnabled, setEditApiEnabled] = useState(false); + const [editApiKey, setEditApiKey] = useState(undefined); + // Visibility states + const [saveVisibility, setSaveVisibility] = useState<'private' | 'public' | 'groups'>('private'); + const [editVisibility, setEditVisibility] = useState<'private' | 'public' | 'groups'>('private'); + const [saveGroupIds, setSaveGroupIds] = useState([]); + const [editGroupIds, setEditGroupIds] = useState([]); + const [userGroups, setUserGroups] = useState>([]); + + // Режим второго слайда: 'edit' | 'save' | 'columns' + const [slide2Mode, setSlide2Mode] = useState<'edit' | 'save' | 'columns'>('edit'); + + // ---- Функции для управления колонками ---- + const loadColumns = async () => { + setColumnsLoading(true); + setColumnsError(''); + + try { + // Для сохранённого фильтра используем его id, для временного - 'temporary' + const filterId = filterForEdit?.id || (activeFilterId === TEMPORARY_FILTER_ID ? TEMPORARY_FILTER_ID : undefined); + let url = `/adminizer/model/${modelName}/columns`; + if (filterId) { + url += `?filterId=${filterId}`; + } + const response = await fetch(url); + const data = await response.json(); + + if (response.ok) { + const { availableColumns: backendColumns, filterColumns: filterCols, hasFilterConfig } = data; + + if (hasFilterConfig && filterCols.length > 0) { + const columnsMap = new Map(backendColumns.map((col: ColumnConfig) => [col.fieldName, col])); + const visible: ColumnConfig[] = []; + const hidden: ColumnConfig[] = []; + + const sortedFilterColumns = [...filterCols].sort((a, b) => a.order - b.order); + for (const filterCol of sortedFilterColumns) { + const baseCol = columnsMap.get(filterCol.fieldName); + if (baseCol) { + visible.push({ ...baseCol, order: filterCol.order }); + } + } + for (const [fieldName, baseCol] of columnsMap) { + if (!visible.find(v => v.fieldName === fieldName)) { + hidden.push({ ...baseCol, order: visible.length + hidden.length }); + } + } + + setColumnsVisible(visible); + setColumnsAvailable(hidden); + } else { + const allVisible = backendColumns.map((col: ColumnConfig, index: number) => ({ ...col, order: index })); + setColumnsVisible(allVisible); + setColumnsAvailable([]); + } + } else { + setColumnsError(data.error || t('Error loading columns')); + } + } catch (err: any) { + console.error('Error loading columns:', err); + setColumnsError(t('Network error') + ': ' + err.message); + } finally { + setColumnsLoading(false); + } + }; + + const handleOpenColumnsDialog = () => { + setSlide2Mode('columns'); + loadColumns(); + }; + + const handleSaveColumns = () => { + const visibleColumns = columnsVisible.map((col, index) => ({ ...col, order: index })); + setFilterColumns(visibleColumns); + setSlide2Mode('edit'); + }; + + const moveColumnToVisible = (column: ColumnConfig) => { + setColumnsAvailable(prev => prev.filter(c => c.fieldName !== column.fieldName)); + setColumnsVisible(prev => [...prev, { ...column, order: prev.length }]); + }; + + const moveColumnToAvailable = (column: ColumnConfig) => { + setColumnsVisible(prev => prev.filter(c => c.fieldName !== column.fieldName)); + setColumnsAvailable(prev => [...prev, { ...column, order: prev.length }]); + }; + + const moveAllColumnsToVisible = () => { + setColumnsVisible(prev => [ + ...prev, + ...columnsAvailable.map((col, index) => ({ ...col, order: prev.length + index })) + ]); + setColumnsAvailable([]); + }; + + const moveAllColumnsToAvailable = () => { + setColumnsAvailable(prev => [ + ...prev, + ...columnsVisible.map((col, index) => ({ ...col, order: prev.length + index })) + ]); + setColumnsVisible([]); + }; + + const moveColumnUp = (index: number) => { + if (index === 0) return; + setColumnsVisible(prev => { + const newCols = [...prev]; + [newCols[index - 1], newCols[index]] = [newCols[index], newCols[index - 1]]; + return newCols.map((col, i) => ({ ...col, order: i })); + }); + }; + + const moveColumnDown = (index: number) => { + if (index >= columnsVisible.length - 1) return; + setColumnsVisible(prev => { + const newCols = [...prev]; + [newCols[index], newCols[index + 1]] = [newCols[index + 1], newCols[index]]; + return newCols.map((col, i) => ({ ...col, order: i })); + }); + }; + // ---- Конец функций для колонок ---- + + // State для календарей в условиях фильтра + const [fromOpen, setFromOpen] = useState(false); + const [toOpen, setToOpen] = useState(false); + const [dateOpen, setDateOpen] = useState(false); + const previousRouteStateRef = useRef<{modelName: string; activeFilterId?: string} | null>(null); + const availableFieldsById = useMemo( + () => new Map(availableFields.map((field) => [field.id, field])), + [availableFields] + ); + const activeFieldIds = useMemo( + () => new Set(activeFilters.map((activeFilter) => activeFilter.fieldId)), + [activeFilters] + ); + + useEffect(() => { + const previous = previousRouteStateRef.current; + const routeChanged = previous + && (previous.modelName !== modelName || previous.activeFilterId !== activeFilterId); + + if (routeChanged && (!activeFilterId || previous?.modelName !== modelName)) { + setActiveFilters([]); + setTempFilterName(''); + setFilterForEdit(null); + setSlide2Mode('edit'); + } + + previousRouteStateRef.current = {modelName, activeFilterId}; + }, [activeFilterId, modelName]); + + // Загрузка временного фильтра из sessionStorage при изменении URL/modelName + useEffect(() => { + if (!modelName) { + setHasTemporaryFilter(false); + setTemporaryFilterData(null); + return; + } + + const storageKey = getTemporaryFilterStorageKey(modelName); + const stored = sessionStorage.getItem(storageKey); + + if (!stored) { + const temporaryFilterFromPage = activeFilterId === TEMPORARY_FILTER_ID + && activeFilterFromPage?.id === TEMPORARY_FILTER_ID + ? activeFilterFromPage + : null; + + if (temporaryFilterFromPage) { + const meta = getTemporaryFilterMeta(temporaryFilterFromPage); + setHasTemporaryFilter(Boolean(meta)); + setTemporaryFilterData(meta); + setTempFilterName(meta?.name || ''); + + if (meta) { + sessionStorage.setItem(storageKey, JSON.stringify(meta)); + } + + return; + } + + setHasTemporaryFilter(false); + setTemporaryFilterData(null); + + if (activeFilterId === TEMPORARY_FILTER_ID) { + clearActiveFilter(); + } + + return; + } + + try { + const parsed = JSON.parse(stored); + const meta = getTemporaryFilterMeta(parsed); + + if (!meta) { + throw new Error('Invalid temporary filter metadata'); + } + + setTemporaryFilterData(meta); + setTempFilterName(meta.name); + setHasTemporaryFilter(true); + + if (Array.isArray(parsed.conditions)) { + sessionStorage.setItem(storageKey, JSON.stringify(meta)); + } + } catch (error) { + console.error('Error loading temporary filter:', error); + sessionStorage.removeItem(storageKey); + setHasTemporaryFilter(false); + setTemporaryFilterData(null); + + if (activeFilterId === TEMPORARY_FILTER_ID) { + clearActiveFilter(); + } + } + }, [activeFilterFromPage, activeFilterId, clearActiveFilter, modelName]); + + // Загрузка полей для фильтрации с бэкенда + useEffect(() => { + const loadFilterFields = async () => { + try { + if (!modelName) { + console.error('Could not find entity in URL'); + return; + } + + const response = await axios.get(`/adminizer/model/${modelName}/filter-fields`); + const data = response.data; + setAvailableFields(data.fields || []); + } catch (error) { + console.error('Error loading filter fields:', error); + } + }; + + loadFilterFields(); + }, [modelName]); + + // Загрузка всех групп (для админов) + useEffect(() => { + const loadGroups = async () => { + if (!user?.isAdministrator) { + return; + } + try { + const response = await axios.get('/adminizer/groups'); + setUserGroups(response.data.groups || []); + } catch (error) { + console.error('Error loading groups:', error); + } + }; + + loadGroups(); + }, [user?.isAdministrator]); + + // Экспортируем функции наружу через window для доступа из TableToolbar + useEffect(() => { + (window as any).adminizerFilterPanel = { + openFilterDialog: () => openFilterDialog(), + openSaveFilterDialog: () => handleOpenSaveDialog(), + openEditCurrentFilter: (filterId?: string) => openEditCurrentFilter(filterId), + getModelName: () => modelName, + getActiveFilterId: () => activeFilterId, + hasTemporaryFilter: () => hasTemporaryFilter + }; + + return () => { + delete (window as any).adminizerFilterPanel; + }; + }, [modelName, activeFilterId, hasTemporaryFilter]); + + /** + * Открытие основного диалога фильтров + */ + const openFilterDialog = () => { + setSlide2Mode('edit'); // Сбрасываем режим + filterDialogRef.current?.open(); + }; + + /** + * Open edit view for the active saved filter. + */ + const openEditCurrentFilter = async (requestedFilterId?: string) => { + const targetFilterId = requestedFilterId || activeFilterId; + + if (!targetFilterId || !modelName) { + return; + } + + if (targetFilterId === 'temporary') { + handleEditTemporaryFilter(); + return; + } + + setSlide2Mode('edit'); + filterDialogRef.current?.open(); + + try { + const response = await axios.get(`/adminizer/model/${modelName}/saved-filters`); + const savedFilters = response.data?.filters || []; + const targetFilter = savedFilters.find((filter: any) => String(filter.id) === String(targetFilterId)); + + if (!targetFilter) { + return; + } + + handleEditFilter(targetFilter); + } catch (error) { + console.error('Error loading filter for edit:', error); + } + }; + + const loadTemporaryFilterPayload = async (): Promise => { + if (!modelName) { + return null; + } + + if (activeFilterId === TEMPORARY_FILTER_ID && activeFilterFromPage?.id === TEMPORARY_FILTER_ID) { + return { + name: activeFilterFromPage.name, + conditions: activeFilterFromPage.conditions || [] + }; + } + + try { + const response = await axios.get(`/adminizer/model/${modelName}/filter/temporary`); + const filter = response.data?.filter; + + if (!filter) { + return null; + } + + return { + name: filter.name, + conditions: filter.conditions || [], + columns: filter.columns + }; + } catch (error: any) { + if (error.response?.status === 404) { + sessionStorage.removeItem(getTemporaryFilterStorageKey(modelName)); + setHasTemporaryFilter(false); + setTemporaryFilterData(null); + } else { + console.error('Error loading temporary filter:', error); + } + + return null; + } + }; + + /** + * Редактирование временного фильтра + */ + const handleEditTemporaryFilter = async () => { + const currentTemporaryFilter = await loadTemporaryFilterPayload(); + + if (currentTemporaryFilter) { + const meta = getTemporaryFilterMeta(currentTemporaryFilter); + setSlide2Mode('edit'); + setFilterForEdit(null); + setTemporaryFilterData(meta); + setTempFilterName(currentTemporaryFilter.name); + loadFilterIntoPanel({conditions: currentTemporaryFilter.conditions}); + filterDialogRef.current?.open(); + requestAnimationFrame(() => { + filterDialogRef.current?.next(); + }); + } + }; + + /** + * Применение временного фильтра + */ + const handleApplyTemporaryFilter = () => { + setActiveFilterId(TEMPORARY_FILTER_ID); + + // Закрываем диалог + filterDialogRef.current?.close(); + + // Отправляем событие об изменении фильтра + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + }; + + /** + * Сохранение временного фильтра (переключает на 2 слайд в режиме save) + */ + const handleSaveTemporaryFilter = async () => { + if (activeFilters.length === 0) { + const currentTemporaryFilter = await loadTemporaryFilterPayload(); + + if (currentTemporaryFilter) { + loadFilterIntoPanel({conditions: currentTemporaryFilter.conditions}); + const meta = getTemporaryFilterMeta(currentTemporaryFilter); + setTemporaryFilterData(meta); + setTempFilterName(currentTemporaryFilter.name); + } + } + + // Сбрасываем форму мета-данных + setSaveMeta({ + name: '', + description: '', + icon: 'bookmark', + color: '#3b82f6' + }); + setSaveVisibility('private'); // По умолчанию приватный + setSaveGroupIds([]); + setIsNameDirty(false); + setSaveError(''); + setSlide2Mode('save'); + loadUserApiKey(); // Load user API key when opening save dialog + + // Открываем второй слайд (в режиме сохранения) + filterDialogRef.current?.next(); + }; + + // ---- API helper functions ---- + const loadUserApiKey = async () => { + if (userKey) return; // Already loaded + try { + const response = await axios.get('/adminizer/api/user-key'); + setUserKey(response.data.apiKey); + } catch (error) { + console.error('Failed to load user API key:', error); + } + }; + + const getFeedBaseUrl = () => `${window.location.origin}/adminizer/api/feed`; + + // Edit mode API helpers + const getEditJsonFeedUrl = () => `${getFeedBaseUrl()}/${editApiKey || '...'}.json?userKey=${userKey || '...'}`; + const getEditXmlFeedUrl = () => `${getFeedBaseUrl()}/${editApiKey || '...'}.xml?userKey=${userKey || '...'}`; + + const copyEditToClipboard = async (text: string, label: string) => { + try { + await navigator.clipboard.writeText(text); + toast.success(`${label} ${t('copied to clipboard')}`); + } catch (err) { + toast.error(t('Failed to copy')); + } + }; + + // ---- End API helper functions ---- + + const convertFilterValue = (value: any): any => { + if (Array.isArray(value)) { + return value.map(v => convertFilterValue(v)); + } + if (value instanceof Date) { + return value.toISOString(); + } + if (value && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([k, v]) => [k, convertFilterValue(v)]) + ); + } + return value; + }; + + const isPlainObjectValue = useCallback((value: unknown): value is Record => { + return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date); + }, []); + + const getCustomInputEntries = useCallback((field?: FilterField): Array<[string, { placeholder: string; type?: 'text' | 'number' }]> => { + if (!field?.customFilterInputConfig) { + return []; + } + + return Object.entries(field.customFilterInputConfig).slice(0, 3); + }, []); + + const getDefaultCustomFilterValue = useCallback((field: FilterField, prevValue: ActiveFilter['value']) => { + const customEntries = getCustomInputEntries(field); + if (customEntries.length === 0) { + return isPlainObjectValue(prevValue) ? '' : (prevValue ?? ''); + } + + const prevObject = isPlainObjectValue(prevValue) + ? prevValue + : (customEntries.length === 1 && prevValue !== undefined && prevValue !== null + ? { [customEntries[0][0]]: prevValue } + : {}); + return Object.fromEntries( + customEntries.map(([id]) => [id, prevObject[id] ?? '']) + ); + }, [getCustomInputEntries, isPlainObjectValue]); + + const serializeFilterCondition = (filter: ActiveFilter) => { + const convertedValue = convertFilterValue(filter.value); + + if (filter.customHandler && filter.condition === 'custom') { + const field = availableFieldsById.get(filter.fieldId); + return { + id: filter.id, + field: filter.fieldId, + customHandler: filter.customHandler, + customHandlerName: field?.customFilterConditionLabel, + operator: 'custom', + value: convertedValue + }; + } + + return { + id: filter.id, + ...(filter.relationField + ? {relation: filter.fieldId, relationField: filter.relationField} + : {field: filter.fieldId}), + operator: filter.condition, + value: convertedValue + }; + }; + + /** + * Сохранение фильтра (с мета-данными) + */ + const handleSaveFilter = async () => { + if (!saveMeta.name.trim()) { + setSaveError(t('Enter filter name')); + return; + } + + setIsSaving(true); + setSaveError(''); + + try { + const conditions = activeFilters + .filter(f => f.enabled) + .map(serializeFilterCondition); + + const payload: any = { + name: saveMeta.name.trim(), + description: saveMeta.description?.trim() || undefined, + conditions, + icon: saveMeta.icon || 'bookmark', + color: saveMeta.color || '#3b82f6', + visibility: saveVisibility, + groupIds: saveVisibility === 'groups' ? saveGroupIds : undefined, + }; + + // Добавляем конфигурацию колонок + if (filterColumns.length > 0) { + payload.columns = filterColumns + .map((col, index) => ({ + fieldName: col.fieldName, + order: index + })); + } + + const response = await axios.post(`/adminizer/model/${modelName}/filter`, payload); + const filter = response.data.filter; + + // Очищаем временный фильтр после успешного сохранения + sessionStorage.removeItem(getTemporaryFilterStorageKey(modelName)); + setHasTemporaryFilter(false); + setTemporaryFilterData(null); + + // Сбрасываем состояние + setActiveFilters([]); + setFilterForEdit(null); + setTempFilterName(''); + + // Возвращаемся на первый слайд + setSlide2Mode('edit'); + filterDialogRef.current?.prev(); + + // Отправляем событие об изменении фильтра (для обновления списка) + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + + // Apply saved filter immediately using the same flow as the saved filters list + handleApplySavedFilter({ + id: String(filter.id), + name: filter.name || saveMeta.name.trim(), + conditions: filter.conditions || conditions, + icon: filter.icon, + color: filter.color + }); + + } catch (error: any) { + console.error('Error saving filter:', error); + setSaveError(error.response?.data?.error || t('Error saving filter')); + } finally { + setIsSaving(false); + } + }; + + /** + * Сброс временного фильтра + */ + const handleDiscardTemporaryFilter = () => { + sessionStorage.removeItem(getTemporaryFilterStorageKey(modelName)); + setHasTemporaryFilter(false); + setTemporaryFilterData(null); + setTempFilterName(''); + setActiveFilters([]); + + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + clearActiveFilter(); + + filterDialogRef.current?.close(); + }; + + /** + * Загрузка условий фильтра в панель + */ + const loadFilterIntoPanel = useCallback((filterData: {conditions: any[]}) => { + const getFallbackFieldIdFromHandler = (handlerId?: string): string | undefined => { + if (!handlerId) return undefined; + const byHandler = availableFields.find( + (field) => field.isCustomFilter && field.customFilterHandlerId === handlerId + ); + if (byHandler) return byHandler.id; + const parts = handlerId.split('.'); + return parts[parts.length - 1]; + }; + + const getConditionFieldType = (cond: any, resolvedFieldId: string): string | undefined => { + const baseField = availableFields.find((field) => field.id === resolvedFieldId); + if (!baseField) return undefined; + if (!baseField.isRelation) return baseField.type; + + const relationField = (baseField.relationFields || []).find( + (rf) => rf.id === cond.relationField + ); + return relationField?.type; + }; + + const parseConditionValue = (cond: any, resolvedFieldId: string): any => { + const rawValue = cond.value; + if (rawValue === undefined || rawValue === null) { + return ''; + } + + if (cond.customHandler) { + return rawValue; + } + + const fieldType = getConditionFieldType(cond, resolvedFieldId); + const isDateType = fieldType === 'date' || fieldType === 'datetime'; + if (!isDateType) { + return rawValue; + } + + // Keep month/year operators as plain strings for HTML inputs + if (['month', 'year', 'monthBetween', 'yearBetween'].includes(cond.operator)) { + if (Array.isArray(rawValue)) { + return rawValue.map((v: any) => (v === undefined || v === null ? '' : String(v))); + } + return String(rawValue); + } + + if (Array.isArray(rawValue)) { + return rawValue.map((v: any) => { + if (typeof v === 'string' && !isNaN(Date.parse(v))) { + return new Date(v); + } + return v; + }); + } + + if (typeof rawValue === 'string' && !isNaN(Date.parse(rawValue))) { + return new Date(rawValue); + } + + return rawValue; + }; + + const loadedFilters: ActiveFilter[] = filterData.conditions.map((cond) => { + const resolvedFieldId = cond.field || cond.relation || getFallbackFieldIdFromHandler(cond.customHandler) || ''; + const value = parseConditionValue(cond, resolvedFieldId); + + return { + id: cond.id || `filter-${Date.now()}-${Math.random()}`, + fieldId: resolvedFieldId, + relationField: cond.relationField, + customHandler: cond.customHandler, + enabled: true, + condition: cond.operator || (cond.customHandler ? 'custom' : 'eq'), + value: (value === undefined || value === null) ? '' : value, + }; + }); + + setActiveFilters(loadedFilters); + }, [availableFields]); + + /** + * Применение фильтра (без сохранения) + */ + const handleApplyFilters = async (filters: ActiveFilter[]) => { + if (!modelName) { + console.error('[FilterPanel] Model name is not set!'); + alert(t('Error') + ': ' + t('Model not found')); + return; + } + + try { + const conditions = filters + .filter(f => f.enabled) + .map(serializeFilterCondition); + + const payload: any = { + name: tempFilterName || t('Temporary filter'), + conditions + }; + + // Добавляем конфигурацию колонок + if (filterColumns.length > 0) { + payload.columns = filterColumns + .map((col, index) => ({ + fieldName: col.fieldName, + order: index + })); + } + + // Отправляем POST запрос на применение временного фильтра + const response = await axios.post(`/adminizer/model/${modelName}/filter/apply`, payload); + const data = response.data; + + if (data.success) { + // Сохраняем временные данные в sessionStorage с ключом модели + sessionStorage.setItem(getTemporaryFilterStorageKey(modelName), JSON.stringify({ + name: payload.name, + conditionCount: payload.conditions.length, + isModified: false + })); + + // Обновляем состояние временного фильтра + setTemporaryFilterData({ + name: payload.name, + conditionCount: payload.conditions.length, + isModified: false + }); + setHasTemporaryFilter(true); + + // Обновляем заголовок второго слайда + setTempFilterName(payload.name); + + // Обновляем активные фильтры + setActiveFilters(filters); + setActiveFilterId(TEMPORARY_FILTER_ID); + + // Закрываем диалог + filterDialogRef.current?.close(); + + // Отправляем событие об изменении фильтра + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + } + + } catch (error: any) { + console.error('[FilterPanel] Error applying filter:', error); + + if (error.response?.status === 401) { + console.error('Session expired, redirecting to login...'); + window.location.href = '/adminizer/model/userap/login'; + return; + } + + alert(t('Error applying filter') + ': ' + (error.response?.data?.error || error.message)); + } + }; + + /** + * Сохранение изменений сохранённого фильтра (мета + условия) + */ + const handleUpdateSavedFilter = async () => { + if (!editMeta.name.trim()) { + setSaveError(t('Enter filter name')); + return; + } + + setIsSaving(true); + setSaveError(''); + + try { + const conditions = activeFilters + .filter(f => f.enabled) + .map(serializeFilterCondition); + + const payload: any = { + filterId: filterForEdit.id, + name: editMeta.name.trim(), + description: editMeta.description?.trim() || undefined, + conditions, + icon: editMeta.icon || 'bookmark', + color: editMeta.color || '#3b82f6', + visibility: editVisibility, + groupIds: editVisibility === 'groups' ? editGroupIds : undefined, + sortField: filterForEdit.sortField, + sortDirection: filterForEdit.sortDirection, + }; + + if (editVisibility === 'private') { + payload.apiEnabled = editApiEnabled; + payload.regenerateApiKey = editApiEnabled && !editApiKey; + } + + // Добавляем конфигурацию колонок + if (filterColumns.length > 0) { + payload.columns = filterColumns + .map((col, index) => ({ + fieldName: col.fieldName, + order: index + })); + } + + const response = await axios.post(`/adminizer/model/${modelName}/filter`, payload); + const filter = response.data.filter; + + // Обновляем состояние + setTempFilterName(filter.name); + setFilterForEdit(null); + + // Сбрасываем активные фильтры + setActiveFilters([]); + + // Возвращаемся на первый слайд + setSlide2Mode('edit'); + filterDialogRef.current?.prev(); + + // Отправляем событие об изменении фильтра (для обновления списка) + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + + // Apply updated saved filter immediately + handleApplySavedFilter({ + id: String(filter.id), + name: filter.name || editMeta.name.trim(), + conditions: filter.conditions || conditions, + icon: filter.icon, + color: filter.color + }); + + } catch (error: any) { + console.error('Error updating filter:', error); + setSaveError(error.response?.data?.error || t('Error saving filter')); + } finally { + setIsSaving(false); + } + }; + const handleApplySavedFilter = useCallback((filter: { + id: string; + name: string; + conditions: any[]; + icon?: string; + color?: string; + }) => { + setActiveFilterId(filter.id); + setTempFilterName(filter.name); + + // Загружаем условия в панель (для возможного редактирования) + loadFilterIntoPanel({conditions: filter.conditions}); + + // Отправляем событие об изменении фильтра + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + }, [loadFilterIntoPanel, setActiveFilterId]); + + /** + * Сброс фильтра + */ + const handleResetFilter = useCallback(() => { + setActiveFilters([]); + setTempFilterName(''); + + // Отправляем событие об изменении фильтра + window.dispatchEvent(new CustomEvent('adminizer:filterChanged')); + clearActiveFilter(); + }, [clearActiveFilter]); + + /** + * Редактирование фильтра + */ + const handleEditFilter = useCallback((filter: { + id: string; + name: string; + description?: string; + conditions: any[]; + icon?: string; + color?: string; + apiEnabled?: boolean; + apiKey?: string; + visibility?: string; + groupIds?: number[]; + }) => { + setFilterForEdit(filter); + setTempFilterName(filter.name); + setEditMeta({ + name: filter.name, + description: filter.description || '', + icon: filter.icon || 'bookmark', + color: filter.color || '#3b82f6' + }); + setEditVisibility((filter.visibility as 'private' | 'public' | 'groups') || 'private'); + setEditGroupIds(filter.groupIds || []); + setEditApiEnabled(filter.apiEnabled || false); + setEditApiKey(filter.apiKey || undefined); + loadUserApiKey(); // Ensure user API key is loaded + loadFilterIntoPanel({conditions: filter.conditions}); + setSlide2Mode('edit'); + // Открываем второй слайд (редактирование сохранённого фильтра с метаполя ми) + filterDialogRef.current?.next(); + }, [loadFilterIntoPanel]); + + /** + * Удаление фильтра + */ + const handleDeleteFilter = useCallback((filterId: string) => { + if (activeFilterId === filterId) { + handleResetFilter(); + } + }, [activeFilterId, handleResetFilter]); + + const getCurrentMonthValue = () => { + const now = new Date(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + return `${now.getFullYear()}-${month}`; + }; + + const getCurrentYearValue = () => String(new Date().getFullYear()); + + const getDefaultValueForCondition = ( + condition: string, + prevValue: ActiveFilter['value'], + field?: FilterField + ) => { + if (condition === 'custom' && field?.isCustomFilter) { + return getDefaultCustomFilterValue(field, prevValue); + } + + if (isPlainObjectValue(prevValue)) { + return ''; + } + + if (condition === 'isNull' || condition === 'isNotNull' || condition === 'today') { + return ''; + } + + if (condition === 'between') { + return Array.isArray(prevValue) && prevValue.length === 2 ? prevValue : ['', '']; + } + + if (condition === 'month') { + return typeof prevValue === 'string' && /^\d{4}-\d{2}$/.test(prevValue) ? prevValue : getCurrentMonthValue(); + } + + if (condition === 'year') { + if (typeof prevValue === 'string' && /^\d{4}$/.test(prevValue)) return prevValue; + if (typeof prevValue === 'number') return String(prevValue); + return getCurrentYearValue(); + } + + if (condition === 'monthBetween') { + if ( + Array.isArray(prevValue) && + prevValue.length === 2 && + typeof prevValue[0] === 'string' && + /^\d{4}-\d{2}$/.test(prevValue[0]) && + typeof prevValue[1] === 'string' && + /^\d{4}-\d{2}$/.test(prevValue[1]) + ) { + return prevValue; + } + const currentMonth = getCurrentMonthValue(); + return [currentMonth, currentMonth]; + } + + if (condition === 'yearBetween') { + const isYear = (v: any) => + (typeof v === 'string' && /^\d{4}$/.test(v)) || typeof v === 'number'; + + if (Array.isArray(prevValue) && prevValue.length === 2 && isYear(prevValue[0]) && isYear(prevValue[1])) { + return [String(prevValue[0]), String(prevValue[1])]; + } + + const currentYear = getCurrentYearValue(); + return [currentYear, currentYear]; + } + + return prevValue; + }; + + /** + * Изменение условия + */ + const handleFilterChange = useCallback((filterId: string, updates: Partial) => { + setActiveFilters((currentFilters) => currentFilters.map(f => + f.id === filterId ? {...f, ...updates} : f + )); + + // Помечаем временный фильтр как изменённый + if (hasTemporaryFilter) { + setTemporaryFilterData((currentTemporaryFilter) => currentTemporaryFilter ? { + ...currentTemporaryFilter, + isModified: true + } : currentTemporaryFilter); + } + }, [hasTemporaryFilter]); + + /** + * Добавление условия + */ + const handleAddFilter = useCallback((fieldId: string) => { + if (activeFieldIds.has(fieldId)) { + setSelectValue('placeholder'); + return; + } + + const field = availableFieldsById.get(fieldId); + if (!field) return; + + const defaultCondition = field.isCustomFilter ? 'custom' : 'eq'; + const defaultValue = defaultCondition === 'custom' + ? getDefaultCustomFilterValue(field, '') + : ''; + + const newFilter: ActiveFilter = { + id: Date.now().toString(), + fieldId, + relationField: field.isRelation ? field.relationFields?.[0]?.id : undefined, + customHandler: field.isCustomFilter ? field.customFilterHandlerId : undefined, + enabled: true, + condition: defaultCondition, + value: defaultValue, + }; + const newFilters = [...activeFilters, newFilter]; + setActiveFilters(newFilters); + setSelectValue('placeholder'); + + // Помечаем временный фильтр как изменённый + if (hasTemporaryFilter) { + setTemporaryFilterData((currentTemporaryFilter) => currentTemporaryFilter ? { + ...currentTemporaryFilter, + conditionCount: newFilters.length, + isModified: true + } : currentTemporaryFilter); + } + }, [activeFieldIds, activeFilters, availableFieldsById, getDefaultCustomFilterValue, hasTemporaryFilter]); + + /** + * Удаление условия + */ + const handleRemoveFilter = useCallback((filterId: string) => { + setActiveFilters((currentFilters) => { + const newFilters = currentFilters.filter(f => f.id !== filterId); + + if (hasTemporaryFilter) { + setTemporaryFilterData((currentTemporaryFilter) => currentTemporaryFilter ? { + ...currentTemporaryFilter, + conditionCount: newFilters.length, + isModified: true + } : currentTemporaryFilter); + } + + return newFilters; + }); + }, [hasTemporaryFilter]); + + /** + * Закрытие диалога и сброс + */ + const handleCloseDialog = useCallback(() => { + filterDialogRef.current?.close(); + setFilterForEdit(null); + }, []); + + const getEffectiveFieldForInput = useCallback((filter: ActiveFilter, field: FilterField): FilterField | null => { + if (!field.isRelation) { + return field; + } + + if (!field.relationFields || field.relationFields.length === 0) { + return null; + } + + const selectedRelationField = field.relationFields.find(rf => rf.id === filter.relationField) || field.relationFields[0]; + return { + id: selectedRelationField.id, + label: selectedRelationField.label, + type: selectedRelationField.type, + options: selectedRelationField.options, + isCustomFilter: false, + }; + }, []); + + const defaultConditionOptions = useMemo(() => [ + {value: 'eq', label: t('Equal')}, + {value: 'neq', label: t('Not equal')}, + {value: 'like', label: t('Contains')}, + {value: 'startsWith', label: t('Starts with')}, + {value: 'endsWith', label: t('Ends with')}, + {value: 'isNull', label: t('Is empty')}, + {value: 'isNotNull', label: t('Is not empty')}, + ], [t]); + + const conditionOptionsByFieldId = useMemo(() => { + const optionsByFieldId = new Map(); + + availableFields.forEach((field) => { + if (field.isRelation) { + optionsByFieldId.set(field.id, [ + {value: 'eq', label: t('Equal')}, + {value: 'neq', label: t('Not equal')}, + ]); + return; + } + + let conditions: FilterConditionOption[] = []; + + switch (field.type) { + case 'number': + case 'integer': + case 'float': + conditions = [ + {value: 'eq', label: t('Equal')}, + {value: 'neq', label: t('Not equal')}, + {value: 'gt', label: t('Greater than')}, + {value: 'gte', label: t('Greater or equal')}, + {value: 'lt', label: t('Less than')}, + {value: 'lte', label: t('Less or equal')}, + {value: 'between', label: t('Between')}, + {value: 'isNull', label: t('Is empty')}, + {value: 'isNotNull', label: t('Is not empty')}, + ]; + break; + case 'date': + case 'datetime': + conditions = [ + {value: 'eq', label: t('Equal')}, + {value: 'neq', label: t('Not equal')}, + {value: 'gt', label: t('After')}, + {value: 'gte', label: t('After or equal')}, + {value: 'lt', label: t('Before')}, + {value: 'lte', label: t('Before or equal')}, + {value: 'between', label: t('Between')}, + {value: 'today', label: t('Today')}, + {value: 'month', label: t('Month')}, + {value: 'year', label: t('Year')}, + {value: 'monthBetween', label: t('Month range')}, + {value: 'yearBetween', label: t('Year range')}, + {value: 'isNull', label: t('Is empty')}, + {value: 'isNotNull', label: t('Is not empty')}, + ]; + break; + case 'boolean': + conditions = [ + {value: 'eq', label: t('Equal')}, + {value: 'neq', label: t('Not equal')}, + {value: 'isNull', label: t('Is empty')}, + {value: 'isNotNull', label: t('Is not empty')}, + ]; + break; + case 'select': + case 'select-many': + case 'association': + case 'association-many': + conditions = [ + {value: 'eq', label: t('Equal')}, + {value: 'neq', label: t('Not equal')}, + {value: 'in', label: t('In list')}, + {value: 'notIn', label: t('Not in list')}, + {value: 'isNull', label: t('Is empty')}, + {value: 'isNotNull', label: t('Is not empty')}, + ]; + break; + case 'json': + case 'jsoneditor': + case 'object': + case 'array': + conditions = [ + {value: 'isNull', label: t('Is empty')}, + {value: 'isNotNull', label: t('Is not empty')}, + ]; + break; + default: + conditions = [...defaultConditionOptions]; + break; + } + + if (field.isCustomFilter && field.customFilterHandlerId) { + conditions = [ + ...conditions, + { + value: 'custom', + label: field.customFilterConditionLabel || t('Custom filtering'), + }, + ]; + } + + optionsByFieldId.set(field.id, conditions); + }); + + return optionsByFieldId; + }, [availableFields, defaultConditionOptions, t]); + + // Условия для разных типов полей + const getConditionsForField = useCallback((fieldId: string) => { + return conditionOptionsByFieldId.get(fieldId) || defaultConditionOptions; + }, [conditionOptionsByFieldId, defaultConditionOptions]); + + const fieldsAvailableToAdd = useMemo(() => ( + availableFields.filter((field) => !activeFieldIds.has(field.id)) + ), [activeFieldIds, availableFields]); + + // Рендер значения условия в зависимости от типа поля + const renderValueInput = useCallback((filter: ActiveFilter, field: FilterField) => { + if (field.isCustomFilter && filter.condition === 'custom') { + const customEntries = getCustomInputEntries(field); + + if (customEntries.length > 0) { + const customValue = isPlainObjectValue(filter.value) + ? filter.value + : (customEntries.length === 1 && filter.value !== undefined && filter.value !== null + ? { [customEntries[0][0]]: filter.value } + : {}); + return ( +
+ {customEntries.map(([id, inputConfig]) => ( + handleFilterChange(filter.id, { + value: { + ...customValue, + [id]: e.target.value, + } + })} + placeholder={inputConfig.placeholder || id} + className="w-full sm:w-[150px] h-9" + /> + ))} +
+ ); + } + + return ( + handleFilterChange(filter.id, {value: e.target.value})} + placeholder={t('Value...')} + className="w-full sm:w-[220px] h-9" + /> + ); + } + + if (filter.condition === 'isNull' || filter.condition === 'isNotNull' || filter.condition === 'today') { + return null; + } + + // Для boolean - селект с true/false + if (field.type === 'boolean') { + return ( + + ); + } + + // Для числовых типов - инпут с поддержкой операторов + if (field.type === 'number' || field.type === 'integer' || field.type === 'float') { + // Для between показываем два инпута + if (filter.condition === 'between') { + const valueArray = Array.isArray(filter.value) ? filter.value : ['', '']; + return ( +
+ handleFilterChange(filter.id, { + value: [parseFloat(e.target.value) || '', valueArray[1]] + })} + placeholder={t('From...')} + className="w-full sm:w-[120px] h-9" + /> + - + handleFilterChange(filter.id, { + value: [valueArray[0], parseFloat(e.target.value) || ''] + })} + placeholder={t('To...')} + className="w-full sm:w-[120px] h-9" + /> +
+ ); + } + // Для остальных условий - один инпут + return ( + handleFilterChange(filter.id, { value: parseFloat(e.target.value) || '' })} + placeholder={t('Value...')} + className="w-full sm:w-[150px] h-9" + /> + ); + } + + // Для date + if ((field.type === 'date' || field.type === 'datetime') && filter.condition === 'month') { + return ( + handleFilterChange(filter.id, { value: e.target.value })} + className="w-full sm:w-[160px] h-9" + /> + ); + } + + if ((field.type === 'date' || field.type === 'datetime') && filter.condition === 'year') { + return ( + handleFilterChange(filter.id, { value: e.target.value })} + placeholder={t('Year')} + className="w-full sm:w-[130px] h-9" + /> + ); + } + + if ((field.type === 'date' || field.type === 'datetime') && filter.condition === 'monthBetween') { + const valueArray = Array.isArray(filter.value) ? filter.value : ['', '']; + return ( +
+ handleFilterChange(filter.id, { value: [e.target.value, valueArray[1]] })} + className="w-full sm:w-[160px] h-9" + /> + - + handleFilterChange(filter.id, { value: [valueArray[0], e.target.value] })} + className="w-full sm:w-[160px] h-9" + /> +
+ ); + } + + if ((field.type === 'date' || field.type === 'datetime') && filter.condition === 'yearBetween') { + const valueArray = Array.isArray(filter.value) ? filter.value : ['', '']; + return ( +
+ handleFilterChange(filter.id, { value: [e.target.value, valueArray[1]] })} + placeholder={t('From...')} + className="w-full sm:w-[120px] h-9" + /> + - + handleFilterChange(filter.id, { value: [valueArray[0], e.target.value] })} + placeholder={t('To...')} + className="w-full sm:w-[120px] h-9" + /> +
+ ); + } + + if (field.type === 'date') { + // Для between показываем два календаря + if (filter.condition === 'between') { + const valueArray = Array.isArray(filter.value) ? filter.value : ['', '']; + const from = valueArray[0]; + const to = valueArray[1]; + return ( +
+ + + + + + { + handleFilterChange(filter.id, { value: [date || '', to] as (string | Date)[] }); + setFromOpen(false); + }} + initialFocus + /> + + + - + + + + + + { + handleFilterChange(filter.id, { value: [from, date || ''] as (string | Date)[] }); + setToOpen(false); + }} + initialFocus + /> + + +
+ ); + } + // Для остальных условий - один календарь + return ( + + + + + + { + handleFilterChange(filter.id, { value: date || '' }); + setDateOpen(false); + }} + initialFocus + /> + + + ); + } + + // Для month/week - HTML5 input + if (field.type === 'month' || field.type === 'week') { + return ( + handleFilterChange(filter.id, { value: e.target.value })} + className="w-full sm:w-[150px] h-9" + /> + ); + } + + // Для datetime - HTML5 datetime-local input + if (field.type === 'datetime') { + // Для between показываем два datetime-local инпута + if (filter.condition === 'between') { + const valueArray = Array.isArray(filter.value) ? filter.value : ['', '']; + return ( +
+ handleFilterChange(filter.id, { value: [e.target.value, valueArray[1]] })} + className="w-full sm:w-[180px] h-9" + /> + - + handleFilterChange(filter.id, { value: [valueArray[0], e.target.value] })} + className="w-full sm:w-[180px] h-9" + /> +
+ ); + } + // Для остальных условий - один datetime-local инпут + return ( + handleFilterChange(filter.id, { value: e.target.value })} + className="w-full sm:w-[180px] h-9" + /> + ); + } + + // Для time - HTML5 time input + if (field.type === 'time') { + return ( + handleFilterChange(filter.id, { value: e.target.value })} + className="w-full sm:w-[150px] h-9" + /> + ); + } + + // Для select (мультиселект через Command) + if (field.type === 'select' && filter.condition === 'in') { + const selectedValues = Array.isArray(filter.value) ? filter.value : [filter.value].filter(Boolean); + return ( + + + + + + + + + {t('No results')} + + {field.options?.map((option) => { + const isSelected = selectedValues.includes(option.value); + return ( + { + const newValues = isSelected + ? selectedValues.filter(v => v !== option.value) + : [...selectedValues, option.value]; + handleFilterChange(filter.id, { value: newValues }); + }} + > + + {option.label} + + ); + })} + + + + + + ); + } + + // Для select-many (мультиселект через Command) + if (field.type === 'select-many' && filter.condition === 'in') { + const selectedValues = Array.isArray(filter.value) ? filter.value : [filter.value].filter(Boolean); + return ( + + + + + + + + + {t('No results')} + + {field.options?.map((option) => { + const isSelected = selectedValues.includes(option.value); + return ( + { + const newValues = isSelected + ? selectedValues.filter(v => v !== option.value) + : [...selectedValues, option.value]; + handleFilterChange(filter.id, { value: newValues }); + }} + > + + {option.label} + + ); + })} + + + + + + ); + } + + // Для select (одиночный выбор) + if (field.type === 'select') { + return ( + + ); + } + + // Для select-many (не in) - мультиселект через Command + if (field.type === 'select-many') { + const selectedValues = Array.isArray(filter.value) ? filter.value : [filter.value].filter(Boolean); + return ( + + + + + + + + + {t('No results')} + + {field.options?.map((option) => { + const isSelected = selectedValues.includes(option.value); + return ( + { + const newValues = isSelected + ? selectedValues.filter(v => v !== option.value) + : [...selectedValues, option.value]; + handleFilterChange(filter.id, { value: newValues }); + }} + > + + {option.label} + + ); + })} + + + + + + ); + } + + // По умолчанию - текстовый инпут + return ( + handleFilterChange(filter.id, {value: e.target.value})} + placeholder={t('Value...')} + className="w-full sm:w-[200px] h-9" + /> + ); + }, [ + dateOpen, + fromOpen, + getCustomInputEntries, + handleFilterChange, + isPlainObjectValue, + t, + toOpen, + ]); + + return ( + <> + {/* Основной DialogStack с каскадными диалогами */} + + + + {/* Первый диалог: список сохранённых фильтров */} + +
+
+ + + {t('Filters')} + + + {activeFilterId && activeFilterId !== TEMPORARY_FILTER_ID + ? t('Select a filter or create a new one') + : t('Select a saved filter or create a new one')} + + + +
+ {/* Кнопка создания нового фильтра */} + + + {/* Временный фильтр (активный) */} + {hasTemporaryFilter && temporaryFilterData && ( +
+
+ {t('Active filter')} +
+
+
+ 📝 +
+
+ {temporaryFilterData.name} +
+
+ {temporaryFilterData.conditionCount} {t('conditions')} + {temporaryFilterData.isModified && ` (${t('modified')})`} +
+
+
+
+ + + + +
+
+
+ )} + + {/* Список сохранённых фильтров */} + {modelName && ( + <> + {!hasTemporaryFilter && ( +
+ {t('Saved filters')} +
+ )} + + + )} + + {/* Кнопка сброса активного фильтра */} + {activeFilterId && activeFilterId !== TEMPORARY_FILTER_ID && ( +
+ +
+ )} +
+
+
+
+ + {/* Второй слайд: универсальный (редактирование / сохранение / колонки) */} + +
+
+ {slide2Mode === 'columns' ? ( + /* === Настройка колонок === */ + <> + + {t('Columns settings')} + + {t('Select and arrange columns to display in the table')} + + + + {columnsLoading ? ( +
+

{t('Loading columns...')}

+
+ ) : ( +
+
+ {/* Левый блок - доступные */} +
+
+

{t('Available columns')}

+ {columnsAvailable.length > 0 && ( + + )} +
+
+ {columnsAvailable.length === 0 ? ( +

{t('No available columns')}

+ ) : ( + columnsAvailable.map((col) => ( +
+ {col.label} + +
+ )) + )} +
+
+ + {/* Правый блок - видимые */} +
+
+

{t('Visible columns')}

+ {columnsVisible.length > 0 && ( + + )} +
+
+ {columnsVisible.length === 0 ? ( +

{t('Move columns here')}

+ ) : ( + columnsVisible.map((col, index) => ( +
+
+
+ + +
+ {col.label} +
+ +
+ )) + )} +
+
+
+ +

+ {t('Use buttons')} ← → {t('to move and')} ↑ ↓ {t('to change order')} +

+ + {columnsError && ( +
+ {columnsError} +
+ )} +
+ )} + + +
+ +
+ +
+ + + ) : ( + <> + + + {slide2Mode === 'edit' + ? (filterForEdit ? `${t('Edit filter')}: ${tempFilterName}` : tempFilterName || t('New filter')) + : t('Save filter')} + + + {slide2Mode === 'edit' + ? (filterForEdit ? t('Change filter conditions and parameters') : t('Configure filter conditions')) + : t('Specify name and parameters for saving')} + + + + {slide2Mode === 'edit' ? ( + /* === РЕЖИМ РЕДАКТИРОВАНИЯ === */ +
+ {/* Метаполя - показываем только для сохранённого фильтра */} + {filterForEdit && ( +
+

{t('Filter parameters')}

+ + {/* Название */} +
+ + setEditMeta({...editMeta, name: e.target.value})} + placeholder={t('Enter filter name')} + /> +
+ + {/* Описание */} +
+ +