From 89dd55d51e02cfcc66c7d17d5d4813ae34b47840 Mon Sep 17 00:00:00 2001 From: Naffah Abdulla Rasheed Date: Thu, 5 Nov 2026 20:08:49 +0500 Subject: [PATCH 01/11] Foundations and core of v2: PHP 8.4 / Laravel 12+, portable SQL, charts, cache, stencil forms - PHP ^8.4, Laravel 12/13, Testbench 10/11. Drops javaabu/helpers, javaabu/settings and maatwebsite/excel; javaabu/forms ^2. Branch alias dev-next-gen: 2.x-dev. - Date grouping for MySQL, MariaDB, PostgreSQL and SQLite. Weeks are keyed by the date they start on; months by YYYY-MM; hourly keys now match the SQL. - CSV exports without a dependency; a plain date_to covers the whole day. - Route::stats() / Route::statsApi(), configurable controllers, a StatsPolicy and view_stats through javaabu/permissions, any guard. - Chart builders (TimeSeriesChart, CategoryChart, PieChart, Sparkline, StatTile) serialised for Onu and drawn as SVG in Blade; several stats or periods on a plot. - StatsCache with versioned keys and TTLs, Stats::warm(), stats:warm and the WarmStatsCache job, the InvalidatesStats model trait. - Stats forms defined with stencil; javaabu/filters filters via StatsFilter::from(). - Pint, CI matrix (PHP 8.4/8.5 x Laravel 12/13 x lowest/stable), MySQL, MariaDB and PostgreSQL jobs, a job without optional packages, the JS job, Codecov, badges. Refs #1, #2, #3, #4, #5, #6, #7 Co-Authored-By: Claude Opus 5.5 --- .github/coverage.svg | 20 - .github/workflows/run-tests.yml | 99 - .github/workflows/tests.yml | 257 + .gitignore | 1 + .php_cs.dist.php | 35 - .prettierignore | 16 + .prettierrc.json | 5 + README.md | 12 +- composer.json | 90 +- config/stats.php | 154 +- dist/.vite/manifest.json | 11 + dist/assets/charts-B-a9Wmv-.css | 1 + eslint.config.js | 19 + package.json | 54 + phpunit.xml.dist | 9 +- pint.json | 24 + pnpm-lock.yaml | 5485 +++++++++++++++++ pnpm-workspace.yaml | 1 + resources/css/charts.css | 469 ++ resources/views/categorical/index.blade.php | 5 + .../components/charts/cartesian.blade.php | 69 + .../views/components/charts/delta.blade.php | 11 + .../views/components/charts/header.blade.php | 6 + .../views/components/charts/pie.blade.php | 40 + .../components/charts/sparkline.blade.php | 10 + .../components/charts/stat-tile.blade.php | 34 + .../views/components/charts/table.blade.php | 24 + .../views/components/stat-tiles.blade.php | 6 + .../categorical-stats/_form.blade.php | 124 - .../categorical-stats/_generator.blade.php | 15 - .../categorical-stats/_script.blade.php | 215 - .../categorical-stats/index.blade.php | 8 - .../time-series-stats/_form.blade.php | 93 - .../time-series-stats/_generator.blade.php | 15 - .../time-series-stats/_script.blade.php | 225 - .../time-series-stats/index.blade.php | 7 - .../time-series-stats/stats.blade.php | 4 - resources/views/partials/page.blade.php | 28 + resources/views/time-series/index.blade.php | 5 + src/Cache/StatsCache.php | 209 + src/CategoricalStats.php | 24 +- .../EloquentCategoryProvider.php | 2 +- src/Charts/CategoryChart.php | 227 + src/Charts/Chart.php | 327 + src/Charts/Concerns/HasCategories.php | 116 + src/Charts/Concerns/HasStats.php | 125 + src/Charts/PieChart.php | 154 + src/Charts/Sparkline.php | 128 + src/Charts/StatTile.php | 214 + src/Charts/TimeSeriesChart.php | 334 + .../GenerateCategoricalStatCommand.php | 4 +- .../GenerateTimeSeriesStatCommand.php | 3 +- src/Commands/WarmStatsCacheCommand.php | 40 + src/Concerns/ExportsCategoricalStats.php | 67 +- src/Concerns/ExportsTimeSeriesStats.php | 86 +- src/Concerns/HasDateRange.php | 2 + src/Concerns/HasFilters.php | 2 +- src/Concerns/InvalidatesStats.php | 47 + src/Concerns/IsCategoricalStatsProvider.php | 2 +- src/Contracts/HasFields.php | 37 + src/Contracts/InteractsWithDateRange.php | 2 - src/Contracts/InteractsWithFilters.php | 2 + src/Contracts/TimeSeriesStatsRepository.php | 4 +- src/Enums/CategoricalModes.php | 19 +- src/Enums/Concerns/HasLabels.php | 40 + src/Enums/PresetDateRanges.php | 30 +- src/Enums/TimeSeriesModes.php | 143 +- src/Exceptions/InvalidFiltersException.php | 3 +- src/Exceptions/StatsException.php | 28 + src/Exports/CategoricalStatsExport.php | 95 +- src/Exports/CsvExport.php | 110 + src/Exports/TimeSeriesStatsExport.php | 115 +- src/Facades/Stats.php | 30 + src/Filters/AbstractFilter.php | 1 + src/Filters/ClosureFilter.php | 5 +- src/Filters/ModelFilter.php | 81 + src/Filters/ScopeFilter.php | 6 +- src/Filters/StatsFilter.php | 13 +- .../CombinedCategoricalStatsFormatter.php | 2 +- .../AbstractTimeSeriesStatsFormatter.php | 1 + .../TimeSeries/ChartjsStatsFormatter.php | 3 +- .../TimeSeries/CombinedStatsFormatter.php | 5 +- .../TimeSeries/DefaultStatsFormatter.php | 1 + .../TimeSeries/FlotStatsFormatter.php | 1 + .../SparklineChartsStatsFormatter.php | 1 + src/Forms/CategoricalStatsForm.php | 72 + src/Forms/Fields/CategorySelect.php | 45 + src/Forms/StatsForm.php | 154 + src/Forms/TimeSeriesStatsForm.php | 54 + .../AbstractCategoricalStatGenerator.php | 7 +- src/Generators/AbstractStatGenerator.php | 6 +- .../Api/CategoricalStatsApiController.php | 112 +- .../Api/CategoryProviderApiController.php | 26 +- .../Api/TimeSeriesStatsApiController.php | 127 +- .../CategoricalStatsController.php | 128 +- .../Controllers/Concerns/RendersStatsPage.php | 103 + .../Controllers/TimeSeriesStatsController.php | 108 +- .../AbortIfCannotViewAnyTimeSeriesStats.php | 8 +- src/Http/Requests/CategoricalStatsRequest.php | 153 +- src/Http/Requests/Concerns/ResolvesStats.php | 177 + src/Http/Requests/TimeSeriesStatsRequest.php | 173 +- src/Jobs/WarmStatsCache.php | 38 + src/Policies/StatsPolicy.php | 34 + .../AbstractCategoricalStatsRepository.php | 16 +- .../CountCategoricalStatsRepository.php | 2 +- .../SumCategoricalStatsRepository.php | 2 +- .../AbstractTimeSeriesStatsRepository.php | 16 +- .../TimeSeries/ActivityLogStatsRepository.php | 5 +- .../TimeSeries/AggregateStatsRepository.php | 110 +- .../TimeSeries/CountStatsRepository.php | 3 +- .../TimeSeries/LoginsRepository.php | 6 +- .../TimeSeries/SignupsRepository.php | 4 +- .../TimeSeries/SumStatsRepository.php | 7 +- .../TimeSeries/UserLoginsRepository.php | 8 +- .../TimeSeries/UserSignupsRepository.php | 25 +- src/Routing/StatsRoutes.php | 145 + src/StatsManager.php | 148 + src/StatsServiceProvider.php | 167 +- src/Support/CategoryProviderFactory.php | 2 +- src/Support/CategoryProviderTitleResolver.php | 6 +- src/Support/ExactDateRange.php | 15 +- src/Support/ExportFileName.php | 25 + src/Support/Numbers.php | 97 + src/Support/TimeSeriesSteps.php | 58 + src/TimeSeriesStats.php | 88 +- src/Views/Components/Categorical.php | 59 - src/Views/Components/CategoryChart.php | 8 + src/Views/Components/Chart.php | 380 ++ src/Views/Components/Component.php | 53 - src/Views/Components/PieChart.php | 8 + src/Views/Components/Sparkline.php | 8 + src/Views/Components/StatTile.php | 26 + src/Views/Components/TimeSeries.php | 41 - src/Views/Components/TimeSeriesChart.php | 8 + src/Views/Svg/CartesianChart.php | 460 ++ src/Views/Svg/PieGeometry.php | 78 + src/Views/Svg/Scale.php | 62 + src/Views/Svg/SparklineGeometry.php | 46 + src/Views/ViewRenderer.php | 64 + tests/Concerns/InteractsWithStats.php | 55 + .../GenerateCategoricalStatCommandTest.php | 18 +- .../GenerateTimeSeriesStatCommandTest.php | 12 +- .../CategoricalStatsApiControllerTest.php | 20 +- .../CategoricalStatsControllerTest.php | 75 - .../TimeSeriesStatsApiControllerTest.php | 114 +- .../TimeSeriesStatsControllerTest.php | 134 - .../Formatters/ChartjsStatsFormatterTest.php | 19 +- .../Formatters/CombinedStatsFormatterTest.php | 10 +- .../Formatters/DefaultStatsFormatterTest.php | 11 +- .../Formatters/FlotStatsFormatterTest.php | 10 +- .../SparklineChartsStatsFormatterTest.php | 11 +- ...bortIfCannotViewAnyTimeSeriesStatsTest.php | 42 +- .../Pages/CategoricalStatsPageTest.php | 112 + .../Feature/Pages/TimeSeriesStatsPageTest.php | 171 + .../CategoricalStatsRepositoryMySqlTest.php | 8 +- .../CategoricalStatsRepositoryTest.php | 19 +- .../Repositories/CountStatsRepositoryTest.php | 42 +- .../Repositories/SumStatsRepositoryTest.php | 39 +- .../Repositories/UserLoginsRepositoryTest.php | 15 +- .../UserSignupsRepositoryTest.php | 8 +- tests/TestCase.php | 113 +- .../TestSupport/Factories/ActivityFactory.php | 2 +- .../TestSupport/Factories/PaymentFactory.php | 6 + .../Models/CategoricalSearchUser.php | 2 +- .../Models/CategoryProviderUser.php | 2 +- tests/TestSupport/Models/Payment.php | 6 +- tests/TestSupport/Models/User.php | 10 +- tests/TestSupport/MySQLRefreshDatabase.php | 33 - .../TestSupport/MySQLRefreshDatabaseState.php | 12 - .../Providers/TestServiceProvider.php | 5 +- .../TimeSeries/PaymentsByStatusCount.php | 49 + .../Stats/TimeSeries/PaymentsCount.php | 1 + .../Stats/TimeSeries/PaymentsSum.php | 1 + .../TimeSeries/TestUserLoginsRepository.php | 2 +- .../TimeSeries/TestUserSignupsRepository.php | 2 +- .../TimeSeries/UserLogoutsRepository.php | 1 + .../TimeSeries/UserLogoutsUnregistered.php | 1 + .../TimeSeries/UserLogoutsWithPermission.php | 1 + .../2024_02_05_223412_create_users_table.php | 5 +- ...02_06_000000_create_activity_log_table.php | 34 + ...024_07_22_223412_create_payments_table.php | 6 +- tests/Unit/CategoricalStatsTest.php | 10 +- .../CategoryProviderTest.php | 58 +- tests/Unit/Concerns/HasDateRangeTest.php | 53 +- tests/Unit/Concerns/HasFiltersTest.php | 47 +- tests/Unit/Enums/PresetDateRangesTest.php | 13 +- tests/Unit/Enums/TimeSeriesModesTest.php | 73 +- tests/Unit/Filters/ClosureFilterTest.php | 11 +- tests/Unit/Filters/ExactFilterTest.php | 20 +- tests/Unit/Filters/ScopeFilterTest.php | 20 +- .../AbstractTimeSeriesStatsFormatterTest.php | 13 +- .../Generators/CountStatsGeneratorTest.php | 5 +- .../Unit/Generators/SumStatsGeneratorTest.php | 4 +- .../AbstractTimeSeriesStatsRepositoryTest.php | 34 +- tests/Unit/Support/ExactDateRangeTest.php | 4 +- tests/Unit/TimeSeriesStatsTest.php | 91 +- tests/js/setup.ts | 5 + tsconfig.json | 26 + vite.config.ts | 26 + vitest.config.ts | 24 + 200 files changed, 13525 insertions(+), 2629 deletions(-) delete mode 100644 .github/coverage.svg delete mode 100644 .github/workflows/run-tests.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 .php_cs.dist.php create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 dist/.vite/manifest.json create mode 100644 dist/assets/charts-B-a9Wmv-.css create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 pint.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 resources/css/charts.css create mode 100644 resources/views/categorical/index.blade.php create mode 100644 resources/views/components/charts/cartesian.blade.php create mode 100644 resources/views/components/charts/delta.blade.php create mode 100644 resources/views/components/charts/header.blade.php create mode 100644 resources/views/components/charts/pie.blade.php create mode 100644 resources/views/components/charts/sparkline.blade.php create mode 100644 resources/views/components/charts/stat-tile.blade.php create mode 100644 resources/views/components/charts/table.blade.php create mode 100644 resources/views/components/stat-tiles.blade.php delete mode 100644 resources/views/material-admin-26/categorical-stats/_form.blade.php delete mode 100644 resources/views/material-admin-26/categorical-stats/_generator.blade.php delete mode 100644 resources/views/material-admin-26/categorical-stats/_script.blade.php delete mode 100644 resources/views/material-admin-26/categorical-stats/index.blade.php delete mode 100644 resources/views/material-admin-26/time-series-stats/_form.blade.php delete mode 100644 resources/views/material-admin-26/time-series-stats/_generator.blade.php delete mode 100644 resources/views/material-admin-26/time-series-stats/_script.blade.php delete mode 100644 resources/views/material-admin-26/time-series-stats/index.blade.php delete mode 100644 resources/views/material-admin-26/time-series-stats/stats.blade.php create mode 100644 resources/views/partials/page.blade.php create mode 100644 resources/views/time-series/index.blade.php create mode 100644 src/Cache/StatsCache.php create mode 100644 src/Charts/CategoryChart.php create mode 100644 src/Charts/Chart.php create mode 100644 src/Charts/Concerns/HasCategories.php create mode 100644 src/Charts/Concerns/HasStats.php create mode 100644 src/Charts/PieChart.php create mode 100644 src/Charts/Sparkline.php create mode 100644 src/Charts/StatTile.php create mode 100644 src/Charts/TimeSeriesChart.php create mode 100644 src/Commands/WarmStatsCacheCommand.php create mode 100644 src/Concerns/InvalidatesStats.php create mode 100644 src/Contracts/HasFields.php create mode 100644 src/Enums/Concerns/HasLabels.php create mode 100644 src/Exceptions/StatsException.php create mode 100644 src/Exports/CsvExport.php create mode 100644 src/Facades/Stats.php create mode 100644 src/Filters/ModelFilter.php create mode 100644 src/Forms/CategoricalStatsForm.php create mode 100644 src/Forms/Fields/CategorySelect.php create mode 100644 src/Forms/StatsForm.php create mode 100644 src/Forms/TimeSeriesStatsForm.php create mode 100644 src/Http/Controllers/Concerns/RendersStatsPage.php create mode 100644 src/Http/Requests/Concerns/ResolvesStats.php create mode 100644 src/Jobs/WarmStatsCache.php create mode 100644 src/Policies/StatsPolicy.php create mode 100644 src/Routing/StatsRoutes.php create mode 100644 src/StatsManager.php create mode 100644 src/Support/ExportFileName.php create mode 100644 src/Support/Numbers.php create mode 100644 src/Support/TimeSeriesSteps.php delete mode 100644 src/Views/Components/Categorical.php create mode 100644 src/Views/Components/CategoryChart.php create mode 100644 src/Views/Components/Chart.php delete mode 100644 src/Views/Components/Component.php create mode 100644 src/Views/Components/PieChart.php create mode 100644 src/Views/Components/Sparkline.php create mode 100644 src/Views/Components/StatTile.php delete mode 100644 src/Views/Components/TimeSeries.php create mode 100644 src/Views/Components/TimeSeriesChart.php create mode 100644 src/Views/Svg/CartesianChart.php create mode 100644 src/Views/Svg/PieGeometry.php create mode 100644 src/Views/Svg/Scale.php create mode 100644 src/Views/Svg/SparklineGeometry.php create mode 100644 src/Views/ViewRenderer.php create mode 100644 tests/Concerns/InteractsWithStats.php delete mode 100644 tests/Feature/Controllers/CategoricalStatsControllerTest.php delete mode 100644 tests/Feature/Controllers/TimeSeriesStatsControllerTest.php create mode 100644 tests/Feature/Pages/CategoricalStatsPageTest.php create mode 100644 tests/Feature/Pages/TimeSeriesStatsPageTest.php delete mode 100644 tests/TestSupport/MySQLRefreshDatabase.php delete mode 100644 tests/TestSupport/MySQLRefreshDatabaseState.php create mode 100644 tests/TestSupport/Stats/TimeSeries/PaymentsByStatusCount.php create mode 100644 tests/TestSupport/database/2024_02_06_000000_create_activity_log_table.php create mode 100644 tests/js/setup.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts create mode 100644 vitest.config.ts diff --git a/.github/coverage.svg b/.github/coverage.svg deleted file mode 100644 index 03d4ff0..0000000 --- a/.github/coverage.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - coverage - - 81 % - - \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml deleted file mode 100644 index e334463..0000000 --- a/.github/workflows/run-tests.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: tests - -on: - pull_request: - branches: - - main - - 'feature/**' - push: - branches: - - main - - 'feature/**' - -jobs: - tests: - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: true - matrix: - os: [ ubuntu-latest ] - php: [ 8.4, 8.3, 8.2, 8.1 ] - laravel: [ 13.*, 12.*, 11.*, 10.*, 9.* ] - stability: [ prefer-lowest, prefer-stable ] - include: - - laravel: 13.* - testbench: 11.* - carbon: ^3.8.4 - - laravel: 12.* - testbench: 10.* - carbon: ^3.8.4 - - laravel: 11.* - testbench: ^9.2 - carbon: ^3.0 - - laravel: 10.* - testbench: 8.* - carbon: ^2.63 - - laravel: 9.* - testbench: 7.* - carbon: ^2.63 - exclude: - - laravel: 13.* - php: 8.2 - - laravel: 13.* - php: 8.1 - - laravel: 12.* - php: 8.1 - - laravel: 11.* - php: 8.1 - - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} - - services: - mysql: - image: mysql:8.0 - env: - MYSQL_USER: user - MYSQL_PASSWORD: secret - MYSQL_DATABASE: stats_test - MYSQL_ROOT_PASSWORD: secretroot - ports: - - 3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install SQLite 3 - run: | - sudo apt-get update - sudo apt-get install sqlite3 -y - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv - coverage: ${{ startsWith(github.event.head_commit.message, 'coverage') && matrix.php == '8.4' && matrix.laravel == '13.*' && matrix.stability == 'prefer-stable' && 'xdebug' || 'none' }} - - - name: Install dependencies - run: | - composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:${{ matrix.carbon }}" --no-interaction --no-update - composer update --${{ matrix.stability }} --prefer-dist --no-interaction - - - name: Execute tests - run: | - vendor/bin/phpunit ${{ startsWith(github.event.head_commit.message, 'coverage') && matrix.php == '8.4' && matrix.laravel == '13.*' && matrix.stability == 'prefer-stable' && '--coverage-clover=clover.xml' || '' }} - env: - DB_USERNAME: user - DB_PASSWORD: secret - DB_PORT: ${{ job.services.mysql.ports[3306] }} - - - name: Make code coverage badge - if: startsWith(github.event.head_commit.message, 'coverage') && matrix.php == '8.4' && matrix.laravel == '13.*' && matrix.stability == 'prefer-stable' - uses: timkrase/phpunit-coverage-badge@v1.2.1 - with: - coverage_badge_path: .github/coverage.svg - push_badge: true - repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..5dd3506 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,257 @@ +name: tests + +on: + pull_request: + push: + branches: + - main + - next-gen + workflow_dispatch: + +# The unreleased javaabu/* dependencies come from VCS repositories; the job's own +# token keeps Composer within GitHub's API limits +env: + COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ github.token }}"}}' + +jobs: + pint: + runs-on: ubuntu-latest + + name: Pint + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction --no-progress + + - name: Check code style + run: vendor/bin/pint --test + + dependencies: + runs-on: ubuntu-latest + + name: Production dependencies + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction --no-progress + + # javaabu/helpers pins javaabu/settings ^1, which can't be installed next to settings v2 + - name: No javaabu/helpers or javaabu/settings ^1 + run: | + composer show --tree --no-dev | tee tree.txt + if grep -E "javaabu/helpers|javaabu/settings (\^|~)?1" tree.txt; then + echo "javaabu/helpers or javaabu/settings ^1 is in the production dependency tree" && exit 1 + fi + + js: + runs-on: ubuntu-latest + + name: React charts and pages, Blade styles + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + # The pages import stencil's React renderer from vendor/javaabu/stencil + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + coverage: none + + - name: Install Composer dependencies + run: composer update --prefer-dist --no-interaction --no-progress + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + # Keep the store out of pnpm's own node_modules/.bin, where @javaabu/forms' + # prepare build can't emit portable types (Javaabu/react-forms#20). + - name: Use a pnpm store outside node_modules + run: pnpm config set store-dir ~/.pnpm-store + + - name: Setup Node + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: pnpm + + - name: Install npm dependencies + run: pnpm install --frozen-lockfile + + - run: pnpm lint + - run: pnpm format:check + - run: pnpm typecheck + + - name: Tests with coverage + run: pnpm test:coverage + + - name: Upload coverage + uses: codecov/codecov-action@v5 + with: + files: coverage/lcov.info + flags: js + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: false + + - name: Check that the committed dist is up to date + run: | + pnpm build + git diff --exit-code -- dist || (echo "dist is out of date: run pnpm build and commit it." && exit 1) + + databases: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + database: [ mysql, mariadb, pgsql ] + + name: ${{ matrix.database }} + + services: + mysql: + image: mysql:8.4 + env: + MYSQL_DATABASE: stats_test + MYSQL_ROOT_PASSWORD: secret + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 + + mariadb: + image: mariadb:11 + env: + MARIADB_DATABASE: stats_test + MARIADB_ROOT_PASSWORD: secret + ports: + - 3307:3306 + options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=5 + + postgres: + image: postgres:17 + env: + POSTGRES_DB: stats_test + POSTGRES_PASSWORD: secret + ports: + - 5432:5432 + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=5 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + extensions: curl, mbstring, zip, pcntl, pdo, pdo_mysql, pdo_pgsql, sqlite, pdo_sqlite, iconv, intl + coverage: none + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit + env: + DB_CONNECTION: ${{ matrix.database }} + DB_DATABASE: stats_test + DB_HOST: 127.0.0.1 + DB_PORT: ${{ matrix.database == 'pgsql' && 5432 || (matrix.database == 'mariadb' && 3307 || 3306) }} + DB_USERNAME: ${{ matrix.database == 'pgsql' && 'postgres' || 'root' }} + DB_PASSWORD: secret + + without-optional: + runs-on: ubuntu-latest + + name: Without optional packages + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv + coverage: none + + - name: Install dependencies without activitylog and Inertia + run: | + composer remove --dev spatie/laravel-activitylog inertiajs/inertia-laravel --no-interaction --no-update + composer update --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit + + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: [ 8.4, 8.5 ] + laravel: [ 12.*, 13.* ] + stability: [ prefer-lowest, prefer-stable ] + include: + - laravel: 12.* + testbench: 10.* + - laravel: 13.* + testbench: 11.* + + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + + env: + COVERAGE: ${{ matrix.php == '8.4' && matrix.laravel == '13.*' && matrix.stability == 'prefer-stable' }} + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv, intl + coverage: ${{ env.COVERAGE == 'true' && 'pcov' || 'none' }} + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + + - name: Execute tests + if: env.COVERAGE != 'true' + run: vendor/bin/phpunit + + - name: Execute tests with coverage + if: env.COVERAGE == 'true' + run: vendor/bin/phpunit --coverage-clover build/coverage.xml + + - name: Upload coverage to Codecov + if: env.COVERAGE == 'true' + uses: codecov/codecov-action@v5 + with: + files: build/coverage.xml + flags: php + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 8683765..0022109 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage .php-cs-fixer.cache .idea clover.xml +node_modules diff --git a/.php_cs.dist.php b/.php_cs.dist.php deleted file mode 100644 index 8648fdc..0000000 --- a/.php_cs.dist.php +++ /dev/null @@ -1,35 +0,0 @@ -in([ - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->name('*.php') - ->notName('*.blade.php') - ->ignoreDotFiles(true) - ->ignoreVCS(true); - -return (new PhpCsFixer\Config()) - ->setRules([ - '@PSR12' => true, - 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => ['sort_algorithm' => 'alpha'], - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => true, - 'trailing_comma_in_multiline' => true, - 'phpdoc_scalar' => true, - 'unary_operator_spaces' => true, - 'binary_operator_spaces' => true, - 'blank_line_before_statement' => [ - 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], - ], - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_var_without_name' => true, - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - 'keep_multiple_spaces_after_comma' => true, - ], - 'single_trait_insert_per_statement' => true, - ]) - ->setFinder($finder); diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..92bdd84 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,16 @@ +dist +coverage +node_modules +vendor +build +pnpm-lock.yaml +*.php +*.md +tests/js/fixtures +composer.json +composer.lock +docs/**/*.json +lang +stubs +.github +pint.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..6c1772b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "semi": false, + "printWidth": 120, + "tabWidth": 2 +} diff --git a/README.md b/README.md index e99fa6b..35cfeb3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ # Stats -[![Latest Version on Packagist](https://img.shields.io/packagist/v/javaabu/stats.svg?style=flat-square)](https://packagist.org/packages/javaabu/stats) -[![Test Status](../../actions/workflows/run-tests.yml/badge.svg)](../../actions/workflows/run-tests.yml) -![Code Coverage Badge](./.github/coverage.svg) -[![Total Downloads](https://img.shields.io/packagist/dt/javaabu/stats.svg?style=flat-square)](https://packagist.org/packages/javaabu/stats) +[![Tests](https://github.com/n4ff4h/stats/actions/workflows/tests.yml/badge.svg?branch=next-gen)](https://github.com/n4ff4h/stats/actions/workflows/tests.yml?query=branch%3Anext-gen) +[![codecov](https://codecov.io/gh/n4ff4h/stats/branch/next-gen/graph/badge.svg)](https://codecov.io/gh/n4ff4h/stats/tree/next-gen) +[![Latest Version on Packagist](https://img.shields.io/packagist/v/javaabu/stats.svg)](https://packagist.org/packages/javaabu/stats) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md) - -## Introduction -Simplifies stats generation for Laravel +Time series and categorical stats for Laravel: define a stat once, then chart it in Blade or Inertia React with Onu, compare periods, cache dashboards and export CSV. ## Documentation diff --git a/composer.json b/composer.json index 422a094..9cd190c 100644 --- a/composer.json +++ b/composer.json @@ -1,37 +1,83 @@ { "name": "javaabu/stats", - "description": "Simplifies stats generation for Laravel", + "description": "Time series and categorical stats for Laravel: define a stat once, then chart it in Blade or Inertia React with Onu, compare periods, cache dashboards and export CSV", "homepage": "https://github.com/Javaabu/stats", "license": "MIT", "keywords": [ "javaabu", "laravel", - "stats" + "stats", + "charts", + "dashboard", + "inertia", + "dhivehi" ], "authors": [ { "name": "Arushad Ahmed", "email": "arushad@javaabu.com", "role": "Developer" + }, + { + "name": "Javaabu Pvt. Ltd.", + "email": "info@javaabu.com", + "homepage": "https://javaabu.com", + "role": "Developer" + } + ], + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/Javaabu/filters" + }, + { + "type": "vcs", + "url": "https://github.com/Javaabu/forms" + }, + { + "type": "vcs", + "url": "https://github.com/Javaabu/manifest" + }, + { + "type": "vcs", + "url": "https://github.com/Javaabu/permissions" + }, + { + "type": "vcs", + "url": "https://github.com/Javaabu/stencil" } ], "require": { - "php": "^8.1", - "illuminate/support": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", - "maatwebsite/excel": "^3.1", - "javaabu/forms": "^1.68", - "javaabu/settings": "^1.8", - "javaabu/generator-helpers": "^1.3" + "php": "^8.4", + "illuminate/cache": "^12.0 || ^13.0", + "illuminate/console": "^12.0 || ^13.0", + "illuminate/contracts": "^12.0 || ^13.0", + "illuminate/database": "^12.0 || ^13.0", + "illuminate/http": "^12.0 || ^13.0", + "illuminate/queue": "^12.0 || ^13.0", + "illuminate/routing": "^12.0 || ^13.0", + "illuminate/support": "^12.0 || ^13.0", + "illuminate/validation": "^12.0 || ^13.0", + "illuminate/view": "^12.0 || ^13.0", + "javaabu/filters": "^1.0@dev", + "javaabu/forms": "^2.0@dev", + "javaabu/generator-helpers": "^1.3", + "javaabu/manifest": "^1.0@dev", + "javaabu/permissions": "^3.0@dev", + "javaabu/stencil": "^1.0@dev", + "nesbot/carbon": "^3.8.4" }, "require-dev": { - "laravel/pint": "^1.14", - "orchestra/testbench": "^7.0 || ^8.0 || ^9.2 || ^10.0 || ^11.0", - "phpunit/phpunit": "^9.5 || ^10.5 || ^11.5.3 || ^12.5.8 || ^13.0.3", - "spatie/laravel-activitylog": "^4.8", - "plannr/laravel-fast-refresh-database": "^1.2" + "inertiajs/inertia-laravel": "^2.0", + "javaabu/boost-javaabu-guidelines": "^1.4", + "laravel/pint": "^1.24", + "orchestra/testbench": "^10.0 || ^11.0", + "phpunit/phpunit": "^11.5.3 || ^12.5.8", + "spatie/laravel-activitylog": "^4.10" }, "suggest": { - "spatie/laravel-activitylog": "Required for included activity log based stats" + "inertiajs/inertia-laravel": "Renders the stats pages with Inertia React when stats.views is inertia (^2.0)", + "spatie/laravel-activitylog": "Needed by the built-in user logins stat (^4.10)" }, "autoload": { "psr-4": { @@ -43,16 +89,28 @@ "Javaabu\\Stats\\Tests\\": "tests/" } }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-coverage": "vendor/bin/phpunit --coverage-clover build/coverage.xml", + "lint": "vendor/bin/pint --test", + "format": "vendor/bin/pint" + }, "extra": { "branch-alias": { - "dev-main": "1.0-dev" + "dev-next-gen": "2.x-dev" }, "laravel": { "providers": [ "Javaabu\\Stats\\StatsServiceProvider" - ] + ], + "aliases": { + "Stats": "Javaabu\\Stats\\Facades\\Stats" + } } }, + "config": { + "sort-packages": true + }, "minimum-stability": "dev", "prefer-stable": true } diff --git a/config/stats.php b/config/stats.php index 9b599ce..ae883e5 100644 --- a/config/stats.php +++ b/config/stats.php @@ -1,41 +1,66 @@ class. You can also register them from a + | service provider with TimeSeriesStats::register() and + | CategoricalStats::register(). | */ - 'week_starts_on_sunday' => true, + 'time_series_stats' => [ + // + ], + 'categorical_stats' => [ + // + ], /* |-------------------------------------------------------------------------- - | The locale used for dates + | Built-in stats |-------------------------------------------------------------------------- | - | The locale used to format dates + | Registers user_signups (and user_logins, with spatie/laravel-activitylog) + | for user_model, or for the model of the default guard's user provider + | when user_model is null. | */ - 'date_locale' => 'en_GB', + 'default_stats' => true, + + 'user_model' => null, /* |-------------------------------------------------------------------------- - | Date formats for different time modes + | Dates |-------------------------------------------------------------------------- | - | Used to format the date when generating stats in different time series modes - | Uses momentJS compatible formats + | week_starts_on_sunday: true for Sunday, false for Monday. + | date_locale: the locale used to work out weeks. + | date_formats: how each step is shown, as Carbon isoFormat (moment.js) formats. | */ + 'week_starts_on_sunday' => true, + + 'date_locale' => 'en_GB', + 'date_formats' => [ 'hour' => 'D MMM YY hh:mm A', 'day' => 'D MMM YY', @@ -46,97 +71,96 @@ /* |-------------------------------------------------------------------------- - | Default time series mode + | Defaults |-------------------------------------------------------------------------- | - | Default mode to show on the stats form + | What the stats pages and charts use when nothing else is chosen. | */ - 'default_time_series_mode' => \Javaabu\Stats\Enums\TimeSeriesModes::DAY, + 'default_time_series_mode' => TimeSeriesModes::DAY, - /* - |-------------------------------------------------------------------------- - | Default categorical mode - |-------------------------------------------------------------------------- - */ + 'default_categorical_mode' => CategoricalModes::NON_EMPTY, - 'default_categorical_mode' => \Javaabu\Stats\Enums\CategoricalModes::NON_EMPTY, - - /* - |-------------------------------------------------------------------------- - | Categorical items per page - |-------------------------------------------------------------------------- - | - | Default page size for the categorical provider search endpoint. - | - */ + 'default_date_range' => PresetDateRanges::LAST_7_DAYS, 'categorical_items_per_page' => 15, /* |-------------------------------------------------------------------------- - | Default date range + | Cache |-------------------------------------------------------------------------- | - | Default date range to show on stats + | Charts (StatTile, TimeSeriesChart, …) cache their data. Results of + | periods that ended before today keep for ttl.past seconds; anything that + | includes today keeps for ttl.current seconds. store null means the app's + | default cache store. Stats::forget('metric') and the InvalidatesStats + | model trait clear them. + | + | pages: whether the stats pages cache too (off: they show live data). + | warm: the queue connection and queue of the WarmStatsCache job. | */ - 'default_date_range' => \Javaabu\Stats\Enums\PresetDateRanges::LAST_7_DAYS, + 'cache' => [ + 'enabled' => env('STATS_CACHE', true), + 'store' => env('STATS_CACHE_STORE'), + 'prefix' => 'javaabu_stats', + 'ttl' => [ + 'current' => 60 * 10, + 'past' => 60 * 60 * 24, + ], + 'pages' => false, + 'warm' => [ + 'connection' => null, + 'queue' => null, + ], + ], /* |-------------------------------------------------------------------------- - | Default CSS Framework + | Views |-------------------------------------------------------------------------- | - | This option controls the default CSS framework that will be used by the - | package when rendering views + | The pages Route::stats() renders: blade (javaabu/forms components and + | SVG charts) or inertia (React pages with Onu's charts, shipped through + | javaabu/manifest). The Blade layout the pages extend, and the section + | they fill. | - | Supported: "material-admin-26" + | rtl_locales: locales whose Blade charts run right to left. | */ - 'framework' => 'material-admin-26', + 'views' => env('STATS_VIEWS', 'blade'), - /* - |-------------------------------------------------------------------------- - | Default Layout - |-------------------------------------------------------------------------- - | - | Default layout view for stats views - | - */ + 'layout' => 'layouts.app', + + 'layout_section' => 'content', - 'default_layout' => 'layouts.admin', + 'rtl_locales' => ['dv', 'ar', 'fa', 'he', 'ur'], /* |-------------------------------------------------------------------------- - | Scripts Stack + | Controllers and authorization |-------------------------------------------------------------------------- | - | The name of the stack to push scripts + | Replace a controller with a subclass, or pass `controllers` to + | Route::stats() / Route::statsApi(). The policy here is the package + | default; apps can also override it with config('permissions.policies') + | or Gate::policy(). Turn off register_permissions to register your own. | */ - 'scripts_stack' => 'scripts', - - /* - |-------------------------------------------------------------------------- - | View for time series stats - |-------------------------------------------------------------------------- - | - | Used in the main time series stats controller - | - */ + 'controllers' => [ + 'time_series' => TimeSeriesStatsController::class, + 'categorical' => CategoricalStatsController::class, + 'time_series_api' => TimeSeriesStatsApiController::class, + 'categorical_api' => CategoricalStatsApiController::class, + 'categories' => CategoryProviderApiController::class, + ], - 'time_series_stats_view' => 'stats::material-admin-26.time-series-stats.index', + 'policy' => StatsPolicy::class, - /* - |-------------------------------------------------------------------------- - | View for categorical stats - |-------------------------------------------------------------------------- - */ + 'register_permissions' => true, - 'categorical_stats_view' => 'stats::material-admin-26.categorical-stats.index', ]; diff --git a/dist/.vite/manifest.json b/dist/.vite/manifest.json new file mode 100644 index 0000000..a4c06f6 --- /dev/null +++ b/dist/.vite/manifest.json @@ -0,0 +1,11 @@ +{ + "resources/css/charts.css": { + "file": "assets/charts-B-a9Wmv-.css", + "name": "charts", + "names": [ + "charts.css" + ], + "src": "resources/css/charts.css", + "isEntry": true + } +} \ No newline at end of file diff --git a/dist/assets/charts-B-a9Wmv-.css b/dist/assets/charts-B-a9Wmv-.css new file mode 100644 index 0000000..bc038b6 --- /dev/null +++ b/dist/assets/charts-B-a9Wmv-.css @@ -0,0 +1 @@ +.stats-chart,.stats-tile,.stats-sparkline,.stats-tiles,.stats-page{--stats-foreground:var(--foreground,#0a0a0a);--stats-muted:var(--muted-foreground,#737373);--stats-border:var(--border,#e5e5e5);--stats-card:var(--card,#fff);--stats-card-foreground:var(--card-foreground,var(--stats-foreground));--stats-radius:var(--radius,.625rem);--stats-positive:var(--success,#16a34a);--stats-negative:var(--destructive,#dc2626);--stats-previous:var(--muted-foreground,#a3a3a3)}.stats-sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;block-size:1px;inline-size:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.stats-chart{inline-size:100%;min-inline-size:0;color:var(--stats-foreground);flex-direction:column;gap:.75rem;margin:0;display:flex}.stats-chart__header{gap:.125rem;display:grid}.stats-chart__title{font-weight:500;line-height:1.375}.stats-chart__description,.stats-chart__empty,.stats-page__empty{color:var(--stats-muted);font-size:.875rem}.stats-chart__totals{flex-wrap:wrap;align-items:flex-end;gap:.5rem 1.5rem;display:flex}.stats-chart__total{gap:.125rem;display:grid}.stats-chart__total-label{color:var(--stats-muted);font-size:.75rem}.stats-chart__total-value{font-variant-numeric:tabular-nums;font-size:1.5rem;font-weight:600;line-height:1}.stats-chart__total--previous .stats-chart__total-value{color:var(--stats-muted);font-size:1.125rem;font-weight:400}.stats-chart__body{min-inline-size:0}.stats-chart__svg{block-size:auto;inline-size:100%;display:block;overflow:visible}.stats-chart__grid line{stroke:var(--stats-border);stroke-width:1px}.stats-chart__zero{stroke:var(--stats-muted)!important}.stats-chart__tick{fill:var(--stats-muted);font-variant-numeric:tabular-nums;font-size:11px}.stats-chart__line{fill:none;stroke:var(--stats-series);stroke-width:2px;stroke-linejoin:round;stroke-linecap:round}.stats-chart__area{fill:var(--stats-series);fill-opacity:.2;stroke:none}.stats-chart__bar{fill:var(--stats-series)}.stats-chart__series[data-previous] .stats-chart__bar{fill-opacity:.45}.stats-chart__point{fill:var(--stats-card);stroke:var(--stats-series);stroke-width:2px}.stats-chart__point[data-hidden]{opacity:0}.stats-chart__point:hover,.stats-chart__bar:hover{opacity:.85}.stats-chart__legend{color:var(--stats-muted);flex-wrap:wrap;justify-content:center;gap:.25rem 1rem;margin:.5rem 0 0;padding:0;font-size:.75rem;list-style:none;display:flex}.stats-chart__legend li{align-items:center;gap:.375rem;display:inline-flex}.stats-chart__swatch{background:var(--stats-series);border-radius:2px;block-size:.625rem;inline-size:.625rem;display:inline-block}.stats-chart__swatch[data-dashed]{border-block-start:2px dashed var(--stats-series);background:0 0;block-size:0}.stats-chart__details summary{cursor:pointer;color:var(--stats-muted);inline-size:-moz-fit-content;inline-size:fit-content;font-size:.875rem}.stats-chart__details summary:focus-visible{outline:2px solid var(--ring,var(--stats-foreground));outline-offset:2px}.stats-chart__table-wrapper:focus-visible{outline:2px solid var(--ring,var(--stats-foreground));outline-offset:2px}a.stats-tile:focus-visible{outline:2px solid var(--ring,var(--stats-foreground));outline-offset:2px}.stats-chart__table-wrapper{border:1px solid var(--stats-border);border-radius:var(--stats-radius);max-block-size:24rem;margin-block-start:.5rem;overflow:auto}.stats-chart__table{border-collapse:collapse;font-variant-numeric:tabular-nums;inline-size:100%;font-size:.875rem}.stats-chart__table th,.stats-chart__table td{text-align:start;border-block-end:1px solid var(--stats-border);padding:.5rem .75rem}.stats-chart__table thead th{background:var(--muted,#f5f5f5);font-weight:500;position:sticky;top:0}.stats-chart__table tbody th{font-weight:400}.stats-chart__table .stats-chart__number{text-align:end}.stats-chart__body--pie{flex-wrap:wrap;justify-content:center;align-items:center;gap:1rem 2rem;display:flex}.stats-chart__pie{inline-size:min(100%,15rem);position:relative}.stats-chart__slice{fill:var(--stats-series);stroke:var(--stats-card);stroke-width:2px}.stats-chart__center{pointer-events:none;flex-direction:column;justify-content:center;align-items:center;display:flex;position:absolute;top:0;bottom:0;left:0;right:0}.stats-chart__center-value{font-variant-numeric:tabular-nums;font-size:1.25rem;font-weight:600}.stats-chart__center-label{color:var(--stats-muted);font-size:.75rem}.stats-chart__legend--pie{flex-direction:column;align-items:stretch;font-size:.875rem}.stats-chart__legend--pie li{grid-template-columns:auto 1fr auto;gap:.5rem;display:grid}.stats-chart__legend-value{color:var(--stats-foreground);font-variant-numeric:tabular-nums}.stats-delta{font-variant-numeric:tabular-nums;flex-wrap:wrap;align-items:center;gap:.25rem;font-size:.875rem;display:inline-flex}.stats-delta[data-sentiment=positive] .stats-delta__value,.stats-delta[data-sentiment=positive] .stats-delta__icon{color:var(--stats-positive)}.stats-delta[data-sentiment=negative] .stats-delta__value,.stats-delta[data-sentiment=negative] .stats-delta__icon{color:var(--stats-negative)}.stats-delta__context{color:var(--stats-muted)}.stats-sparkline{block-size:2rem;inline-size:100%;color:var(--stats-series,var(--chart-1))}.stats-sparkline svg{block-size:100%;inline-size:100%;display:block}.stats-sparkline__line{fill:none;stroke:var(--stats-series,var(--chart-1));stroke-width:1.5px;stroke-linejoin:round}.stats-sparkline__area{fill:var(--stats-series,var(--chart-1));fill-opacity:.2}.stats-tiles{grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr));gap:1rem;display:grid}.stats-tiles[data-columns="2"]{grid-template-columns:repeat(auto-fit,minmax(max(min(100%,14rem),50% - .5rem),1fr))}.stats-tiles[data-columns="3"]{grid-template-columns:repeat(auto-fit,minmax(max(min(100%,14rem),33.3333% - .666667rem),1fr))}.stats-tiles[data-columns="4"]{grid-template-columns:repeat(auto-fit,minmax(max(min(100%,12rem),25% - .75rem),1fr))}.stats-tiles>*{min-inline-size:0}.stats-tile{border:1px solid var(--stats-border);border-radius:calc(var(--stats-radius) + 4px);background:var(--stats-card);color:var(--stats-card-foreground);flex-direction:column;padding:1.25rem 1.5rem;text-decoration:none;display:flex}a.stats-tile:hover{border-color:var(--stats-muted)}.stats-tile__header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.stats-tile__label{color:var(--stats-muted);font-size:.875rem;line-height:1.375rem}.stats-tile__icon{color:var(--stats-muted)}.stats-tile__value{font-variant-numeric:tabular-nums;margin-block-start:.5rem;font-size:1.875rem;line-height:1}.stats-tile .stats-delta,.stats-tile__period{margin-block-start:.25rem}.stats-tile__period{color:var(--stats-muted);font-size:.875rem}.stats-tile__sparkline{--stats-series:var(--chart-1);block-size:2.875rem;margin-block-start:.5rem}.stats-tile__footer{border-block-start:1px solid var(--stats-border);color:var(--stats-muted);margin-block-start:.75rem;padding-block-start:.5rem;font-size:.875rem}.stats-page{flex-direction:column;gap:1.5rem;display:flex}.stats-page__title{margin:0;font-size:1.5rem;font-weight:600}.stats-page__export{justify-content:flex-end;display:flex}@media (forced-colors:active){.stats-chart__area,.stats-sparkline__area{fill-opacity:1}}@media (prefers-reduced-motion:no-preference){.stats-chart__bar,.stats-chart__point{transition:opacity .15s}} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c00ebba --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,19 @@ +import js from "@eslint/js" +import reactHooks from "eslint-plugin-react-hooks" +import globals from "globals" +import tseslint from "typescript-eslint" + +export default tseslint.config( + { ignores: ["dist", "coverage", "node_modules", "vendor", "build"] }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ["**/*.{ts,tsx,js,mjs}"], + languageOptions: { globals: { ...globals.browser, ...globals.node } }, + plugins: { "react-hooks": reactHooks }, + rules: { + ...reactHooks.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }], + }, + }, +) diff --git a/package.json b/package.json new file mode 100644 index 0000000..a54db96 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "javaabu-stats-assets", + "private": true, + "description": "Build and test tooling for javaabu/stats' front end: the React charts and Inertia pages (resources/js, imported by apps as @javaabu/stats through javaabu/manifest) and the Blade chart styles (prebuilt into dist/). Apps don't install this.", + "license": "MIT", + "type": "module", + "packageManager": "pnpm@10.28.0", + "engines": { + "node": ">=20.19" + }, + "scripts": { + "build": "vite build", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "tsc -p tsconfig.json", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "check": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@inertiajs/react": "^2.3.0", + "@javaabu/forms": "github:Javaabu/react-forms#next-gen", + "@javaabu/i18n": "github:Javaabu/i18n#build-on-git-install", + "@javaabu/onu": "^0.2.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@types/node": "^26.6.6", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitest/coverage-v8": "^5.1.0", + "eslint": "^10.14.0", + "eslint-plugin-react-hooks": "^7.1.1", + "globals": "^17.14.0", + "jsdom": "^30.1.3", + "prettier": "^3.9.10", + "react": "^19.3.1", + "react-dom": "^19.3.1", + "recharts": "^3.10.1", + "typescript": "~6.0.3", + "typescript-eslint": "^8.72.1", + "vite": "^8.3.5", + "vitest": "^5.1.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@javaabu/forms", + "@javaabu/i18n" + ] + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ec3c1fa..2907de8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,19 +15,14 @@ src - - - - - - - + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..a676439 --- /dev/null +++ b/pint.json @@ -0,0 +1,24 @@ +{ + "preset": "laravel", + "rules": { + "concat_space": { + "spacing": "one" + }, + "global_namespace_import": { + "import_classes": true, + "import_constants": false, + "import_functions": false + }, + "no_unused_imports": true, + "ordered_imports": { + "sort_algorithm": "alpha" + }, + "php_unit_method_casing": { + "case": "snake_case" + } + }, + "exclude": [ + "tests/TestSupport/stubs", + "stubs" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c2b1bd0 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5485 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.14.0) + '@inertiajs/react': + specifier: ^2.3.0 + version: 2.3.29(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@javaabu/forms': + specifier: github:Javaabu/react-forms#next-gen + version: https://codeload.github.com/Javaabu/react-forms/tar.gz/9ba003a130a02bc5411a1e257c66a6c504a77d21(@inertiajs/react@2.3.29(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(@javaabu/onu@0.2.1(@tanstack/react-query@5.106.1(@types/react@19.3.0)(react@19.3.1))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-hook-form@7.91.0(@types/react@19.3.0)(react@19.3.1))(react@19.3.1)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1))(tailwindcss@4.3.4))(@testing-library/react@16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@javaabu/i18n': + specifier: github:Javaabu/i18n#build-on-git-install + version: https://codeload.github.com/Javaabu/i18n/tar.gz/76730212d6f0f1d87a78635ed6e005dd5713cbba(react@19.3.1) + '@javaabu/onu': + specifier: ^0.2.1 + version: 0.2.1(@tanstack/react-query@5.106.1(@types/react@19.3.0)(react@19.3.1))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-hook-form@7.91.0(@types/react@19.3.0)(react@19.3.1))(react@19.3.1)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1))(tailwindcss@4.3.4) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.2 + '@testing-library/jest-dom': + specifier: ^7.0.1 + version: 7.0.1(@testing-library/dom@10.4.2)(vitest@5.1.0) + '@testing-library/react': + specifier: ^16.3.3 + version: 16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@types/node': + specifier: ^26.6.6 + version: 26.6.6 + '@types/react': + specifier: ^19.3.0 + version: 19.3.0 + '@types/react-dom': + specifier: ^19.3.0 + version: 19.3.0(@types/react@19.3.0) + '@vitest/coverage-v8': + specifier: ^5.1.0 + version: 5.1.0(vitest@5.1.0) + eslint: + specifier: ^10.14.0 + version: 10.14.0 + eslint-plugin-react-hooks: + specifier: ^7.1.1 + version: 7.1.1(eslint@10.14.0) + globals: + specifier: ^17.14.0 + version: 17.14.0 + jsdom: + specifier: ^30.1.3 + version: 30.1.3 + prettier: + specifier: ^3.9.10 + version: 3.9.10 + react: + specifier: ^19.3.1 + version: 19.3.1 + react-dom: + specifier: ^19.3.1 + version: 19.3.1(react@19.3.1) + recharts: + specifier: ^3.10.1 + version: 3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1) + typescript: + specifier: ~6.0.3 + version: 6.0.3 + typescript-eslint: + specifier: ^8.72.1 + version: 8.72.1(eslint@10.14.0)(typescript@6.0.3) + vite: + specifier: ^8.3.5 + version: 8.3.6(@types/node@26.6.6) + vitest: + specifier: ^5.1.0 + version: 5.1.0(@types/node@26.6.6)(@vitest/coverage-v8@5.1.0)(jsdom@30.1.3)(vite@8.3.6(@types/node@26.6.6)) + +packages: + + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@asamuzakjp/css-color@7.1.9': + resolution: {integrity: sha512-vZ6JQVq8Ivgz6b/IBlAf90wZviXdluY13cQOPO+nJHbmXwGrPl2GcpqUY7a55/8ZVdfONXpCFMpRijOBCHNlcQ==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + '@asamuzakjp/dom-selector@10.0.5': + resolution: {integrity: sha512-EgmJ7oEaGQECTn3H3wmnQpEy4hKY66VvM7VQndwPIQob3p877BgbyOUE3EX0LgXQCciF2H8KMdR+RznqjMNOLw==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.13': + resolution: {integrity: sha512-j1nvOQCZYSgp7HLmisnCXFTpVvFODiijQ9QHkORfGKWH6YDtskTEeCJS7eV545GZ9UBzrq0e2xsOd5QQ5PjIqw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.13': + resolution: {integrity: sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.13': + resolution: {integrity: sha512-8WxJ4/Vmes3ghacj8VOJy3NW8eyHiFY/4thU/LSBuBE470+igjR/j1ZTNn8fV5VdNrLx6iXUwig526UdOvfP0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.13': + resolution: {integrity: sha512-/U96wOz+SCoZ7Da/8kjDGltcNY280JPQcRNPK0Hk3PTbdpaiBDQImtC6JsbVwb1HGMr3usZQU2WsWVjhYNmRzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.13': + resolution: {integrity: sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.13': + resolution: {integrity: sha512-laoRpD/kO/iA4CDlgJbixHekg7oPvWALK0PQyK3Ob2I2njZRvVAD77RfWCIImtqWCq6KijoBoZK7bp/HlTWASQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.13': + resolution: {integrity: sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.13': + resolution: {integrity: sha512-v+oN26xP4YIlQsbQ/CbTH1WG5m/l/DjKKZNy/Kp3f/AFvbgt7Em/D0FeMM3O2hX0YzeN2M16W8lOpyhIkfCBcQ==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.13': + resolution: {integrity: sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.11': + resolution: {integrity: sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.3': + resolution: {integrity: sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==} + hasBin: true + + '@cacheable/memory@2.2.0': + resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} + + '@cacheable/utils@2.5.0': + resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==} + + '@csstools/color-helpers@6.1.2': + resolution: {integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.4.3': + resolution: {integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-color-parser@4.2.7': + resolution: {integrity: sha512-zgm9yjQz5iJB4VL8RyVwd71TWB8Q89FSxt/NzXoWqPSkEBOVtn9L6vAPtSc9hpKfB0Awo/AL2VKFHjYetBLFHQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.2 + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-parser-algorithms@4.0.2': + resolution: {integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.2 + + '@csstools/css-syntax-patches-for-csstree@1.1.16': + resolution: {integrity: sha512-LsDbJPP/B8cPasUJyTA5S99F7C4G93ngW8hUluXz8CFeOaaRnBPtBiIlvgcPYU0HmHNz8CXg+1vwFrIB4F6Dgw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.2': + resolution: {integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==} + engines: {node: '>=20.19.0'} + + '@date-fns/tz@1.5.0': + resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==} + + '@dnd-kit/abstract@0.5.3': + resolution: {integrity: sha512-KZQ2vftkO9BHupVzlUqB5d7JETL7ITlvslx3hrj4aObwjliAzuzV/n5wCpQllNd0Hxvg7FaQtLekJ8dplqKwbA==} + + '@dnd-kit/collision@0.5.3': + resolution: {integrity: sha512-DBo8czkI72xnsHRWOMTn/GAyYgSWy9bmde85mrZ7ShqLhcbIbcu4PVJ3ZU9MBikePCSAVOoUTZE2gS40jogrEw==} + + '@dnd-kit/dom@0.5.3': + resolution: {integrity: sha512-JJe+Qu/JmofGzHes3np91sQYwp5spvLcknjRkq+VO59JTqSnQNPXlhHXavRXTwQFabaaRyRzXyH/faAvubCNGw==} + + '@dnd-kit/geometry@0.5.3': + resolution: {integrity: sha512-U+zscNjoHfJ1xUV+XDGF89jPCoILLivWSb/U9mfM7h83f6o3X9AS9uIwG46bXUpCqPvlI/9JcOlyga5uvfyjgw==} + + '@dnd-kit/helpers@0.5.3': + resolution: {integrity: sha512-fErbbjwKMJEdr7e+AMsXaZ6aQY4xPYBMK+nqybB34tE6/lQDLetZHsgLDKNVePsYIZCq/7uWMtlFjqysscWg5A==} + + '@dnd-kit/react@0.5.3': + resolution: {integrity: sha512-BGn6bakgcRSoKMRQP8cRIGu59e5lGC3/sL0zVWmDMo/9CMPSxgjCcQltDn5TDTwAARnEbuwNEksor4rLtH/yEg==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@dnd-kit/state@0.5.3': + resolution: {integrity: sha512-NJTQkBglAT/SXLc+IOLiKwNvsTrWmPG5AO+7Gn0F0n8XyVaf4VCbTCuPuU48sDBfcpg23O0tfGkfZNiZbYTvMw==} + + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.7.0': + resolution: {integrity: sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.3': + resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@exodus/bytes@1.16.0': + resolution: {integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inertiajs/core@2.3.29': + resolution: {integrity: sha512-Cx3zibnKdsmEnXpsWhwmE9i4k5NFtK+4gtceL3Kb6RwYC1J0wi5y/EesNMkpdJLsAT2h4ugUvu9Ed9T1EUl0/A==} + + '@inertiajs/react@2.3.29': + resolution: {integrity: sha512-09SWBPxv8UrJdOWtQrEp1ro6jv1qJ8yOxH8eL+na9K/wbh7k9R0ALvAO3ofY52Cwuh7HMuSPgtRzk/q8E1k0Fg==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@internationalized/date@3.12.4': + resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==} + + '@internationalized/number@3.6.9': + resolution: {integrity: sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g==} + + '@internationalized/string@3.2.10': + resolution: {integrity: sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==} + + '@javaabu/forms@https://codeload.github.com/Javaabu/react-forms/tar.gz/9ba003a130a02bc5411a1e257c66a6c504a77d21': + resolution: {tarball: https://codeload.github.com/Javaabu/react-forms/tar.gz/9ba003a130a02bc5411a1e257c66a6c504a77d21} + version: 0.0.0 + engines: {node: '>=20.19'} + peerDependencies: + '@inertiajs/react': ^2.0.0 || ^3.0.0 + '@javaabu/onu': ^0.2.1 + '@testing-library/react': ^16.0.0 + react: ^18.3.0 || ^19.0.0 + react-dom: ^18.3.0 || ^19.0.0 + peerDependenciesMeta: + '@inertiajs/react': + optional: true + '@javaabu/onu': + optional: true + '@testing-library/react': + optional: true + + '@javaabu/i18n@https://codeload.github.com/Javaabu/i18n/tar.gz/76730212d6f0f1d87a78635ed6e005dd5713cbba': + resolution: {tarball: https://codeload.github.com/Javaabu/i18n/tar.gz/76730212d6f0f1d87a78635ed6e005dd5713cbba} + version: 0.0.0 + engines: {node: '>=20.19'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + vue: ^3.3.0 + peerDependenciesMeta: + react: + optional: true + vue: + optional: true + + '@javaabu/onu@0.2.1': + resolution: {integrity: sha512-GE4takAQwNQqD9ZmQooH7KMBrLUoiR4UzJsDCGr6cLYigoLF22tjRD3CzrdVGVl+SAhz2jjwOozxzyvAyTNmxA==} + peerDependencies: + '@deck.gl/aggregation-layers': ^9.4.0 + '@deck.gl/core': ^9.4.0 + '@deck.gl/google-maps': ^9.4.0 + '@deck.gl/layers': ^9.4.0 + '@fontsource-variable/geist': ^5.3.0 + '@fontsource-variable/inter': ^5.3.0 + '@fontsource-variable/jetbrains-mono': ^5.3.0 + '@fontsource-variable/noto-sans-thaana': ^5.3.0 + '@fontsource-variable/source-serif-4': ^5.3.0 + '@fortawesome/fontawesome-svg-core': ^7.3.1 + '@fortawesome/react-fontawesome': ^3.5.0 + '@fullcalendar/core': ^6.1.21 + '@fullcalendar/daygrid': ^6.1.21 + '@fullcalendar/interaction': ^6.1.21 + '@fullcalendar/list': ^6.1.21 + '@fullcalendar/react': ^6.1.21 + '@fullcalendar/timegrid': ^6.1.21 + '@googlemaps/markerclusterer': ^2.6.2 + '@iconify/react': ^6.0.2 + '@tanstack/react-query': ^5.104.1 + '@tiptap/core': ^3.31.4 + '@tiptap/extension-image': ^3.31.4 + '@tiptap/extension-placeholder': ^3.31.4 + '@tiptap/extension-table': ^3.31.4 + '@tiptap/extension-text-align': ^3.31.4 + '@tiptap/pm': ^3.31.4 + '@tiptap/react': ^3.31.4 + '@tiptap/starter-kit': ^3.31.4 + '@tiptap/static-renderer': ^3.31.4 + '@tiptap/suggestion': ^3.31.4 + '@turf/turf': ^7.4.0 + '@vis.gl/react-google-maps': ^1.10.1 + barcode-detector: ^3.2.2 + media-chrome: ^4.19.3 + react: ^19.0.0 + react-dom: ^19.0.0 + react-easy-crop: ^6.2.4 + react-hook-form: ^7.89.0 + recharts: ^3.10.1 + shiki: ^4.5.0 + signature_pad: ^5.1.4 + tailwindcss: ^4.0.0 + terra-draw: ^1.37.0 + terra-draw-google-maps-adapter: ^1.7.0 + write-excel-file: ^4.1.1 + yet-another-react-lightbox: ^3.32.2 + peerDependenciesMeta: + '@deck.gl/aggregation-layers': + optional: true + '@deck.gl/core': + optional: true + '@deck.gl/google-maps': + optional: true + '@deck.gl/layers': + optional: true + '@fontsource-variable/geist': + optional: true + '@fontsource-variable/inter': + optional: true + '@fontsource-variable/jetbrains-mono': + optional: true + '@fontsource-variable/noto-sans-thaana': + optional: true + '@fontsource-variable/source-serif-4': + optional: true + '@fortawesome/fontawesome-svg-core': + optional: true + '@fortawesome/react-fontawesome': + optional: true + '@fullcalendar/core': + optional: true + '@fullcalendar/daygrid': + optional: true + '@fullcalendar/interaction': + optional: true + '@fullcalendar/list': + optional: true + '@fullcalendar/react': + optional: true + '@fullcalendar/timegrid': + optional: true + '@googlemaps/markerclusterer': + optional: true + '@iconify/react': + optional: true + '@tiptap/core': + optional: true + '@tiptap/extension-image': + optional: true + '@tiptap/extension-placeholder': + optional: true + '@tiptap/extension-table': + optional: true + '@tiptap/extension-text-align': + optional: true + '@tiptap/pm': + optional: true + '@tiptap/react': + optional: true + '@tiptap/starter-kit': + optional: true + '@tiptap/static-renderer': + optional: true + '@tiptap/suggestion': + optional: true + '@turf/turf': + optional: true + '@vis.gl/react-google-maps': + optional: true + barcode-detector: + optional: true + media-chrome: + optional: true + react-easy-crop: + optional: true + recharts: + optional: true + shiki: + optional: true + signature_pad: + optional: true + terra-draw: + optional: true + terra-draw-google-maps-adapter: + optional: true + write-excel-file: + optional: true + yet-another-react-lightbox: + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@keyv/bigmap@1.3.1': + resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.6.0 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@oxc-project/types@0.156.0': + resolution: {integrity: sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==} + + '@preact/signals-core@1.14.5': + resolution: {integrity: sha512-vcRv7Ityz5JVmeiVqIluLFzT/PDS/AlhHUpw3/aUWqcVUs6bDGIPXE/6RSC4evBNWYvnniozg5Syfj33al3gkg==} + + '@radix-ui/number@1.1.3': + resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} + + '@radix-ui/primitive@1.2.0': + resolution: {integrity: sha512-KABY+VMIRPJL+Vyzv/335FEhXXD/xDv8BMb1kl48/aXJZMH0LtHCTx5ga5RnxaEP3nZHtmTLf1P9vanmFX46bw==} + + '@radix-ui/react-accessible-icon@1.2.0': + resolution: {integrity: sha512-BB4+HHkFl3HipEAhm/mLhrpXV+2kRX7MvY4AaImUcQVeWQt5KhV1JEByNs8NGfJOcr95RcQGijt9+OrCo68xGA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.3.1': + resolution: {integrity: sha512-lCZ6sbGS/7WO0j84yx4srIgOf0MRJ/sZ13RonADXxvKBym8xrxNCq5f6DibGcr38sIlwPk8/52uYdXeOPsbEDw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.2.1': + resolution: {integrity: sha512-MWC4N7RY+qsbNrFNo7yQ9ZbdoZEW+B5DtpWxJfUnVCqcQxxqqg3qis3RMPTDbCNaTIdHJSn5XDkl9ZdVWXknLg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.17': + resolution: {integrity: sha512-67jjcVvxjXCCStnTW3Z+M7jmaFMuMR7M2JgpDzH4w7JSRrikmzhjfYgP/09ECb8n7whnsgh1ifJ+Ie96qEj+pQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.2.0': + resolution: {integrity: sha512-gHEU+Lx8DlT/FYIjZQvvBJrFSwUngXTjh7FeL9QvJaNALDLez6pgDj2axCUihBjtPBy82LN0gNb25+sTcSXNmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.3.1': + resolution: {integrity: sha512-ssZOkfQNkFpYVmCPQlr978xSaU7moSrGXdwg4j2Xs2WlGl/1KsSZtfpK+WnLnx0Rwll1BAh6oUrAy/uR31SBvQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.4.1': + resolution: {integrity: sha512-KWtwhh+98ML3seZ9vO9yRY2OwaQG90dCiDAxjpSNY2AaZ3Hik3B0dsk6m0ILywo/no3A9/SozadyPMNweVT8yw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.2.1': + resolution: {integrity: sha512-fSzLb/qy1wuZSxQBcxpW0hNXYHDMWiIXDGyJGdA4+FSLknOWZsNRrTCUavxb4PI8b8lvHo/CysO6NVi5zLpo5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.2.0': + resolution: {integrity: sha512-+yiQzqcc7Sy7/5s0jrMCjpPRFB3538VNrXPWdTLZhE/SCfgEws4aI6lHWN/vvcnyhHndROWBzqmqB4QBrITZ9Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.4.1': + resolution: {integrity: sha512-Pddn3Z8lio3/ChxyXaEP3jHTTo1LTHCWwF8+mG1chQlWijP+ttVHBKwezrQTiV6Mj670ojvumqvuGh+mdv33iQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.3.0': + resolution: {integrity: sha512-v5AYEocMISkbJ+1fTXzZpiFv7NRr3D2WiYbLIKf+FkmJ8VkP6d503eV2n34MFussHXY1pcoq3yfJiQH77dc4Mg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.3.1': + resolution: {integrity: sha512-+0E4qvVTwjAWra9sEhJ+9BTsvHE00zkUuNw1VGh7S+Gc13dfYk+S1PVQdchWDj+XYHhAai7Z7IpzXVDCGsDplg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.2.0': + resolution: {integrity: sha512-FtRA52a/r+LKStFT+neLUm+8vSe7wbzkbQGGeU8ixFiwC8NU14Evoq9ATECqL3eGbcUzJR4fyfA4KkK/q6dfqg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.2.1': + resolution: {integrity: sha512-KIfDcAsx+oVylFZ4xryVKusJUrLLhTQmfnJW4kwk4Km5myvhHCrj04hnWWUC166+xD4DHdH8tQGylwYX1h6+Pg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.2.1': + resolution: {integrity: sha512-XsHcmYG2AcqnvTqrOXXWcMrhquEIlqucizuDH1MV/qMRW+DndpBCMvNeTRXOxqmG1H5j89M/+I0/34Zdd+zDcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.2.0': + resolution: {integrity: sha512-MPm5R0iRGUDrDnPNxmLBkv+X7pN4XKzt+MbWJEuJQsH3NFH4omrbpXpucyYCCKTSt+na/ZxGhEWQrWTZ0285cA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.3.1': + resolution: {integrity: sha512-ugeMhbXt4y5S2T1GnIqbWDKEN962PDwnX1fN0eKOOgf5f63Dh2mn8F1FUySlgW0iF6GiVsRF32DzuDIWTUyQJg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.3.0': + resolution: {integrity: sha512-KnmrCM27m7Jo1R8S6cFDPrEeq20Jfn5dqde0SZrJr2lVXlj6/QZSUCyFoYh1d8ti0uUrMNK5Lx6dUDuINT+f4Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.2.1': + resolution: {integrity: sha512-4QooHxgmpCKRjj1Pft+hETKidYcrbPKcVwMGtk2Oa5DcWY2IH1fHuQt9LG6q5hUQBBV2MhQxSvtx6B/C1xfiTw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.17': + resolution: {integrity: sha512-xmj7MN/drx1oUAXkfiFIvyDmGNUIN/omGobvA5oAoNqu9zO2MkISYGZOkBcqSzs/UZXXwV+/3ACRE7q7mGcmkA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.2.1': + resolution: {integrity: sha512-G34BsU90SBj5foYpz8pQqq/Q0Dlfyyl6KitWEfRwXC0QvfwhWNAwS49DWP2+rRJ0iwv20Z2Qx7Sk+T03bGo5JA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.2.1': + resolution: {integrity: sha512-bRTYtS9JITV2dUYqYZb7+N5SooQBkZrNRejfmyrSKe3Y57aXhKgx//kBUP0kY7pEpsWR/U1AHsAJRzjwCrK3PA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.4.1': + resolution: {integrity: sha512-XPPZjkeyCSMNNvt0FyDv/PUauG7Ow4GwHQ/XZSwW36F/TS7rR4HFTsXH8ewLnXTOLn3x96/pmTPNQIVoTX/86g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.2.1': + resolution: {integrity: sha512-KtHdcgnaAK6rohQ/MMJoGYcbgSRJsIuGBdHrFeTB1/9SaKlDs14oSOniW6BL6M43HqvgCO3u4WuVdFLkuc6sqw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.2.1': + resolution: {integrity: sha512-vu4+ZoJAaFjdI7l1lDSZRs+4dEv83Jmu7lyzajPrAeQzX74y84n0rUNWbWSgN9XB5NE8IY86Z02kGH6oRrzR+w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.3.1': + resolution: {integrity: sha512-ZNxvXRx5W2Wos4fXCXb5lpdqN0Z5chf+mStOUOTr4ik7d+62MRvoe9rzCA/eGNg36lxXqSrKEKmwuZEcUrIstw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.4.0': + resolution: {integrity: sha512-yCWRvLeGv9Pz3+efwHqGPp70mZ56r3kfIgQVF56Oj2aAa4uP/Tb+IE59wNcc+758rctqbCKx4LbK8II/r7Bn0Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.2.0': + resolution: {integrity: sha512-StX7xXnaRU41VvFtMm9zlhVGz8XF6GmWWTXftkQaY+tqVkbs/R9UmAp5mLzHHapk5hM9RMZQvO/3usyIP/g42g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.11': + resolution: {integrity: sha512-Z7/atlkab36Z8hm+V8zYc79r0W+tiOB1o0tun1By94JyadkekLQmtEAnBSP9XKXBNQsStoStS4+gX10JpS5akA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.2.0': + resolution: {integrity: sha512-HCTSPMNqv55Qk0IX4geJbpyXazmqulcimwuyjxTkKi29v+QZbG2Zho9BnbBq4YS2PF+KlNb2CXoLz66uUmblkg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.2.1': + resolution: {integrity: sha512-yQBiM6J3YM8Ums1orkEFpbznK47geKxa0igikzcaS1xHgdba3DdAQBsGbCTt5BVfR8zhEvgxQ0poGcINaD3lvw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.5.1': + resolution: {integrity: sha512-QbyVT1ddnjqB9H+CUYKkNWdU9fIK3QHCYtjf7mS6ekSqiuXkWtWn5UZW9mhA8qtl3vrVyIsmR9gePstAtKrqkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.2.1': + resolution: {integrity: sha512-UwraORzVUeEPMclhO1p0vz0qI+TbdDLO9i+FiOqhJyHKwdpCRCck9yVEscUnAMIzCiuNoQa8D4A9bIpOPXGt6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.4.0': + resolution: {integrity: sha512-CFrLqHNejWq5FhKAcj9V/WPGRsol76HBQto81NdOBVFEOqIEkJkKIJt0Z824A/9hHLCtoiR/Kw3euRgzXP2pxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.4.1': + resolution: {integrity: sha512-888ZwW0Md6GyptuqYABRX9nLKgoYfcooCyBBvVaX5Jp3e+8Jp2i4Zo/Uy50Hm+Ysn2JfloQXstW4U36w8teVtg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.2.0': + resolution: {integrity: sha512-1jUAap0wZEqVP05Ou7kJgRxe/W+Qftng+rUNWGa0qkKbv9r5pWTG3E7a3ELKo2eJoHKMECnzuYrZpSyO1YgcYQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.6.1': + resolution: {integrity: sha512-IRNRR90moPqxfwCJ0XbMJIxo70W0/1ZExT/sNCxEHMaPx0SF9HoQrcPEC75+H28cFKa0b+KH63anfLYJmwKacw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.5.0': + resolution: {integrity: sha512-tyYd9ddPXAysAbdbD9eYxgwpCSv6f3PncqVSNKQFSR5lcJVLz3cb/pANDoYrLiHBpXv7skortyUgGUWsYuPPFQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.4.1': + resolution: {integrity: sha512-NpBGwc40rY02ZmSvuyeKk0Bvc8ZsNeTMi4qHvJ+ZH4EiYCv0zIXEgdCW4bVdzgIUeP9xxbLJSxKOUfqITYNmkA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.2.1': + resolution: {integrity: sha512-hRoJlode+hg5ZNlR+3tkfkUliIybdTpD5lkAvHQ9yaXseGf7XVO9Ib2GPEIOuaJJbo7YWYbNpDDRfo8Dhf6/xA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.3.1': + resolution: {integrity: sha512-iQvL0J7XK4HtJNGkkWMyYrboNseI7vtotFeUkIno8YNxVA9oM7B6//fh0Mc6TSKI96+zioLWi98lQLso88yJmQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.2.1': + resolution: {integrity: sha512-O8Ldx5ao4p4KoW+DO8/PmkOe5mdKNi3F2SseqjVj88wvDGDHe60cUmO0Zm/uHsKrvNgQGV8Efpuh95Tht/h4vw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.2.1': + resolution: {integrity: sha512-+3MUq+WyiBplXYqQ0EIYlq8R4sLQ0T5NbOK7DMdDLCKO93pe2uE0DUJTWWfTBprpJJXxfoF3eMW0xpakVZrYsg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.2.1': + resolution: {integrity: sha512-Cm8m7JTEeUdbfA6T9xgyu8HfOpyvSx8pB/wj0DlEuWaJe9FilIs4Az9wci/lU9RbthV/UkltszMw5WnnD+m/Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.4.1': + resolution: {integrity: sha512-NxLFGSFtZ78zVfcoOhTwR1xwqo5qx1Jh8Pw6FMZ9McCaqfRzz7/gIRahs2Oy5XlPgQ+8QkUcKbN5B0U44J/kCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.5': + resolution: {integrity: sha512-Y5GBf4NKeADoXI1geiMlMuMoURT3k9MizzzKhGb/m55H94aGigZ01PIpkvsO6lML13BA6XYAXSJ6gza7vTn5nw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.3.0': + resolution: {integrity: sha512-TAcBI7bw2ljYCbv4+zuTSd6zICu8pRt6SDN2vJ/Z06K2MXh6I9WYFe/A9ovlFDtR5HVHGm5iDxDoABm/++dbKg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.6': + resolution: {integrity: sha512-9cRullnQRv+kqnRmFlmh0WC3kAPuGmHiQ9VwsXdrYk//sBhgCKEttcpPEPgIQbyx6TGUmooVEHYTyohwz/lSJg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.3': + resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.4': + resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.4': + resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.5': + resolution: {integrity: sha512-V1McDQkMGzgivwqdu7u96APs7oi0Jvj9LTzgq3WWrDZqxWdAqU9yZnZQqk5OizGFR8badUQwZsrlao7MOlWYOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.13': + resolution: {integrity: sha512-feijiN1oPk4rck4u9Dm9tsU4mT6Wp9oeEEhaz1WUEs1VuqLf8xzsFG+Qk+uWq6TnMH9O+WRECRg0NftyP9E4Fw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.3': + resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} + + '@react-types/shared@3.37.0': + resolution: {integrity: sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + '@reduxjs/toolkit@2.14.0': + resolution: {integrity: sha512-m/PxMwRe0xLL6XUsAgy0YjInklHcfi9uMwFhJk+QINqjxqPvyc89T5cMjszA5bDmTE0gwGc7zr9LRO4gL+mhaw==} + peerDependencies: + '@types/react': '*' + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-redux: + optional: true + + '@rolldown/binding-android-arm-eabi@1.2.16': + resolution: {integrity: sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.16': + resolution: {integrity: sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.16': + resolution: {integrity: sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.16': + resolution: {integrity: sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.16': + resolution: {integrity: sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.16': + resolution: {integrity: sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.16': + resolution: {integrity: sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.2.16': + resolution: {integrity: sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.2.16': + resolution: {integrity: sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.2.16': + resolution: {integrity: sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.2.16': + resolution: {integrity: sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.2.16': + resolution: {integrity: sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.2.16': + resolution: {integrity: sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.16': + resolution: {integrity: sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.16': + resolution: {integrity: sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sindresorhus/slugify@3.0.1': + resolution: {integrity: sha512-N1oPvN6sytRKZCskv0L+vSyvnUYi9JplPXZ90lT6EBsijjHrUdoXttb0Lna6pw1TWMe8vlajQnLDtU9KYHqtDg==} + engines: {node: '>=20'} + + '@sindresorhus/transliterate@2.3.1': + resolution: {integrity: sha512-gVaaGtKYMYAMmI8buULVH3A2TXVJ98QiwGwI7ddrWGuGidGC2uRt4FHs22+8iROJ0QTzju9CuMjlVsrvpqsdhA==} + engines: {node: '>=20'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@swc/helpers@0.5.24': + resolution: {integrity: sha512-6tcXo4kA8HgGO7AVmawB57RYi/piBPYVHVcO6dXjX+SZw8GoHmi262sp4v2Ha2kNP9Cx84qei27yT+fjS1lxgg==} + + '@tanstack/query-core@5.106.1': + resolution: {integrity: sha512-erYv/UACddcdgjOL2ixmFdK6tO/AVBeUcTCOtX2N51yZpDQ2U7KJ+xgB/bCNfsF3uoCDTqGHEEbDiFsIO9YUmQ==} + + '@tanstack/react-query@5.106.1': + resolution: {integrity: sha512-ORDvJG/DSfmlFL9lQ8GDkE0VjtQbLeRDY7BL8xjKkQFGLfjFGfNWNuteWgPqA2gPJgLLuWfQ7IX0FlsHCJriyg==} + peerDependencies: + '@types/react': ^18 || ^19 + react: ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + '@tanstack/react-store@0.11.2': + resolution: {integrity: sha512-oj5O5DmiMWE+ih+SioX2ayC1kbK7d3fpLx0B75fPJr9nY3zU+hoW010yr64qXsY/9fyiLVE2bn5mGNV3owm8RQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/react-table@9.3.1': + resolution: {integrity: sha512-E6Ya5TnY6EaGyZYIUqig/29Lr/gJSAubQkLJFqiWkUl5nRCr0Xr9hvJot+NiRTtG+TzGxHTiqUW3bnLtQxlHug==} + engines: {node: '>=20'} + peerDependencies: + react: '>=18' + + '@tanstack/react-virtual@3.14.26': + resolution: {integrity: sha512-HUDlfsrYH6ko5xH56L62Mcl/uzT+AGVNMHFuDNqDJMdZWc1AQa2oeVKNUvZ/RWBZQ4TYtyQerFrNvLnp39h65A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/store@0.11.2': + resolution: {integrity: sha512-sJ4mjol8uQsHV0gOJzzjwXfh2Fwm+Sz0+8deqiTm4jGbMdjzNSW+xZCFm0kUa870uhd8yi+DpKnZb5Kc4apa0Q==} + + '@tanstack/table-core@9.3.1': + resolution: {integrity: sha512-zQvlxSNi2qGM3L4jh85Z6hqMfdIR5qOfD9KHOU32nuKFJGN0RlJ7UvV1lsPb1fPVhpFYI0VP3jJGxLdLDpTKjQ==} + engines: {node: '>=20'} + + '@tanstack/virtual-core@3.19.11': + resolution: {integrity: sha512-/5iHb0RHBvj8Di59FDs4ESNm45jls33aZAi4zccVXZjc/6ZvwQP6nzEYeT6weXjTf9E6/vrp2JhYWRyO+ZP13A==} + + '@testing-library/dom@10.4.2': + resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@7.0.1': + resolution: {integrity: sha512-oMDTC3oA+6CXSO2JZnvOI7CA6oVub6kij5ggk9ohwye5slmkwxYDXcPOVxgMw/RQlticjtO0C1RZkR97HgrWMw==} + engines: {node: '>=22', npm: '>=6', yarn: '>=1'} + peerDependencies: + '@testing-library/dom': '>=10 <11' + vitest: '>= 0.32' + peerDependenciesMeta: + vitest: + optional: true + + '@testing-library/react@16.3.3': + resolution: {integrity: sha512-Uo193NgQbPMz6lrrhtRQQFcMC6Re/ELLFbbuVL30WDlZxlpZf9/lMHTAVxPRLw1q1iu9OJmR1c2BLiENRstdBg==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.2.0': + resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.10': + resolution: {integrity: sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.27': + resolution: {integrity: sha512-VenMRv/eKNrspX9v3H4dbmjsrnCOYdYBG3kxXKk3dG6X+urBGcj2Prww3xw1U5K4FV/mE4h5GbWd0szEKf+9ew==} + + '@types/node@26.6.6': + resolution: {integrity: sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==} + + '@types/react-dom@19.3.0': + resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==} + peerDependencies: + '@types/react': ^19.3.0 + + '@types/react@19.3.0': + resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@typescript-eslint/eslint-plugin@8.72.1': + resolution: {integrity: sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.72.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.72.1': + resolution: {integrity: sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.72.1': + resolution: {integrity: sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.72.1': + resolution: {integrity: sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.72.1': + resolution: {integrity: sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.72.1': + resolution: {integrity: sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.72.1': + resolution: {integrity: sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.72.1': + resolution: {integrity: sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.72.1': + resolution: {integrity: sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.72.1': + resolution: {integrity: sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/coverage-v8@5.1.0': + resolution: {integrity: sha512-OY9aShBQG5KOIw2zCo9fDoCHI9RHliNHJ//nM7m6jGg41Hyke9JOGWmeMlK74xYErsxMoK8mIB3gAeEyFVA7LA==} + peerDependencies: + '@vitest/browser': 5.1.0 + vitest: 5.1.0 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/istanbul-lib-coverage@2.1.0': + resolution: {integrity: sha512-WOjuS+xcjSIy7ruYBQ+9BUcV7XZQ0FyVcbWbWxW9TRx/kLGx5SQaeFRIFDJxjNplb6YsT7SpKo2zuXXUo91OIQ==} + engines: {node: '>=22'} + + '@vitest/istanbul-lib-report@2.1.0': + resolution: {integrity: sha512-+ZFta25nWtGCOBG9/IET8w1RO5Oawsd6H8Uf6AJ9EA/StFuKVtKAEJEJ6uRt84OH+T4YkI0ASmIHAhRA+0KVxw==} + engines: {node: '>=22'} + + '@vitest/mocker@5.1.0': + resolution: {integrity: sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ==} + peerDependencies: + msw: ^2.4.9 || ^3.0.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/spy@5.1.0': + resolution: {integrity: sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.19.0: + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.7: + resolution: {integrity: sha512-kFL68AG6ajd8fg248zwM9GQrUWEp79gsmjum34OEXjs4yHuUMZfYKwOLW9GMmB4oNvVrj+EAGxsP7ye2UR9UlA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.21.0: + resolution: {integrity: sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.11.35: + resolution: {integrity: sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.1.0: + resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==} + + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + + browserslist@4.29.5: + resolution: {integrity: sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cacheable@2.5.0: + resolution: {integrity: sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001822: + resolution: {integrity: sha512-Y3fvWQWzc69dlCyr07tuNrEhotv+db5FpMSPD3slZnkPKwRNOKwFBz2dP966XUmWMXLTOt+YShkFoQoom3U7hg==} + + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + country-flag-icons@1.6.20: + resolution: {integrity: sha512-py8JiEKzjhYw6HPJ0L7SxLgCYim36UPRTZX43/kqGueUCZLSvnrqAiwW8HtQibur7mdkFQUkjOgdK+o/9FBtaw==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.5: + resolution: {integrity: sha512-Th8qGXoBalmWsstpkuhmBuTsBQIFqYOJtjlBvNVvBfJ8E3uYusBUwUR8HU20U0qKKQkgCvKyR1YjiozWadk5QA==} + engines: {node: '>=12'} + + d3-color@3.1.1: + resolution: {integrity: sha512-lfab8x0Bj0UO7SNsY8dZSbNIA6ipbkE3X0NG9nqjCu6CfCHZseuDnOSJY7hidKrjpP++i1M2qT+FlAdPKYRilA==} + engines: {node: '>=12'} + + d3-ease@3.0.2: + resolution: {integrity: sha512-B5znKqRmKROAN4oHWQ50W/qisVjA9GXh2WMsVKdFVp4DxWmcrOsweqgUOUVsv5th96kwaZlokU6SlV4J3gak1w==} + engines: {node: '>=12'} + + d3-format@3.1.3: + resolution: {integrity: sha512-+F+/49swqmGuQJmSKnVMIDH/QkEFV5li9QRj0xm/KhyAUBv099O5PrvpQFxBp24PYph17Mnvt9mjKQV0ii0nMA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.2: + resolution: {integrity: sha512-IRzGU62DtxhvsnSTWMQIdnoZ6gA+BCWwBon1EJI6SjoN595799b6z/QRBzXfFhE8iupUPp3042yBNo0STPqnzQ==} + engines: {node: '>=12'} + + d3-path@3.1.1: + resolution: {integrity: sha512-jsbgV8zIT38ReB9O7X7gcLndTQRONSqNNd+oGIaTBdUug4r5jFmp0N6RY9R5GWbdUDucn0qoTGyfjapT4tDhcg==} + engines: {node: '>=12'} + + d3-scale@4.0.3: + resolution: {integrity: sha512-66cWobCNzcMvlfoaDHCxuIv9TBUWG1BLOWdv9kHHFuCr1JdqAWPvTmsaEYDD5NRMyWZ5+7u0GqiCDfMXMqh3hg==} + engines: {node: '>=12'} + + d3-shape@3.2.1: + resolution: {integrity: sha512-dm2o+lbKdzg9tY7GEag/dB9hcbodLfwkE/DkvqbELJXaMz0yKzWWhZqhbI7CKubukA7HnRyz0avdtvi7EPxPvw==} + engines: {node: '>=12'} + + d3-time-format@4.1.1: + resolution: {integrity: sha512-22XaqjyCjpqgda20erhsOjGmC3vvYwTNYniKAd45W5UWLDF43moeOuV9qYpRqobIlsnK0UhnKZjUUJm3VgORUA==} + engines: {node: '>=12'} + + d3-time@3.1.1: + resolution: {integrity: sha512-Ze7rjrhMzdeulh0/hjcfFj7XLAHOfowtEMfsoG6DU866HPynQNW7Wfen6hzXcqKTv8XzxvF6Hzr5XD6bMHr2ug==} + engines: {node: '>=12'} + + d3-timer@3.0.2: + resolution: {integrity: sha512-1Il1aliroVSErrv5aulRBxAY+7+DnaoLIWGd1C6SsBTWDeobWiP/V69vwb8vT6abR1kKwaNe1196pmaQ1fK90A==} + engines: {node: '>=12'} + + data-urls@8.0.0: + resolution: {integrity: sha512-JQUCCK+/CAGbGGx618PlFZMXH81Jtj7t73CHv9dd/zVWvxgGT2zLFRjnKb+Ng46GIwcy+f7f88qd4duqllK+WQ==} + engines: {node: ^22.14.0 || >=24.0.0} + + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.477: + resolution: {integrity: sha512-PN++F4zWZoVDGWinxMnSCpVqKhqFOJfLXxdU8bYJKudAEsHL/XKVITOKWVbfMt1r276Mi9RDLU9VHx9uKJ4RMQ==} + + embla-carousel-react@8.6.0: + resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + embla-carousel-reactive-utils@8.6.0: + resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: + resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + + entities@9.0.0: + resolution: {integrity: sha512-oJFNDJg3kTPwAD1cNn2+WmUq4wFoo09npc3Ydf1D7ED4smRiWrsKnaLxykrTWgyGEcusc87P5dxlAleol9kpkg==} + engines: {node: '>=20.19.0'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-toolkit@1.53.1: + resolution: {integrity: sha512-VhMc6IXmexu8iYFfL2aaAww6IvJTdHlqBLruLoEUs33YD4IcP5DdZcLOybMsMmmLsiy1c8PsiLsrM0DdIuuRUA==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.14.0: + resolution: {integrity: sha512-T0pNsxkb9IKmtQjhpK3d7nWOpFv+YHuMbTJnSVMoQblF0onvXfErrtp4V1jHtsQPan7dKaGVD0q1WCXnzMF31A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@11.1.5: + resolution: {integrity: sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@6.1.23: + resolution: {integrity: sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==} + + flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} + + follow-redirects@1.16.2: + resolution: {integrity: sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.14.0: + resolution: {integrity: sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==} + engines: {node: '>=18'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hashery@1.5.1: + resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} + engines: {node: '>=20'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + + hookified@2.2.0: + resolution: {integrity: sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==} + + html-encoding-sniffer@7.0.0: + resolution: {integrity: sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w==} + engines: {node: ^22.13.0 || >=24.0.0} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.14: + resolution: {integrity: sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==} + engines: {node: '>= 4'} + + immer@11.2.9: + resolution: {integrity: sha512-IQ4UEtXjZjyefyWjtLEDXUSrF6T2hwjKPi6BIJl3bY/2XXW24d6cNGdXJSBOA/+KNnuiXkZY6DR0C3efolhitA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + input-format@0.3.14: + resolution: {integrity: sha512-gHMrgrbCgmT4uK5Um5eVDUohuV9lcs95ZUUN9Px2Y0VIfjTzT2wF8Q3Z4fwLFm7c5Z2OXCm53FHoovj6SlOKdg==} + peerDependencies: + react: '>=18.1.0' + react-dom: '>=18.1.0' + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + input-otp@1.5.0: + resolution: {integrity: sha512-3AcfdW1sNG0FmSA5hHMBXG7jNW5CdcdKs8ln8JOmn6S2SRkoXoJx+2UwC+ZvjflfnOdHJx0SfQ9/YjlxVYLtGQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsdom@30.1.3: + resolution: {integrity: sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + + laravel-precognition@1.0.2: + resolution: {integrity: sha512-0H08JDdMWONrL/N314fvsO3FATJwGGlFKGkMF3nNmizVFJaWs17816iM+sX7Rp8d5hUjYCx6WLfsehSKfaTxjg==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + libphonenumber-js@1.13.15: + resolution: {integrity: sha512-AHdhEQeDFEJ4GKDAQ9AwxChy+v2kUILUB3TGQRpkXMkWePjXTU/WQEzWVQoIx/Gz8fE73Wr5SEuZCy2vk8jrMQ==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@11.5.3: + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@1.69.0: + resolution: {integrity: sha512-UTFECm4yEgkgMN0kfLuxWcmlk+4u02Jrqehgk3ZXRx/Ud68OpP7hjkp67bvkBHmIpMaZCIveF1QDYeupO7XmYQ==} + peerDependencies: + '@types/react': '*' + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@1.4.5: + resolution: {integrity: sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==} + + magicast@0.5.5: + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.63: + resolution: {integrity: sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + obug@3.0.0: + resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} + engines: {node: '>=12.20.0'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parse5@8.1.0: + resolution: {integrity: sha512-sq3qBFpee5jeocbFbO48VHlGr8sT7mbXS0vxlKN6sottBtJzrOG3998sr0llSZcQAGgfpdmEYAIZE0P9z0dE8w==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.32: + resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.9.10: + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qified@0.10.1: + resolution: {integrity: sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==} + engines: {node: '>=20'} + + qs@6.17.1: + resolution: {integrity: sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==} + engines: {node: '>=0.6'} + + radix-ui@1.8.1: + resolution: {integrity: sha512-HPfpVGizO9xANlsFSauruLhRe/3+iOt3vPX0MXYle/UQi3gorQrSYtBcdiv0BZYewcl2rvL59qrIuLFMGAmUrw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-aria-components@1.22.1: + resolution: {integrity: sha512-UTHaYHOaM71bOCjGPF50DcLywlrwAodE63IP64LB4kO+OizatQEL8TesVEOM8+TkBeSgHoflJ45388IlJfa7aw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + react-aria@3.53.1: + resolution: {integrity: sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + react-day-picker@10.0.2: + resolution: {integrity: sha512-QetiKtZWE9Uaw/pM/SYNv6WPYOTRnSJ2puL0GgwkmUFMIwiY0dtjH1W4B7DOVXVNQM0ZOClGpH/heOfBSzWeiA==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=16.8.0' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + react-dom@19.3.1: + resolution: {integrity: sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==} + peerDependencies: + react: ^19.3.1 + + react-hook-form@7.91.0: + resolution: {integrity: sha512-5BEBUnCBwa28AFXYTYmOaB/+CSpJ/+CWVASHEvIGUzFdHyUiPHeRhG7r5hOZ8nZi0yP7pbHdFNXO/p31EKTJFQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-phone-number-input@3.4.18: + resolution: {integrity: sha512-EmPhJFSxQB7CTmLAlQfGAQQPF+WmJECU08gjyUt50zd2iESb4ETsm6z+BRfUEjH+TXct4EWvJvub+OztJxb/Cg==} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-redux@9.3.1: + resolution: {integrity: sha512-vxMLT0lalUjbZPkoTepWyKAZbjIIFssNx/CEL9lTfwQBU6XpkGSmCNdcCIrkOBUgB68uftZMnbXVb6DwGnhRKQ==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-stately@3.51.0: + resolution: {integrity: sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.3.1: + resolution: {integrity: sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==} + engines: {node: '>=0.10.0'} + + recharts@3.10.1: + resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reselect@5.2.0: + resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} + + rolldown@1.2.16: + resolution: {integrity: sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.28.0: + resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} + + semver@6.3.2: + resolution: {integrity: sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==} + hasBin: true + + semver@7.8.6: + resolution: {integrity: sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + sonner@2.0.8: + resolution: {integrity: sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + + std-env@4.3.0: + resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} + + tailwindcss@4.3.4: + resolution: {integrity: sha512-PCJZTFFdG+sL3gvnP1Do9o6GbkvH2q0p04xSs3f7cSvmAeK+sttG+/7ncK1fdRwKFfC7n+wDDJaOMBSefkI9ow==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@6.2.3: + resolution: {integrity: sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==} + engines: {node: '>=20.0.0'} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + + tldts-core@7.4.20: + resolution: {integrity: sha512-C630TWXZCZmLq0F2bW4QqwnaaxCQ/svpAqvJwnC4JvgEM5ohhBnGsEzTLSOXcN+xjz+kEaHS5uoWIw2klnqd0A==} + + tldts@7.4.19: + resolution: {integrity: sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q==} + hasBin: true + + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@7.0.0: + resolution: {integrity: sha512-BcDSDW+SIGWVOns1Zm6Yg91q9AEj7eaID0QDY9oIqJD1A8dscNwMcl9Sms2DeuWCNov1X0/oXanHGYQBSLwZsg==} + engines: {node: ^22.14.0 || >=24.0.0} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.9.1: + resolution: {integrity: sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.72.1: + resolution: {integrity: sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@8.9.0: + resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + + undici@8.12.4: + resolution: {integrity: sha512-8Mx+uNKKER2bA4AdtedkqRbwUCIJFr2nJckXRe5yrDqzePqwLP7VNcctN8/XdmDSbF7mS+jJKIszlL+vbam1VA==} + engines: {node: '>=22.19.0'} + + update-browserslist-db@1.3.4: + resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uqr@0.1.3: + resolution: {integrity: sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.7.0: + resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + + victory-vendor@37.4.1: + resolution: {integrity: sha512-geV4p2GVfVM9lgm9ZRS3pvsbAPS+BUGhM9W9nPzPHTKi+/+KjNrFXdgP6KkG+XspNZuvcXmchcrtklfuQ4gniw==} + + vite@8.3.6: + resolution: {integrity: sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 || ^0.8.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@5.1.0: + resolution: {integrity: sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.1.0 + '@vitest/browser-preview': 5.1.0 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.1.0 + '@vitest/coverage-v8': 5.1.0 + '@vitest/ui': 5.1.0 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@6.0.0: + resolution: {integrity: sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@17.3.0: + resolution: {integrity: sha512-KwOAJrpf+GTgBvcBQJcP38sMF0DsQKdwtG+TxM2pW44P6E5k0hDbWrDmyuJDIF0Uef0aQq0ZoJs+5oI+k8ddhg==} + engines: {node: ^22.14.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.6.7: + resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} + +snapshots: + + '@adobe/css-tools@4.5.0': {} + + '@asamuzakjp/css-color@7.1.9': + dependencies: + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-color-parser': 4.2.7(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + lru-cache: 11.5.3 + + '@asamuzakjp/dom-selector@10.0.5': + dependencies: + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.13': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.13 + '@babel/helper-compilation-targets': 7.29.13 + '@babel/helper-module-transforms': 7.29.13(@babel/core@7.29.13) + '@babel/helpers': 7.29.13 + '@babel/parser': 7.29.13 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.13 + '@babel/types': 7.29.11 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.2 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.13': + dependencies: + '@babel/parser': 7.29.13 + '@babel/types': 7.29.11 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.13': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.29.5 + lru-cache: 5.1.1 + semver: 6.3.2 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.13 + '@babel/types': 7.29.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.13(@babel/core@7.29.13)': + dependencies: + '@babel/core': 7.29.13 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.13 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.13': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.13': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.11 + + '@babel/parser@7.29.13': + dependencies: + '@babel/types': 7.29.11 + + '@babel/runtime@7.29.13': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.13 + '@babel/types': 7.29.11 + + '@babel/traverse@7.29.13': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.13 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.13 + '@babel/template': 7.29.7 + '@babel/types': 7.29.11 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.11': + dependencies: + '@babel/helper-string-parser': 7.29.13 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.3': + dependencies: + css-tree: 3.2.1 + + '@cacheable/memory@2.2.0': + dependencies: + '@cacheable/utils': 2.5.0 + '@keyv/bigmap': 1.3.1(keyv@5.6.0) + hookified: 1.15.1 + keyv: 5.6.0 + + '@cacheable/utils@2.5.0': + dependencies: + hashery: 1.5.1 + keyv: 5.6.0 + + '@csstools/color-helpers@6.1.2': {} + + '@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-color-parser@4.2.7(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/color-helpers': 6.1.2 + '@csstools/css-calc': 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + '@csstools/css-parser-algorithms': 4.0.2(@csstools/css-tokenizer@4.0.2) + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)': + dependencies: + '@csstools/css-tokenizer': 4.0.2 + + '@csstools/css-syntax-patches-for-csstree@1.1.16(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.2': {} + + '@date-fns/tz@1.5.0': {} + + '@dnd-kit/abstract@0.5.3': + dependencies: + '@dnd-kit/geometry': 0.5.3 + '@dnd-kit/state': 0.5.3 + tslib: 2.9.1 + + '@dnd-kit/collision@0.5.3': + dependencies: + '@dnd-kit/abstract': 0.5.3 + '@dnd-kit/geometry': 0.5.3 + tslib: 2.9.1 + + '@dnd-kit/dom@0.5.3': + dependencies: + '@dnd-kit/abstract': 0.5.3 + '@dnd-kit/collision': 0.5.3 + '@dnd-kit/geometry': 0.5.3 + '@dnd-kit/state': 0.5.3 + tslib: 2.9.1 + + '@dnd-kit/geometry@0.5.3': + dependencies: + '@dnd-kit/state': 0.5.3 + tslib: 2.9.1 + + '@dnd-kit/helpers@0.5.3': + dependencies: + '@dnd-kit/abstract': 0.5.3 + tslib: 2.9.1 + + '@dnd-kit/react@0.5.3(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@dnd-kit/abstract': 0.5.3 + '@dnd-kit/dom': 0.5.3 + '@dnd-kit/state': 0.5.3 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + tslib: 2.9.1 + + '@dnd-kit/state@0.5.3': + dependencies: + '@preact/signals-core': 1.14.5 + tslib: 2.9.1 + + '@eslint-community/eslint-utils@4.10.1(eslint@10.14.0)': + dependencies: + eslint: 10.14.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.6 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.7.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.14.0)': + optionalDependencies: + eslint: 10.14.0 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.3': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@exodus/bytes@1.16.0': {} + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/react-dom@2.1.9(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@floating-ui/dom': 1.8.0 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + '@floating-ui/utils@0.2.12': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inertiajs/core@2.3.29': + dependencies: + '@types/lodash-es': 4.17.12 + axios: 1.21.0 + laravel-precognition: 1.0.2 + lodash-es: 4.18.1 + qs: 6.17.1 + transitivePeerDependencies: + - debug + - supports-color + + '@inertiajs/react@2.3.29(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@inertiajs/core': 2.3.29 + '@types/lodash-es': 4.17.12 + laravel-precognition: 1.0.2 + lodash-es: 4.18.1 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + transitivePeerDependencies: + - debug + - supports-color + + '@internationalized/date@3.12.4': + dependencies: + '@swc/helpers': 0.5.24 + + '@internationalized/number@3.6.9': + dependencies: + '@swc/helpers': 0.5.24 + + '@internationalized/string@3.2.10': + dependencies: + '@swc/helpers': 0.5.24 + + '@javaabu/forms@https://codeload.github.com/Javaabu/react-forms/tar.gz/9ba003a130a02bc5411a1e257c66a6c504a77d21(@inertiajs/react@2.3.29(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(@javaabu/onu@0.2.1(@tanstack/react-query@5.106.1(@types/react@19.3.0)(react@19.3.1))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-hook-form@7.91.0(@types/react@19.3.0)(react@19.3.1))(react@19.3.1)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1))(tailwindcss@4.3.4))(@testing-library/react@16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1))(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@javaabu/i18n': https://codeload.github.com/Javaabu/i18n/tar.gz/76730212d6f0f1d87a78635ed6e005dd5713cbba(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@inertiajs/react': 2.3.29(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@javaabu/onu': 0.2.1(@tanstack/react-query@5.106.1(@types/react@19.3.0)(react@19.3.1))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-hook-form@7.91.0(@types/react@19.3.0)(react@19.3.1))(react@19.3.1)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1))(tailwindcss@4.3.4) + '@testing-library/react': 16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + transitivePeerDependencies: + - vue + + '@javaabu/i18n@https://codeload.github.com/Javaabu/i18n/tar.gz/76730212d6f0f1d87a78635ed6e005dd5713cbba(react@19.3.1)': + optionalDependencies: + react: 19.3.1 + + '@javaabu/onu@0.2.1(@tanstack/react-query@5.106.1(@types/react@19.3.0)(react@19.3.1))(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-hook-form@7.91.0(@types/react@19.3.0)(react@19.3.1))(react@19.3.1)(recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1))(tailwindcss@4.3.4)': + dependencies: + '@dnd-kit/dom': 0.5.3 + '@dnd-kit/helpers': 0.5.3 + '@dnd-kit/react': 0.5.3(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@internationalized/date': 3.12.4 + '@sindresorhus/slugify': 3.0.1 + '@tanstack/react-query': 5.106.1(@types/react@19.3.0)(react@19.3.1) + '@tanstack/react-table': 9.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@tanstack/react-virtual': 3.14.26(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + class-variance-authority: 0.7.1 + clsx: 2.1.1 + cmdk: 1.1.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + date-fns: 4.4.0 + embla-carousel-react: 8.6.0(react@19.3.1) + input-otp: 1.5.0(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + lucide-react: 1.69.0(@types/react@19.3.0)(react@19.3.1) + radix-ui: 1.8.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-aria-components: 1.22.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react-day-picker: 10.0.2(@types/react@19.3.0)(react@19.3.1) + react-dom: 19.3.1(react@19.3.1) + react-hook-form: 7.91.0(@types/react@19.3.0)(react@19.3.1) + react-phone-number-input: 3.4.18(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + sonner: 2.0.8(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + tailwind-merge: 3.7.0 + tailwindcss: 4.3.4 + tw-animate-css: 1.4.0 + uqr: 0.1.3 + vaul: 1.1.2(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + optionalDependencies: + recharts: 3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@keyv/bigmap@1.3.1(keyv@5.6.0)': + dependencies: + hashery: 1.5.1 + hookified: 1.15.1 + keyv: 5.6.0 + + '@keyv/serialize@1.1.1': {} + + '@oxc-project/types@0.156.0': {} + + '@preact/signals-core@1.14.5': {} + + '@radix-ui/number@1.1.3': {} + + '@radix-ui/primitive@1.2.0': {} + + '@radix-ui/react-accessible-icon@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-accordion@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collapsible': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-alert-dialog@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-arrow@1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-aspect-ratio@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-avatar@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-checkbox@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-collapsible@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-collection@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-context-menu@2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-context@1.3.0(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-dialog@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + aria-hidden: 1.2.6 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-direction@1.2.0(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-dismissable-layer@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-dropdown-menu@2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-focus-guards@1.2.0(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-focus-scope@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-form@0.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-label': 2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-hover-card@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-id@1.1.4(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-label@2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-menu@2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + aria-hidden: 1.2.6 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-menubar@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-navigation-menu@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-one-time-password-field@0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-password-toggle-field@0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-popover@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + aria-hidden: 1.2.6 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-popper@1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-arrow': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/rect': 1.1.3 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-portal@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-presence@1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-primitive@2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-progress@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-radio-group@1.5.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-roving-focus@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-scroll-area@1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-select@2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + aria-hidden: 1.2.6 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-remove-scroll: 2.7.2(@types/react@19.3.0)(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-separator@1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-slider@1.6.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-slot@1.5.0(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-switch@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-tabs@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-toast@1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-toggle-group@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-toggle@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-toolbar@1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-separator': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle-group': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-tooltip@1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/react-use-callback-ref@1.1.5(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-controllable-state@1.3.0(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-escape-keydown@1.1.6(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-previous@1.1.4(@types/react@19.3.0)(react@19.3.1)': + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-rect@1.1.4(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/rect': 1.1.3 + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-use-size@1.1.5(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@radix-ui/react-visually-hidden@1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@radix-ui/rect@1.1.3': {} + + '@react-types/shared@3.37.0(react@19.3.1)': + dependencies: + react: 19.3.1 + + '@reduxjs/toolkit@2.14.0(@types/react@19.3.0)(react-redux@9.3.1(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1))(react@19.3.1)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.2.9 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.2.0 + optionalDependencies: + '@types/react': 19.3.0 + react: 19.3.1 + react-redux: 9.3.1(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1) + + '@rolldown/binding-android-arm-eabi@1.2.16': + optional: true + + '@rolldown/binding-android-arm64@1.2.16': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.16': + optional: true + + '@rolldown/binding-darwin-x64@1.2.16': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.16': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.16': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.16': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.16': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.16': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.16': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.16': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.16': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.16': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.16': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.16': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sindresorhus/slugify@3.0.1': + dependencies: + '@sindresorhus/transliterate': 2.3.1 + escape-string-regexp: 5.0.0 + + '@sindresorhus/transliterate@2.3.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@swc/helpers@0.5.24': {} + + '@tanstack/query-core@5.106.1': {} + + '@tanstack/react-query@5.106.1(@types/react@19.3.0)(react@19.3.1)': + dependencies: + '@tanstack/query-core': 5.106.1 + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + '@tanstack/react-store@0.11.2(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@tanstack/store': 0.11.2 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + use-sync-external-store: 1.7.0(react@19.3.1) + + '@tanstack/react-table@9.3.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@tanstack/react-store': 0.11.2(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@tanstack/table-core': 9.3.1 + react: 19.3.1 + transitivePeerDependencies: + - react-dom + + '@tanstack/react-virtual@3.14.26(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@tanstack/virtual-core': 3.19.11 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + '@tanstack/store@0.11.2': {} + + '@tanstack/table-core@9.3.1': + dependencies: + '@tanstack/store': 0.11.2 + + '@tanstack/virtual-core@3.19.11': {} + + '@testing-library/dom@10.4.2': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.13 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@7.0.1(@testing-library/dom@10.4.2)(vitest@5.1.0)': + dependencies: + '@adobe/css-tools': 4.5.0 + '@testing-library/dom': 10.4.2 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + optionalDependencies: + vitest: 5.1.0(@types/node@26.6.6)(@vitest/coverage-v8@5.1.0)(jsdom@30.1.3)(vite@8.3.6(@types/node@26.6.6)) + + '@testing-library/react@16.3.3(@testing-library/dom@10.4.2)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + dependencies: + '@babel/runtime': 7.29.13 + '@testing-library/dom': 10.4.2 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.2.0': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.10': {} + + '@types/json-schema@7.0.15': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.27 + + '@types/lodash@4.17.27': {} + + '@types/node@26.6.6': + dependencies: + undici-types: 8.9.0 + + '@types/react-dom@19.3.0(@types/react@19.3.0)': + dependencies: + '@types/react': 19.3.0 + + '@types/react@19.3.0': + dependencies: + csstype: 3.2.3 + + '@types/use-sync-external-store@0.0.6': {} + + '@typescript-eslint/eslint-plugin@8.72.1(@typescript-eslint/parser@8.72.1(eslint@10.14.0)(typescript@6.0.3))(eslint@10.14.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.72.1(eslint@10.14.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.72.1 + '@typescript-eslint/type-utils': 8.72.1(eslint@10.14.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.1(eslint@10.14.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.72.1 + eslint: 10.14.0 + ignore: 7.0.14 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.72.1(eslint@10.14.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.72.1 + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/typescript-estree': 8.72.1(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.72.1 + debug: 4.4.3 + eslint: 10.14.0 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.72.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.72.1(typescript@6.0.3) + '@typescript-eslint/types': 8.72.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.72.1': + dependencies: + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/visitor-keys': 8.72.1 + + '@typescript-eslint/tsconfig-utils@8.72.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.72.1(eslint@10.14.0)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/typescript-estree': 8.72.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.1(eslint@10.14.0)(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.14.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.72.1': {} + + '@typescript-eslint/typescript-estree@8.72.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.72.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.72.1(typescript@6.0.3) + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/visitor-keys': 8.72.1 + debug: 4.4.3 + minimatch: 10.2.6 + semver: 7.8.6 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.72.1(eslint@10.14.0)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.14.0) + '@typescript-eslint/scope-manager': 8.72.1 + '@typescript-eslint/types': 8.72.1 + '@typescript-eslint/typescript-estree': 8.72.1(typescript@6.0.3) + eslint: 10.14.0 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.72.1': + dependencies: + '@typescript-eslint/types': 8.72.1 + eslint-visitor-keys: 5.0.1 + + '@vitest/coverage-v8@5.1.0(vitest@5.1.0)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/istanbul-lib-coverage': 2.1.0 + '@vitest/istanbul-lib-report': 2.1.0 + ast-v8-to-istanbul: 1.0.7 + magicast: 0.5.5 + obug: 3.0.0 + std-env: 4.3.0 + tinyrainbow: 3.2.0 + vitest: 5.1.0(@types/node@26.6.6)(@vitest/coverage-v8@5.1.0)(jsdom@30.1.3)(vite@8.3.6(@types/node@26.6.6)) + + '@vitest/istanbul-lib-coverage@2.1.0': {} + + '@vitest/istanbul-lib-report@2.1.0': + dependencies: + '@vitest/istanbul-lib-coverage': 2.1.0 + + '@vitest/mocker@5.1.0(vite@8.3.6(@types/node@26.6.6))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.1.0 + estree-walker: 3.0.3 + magic-string: 1.4.5 + optionalDependencies: + vite: 8.3.6(@types/node@26.6.6) + + '@vitest/spy@5.1.0': {} + + acorn-jsx@5.3.2(acorn@8.19.0): + dependencies: + acorn: 8.19.0 + + acorn@8.19.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.9.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.7: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + asynckit@0.4.0: {} + + axios@1.21.0: + dependencies: + follow-redirects: 1.16.2 + form-data: 4.0.6 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.11.35: {} + + bidi-js@1.1.0: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.29.5: + dependencies: + baseline-browser-mapping: 2.11.35 + caniuse-lite: 1.0.30001822 + electron-to-chromium: 1.5.477 + node-releases: 2.0.63 + update-browserslist-db: 1.3.4(browserslist@4.29.5) + + cacheable@2.5.0: + dependencies: + '@cacheable/memory': 2.2.0 + '@cacheable/utils': 2.5.0 + hookified: 1.15.1 + keyv: 5.6.0 + qified: 0.10.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caniuse-lite@1.0.30001822: {} + + chai@6.3.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + classnames@2.5.1: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + convert-source-map@2.0.0: {} + + country-flag-icons@1.6.20: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.2 + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + d3-array@3.2.5: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.1: {} + + d3-ease@3.0.2: {} + + d3-format@3.1.3: {} + + d3-interpolate@3.0.2: + dependencies: + d3-color: 3.1.1 + + d3-path@3.1.1: {} + + d3-scale@4.0.3: + dependencies: + d3-array: 3.2.5 + d3-format: 3.1.3 + d3-interpolate: 3.0.2 + d3-time: 3.1.1 + d3-time-format: 4.1.1 + + d3-shape@3.2.1: + dependencies: + d3-path: 3.1.1 + + d3-time-format@4.1.1: + dependencies: + d3-time: 3.1.1 + + d3-time@3.1.1: + dependencies: + d3-array: 3.2.5 + + d3-timer@3.0.2: {} + + data-urls@8.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 17.3.0 + transitivePeerDependencies: + - '@noble/hashes' + + date-fns@4.4.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.477: {} + + embla-carousel-react@8.6.0(react@19.3.1): + dependencies: + embla-carousel: 8.6.0 + embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) + react: 19.3.1 + + embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: {} + + entities@9.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@2.3.2: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + es-toolkit@1.53.1: {} + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-plugin-react-hooks@7.1.1(eslint@10.14.0): + dependencies: + '@babel/core': 7.29.13 + '@babel/parser': 7.29.13 + eslint: 10.14.0 + hermes-parser: 0.25.1 + zod: 4.6.7 + zod-validation-error: 4.0.2(zod@4.6.7) + transitivePeerDependencies: + - supports-color + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.10 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.14.0: + dependencies: + '@eslint-community/eslint-utils': 4.10.1(eslint@10.14.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.7.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.3 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.10 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 11.1.5 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.6 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.10 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + file-entry-cache@11.1.5: + dependencies: + flat-cache: 6.1.23 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@6.1.23: + dependencies: + cacheable: 2.5.0 + flatted: 3.4.4 + hookified: 1.15.1 + + flatted@3.4.4: {} + + follow-redirects@1.16.2: {} + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.14.0: {} + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hashery@1.5.1: + dependencies: + hookified: 1.15.1 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hookified@1.15.1: {} + + hookified@2.2.0: {} + + html-encoding-sniffer@7.0.0: + dependencies: + '@exodus/bytes': 1.16.0 + transitivePeerDependencies: + - '@noble/hashes' + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ignore@5.3.2: {} + + ignore@7.0.14: {} + + immer@11.2.9: {} + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + input-format@0.3.14(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + prop-types: 15.8.1 + optionalDependencies: + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + input-otp@1.5.0(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + internmap@2.0.3: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + js-tokens@10.0.0: {} + + js-tokens@4.0.0: {} + + jsdom@30.1.3: + dependencies: + '@asamuzakjp/css-color': 7.1.9 + '@asamuzakjp/dom-selector': 10.0.5 + '@bramus/specificity': 2.4.3 + '@csstools/css-syntax-patches-for-csstree': 1.1.16(css-tree@3.2.1) + '@exodus/bytes': 1.16.0 + css-tree: 3.2.1 + data-urls: 8.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 7.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + parse5: 8.1.0 + saxes: 6.0.0 + tough-cookie: 6.0.2 + undici: 8.12.4 + w3c-xmlserializer: 6.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.3.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + jsesc@3.1.0: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@5.6.0: + dependencies: + '@keyv/serialize': 1.1.1 + + laravel-precognition@1.0.2: + dependencies: + axios: 1.21.0 + lodash-es: 4.18.1 + transitivePeerDependencies: + - debug + - supports-color + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + libphonenumber-js@1.13.15: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash-es@4.18.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@11.5.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@1.69.0(@types/react@19.3.0)(react@19.3.1): + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + lz-string@1.5.0: {} + + magic-string@1.4.5: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.5.5: + dependencies: + '@babel/parser': 7.29.13 + '@babel/types': 7.29.11 + source-map-js: 1.2.2 + + math-intrinsics@1.1.0: {} + + mdn-data@2.27.1: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + min-indent@1.0.1: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + ms@2.1.3: {} + + nanoid@3.3.20: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.63: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + obug@3.0.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parse5@8.1.0: + dependencies: + entities: 9.0.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.32: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + + prelude-ls@1.2.1: {} + + prettier@3.9.10: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@2.1.0: {} + + punycode@2.3.1: {} + + qified@0.10.1: + dependencies: + hookified: 2.2.0 + + qs@6.17.1: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + radix-ui@1.8.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@radix-ui/primitive': 1.2.0 + '@radix-ui/react-accessible-icon': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-accordion': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-alert-dialog': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-arrow': 1.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-aspect-ratio': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-avatar': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-checkbox': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-collapsible': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-collection': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-context-menu': 2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-direction': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-dismissable-layer': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-dropdown-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-focus-guards': 1.2.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-focus-scope': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-form': 0.3.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-hover-card': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-label': 2.1.17(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-menu': 2.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-menubar': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-navigation-menu': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-one-time-password-field': 0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-password-toggle-field': 0.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-popover': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-popper': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-portal': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-primitive': 2.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-progress': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-radio-group': 1.5.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-roving-focus': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-scroll-area': 1.4.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-select': 2.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-separator': 1.2.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slider': 1.6.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-slot': 1.5.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-switch': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-tabs': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toast': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toggle-group': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-toolbar': 1.2.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-tooltip': 1.4.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@radix-ui/react-use-callback-ref': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-controllable-state': 1.3.0(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.6(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.3.0)(react@19.3.1) + '@radix-ui/react-visually-hidden': 1.2.13(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + '@types/react-dom': 19.3.0(@types/react@19.3.0) + + react-aria-components@1.22.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@internationalized/date': 3.12.4 + '@internationalized/string': 3.2.10 + '@react-types/shared': 3.37.0(react@19.3.1) + '@swc/helpers': 0.5.24 + client-only: 0.0.1 + react: 19.3.1 + react-aria: 3.53.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react-dom: 19.3.1(react@19.3.1) + react-stately: 3.51.0(react@19.3.1) + use-sync-external-store: 1.7.0(react@19.3.1) + + react-aria@3.53.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@internationalized/date': 3.12.4 + '@internationalized/number': 3.6.9 + '@internationalized/string': 3.2.10 + '@react-types/shared': 3.37.0(react@19.3.1) + '@swc/helpers': 0.5.24 + aria-hidden: 1.2.6 + clsx: 2.1.1 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-stately: 3.51.0(react@19.3.1) + use-sync-external-store: 1.7.0(react@19.3.1) + + react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.1): + dependencies: + '@date-fns/tz': 1.5.0 + date-fns: 4.4.0 + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + react-dom@19.3.1(react@19.3.1): + dependencies: + react: 19.3.1 + scheduler: 0.28.0 + + react-hook-form@7.91.0(@types/react@19.3.0)(react@19.3.1): + dependencies: + react: 19.3.1 + optionalDependencies: + '@types/react': 19.3.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-phone-number-input@3.4.18(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + classnames: 2.5.1 + country-flag-icons: 1.6.20 + input-format: 0.3.14(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + libphonenumber-js: 1.13.15 + prop-types: 15.8.1 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + + react-redux@9.3.1(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.3.1 + use-sync-external-store: 1.7.0(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + redux: 5.0.1 + + react-remove-scroll-bar@2.3.8(@types/react@19.3.0)(react@19.3.1): + dependencies: + react: 19.3.1 + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.1) + tslib: 2.9.1 + optionalDependencies: + '@types/react': 19.3.0 + + react-remove-scroll@2.7.2(@types/react@19.3.0)(react@19.3.1): + dependencies: + react: 19.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.3.0)(react@19.3.1) + react-style-singleton: 2.2.3(@types/react@19.3.0)(react@19.3.1) + tslib: 2.9.1 + use-callback-ref: 1.3.3(@types/react@19.3.0)(react@19.3.1) + use-sidecar: 1.1.3(@types/react@19.3.0)(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + + react-stately@3.51.0(react@19.3.1): + dependencies: + '@internationalized/date': 3.12.4 + '@internationalized/number': 3.6.9 + '@internationalized/string': 3.2.10 + '@react-types/shared': 3.37.0(react@19.3.1) + '@swc/helpers': 0.5.24 + react: 19.3.1 + use-sync-external-store: 1.7.0(react@19.3.1) + + react-style-singleton@2.2.3(@types/react@19.3.0)(react@19.3.1): + dependencies: + get-nonce: 1.0.1 + react: 19.3.1 + tslib: 2.9.1 + optionalDependencies: + '@types/react': 19.3.0 + + react@19.3.1: {} + + recharts@3.10.1(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react-is@17.0.2)(react@19.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.14.0(@types/react@19.3.0)(react-redux@9.3.1(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1))(react@19.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.53.1 + eventemitter3: 5.0.4 + immer: 11.2.9 + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + react-is: 17.0.2 + react-redux: 9.3.1(@types/react@19.3.0)(react@19.3.1)(redux@5.0.1) + reselect: 5.2.0 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.7.0(react@19.3.1) + victory-vendor: 37.4.1 + transitivePeerDependencies: + - '@types/react' + - redux + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + require-from-string@2.0.2: {} + + reselect@5.2.0: {} + + rolldown@1.2.16: + dependencies: + '@oxc-project/types': 0.156.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.16 + '@rolldown/binding-android-arm64': 1.2.16 + '@rolldown/binding-darwin-arm64': 1.2.16 + '@rolldown/binding-darwin-x64': 1.2.16 + '@rolldown/binding-freebsd-x64': 1.2.16 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.16 + '@rolldown/binding-linux-arm64-gnu': 1.2.16 + '@rolldown/binding-linux-arm64-musl': 1.2.16 + '@rolldown/binding-linux-ppc64-gnu': 1.2.16 + '@rolldown/binding-linux-s390x-gnu': 1.2.16 + '@rolldown/binding-linux-x64-gnu': 1.2.16 + '@rolldown/binding-linux-x64-musl': 1.2.16 + '@rolldown/binding-openharmony-arm64': 1.2.16 + '@rolldown/binding-win32-arm64-msvc': 1.2.16 + '@rolldown/binding-win32-x64-msvc': 1.2.16 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.28.0: {} + + semver@6.3.2: {} + + semver@7.8.6: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + sonner@2.0.8(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + optionalDependencies: + '@types/react': 19.3.0 + + source-map-js@1.2.2: {} + + std-env@4.3.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + tailwind-merge@3.7.0: {} + + tailwindcss@4.3.4: {} + + tiny-invariant@1.3.3: {} + + tinybench@6.2.3: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.2.0: {} + + tldts-core@7.4.20: {} + + tldts@7.4.19: + dependencies: + tldts-core: 7.4.20 + + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.19 + + tr46@7.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.9.1: {} + + tw-animate-css@1.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.72.1(eslint@10.14.0)(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.72.1(@typescript-eslint/parser@8.72.1(eslint@10.14.0)(typescript@6.0.3))(eslint@10.14.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.72.1(eslint@10.14.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.72.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.72.1(eslint@10.14.0)(typescript@6.0.3) + eslint: 10.14.0 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + undici-types@8.9.0: {} + + undici@8.12.4: {} + + update-browserslist-db@1.3.4(browserslist@4.29.5): + dependencies: + browserslist: 4.29.5 + escalade: 3.2.0 + picocolors: 1.1.1 + + uqr@0.1.3: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.3.0)(react@19.3.1): + dependencies: + react: 19.3.1 + tslib: 2.9.1 + optionalDependencies: + '@types/react': 19.3.0 + + use-sidecar@1.1.3(@types/react@19.3.0)(react@19.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.3.1 + tslib: 2.9.1 + optionalDependencies: + '@types/react': 19.3.0 + + use-sync-external-store@1.7.0(react@19.3.1): + dependencies: + react: 19.3.1 + + vaul@1.1.2(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1): + dependencies: + '@radix-ui/react-dialog': 1.3.1(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + react: 19.3.1 + react-dom: 19.3.1(react@19.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + victory-vendor@37.4.1: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.2.0 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.5 + d3-ease: 3.0.2 + d3-interpolate: 3.0.2 + d3-scale: 4.0.3 + d3-shape: 3.2.1 + d3-time: 3.1.1 + d3-timer: 3.0.2 + + vite@8.3.6(@types/node@26.6.6): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.32 + rolldown: 1.2.16 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.6.6 + fsevents: 2.3.3 + + vitest@5.1.0(@types/node@26.6.6)(@vitest/coverage-v8@5.1.0)(jsdom@30.1.3)(vite@8.3.6(@types/node@26.6.6)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.1.0(vite@8.3.6(@types/node@26.6.6)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.5 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.3 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.6(@types/node@26.6.6) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 26.6.6 + '@vitest/coverage-v8': 5.1.0(vitest@5.1.0) + jsdom: 30.1.3 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@6.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@17.3.0: + dependencies: + '@exodus/bytes': 1.16.0 + tr46: 7.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@3.2.1: {} + + word-wrap@1.2.5: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.6.7): + dependencies: + zod: 4.6.7 + + zod@4.6.7: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..cc5d10a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1 @@ +storeDir: /Users/naffah/.pnpm-store diff --git a/resources/css/charts.css b/resources/css/charts.css new file mode 100644 index 0000000..fc5f8f1 --- /dev/null +++ b/resources/css/charts.css @@ -0,0 +1,469 @@ +/* + * javaabu/stats: the Blade charts. Every colour, radius and font comes from + * Onu's tokens (--chart-1 … --chart-8, --card, --border, --muted-foreground, …), + * so the SVG charts follow the theme, dark mode and --onu-primary like the + * React charts. Logical properties only, so the layout works right to left. + */ + +.stats-chart, +.stats-tile, +.stats-sparkline, +.stats-tiles, +.stats-page { + --stats-foreground: var(--foreground, #0a0a0a); + --stats-muted: var(--muted-foreground, #737373); + --stats-border: var(--border, #e5e5e5); + --stats-card: var(--card, #fff); + --stats-card-foreground: var(--card-foreground, var(--stats-foreground)); + --stats-radius: var(--radius, 0.625rem); + --stats-positive: var(--success, #16a34a); + --stats-negative: var(--destructive, #dc2626); + --stats-previous: var(--muted-foreground, #a3a3a3); +} + +.stats-sr-only { + position: absolute; + inline-size: 1px; + block-size: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Chart */ + +.stats-chart { + display: flex; + flex-direction: column; + gap: 0.75rem; + min-inline-size: 0; + inline-size: 100%; + margin: 0; + color: var(--stats-foreground); +} + +.stats-chart__header { + display: grid; + gap: 0.125rem; +} + +.stats-chart__title { + font-weight: 500; + line-height: 1.375; +} + +.stats-chart__description, +.stats-chart__empty, +.stats-page__empty { + font-size: 0.875rem; + color: var(--stats-muted); +} + +.stats-chart__totals { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 0.5rem 1.5rem; +} + +.stats-chart__total { + display: grid; + gap: 0.125rem; +} + +.stats-chart__total-label { + font-size: 0.75rem; + color: var(--stats-muted); +} + +.stats-chart__total-value { + font-size: 1.5rem; + font-weight: 600; + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.stats-chart__total--previous .stats-chart__total-value { + font-size: 1.125rem; + font-weight: 400; + color: var(--stats-muted); +} + +.stats-chart__body { + min-inline-size: 0; +} + +.stats-chart__svg { + display: block; + inline-size: 100%; + block-size: auto; + overflow: visible; +} + +.stats-chart__grid line { + stroke: var(--stats-border); + stroke-width: 1; +} + +.stats-chart__zero { + stroke: var(--stats-muted) !important; +} + +.stats-chart__tick { + fill: var(--stats-muted); + font-size: 11px; + font-variant-numeric: tabular-nums; +} + +.stats-chart__line { + fill: none; + stroke: var(--stats-series); + stroke-width: 2; + stroke-linejoin: round; + stroke-linecap: round; +} + +.stats-chart__area { + fill: var(--stats-series); + fill-opacity: 0.2; + stroke: none; +} + +.stats-chart__bar { + fill: var(--stats-series); +} + +.stats-chart__series[data-previous] .stats-chart__bar { + fill-opacity: 0.45; +} + +.stats-chart__point { + fill: var(--stats-card); + stroke: var(--stats-series); + stroke-width: 2; +} + +.stats-chart__point[data-hidden] { + opacity: 0; +} + +.stats-chart__point:hover, +.stats-chart__bar:hover { + opacity: 0.85; +} + +.stats-chart__legend { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.25rem 1rem; + margin: 0.5rem 0 0; + padding: 0; + list-style: none; + font-size: 0.75rem; + color: var(--stats-muted); +} + +.stats-chart__legend li { + display: inline-flex; + align-items: center; + gap: 0.375rem; +} + +.stats-chart__swatch { + display: inline-block; + inline-size: 0.625rem; + block-size: 0.625rem; + border-radius: 2px; + background: var(--stats-series); +} + +.stats-chart__swatch[data-dashed] { + block-size: 0; + border-block-start: 2px dashed var(--stats-series); + background: none; +} + +.stats-chart__details summary { + cursor: pointer; + font-size: 0.875rem; + color: var(--stats-muted); + inline-size: fit-content; +} + +.stats-chart__details summary:focus-visible, +.stats-chart__table-wrapper:focus-visible, +a.stats-tile:focus-visible { + outline: 2px solid var(--ring, var(--stats-foreground)); + outline-offset: 2px; +} + +.stats-chart__table-wrapper { + max-block-size: 24rem; + overflow: auto; + margin-block-start: 0.5rem; + border: 1px solid var(--stats-border); + border-radius: var(--stats-radius); +} + +.stats-chart__table { + inline-size: 100%; + border-collapse: collapse; + font-size: 0.875rem; + font-variant-numeric: tabular-nums; +} + +.stats-chart__table th, +.stats-chart__table td { + padding: 0.5rem 0.75rem; + text-align: start; + border-block-end: 1px solid var(--stats-border); +} + +.stats-chart__table thead th { + position: sticky; + inset-block-start: 0; + background: var(--muted, #f5f5f5); + font-weight: 500; +} + +.stats-chart__table tbody th { + font-weight: 400; +} + +.stats-chart__table .stats-chart__number { + text-align: end; +} + +/* Pie */ + +.stats-chart__body--pie { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: 1rem 2rem; +} + +.stats-chart__pie { + position: relative; + inline-size: min(100%, 15rem); +} + +.stats-chart__slice { + fill: var(--stats-series); + stroke: var(--stats-card); + stroke-width: 2; +} + +.stats-chart__center { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + pointer-events: none; +} + +.stats-chart__center-value { + font-size: 1.25rem; + font-weight: 600; + font-variant-numeric: tabular-nums; +} + +.stats-chart__center-label { + font-size: 0.75rem; + color: var(--stats-muted); +} + +.stats-chart__legend--pie { + flex-direction: column; + align-items: stretch; + font-size: 0.875rem; +} + +.stats-chart__legend--pie li { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 0.5rem; +} + +.stats-chart__legend-value { + color: var(--stats-foreground); + font-variant-numeric: tabular-nums; +} + +/* Delta */ + +.stats-delta { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; + font-variant-numeric: tabular-nums; +} + +.stats-delta[data-sentiment="positive"] .stats-delta__value, +.stats-delta[data-sentiment="positive"] .stats-delta__icon { + color: var(--stats-positive); +} + +.stats-delta[data-sentiment="negative"] .stats-delta__value, +.stats-delta[data-sentiment="negative"] .stats-delta__icon { + color: var(--stats-negative); +} + +.stats-delta__context { + color: var(--stats-muted); +} + +/* Sparkline */ + +.stats-sparkline { + inline-size: 100%; + block-size: 2rem; + color: var(--stats-series, var(--chart-1)); +} + +.stats-sparkline svg { + display: block; + inline-size: 100%; + block-size: 100%; +} + +.stats-sparkline__line { + fill: none; + stroke: var(--stats-series, var(--chart-1)); + stroke-width: 1.5; + stroke-linejoin: round; +} + +.stats-sparkline__area { + fill: var(--stats-series, var(--chart-1)); + fill-opacity: 0.2; +} + +/* Stat tiles */ + +.stats-tiles { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); + gap: 1rem; +} + +.stats-tiles[data-columns="2"] { + grid-template-columns: repeat(auto-fit, minmax(max(min(100%, 14rem), calc((100% - 1rem) / 2)), 1fr)); +} + +.stats-tiles[data-columns="3"] { + grid-template-columns: repeat(auto-fit, minmax(max(min(100%, 14rem), calc((100% - 2rem) / 3)), 1fr)); +} + +.stats-tiles[data-columns="4"] { + grid-template-columns: repeat(auto-fit, minmax(max(min(100%, 12rem), calc((100% - 3rem) / 4)), 1fr)); +} + +.stats-tiles > * { + min-inline-size: 0; +} + +.stats-tile { + display: flex; + flex-direction: column; + padding: 1.25rem 1.5rem; + border: 1px solid var(--stats-border); + border-radius: calc(var(--stats-radius) + 4px); + background: var(--stats-card); + color: var(--stats-card-foreground); + text-decoration: none; +} + +a.stats-tile:hover { + border-color: var(--stats-muted); +} + +.stats-tile__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; +} + +.stats-tile__label { + font-size: 0.875rem; + line-height: 1.375rem; + color: var(--stats-muted); +} + +.stats-tile__icon { + color: var(--stats-muted); +} + +.stats-tile__value { + margin-block-start: 0.5rem; + font-size: 1.875rem; + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.stats-tile .stats-delta, +.stats-tile__period { + margin-block-start: 0.25rem; +} + +.stats-tile__period { + font-size: 0.875rem; + color: var(--stats-muted); +} + +.stats-tile__sparkline { + --stats-series: var(--chart-1); + margin-block-start: 0.5rem; + block-size: 2.875rem; +} + +.stats-tile__footer { + margin-block-start: 0.75rem; + padding-block-start: 0.5rem; + border-block-start: 1px solid var(--stats-border); + font-size: 0.875rem; + color: var(--stats-muted); +} + +/* Pages */ + +.stats-page { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.stats-page__title { + margin: 0; + font-size: 1.5rem; + font-weight: 600; +} + +.stats-page__export { + display: flex; + justify-content: flex-end; +} + +@media (forced-colors: active) { + .stats-chart__area, + .stats-sparkline__area { + fill-opacity: 1; + } +} + +@media (prefers-reduced-motion: no-preference) { + .stats-chart__bar, + .stats-chart__point { + transition: opacity 150ms ease; + } +} diff --git a/resources/views/categorical/index.blade.php b/resources/views/categorical/index.blade.php new file mode 100644 index 0000000..24c0600 --- /dev/null +++ b/resources/views/categorical/index.blade.php @@ -0,0 +1,5 @@ +@extends($layout) + +@section($layout_section) + @include('stats::partials.page', ['page' => 'categorical']) +@endsection diff --git a/resources/views/components/charts/cartesian.blade.php b/resources/views/components/charts/cartesian.blade.php new file mode 100644 index 0000000..14e7ed8 --- /dev/null +++ b/resources/views/components/charts/cartesian.blade.php @@ -0,0 +1,69 @@ +
merge(['class' => 'stats-chart', 'id' => $id]) }} data-slot="chart" data-chart-variant="{{ $svg['variant'] }}" aria-labelledby="{{ $id }}-title" @if($description) aria-describedby="{{ $id }}-description" @endif> + @include('stats::components.charts.header') + + @if($totals) +
+
+ {{ $totals['current_label'] }} + {{ $totals['current'] }} +
+
+ {{ $totals['previous_label'] }} + {{ $totals['previous'] }} +
+ @if($totals['delta']) + @include('stats::components.charts.delta', ['delta' => $totals['delta'], 'context' => null]) + @endif +
+ @endif + + @if($has_data) +
+ + + + @foreach($svg['series'] as $series) + + @if($series['area']) + + @endif + @if($series['line']) + + @endif + @foreach($series['bars'] as $bar) + {{ $bar['title'] }} + @endforeach + @foreach($series['points'] as $point) + 31) data-hidden @endif>{{ $point['title'] }} + @endforeach + + @endforeach + + + @if($legend) +
    + @foreach($legend as $item) +
  • {{ $item['label'] }}
  • + @endforeach +
+ @endif + +

{{ $summary }}

+
+ + @include('stats::components.charts.table', ['table' => $table]) + @else +

{{ $empty }}

+ @endif +
diff --git a/resources/views/components/charts/delta.blade.php b/resources/views/components/charts/delta.blade.php new file mode 100644 index 0000000..3fe1348 --- /dev/null +++ b/resources/views/components/charts/delta.blade.php @@ -0,0 +1,11 @@ +{{-- A change: arrow, sign and colour, read out as "Up 12%" --}} + + @if($delta['direction'] !== 'flat') + + @endif + + {{ $delta['spoken'] }} + @if(! empty($context)) + {{ $context }} + @endif + diff --git a/resources/views/components/charts/header.blade.php b/resources/views/components/charts/header.blade.php new file mode 100644 index 0000000..c67f5d3 --- /dev/null +++ b/resources/views/components/charts/header.blade.php @@ -0,0 +1,6 @@ +
+ {{ $title }} + @if($description) + {{ $description }} + @endif +
diff --git a/resources/views/components/charts/pie.blade.php b/resources/views/components/charts/pie.blade.php new file mode 100644 index 0000000..515fe04 --- /dev/null +++ b/resources/views/components/charts/pie.blade.php @@ -0,0 +1,40 @@ +
merge(['class' => 'stats-chart stats-chart--pie', 'id' => $id]) }} data-slot="chart" data-chart-variant="{{ $donut ? 'donut' : 'pie' }}" aria-labelledby="{{ $id }}-title" @if($description) aria-describedby="{{ $id }}-description" @endif> + @include('stats::components.charts.header') + + @if($has_data) +
+
+ + @foreach($slices as $slice) + @if($slice['path']) + {{ $slice['name'] }}: {{ $slice['value'] }} ({{ $slice['share'] }}) + @endif + @endforeach + + @if($center) + + @endif +
+ +
    + @foreach($slices as $slice) +
  • {{ $slice['name'] }}{{ $slice['share'] }}
  • + @endforeach +
+ +

{{ $summary }}

+
+ + @include('stats::components.charts.table', ['table' => [ + 'headers' => [$name_label, $value_label, $share_label], + 'rows' => array_map(fn (array $slice) => ['label' => $slice['name'], 'cells' => [$slice['value'], $slice['share']]], $slices), + ]]) + @else +

{{ $empty }}

+ @endif +
diff --git a/resources/views/components/charts/sparkline.blade.php b/resources/views/components/charts/sparkline.blade.php new file mode 100644 index 0000000..7aa689c --- /dev/null +++ b/resources/views/components/charts/sparkline.blade.php @@ -0,0 +1,10 @@ +
merge(['class' => 'stats-sparkline']) }} data-slot="sparkline" role="img" aria-label="{{ $label }}" style="--stats-series: {{ $color }}"> + +
diff --git a/resources/views/components/charts/stat-tile.blade.php b/resources/views/components/charts/stat-tile.blade.php new file mode 100644 index 0000000..58740a6 --- /dev/null +++ b/resources/views/components/charts/stat-tile.blade.php @@ -0,0 +1,34 @@ +@php($tag = $href ? 'a' : 'div') +<{{ $tag }} {{ $attributes->merge(['class' => 'stats-tile', 'id' => $id]) }} data-slot="stat-tile" @if($href) href="{{ $href }}" @else role="group" @endif aria-labelledby="{{ $id }}-label"> +
+ {{ $label }} + @if($icon) + + @endif +
+ +
{{ $value }}
+ + @if($delta) + @include('stats::components.charts.delta', ['delta' => $delta, 'context' => $delta_context]) + @elseif($period) + {{ $period }} + @endif + + @if($sparkline) + + @endif + + @if($footer) + + @endif + diff --git a/resources/views/components/charts/table.blade.php b/resources/views/components/charts/table.blade.php new file mode 100644 index 0000000..f05cb8e --- /dev/null +++ b/resources/views/components/charts/table.blade.php @@ -0,0 +1,24 @@ +
+ {{ $strings['view_table'] }} +
+ + + + @foreach($table['headers'] as $i => $header) + + @endforeach + + + + @foreach($table['rows'] as $row) + + + @foreach($row['cells'] as $cell) + + @endforeach + + @endforeach + +
0) class="stats-chart__number" @endif>{{ $header }}
{{ $row['label'] }}{{ $cell }}
+
+
diff --git a/resources/views/components/stat-tiles.blade.php b/resources/views/components/stat-tiles.blade.php new file mode 100644 index 0000000..312cb59 --- /dev/null +++ b/resources/views/components/stat-tiles.blade.php @@ -0,0 +1,6 @@ +@props(['columns' => 4]) + +{{-- A responsive grid of stat tiles: one column in narrow containers, up to $columns on wide ones --}} +
merge(['class' => 'stats-tiles']) }} data-slot="stat-tiles" data-columns="{{ $columns }}"> + {{ $slot }} +
diff --git a/resources/views/material-admin-26/categorical-stats/_form.blade.php b/resources/views/material-admin-26/categorical-stats/_form.blade.php deleted file mode 100644 index b19622a..0000000 --- a/resources/views/material-admin-26/categorical-stats/_form.blade.php +++ /dev/null @@ -1,124 +0,0 @@ -
-
- - - @foreach($filters as $filter => $value) - - @endforeach -
- -
- -
- -
-
- -
-
-
- - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- @php - $compares = [ - ' ' => __('No Comparison'), - 1 => __('Previous Period'), - 0 => __('Custom Period'), - ]; - @endphp - - -
-
-
- -
-
-
-
- -
-
-
-
- - {{ __('Generate') }} - - - - {{ __('Download') }} - -
-
-
diff --git a/resources/views/material-admin-26/categorical-stats/_generator.blade.php b/resources/views/material-admin-26/categorical-stats/_generator.blade.php deleted file mode 100644 index 03f50d5..0000000 --- a/resources/views/material-admin-26/categorical-stats/_generator.blade.php +++ /dev/null @@ -1,15 +0,0 @@ - - - @include('stats::material-admin-26.categorical-stats._form') - - - -
-
- -
-
- -@pushonce(config('stats.scripts_stack')) - @include('stats::material-admin-26.categorical-stats._script') -@endpushonce diff --git a/resources/views/material-admin-26/categorical-stats/_script.blade.php b/resources/views/material-admin-26/categorical-stats/_script.blade.php deleted file mode 100644 index cbf6fe4..0000000 --- a/resources/views/material-admin-26/categorical-stats/_script.blade.php +++ /dev/null @@ -1,215 +0,0 @@ - - diff --git a/resources/views/material-admin-26/categorical-stats/index.blade.php b/resources/views/material-admin-26/categorical-stats/index.blade.php deleted file mode 100644 index bd1af14..0000000 --- a/resources/views/material-admin-26/categorical-stats/index.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -@extends(config('stats.default_layout')) - -@section('title', __('Categorical Stats')) -@section('page-title', __('Categorical Stats')) - -@section('content') - -@endsection diff --git a/resources/views/material-admin-26/time-series-stats/_form.blade.php b/resources/views/material-admin-26/time-series-stats/_form.blade.php deleted file mode 100644 index d30524f..0000000 --- a/resources/views/material-admin-26/time-series-stats/_form.blade.php +++ /dev/null @@ -1,93 +0,0 @@ -
-
- - - @foreach($filters as $filter => $value) - - @endforeach -
- -
- -
- -
-
- -
-
-
- - -
-
- -
-
- -
-
- -
-
- -
-
- @php - $compares = [ - ' ' => __('No Comparision'), - 1 => __('Previous Period'), - 0 => __('Custom Period'), - ] - @endphp - - -
-
-
- -
-
-
-
- -
-
-
-
- - {{ __('Generate') }} - - - - {{ __('Download') }} - -
-
-
diff --git a/resources/views/material-admin-26/time-series-stats/_generator.blade.php b/resources/views/material-admin-26/time-series-stats/_generator.blade.php deleted file mode 100644 index f402cc3..0000000 --- a/resources/views/material-admin-26/time-series-stats/_generator.blade.php +++ /dev/null @@ -1,15 +0,0 @@ - - - @include('stats::material-admin-26.time-series-stats._form') - - - -
-
- -
-
- -@pushonce(config('stats.scripts_stack')) - @include('stats::material-admin-26.time-series-stats._script') -@endpushonce diff --git a/resources/views/material-admin-26/time-series-stats/_script.blade.php b/resources/views/material-admin-26/time-series-stats/_script.blade.php deleted file mode 100644 index e994168..0000000 --- a/resources/views/material-admin-26/time-series-stats/_script.blade.php +++ /dev/null @@ -1,225 +0,0 @@ - - diff --git a/resources/views/material-admin-26/time-series-stats/index.blade.php b/resources/views/material-admin-26/time-series-stats/index.blade.php deleted file mode 100644 index 8c08625..0000000 --- a/resources/views/material-admin-26/time-series-stats/index.blade.php +++ /dev/null @@ -1,7 +0,0 @@ -@extends('stats::material-admin-26.time-series-stats.stats') - -@section('page-title', __('Time Series Statistics')) - -@section('content') - -@endsection diff --git a/resources/views/material-admin-26/time-series-stats/stats.blade.php b/resources/views/material-admin-26/time-series-stats/stats.blade.php deleted file mode 100644 index 0d9b2a9..0000000 --- a/resources/views/material-admin-26/time-series-stats/stats.blade.php +++ /dev/null @@ -1,4 +0,0 @@ -@extends(config('stats.default_layout')) - -@section('title', 'Time Series Stats') -@section('page-title', __('Time Series Stats')) diff --git a/resources/views/partials/page.blade.php b/resources/views/partials/page.blade.php new file mode 100644 index 0000000..4c7ce40 --- /dev/null +++ b/resources/views/partials/page.blade.php @@ -0,0 +1,28 @@ +{{-- The stats page body: the form, then the chart and the CSV download --}} +
+

{{ $strings['title'] }}

+ + {{ $form }} + + @if($chart) + + + @if($export) +
+ @csrf + @foreach(\Illuminate\Support\Arr::dot(array_filter($export['params'], fn ($value) => $value !== null && $value !== '' && $value !== [])) as $name => $value) + @php + $segments = explode('.', $name); + $input_name = array_shift($segments) . implode('', array_map(fn ($segment) => is_numeric($segment) ? '[]' : "[{$segment}]", $segments)); + @endphp + + @endforeach + {{ $strings['export'] }} +
+ @endif + @elseif(isset($errors) && $errors->any()) + {{ $strings['invalid'] }} + @else +

{{ $strings['empty'] }}

+ @endif +
diff --git a/resources/views/time-series/index.blade.php b/resources/views/time-series/index.blade.php new file mode 100644 index 0000000..d2fe31b --- /dev/null +++ b/resources/views/time-series/index.blade.php @@ -0,0 +1,5 @@ +@extends($layout) + +@section($layout_section) + @include('stats::partials.page', ['page' => 'time-series']) +@endsection diff --git a/src/Cache/StatsCache.php b/src/Cache/StatsCache.php new file mode 100644 index 0000000..038d7c8 --- /dev/null +++ b/src/Cache/StatsCache.php @@ -0,0 +1,209 @@ +cache->store(config('stats.cache.store')); + } + + public function prefix(): string + { + return (string) config('stats.cache.prefix', 'javaabu_stats'); + } + + /** + * Get the value from the cache, or compute and store it + * + * @template TValue + * + * @param array $key what the value depends on + * @param list $metrics the metrics it's computed from, for invalidation + * @param Closure(): TValue $callback + * @return TValue + */ + public function remember(array $key, array $metrics, Closure $callback, ?int $ttl = null, ?CarbonInterface $ends_at = null): mixed + { + if (! $this->enabled() || $ttl === 0) { + return $callback(); + } + + return $this->store()->remember( + $this->key($key, $metrics), + $ttl ?? $this->ttlFor($ends_at), + $callback, + ); + } + + /** + * Compute the value and store it, whatever is in the cache + * + * @template TValue + * + * @param array $key + * @param list $metrics + * @param Closure(): TValue $callback + * @return TValue + */ + public function refresh(array $key, array $metrics, Closure $callback, ?int $ttl = null, ?CarbonInterface $ends_at = null): mixed + { + $value = $callback(); + + if ($this->enabled() && $ttl !== 0) { + $this->store()->put($this->key($key, $metrics), $value, $ttl ?? $this->ttlFor($ends_at)); + } + + return $value; + } + + /** + * Whether a value is cached + * + * @param array $key + * @param list $metrics + */ + public function has(array $key, array $metrics): bool + { + return $this->store()->has($this->key($key, $metrics)); + } + + /** + * The seconds to keep a result whose period ends at the given time + */ + public function ttlFor(?CarbonInterface $ends_at = null): int + { + if ($ends_at && $ends_at->lt(now()->startOfDay())) { + return (int) config('stats.cache.ttl.past', 60 * 60 * 24); + } + + return (int) config('stats.cache.ttl.current', 60 * 10); + } + + /** + * Invalidate what's cached for the metrics (all metrics for `*` or none given) + * + * @param string|list $metrics + */ + public function forget(string|array $metrics = self::ALL): void + { + $metrics = (array) $metrics; + + if ($metrics === [] || in_array(self::ALL, $metrics, true)) { + $this->flush(); + + return; + } + + foreach (array_unique($metrics) as $metric) { + $this->store()->forever($this->versionKey($metric), $this->newVersion()); + } + } + + /** + * Invalidate everything the package cached + */ + public function flush(): void + { + $this->store()->forever($this->versionKey(self::ALL), $this->newVersion()); + } + + /** + * The cache key for a value + * + * @param array $key + * @param list $metrics + */ + public function key(array $key, array $metrics): string + { + $metrics = array_values(array_unique($metrics)); + sort($metrics); + + $versions = [$this->version(self::ALL)]; + + foreach ($metrics as $metric) { + $versions[] = $this->version($metric); + } + + $hash = md5(json_encode([$this->normalize($key), $metrics, $versions], JSON_THROW_ON_ERROR)); + + return $this->prefix() . ':' . $hash; + } + + /** + * The current version of a metric's results + */ + public function version(string $metric): string + { + return (string) ($this->store()->get($this->versionKey($metric)) ?? '0'); + } + + protected function versionKey(string $metric): string + { + return $this->prefix() . ':version:' . ($metric === self::ALL ? '*' : md5($metric)); + } + + protected function newVersion(): string + { + return Str::random(12); + } + + /** + * Dates, enums and objects as stable scalars + */ + protected function normalize(mixed $value): mixed + { + if ($value instanceof DateTimeInterface) { + return $value->format('Y-m-d H:i:s'); + } + + if ($value instanceof BackedEnum) { + return $value->value; + } + + if ($value instanceof UnitEnum) { + return $value->name; + } + + if (is_array($value)) { + ksort($value); + + return array_map(fn ($item) => $this->normalize($item), $value); + } + + if (is_object($value)) { + return get_class($value) . ':' . md5(serialize($value)); + } + + return $value; + } +} diff --git a/src/CategoricalStats.php b/src/CategoricalStats.php index a66c178..f344bb9 100644 --- a/src/CategoricalStats.php +++ b/src/CategoricalStats.php @@ -203,12 +203,14 @@ public static function canViewAny(?Authorizable $user = null): bool public static function defaultMode(): CategoricalModes { - return config('stats.default_categorical_mode'); + $mode = config('stats.default_categorical_mode', CategoricalModes::NON_EMPTY); + + return $mode instanceof CategoricalModes ? $mode : CategoricalModes::from($mode); } public static function defaultDateRange(): PresetDateRanges { - return config('stats.default_date_range'); + return TimeSeriesStats::defaultDateRange(); } public static function categoricalItemsPerPage(): int @@ -217,7 +219,9 @@ public static function categoricalItemsPerPage(): int } /** - * Register the categorical API routes. + * Register the categorical JSON API routes + * + * @deprecated Use Route::statsApi() * * @param list $middleware */ @@ -227,17 +231,19 @@ public static function registerApiRoutes( string $categories_name = 'stats.categorical.categories', array $middleware = ['stats.view-categorical'] ): void { - Route::get($url, [CategoricalStatsApiController::class, 'index']) + Route::get($url, [config('stats.controllers.categorical_api', CategoricalStatsApiController::class), 'index']) ->name($name) ->middleware($middleware); - Route::get($url.'/categories', [CategoryProviderApiController::class, 'index']) + Route::get($url . '/categories', [config('stats.controllers.categories', CategoryProviderApiController::class), 'index']) ->name($categories_name) ->middleware($middleware); } /** - * Register the categorical admin routes. + * Register the categorical page and export routes + * + * @deprecated Use Route::stats() * * @param list $middleware */ @@ -247,11 +253,13 @@ public static function registerRoutes( string $export_name = 'stats.categorical.export', array $middleware = ['stats.view-categorical'] ): void { - Route::get($url, [CategoricalStatsController::class, 'index']) + $controller = config('stats.controllers.categorical', CategoricalStatsController::class); + + Route::get($url, [$controller, 'index']) ->name($index_name) ->middleware($middleware); - Route::post($url, [CategoricalStatsController::class, 'export']) + Route::post($url, [$controller, 'export']) ->name($export_name) ->middleware($middleware); } diff --git a/src/CategoryProviders/EloquentCategoryProvider.php b/src/CategoryProviders/EloquentCategoryProvider.php index 23c28a0..b009dff 100644 --- a/src/CategoryProviders/EloquentCategoryProvider.php +++ b/src/CategoryProviders/EloquentCategoryProvider.php @@ -121,7 +121,7 @@ public function searchCategoricalStatsItems(?string $search = null, int $per_pag ? $query->getModel()->getCategoricalStatsSearchField() : ($this->label_field == 'admin_link_name' ? 'name' : $this->label_field); - $query->where($search_field, 'like', '%'.$search.'%'); + $query->whereLike($search_field, '%' . $search . '%'); } } diff --git a/src/Charts/CategoryChart.php b/src/Charts/CategoryChart.php new file mode 100644 index 0000000..1e46322 --- /dev/null +++ b/src/Charts/CategoryChart.php @@ -0,0 +1,227 @@ +range(PresetDateRanges::THIS_YEAR)->compare(); + * CategoryChart::make()->stats(['payments_by_island', 'refunds_by_island'])->horizontal()->top(10); + * ``` + */ +class CategoryChart extends Chart +{ + use HasCategories; + use HasStats; + + public const string BAR = 'bar'; + + public const string LINE = 'line'; + + public const string AREA = 'area'; + + protected DateRange|bool $compare = false; + + protected string $variant = self::BAR; + + protected string $orientation = 'vertical'; + + protected bool $stacked = false; + + protected bool $positive_is_good = true; + + public static function make(?string $metric = null, ?string $label = null): static + { + $chart = new static; + + if ($metric !== null) { + $chart->stat($metric, $label); + } + + return $chart; + } + + public function type(): string + { + return 'category'; + } + + public function compare(DateRange|string|bool $with = true): static + { + $this->compare = is_string($with) ? PresetDateRanges::from($with) : $with; + + return $this; + } + + public function getCompareRange(): ?DateRange + { + if ($this->compare instanceof DateRange) { + return $this->compare; + } + + return $this->compare ? $this->getRange()->getPreviousDateRange() : null; + } + + public function positiveIsGood(bool $positive_is_good = true): static + { + $this->positive_is_good = $positive_is_good; + + return $this; + } + + public function variant(string $variant): static + { + $this->variant = $variant; + + return $this; + } + + public function bar(): static + { + return $this->variant(self::BAR); + } + + public function line(): static + { + return $this->variant(self::LINE); + } + + public function area(): static + { + return $this->variant(self::AREA); + } + + /** + * Bars along the side, for long category names + */ + public function horizontal(bool $horizontal = true): static + { + $this->orientation = $horizontal ? 'horizontal' : 'vertical'; + + return $this; + } + + public function stacked(bool $stacked = true): static + { + $this->stacked = $stacked; + + return $this; + } + + protected function defaultTitle(): string + { + $series = $this->series[0] ?? null; + + return $series ? $this->makeStat($series['metric'], $this->getRange(), $series['filters'])->getName() : ''; + } + + protected function compute(): array + { + $range = $this->getRange(); + $compare_range = $this->getCompareRange(); + $keys = $this->seriesKeys(); + $columns = []; + $first_stat = null; + + foreach ($this->series as $i => $series) { + $stat = $this->makeStat($series['metric'], $range, $series['filters']); + $first_stat ??= $stat; + + $columns[] = [ + 'key' => $keys[$i], + 'label' => $i === 0 && $compare_range + ? TimeSeriesSteps::rangeLabel($range, $stat->getDateFrom(), $stat->getDateTo()) + : ($series['label'] ?? $stat->getName()), + 'values' => $stat->resultsToArray($stat->results()), + ]; + } + + if ($compare_range && $first_stat) { + $series = $this->series[0]; + $stat = $this->makeStat($series['metric'], $compare_range, $series['filters']); + + $columns[] = [ + 'key' => 'previous', + 'label' => TimeSeriesSteps::rangeLabel($compare_range, $stat->getDateFrom(), $stat->getDateTo()), + 'values' => $stat->resultsToArray($stat->results()), + ]; + } + + $names = $first_stat ? $this->categoryNames($first_stat, array_column($columns, 'values')) : []; + $rows = []; + + foreach ($names as $id => $name) { + $row = ['category' => $name, 'categoryId' => $id]; + + foreach ($columns as $column) { + $row[$column['key']] = ($column['values'][$id] ?? 0) + 0; + } + + $rows[] = $row; + } + + if ($this->limit !== null && $columns !== []) { + $key = $columns[0]['key']; + + usort($rows, fn (array $a, array $b) => $b[$key] <=> $a[$key]); + + $rows = array_slice($rows, 0, $this->limit); + } + + $config = []; + + foreach ($columns as $column) { + $config[$column['key']] = ['label' => $column['label']]; + } + + $comparison = $compare_range && $first_stat ? [ + 'previousKey' => 'previous', + 'currentLabel' => $columns[0]['label'], + 'previousLabel' => end($columns)['label'], + 'positiveIsGood' => $this->positive_is_good, + ] : null; + + return [ + 'data' => $rows, + 'config' => $config, + 'categoryKey' => 'category', + 'categoryLabel' => $first_stat?->getCategoryTitle(), + 'aggregateLabel' => $first_stat?->getAggregateFieldLabel(), + 'variant' => $this->variant, + 'orientation' => $this->variant === self::BAR ? $this->orientation : null, + 'stacked' => $this->stacked && $comparison === null, + 'comparison' => $comparison, + ]; + } + + protected function cacheKey(): array + { + $compare = $this->getCompareRange(); + + return [ + ...$this->seriesCacheKey(), + ...$this->categoriesCacheKey(), + 'range' => static::rangeKey($this->getRange()), + 'compare' => $compare ? static::rangeKey($compare) : null, + 'variant' => $this->variant, + 'orientation' => $this->orientation, + 'stacked' => $this->stacked, + 'positive_is_good' => $this->positive_is_good, + ]; + } + + protected function endsAt(): ?CarbonInterface + { + return $this->getRange()->getDateTo(); + } +} diff --git a/src/Charts/Chart.php b/src/Charts/Chart.php new file mode 100644 index 0000000..af9ee73 --- /dev/null +++ b/src/Charts/Chart.php @@ -0,0 +1,327 @@ +`) or as SVG in Blade + * (``). + * + * The data is computed when the chart is serialised, and cached (see + * {@see StatsCache}) unless caching is turned off for the chart. + * + * @implements Arrayable + */ +abstract class Chart implements Arrayable, Htmlable, JsonSerializable +{ + public const string FORMAT_NUMBER = 'number'; + + public const string FORMAT_INTEGER = 'integer'; + + public const string FORMAT_COMPACT = 'compact'; + + public const string FORMAT_PERCENT = 'percent'; + + public const string FORMAT_CURRENCY = 'currency'; + + protected ?string $title = null; + + protected ?string $description = null; + + protected ?string $id = null; + + protected ?DateRange $range = null; + + /** @var array */ + protected array $filters = []; + + protected string $value_format = self::FORMAT_NUMBER; + + protected ?int $height = null; + + /** Seconds to cache the data: null for the default, 0 for none */ + protected ?int $cache_ttl = null; + + protected bool $cache_enabled = true; + + /** @var array|null */ + protected ?array $computed = null; + + /** + * The chart's type, as the React and Blade renderers know it + */ + abstract public function type(): string; + + /** + * The metrics the chart is computed from + * + * @return list + */ + abstract public function metrics(): array; + + /** + * Compute the chart's data + * + * @return array + */ + abstract protected function compute(): array; + + /** + * What the cached data depends on, besides the metrics + * + * @return array + */ + abstract protected function cacheKey(): array; + + /** + * The latest moment the chart's data covers, which decides its TTL + */ + abstract protected function endsAt(): ?CarbonInterface; + + public function title(?string $title): static + { + $this->title = $title; + + return $this; + } + + public function getTitle(): string + { + return $this->title ?? $this->defaultTitle(); + } + + /** + * The title when none is given: the name of the first stat + */ + abstract protected function defaultTitle(): string; + + public function description(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function id(?string $id): static + { + $this->id = $id; + + return $this; + } + + public function getId(): string + { + return $this->id ??= 'stats-chart-' . substr(md5(static::class . json_encode($this->cacheKey()) . implode(',', $this->metrics())), 0, 8); + } + + /** + * The date range of the chart. Default: `config('stats.default_date_range')` + */ + public function range(DateRange|string $range): static + { + $this->range = is_string($range) ? PresetDateRanges::from($range) : $range; + + $this->computed = null; + + return $this; + } + + public function getRange(): DateRange + { + return $this->range ?? TimeSeriesStats::defaultDateRange(); + } + + /** + * Fixed filters for every stat of the chart + * + * @param array $filters + */ + public function filters(array $filters): static + { + $this->filters = $filters; + + $this->computed = null; + + return $this; + } + + /** + * @return array + */ + public function getFilters(): array + { + return $this->filters; + } + + /** + * How values are formatted: number, integer, compact, percent or currency (MVR) + */ + public function valueFormat(string $format): static + { + $this->value_format = $format; + + return $this; + } + + public function getValueFormat(): string + { + return $this->value_format; + } + + public function height(?int $height): static + { + $this->height = $height; + + return $this; + } + + public function getHeight(): ?int + { + return $this->height; + } + + /** + * Cache the data for the given seconds, or `false` not to cache it + */ + public function cache(int|bool $seconds = true, ?int $minutes = null): static + { + if ($minutes !== null) { + $seconds = $minutes * 60; + } + + $this->cache_enabled = $seconds !== false && $seconds !== 0; + $this->cache_ttl = is_int($seconds) && $seconds > 0 ? $seconds : null; + + return $this; + } + + public function withoutCache(): static + { + return $this->cache(false); + } + + public function isCached(): bool + { + return $this->cache_enabled; + } + + /** + * The chart's data, from the cache when it's there + * + * @return array + */ + public function data(): array + { + if ($this->computed !== null) { + return $this->computed; + } + + if (! $this->cache_enabled) { + return $this->computed = $this->compute(); + } + + return $this->computed = $this->statsCache()->remember( + $this->fullCacheKey(), + $this->metrics(), + fn () => $this->compute(), + $this->cache_ttl, + $this->endsAt(), + ); + } + + /** + * Compute the data again and put it in the cache + * + * @return array + */ + public function refresh(): array + { + if (! $this->cache_enabled) { + return $this->computed = $this->compute(); + } + + return $this->computed = $this->statsCache()->refresh( + $this->fullCacheKey(), + $this->metrics(), + fn () => $this->compute(), + $this->cache_ttl, + $this->endsAt(), + ); + } + + /** + * Whether the chart's data is in the cache + */ + public function isWarm(): bool + { + return $this->cache_enabled && $this->statsCache()->has($this->fullCacheKey(), $this->metrics()); + } + + /** + * @return array + */ + protected function fullCacheKey(): array + { + return [ + 'type' => $this->type(), + 'chart' => static::class, + 'locale' => app()->getLocale(), + ...$this->cacheKey(), + ]; + } + + protected function statsCache(): StatsCache + { + return app(StatsCache::class); + } + + /** + * The chart as the props of the React `` and the Blade components + * + * @return array + */ + public function toArray(): array + { + return [ + 'type' => $this->type(), + 'id' => $this->getId(), + 'title' => $this->getTitle(), + 'description' => $this->description, + 'valueFormat' => $this->value_format, + 'height' => $this->height, + ...$this->data(), + ]; + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * The chart as SVG, through the `` Blade component + */ + public function render(): string + { + return Blade::renderComponent(new ChartComponent($this)); + } + + public function toHtml(): string + { + return $this->render(); + } +} diff --git a/src/Charts/Concerns/HasCategories.php b/src/Charts/Concerns/HasCategories.php new file mode 100644 index 0000000..9b24910 --- /dev/null +++ b/src/Charts/Concerns/HasCategories.php @@ -0,0 +1,116 @@ + */ + protected array $categories = []; + + protected ?int $limit = null; + + /** + * Which categories to show: with data (non_empty), all, or specific ones + */ + public function categoryMode(CategoricalModes|string $mode): static + { + $this->category_mode = is_string($mode) ? CategoricalModes::from($mode) : $mode; + + return $this; + } + + public function getCategoryMode(): CategoricalModes + { + if ($this->categories !== []) { + return CategoricalModes::SPECIFIC; + } + + return $this->category_mode ?? CategoricalStats::defaultMode(); + } + + /** + * Show only these categories, by id + * + * @param list $ids + */ + public function categories(array $ids): static + { + $this->categories = array_values($ids); + + return $this; + } + + /** + * Show only the categories with the highest values + */ + public function top(?int $limit): static + { + $this->limit = $limit; + + return $this; + } + + /** + * @param array $filters + */ + public function makeStat(string $metric, DateRange $range, array $filters = []): CategoricalStatsRepository + { + if (! class_exists(CategoricalStats::getClassNameForMetric($metric))) { + throw StatsException::unknownMetric($metric); + } + + return CategoricalStats::createFromMetric($metric, $range, array_merge($this->filters, $filters)); + } + + /** + * Labels keyed by category id, in the provider's order, for the values given + * + * @param list> $value_sets + * @return array + */ + protected function categoryNames(CategoricalStatsRepository $stat, array $value_sets): array + { + $mode = $this->getCategoryMode(); + + if ($mode === CategoricalModes::SPECIFIC) { + return $stat->resolveCategoryNames($mode, $this->categories); + } + + if ($mode === CategoricalModes::ALL) { + return $stat->resolveCategoryNames($mode); + } + + $ids = []; + + foreach ($value_sets as $values) { + foreach ($values as $id => $value) { + $ids[(string) $id] = $id; + } + } + + return $ids === [] ? [] : $stat->resolveCategoryNames(CategoricalModes::SPECIFIC, array_values($ids)); + } + + /** + * @return array + */ + protected function categoriesCacheKey(): array + { + return [ + 'category_mode' => $this->getCategoryMode()->value, + 'categories' => $this->categories, + 'limit' => $this->limit, + ]; + } +} diff --git a/src/Charts/Concerns/HasStats.php b/src/Charts/Concerns/HasStats.php new file mode 100644 index 0000000..d14b785 --- /dev/null +++ b/src/Charts/Concerns/HasStats.php @@ -0,0 +1,125 @@ +}> */ + protected array $series = []; + + /** + * Add a stat to the chart + * + * @param array $filters filters for this stat only + */ + public function stat(string $metric, ?string $label = null, array $filters = []): static + { + $this->series[] = ['metric' => $metric, 'label' => $label, 'filters' => $filters]; + + $this->computed = null; + + return $this; + } + + /** + * Add several stats: a list of metrics, or labels keyed by metric + * + * @param array $metrics + */ + public function stats(array $metrics): static + { + foreach ($metrics as $key => $value) { + is_int($key) ? $this->stat($value) : $this->stat($key, $value); + } + + return $this; + } + + /** + * @return list + */ + public function metrics(): array + { + return array_values(array_unique(array_column($this->series, 'metric'))); + } + + /** + * @return list}> + */ + public function getSeries(): array + { + return $this->series; + } + + /** + * Data keys for the series: CSS-safe and unique (`payments_count`, `payments_count_2`) + * + * @return list + */ + protected function seriesKeys(): array + { + $keys = []; + + foreach ($this->series as $series) { + $key = Str::of(class_basename($series['metric']))->snake()->slug('_')->toString() ?: 'stat'; + $base = $key; + $i = 2; + + while (in_array($key, $keys, true) || in_array($key, ['date', 'category', 'previous'], true)) { + $key = $base . '_' . $i++; + } + + $keys[] = $key; + } + + return $keys; + } + + /** + * The chart's fixed filters merged with a series' own + * + * @param array{metric: string, label: string|null, filters: array} $series + * @return array + */ + protected function filtersFor(array $series): array + { + return array_merge($this->filters, $series['filters']); + } + + /** + * @return array + */ + protected function seriesCacheKey(): array + { + return [ + 'series' => $this->series, + 'filters' => $this->filters, + ]; + } + + /** + * The dates a range resolves to now, for cache keys. Presets are keyed by + * day, so a range that ends "now" (lifetime) keeps its key all day; the + * TTL decides how fresh it is. + * + * @return array{name: string, from: string, to: string} + */ + protected static function rangeKey(DateRange $range): array + { + $format = $range instanceof PresetDateRanges ? 'Y-m-d' : 'Y-m-d H:i:s'; + + return [ + 'name' => $range->getName(), + 'from' => $range->getDateFrom()->format($format), + 'to' => $range->getDateTo()->format($format), + ]; + } +} diff --git a/src/Charts/PieChart.php b/src/Charts/PieChart.php new file mode 100644 index 0000000..1659287 --- /dev/null +++ b/src/Charts/PieChart.php @@ -0,0 +1,154 @@ +range(PresetDateRanges::THIS_MONTH)->donut(); + * PieChart::make('applications_by_island')->top(5); // the rest as "Other" + * ``` + */ +class PieChart extends Chart +{ + use HasCategories; + use HasStats; + + protected bool $donut = false; + + protected ?string $center_value = null; + + protected ?string $center_label = null; + + protected bool $show_center = true; + + protected bool $keep_empty = false; + + public static function make(?string $metric = null, ?string $label = null): static + { + $chart = new static; + + if ($metric !== null) { + $chart->stat($metric, $label); + } + + return $chart; + } + + public function type(): string + { + return 'pie'; + } + + public function donut(bool $donut = true): static + { + $this->donut = $donut; + + return $this; + } + + /** + * The donut's centre: a value and a label, or `false` for none. Default: the total + */ + public function centerLabel(string|false|null $value, ?string $label = null): static + { + $this->show_center = $value !== false; + $this->center_value = is_string($value) ? $value : null; + $this->center_label = $label; + + return $this; + } + + /** + * Keep categories with no value as empty slices in the table + */ + public function keepEmpty(bool $keep = true): static + { + $this->keep_empty = $keep; + + return $this; + } + + protected function defaultTitle(): string + { + $series = $this->series[0] ?? null; + + return $series ? $this->makeStat($series['metric'], $this->getRange(), $series['filters'])->getName() : ''; + } + + protected function compute(): array + { + $series = $this->series[0] ?? null; + + if (! $series) { + return ['data' => [], 'variant' => $this->donut ? 'donut' : 'pie', 'centerLabel' => null, 'total' => 0]; + } + + $stat = $this->makeStat($series['metric'], $this->getRange(), $series['filters']); + $values = $stat->resultsToArray($stat->results()); + $slices = []; + + foreach ($this->categoryNames($stat, [$values]) as $id => $name) { + $value = ($values[$id] ?? 0) + 0; + + if ($value <= 0 && ! $this->keep_empty) { + continue; + } + + $slices[] = ['name' => $name, 'value' => $value, 'categoryId' => $id]; + } + + usort($slices, fn (array $a, array $b) => $b['value'] <=> $a['value']); + + if ($this->limit !== null && count($slices) > $this->limit) { + $rest = array_slice($slices, $this->limit); + + $slices = array_slice($slices, 0, $this->limit); + $slices[] = [ + 'name' => __('Other'), + 'value' => array_sum(array_column($rest, 'value')), + 'categoryId' => null, + ]; + } + + $center = null; + + if ($this->donut && $this->show_center && ($this->center_value !== null || $this->center_label !== null)) { + $center = array_filter(['value' => $this->center_value, 'label' => $this->center_label], fn ($value) => $value !== null); + } + + return [ + 'data' => $slices, + 'nameKey' => 'name', + 'valueKey' => 'value', + 'variant' => $this->donut ? 'donut' : 'pie', + 'centerLabel' => $this->show_center ? $center : false, + 'nameLabel' => $stat->getCategoryTitle(), + 'aggregateLabel' => $stat->getAggregateFieldLabel(), + 'total' => array_sum(array_column($slices, 'value')), + ]; + } + + protected function cacheKey(): array + { + return [ + ...$this->seriesCacheKey(), + ...$this->categoriesCacheKey(), + 'range' => static::rangeKey($this->getRange()), + 'donut' => $this->donut, + 'center' => [$this->show_center, $this->center_value, $this->center_label], + 'keep_empty' => $this->keep_empty, + ]; + } + + protected function endsAt(): ?CarbonInterface + { + return $this->getRange()->getDateTo(); + } +} diff --git a/src/Charts/Sparkline.php b/src/Charts/Sparkline.php new file mode 100644 index 0000000..dfc9a1f --- /dev/null +++ b/src/Charts/Sparkline.php @@ -0,0 +1,128 @@ +range(PresetDateRanges::LAST_30_DAYS)->area(); + * ``` + */ +class Sparkline extends Chart +{ + use HasStats; + + protected ?TimeSeriesModes $mode = null; + + protected string $variant = 'line'; + + protected ?string $color = null; + + public static function make(?string $metric = null, ?string $label = null): static + { + $chart = new static; + + if ($metric !== null) { + $chart->stat($metric, $label); + } + + return $chart; + } + + public function type(): string + { + return 'sparkline'; + } + + public function mode(TimeSeriesModes|string $mode): static + { + $this->mode = is_string($mode) ? TimeSeriesModes::from($mode) : $mode; + + return $this; + } + + public function getMode(): TimeSeriesModes + { + return $this->mode ?? TimeSeriesStats::defaultMode(); + } + + public function area(bool $area = true): static + { + $this->variant = $area ? 'area' : 'line'; + + return $this; + } + + /** + * Any CSS colour or token. Default: `var(--chart-1)` + */ + public function color(?string $color): static + { + $this->color = $color; + + return $this; + } + + protected function defaultTitle(): string + { + $series = $this->series[0] ?? null; + + return $series ? $this->makeStat($series['metric'], $series['filters'])->getName() : ''; + } + + /** + * @param array $filters + */ + protected function makeStat(string $metric, array $filters = []): TimeSeriesStatsRepository + { + if (! class_exists(TimeSeriesStats::getClassNameForMetric($metric))) { + throw StatsException::unknownMetric($metric); + } + + return TimeSeriesStats::createFromMetric($metric, $this->getRange(), array_merge($this->filters, $filters)); + } + + protected function compute(): array + { + $series = $this->series[0] ?? null; + $values = []; + + if ($series) { + $values = array_column(TimeSeriesSteps::for($this->makeStat($series['metric'], $series['filters']), $this->getMode()), 'value'); + } + + return [ + 'data' => $values, + 'label' => $series['label'] ?? $this->getTitle(), + 'variant' => $this->variant, + 'color' => $this->color, + 'mode' => $this->getMode()->value, + ]; + } + + protected function cacheKey(): array + { + return [ + ...$this->seriesCacheKey(), + 'mode' => $this->getMode()->value, + 'range' => static::rangeKey($this->getRange()), + 'variant' => $this->variant, + 'color' => $this->color, + ]; + } + + protected function endsAt(): ?CarbonInterface + { + return $this->getRange()->getDateTo(); + } +} diff --git a/src/Charts/StatTile.php b/src/Charts/StatTile.php new file mode 100644 index 0000000..3a00bd4 --- /dev/null +++ b/src/Charts/StatTile.php @@ -0,0 +1,214 @@ +` in Blade. + * + * ```php + * StatTile::make('payments_count')->range(PresetDateRanges::THIS_MONTH)->compare()->sparkline(); + * ``` + */ +class StatTile extends Chart +{ + use HasStats; + + protected DateRange|bool $compare = false; + + protected ?TimeSeriesModes $sparkline_mode = null; + + protected bool $has_sparkline = false; + + protected ?string $icon = null; + + protected ?string $href = null; + + protected bool $positive_is_good = true; + + protected ?string $delta_context = null; + + protected ?string $footer = null; + + public static function make(?string $metric = null, ?string $label = null): static + { + $chart = new static; + + if ($metric !== null) { + $chart->stat($metric, $label); + } + + return $chart; + } + + public function type(): string + { + return 'stat-tile'; + } + + /** + * The tile's label. Default: the stat's name + */ + public function label(?string $label): static + { + return $this->title($label); + } + + /** + * Show the change from the previous period, or from the given period + */ + public function compare(DateRange|string|bool $with = true): static + { + $this->compare = is_string($with) ? PresetDateRanges::from($with) : $with; + + return $this; + } + + public function getCompareRange(): ?DateRange + { + if ($this->compare instanceof DateRange) { + return $this->compare; + } + + return $this->compare ? $this->getRange()->getPreviousDateRange() : null; + } + + /** + * Show a sparkline of the range, grouped by the mode (default: the default mode) + */ + public function sparkline(TimeSeriesModes|string|bool $mode = true): static + { + $this->has_sparkline = $mode !== false; + $this->sparkline_mode = is_string($mode) ? TimeSeriesModes::from($mode) : ($mode instanceof TimeSeriesModes ? $mode : null); + + return $this; + } + + /** + * An Onu semantic icon name (`file-text`, `credit-card`, …) + */ + public function icon(?string $icon): static + { + $this->icon = $icon; + + return $this; + } + + /** + * Make the tile a link + */ + public function href(?string $href): static + { + $this->href = $href; + + return $this; + } + + public function positiveIsGood(bool $positive_is_good = true): static + { + $this->positive_is_good = $positive_is_good; + + return $this; + } + + /** + * Text after the change. Default: "vs previous period" + */ + public function deltaContext(?string $context): static + { + $this->delta_context = $context; + + return $this; + } + + public function footer(?string $footer): static + { + $this->footer = $footer; + + return $this; + } + + protected function defaultTitle(): string + { + $series = $this->series[0] ?? null; + + return $series['label'] ?? ($series ? $this->makeStat($series['metric'], $this->getRange(), $series['filters'])->getName() : ''); + } + + /** + * @param array $filters + */ + protected function makeStat(string $metric, DateRange $range, array $filters = []): TimeSeriesStatsRepository + { + if (! class_exists(TimeSeriesStats::getClassNameForMetric($metric))) { + throw StatsException::unknownMetric($metric); + } + + return TimeSeriesStats::createFromMetric($metric, $range, array_merge($this->filters, $filters)); + } + + protected function compute(): array + { + $series = $this->series[0] ?? null; + + if (! $series) { + return ['value' => null]; + } + + $stat = $this->makeStat($series['metric'], $this->getRange(), $series['filters']); + $compare_range = $this->getCompareRange(); + $previous = $compare_range ? $this->makeStat($series['metric'], $compare_range, $series['filters'])->total() : null; + $sparkline = null; + + if ($this->has_sparkline) { + $sparkline = array_column(TimeSeriesSteps::for($stat, $this->sparkline_mode ?? TimeSeriesStats::defaultMode()), 'value'); + } + + return [ + 'label' => $this->getTitle(), + 'value' => $stat->total(), + 'previousValue' => $previous, + 'deltaContext' => $compare_range ? ($this->delta_context ?? __('vs previous period')) : null, + 'positiveIsGood' => $this->positive_is_good, + 'sparkline' => $sparkline, + 'icon' => $this->icon, + 'href' => $this->href, + 'footer' => $this->footer, + 'period' => TimeSeriesSteps::rangeLabel($this->getRange(), $stat->getDateFrom(), $stat->getDateTo()), + ]; + } + + protected function cacheKey(): array + { + $compare = $this->getCompareRange(); + + return [ + ...$this->seriesCacheKey(), + 'range' => static::rangeKey($this->getRange()), + 'compare' => $compare ? static::rangeKey($compare) : null, + 'sparkline' => $this->has_sparkline ? ($this->sparkline_mode ?? TimeSeriesStats::defaultMode())->value : null, + 'title' => $this->title, + 'icon' => $this->icon, + 'href' => $this->href, + 'positive_is_good' => $this->positive_is_good, + 'delta_context' => $this->delta_context, + 'footer' => $this->footer, + ]; + } + + protected function endsAt(): ?CarbonInterface + { + return $this->getRange()->getDateTo(); + } +} diff --git a/src/Charts/TimeSeriesChart.php b/src/Charts/TimeSeriesChart.php new file mode 100644 index 0000000..11d79c8 --- /dev/null +++ b/src/Charts/TimeSeriesChart.php @@ -0,0 +1,334 @@ +range(PresetDateRanges::LAST_30_DAYS)->compare(); + * TimeSeriesChart::make()->stats(['payments_count', 'refunds_count'])->variant('bar'); + * TimeSeriesChart::make('payments_count')->mode(TimeSeriesModes::MONTH) + * ->periods([PresetDateRanges::THIS_YEAR, PresetDateRanges::LAST_YEAR]); + * ``` + */ +class TimeSeriesChart extends Chart +{ + use HasStats; + + public const string LINE = 'line'; + + public const string AREA = 'area'; + + public const string BAR = 'bar'; + + protected ?TimeSeriesModes $mode = null; + + /** @var list */ + protected array $periods = []; + + protected DateRange|bool $compare = false; + + protected string $variant = self::LINE; + + protected bool $stacked = false; + + protected bool $brush = false; + + protected bool $positive_is_good = true; + + public static function make(?string $metric = null, ?string $label = null): static + { + $chart = new static; + + if ($metric !== null) { + $chart->stat($metric, $label); + } + + return $chart; + } + + public function type(): string + { + return 'time-series'; + } + + /** + * Group by hour, day, week, month or year. Default: `config('stats.default_time_series_mode')` + */ + public function mode(TimeSeriesModes|string $mode): static + { + $this->mode = is_string($mode) ? TimeSeriesModes::from($mode) : $mode; + + return $this; + } + + public function getMode(): TimeSeriesModes + { + return $this->mode ?? TimeSeriesStats::defaultMode(); + } + + /** + * Plot the first stat over each period, step by step on the first period's dates + * + * @param list $periods + */ + public function periods(array $periods): static + { + $this->periods = array_map(fn ($period) => is_string($period) ? PresetDateRanges::from($period) : $period, array_values($periods)); + + return $this; + } + + /** + * Compare the first stat with the previous period, or with the given period + */ + public function compare(DateRange|string|bool $with = true): static + { + $this->compare = is_string($with) ? PresetDateRanges::from($with) : $with; + + return $this; + } + + /** + * Whether a rise is good (green) in the comparison. False for costs, complaints, … + */ + public function positiveIsGood(bool $positive_is_good = true): static + { + $this->positive_is_good = $positive_is_good; + + return $this; + } + + /** + * How the series are drawn: line, area or bar + */ + public function variant(string $variant): static + { + $this->variant = $variant; + + return $this; + } + + public function line(): static + { + return $this->variant(self::LINE); + } + + public function area(): static + { + return $this->variant(self::AREA); + } + + public function bar(): static + { + return $this->variant(self::BAR); + } + + public function stacked(bool $stacked = true): static + { + $this->stacked = $stacked; + + return $this; + } + + /** + * Add a range slider to zoom in (React only) + */ + public function brush(bool $brush = true): static + { + $this->brush = $brush; + + return $this; + } + + public function getCompareRange(): ?DateRange + { + if ($this->compare instanceof DateRange) { + return $this->compare; + } + + return $this->compare ? $this->getRange()->getPreviousDateRange() : null; + } + + protected function defaultTitle(): string + { + $metric = $this->series[0]['metric'] ?? null; + + return $metric ? $this->makeStat($metric, $this->getRange(), $this->series[0]['filters'])->getName() : ''; + } + + public function makeStat(string $metric, DateRange $range, array $filters = []): TimeSeriesStatsRepository + { + if (! class_exists(TimeSeriesStats::getClassNameForMetric($metric))) { + throw StatsException::unknownMetric($metric); + } + + return TimeSeriesStats::createFromMetric($metric, $range, array_merge($this->filters, $filters)); + } + + protected function compute(): array + { + $mode = $this->getMode(); + + $columns = $this->periods !== [] ? $this->periodColumns($mode) : $this->seriesColumns($mode); + + $first = $columns[0] ?? null; + $rows = []; + $labels = []; + + foreach ($first['steps'] ?? [] as $i => $step) { + $row = ['date' => $mode->toIsoDate($step['date'])]; + + foreach ($columns as $column) { + $row[$column['key']] = $column['steps'][$i]['value'] ?? null; + } + + $rows[] = $row; + $labels[] = $mode->formatDate($step['date']); + } + + $config = []; + $totals = []; + + foreach ($columns as $column) { + $config[$column['key']] = ['label' => $column['label']]; + $totals[$column['key']] = array_sum(array_column($column['steps'], 'value')); + } + + $comparison = null; + + if ($this->periods === [] && ($previous = collect($columns)->firstWhere('key', 'previous'))) { + $comparison = [ + 'previousKey' => 'previous', + 'currentLabel' => $first['label'], + 'previousLabel' => $previous['label'], + 'positiveIsGood' => $this->positive_is_good, + ]; + } + + return [ + 'data' => $rows, + 'labels' => $labels, + 'config' => $config, + 'totals' => $totals, + 'dateKey' => 'date', + 'mode' => $mode->value, + 'interval' => $mode->chartInterval(), + 'variant' => $this->variant, + 'stacked' => $this->stacked && $comparison === null, + 'brush' => $this->brush, + 'comparison' => $comparison, + 'aggregateLabel' => $first['aggregate_label'] ?? null, + ]; + } + + /** + * One column per stat, and one for the comparison + * + * @return list>, aggregate_label: string}> + */ + protected function seriesColumns(TimeSeriesModes $mode): array + { + $range = $this->getRange(); + $compare_range = $this->getCompareRange(); + $keys = $this->seriesKeys(); + $columns = []; + + foreach ($this->series as $i => $series) { + $stat = $this->makeStat($series['metric'], $range, $series['filters']); + + $label = $series['label'] ?? $stat->getName(); + + if ($i === 0 && $compare_range) { + $label = TimeSeriesSteps::rangeLabel($range, $stat->getDateFrom(), $stat->getDateTo()); + } + + $columns[] = [ + 'key' => $keys[$i], + 'label' => $label, + 'steps' => TimeSeriesSteps::for($stat, $mode), + 'aggregate_label' => $stat->getAggregateFieldLabel(), + ]; + } + + if ($compare_range && $this->series !== []) { + $series = $this->series[0]; + $stat = $this->makeStat($series['metric'], $compare_range, $series['filters']); + + $columns[] = [ + 'key' => 'previous', + 'label' => TimeSeriesSteps::rangeLabel($compare_range, $stat->getDateFrom(), $stat->getDateTo()), + 'steps' => TimeSeriesSteps::for($stat, $mode), + 'aggregate_label' => $stat->getAggregateFieldLabel(), + ]; + } + + return $columns; + } + + /** + * One column per period of the first stat + * + * @return list>, aggregate_label: string}> + */ + protected function periodColumns(TimeSeriesModes $mode): array + { + $series = $this->series[0] ?? null; + + if (! $series) { + return []; + } + + $columns = []; + + foreach ($this->periods as $i => $period) { + $stat = $this->makeStat($series['metric'], $period, $series['filters']); + + $columns[] = [ + 'key' => 'period_' . ($i + 1), + 'label' => TimeSeriesSteps::rangeLabel($period, $stat->getDateFrom(), $stat->getDateTo()), + 'steps' => TimeSeriesSteps::for($stat, $mode), + 'aggregate_label' => $stat->getAggregateFieldLabel(), + ]; + } + + return $columns; + } + + protected function cacheKey(): array + { + $compare = $this->getCompareRange(); + + return [ + ...$this->seriesCacheKey(), + 'mode' => $this->getMode()->value, + 'range' => static::rangeKey($this->getRange()), + 'compare' => $compare ? static::rangeKey($compare) : null, + 'periods' => array_map(fn (DateRange $period) => static::rangeKey($period), $this->periods), + 'variant' => $this->variant, + 'stacked' => $this->stacked, + 'brush' => $this->brush, + 'positive_is_good' => $this->positive_is_good, + ]; + } + + protected function endsAt(): ?CarbonInterface + { + $ranges = $this->periods !== [] ? $this->periods : [$this->getRange()]; + + return collect($ranges)->map(fn (DateRange $range) => $range->getDateTo())->max(); + } +} diff --git a/src/Commands/GenerateCategoricalStatCommand.php b/src/Commands/GenerateCategoricalStatCommand.php index ddcd60b..458d00b 100644 --- a/src/Commands/GenerateCategoricalStatCommand.php +++ b/src/Commands/GenerateCategoricalStatCommand.php @@ -64,7 +64,7 @@ public function handle(): int ); $path = $this->getPath(app_path('Stats/Categorical'), $path); - $file_name = $generator->getName().'.php'; + $file_name = $generator->getName() . '.php'; $file_path = $this->getFullFilePath($path, $file_name); $output = $generator->render(); @@ -112,7 +112,7 @@ protected function registerStat(AbstractCategoricalStatGenerator $generator): vo [ 'search' => 'CategoricalStats::register([', 'keep_search' => true, - 'content' => $line_ending.$this->getRenderer()->addIndentation("'{$metric}' => {$class_name}::class,", 3), + 'content' => $line_ending . $this->getRenderer()->addIndentation("'{$metric}' => {$class_name}::class,", 3), ], ]; diff --git a/src/Commands/GenerateTimeSeriesStatCommand.php b/src/Commands/GenerateTimeSeriesStatCommand.php index ec5befe..d49a38e 100644 --- a/src/Commands/GenerateTimeSeriesStatCommand.php +++ b/src/Commands/GenerateTimeSeriesStatCommand.php @@ -23,7 +23,7 @@ protected function getArguments() { return [ ['name', InputArgument::REQUIRED, 'The name for the stat class.'], - ['model', InputArgument::REQUIRED, 'The model class for which you want to generate the stats for. Can be the model class or morph name.'] + ['model', InputArgument::REQUIRED, 'The model class for which you want to generate the stats for. Can be the model class or morph name.'], ]; } @@ -73,7 +73,6 @@ protected function getGenerator(string $type, string $name, string $model_class) return new $class($name, $model_class); } - /** * @return class-string */ diff --git a/src/Commands/WarmStatsCacheCommand.php b/src/Commands/WarmStatsCacheCommand.php new file mode 100644 index 0000000..0c4dce5 --- /dev/null +++ b/src/Commands/WarmStatsCacheCommand.php @@ -0,0 +1,40 @@ +option('flush')) { + $stats->flush(); + } + + if ($this->option('queue')) { + WarmStatsCache::dispatch(); + + $this->components->info('Dispatched the job that warms the stats cache.'); + + return self::SUCCESS; + } + + $count = $stats->warmNow(function (Chart $chart) { + $this->components->twoColumnDetail($chart->getTitle(), $chart->type()); + }); + + $this->components->info("Warmed {$count} " . str('chart')->plural($count) . '.'); + + return self::SUCCESS; + } +} diff --git a/src/Concerns/ExportsCategoricalStats.php b/src/Concerns/ExportsCategoricalStats.php index 30f3f93..0c3c591 100644 --- a/src/Concerns/ExportsCategoricalStats.php +++ b/src/Concerns/ExportsCategoricalStats.php @@ -2,69 +2,36 @@ namespace Javaabu\Stats\Concerns; -use Illuminate\Validation\Rule; use Javaabu\Stats\CategoricalStats; -use Javaabu\Stats\Enums\CategoricalModes; -use Javaabu\Stats\Enums\PresetDateRanges; use Javaabu\Stats\Exports\CategoricalStatsExport; use Javaabu\Stats\Http\Requests\CategoricalStatsRequest; -use Javaabu\Stats\Support\ExactDateRange; +use Javaabu\Stats\Support\ExportFileName; +use Symfony\Component\HttpFoundation\StreamedResponse; +/** + * Downloads a categorical stat as CSV, for controllers + */ trait ExportsCategoricalStats { /** - * @param array $filters + * @param array $filters extra fixed filters, e.g. the model of a model-specific page */ - protected function validateCategoricalStatsFilters(CategoricalStatsRequest $request, array $filters = []): void + public function exportCategoricalStats(CategoricalStatsRequest $request, array $filters = [], string $export_title = ''): StreamedResponse { - if ($filters) { - $this->validate($request, [ - 'metric' => [ - 'required', - 'string', - Rule::in(CategoricalStats::allowedMetrics($filters, $request->user())), - ], - ]); - } - } + $metric = $request->metric(); - /** - * @param array $filters - */ - public function exportCategoricalStats( - CategoricalStatsRequest $request, - array $filters = [], - string $export_title = '' - ) { - $this->validateCategoricalStatsFilters($request, $filters); - $filters = array_merge($request->input('filters', []), $filters); - $metric = $request->input('metric'); - $mode = CategoricalModes::from($request->input('mode')); - $categorical_values = $request->input('categorical_values', []); - $range = $request->filled('date_range') - ? PresetDateRanges::from($request->input('date_range')) - : new ExactDateRange($request->input('date_from'), $request->input('date_to')); - $compare_range = null; + abort_unless(in_array($metric, CategoricalStats::allowedMetrics(array_merge($request->fixedFilters(), $filters), $request->statsUser()), true), 403); + + $stats = $request->stats(); + $compare = $request->compareStats(); - if ($request->filled('compare_date_range')) { - $compare_range = PresetDateRanges::from($request->input('compare_date_range')); - } elseif ($request->filled('compare_date_from')) { - $compare_range = new ExactDateRange( - $request->input('compare_date_from'), - $request->input('compare_date_to') - ); - } elseif ($request->input('compare')) { - $compare_range = $range->getPreviousDateRange(); + if ($filters) { + $stats->setFilters(array_merge($stats->getFilters(), $filters)); + $compare?->setFilters(array_merge($compare->getFilters(), $filters)); } - $stats = CategoricalStats::createFromMetric($metric, $range, $filters); - $compare = $compare_range ? CategoricalStats::createFromMetric($metric, $compare_range, $filters) : null; - $exporter = new CategoricalStatsExport($stats, $compare, $mode, $categorical_values); - $document_title = slug_to_title($export_title ?: get_setting('app_name'), '-').' '. - $exporter->getReportTitle().' '. - $stats->formattedDateRange('YYYYMMDD', '-'). - ($compare ? ' '.$compare->formattedDateRange('YYYYMMDD', '-') : ''); + $exporter = new CategoricalStatsExport($stats, $compare, $request->mode(), $request->categoricalValues()); - return $exporter->download($document_title.'.csv'); + return $exporter->download(ExportFileName::make($exporter->getReportTitle(), $stats, $compare, $export_title)); } } diff --git a/src/Concerns/ExportsTimeSeriesStats.php b/src/Concerns/ExportsTimeSeriesStats.php index 0d2b4b8..b265329 100644 --- a/src/Concerns/ExportsTimeSeriesStats.php +++ b/src/Concerns/ExportsTimeSeriesStats.php @@ -1,89 +1,37 @@ validate($request, [ - 'metric' => [ - 'required', - 'string', - Rule::in(TimeSeriesStats::allowedMetrics($filters, $request->user())), - ], - ]); - } - } - - /** - * Export the stat - * - * @param TimeSeriesStatsRequest $request - * @param array $filters - * @param string $export_title - * @return \Illuminate\Http\Response - * @throws ValidationException + * @param array $filters extra fixed filters, e.g. the model of a model-specific page */ - public function exportStats(TimeSeriesStatsRequest $request, array $filters = [], string $export_title = '') + public function exportStats(TimeSeriesStatsRequest $request, array $filters = [], string $export_title = ''): StreamedResponse { - $this->validateStatsFilters($request); + $metric = $request->metric(); - $metric = $request->input('metric'); - $mode = TimeSeriesModes::from($request->input('mode')); + abort_unless(in_array($metric, TimeSeriesStats::allowedMetrics(array_merge($request->fixedFilters(), $filters), $request->statsUser()), true), 403); - if ($request->filled('date_range')) { - $range = PresetDateRanges::from($request->input('date_range')); - } else { - $range = new ExactDateRange( - $request->input('date_from'), - $request->input('date_to'), - ); - } - - $compare_range = null; - - if ($request->filled('compare_date_range')) { - $compare_range = PresetDateRanges::from($request->input('compare_date_range')); - } elseif ($request->filled('compare_date_from')) { - $compare_range = new ExactDateRange( - $request->input('compare_date_from'), - $request->input('compare_date_to'), - ); - } elseif ($request->input('compare')) { - $compare_range = $range->getPreviousDateRange(); - } + $stats = $request->stats(); + $compare = $request->compareStats(); - $stats = TimeSeriesStats::createFromMetric($metric, $range, $filters); - - if ($compare_range) { - $compare = TimeSeriesStats::createFromMetric($metric, $compare_range, $filters); - } else { - $compare = null; + if ($filters) { + $stats->setFilters(array_merge($stats->getFilters(), $filters)); + $compare?->setFilters(array_merge($compare->getFilters(), $filters)); } - $exporter = new TimeSeriesStatsExport($mode, $stats, $compare); - - $title = $exporter->getReportTitle(); - - $document_title = slug_to_title($export_title ?: get_setting('app_name'), '-'). - ' '.$title.' '. - $stats->formattedDateRange('YYYYMMDD', '-'). - ($compare ? ' '.$compare->formattedDateRange('YYYYMMDD', '-') : ''); + $exporter = new TimeSeriesStatsExport($request->mode(), $stats, $compare); - return $exporter->download($document_title.'.csv'); + return $exporter->download(ExportFileName::make($exporter->getReportTitle(), $stats, $compare, $export_title)); } } diff --git a/src/Concerns/HasDateRange.php b/src/Concerns/HasDateRange.php index 63a0a3f..fe081b3 100644 --- a/src/Concerns/HasDateRange.php +++ b/src/Concerns/HasDateRange.php @@ -14,7 +14,9 @@ trait HasDateRange { protected Carbon $date_from; + protected Carbon $date_to; + protected DateRange $date_range; public function getDateFrom(): Carbon diff --git a/src/Concerns/HasFilters.php b/src/Concerns/HasFilters.php index 4e9cf85..8857b14 100644 --- a/src/Concerns/HasFilters.php +++ b/src/Concerns/HasFilters.php @@ -63,7 +63,7 @@ public function ensureAllFiltersAllowed(array $filters, bool $throw_on_fail = fa if ($diff->count()) { throw InvalidFiltersException::filtersNotAllowed($diff, $allowed_filter_names); } - } else { + } else { foreach ($filter_names as $filter) { if (! $allowed_filter_names->contains($filter)) { return false; diff --git a/src/Concerns/InvalidatesStats.php b/src/Concerns/InvalidatesStats.php new file mode 100644 index 0000000..6e407cf --- /dev/null +++ b/src/Concerns/InvalidatesStats.php @@ -0,0 +1,47 @@ + app(StatsCache::class)->forget($model->statsToInvalidate()); + + static::saved($forget); + static::deleted($forget); + + if (method_exists(static::class, 'restored')) { + static::restored($forget); + } + } + + /** + * The metrics whose cached results this model affects + * + * @return list + */ + public function statsToInvalidate(): array + { + return property_exists($this, 'invalidates_stats') ? $this->invalidates_stats : [StatsCache::ALL]; + } +} diff --git a/src/Concerns/IsCategoricalStatsProvider.php b/src/Concerns/IsCategoricalStatsProvider.php index cd0669b..1bd892e 100644 --- a/src/Concerns/IsCategoricalStatsProvider.php +++ b/src/Concerns/IsCategoricalStatsProvider.php @@ -77,7 +77,7 @@ public function searchCategoricalStatsItems(?string $search = null, int $per_pag } elseif ($this->hasNamedScope('search')) { $query->search($search); } else { - $query->where($this->getCategoricalStatsSearchField(), 'like', '%'.$search.'%'); + $query->whereLike($this->getCategoricalStatsSearchField(), '%' . $search . '%'); } } diff --git a/src/Contracts/HasFields.php b/src/Contracts/HasFields.php new file mode 100644 index 0000000..0f5893f --- /dev/null +++ b/src/Contracts/HasFields.php @@ -0,0 +1,37 @@ + + */ + public function getFields(): array; + + /** + * The query parameters the filter reads + * + * @return array + */ + public function getQueryKeys(): array; + + /** + * The filter's value from the input, or null when it doesn't apply + * + * @param array $input + */ + public function resolveValue(array $input): mixed; +} diff --git a/src/Contracts/InteractsWithDateRange.php b/src/Contracts/InteractsWithDateRange.php index e9bb492..8442967 100644 --- a/src/Contracts/InteractsWithDateRange.php +++ b/src/Contracts/InteractsWithDateRange.php @@ -23,7 +23,6 @@ public function getDateTo(): Carbon; */ public function getDateRange(): DateRange; - /** * Get the formatted date from to */ @@ -69,7 +68,6 @@ public function interval(TimeSeriesModes $mode): int; */ public function applyDateFilters(Builder $query): Builder; - /** * Get the filtered query without date filters */ diff --git a/src/Contracts/InteractsWithFilters.php b/src/Contracts/InteractsWithFilters.php index ac17cb3..36ff79a 100644 --- a/src/Contracts/InteractsWithFilters.php +++ b/src/Contracts/InteractsWithFilters.php @@ -1,4 +1,5 @@ resolveCategoryNames($this, $categorical_values); } + public function getLabel(): string + { + return match ($this) { + self::NON_EMPTY => __('Categories with data'), + self::ALL => __('All categories'), + self::SPECIFIC => __('Specific categories'), + }; + } + public function getDescription(): string { return match ($this) { diff --git a/src/Enums/Concerns/HasLabels.php b/src/Enums/Concerns/HasLabels.php new file mode 100644 index 0000000..f12c7a8 --- /dev/null +++ b/src/Enums/Concerns/HasLabels.php @@ -0,0 +1,40 @@ + + */ + public static function getLabels(): array + { + $labels = []; + + foreach (self::cases() as $case) { + $labels[$case->value] = $case->getLabel(); + } + + return $labels; + } + + /** + * The cases as a list of options, in order + * + * @return list + */ + public static function options(): array + { + return array_map(fn (self $case) => [ + 'value' => $case->value, + 'label' => $case->getLabel(), + ], self::cases()); + } +} diff --git a/src/Enums/PresetDateRanges.php b/src/Enums/PresetDateRanges.php index e8b88cd..910dc42 100644 --- a/src/Enums/PresetDateRanges.php +++ b/src/Enums/PresetDateRanges.php @@ -3,15 +3,17 @@ namespace Javaabu\Stats\Enums; use Carbon\Carbon; -use Javaabu\Helpers\Enums\IsEnum; -use Javaabu\Helpers\Enums\NativeEnumsTrait; use Javaabu\Stats\Contracts\DateRange; +use Javaabu\Stats\Enums\Concerns\HasLabels; use Javaabu\Stats\Support\ExactDateRange; use Javaabu\Stats\TimeSeriesStats; -enum PresetDateRanges: string implements IsEnum, DateRange +/** + * Date ranges relative to now, which also give the range before them for comparisons + */ +enum PresetDateRanges: string implements DateRange { - use NativeEnumsTrait; + use HasLabels; case TODAY = 'today'; case YESTERDAY = 'yesterday'; @@ -71,6 +73,26 @@ public function getName(): string return $this->value; } + public function getLabel(): string + { + return match ($this) { + self::TODAY => __('Today'), + self::YESTERDAY => __('Yesterday'), + self::THIS_WEEK => __('This week'), + self::LAST_WEEK => __('Last week'), + self::THIS_MONTH => __('This month'), + self::LAST_MONTH => __('Last month'), + self::THIS_YEAR => __('This year'), + self::LAST_YEAR => __('Last year'), + self::LAST_7_DAYS => __('Last 7 days'), + self::LAST_14_DAYS => __('Last 14 days'), + self::LAST_30_DAYS => __('Last 30 days'), + self::LAST_5_YEARS => __('Last 5 years'), + self::LAST_10_YEARS => __('Last 10 years'), + self::LIFETIME => __('Lifetime'), + }; + } + protected function generatePreviousFromDate(): Carbon { $from_date = $this->getDateFrom(); diff --git a/src/Enums/TimeSeriesModes.php b/src/Enums/TimeSeriesModes.php index 4b4caf8..97d7807 100644 --- a/src/Enums/TimeSeriesModes.php +++ b/src/Enums/TimeSeriesModes.php @@ -4,13 +4,16 @@ use Carbon\Carbon; use Illuminate\Support\Str; -use Javaabu\Helpers\Enums\IsEnum; -use Javaabu\Helpers\Enums\NativeEnumsTrait; +use Javaabu\Stats\Enums\Concerns\HasLabels; +use Javaabu\Stats\Exceptions\StatsException; use Javaabu\Stats\TimeSeriesStats; -enum TimeSeriesModes: string implements IsEnum +/** + * How a time series stat is grouped: one value per hour, day, week, month or year + */ +enum TimeSeriesModes: string { - use NativeEnumsTrait; + use HasLabels; case HOUR = 'hour'; case DAY = 'day'; @@ -18,15 +21,15 @@ enum TimeSeriesModes: string implements IsEnum case MONTH = 'month'; case YEAR = 'year'; - public static function getLabels(): array + public function getLabel(): string { - return [ - self::HOUR->value => __('Hourly'), - self::DAY->value => __('Day'), - self::WEEK->value => __('Week'), - self::MONTH->value => __('Month'), - self::YEAR->value => __('Year'), - ]; + return match ($this) { + self::HOUR => __('Hour'), + self::DAY => __('Day'), + self::WEEK => __('Week'), + self::MONTH => __('Month'), + self::YEAR => __('Year'), + }; } public function diffMethodName(): string @@ -44,37 +47,105 @@ public function queryMethodName(): string return Str::camel($this->value); } + /** + * The number of steps between the two dates, not counting the first + */ public function interval(Carbon $date_from, Carbon $date_to): int { $diff_method = $this->diffMethodName(); - return (int) $date_from->{$diff_method}($date_to, true); + return (int) $this->startOfStep($date_from)->{$diff_method}($this->startOfStep($date_to), true); } + /** + * The display format (moment.js / Carbon isoFormat), from `config('stats.date_formats')` + */ public function getDateFormat(): string { return config('stats.date_formats.' . $this->value); } + /** + * The isoFormat of the keys the database groups by + */ public function getInternalDateFormat(): string { return match ($this) { - self::HOUR => 'YYYY-MM-DD HH:mm', - self::DAY => 'YYYY-MM-DD', - self::WEEK => 'ggggww', - self::MONTH => 'YYYY, MM', + self::HOUR => 'YYYY-MM-DD HH:00', + self::DAY, self::WEEK => 'YYYY-MM-DD', + self::MONTH => 'YYYY-MM', self::YEAR => 'YYYY', }; } - public function getSql(string $date_field): string + /** + * The SQL that turns a date column into the step's key. Weeks are keyed + * by the date they start on, so every database groups them the same way. + * + * @throws StatsException for a database driver that isn't supported + */ + public function getSql(string $date_field, string $driver = 'mysql'): string { + return match ($driver) { + 'mysql', 'mariadb' => $this->mysqlSql($date_field), + 'pgsql' => $this->pgsqlSql($date_field), + 'sqlite' => $this->sqliteSql($date_field), + default => throw StatsException::unsupportedDriver($driver), + }; + } + + protected function mysqlSql(string $date_field): string + { + $week_start = TimeSeriesStats::firstDayOfWeek(); + return match ($this) { - self::HOUR => "DATE_FORMAT($date_field, '%Y-%m-%d %H:00')", - self::DAY => "DATE($date_field)", - self::WEEK => "YEARWEEK($date_field, " . TimeSeriesStats::weekMode() . ")", - self::MONTH => "DATE_FORMAT($date_field, '%Y, %m')", - self::YEAR => "YEAR($date_field)", + self::HOUR => "DATE_FORMAT({$date_field}, '%Y-%m-%d %H:00')", + self::DAY => "DATE_FORMAT({$date_field}, '%Y-%m-%d')", + self::WEEK => "DATE_FORMAT(DATE_SUB({$date_field}, INTERVAL MOD(DAYOFWEEK({$date_field}) - 1 - {$week_start} + 7, 7) DAY), '%Y-%m-%d')", + self::MONTH => "DATE_FORMAT({$date_field}, '%Y-%m')", + self::YEAR => "DATE_FORMAT({$date_field}, '%Y')", + }; + } + + protected function pgsqlSql(string $date_field): string + { + $week_start = TimeSeriesStats::firstDayOfWeek(); + + return match ($this) { + self::HOUR => "to_char({$date_field}, 'YYYY-MM-DD HH24:00')", + self::DAY => "to_char({$date_field}, 'YYYY-MM-DD')", + self::WEEK => "to_char(CAST({$date_field} AS date) - ((CAST(EXTRACT(DOW FROM {$date_field}) AS integer) - {$week_start} + 7) % 7), 'YYYY-MM-DD')", + self::MONTH => "to_char({$date_field}, 'YYYY-MM')", + self::YEAR => "to_char({$date_field}, 'YYYY')", + }; + } + + protected function sqliteSql(string $date_field): string + { + $week_start = TimeSeriesStats::firstDayOfWeek(); + + return match ($this) { + self::HOUR => "strftime('%Y-%m-%d %H:00', {$date_field})", + self::DAY => "strftime('%Y-%m-%d', {$date_field})", + self::WEEK => "date({$date_field}, '-' || ((CAST(strftime('%w', {$date_field}) AS INTEGER) - {$week_start} + 7) % 7) || ' days')", + self::MONTH => "strftime('%Y-%m', {$date_field})", + self::YEAR => "strftime('%Y', {$date_field})", + }; + } + + /** + * The start of the step the date is in + */ + public function startOfStep(Carbon $date): Carbon + { + $date = $date->copy(); + + return match ($this) { + self::HOUR => $date->startOfHour(), + self::DAY => $date->startOfDay(), + self::WEEK => $date->startOfWeek(TimeSeriesStats::firstDayOfWeek())->startOfDay(), + self::MONTH => $date->startOfMonth(), + self::YEAR => $date->startOfYear(), }; } @@ -87,6 +158,9 @@ public function increment(Carbon $date): Carbon return $date->{$method}(); } + /** + * Format a date for display, or as the key the database groups by + */ public function formatDate(Carbon $date, bool $for_display = true): string { $date = $date->copy()->locale(TimeSeriesStats::dateLocale()); @@ -95,7 +169,30 @@ public function formatDate(Carbon $date, bool $for_display = true): string return $date->isoFormat($this->getDateFormat()); } + if ($this === self::WEEK) { + $date = $this->startOfStep($date); + } + return $date->isoFormat($this->getInternalDateFormat()); } + /** + * The step as an ISO 8601 date, for charts with a time axis + */ + public function toIsoDate(Carbon $date): string + { + $date = $this->startOfStep($date); + + return $this === self::HOUR + ? $date->format('Y-m-d\TH:i:s') + : $date->format('Y-m-d'); + } + + /** + * The interval name Onu's TimeSeriesChart understands, if any + */ + public function chartInterval(): ?string + { + return $this === self::YEAR ? null : $this->value; + } } diff --git a/src/Exceptions/InvalidFiltersException.php b/src/Exceptions/InvalidFiltersException.php index 86bb083..de5ba26 100644 --- a/src/Exceptions/InvalidFiltersException.php +++ b/src/Exceptions/InvalidFiltersException.php @@ -3,8 +3,9 @@ namespace Javaabu\Stats\Exceptions; use Illuminate\Support\Collection; +use InvalidArgumentException; -class InvalidFiltersException extends \InvalidArgumentException +class InvalidFiltersException extends InvalidArgumentException { public Collection $unknown_filters; diff --git a/src/Exceptions/StatsException.php b/src/Exceptions/StatsException.php new file mode 100644 index 0000000..576cc5d --- /dev/null +++ b/src/Exceptions/StatsException.php @@ -0,0 +1,28 @@ + */ - protected array $categorical_values; - /** * @param list $categorical_values */ public function __construct( - CategoricalStatsRepository $stats, - ?CategoricalStatsRepository $compare = null, - CategoricalModes $mode = CategoricalModes::NON_EMPTY, - array $categorical_values = [] + protected CategoricalStatsRepository $stats, + protected ?CategoricalStatsRepository $compare = null, + protected CategoricalModes $mode = CategoricalModes::NON_EMPTY, + protected array $categorical_values = [], ) { $this->formatter = new CombinedCategoricalStatsFormatter; - $this->stats = $stats; - $this->compare = $compare; - $this->mode = $mode; - $this->categorical_values = $categorical_values; } public function getReportTitle(): string @@ -56,53 +35,43 @@ public function formattedDateRange(string $format = 'YYYYMMDD', string $separato $date_range = $this->stats->formattedDateRange($format, $separator); if ($compare = $this->compare) { - $date_range .= ' '.$compare->formattedDateRange($format, $separator); + $date_range .= ' ' . $compare->formattedDateRange($format, $separator); } return $date_range; } - public static function beforeSheet(BeforeSheet $event) - { - $sheet = $event->sheet; - /** @var CategoricalStatsExport $export */ - $export = $event->getConcernable(); - - $sheet->append([ - ['# '.str_repeat('-', 40)], - ['# '.$export->getReportTitle()], - ['# '.$export->formattedDateRange()], - ['# '.str_repeat('-', 40)], - [' '], - ]); - } - - /** @return list> */ + /** + * @return list> + */ public function array(): array { return $this->formatter->format($this->stats, $this->compare, $this->mode, $this->categorical_values); } - /** @return list */ + /** + * @return list + */ public function headings(): array { $headings = [$this->stats->getCategoryIdTitle(), $this->stats->getCategoryTitle()]; - if ($this->compare) { - $headings[] = $this->stats->getAggregateFieldLabel().' - '.$this->stats->formattedDateRange('YYYYMMDD', '-'); - $headings[] = $this->stats->getAggregateFieldLabel().' - '.$this->compare->formattedDateRange('YYYYMMDD', '-'); - } else { + if (! $this->compare) { $headings[] = $this->stats->getAggregateFieldLabel(); + + return $headings; } + $headings[] = $this->stats->getAggregateFieldLabel() . ' - ' . $this->stats->formattedDateRange('YYYYMMDD', '-'); + $headings[] = $this->stats->getAggregateFieldLabel() . ' - ' . $this->compare->formattedDateRange('YYYYMMDD', '-'); + return $headings; } /** - * @param array $row * @return list */ - public function map($row): array + public function map(mixed $row): array { $values = [ $row[$this->stats->getCategoryFieldAlias()], @@ -111,25 +80,9 @@ public function map($row): array ]; if ($this->compare) { - $values[] = $row['compare_'.$this->stats->getAggregateFieldName()]; + $values[] = $row['compare_' . $this->stats->getAggregateFieldName()]; } return $values; } - - /** @return array */ - public function columnFormats(): array - { - $formats = [ - 'A' => NumberFormat::FORMAT_TEXT, - 'B' => NumberFormat::FORMAT_TEXT, - 'C' => NumberFormat::FORMAT_NUMBER, - ]; - - if ($this->compare) { - $formats['D'] = NumberFormat::FORMAT_NUMBER; - } - - return $formats; - } } diff --git a/src/Exports/CsvExport.php b/src/Exports/CsvExport.php new file mode 100644 index 0000000..d83846c --- /dev/null +++ b/src/Exports/CsvExport.php @@ -0,0 +1,110 @@ + + */ + abstract public function array(): array; + + /** + * @return list + */ + abstract public function headings(): array; + + /** + * One row of the file + * + * @return list + */ + abstract public function map(mixed $row): array; + + abstract public function getReportTitle(): string; + + abstract public function formattedDateRange(string $format = 'YYYYMMDD', string $separator = '-'): string; + + /** + * The comment lines at the top of the file + * + * @return list + */ + public function preamble(): array + { + return [ + '# ' . str_repeat('-', 40), + '# ' . $this->getReportTitle(), + '# ' . $this->formattedDateRange(), + '# ' . str_repeat('-', 40), + ]; + } + + /** + * Every line of the file, as lists of cells + * + * @return list> + */ + public function lines(): array + { + $lines = array_map(fn (string $line) => [$line], $this->preamble()); + $lines[] = []; + $lines[] = $this->headings(); + + foreach ($this->array() as $row) { + $lines[] = $this->map($row); + } + + return $lines; + } + + /** + * The file's contents + */ + public function toCsv(): string + { + $handle = fopen('php://temp', 'r+'); + + $this->write($handle); + + rewind($handle); + $csv = stream_get_contents($handle); + fclose($handle); + + return $csv; + } + + public function download(string $file_name): StreamedResponse + { + return response()->streamDownload(function () { + $handle = fopen('php://output', 'w'); + + $this->write($handle); + + fclose($handle); + }, $file_name, [ + 'Content-Type' => 'text/csv; charset=UTF-8', + ]); + } + + /** + * @param resource $handle + */ + protected function write($handle): void + { + fwrite($handle, "\u{FEFF}"); + + foreach ($this->lines() as $line) { + fputcsv($handle, $line, escape: ''); + } + } +} diff --git a/src/Exports/TimeSeriesStatsExport.php b/src/Exports/TimeSeriesStatsExport.php index 20e088d..f41d384 100644 --- a/src/Exports/TimeSeriesStatsExport.php +++ b/src/Exports/TimeSeriesStatsExport.php @@ -1,72 +1,36 @@ formatter = new CombinedStatsFormatter(); - $this->mode = $mode; - $this->stats = $stats; - $this->compare = $compare; + public function __construct( + protected TimeSeriesModes $mode, + protected TimeSeriesStatsRepository $stats, + protected ?TimeSeriesStatsRepository $compare = null, + ) { + $this->formatter = new CombinedStatsFormatter; } - /** - * Get the metric - */ public function metric(): string { return $this->stats->metric(); } - /** - * Get the report title - */ public function getReportTitle(): string { return $this->stats->getName(); } - /** - * Get the formatted date range - */ public function formattedDateRange(string $format = 'YYYYMMDD', string $separator = '-'): string { $date_range = $this->stats->formattedDateRange($format, $separator); @@ -79,41 +43,22 @@ public function formattedDateRange(string $format = 'YYYYMMDD', string $separato } /** - * Before sheet event handler - * - * @param BeforeSheet $event + * @return list> */ - public static function beforeSheet(BeforeSheet $event) - { - $sheet = $event->sheet; - /** @var TimeSeriesStatsExport $export */ - $export = $event->getConcernable(); - - $sheet->append([ - ['# '.str_repeat('-', 40)], - ['# '.$export->getReportTitle()], - ['# '.$export->formattedDateRange()], - ['# '.str_repeat('-', 40)], - [' '], - ]); - } - public function array(): array { return $this->formatter->format($this->mode, $this->stats, $this->compare); } /** - * @return array + * @return list */ public function headings(): array { - $headings = [ - $this->mode->getLabel(), - ]; + $headings = [$this->mode->getLabel()]; if ($this->compare) { - $headings[] = 'Date Range'; + $headings[] = __('Date Range'); } $headings[] = $this->stats->getAggregateFieldLabel(); @@ -122,15 +67,11 @@ public function headings(): array } /** - * @param mixed $row - * - * @return array + * @return list */ - public function map($row): array + public function map(mixed $row): array { - $values = []; - - $values[] = $row[$this->mode->value]; + $values = [$row[$this->mode->value]]; if ($this->compare) { $values[] = $row['date_range']; @@ -140,24 +81,4 @@ public function map($row): array return $values; } - - /** - * Format the columns - * - * @return array - */ - public function columnFormats(): array - { - $formats = []; - - $formats['A'] = NumberFormat::FORMAT_TEXT; - - if ($this->compare) { - $formats['B'] = NumberFormat::FORMAT_TEXT; - } - - $formats[$this->compare ? 'C' : 'B'] = NumberFormat::FORMAT_NUMBER; - - return $formats; - } } diff --git a/src/Facades/Stats.php b/src/Facades/Stats.php new file mode 100644 index 0000000..f096953 --- /dev/null +++ b/src/Facades/Stats.php @@ -0,0 +1,30 @@ +callback = $callback; diff --git a/src/Filters/ModelFilter.php b/src/Filters/ModelFilter.php new file mode 100644 index 0000000..8171784 --- /dev/null +++ b/src/Filters/ModelFilter.php @@ -0,0 +1,81 @@ +options(PaymentStatus::class)), + * StatsFilter::from(SelectFilter::make('island_id')->relationship('island', 'name')), + * ]; + * } + * ``` + */ +class ModelFilter implements Filter, HasFields +{ + /** Parameters the stats forms use, which a filter can't be named after */ + public const array RESERVED = [ + 'metric', 'with', 'mode', 'variant', 'format', 'filters', 'categorical_values', + 'date_range', 'date_from', 'date_to', + 'compare', 'compare_to', 'compare_date_range', 'compare_date_from', 'compare_date_to', + 'page', 'per_page', 'search', + ]; + + public function __construct(protected FiltersFilter $filter) + { + foreach ([$filter->getName(), ...$filter->getQueryKeys()] as $name) { + if (in_array($name, self::RESERVED, true)) { + throw StatsException::reservedFilterName($name); + } + } + } + + public function getName(): string + { + return $this->filter->getName(); + } + + public function getFilter(): FiltersFilter + { + return $this->filter; + } + + public function apply(Builder $query, $value, InteractsWithFilters $stat): Builder + { + $value = $this->filter->normalizeValue($value); + + if ($value !== null) { + $this->filter->apply($query, $value); + } + + return $query; + } + + public function getFields(): array + { + return $this->filter->getComponents(); + } + + public function getQueryKeys(): array + { + return $this->filter->getQueryKeys(); + } + + public function resolveValue(array $input): mixed + { + return $this->filter->resolveValue($input); + } +} diff --git a/src/Filters/ScopeFilter.php b/src/Filters/ScopeFilter.php index 48f04de..25bd122 100644 --- a/src/Filters/ScopeFilter.php +++ b/src/Filters/ScopeFilter.php @@ -12,9 +12,9 @@ public function __construct(string $name, string $internal_name = '') { if (! $internal_name) { $internal_name = Str::of($name) - ->snake(' ') - ->camel() - ->toString(); + ->snake(' ') + ->camel() + ->toString(); } parent::__construct($name, $internal_name); diff --git a/src/Filters/StatsFilter.php b/src/Filters/StatsFilter.php index b6d6924..57ad3fc 100644 --- a/src/Filters/StatsFilter.php +++ b/src/Filters/StatsFilter.php @@ -2,6 +2,9 @@ namespace Javaabu\Stats\Filters; +use Closure; +use Javaabu\Filters\Filter; + class StatsFilter { public static function scope(string $name, string $internal_name = ''): ScopeFilter @@ -14,8 +17,16 @@ public static function exact(string $name, string $internal_name = ''): ExactFil return new ExactFilter($name, $internal_name); } - public static function closure(string $name, \Closure $callback): ClosureFilter + public static function closure(string $name, Closure $callback): ClosureFilter { return new ClosureFilter($name, $callback); } + + /** + * A javaabu/filters filter, with fields in the stats form + */ + public static function from(Filter $filter): ModelFilter + { + return new ModelFilter($filter); + } } diff --git a/src/Formatters/Categorical/CombinedCategoricalStatsFormatter.php b/src/Formatters/Categorical/CombinedCategoricalStatsFormatter.php index bb2533f..5e80285 100644 --- a/src/Formatters/Categorical/CombinedCategoricalStatsFormatter.php +++ b/src/Formatters/Categorical/CombinedCategoricalStatsFormatter.php @@ -29,7 +29,7 @@ public function format( $stats->getCategoryFieldAlias() => $id, $stats->getCategoryNameFieldAlias() => $label, $stats->getAggregateFieldName() => $stats_values[$id] ?? 0, - 'compare_'.$stats->getAggregateFieldName() => $compare ? ($compare_values[$id] ?? 0) : null, + 'compare_' . $stats->getAggregateFieldName() => $compare ? ($compare_values[$id] ?? 0) : null, ]; } diff --git a/src/Formatters/TimeSeries/AbstractTimeSeriesStatsFormatter.php b/src/Formatters/TimeSeries/AbstractTimeSeriesStatsFormatter.php index 9682116..6aca8c3 100644 --- a/src/Formatters/TimeSeries/AbstractTimeSeriesStatsFormatter.php +++ b/src/Formatters/TimeSeries/AbstractTimeSeriesStatsFormatter.php @@ -1,4 +1,5 @@ formatDate($compare_next_date, false); $comparison = $compare ? $compare->get($compare_date_str) : null; $compare_data[] = $comparison ? $comparison + 0 : 0; - $label .= '#'.$mode->formatDate($compare_next_date); + $label .= '#' . $mode->formatDate($compare_next_date); $compare_next_date = $mode->increment($compare_next_date); } diff --git a/src/Formatters/TimeSeries/CombinedStatsFormatter.php b/src/Formatters/TimeSeries/CombinedStatsFormatter.php index c3d8ce5..f87dc92 100644 --- a/src/Formatters/TimeSeries/CombinedStatsFormatter.php +++ b/src/Formatters/TimeSeries/CombinedStatsFormatter.php @@ -1,14 +1,13 @@ interval($mode); - //dd($stats_interval); + // dd($stats_interval); $compare_interval = $compare_repo ? $compare_repo->interval($mode) : 0; $max_interval = max($stats_interval, $compare_interval); diff --git a/src/Formatters/TimeSeries/DefaultStatsFormatter.php b/src/Formatters/TimeSeries/DefaultStatsFormatter.php index 21be7c7..229e9ac 100644 --- a/src/Formatters/TimeSeries/DefaultStatsFormatter.php +++ b/src/Formatters/TimeSeries/DefaultStatsFormatter.php @@ -1,4 +1,5 @@ metricField(), + Select::make('mode') + ->label(__('Categories')) + ->options(CategoricalModes::getLabels()) + ->default(CategoricalStats::defaultMode()->value) + ->clearable(false) + ->required(), + ...$this->dateRangeFields(), + CategorySelect::make('categorical_values') + ->label(__('Choose categories')) + ->ajax($this->categoriesUrl(), 'label', 'id') + ->dependsOn('#metric', 'metric') + ->columnSpan(['md' => 2]) + ->visibleWhen('mode', CategoricalModes::SPECIFIC->value) + ->required(), + $this->withField(), + Select::make('variant') + ->label(__('Chart')) + ->options([ + 'bar' => __('Bar'), + 'horizontal' => __('Horizontal bar'), + 'line' => __('Line'), + self::PIE => __('Pie'), + ]) + ->default('bar') + ->clearable(false), + ...$this->filterFields(), + ...$this->fixedFilterFields(), + ]; + } + + protected function categoriesUrl(): string + { + $url = (string) $this->context('categories_url', ''); + $filters = $this->fixedFilters(); + + if ($filters === [] || $url === '') { + return $url; + } + + return $url . (str_contains($url, '?') ? '&' : '?') . http_build_query(['filters' => $filters]); + } + + protected function statFor(string $metric): InteractsWithFilters + { + return CategoricalStats::createFromMetric($metric, filters: $this->fixedFilters()); + } +} diff --git a/src/Forms/Fields/CategorySelect.php b/src/Forms/Fields/CategorySelect.php new file mode 100644 index 0000000..e481d69 --- /dev/null +++ b/src/Forms/Fields/CategorySelect.php @@ -0,0 +1,45 @@ +searchable()->multiple(); + } + + /** + * Load the options for the value of another field + */ + public function dependsOn(string|Closure $parent_selector, string|Closure $filter_field): static + { + $this->parent_selector = $parent_selector; + $this->filter_field = $filter_field; + + return $this; + } + + protected function getFieldProps(): array + { + return array_filter([ + ...parent::getFieldProps(), + 'parent' => $this->evaluate($this->parent_selector), + 'filterField' => $this->evaluate($this->filter_field), + ], fn ($value) => $value !== null); + } +} diff --git a/src/Forms/StatsForm.php b/src/Forms/StatsForm.php new file mode 100644 index 0000000..3a83c6d --- /dev/null +++ b/src/Forms/StatsForm.php @@ -0,0 +1,154 @@ +method('GET') + ->columns(['md' => 2, 'lg' => 4]) + ->submit(__('Generate')); + } + + /** + * The stat of a metric, to read its filters + */ + abstract protected function statFor(string $metric): InteractsWithFilters; + + /** + * @return array + */ + protected function metricOptions(): array + { + return (array) $this->context('metrics', []); + } + + /** + * @return array + */ + protected function fixedFilters(): array + { + return (array) $this->context('filters', []); + } + + protected function metricField(): Select + { + return Select::make('metric') + ->label(__('Stat')) + ->options($this->metricOptions()) + ->searchable() + ->clearable(false) + ->required(); + } + + protected function withField(): Select + { + return Select::make('with') + ->label(__('Plot with')) + ->help(__('Other stats to show on the same chart.')) + ->options($this->metricOptions()) + ->multiple() + ->searchable(); + } + + /** + * @return array + */ + protected function dateRangeFields(): array + { + return [ + Select::make('date_range') + ->label(__('Date range')) + ->options([...PresetDateRanges::getLabels(), TimeSeriesStatsRequest::CUSTOM_RANGE => __('Custom range')]) + ->clearable(false) + ->required(), + Select::make('compare_to') + ->label(__('Compare to')) + ->placeholder(__('No comparison')) + ->options([ + TimeSeriesStatsRequest::COMPARE_PREVIOUS => __('Previous period'), + TimeSeriesStatsRequest::COMPARE_CUSTOM => __('Custom period'), + ]), + Group::make([ + DatePicker::make('date_from')->label(__('From'))->required(), + DatePicker::make('date_to')->label(__('To'))->required(), + ])->columns(2)->columnSpan(['md' => 2])->visibleWhen('date_range', TimeSeriesStatsRequest::CUSTOM_RANGE), + Group::make([ + DatePicker::make('compare_date_from')->label(__('Compare from'))->required(), + DatePicker::make('compare_date_to')->label(__('Compare to date'))->required(), + ])->columns(2)->columnSpan(['md' => 2])->visibleWhen('compare_to', TimeSeriesStatsRequest::COMPARE_CUSTOM), + ]; + } + + /** + * The fields of every filter the stats allow, each shown while a stat + * that has it is selected + * + * @return array + */ + protected function filterFields(): array + { + $fixed = array_keys($this->fixedFilters()); + $filters = []; + $metrics_by_filter = []; + + foreach (array_keys($this->metricOptions()) as $metric) { + foreach ($this->statFor($metric)->allowedFilters() as $filter) { + if (! $filter instanceof HasFields || in_array($filter->getName(), $fixed, true)) { + continue; + } + + $filters[$filter->getName()] ??= $filter; + $metrics_by_filter[$filter->getName()][] = $metric; + } + } + + $components = []; + + foreach ($filters as $name => $filter) { + $components[] = Group::make($filter->getFields()) + ->visibleWhen('metric', $metrics_by_filter[$name]); + } + + return $components; + } + + /** + * Hidden `filters[...]` fields for the fixed filters + * + * @return array