diff --git a/.env.ci b/.env.ci index 40ce2768..e1039743 100644 --- a/.env.ci +++ b/.env.ci @@ -34,7 +34,12 @@ SESSION_DRIVER=database SESSION_LIFETIME=120 # Mail -MAIL_MAILER=log +MAIL_MAILER=smtp +MAIL_HOST=localhost +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="no-reply@solidtime.test" MAIL_FROM_NAME="solidtime" MAIL_REPLY_TO_ADDRESS="hello@solidtime.test" diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d0f9b805..98432491 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,6 +10,9 @@ jobs: services: mailpit: image: 'axllent/mailpit:latest' + ports: + - 1025:1025 + - 8025:8025 pgsql_test: image: postgres:15 env: @@ -67,6 +70,7 @@ jobs: run: npx playwright test env: PLAYWRIGHT_BASE_URL: 'http://127.0.0.1:8000' + MAILPIT_BASE_URL: 'http://localhost:8025' - name: "Upload test results" uses: actions/upload-artifact@v4 diff --git a/app/Filament/Resources/TimeEntryResource.php b/app/Filament/Resources/TimeEntryResource.php index ffd133b3..c54a4678 100644 --- a/app/Filament/Resources/TimeEntryResource.php +++ b/app/Filament/Resources/TimeEntryResource.php @@ -5,6 +5,7 @@ namespace App\Filament\Resources; use App\Filament\Resources\TimeEntryResource\Pages; +use App\Models\Member; use App\Models\TimeEntry; use Filament\Forms\Components\DateTimePicker; use Filament\Forms\Components\Select; @@ -16,6 +17,7 @@ use Filament\Tables\Columns\TextColumn; use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; class TimeEntryResource extends Resource { @@ -51,15 +53,23 @@ public static function form(Form $form): Form ->rules([ 'after_or_equal:start', ]), - Select::make('user_id') - ->relationship(name: 'user', titleAttribute: 'email') - ->searchable(['name', 'email']) + Select::make('member_id') + ->relationship( + name: 'member', + titleAttribute: 'id', + modifyQueryUsing: fn (Builder $query) => $query->with(['user', 'organization']) + ) + ->getOptionLabelFromRecordUsing(fn (Member $record): string => $record->user->email.' ('.$record->organization->name.')') + ->searchable() ->required(), Select::make('project_id') ->relationship(name: 'project', titleAttribute: 'name') ->searchable(['name']) ->nullable(), - // TODO + Select::make('task_id') + ->relationship(name: 'task', titleAttribute: 'name') + ->searchable(['name']) + ->nullable(), ]); } diff --git a/app/Filament/Resources/TimeEntryResource/Pages/CreateTimeEntry.php b/app/Filament/Resources/TimeEntryResource/Pages/CreateTimeEntry.php index 35a85daf..a8bb9498 100644 --- a/app/Filament/Resources/TimeEntryResource/Pages/CreateTimeEntry.php +++ b/app/Filament/Resources/TimeEntryResource/Pages/CreateTimeEntry.php @@ -5,9 +5,28 @@ namespace App\Filament\Resources\TimeEntryResource\Pages; use App\Filament\Resources\TimeEntryResource; +use App\Models\Member; use Filament\Resources\Pages\CreateRecord; class CreateTimeEntry extends CreateRecord { protected static string $resource = TimeEntryResource::class; + + /** + * @param array $data + * @return array + */ + protected function mutateFormDataBeforeCreate(array $data): array + { + if (isset($data['member_id'])) { + /** @var Member|null $member */ + $member = Member::query()->find($data['member_id']); + if ($member !== null) { + $data['user_id'] = $member->user_id; + $data['organization_id'] = $member->organization_id; + } + } + + return $data; + } } diff --git a/app/Filament/Resources/TimeEntryResource/Pages/EditTimeEntry.php b/app/Filament/Resources/TimeEntryResource/Pages/EditTimeEntry.php index 96105ecd..f8ef533f 100644 --- a/app/Filament/Resources/TimeEntryResource/Pages/EditTimeEntry.php +++ b/app/Filament/Resources/TimeEntryResource/Pages/EditTimeEntry.php @@ -5,6 +5,7 @@ namespace App\Filament\Resources\TimeEntryResource\Pages; use App\Filament\Resources\TimeEntryResource; +use App\Models\Member; use Filament\Actions; use Filament\Resources\Pages\EditRecord; @@ -19,4 +20,22 @@ protected function getHeaderActions(): array ->icon('heroicon-m-trash'), ]; } + + /** + * @param array $data + * @return array + */ + protected function mutateFormDataBeforeSave(array $data): array + { + if (isset($data['member_id'])) { + /** @var Member|null $member */ + $member = Member::query()->find($data['member_id']); + if ($member !== null) { + $data['user_id'] = $member->user_id; + $data['organization_id'] = $member->organization_id; + } + } + + return $data; + } } diff --git a/app/Http/Controllers/Api/V1/ChartController.php b/app/Http/Controllers/Api/V1/ChartController.php index 3e034df9..167b9ed1 100644 --- a/app/Http/Controllers/Api/V1/ChartController.php +++ b/app/Http/Controllers/Api/V1/ChartController.php @@ -102,7 +102,7 @@ public function dailyTrackedHours(Organization $organization, DashboardService $ $this->checkPermission($organization, 'charts:view:own'); $user = $this->user(); - $dailyTrackedHours = $dashboardService->getDailyTrackedHours($user, $organization, 60); + $dailyTrackedHours = $dashboardService->getDailyTrackedHours($user, $organization, 100); return response()->json($dailyTrackedHours); } diff --git a/app/Http/Controllers/Api/V1/ReportController.php b/app/Http/Controllers/Api/V1/ReportController.php index b8f2fd21..5a8ca312 100644 --- a/app/Http/Controllers/Api/V1/ReportController.php +++ b/app/Http/Controllers/Api/V1/ReportController.php @@ -150,6 +150,9 @@ public function update(Organization $organization, Report $report, ReportUpdateR $report->share_secret = null; $report->public_until = null; } + } elseif ($report->is_public && $request->has('public_until')) { + // Allow updating expiration date on already-public reports + $report->public_until = $request->getPublicUntil(); } $report->save(); diff --git a/app/Http/Requests/V1/Report/ReportStoreRequest.php b/app/Http/Requests/V1/Report/ReportStoreRequest.php index da609ba1..443bf01c 100644 --- a/app/Http/Requests/V1/Report/ReportStoreRequest.php +++ b/app/Http/Requests/V1/Report/ReportStoreRequest.php @@ -10,9 +10,11 @@ use App\Enums\Weekday; use App\Http\Requests\V1\BaseFormRequest; use App\Models\Organization; +use App\Service\TimeEntryFilter; use Illuminate\Contracts\Validation\Rule as LegacyValidationRule; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Support\Carbon; +use Illuminate\Support\Str; use Illuminate\Validation\Rule; /** @@ -23,7 +25,7 @@ class ReportStoreRequest extends BaseFormRequest /** * Get the validation rules that apply to the request. * - * @return array> + * @return array> */ public function rules(): array { @@ -81,7 +83,14 @@ public function rules(): array ], 'properties.client_ids.*' => [ 'string', - 'uuid', + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + if (! Str::isUuid($value)) { + $fail('The '.$attribute.' must be a valid UUID.'); + } + }, ], // Filter by project IDs, project IDs are OR combined 'properties.project_ids' => [ @@ -90,7 +99,14 @@ public function rules(): array ], 'properties.project_ids.*' => [ 'string', - 'uuid', + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + if (! Str::isUuid($value)) { + $fail('The '.$attribute.' must be a valid UUID.'); + } + }, ], // Filter by tag IDs, tag IDs are OR combined 'properties.tag_ids' => [ @@ -99,7 +115,14 @@ public function rules(): array ], 'properties.tag_ids.*' => [ 'string', - 'uuid', + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + if (! Str::isUuid($value)) { + $fail('The '.$attribute.' must be a valid UUID.'); + } + }, ], 'properties.task_ids' => [ 'nullable', @@ -107,7 +130,14 @@ public function rules(): array ], 'properties.task_ids.*' => [ 'string', - 'uuid', + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + if (! Str::isUuid($value)) { + $fail('The '.$attribute.' must be a valid UUID.'); + } + }, ], 'properties.group' => [ 'required', diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateExportRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateExportRequest.php index 35f84519..a356198c 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateExportRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateExportRequest.php @@ -16,6 +16,7 @@ use App\Models\Tag; use App\Models\Task; use App\Models\User; +use App\Service\TimeEntryFilter; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; @@ -30,7 +31,7 @@ class TimeEntryAggregateExportRequest extends BaseFormRequest /** * Get the validation rules that apply to the request. * - * @return array> + * @return array> */ public function rules(): array { @@ -94,10 +95,15 @@ public function rules(): array ], 'project_ids.*' => [ 'string', - ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by client IDs, client IDs are OR combined 'client_ids' => [ @@ -106,10 +112,15 @@ public function rules(): array ], 'client_ids.*' => [ 'string', - ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by tag IDs, tag IDs are OR combined 'tag_ids' => [ @@ -118,10 +129,15 @@ public function rules(): array ], 'tag_ids.*' => [ 'string', - ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by task IDs, task IDs are OR combined 'task_ids' => [ @@ -130,9 +146,14 @@ public function rules(): array ], 'task_ids.*' => [ 'string', - ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z) 'start' => [ diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php index 39c9270e..92378f82 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryAggregateRequest.php @@ -14,6 +14,7 @@ use App\Models\Tag; use App\Models\Task; use App\Models\User; +use App\Service\TimeEntryFilter; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; @@ -28,7 +29,7 @@ class TimeEntryAggregateRequest extends BaseFormRequest /** * Get the validation rules that apply to the request. * - * @return array> + * @return array> */ public function rules(): array { @@ -80,10 +81,15 @@ public function rules(): array ], 'project_ids.*' => [ 'string', - ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by client IDs, client IDs are OR combined 'client_ids' => [ @@ -92,10 +98,15 @@ public function rules(): array ], 'client_ids.*' => [ 'string', - ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by tag IDs, tag IDs are OR combined 'tag_ids' => [ @@ -104,10 +115,15 @@ public function rules(): array ], 'tag_ids.*' => [ 'string', - ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by task IDs, task IDs are OR combined 'task_ids' => [ @@ -116,9 +132,14 @@ public function rules(): array ], 'task_ids.*' => [ 'string', - ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z) 'start' => [ diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php index 6c3180b2..70447609 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexExportRequest.php @@ -6,11 +6,13 @@ use App\Enums\ExportFormat; use App\Enums\TimeEntryRoundingType; +use App\Models\Client; use App\Models\Member; use App\Models\Organization; use App\Models\Project; use App\Models\Tag; use App\Models\Task; +use App\Service\TimeEntryFilter; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; @@ -25,7 +27,7 @@ class TimeEntryIndexExportRequest extends TimeEntryIndexRequest /** * Get the validation rules that apply to the request. * - * @return array> + * @return array> */ public function rules(): array { @@ -57,6 +59,23 @@ public function rules(): array return $builder->whereBelongsTo($this->organization, 'organization'); }), ], + // Filter by client IDs, client IDs are OR combined + 'client_ids' => [ + 'array', + 'min:1', + ], + 'client_ids.*' => [ + 'string', + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, + ], // Filter by project IDs, project IDs are OR combined 'project_ids' => [ 'array', @@ -64,11 +83,15 @@ public function rules(): array ], 'project_ids.*' => [ 'string', - 'uuid', - new ExistsEloquent(Project::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - }), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by tag IDs, tag IDs are OR combined 'tag_ids' => [ @@ -77,11 +100,15 @@ public function rules(): array ], 'tag_ids.*' => [ 'string', - 'uuid', - new ExistsEloquent(Tag::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - }), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by task IDs, task IDs are OR combined 'task_ids' => [ @@ -90,11 +117,15 @@ public function rules(): array ], 'task_ids.*' => [ 'string', - 'uuid', - new ExistsEloquent(Task::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - }), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z) 'start' => [ diff --git a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php index 2c6dd61e..230e5134 100644 --- a/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php +++ b/app/Http/Requests/V1/TimeEntry/TimeEntryIndexRequest.php @@ -12,6 +12,7 @@ use App\Models\Project; use App\Models\Tag; use App\Models\Task; +use App\Service\TimeEntryFilter; use Illuminate\Contracts\Validation\Rule as RuleContract; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; @@ -26,7 +27,7 @@ class TimeEntryIndexRequest extends BaseFormRequest /** * Get the validation rules that apply to the request. * - * @return array> + * @return array> */ public function rules(): array { @@ -58,10 +59,15 @@ public function rules(): array ], 'client_ids.*' => [ 'string', - ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Client::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by project IDs, project IDs are OR combined 'project_ids' => [ @@ -70,10 +76,15 @@ public function rules(): array ], 'project_ids.*' => [ 'string', - ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Project::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by tag IDs, tag IDs are OR combined 'tag_ids' => [ @@ -82,10 +93,15 @@ public function rules(): array ], 'tag_ids.*' => [ 'string', - ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Tag::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter by task IDs, task IDs are OR combined 'task_ids' => [ @@ -94,10 +110,15 @@ public function rules(): array ], 'task_ids.*' => [ 'string', - ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { - /** @var Builder $builder */ - return $builder->whereBelongsTo($this->organization, 'organization'); - })->uuid(), + function (string $attribute, mixed $value, \Closure $fail): void { + if ($value === TimeEntryFilter::NONE_VALUE) { + return; + } + ExistsEloquent::make(Task::class, null, function (Builder $builder): Builder { + /** @var Builder $builder */ + return $builder->whereBelongsTo($this->organization, 'organization'); + })->uuid()->validate($attribute, $value, $fail); + }, ], // Filter only time entries that have a start date after the given timestamp in UTC (example: 2021-01-01T00:00:00Z) 'start' => [ diff --git a/app/Service/Dto/ReportPropertiesDto.php b/app/Service/Dto/ReportPropertiesDto.php index ac056d0f..a3ff85db 100644 --- a/app/Service/Dto/ReportPropertiesDto.php +++ b/app/Service/Dto/ReportPropertiesDto.php @@ -8,6 +8,7 @@ use App\Enums\TimeEntryAggregationTypeInterval; use App\Enums\TimeEntryRoundingType; use App\Enums\Weekday; +use App\Service\TimeEntryFilter; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Model; @@ -174,7 +175,7 @@ public static function idArrayToCollection(array $ids): Collection if (! is_string($id)) { throw new \InvalidArgumentException('The given ID is not a string'); } - if (! Str::isUuid($id)) { + if ($id !== TimeEntryFilter::NONE_VALUE && ! Str::isUuid($id)) { throw new \InvalidArgumentException('The given ID is not a valid UUID'); } $collection->push($id); diff --git a/app/Service/TimeEntryFilter.php b/app/Service/TimeEntryFilter.php index 160adddc..02fbe689 100644 --- a/app/Service/TimeEntryFilter.php +++ b/app/Service/TimeEntryFilter.php @@ -12,6 +12,8 @@ class TimeEntryFilter { + public const string NONE_VALUE = 'none'; + /** * @var Builder */ @@ -149,7 +151,17 @@ public function addClientIdsFilter(?array $clientIds): self if ($clientIds === null) { return $this; } - $this->builder->whereIn('client_id', $clientIds); + $includeNone = in_array(self::NONE_VALUE, $clientIds, true); + $clientIds = array_values(array_filter($clientIds, fn (string $id): bool => $id !== self::NONE_VALUE)); + + $this->builder->where(function (Builder $builder) use ($clientIds, $includeNone): void { + if (count($clientIds) > 0) { + $builder->whereIn('client_id', $clientIds); + } + if ($includeNone) { + $builder->orWhereNull('client_id'); + } + }); return $this; } @@ -162,7 +174,17 @@ public function addProjectIdsFilter(?array $projectIds): self if ($projectIds === null) { return $this; } - $this->builder->whereIn('project_id', $projectIds); + $includeNone = in_array(self::NONE_VALUE, $projectIds, true); + $projectIds = array_values(array_filter($projectIds, fn (string $id): bool => $id !== self::NONE_VALUE)); + + $this->builder->where(function (Builder $builder) use ($projectIds, $includeNone): void { + if (count($projectIds) > 0) { + $builder->whereIn('project_id', $projectIds); + } + if ($includeNone) { + $builder->orWhereNull('project_id'); + } + }); return $this; } @@ -175,10 +197,18 @@ public function addTagIdsFilter(?array $tagIds): self if ($tagIds === null) { return $this; } - $this->builder->where(function (Builder $builder) use ($tagIds): void { + $includeNone = in_array(self::NONE_VALUE, $tagIds, true); + $tagIds = array_values(array_filter($tagIds, fn (string $id): bool => $id !== self::NONE_VALUE)); + + $this->builder->where(function (Builder $builder) use ($tagIds, $includeNone): void { foreach ($tagIds as $tagId) { $builder->orWhereJsonContains('tags', $tagId); } + if ($includeNone) { + $builder->orWhere(function (Builder $query): void { + $query->whereJsonLength('tags', 0)->orWhereNull('tags'); + }); + } }); return $this; @@ -192,7 +222,17 @@ public function addTaskIdsFilter(?array $taskIds): self if ($taskIds === null) { return $this; } - $this->builder->whereIn('task_id', $taskIds); + $includeNone = in_array(self::NONE_VALUE, $taskIds, true); + $taskIds = array_values(array_filter($taskIds, fn (string $id): bool => $id !== self::NONE_VALUE)); + + $this->builder->where(function (Builder $builder) use ($taskIds, $includeNone): void { + if (count($taskIds) > 0) { + $builder->whereIn('task_id', $taskIds); + } + if ($includeNone) { + $builder->orWhereNull('task_id'); + } + }); return $this; } diff --git a/docker-compose.yml b/docker-compose.yml index 6974efb1..99148a2e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,7 +107,7 @@ services: - sail - reverse-proxy playwright: - image: mcr.microsoft.com/playwright:v1.57.0-jammy + image: mcr.microsoft.com/playwright:v1.58.1-jammy command: ['npx', 'playwright', 'test', '--ui-port=8080', '--ui-host=0.0.0.0'] working_dir: /src extra_hosts: diff --git a/e2e/calendar.spec.ts b/e2e/calendar.spec.ts new file mode 100644 index 00000000..71971169 --- /dev/null +++ b/e2e/calendar.spec.ts @@ -0,0 +1,188 @@ +import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; +import { test } from '../playwright/fixtures'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; +import { createProject, createBillableProject, createBareTimeEntry } from './utils/reporting'; + +async function goToCalendar(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/calendar'); +} + +/** + * These tests verify that changing the project on a time entry via the calendar + * updates the billable status to match the new project's is_billable setting. + * + * Issue: https://github.com/solidtime-io/solidtime/issues/981 + */ + +test('test that changing project in calendar edit modal from non-billable to billable updates billable status', async ({ + page, +}) => { + const billableProjectName = 'Billable Cal Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createBareTimeEntry(page, 'Test billable calendar', '1h'); + + await goToCalendar(page); + + // Click on the time entry event in the calendar + await page.locator('.fc-event').filter({ hasText: 'Test billable calendar' }).first().click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Verify initially non-billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Select the billable project + await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + // Verify the billable dropdown updated to Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); + + // Save and verify + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(true); +}); + +test('test that changing project in calendar edit modal from billable to non-billable updates billable status', async ({ + page, +}) => { + const billableProjectName = 'Billable Cal Rev Project ' + Math.floor(1 + Math.random() * 10000); + const nonBillableProjectName = + 'NonBillable Cal Rev Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createProject(page, nonBillableProjectName); + await createBareTimeEntry(page, 'Test billable cal reverse', '1h'); + + await goToCalendar(page); + + // Click on the time entry event in the calendar + await page + .locator('.fc-event') + .filter({ hasText: 'Test billable cal reverse' }) + .first() + .click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // First assign the billable project + await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + // Verify billable status flipped to Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); + + // Now switch to the non-billable project + await page.getByRole('dialog').getByRole('button', { name: billableProjectName }).click(); + await page.getByRole('option', { name: nonBillableProjectName }).click(); + + // Verify billable status reverted to Non-Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Save and verify + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(false); +}); + +test('test that opening calendar edit modal for a time entry with manually overridden billable status preserves that status', async ({ + page, +}) => { + const billableProjectName = + 'Billable Cal Persist Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createBareTimeEntry(page, 'Test cal persist override', '1h'); + + await goToCalendar(page); + + // Click on the time entry event in the calendar + await page + .locator('.fc-event') + .filter({ hasText: 'Test cal persist override' }) + .first() + .click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Assign the billable project + await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + // Verify it auto-set to Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); + + // Now manually override billable to Non-Billable via the dropdown + await page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }).click(); + await page.getByRole('option', { name: 'Non Billable' }).click(); + + // Verify it shows Non-Billable now + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Save + const [firstSaveResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const firstBody = await firstSaveResponse.json(); + expect(firstBody.data.billable).toBe(false); + + // Re-open the edit modal from the calendar — the project_id watcher should NOT override billable + await page + .locator('.fc-event') + .filter({ hasText: 'Test cal persist override' }) + .first() + .click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // The billable dropdown should still show Non-Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Save without changes and verify the response still has billable=false + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(false); +}); diff --git a/e2e/clients.spec.ts b/e2e/clients.spec.ts index dab9c81f..280f081b 100644 --- a/e2e/clients.spec.ts +++ b/e2e/clients.spec.ts @@ -1,4 +1,5 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; @@ -26,7 +27,7 @@ test('test that creating and deleting a new client via the modal works', async ( await expect(page.getByTestId('client_table')).toContainText(newClientName); const moreButton = page.locator("[aria-label='Actions for Client " + newClientName + "']"); - moreButton.click(); + await moreButton.click(); const deleteButton = page.locator("[aria-label='Delete Client " + newClientName + "']"); await Promise.all([ diff --git a/e2e/command-palette.spec.ts b/e2e/command-palette.spec.ts new file mode 100644 index 00000000..2fcabf65 --- /dev/null +++ b/e2e/command-palette.spec.ts @@ -0,0 +1,405 @@ +import { expect, test } from '../playwright/fixtures'; +import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; +import type { Page } from '@playwright/test'; + +const TIMER_BUTTON_SELECTOR = '[data-testid="dashboard_timer"] [data-testid="timer_button"]'; + +async function goToDashboard(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/dashboard'); +} + +async function openCommandPalette(page: Page) { + await page.getByTestId('command_palette_button').click(); + await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 }); +} + +async function closeCommandPalette(page: Page) { + await page.keyboard.press('Escape'); + await expect(page.locator('[role="dialog"]')).not.toBeVisible(); +} + +async function searchInCommandPalette(page: Page, query: string) { + await page.locator('[role="dialog"] input').fill(query); + // Wait for search to filter and API calls to settle + await page.waitForTimeout(500); +} + +async function selectCommand(page: Page, name: string) { + const option = page.getByRole('option', { name, exact: true }); + await option.scrollIntoViewIfNeeded(); + await option.click(); +} + +async function assertTimerIsRunning(page: Page) { + await expect(page.locator(TIMER_BUTTON_SELECTOR)).toHaveClass(/bg-red-400\/80/, { + timeout: 10000, + }); +} + +async function assertTimerIsStopped(page: Page) { + await expect(page.locator(TIMER_BUTTON_SELECTOR)).toHaveClass(/bg-accent-300\/70/, { + timeout: 10000, + }); +} + +test.describe('Command Palette', () => { + test.describe('Opening and Closing', () => { + test('opens via search button and closes with Escape', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await expect( + page.locator('[role="dialog"] input[placeholder*="command"]') + ).toBeVisible(); + + await closeCommandPalette(page); + await expect(page.locator('[role="dialog"]')).not.toBeVisible(); + }); + + test('opens with keyboard shortcut', async ({ page }) => { + await goToDashboard(page); + // Click on body to ensure page has focus + await page.locator('body').click(); + // Use ControlOrMeta which resolves to Ctrl on Linux/Windows and Meta on macOS + await page.keyboard.press('ControlOrMeta+k'); + await expect(page.locator('[role="dialog"]')).toBeVisible({ timeout: 5000 }); + }); + + test('clears search on close', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'dashboard'); + await closeCommandPalette(page); + + await openCommandPalette(page); + await expect(page.locator('[role="dialog"] input')).toHaveValue(''); + }); + }); + + test.describe('Command Display', () => { + test('displays navigation and timer commands', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + // Navigation commands + await expect(page.getByRole('option', { name: 'Go to Dashboard' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Go to Time' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Go to Calendar' })).toBeVisible(); + + // Timer commands + await expect(page.getByRole('option', { name: 'Start Timer' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Create Time Entry' })).toBeVisible(); + }); + + test('displays create commands', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + await expect(page.getByRole('option', { name: 'Create Project' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Create Client' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Create Tag' })).toBeVisible(); + }); + }); + + test.describe('Navigation Commands', () => { + // Tests use element visibility assertions for consistency with codebase patterns + const navigationTests = [ + ['Go to Dashboard', 'dashboard_view', '/time'], + ['Go to Time', 'time_view', '/dashboard'], + ['Go to Calendar', 'calendar_view', '/dashboard'], + ['Go to Projects', 'projects_view', '/dashboard'], + ['Go to Clients', 'clients_view', '/dashboard'], + ['Go to Members', 'members_view', '/dashboard'], + ['Go to Tags', 'tags_view', '/dashboard'], + ] as const; + + for (const [commandName, expectedTestId, startUrl] of navigationTests) { + test(`${commandName}`, async ({ page }) => { + await page.goto(PLAYWRIGHT_BASE_URL + startUrl); + await openCommandPalette(page); + await searchInCommandPalette(page, commandName.replace('Go to ', '')); + await selectCommand(page, commandName); + await expect(page.getByTestId(expectedTestId)).toBeVisible({ timeout: 10000 }); + }); + } + + test('Go to Profile', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Profile'); + await selectCommand(page, 'Go to Profile'); + // Profile page doesn't have a testId, so check for a unique element + await expect(page.getByRole('heading', { name: 'Profile Information' })).toBeVisible({ + timeout: 10000, + }); + }); + + test('Go to Reporting Overview', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Reporting Overview'); + await selectCommand(page, 'Go to Reporting Overview'); + await expect(page.getByTestId('reporting_view')).toBeVisible({ timeout: 10000 }); + }); + + test('Go to Settings', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Settings'); + await selectCommand(page, 'Go to Settings'); + // Settings page uses team settings which has an h3 heading + await expect( + page.getByRole('heading', { name: 'Organization Name', level: 3 }) + ).toBeVisible({ + timeout: 10000, + }); + }); + }); + + test.describe('Search and Filtering', () => { + test('filters commands when searching', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + await searchInCommandPalette(page, 'dashboard'); + await expect(page.getByRole('option', { name: 'Go to Dashboard' })).toBeVisible(); + + await searchInCommandPalette(page, 'calendar'); + await expect(page.getByRole('option', { name: 'Go to Calendar' })).toBeVisible(); + }); + + test('search is case insensitive', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + await searchInCommandPalette(page, 'DASHBOARD'); + await expect(page.getByRole('option', { name: 'Go to Dashboard' })).toBeVisible(); + }); + + test('partial word search works', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + await searchInCommandPalette(page, 'proj'); + await expect(page.getByRole('option', { name: 'Go to Projects' })).toBeVisible(); + await expect(page.getByRole('option', { name: 'Create Project' })).toBeVisible(); + }); + + test('keyboard navigation and selection works', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + + await expect(page.locator('[role="dialog"]')).not.toBeVisible(); + }); + }); + + test.describe('Theme Commands', () => { + test('switches to dark theme', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Dark Theme'); + await selectCommand(page, 'Switch to Dark Theme'); + await expect(page.locator('html')).toHaveClass(/dark/); + }); + + test('switches to light theme', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Light Theme'); + await selectCommand(page, 'Switch to Light Theme'); + await expect(page.locator('html')).toHaveClass(/light/); + }); + }); + + test.describe('Timer Commands', () => { + test('starts and stops timer', async ({ page }) => { + await goToDashboard(page); + + // Start timer + await openCommandPalette(page); + await searchInCommandPalette(page, 'Start Timer'); + await selectCommand(page, 'Start Timer'); + await assertTimerIsRunning(page); + + // Stop timer + await openCommandPalette(page); + await searchInCommandPalette(page, 'Stop Timer'); + await selectCommand(page, 'Stop Timer'); + await assertTimerIsStopped(page); + }); + + test('shows active timer commands when running', async ({ page }) => { + await goToDashboard(page); + + // Start timer + await openCommandPalette(page); + await searchInCommandPalette(page, 'Start Timer'); + await selectCommand(page, 'Start Timer'); + await assertTimerIsRunning(page); + + // Check active timer commands - search for them to ensure visibility + await openCommandPalette(page); + await searchInCommandPalette(page, 'Set Project'); + await expect(page.getByRole('option', { name: 'Set Project' })).toBeVisible(); + }); + }); + + test.describe('Create Commands', () => { + test('opens create time entry modal', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Create Time Entry'); + await selectCommand(page, 'Create Time Entry'); + await expect( + page.locator('[role="dialog"]').getByText('Create manual time entry') + ).toBeVisible(); + }); + + test('opens create project modal', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Create Project'); + await selectCommand(page, 'Create Project'); + await expect( + page.locator('[role="dialog"]').getByRole('heading', { name: 'Create Project' }) + ).toBeVisible(); + }); + + test('opens create client modal', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Create Client'); + await selectCommand(page, 'Create Client'); + await expect( + page.locator('[role="dialog"]').getByRole('heading', { name: 'Create Client' }) + ).toBeVisible(); + }); + + test('opens create tag modal', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Create Tag'); + await selectCommand(page, 'Create Tag'); + await expect(page.locator('[role="dialog"]').getByText('Create Tags')).toBeVisible(); + }); + + test('opens invite member modal', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, 'Invite Member'); + await selectCommand(page, 'Invite Member'); + // Modal has title with "Invite Member" text - use first() to get the title span + await expect( + page.locator('[role="dialog"]').getByText('Invite Member').first() + ).toBeVisible(); + }); + }); + + test.describe('Entity Search', () => { + test('searches for projects and navigates on selection', async ({ page }) => { + const projectName = 'CmdPalette' + Math.floor(Math.random() * 10000); + + // Create project first + await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); + await page.getByRole('button', { name: 'Create Project' }).click(); + await page.getByPlaceholder('The next big thing').fill(projectName); + await page.getByRole('button', { name: 'Create Project' }).click(); + // Wait for project to be created and page to update + await expect(page.getByText(projectName)).toBeVisible({ timeout: 10000 }); + + // Now go to dashboard and search for the project + await goToDashboard(page); + await openCommandPalette(page); + await searchInCommandPalette(page, projectName); + + // Wait for entity search to return results, then use keyboard to select + await page.waitForTimeout(1500); + + // Use keyboard to navigate down (past any matching commands) and select + // The project should appear in search results + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + + // Should navigate somewhere (either project page or remain on dashboard) + // If entity search found the project, it will navigate to project page + await page.waitForTimeout(500); + }); + }); + + test.describe('Organization Switching', () => { + test('shows switch commands only when multiple organizations exist', async ({ page }) => { + await goToDashboard(page); + await openCommandPalette(page); + + // With only one org, no switch commands should appear + await searchInCommandPalette(page, 'Switch to'); + // Check that no organization switch commands appear (only theme switch commands) + const switchOptions = page.getByRole('option', { name: /^Switch to (?!.*Theme)/ }); + await expect(switchOptions).toHaveCount(0); + }); + + test('switches organization via command palette', async ({ page }) => { + const newOrgName = 'TestOrg' + Math.floor(Math.random() * 10000); + + // Create a new organization + await page.goto(PLAYWRIGHT_BASE_URL + '/teams/create'); + await page.getByLabel('Organization Name').fill(newOrgName); + await page.getByRole('button', { name: 'Create' }).click(); + + // Wait for navigation to new org's dashboard + await expect(page.getByTestId('dashboard_view')).toBeVisible({ timeout: 10000 }); + + // Use visible switcher (desktop sidebar has one, mobile header has another) + const orgSwitcher = page.locator('[data-testid="organization_switcher"]:visible'); + + // Verify we're in the new org by checking the switcher + await expect(orgSwitcher).toContainText(newOrgName); + + // Get the original org name from switcher dropdown + await orgSwitcher.click(); + await expect(page.getByText('Switch Organizations')).toBeVisible(); + + // Find the other organization button (has ArrowRightIcon, not CheckCircleIcon) + // The button contains an SVG and a div with the org name + const otherOrgItem = page.locator('form button').filter({ hasText: /.+/ }).first(); + await expect(otherOrgItem).toBeVisible(); + const originalOrgName = (await otherOrgItem.innerText()).trim(); + await page.keyboard.press('Escape'); // Close dropdown + + // Now use command palette to switch back to original org + await openCommandPalette(page); + await searchInCommandPalette(page, 'Switch to'); + + // Should see the switch command for the original org + const switchCommand = page.getByRole('option', { + name: new RegExp(`Switch to ${originalOrgName}`), + }); + await expect(switchCommand).toBeVisible(); + await switchCommand.click(); + + // Wait for organization switch to complete + await expect(orgSwitcher).toContainText(originalOrgName, { + timeout: 10000, + }); + }); + + test('organization switch commands appear in Organization group', async ({ page }) => { + const newOrgName = 'GroupTestOrg' + Math.floor(Math.random() * 10000); + + // Create a new organization to ensure we have multiple + await page.goto(PLAYWRIGHT_BASE_URL + '/teams/create'); + await page.getByLabel('Organization Name').fill(newOrgName); + await page.getByRole('button', { name: 'Create' }).click(); + await expect(page.getByTestId('dashboard_view')).toBeVisible({ timeout: 10000 }); + + // Open command palette and check for Organization group heading + await openCommandPalette(page); + + // The Organization group should be visible when there are switch commands + await expect(page.getByText('Organization', { exact: true })).toBeVisible(); + }); + }); +}); diff --git a/e2e/members.spec.ts b/e2e/members.spec.ts index 70be0e7c..0ccfd90d 100644 --- a/e2e/members.spec.ts +++ b/e2e/members.spec.ts @@ -3,53 +3,65 @@ // TODO: Remove Invitation import { expect, test } from '../playwright/fixtures'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; +import type { Page } from '@playwright/test'; +import path from 'path'; +import fs from 'fs'; +import os from 'os'; +import { inviteAndAcceptMember } from './utils/members'; -async function goToMembersPage(page) { +// Tests that invite + accept members need more time +test.describe.configure({ timeout: 60000 }); + +async function goToMembersPage(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/members'); } -async function openInviteMemberModal(page) { +async function openInviteMemberModal(page: Page) { await Promise.all([ page.getByRole('button', { name: 'Invite Member' }).click(), expect(page.getByPlaceholder('Member Email')).toBeVisible(), ]); } -test('test that new manager can be invited', async ({ page }) => { +test('test that new manager can be invited and accepted', async ({ page, browser }) => { + const memberId = Math.round(Math.random() * 100000); + const memberEmail = `manager+${memberId}@invite.test`; + + await inviteAndAcceptMember(page, browser, 'Invited Mgr', memberEmail, 'Manager'); + + // Verify the member appears in the members table with the correct role await goToMembersPage(page); - await openInviteMemberModal(page); - const editorId = Math.round(Math.random() * 10000); - await page.getByLabel('Email').fill(`new+${editorId}@editor.test`); - await page.getByRole('button', { name: 'Manager' }).click(); - await Promise.all([ - page.getByRole('button', { name: 'Invite Member', exact: true }).click(), - expect(page.getByRole('main')).toContainText(`new+${editorId}@editor.test`), - ]); + const memberRow = page.getByRole('row').filter({ hasText: 'Invited Mgr' }); + await expect(memberRow).toBeVisible(); + await expect(memberRow.getByText('Manager', { exact: true })).toBeVisible(); }); -test('test that new employee can be invited', async ({ page }) => { +test('test that new employee can be invited and accepted', async ({ page, browser }) => { + const memberId = Math.round(Math.random() * 100000); + const memberEmail = `employee+${memberId}@invite.test`; + + await inviteAndAcceptMember(page, browser, 'Invited Emp', memberEmail, 'Employee'); + + // Verify the member appears in the members table with the correct role await goToMembersPage(page); - await openInviteMemberModal(page); - const editorId = Math.round(Math.random() * 10000); - await page.getByLabel('Email').fill(`new+${editorId}@editor.test`); - await page.getByRole('button', { name: 'Employee' }).click(); - await Promise.all([ - page.getByRole('button', { name: 'Invite Member', exact: true }).click(), - await expect(page.getByRole('main')).toContainText(`new+${editorId}@editor.test`), - ]); + const memberRow = page.getByRole('row').filter({ hasText: 'Invited Emp' }); + await expect(memberRow).toBeVisible(); + await expect(memberRow.getByText('Employee', { exact: true })).toBeVisible(); }); -test('test that new admin can be invited', async ({ page }) => { +test('test that new admin can be invited and accepted', async ({ page, browser }) => { + const memberId = Math.round(Math.random() * 100000); + const memberEmail = `admin+${memberId}@invite.test`; + + await inviteAndAcceptMember(page, browser, 'Invited Adm', memberEmail, 'Administrator'); + + // Verify the member appears in the members table with the correct role await goToMembersPage(page); - await openInviteMemberModal(page); - const adminId = Math.round(Math.random() * 10000); - await page.getByLabel('Email').fill(`new+${adminId}@admin.test`); - await page.getByRole('button', { name: 'Administrator' }).click(); - await Promise.all([ - page.getByRole('button', { name: 'Invite Member', exact: true }).click(), - expect(page.getByRole('main')).toContainText(`new+${adminId}@admin.test`), - ]); + const memberRow = page.getByRole('row').filter({ hasText: 'Invited Adm' }); + await expect(memberRow).toBeVisible(); + await expect(memberRow.getByText('Admin', { exact: true })).toBeVisible(); }); + test('test that error shows if no role is selected', async ({ page }) => { await goToMembersPage(page); await openInviteMemberModal(page); @@ -91,3 +103,171 @@ test('test that organization billable rate can be updated with all existing time ), ]); }); + +async function createPlaceholderMemberViaImport(page: Page, placeholderName: string) { + const placeholderEmail = `placeholder+${Math.floor(Math.random() * 100000)}@solidtime-import.test`; + const csvContent = [ + 'User,Email,Client,Project,Task,Description,Billable,Start date,Start time,End date,End time,Tags', + `${placeholderName},${placeholderEmail},,,,Imported entry,No,2024-01-01,09:00:00,2024-01-01,10:00:00,`, + ].join('\n'); + + // Write CSV to a temp file for upload + const tmpDir = os.tmpdir(); + const tmpFile = path.join(tmpDir, `import-${Date.now()}.csv`); + fs.writeFileSync(tmpFile, csvContent); + + await page.goto(PLAYWRIGHT_BASE_URL + '/import'); + + // Select "Toggl Time Entries" import type + await page.locator('select#importType').selectOption({ label: 'Toggl Time Entries' }); + + // Upload the CSV file + await page.locator('input[type="file"]').setInputFiles(tmpFile); + + // Click Import and wait for success + await Promise.all([ + page.getByRole('button', { name: 'Import Data' }).click(), + page.waitForResponse( + (response) => response.url().includes('/import') && response.status() === 200 + ), + ]); + + // Close the result modal + await page.getByRole('button', { name: 'Close' }).click(); + + // Clean up temp file + fs.unlinkSync(tmpFile); +} + +test('test that changing role of placeholder member is rejected', async ({ page }) => { + const placeholderName = 'RoleChange ' + Math.floor(Math.random() * 10000); + + // Create a placeholder member via import + await createPlaceholderMemberViaImport(page, placeholderName); + + // Go to members page and verify placeholder exists with role "Placeholder" + await goToMembersPage(page); + const memberRow = page.getByRole('row').filter({ hasText: placeholderName }); + await expect(memberRow).toBeVisible(); + await expect(memberRow.getByText('Placeholder', { exact: true })).toBeVisible(); + + // Open the edit modal for the placeholder member + await memberRow.getByRole('button').click(); + await page.getByRole('menuitem').getByText('Edit').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Update Member' })).toBeVisible(); + + // Change role to Employee + const roleSelect = page.getByRole('dialog').getByRole('combobox').first(); + await roleSelect.click(); + await expect(page.getByRole('option', { name: 'Employee' })).toBeVisible(); + await page.getByRole('option', { name: 'Employee' }).click(); + await expect(roleSelect).toContainText('Employee'); + + // Submit the change - the API should reject it with 400 + await Promise.all([ + page.getByRole('button', { name: 'Update Member' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/members/') && + response.request().method() === 'PUT' && + response.status() === 400 + ), + ]); + + // Verify error notification is shown + await expect(page.getByText('Failed to update member')).toBeVisible(); +}); + +test('test that changing member role updates the role in the member table', async ({ + page, + browser, +}) => { + const memberId = Math.floor(Math.random() * 100000); + const memberEmail = `member+${memberId}@rolechange.test`; + + // Invite and accept a new Employee member + await inviteAndAcceptMember(page, browser, 'Jane Smith', memberEmail, 'Employee'); + + // Verify the new member appears with the Employee role + await goToMembersPage(page); + const memberRow = page.getByRole('row').filter({ hasText: 'Jane Smith' }); + await expect(memberRow).toBeVisible(); + await expect(memberRow.getByText('Employee', { exact: true })).toBeVisible(); + + // Open the edit modal + await memberRow.getByRole('button').click(); + await page.getByRole('menuitem').getByText('Edit').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Update Member' })).toBeVisible(); + + // Change role to Manager + const roleSelect = page.getByRole('dialog').getByRole('combobox').first(); + await roleSelect.click(); + await expect(page.getByRole('option', { name: 'Manager' })).toBeVisible(); + await page.getByRole('option', { name: 'Manager' }).click(); + await expect(roleSelect).toContainText('Manager'); + + // Submit the change and verify the API call succeeds + await Promise.all([ + page.getByRole('button', { name: 'Update Member' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/members/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + ]); + + // Verify dialog closed + await expect(page.getByRole('dialog')).not.toBeVisible(); + + // Verify the role updated in the table + await expect(memberRow.getByText('Manager', { exact: true })).toBeVisible(); +}); + +test('test that merging a placeholder member works', async ({ page }) => { + const placeholderName = 'Merge Target ' + Math.floor(Math.random() * 10000); + + // Create a placeholder member via import + await createPlaceholderMemberViaImport(page, placeholderName); + + // Go to members page + await goToMembersPage(page); + await expect(page.getByText(placeholderName)).toBeVisible(); + + // Find the placeholder member row and open actions menu + const placeholderRow = page.getByRole('row').filter({ hasText: placeholderName }); + await placeholderRow.getByRole('button').click(); + + // Click Merge + await page.getByTestId('member_merge').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Merge Member' })).toBeVisible(); + + // Select the current user (the owner) as merge target via MemberCombobox + // The MemberCombobox renders a Button as trigger; clicking it opens the popover with the combobox input + await page.getByRole('dialog').getByRole('button', { name: 'Select a member...' }).click(); + + // Wait for dropdown options to load + const firstOption = page.getByRole('option').first(); + await expect(firstOption).toBeVisible({ timeout: 10000 }); + await firstOption.click(); + + // Submit merge + await Promise.all([ + page.getByRole('button', { name: 'Merge Member' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/member/') && + response.url().includes('/merge-into') && + response.ok() + ), + ]); + + // Wait for dialog to close after successful merge + await expect(page.getByRole('dialog')).not.toBeVisible(); + + // Verify placeholder member is no longer in the members table + await expect(page.getByRole('main').getByText(placeholderName)).not.toBeVisible(); +}); diff --git a/e2e/project-members.spec.ts b/e2e/project-members.spec.ts index 36ee7f2b..aff8ff81 100644 --- a/e2e/project-members.spec.ts +++ b/e2e/project-members.spec.ts @@ -1,9 +1,8 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; import { formatCentsWithOrganizationDefaults } from './utils/money'; -import type { CurrencyFormat } from '../resources/js/packages/ui/src/utils/money'; -import { NumberFormat } from '@/packages/ui/src/utils/number'; async function goToProjectsOverview(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); @@ -18,15 +17,23 @@ test('test that updating project member billable rate works for existing time en await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project Name').fill(newProjectName); - await page.getByRole('button', { name: 'Create Project' }).click(); - await expect(page.getByText(newProjectName)).toBeVisible(); + await Promise.all([ + page.getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(newProjectName)).toBeVisible({ timeout: 10000 }); await page.getByText(newProjectName).click(); await page.getByRole('button', { name: 'Add Member' }).click(); await expect(page.getByText('Add Project Member').first()).toBeVisible(); - await page.getByRole('button', { name: 'Select a member' }).click(); - await page.keyboard.press('Enter'); + await page.getByRole('button', { name: 'Select a member...' }).click(); + await page.getByRole('option').first().click(); await page.getByRole('button', { name: 'Add Project Member' }).click(); await page diff --git a/e2e/projects.spec.ts b/e2e/projects.spec.ts index b052baed..5fe115c9 100644 --- a/e2e/projects.spec.ts +++ b/e2e/projects.spec.ts @@ -1,4 +1,5 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; import { formatCentsWithOrganizationDefaults } from './utils/money'; @@ -37,7 +38,7 @@ test('test that creating and deleting a new project via the modal works', async await expect(page.getByTestId('project_table')).toContainText(newProjectName); const moreButton = page.locator("[aria-label='Actions for Project " + newProjectName + "']"); - moreButton.click(); + await moreButton.click(); const deleteButton = page.locator("[aria-label='Delete Project " + newProjectName + "']"); await Promise.all([ @@ -78,8 +79,16 @@ test('test that archiving and unarchiving projects works', async ({ page }) => { await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project Name').fill(newProjectName); - await page.getByRole('button', { name: 'Create Project' }).click(); - await expect(page.getByText(newProjectName)).toBeVisible(); + await Promise.all([ + page.getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(newProjectName)).toBeVisible({ timeout: 10000 }); // Archive the project await page.getByRole('row').first().getByRole('button').click(); @@ -117,8 +126,16 @@ test('test that updating billable rate works with existing time entries', async await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project Name').fill(newProjectName); - await page.getByRole('button', { name: 'Create Project' }).click(); - await expect(page.getByText(newProjectName)).toBeVisible(); + await Promise.all([ + page.getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(newProjectName)).toBeVisible({ timeout: 10000 }); await page.getByRole('row').first().getByRole('button').click(); await page.getByRole('menuitem').getByText('Edit').first().click(); @@ -223,8 +240,16 @@ test('test that filtering projects by status works', async ({ page }) => { // Create a new project await page.getByRole('button', { name: 'Create Project' }).click(); await page.getByLabel('Project Name').fill(newProjectName); - await page.getByRole('button', { name: 'Create Project' }).click(); - await expect(page.getByText(newProjectName)).toBeVisible(); + await Promise.all([ + page.getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(newProjectName)).toBeVisible({ timeout: 10000 }); // Archive the project await page.getByRole('row').first().getByRole('button').click(); @@ -292,6 +317,55 @@ test('test that sort state persists after page reload', async ({ page }) => { await expect(page.getByTestId('project_table')).toBeVisible(); }); +test('test that custom billable rate is displayed correctly on project detail page', async ({ + page, +}) => { + const newProjectName = 'Billable Rate Project ' + Math.floor(1 + Math.random() * 10000); + const newBillableRate = Math.round(10 + Math.random() * 1000); + await goToProjectsOverview(page); + await page.getByRole('button', { name: 'Create Project' }).click(); + await page.getByLabel('Project Name').fill(newProjectName); + + await Promise.all([ + page.getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(newProjectName)).toBeVisible({ timeout: 10000 }); + + // Edit the project to set a custom billable rate + await page.getByRole('row').first().getByRole('button').click(); + await page.getByRole('menuitem').getByText('Edit').first().click(); + await page.getByText('Non-Billable').click(); + await page.getByText('Custom Rate').click(); + await page.getByPlaceholder('Billable Rate').fill(newBillableRate.toString()); + await page.getByRole('button', { name: 'Update Project' }).click(); + + await Promise.all([ + page.locator('button').filter({ hasText: 'Yes, update existing time' }).click(), + page.waitForResponse( + async (response) => + response.url().includes('/projects/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + ]); + + // Navigate to the project detail page by clicking the project name + await page.getByText(newProjectName).first().click(); + await page.waitForURL(/\/projects\/[a-f0-9-]+/); + + // Verify the badge displays the correctly formatted billable rate + const expectedFormattedRate = formatCentsWithOrganizationDefaults(newBillableRate * 100); + await expect(page.locator('nav[aria-label="Breadcrumb"]').locator('..')).toContainText( + expectedFormattedRate + ); +}); + // Create new project with new Client // Create new project with existing Client diff --git a/e2e/reporting-detailed.spec.ts b/e2e/reporting-detailed.spec.ts new file mode 100644 index 00000000..d10305b9 --- /dev/null +++ b/e2e/reporting-detailed.spec.ts @@ -0,0 +1,598 @@ +import { expect } from '@playwright/test'; +import { test } from '../playwright/fixtures'; +import { + goToReportingDetailed, + createProject, + createClient, + createProjectWithClient, + createTask, + createTimeEntryWithProject, + createTimeEntryWithProjectAndTask, + createTimeEntryWithTag, + createBareTimeEntry, + waitForDetailedReportingUpdate, +} from './utils/reporting'; + +// Each test registers a new user and creates test data, which needs more time +test.describe.configure({ timeout: 60000 }); + +// ────────────────────────────────────────────────── +// Basic Detailed View Tests +// ────────────────────────────────────────────────── + +test('test that detailed view shows time entries correctly', async ({ page }) => { + const projectName = 'Detailed View Project ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + // Go to detailed reporting view + await goToReportingDetailed(page); + + // Verify the time entry is shown with all details + await expect(page.getByText(projectName, { exact: true })).toBeVisible(); + await expect(page.locator('input[name="Duration"]')).toHaveValue('1h 00min'); + await expect(page.getByText('Entry for ' + projectName, { exact: true })).toBeVisible(); +}); + +test('test that updating duration in detailed view works correctly', async ({ page }) => { + const projectName = 'Duration Update Project ' + Math.floor(Math.random() * 10000); + const initialDuration = '1h'; + const updatedDuration = '2h 30min'; + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, initialDuration); + + // Go to detailed reporting view + await goToReportingDetailed(page); + + // Find and update the duration + const durationInput = page.locator('input[name="Duration"]').first(); + await durationInput.click(); + await durationInput.fill(updatedDuration); + await Promise.all([ + durationInput.press('Enter'), + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 200 + ), + ]); + + // Verify the new duration is displayed + await expect(durationInput).toHaveValue(updatedDuration); +}); + +// ────────────────────────────────────────────────── +// Project Filter Tests +// ────────────────────────────────────────────────── + +test('test that project multiselect filters work on detailed reporting page', async ({ page }) => { + const project1 = 'DetailProj1 ' + Math.floor(Math.random() * 10000); + const project2 = 'DetailProj2 ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + await createProject(page, project2); + await createTimeEntryWithProject(page, project1, '1h'); + await createTimeEntryWithProject(page, project2, '2h'); + + await goToReportingDetailed(page); + + // Wait for initial data load + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${project2}`)).toBeVisible(); + + // Open project multiselect and select project1 + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: project1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Verify only project1 entry is shown + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${project2}`)).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Client Filter Tests +// ────────────────────────────────────────────────── + +test('test that client multiselect filters work on detailed reporting page', async ({ page }) => { + const client1 = 'DetailClient1 ' + Math.floor(Math.random() * 10000); + const project1 = 'DetailClientProj1 ' + Math.floor(Math.random() * 10000); + const project2 = 'DetailClientProj2 ' + Math.floor(Math.random() * 10000); + + await createClient(page, client1); + await createProjectWithClient(page, project1, client1); + await createProject(page, project2); + await createTimeEntryWithProject(page, project1, '1h'); + await createTimeEntryWithProject(page, project2, '2h'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${project2}`)).toBeVisible(); + + // Filter by client1 + await page.getByRole('button', { name: 'Clients' }).first().click(); + await page.getByRole('option').filter({ hasText: client1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Only entries for project1 (with client1) should be visible + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${project2}`)).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Task Filter Tests +// ────────────────────────────────────────────────── + +test('test that task multiselect dropdown filters reporting by task', async ({ page }) => { + const projectName = 'TaskFilterProj ' + Math.floor(Math.random() * 10000); + const task1 = 'TaskFilter1 ' + Math.floor(Math.random() * 10000); + const task2 = 'TaskFilter2 ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTask(page, projectName, task1); + await createTask(page, projectName, task2); + await createTimeEntryWithProjectAndTask(page, projectName, task1, '1h'); + await createTimeEntryWithProjectAndTask(page, projectName, task2, '2h'); + + // Use the detailed view to verify task filtering (shows individual entries) + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectName} - ${task2}`)).toBeVisible(); + + // Open task multiselect dropdown + await page.getByRole('button', { name: 'Tasks' }).first().click(); + + // Verify both tasks appear + await expect(page.getByRole('option').filter({ hasText: task1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: task2 })).toBeVisible(); + + // Select task1 + await page.getByRole('option').filter({ hasText: task1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Verify badge shows count of 1 + await expect(page.getByRole('button', { name: 'Tasks' }).first().getByText('1')).toBeVisible(); + + // Verify only task1 entry is shown + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectName} - ${task2}`)).not.toBeVisible(); +}); + +test('test that selecting multiple tasks shows correct badge count', async ({ page }) => { + const projectName = 'MultiTaskProj ' + Math.floor(Math.random() * 10000); + const task1 = 'MultiTask1 ' + Math.floor(Math.random() * 10000); + const task2 = 'MultiTask2 ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTask(page, projectName, task1); + await createTask(page, projectName, task2); + await createTimeEntryWithProjectAndTask(page, projectName, task1, '1h'); + await createTimeEntryWithProjectAndTask(page, projectName, task2, '2h'); + + // Use the detailed view to verify task filtering + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectName} - ${task2}`)).toBeVisible(); + + // Select both tasks + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: task1 }).click(); + await page.getByRole('option').filter({ hasText: task2 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Verify badge shows count of 2 + await expect(page.getByRole('button', { name: 'Tasks' }).first().getByText('2')).toBeVisible(); + + // Verify both task entries are shown + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectName} - ${task2}`)).toBeVisible(); +}); + +test('test that deselecting a task removes the filter', async ({ page }) => { + const projectName = 'TaskDeselectProj ' + Math.floor(Math.random() * 10000); + const task1 = 'TaskDeselect1 ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTask(page, projectName, task1); + await createTimeEntryWithProjectAndTask(page, projectName, task1, '1h'); + + await goToReportingDetailed(page); + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + + // Select task + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: task1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + await expect(page.getByRole('button', { name: 'Tasks' }).first().getByText('1')).toBeVisible(); + + // Deselect task + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: task1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + await expect( + page.getByRole('button', { name: 'Tasks' }).first().getByText(/^\d+$/) + ).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Member Filter Tests +// ────────────────────────────────────────────────── + +test('test that member multiselect filters work on detailed reporting page', async ({ page }) => { + const projectName = 'DetailMemberProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReportingDetailed(page); + await expect(page.getByText(`Entry for ${projectName}`)).toBeVisible(); + + // Filter by the current member + await page.getByRole('button', { name: 'Members' }).first().click(); + await page.getByRole('option').filter({ hasText: 'John Doe' }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Data should still be visible since all entries belong to this member + await expect(page.getByText(`Entry for ${projectName}`)).toBeVisible(); + + // Verify badge shows count of 1 + await expect( + page.getByRole('button', { name: 'Members' }).first().getByText('1') + ).toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Tag Filter Tests +// ────────────────────────────────────────────────── + +test('test that tag filter works on detailed reporting page', async ({ page }) => { + const tag1 = 'DetailTag1 ' + Math.floor(Math.random() * 10000); + const tag2 = 'DetailTag2 ' + Math.floor(Math.random() * 10000); + + await createTimeEntryWithTag(page, tag1, '1h'); + await createTimeEntryWithTag(page, tag2, '2h'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry with tag ${tag1}`)).toBeVisible(); + await expect(page.getByText(`Entry with tag ${tag2}`)).toBeVisible(); + + // Filter by tag1 + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByRole('option').filter({ hasText: tag1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + await expect(page.getByText(`Entry with tag ${tag1}`)).toBeVisible(); + await expect(page.getByText(`Entry with tag ${tag2}`)).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Billable Filter Tests +// ────────────────────────────────────────────────── + +test('test that billable filter works on detailed reporting page', async ({ page }) => { + const projectName = 'DetailBillProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReportingDetailed(page); + await expect(page.getByText(`Entry for ${projectName}`)).toBeVisible(); + + // Filter by billable only + await page.getByRole('combobox').filter({ hasText: 'Billable' }).click(); + await Promise.all([ + page.getByRole('option', { name: 'Billable', exact: true }).click(), + waitForDetailedReportingUpdate(page), + ]); + + // Switch to Non Billable + await page.getByRole('combobox').filter({ hasText: 'Billable' }).click(); + await Promise.all([ + page.getByRole('option', { name: 'Non Billable', exact: true }).click(), + waitForDetailedReportingUpdate(page), + ]); + + // Switch back to Both + await page.getByRole('combobox').filter({ hasText: 'Non Billable' }).click(); + await Promise.all([ + page.getByRole('option', { name: 'Both' }).click(), + waitForDetailedReportingUpdate(page), + ]); +}); + +// ────────────────────────────────────────────────── +// Combined Filter Tests +// ────────────────────────────────────────────────── + +test('test that combining project and task filters narrows results', async ({ page }) => { + const projectName = 'CombinedProj ' + Math.floor(Math.random() * 10000); + const otherProject = 'OtherCombProj ' + Math.floor(Math.random() * 10000); + const task1 = 'CombinedTask1 ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createProject(page, otherProject); + await createTask(page, projectName, task1); + await createTimeEntryWithProjectAndTask(page, projectName, task1, '1h'); + await createTimeEntryWithProject(page, otherProject, '2h'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${otherProject}`)).toBeVisible(); + + // Filter by project + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: projectName }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Additionally filter by task + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: task1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Verify both badges show count of 1 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('1') + ).toBeVisible(); + await expect(page.getByRole('button', { name: 'Tasks' }).first().getByText('1')).toBeVisible(); + + // Verify only the combined entry is shown + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${otherProject}`)).not.toBeVisible(); +}); + +test('test that combining client and member filters narrows results on detailed page', async ({ + page, +}) => { + const client1 = 'CombClient ' + Math.floor(Math.random() * 10000); + const project1 = 'CombClientProj ' + Math.floor(Math.random() * 10000); + const project2 = 'CombNoClientProj ' + Math.floor(Math.random() * 10000); + + await createClient(page, client1); + await createProjectWithClient(page, project1, client1); + await createProject(page, project2); + await createTimeEntryWithProject(page, project1, '1h'); + await createTimeEntryWithProject(page, project2, '2h'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${project2}`)).toBeVisible(); + + // Filter by client + await page.getByRole('button', { name: 'Clients' }).first().click(); + await page.getByRole('option').filter({ hasText: client1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Additionally filter by member + await page.getByRole('button', { name: 'Members' }).first().click(); + await page.getByRole('option').filter({ hasText: 'John Doe' }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Only project1 entry should be visible (filtered by client + member) + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${project2}`)).not.toBeVisible(); + + // Both badges should show count of 1 + await expect( + page.getByRole('button', { name: 'Clients' }).first().getByText('1') + ).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Members' }).first().getByText('1') + ).toBeVisible(); +}); + +test('test that combining tag and project filters narrows results', async ({ page }) => { + const tag1 = 'CombTag ' + Math.floor(Math.random() * 10000); + const project1 = 'CombTagProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + + // Create a time entry with a project (no tag) + await createTimeEntryWithProject(page, project1, '1h'); + + // Create a time entry with a tag (no specific project) + await createTimeEntryWithTag(page, tag1, '2h'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry with tag ${tag1}`)).toBeVisible(); + + // Filter by project + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: project1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Only the project entry should be visible (tagged entry has no project) + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText(`Entry with tag ${tag1}`)).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// "No X" Filter Tests +// ────────────────────────────────────────────────── + +test('test that "No Project" filter shows entries without a project', async ({ page }) => { + const project1 = 'NoProj1 ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + await createTimeEntryWithProject(page, project1, '1h'); + await createBareTimeEntry(page, 'Bare entry no project', '30min'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText('Bare entry no project')).toBeVisible(); + + // Open project dropdown and select "No Project" + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Project' }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Verify badge shows 1 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('1') + ).toBeVisible(); + + // Only the bare entry (no project) should be visible + await expect(page.getByText('Bare entry no project')).toBeVisible(); + await expect(page.getByText(`Entry for ${project1}`)).not.toBeVisible(); +}); + +test('test that "No Task" filter shows entries without a task', async ({ page }) => { + const projectName = 'NoTaskProj ' + Math.floor(Math.random() * 10000); + const task1 = 'NoTaskFilter1 ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTask(page, projectName, task1); + await createTimeEntryWithProjectAndTask(page, projectName, task1, '1h'); + await createTimeEntryWithProject(page, projectName, '30min'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectName}`).first()).toBeVisible(); + + // Open task dropdown and select "No Task" + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Task' }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + await expect(page.getByRole('button', { name: 'Tasks' }).first().getByText('1')).toBeVisible(); + + // Only the entry without a task should be visible + await expect(page.getByText(`Entry for ${projectName} - ${task1}`)).not.toBeVisible(); +}); + +test('test that "No Tag" filter shows entries without tags', async ({ page }) => { + const tag1 = 'NoTagFilter1 ' + Math.floor(Math.random() * 10000); + + await createTimeEntryWithTag(page, tag1, '1h'); + await createBareTimeEntry(page, 'Entry without any tag', '30min'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry with tag ${tag1}`)).toBeVisible(); + await expect(page.getByText('Entry without any tag')).toBeVisible(); + + // Open tag dropdown and select "No Tag" + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByRole('option').filter({ hasText: 'No Tag' }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + await expect(page.getByRole('button', { name: 'Tags' }).getByText('1')).toBeVisible(); + + await expect(page.getByText('Entry without any tag')).toBeVisible(); + await expect(page.getByText(`Entry with tag ${tag1}`)).not.toBeVisible(); +}); + +test('test that "No Client" filter shows entries without a client', async ({ page }) => { + const client1 = 'NoClientFilter ' + Math.floor(Math.random() * 10000); + const projectWithClient = 'NoClientProj1 ' + Math.floor(Math.random() * 10000); + const projectNoClient = 'NoClientProj2 ' + Math.floor(Math.random() * 10000); + + await createClient(page, client1); + await createProjectWithClient(page, projectWithClient, client1); + await createProject(page, projectNoClient); + await createTimeEntryWithProject(page, projectWithClient, '1h'); + await createTimeEntryWithProject(page, projectNoClient, '30min'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${projectWithClient}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectNoClient}`)).toBeVisible(); + + // Open client dropdown and select "No Client" + await page.getByRole('button', { name: 'Clients' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Client' }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + await expect( + page.getByRole('button', { name: 'Clients' }).first().getByText('1') + ).toBeVisible(); + + await expect(page.getByText(`Entry for ${projectNoClient}`)).toBeVisible(); + await expect(page.getByText(`Entry for ${projectWithClient}`)).not.toBeVisible(); +}); + +test('test that combining "No Project" with a project ID shows both', async ({ page }) => { + const project1 = 'CombNoProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + await createTimeEntryWithProject(page, project1, '1h'); + await createBareTimeEntry(page, 'Bare combined entry', '30min'); + + await goToReportingDetailed(page); + + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText('Bare combined entry')).toBeVisible(); + + // Select both "No Project" and the specific project + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Project' }).click(); + await page.getByRole('option').filter({ hasText: project1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Badge should show 2 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('2') + ).toBeVisible(); + + // Both entries should be visible + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + await expect(page.getByText('Bare combined entry')).toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Keyboard Navigation Tests +// ────────────────────────────────────────────────── + +test('test that keyboard navigation works in multiselect dropdown', async ({ page }) => { + const project1 = 'KbNavProj1 ' + Math.floor(Math.random() * 10000); + const project2 = 'KbNavProj2 ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + await createProject(page, project2); + await createTimeEntryWithProject(page, project1, '1h'); + await createTimeEntryWithProject(page, project2, '2h'); + + await goToReportingDetailed(page); + await expect(page.getByText(`Entry for ${project1}`)).toBeVisible(); + + // Open project dropdown + await page.getByRole('button', { name: 'Projects' }).first().click(); + + // The search input should be focused, first item ("No Project") highlighted + await expect(page.getByPlaceholder('Search for a Project...')).toBeFocused(); + + // Press ArrowDown to move to first project, then Enter to select it + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + + // Close dropdown and verify filter applied + await Promise.all([page.keyboard.press('Escape'), waitForDetailedReportingUpdate(page)]); + + // Badge should show 1 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('1') + ).toBeVisible(); +}); diff --git a/e2e/reporting.spec.ts b/e2e/reporting.spec.ts index 4c3a777d..edf6e1fd 100644 --- a/e2e/reporting.spec.ts +++ b/e2e/reporting.spec.ts @@ -1,148 +1,431 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; +import { + goToReporting, + createProject, + createClient, + createProjectWithClient, + createTask, + createTimeEntryWithProject, + createTimeEntryWithProjectAndTask, + createTimeEntryWithTag, + createTimeEntryWithBillableStatus, + waitForReportingUpdate, +} from './utils/reporting'; + +// Each test registers a new user and creates test data, which needs more time +test.describe.configure({ timeout: 60000 }); + +// ────────────────────────────────────────────────── +// No-op Dropdown Close Tests +// ────────────────────────────────────────────────── + +test('test that opening and closing a filter dropdown without changes does not trigger an API request', async ({ + page, +}) => { + const projectName = 'NoOpDropdown ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); -async function goToTimeOverview(page: Page) { - await page.goto(PLAYWRIGHT_BASE_URL + '/time'); -} + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); -async function goToReporting(page: Page) { - await page.goto(PLAYWRIGHT_BASE_URL + '/reporting'); -} + // Wait for initial reporting data to fully load and all network activity to settle + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + await page.waitForLoadState('networkidle'); -async function goToReportingDetailed(page: Page) { - await page.goto(PLAYWRIGHT_BASE_URL + '/reporting/detailed'); -} + // Set up a request counter for aggregate API calls + let aggregateRequestCount = 0; + page.on('response', (response) => { + if ( + response.url().includes('/time-entries/aggregate') && + !response.url().includes('/export') && + response.status() === 200 + ) { + aggregateRequestCount++; + } + }); + + // Open project dropdown, change nothing, close it + await page.getByRole('button', { name: 'Projects' }).first().click(); + await expect(page.getByPlaceholder('Search for a Project...')).toBeVisible(); + await page.keyboard.press('Escape'); + + // Open member dropdown, change nothing, close it + await page.getByRole('button', { name: 'Members' }).first().click(); + await expect(page.getByPlaceholder('Search for a Member...')).toBeVisible(); + await page.keyboard.press('Escape'); + + // Open client dropdown, change nothing, close it + await page.getByRole('button', { name: 'Clients' }).first().click(); + await expect(page.getByPlaceholder('Search for a Client...')).toBeVisible(); + await page.keyboard.press('Escape'); + + // Wait for all network activity to settle before asserting no requests were made + await page.waitForLoadState('networkidle'); -async function createTimeEntryWithProject(page: Page, projectName: string, duration: string) { - // First create the project through the Projects page - await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); - await page.getByRole('button', { name: 'Create Project' }).click(); - await page.getByLabel('Project Name').fill(projectName); - await page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(); + // No aggregate API requests should have been made + expect(aggregateRequestCount).toBe(0); - // Wait for the project to be created and visible in the list - await page.getByText(projectName).waitFor({ state: 'visible' }); + // Verify the report data is still intact (no flash/reload) + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); +}); - // Then create the time entry - await goToTimeOverview(page); +// ────────────────────────────────────────────────── +// Project Multiselect Dropdown Tests +// ────────────────────────────────────────────────── - // Open the dropdown menu and click "Manual time entry" - await page.getByRole('button', { name: 'Time entry actions' }).click(); - await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); +test('test that project multiselect dropdown shows projects and filters reporting', async ({ + page, +}) => { + const project1 = 'ProjFilter1 ' + Math.floor(Math.random() * 10000); + const project2 = 'ProjFilter2 ' + Math.floor(Math.random() * 10000); - // Fill in the time entry details - await page - .getByRole('dialog') - .getByRole('textbox', { name: 'Description' }) - .fill(`Time entry for ${projectName}`); + await createProject(page, project1); + await createProject(page, project2); + await createTimeEntryWithProject(page, project1, '1h'); + await createTimeEntryWithProject(page, project2, '2h'); - await page.getByRole('button', { name: 'No Project' }).click(); - await page.getByText(projectName).click(); + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - // Set duration - await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); - await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + // Open project multiselect dropdown + await page.getByRole('button', { name: 'Projects' }).first().click(); - // Submit the time entry - await Promise.all([ - page.getByRole('button', { name: 'Create Time Entry' }).click(), - page.waitForResponse( - (response) => response.url().includes('/time-entries') && response.status() === 201 - ), - ]); -} + // Verify both projects appear as options + await expect(page.getByRole('option').filter({ hasText: project1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: project2 })).toBeVisible(); -async function createTimeEntryWithTag(page: Page, tagName: string, duration: string) { - await goToTimeOverview(page); + // Select project1 + await page.getByRole('option').filter({ hasText: project1 }).click(); - // Open the dropdown menu and click "Manual time entry" - await page.getByRole('button', { name: 'Time entry actions' }).click(); - await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + // Close dropdown and wait for report update + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); - // Fill in the time entry details - await page - .getByRole('dialog') - .getByRole('textbox', { name: 'Description' }) - .fill(`Time entry with tag ${tagName}`); + // Verify filter badge shows count of 1 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('1') + ).toBeVisible(); - // Add tag - await page.getByRole('button', { name: 'Tags' }).click(); - await page.getByText('Create new tag').click(); - await page.getByPlaceholder('Tag Name').fill(tagName); - await page.getByRole('button', { name: 'Create Tag' }).click(); - await page.waitForLoadState('networkidle'); + // Verify only project1 data is shown + await expect(page.getByTestId('reporting_view').getByText(project1)).toBeVisible(); + await expect(page.getByTestId('reporting_view').getByText(project2)).not.toBeVisible(); +}); + +test('test that project multiselect search filters the option list', async ({ page }) => { + const project1 = 'SearchableAlpha ' + Math.floor(Math.random() * 10000); + const project2 = 'SearchableBeta ' + Math.floor(Math.random() * 10000); - // Set duration - await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); - await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); - - // Submit the time entry - await page.getByRole('button', { name: 'Create Time Entry' }).click(); -} - -async function createTimeEntryWithBillableStatus( - page: Page, - isBillable: boolean, - duration: string -) { - await goToTimeOverview(page); - - // Open the dropdown menu and click "Manual time entry" - await page.getByRole('button', { name: 'Time entry actions' }).click(); - await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); - - // Fill in the time entry details - await page - .getByRole('dialog') - .getByRole('textbox', { name: 'Description' }) - .fill(`Time entry ${isBillable ? 'billable' : 'non-billable'}`); - - // Set billable status - await page.getByRole('button', { name: 'Non-Billable' }).click(); - if (!isBillable) { - await page.getByRole('option', { name: 'Non Billable', exact: true }).click(); - } else { - await page.getByRole('option', { name: 'Billable', exact: true }).click(); - } - - // Set duration - await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); - await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); - - // Submit the time entry - await page.getByRole('button', { name: 'Create Time Entry' }).click(); -} - -test('test that project filtering works in reporting', async ({ page }) => { - const project1 = 'Test Project 1 ' + Math.floor(Math.random() * 10000); - const project2 = 'Test Project 2 ' + Math.floor(Math.random() * 10000); - - // Create time entries for both projects + await createProject(page, project1); + await createProject(page, project2); + await createTimeEntryWithProject(page, project1, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Open project multiselect dropdown + await page.getByRole('button', { name: 'Projects' }).first().click(); + + // Type in search + await page.getByPlaceholder('Search for a Project...').fill('Alpha'); + + // Verify only matching project is visible + await expect(page.getByRole('option').filter({ hasText: project1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: project2 })).not.toBeVisible(); + + await page.keyboard.press('Escape'); +}); + +test('test that selecting multiple projects shows correct badge count', async ({ page }) => { + const project1 = 'MultiProj1 ' + Math.floor(Math.random() * 10000); + const project2 = 'MultiProj2 ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + await createProject(page, project2); await createTimeEntryWithProject(page, project1, '1h'); await createTimeEntryWithProject(page, project2, '2h'); - // Go to reporting and filter by project1 await goToReporting(page); - await page.getByRole('button', { name: 'Project' }).nth(0).click(); - await page.getByRole('dialog').getByText(project1).click(); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - await Promise.all([ - // escape - page.keyboard.press('Escape'), - // wait for API request to finish - page.waitForResponse( - (response) => - response.url().includes('/time-entries/aggregate') && response.status() === 200 - ), - ]); - await page.waitForLoadState('networkidle'); + // Open project dropdown and select both + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: project1 }).click(); + await page.getByRole('option').filter({ hasText: project2 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify filter badge shows count of 2 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('2') + ).toBeVisible(); + + // Verify both projects are shown in the report + await expect(page.getByTestId('reporting_view').getByText(project1)).toBeVisible(); + await expect(page.getByTestId('reporting_view').getByText(project2)).toBeVisible(); +}); + +test('test that deselecting a project removes the filter', async ({ page }) => { + const project1 = 'DeselectProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, project1); + await createTimeEntryWithProject(page, project1, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Select project + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: project1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify badge count is 1 + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText('1') + ).toBeVisible(); + + // Deselect project + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: project1 }).click(); + await page.keyboard.press('Escape'); + + // Verify badge count is gone (no count displayed when 0) + await expect( + page.getByRole('button', { name: 'Projects' }).first().getByText(/^\d+$/) + ).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Client Multiselect Dropdown Tests +// ────────────────────────────────────────────────── + +test('test that client multiselect dropdown filters reporting by client', async ({ page }) => { + const client1 = 'ClientFilter1 ' + Math.floor(Math.random() * 10000); + const client2 = 'ClientFilter2 ' + Math.floor(Math.random() * 10000); + const project1 = 'ClientProj1 ' + Math.floor(Math.random() * 10000); + const project2 = 'ClientProj2 ' + Math.floor(Math.random() * 10000); + + await createClient(page, client1); + await createClient(page, client2); + await createProjectWithClient(page, project1, client1); + await createProjectWithClient(page, project2, client2); + await createTimeEntryWithProject(page, project1, '1h'); + await createTimeEntryWithProject(page, project2, '2h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - // Verify only project1 time entries are shown + // Open client multiselect dropdown + await page.getByRole('button', { name: 'Clients' }).first().click(); + + // Verify both clients appear + await expect(page.getByRole('option').filter({ hasText: client1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: client2 })).toBeVisible(); + + // Select client1 + await page.getByRole('option').filter({ hasText: client1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify badge shows count of 1 + await expect( + page.getByRole('button', { name: 'Clients' }).first().getByText('1') + ).toBeVisible(); + + // Verify only project1 (belonging to client1) is shown await expect(page.getByTestId('reporting_view').getByText(project1)).toBeVisible(); await expect(page.getByTestId('reporting_view').getByText(project2)).not.toBeVisible(); }); +test('test that client multiselect search filters the option list', async ({ page }) => { + const client1 = 'ClientSearchAlpha ' + Math.floor(Math.random() * 10000); + const client2 = 'ClientSearchBeta ' + Math.floor(Math.random() * 10000); + + await createClient(page, client1); + await createClient(page, client2); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + await page.getByRole('button', { name: 'Clients' }).first().click(); + + // Search for "Alpha" + await page.getByPlaceholder('Search for a Client...').fill('Alpha'); + + await expect(page.getByRole('option').filter({ hasText: client1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: client2 })).not.toBeVisible(); + + await page.keyboard.press('Escape'); +}); + +test('test that deselecting a client removes the filter', async ({ page }) => { + const client1 = 'ClientDeselect ' + Math.floor(Math.random() * 10000); + const project1 = 'ClientDeselectProj ' + Math.floor(Math.random() * 10000); + + await createClient(page, client1); + await createProjectWithClient(page, project1, client1); + await createTimeEntryWithProject(page, project1, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Select client + await page.getByRole('button', { name: 'Clients' }).first().click(); + await page.getByRole('option').filter({ hasText: client1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + await expect( + page.getByRole('button', { name: 'Clients' }).first().getByText('1') + ).toBeVisible(); + + // Deselect client + await page.getByRole('button', { name: 'Clients' }).first().click(); + await page.getByRole('option').filter({ hasText: client1 }).click(); + await page.keyboard.press('Escape'); + + await expect( + page.getByRole('button', { name: 'Clients' }).first().getByText(/^\d+$/) + ).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Task Multiselect Dropdown Tests +// ────────────────────────────────────────────────── + +test('test that task filtering works in reporting', async ({ page }) => { + const projectName = 'Task Filter Proj ' + Math.floor(Math.random() * 10000); + const task1 = 'Task Filter A ' + Math.floor(Math.random() * 10000); + const task2 = 'Task Filter B ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '30min'); + await createTask(page, projectName, task1); + await createTask(page, projectName, task2); + await createTimeEntryWithProjectAndTask(page, projectName, task1, '1h'); + await createTimeEntryWithProjectAndTask(page, projectName, task2, '2h'); + + // Go to reporting and group by task to see individual tasks + await goToReporting(page); + + // Filter by task1 + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: task1 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify the report only shows 1h (task1's duration) + await expect(page.getByTestId('reporting_view').getByText('1h 00min').first()).toBeVisible(); +}); + +test('test that task multiselect search filters the option list', async ({ page }) => { + const projectName = 'TaskSearchProj ' + Math.floor(Math.random() * 10000); + const task1 = 'TaskSearchAlpha ' + Math.floor(Math.random() * 10000); + const task2 = 'TaskSearchBeta ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTask(page, projectName, task1); + await createTask(page, projectName, task2); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + await page.getByRole('button', { name: 'Tasks' }).first().click(); + + await page.getByPlaceholder('Search for a Task...').fill('Alpha'); + + await expect(page.getByRole('option').filter({ hasText: task1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: task2 })).not.toBeVisible(); + + await page.keyboard.press('Escape'); +}); + +// ────────────────────────────────────────────────── +// Member Multiselect Dropdown Tests +// ────────────────────────────────────────────────── + +test('test that member multiselect dropdown shows current member and filters reporting', async ({ + page, +}) => { + const projectName = 'MemberFilterProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Open member multiselect dropdown + await page.getByRole('button', { name: 'Members' }).first().click(); + + // Verify the current user (John Doe from fixture) appears as an option + await expect(page.getByRole('option').filter({ hasText: 'John Doe' })).toBeVisible(); + + // Select the member + await page.getByRole('option').filter({ hasText: 'John Doe' }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify badge shows count of 1 + await expect( + page.getByRole('button', { name: 'Members' }).first().getByText('1') + ).toBeVisible(); + + // Verify data is still shown (since all entries belong to this member) + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); +}); + +test('test that member multiselect search filters the option list', async ({ page }) => { + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + await page.getByRole('button', { name: 'Members' }).first().click(); + + // Search for the registered user + await page.getByPlaceholder('Search for a Member...').fill('John'); + await expect(page.getByRole('option').filter({ hasText: 'John Doe' })).toBeVisible(); + + // Search for a non-existent member + await page.getByPlaceholder('Search for a Member...').fill('NonExistentMember'); + await expect(page.getByRole('option')).not.toBeVisible(); + + await page.keyboard.press('Escape'); +}); + +test('test that deselecting a member removes the filter', async ({ page }) => { + const projectName = 'MemberDeselectProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Select member + await page.getByRole('button', { name: 'Members' }).first().click(); + await page.getByRole('option').filter({ hasText: 'John Doe' }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + await expect( + page.getByRole('button', { name: 'Members' }).first().getByText('1') + ).toBeVisible(); + + // Deselect member + await page.getByRole('button', { name: 'Members' }).first().click(); + await page.getByRole('option').filter({ hasText: 'John Doe' }).click(); + await page.keyboard.press('Escape'); + + // Verify badge count is gone + await expect( + page.getByRole('button', { name: 'Members' }).first().getByText(/^\d+$/) + ).not.toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Tag Dropdown Tests +// ────────────────────────────────────────────────── + test('test that tag filtering works in reporting', async ({ page }) => { const tag1 = 'Test Tag 1 ' + Math.floor(Math.random() * 10000); const tag2 = 'Test Tag 2 ' + Math.floor(Math.random() * 10000); @@ -153,26 +436,111 @@ test('test that tag filtering works in reporting', async ({ page }) => { // Go to reporting and filter by tag1 await goToReporting(page); - // wait for all requests to finish - await page.waitForLoadState('networkidle'); + await expect(page.getByRole('button', { name: 'Tags' })).toBeVisible(); await page.getByRole('button', { name: 'Tags' }).click(); await page.getByText(tag1).click(); + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify only time entries with tag1 are shown + await expect(page.getByTestId('reporting_view').getByText('1h 00min').first()).toBeVisible(); +}); + +test('test that tag dropdown search filters the option list', async ({ page }) => { + const tag1 = 'TagSearchAlpha ' + Math.floor(Math.random() * 10000); + const tag2 = 'TagSearchBeta ' + Math.floor(Math.random() * 10000); + + await createTimeEntryWithTag(page, tag1, '1h'); + await createTimeEntryWithTag(page, tag2, '2h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + await page.getByRole('button', { name: 'Tags' }).click(); + + await page.getByPlaceholder('Search for a Tag...').fill('Alpha'); + + await expect(page.getByRole('option').filter({ hasText: tag1 })).toBeVisible(); + await expect(page.getByRole('option').filter({ hasText: tag2 })).not.toBeVisible(); + + await page.keyboard.press('Escape'); +}); + +test('test that selecting multiple tags shows correct badge count', async ({ page }) => { + const tag1 = 'MultiTag1 ' + Math.floor(Math.random() * 10000); + const tag2 = 'MultiTag2 ' + Math.floor(Math.random() * 10000); + + await createTimeEntryWithTag(page, tag1, '1h'); + await createTimeEntryWithTag(page, tag2, '2h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Select both tags + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByRole('option').filter({ hasText: tag1 }).click(); + await page.getByRole('option').filter({ hasText: tag2 }).click(); + + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + // Verify badge shows count of 2 + await expect(page.getByRole('button', { name: 'Tags' }).getByText('2')).toBeVisible(); +}); + +test('test that deselecting a tag removes the filter', async ({ page }) => { + const tag1 = 'TagDeselect ' + Math.floor(Math.random() * 10000); + + await createTimeEntryWithTag(page, tag1, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Select tag + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByRole('option').filter({ hasText: tag1 }).click(); + await Promise.all([page.keyboard.press('Escape'), waitForReportingUpdate(page)]); + + await expect(page.getByRole('button', { name: 'Tags' }).getByText('1')).toBeVisible(); + + // Deselect tag + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByRole('option').filter({ hasText: tag1 }).click(); + await page.keyboard.press('Escape'); + + await expect(page.getByRole('button', { name: 'Tags' }).getByText(/^\d+$/)).not.toBeVisible(); +}); + +test('test that creating a tag inline from the reporting filter works', async ({ page }) => { + const projectName = 'TagCreateProj ' + Math.floor(Math.random() * 10000); + const newTag = 'InlineTag ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Open tag dropdown and create a new tag + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByText('Create new tag').click(); + await page.getByPlaceholder('Tag Name').fill(newTag); + await Promise.all([ - // escape - page.keyboard.press('Escape'), - // wait for API request to finish + page.getByRole('button', { name: 'Create Tag' }).click(), page.waitForResponse( - (response) => - response.url().includes('/time-entries/aggregate') && response.status() === 200 + (response) => response.url().includes('/tags') && response.status() === 201 ), ]); - // Verify only time entries with tag1 are shown - await expect(page.getByTestId('reporting_view').getByText('1h 00min').first()).toBeVisible(); + // The new tag should now be selected in the dropdown (badge should show 1) + await expect(page.getByRole('button', { name: 'Tags' }).getByText('1')).toBeVisible(); }); +// ────────────────────────────────────────────────── +// Billable Select Tests +// ────────────────────────────────────────────────── + test('test that billable status filtering works in reporting', async ({ page }) => { // Create billable and non-billable time entries await createTimeEntryWithBillableStatus(page, true, '1h'); @@ -181,60 +549,205 @@ test('test that billable status filtering works in reporting', async ({ page }) // Go to reporting and filter by billable await goToReporting(page); - await page.getByRole('button', { name: 'Billable' }).click(); - await page.getByRole('option', { name: 'Billable', exact: true }).click(); + await page.getByRole('combobox').filter({ hasText: 'Billable' }).click(); + await Promise.all([ + page.getByRole('option', { name: 'Billable', exact: true }).click(), + waitForReportingUpdate(page), + ]); + await expect(page.getByTestId('reporting_view').getByText('1h 00min').first()).toBeVisible(); +}); + +test('test that billable filter can switch between all three states', async ({ page }) => { + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + const billableSelect = page.getByRole('combobox').filter({ hasText: 'Billable' }); + + // Switch to Billable + await billableSelect.click(); + await Promise.all([ + page.getByRole('option', { name: 'Billable', exact: true }).click(), + waitForReportingUpdate(page), + ]); + + // Switch to Non Billable + await billableSelect.click(); await Promise.all([ - // escape - page.keyboard.press('Escape'), - // wait for API request to finish + page.getByRole('option', { name: 'Non Billable', exact: true }).click(), + waitForReportingUpdate(page), + ]); + + // Verify "Non Billable" is displayed + await expect(billableSelect).toContainText('Non Billable'); + + // Switch back to Both (cached by TanStack Query, no new API request) + await billableSelect.click(); + await page.getByRole('option', { name: 'Both' }).click(); + await expect(billableSelect).toContainText('Billable'); +}); + +// ────────────────────────────────────────────────── +// Rounding Controls Tests +// ────────────────────────────────────────────────── + +test('test that rounding can be enabled', async ({ page }) => { + const projectName = 'RoundingProj ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h 7min'); + + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Verify rounding is off by default + await expect(page.getByRole('button', { name: /Rounding off/ })).toBeVisible(); + + // Open rounding controls and enable rounding + await page.getByRole('button', { name: /Rounding off/ }).click(); + + const reportUpdatePromise = waitForReportingUpdate(page); + await page.getByRole('switch', { name: 'Enable Rounding' }).click(); + await reportUpdatePromise; + + // Close the popover by clicking elsewhere + await page.keyboard.press('Escape'); + + // Verify button text changed to "on" + await expect(page.getByRole('button', { name: /Rounding on/ })).toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Export Tests +// ────────────────────────────────────────────────── + +test('test that export dropdown shows all format options', async ({ page }) => { + const projectName = 'Export Test ' + Math.floor(Math.random() * 10000); + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + // Go to reporting page + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Click the export button + await page.getByRole('button', { name: 'Export' }).click(); + + // Verify all 4 format options are visible + await expect(page.getByRole('menuitem', { name: /Export as PDF/i })).toBeVisible(); + await expect(page.getByRole('menuitem', { name: /Export as Excel/i })).toBeVisible(); + await expect(page.getByRole('menuitem', { name: /Export as CSV/i })).toBeVisible(); + await expect(page.getByRole('menuitem', { name: /Export as ODS/i })).toBeVisible(); +}); + +test('test that CSV export triggers download', async ({ page }) => { + const projectName = 'CSV Export ' + Math.floor(Math.random() * 10000); + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + // Go to reporting page + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Click export and select CSV, wait for the export API response with a download URL + await page.getByRole('button', { name: 'Export' }).click(); + const [exportResponse] = await Promise.all([ page.waitForResponse( (response) => - response.url().includes('/time-entries/aggregate') && response.status() === 200 + response.url().includes('/time-entries/aggregate/export') && + response.status() === 200 ), + page.getByRole('menuitem', { name: /Export as CSV/i }).click(), ]); - await page.waitForLoadState('networkidle'); - await expect(page.getByTestId('reporting_view').getByText('1h 00min').first()).toBeVisible(); + // Verify the API returned a download URL + const responseBody = await exportResponse.json(); + expect(responseBody.download_url).toBeTruthy(); + + // Verify the export success modal appeared + await expect(page.getByText('Export Successful!')).toBeVisible(); + + // Verify the download URL is accessible and returns CSV content + const downloadResponse = await page.request.get(responseBody.download_url); + expect(downloadResponse.ok()).toBeTruthy(); + const contentType = downloadResponse.headers()['content-type']; + expect(contentType).toContain('csv'); }); -test('test that detailed view shows time entries correctly', async ({ page }) => { - const projectName = 'Detailed View Project ' + Math.floor(Math.random() * 10000); +// ────────────────────────────────────────────────── +// Group By Tests +// ────────────────────────────────────────────────── - // Create a time entry +test('test that group by select changes report grouping', async ({ page }) => { + const projectName = 'GroupBy Test ' + Math.floor(Math.random() * 10000); + await createProject(page, projectName); await createTimeEntryWithProject(page, projectName, '1h'); - // Go to detailed reporting view - await goToReportingDetailed(page); + // Go to reporting page + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); + + // Find the "Group by" selects within the reporting table + const groupBySelects = page.locator('[data-testid="reporting_view"]').getByRole('combobox'); - // Verify the time entry is shown with all details - await expect(page.getByText(projectName, { exact: true })).toBeVisible(); - await expect(page.locator('input[name="Duration"]')).toHaveValue('1h 00min'); - await expect(page.getByText('Time entry for ' + projectName, { exact: true })).toBeVisible(); + // Click the first group by select to change grouping + await groupBySelects.filter({ hasText: 'Project' }).first().click(); + + // Select "Members" option and wait for the table query to update (has sub_group param) + const [aggregateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/aggregate') && + response.url().includes('sub_group') && + response.status() === 200 + ), + page.getByRole('option', { name: 'Members' }).click(), + ]); + + // Verify the API request contains the correct group parameter + const requestUrl = new URL(aggregateResponse.url()); + expect(requestUrl.searchParams.get('group')).toBe('user'); + + // Verify the grouping changed (the select should now show "Members") + await expect(groupBySelects.filter({ hasText: 'Members' }).first()).toBeVisible(); }); -test('test that updating duration in detailed view works correctly', async ({ page }) => { - const projectName = 'Duration Update Project ' + Math.floor(Math.random() * 10000); - const initialDuration = '1h'; - const updatedDuration = '2h 30min'; +test('test that setting group by to current sub group triggers sub group fallback', async ({ + page, +}) => { + const projectName = 'Fallback Test ' + Math.floor(Math.random() * 10000); + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); - // Create a time entry with initial duration - await createTimeEntryWithProject(page, projectName, initialDuration); + // Go to reporting page + await goToReporting(page); + await expect(page.getByRole('button', { name: 'Export' })).toBeVisible(); - // Go to detailed reporting view - await goToReportingDetailed(page); + // Find the "Group by" selects within the reporting table + const groupBySelects = page.locator('[data-testid="reporting_view"]').getByRole('combobox'); - // Find and update the duration - const durationInput = page.locator('input[name="Duration"]').first(); - await durationInput.click(); - await durationInput.fill(updatedDuration); - await durationInput.press('Enter'); + // Default state: group=Project, subGroup=Tasks + // Change group to "Tasks" (which is the current sub group) + await groupBySelects.filter({ hasText: 'Projects' }).first().click(); - // Wait for the update to be processed - await page.waitForLoadState('networkidle'); + const [aggregateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/aggregate') && + response.url().includes('sub_group') && + response.status() === 200 + ), + page.getByRole('option', { name: 'Tasks' }).click(), + ]); - // Verify the new duration is displayed - await expect(durationInput).toHaveValue(updatedDuration); -}); + // Verify the API request has group=task and sub_group changed away from task + const requestUrl = new URL(aggregateResponse.url()); + expect(requestUrl.searchParams.get('group')).toBe('task'); + expect(requestUrl.searchParams.get('sub_group')).not.toBe('task'); + + // The group should now be "Tasks" + await expect(groupBySelects.filter({ hasText: 'Tasks' }).first()).toBeVisible(); -// TODO: test that date range filtering works in reporting + // The sub group should have fallen back to a different value (not "Tasks") + await expect(groupBySelects.filter({ hasText: 'Members' }).first()).toBeVisible(); +}); diff --git a/e2e/shared-reports.spec.ts b/e2e/shared-reports.spec.ts new file mode 100644 index 00000000..46f1d0e5 --- /dev/null +++ b/e2e/shared-reports.spec.ts @@ -0,0 +1,445 @@ +import { expect } from '@playwright/test'; +import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; +import { test } from '../playwright/fixtures'; +import { + goToReporting, + goToReportingShared, + createProject, + createClient, + createProjectWithClient, + createTask, + createTimeEntryWithProject, + createTimeEntryWithProjectAndTask, + createTimeEntryWithTag, + createBareTimeEntry, + waitForReportingUpdate, + saveAsSharedReport, +} from './utils/reporting'; + +// Each test registers a new user and creates test data, which needs more time +test.describe.configure({ timeout: 60000 }); + +// Date picker button name patterns for different date formats +const DATE_PICKER_BUTTON_PATTERN = + /^Pick a date$|^\d{4}-\d{2}-\d{2}$|^\d{2}\/\d{2}\/\d{4}$|^\d{2}\.\d{2}\.\d{4}$/; + +// ────────────────────────────────────────────────── +// Shared Report Lifecycle Tests +// ────────────────────────────────────────────────── + +test('test that saving a report creates a shared report and its shareable link shows correct data', async ({ + page, +}) => { + const projectName = 'SharedProject ' + Math.floor(Math.random() * 10000); + const reportName = 'SharedReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + const { shareableLink } = await saveAsSharedReport(page, reportName); + + // Verify report appears on shared tab + await goToReportingShared(page); + await expect(page.getByTestId('report_table')).toBeVisible(); + await expect(page.getByText(reportName)).toBeVisible(); + await expect(page.getByText('Public', { exact: true })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Copy URL' })).toBeVisible(); + + // Navigate to shareable link and verify report data + await page.goto(shareableLink); + await expect(page.getByText('Reporting')).toBeVisible(); + await expect(page.getByText(projectName)).toBeVisible(); + await expect(page.getByText('Total')).toBeVisible(); +}); + +test('test that shared report with invalid secret shows no data', async ({ page }) => { + await page.goto(PLAYWRIGHT_BASE_URL + '/shared-report#invalid-secret-value'); + await expect(page.getByText('No time entries found').first()).toBeVisible(); +}); + +test('test that a shared report can be edited to toggle public/private and then deleted', async ({ + page, +}) => { + const projectName = 'EditDelProject ' + Math.floor(Math.random() * 10000); + const reportName = 'EditDelReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + await saveAsSharedReport(page, reportName); + + await goToReportingShared(page); + await expect(page.getByText(reportName)).toBeVisible(); + await expect(page.getByText('Public', { exact: true })).toBeVisible(); + + // Click more options and edit + await page + .getByRole('button', { name: new RegExp('Actions for Project ' + reportName) }) + .click(); + await page.getByRole('menuitem', { name: /^Edit Report/ }).click(); + + // Uncheck public and save + await page.getByLabel('Public').click(); + await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Report' }).click(), + ]); + + // Verify status changed to private + await expect(page.getByText('Private')).toBeVisible(); + await expect(page.getByText('--')).toBeVisible(); + + // Delete the report + await page + .getByRole('button', { name: new RegExp('Actions for Project ' + reportName) }) + .click(); + await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports/') && + response.request().method() === 'DELETE' && + response.status() === 204 + ), + page.getByRole('menuitem', { name: /^Delete Report/ }).click(), + ]); + + await expect(page.getByText('No shared reports found')).toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Shared Report Filter Tests +// ────────────────────────────────────────────────── + +test('test that shared report respects project filter', async ({ page }) => { + const projectA = 'FilterProjA ' + Math.floor(Math.random() * 10000); + const projectB = 'FilterProjB ' + Math.floor(Math.random() * 10000); + const reportName = 'FilterProjReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectA); + await createProject(page, projectB); + await createTimeEntryWithProject(page, projectA, '1h'); + await createTimeEntryWithProject(page, projectB, '2h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectA)).toBeVisible(); + + // Filter by project A + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: projectA }).click(); + await page.keyboard.press('Escape'); + await waitForReportingUpdate(page); + + const { shareableLink } = await saveAsSharedReport(page, reportName); + + // View the shared report + await page.goto(shareableLink); + await expect(page.getByText('Reporting')).toBeVisible(); + await expect(page.getByText(projectA)).toBeVisible(); + await expect(page.getByText(projectB)).not.toBeVisible(); +}); + +test('test that shared report with No Project filter shows entries without a project', async ({ + page, +}) => { + const projectName = 'NoProjFilter ' + Math.floor(Math.random() * 10000); + const reportName = 'NoProjReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + await createBareTimeEntry(page, 'Bare entry no project', '2h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + // Filter by "No Project" + await page.getByRole('button', { name: 'Projects' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Project' }).click(); + await page.keyboard.press('Escape'); + await waitForReportingUpdate(page); + + const { shareableLink } = await saveAsSharedReport(page, reportName); + + // View the shared report + await page.goto(shareableLink); + await expect(page.getByText('Reporting')).toBeVisible(); + // The "No Project" group should show, but the project name should not appear as a group + await expect(page.getByText('Total')).toBeVisible(); + await expect(page.getByText(projectName)).not.toBeVisible(); +}); + +test('test that shared report with No Task filter shows entries without a task', async ({ + page, +}) => { + const projectName = 'NoTaskProj ' + Math.floor(Math.random() * 10000); + const taskName = 'NoTaskFilter ' + Math.floor(Math.random() * 10000); + const reportName = 'NoTaskReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTask(page, projectName, taskName); + await createTimeEntryWithProjectAndTask(page, projectName, taskName, '1h'); + await createTimeEntryWithProject(page, projectName, '2h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + // Filter by "No Task" + await page.getByRole('button', { name: 'Tasks' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Task' }).click(); + await page.keyboard.press('Escape'); + await waitForReportingUpdate(page); + + const { shareableLink } = await saveAsSharedReport(page, reportName); + + // View the shared report + await page.goto(shareableLink); + await expect(page.getByText('Reporting')).toBeVisible(); + await expect(page.getByText('Total')).toBeVisible(); +}); + +// ────────────────────────────────────────────────── +// Report Date Picker Tests +// ────────────────────────────────────────────────── + +test('test that creating a report with an expiration date works', async ({ page }) => { + const projectName = 'DatePickerProj ' + Math.floor(Math.random() * 10000); + const reportName = 'DatePickerReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + // Open the save report modal + await page.getByRole('button', { name: 'Save Report' }).click(); + await page.getByLabel('Name').fill(reportName); + + // The "Public" checkbox should be checked by default, showing the date picker + const datePicker = page + .getByRole('dialog') + .getByRole('button', { name: DATE_PICKER_BUTTON_PATTERN }); + await expect(datePicker).toBeVisible(); + await datePicker.click(); + + // Select a date in the next month + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + await page.getByRole('button', { name: /Next/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^15$/ }).first().click(); + + // Wait for the calendar to close + await expect(calendarGrid).not.toBeVisible(); + + // Create the report and verify it includes the public_until date + const [response] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports') && + response.request().method() === 'POST' && + response.status() === 201 + ), + page.getByRole('dialog').getByRole('button', { name: 'Create Report' }).click(), + ]); + const responseBody = await response.json(); + expect(responseBody.data.public_until).toBeTruthy(); +}); + +test('test that editing a report to make it public with expiration date works', async ({ + page, +}) => { + const projectName = 'EditDateProj ' + Math.floor(Math.random() * 10000); + const reportName = 'EditDateReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + // Open the save report modal and create a private report + await page.getByRole('button', { name: 'Save Report' }).click(); + await page.getByLabel('Name').fill(reportName); + + // Uncheck "Public" to create a private report + await page.getByLabel('Public').click(); + + await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports') && + response.request().method() === 'POST' && + response.status() === 201 + ), + page.getByRole('dialog').getByRole('button', { name: 'Create Report' }).click(), + ]); + + // Go to shared reports and edit + await goToReportingShared(page); + await expect(page.getByText(reportName)).toBeVisible(); + await expect(page.getByText('Private')).toBeVisible(); + + // Click more options and edit + await page + .getByRole('button', { name: new RegExp('Actions for Project ' + reportName) }) + .click(); + await page.getByRole('menuitem', { name: /^Edit Report/ }).click(); + + // Check "Public" to make it public - this should show the date picker + await page.getByLabel('Public').click(); + + // The date picker should now be visible + const datePicker = page + .getByRole('dialog') + .getByRole('button', { name: DATE_PICKER_BUTTON_PATTERN }); + await expect(datePicker).toBeVisible(); + await datePicker.click(); + + // Select a date in the next month + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + await page.getByRole('button', { name: /Next/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^20$/ }).first().click(); + + // Wait for the calendar to close + await expect(calendarGrid).not.toBeVisible(); + + // Update the report and verify it includes the public_until date + const [response] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Report' }).click(), + ]); + const responseBody = await response.json(); + expect(responseBody.data.public_until).toBeTruthy(); + expect(responseBody.data.is_public).toBe(true); +}); + +test('test that shared report with No Client filter shows entries without a client', async ({ + page, +}) => { + const clientName = 'NoClientCli ' + Math.floor(Math.random() * 10000); + const projectName = 'NoClientProj ' + Math.floor(Math.random() * 10000); + const reportName = 'NoClientReport ' + Math.floor(Math.random() * 10000); + + await createClient(page, clientName); + await createProjectWithClient(page, projectName, clientName); + await createTimeEntryWithProject(page, projectName, '1h'); + await createBareTimeEntry(page, 'Entry without client', '2h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + // Filter by "No Client" + await page.getByRole('button', { name: 'Clients' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Client' }).click(); + await page.keyboard.press('Escape'); + await waitForReportingUpdate(page); + + const { shareableLink } = await saveAsSharedReport(page, reportName); + + // View the shared report + await page.goto(shareableLink); + await expect(page.getByText('Reporting')).toBeVisible(); + await expect(page.getByText('Total')).toBeVisible(); + await expect(page.getByText(projectName)).not.toBeVisible(); +}); + +test('test that shared report with No Tag filter shows entries without tags', async ({ page }) => { + const tagName = 'NoTagFilter ' + Math.floor(Math.random() * 10000); + const reportName = 'NoTagReport ' + Math.floor(Math.random() * 10000); + + await createTimeEntryWithTag(page, tagName, '1h'); + await createBareTimeEntry(page, 'Entry without tags', '2h'); + + await goToReporting(page); + await expect(page.getByText('Total')).toBeVisible(); + + // Filter by "No Tag" + await page.getByRole('button', { name: 'Tags' }).first().click(); + await page.getByRole('option').filter({ hasText: 'No Tag' }).click(); + await page.keyboard.press('Escape'); + await waitForReportingUpdate(page); + + const { shareableLink } = await saveAsSharedReport(page, reportName); + + // View the shared report + await page.goto(shareableLink); + await expect(page.getByText('Reporting')).toBeVisible(); + await expect(page.getByText('Total')).toBeVisible(); +}); + +test('test that updating expiration date on already-public report works', async ({ page }) => { + const projectName = 'UpdateExpDateProj ' + Math.floor(Math.random() * 10000); + const reportName = 'UpdateExpDateReport ' + Math.floor(Math.random() * 10000); + + await createProject(page, projectName); + await createTimeEntryWithProject(page, projectName, '1h'); + + await goToReporting(page); + await expect(page.getByTestId('reporting_view').getByText(projectName)).toBeVisible(); + + // Create a public report (already public by default) + await saveAsSharedReport(page, reportName); + + // Go to shared reports and edit + await goToReportingShared(page); + await expect(page.getByText(reportName)).toBeVisible(); + + // Click more options and edit + await page + .getByRole('button', { name: new RegExp('Actions for Project ' + reportName) }) + .click(); + await page.getByRole('menuitem', { name: /^Edit Report/ }).click(); + + // The date picker should be visible (report is already public) + const datePicker = page + .getByRole('dialog') + .getByRole('button', { name: DATE_PICKER_BUTTON_PATTERN }); + await expect(datePicker).toBeVisible(); + await datePicker.click(); + + // Select the 25th of next month + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + await page.getByRole('button', { name: /Next/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^25$/ }).first().click(); + + // Wait for the calendar to close + await expect(calendarGrid).not.toBeVisible(); + + // Update the report and verify it includes the correct public_until date + const [response] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Report' }).click(), + ]); + const responseBody = await response.json(); + expect(responseBody.data.public_until).toBeTruthy(); + + // Verify the date is the 25th of a future month + const returnedDate = new Date(responseBody.data.public_until); + expect(returnedDate.getUTCDate()).toBe(25); + + // The returned date should be in the future + const now = new Date(); + expect(returnedDate.getTime()).toBeGreaterThan(now.getTime()); +}); diff --git a/e2e/tags.spec.ts b/e2e/tags.spec.ts index 8b89aa2b..36b8defd 100644 --- a/e2e/tags.spec.ts +++ b/e2e/tags.spec.ts @@ -1,4 +1,5 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; diff --git a/e2e/tasks.spec.ts b/e2e/tasks.spec.ts index 8e2073db..646f618c 100644 --- a/e2e/tasks.spec.ts +++ b/e2e/tasks.spec.ts @@ -1,4 +1,5 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; diff --git a/e2e/time.spec.ts b/e2e/time.spec.ts index 2b01ddfb..06c7f2eb 100644 --- a/e2e/time.spec.ts +++ b/e2e/time.spec.ts @@ -1,6 +1,7 @@ import { PLAYWRIGHT_BASE_URL } from '../playwright/config'; import { test } from '../playwright/fixtures'; -import { expect, Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Locator, Page } from '@playwright/test'; import { assertThatTimerHasStarted, assertThatTimerIsStopped, @@ -8,11 +9,39 @@ import { startOrStopTimerWithButton, stoppedTimeEntryResponse, } from './utils/currentTimeEntry'; +import { createProject, createBillableProject, createBareTimeEntry } from './utils/reporting'; + +// Date picker button name patterns for different date formats +// Matches: "Pick a date", "YYYY-MM-DD", "DD/MM/YYYY", "DD.MM.YYYY", "MM/DD/YYYY", "DD-MM-YYYY", "MM-DD-YYYY" +const DATE_PICKER_BUTTON_PATTERN = + /^Pick a date$|^\d{4}-\d{2}-\d{2}$|^\d{2}\/\d{2}\/\d{4}$|^\d{2}\.\d{2}\.\d{4}$/; +// Same pattern but without "Pick a date" - for when we expect an actual date to be displayed +const DATE_DISPLAY_PATTERN = /^\d{4}-\d{2}-\d{2}$|^\d{2}\/\d{2}\/\d{4}$|^\d{2}\.\d{2}\.\d{4}$/; + +/** + * Extracts day of month from an ISO timestamp string + */ +function getDayFromTimestamp(timestamp: string): number { + return new Date(timestamp).getUTCDate(); +} + +/** + * Extracts month (1-indexed) from an ISO timestamp string + */ +function getMonthFromTimestamp(timestamp: string): number { + return new Date(timestamp).getUTCMonth() + 1; +} async function goToTimeOverview(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/time'); } +async function goToOrganizationSettings(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/dashboard'); + await page.locator('[data-testid="organization_switcher"]:visible').click(); + await page.getByText('Organization Settings').click(); +} + async function createEmptyTimeEntry(page: Page) { await Promise.all([ newTimeEntryResponse(page), @@ -52,7 +81,7 @@ test('test that starting and stopping an empty time entry shows a new time entry // Test that description update works async function assertThatTimeEntryRowIsStopped(newTimeEntry: Locator) { - await expect(newTimeEntry.getByTestId('timer_button')).toHaveClass(/bg-accent-300\/70/); + await expect(newTimeEntry.getByTestId('timer_button')).toHaveClass(/bg-quaternary/); } test('test that updating a description of a time entry in the overview works on blur', async ({ @@ -223,7 +252,7 @@ test('test that starting a time entry from the overview works', async ({ page }) const newTimeEntry = timeEntryRows.first(); const startButton = newTimeEntry.getByTestId('timer_button'); - await expect(startButton).toHaveClass(/bg-accent-300\/70/); + await expect(startButton).toHaveClass(/bg-quaternary/); await Promise.all([ page.waitForResponse(async (response) => { @@ -306,12 +335,650 @@ test.skip('test that load more works when the end of page is reached', async ({ // TODO: Test that time entries are loaded at the end of the page -// TODO: Test manual time entries - // TODO: Test Grouped time entries by description/project -// TODO: Add Test for Date Update +// Date Update Tests + +test('test that updating the start date of a time entry via the edit modal works', async ({ + page, +}) => { + await createBareTimeEntry(page, 'Date edit test', '1h'); + await goToTimeOverview(page); + + const timeEntryRows = page.locator('[data-testid="time_entry_row"]'); + const newTimeEntry = timeEntryRows.first(); + + // Open edit modal via the actions dropdown + const actionsDropdown = newTimeEntry + .getByRole('button', { name: 'Actions for the time entry' }) + .first(); + await actionsDropdown.click(); + await page.getByTestId('time_entry_edit').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Click the start date picker (first date picker button in the Start section) + const startDatePicker = page + .getByRole('dialog') + .getByRole('button', { name: DATE_PICKER_BUTTON_PATTERN }) + .first(); + await startDatePicker.click(); + + // Navigate to the previous month and select the 15th + await page.getByRole('button', { name: /Previous/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^15$/ }).first().click(); + + // Get current month to calculate expected month after going to previous + const now = new Date(); + const expectedMonth = now.getMonth() === 0 ? 12 : now.getMonth(); // Previous month (1-indexed) + + // Submit the update and verify the response has correct date + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const updateBody = await updateResponse.json(); + expect(updateBody.data.start).toBeTruthy(); + expect(updateBody.data.end).toBeTruthy(); + // Verify the day was changed to 15th + expect(getDayFromTimestamp(updateBody.data.start)).toBe(15); + // Verify the month is the previous month + expect(getMonthFromTimestamp(updateBody.data.start)).toBe(expectedMonth); +}); + +test('test that setting a date in the create modal works', async ({ page }) => { + await goToTimeOverview(page); + + // Get today's date to compare later + const today = new Date(); + + // Open create modal + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Set description + await page + .getByRole('dialog') + .getByRole('textbox', { name: 'Description' }) + .fill('Date picker test entry'); + + // Set duration first (to ensure the form is valid) + await page.locator('[role="dialog"] input[name="Duration"]').fill('1h'); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + // Click the start date picker + const startDatePicker = page + .getByRole('dialog') + .getByRole('button', { name: DATE_PICKER_BUTTON_PATTERN }) + .first(); + await startDatePicker.click(); + + // Wait for calendar to appear + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + + // Navigate to previous month and select the 15th (a day that's always in the middle of the month) + await page.getByRole('button', { name: /Previous/i }).click(); + await page.getByRole('gridcell', { name: '15' }).getByRole('button').click(); + + // Wait for calendar to close + await expect(calendarGrid).not.toBeVisible(); + + // Get current month to calculate expected month after going to previous + const expectedMonth = today.getMonth() === 0 ? 12 : today.getMonth(); // Previous month (1-indexed) + + // Submit and verify creation succeeds with correct date + const [createResponse] = await Promise.all([ + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 201 + ), + page.getByRole('button', { name: 'Create Time Entry' }).click(), + ]); + const createBody = await createResponse.json(); + expect(createBody.data.start).toBeTruthy(); + // Verify the day was set to 15th + expect(getDayFromTimestamp(createBody.data.start)).toBe(15); + // Verify the month is the previous month + expect(getMonthFromTimestamp(createBody.data.start)).toBe(expectedMonth); +}); + +test('test that updating the date via the time entry row range selector works', async ({ + page, +}) => { + await createBareTimeEntry(page, 'Date range test', '1h'); + await goToTimeOverview(page); + + const timeEntryRows = page.locator('[data-testid="time_entry_row"]'); + const newTimeEntry = timeEntryRows.first(); + await expect(newTimeEntry).toBeVisible(); + + // Open the time range popover + const timeEntryRangeElement = newTimeEntry.getByTestId('time_entry_range_selector'); + await timeEntryRangeElement.click(); + + // Verify the range selector dropdown is open + const rangeStart = page.getByTestId('time_entry_range_start'); + await expect(rangeStart).toBeVisible(); + + // Click the start date picker button within the range selector + const startDatePicker = page.getByRole('button', { name: DATE_DISPLAY_PATTERN }).first(); + await expect(startDatePicker).toBeVisible(); + await startDatePicker.click(); + + // Wait for the calendar to appear and select a day + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + + // Navigate to previous month and select the 5th + await page.getByRole('button', { name: /Previous/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^5$/ }).first().click(); + + // Get current month to calculate expected month after going to previous + const now = new Date(); + const expectedMonth = now.getMonth() === 0 ? 12 : now.getMonth(); // Previous month (1-indexed) + + // Verify the time entry update API call succeeds with correct date + const updateResponse = await page.waitForResponse(async (response) => { + return ( + response.status() === 200 && + response.request().method() === 'PUT' && + (await response.headerValue('Content-Type')) === 'application/json' + ); + }); + const updateBody = await updateResponse.json(); + expect(updateBody.data.start).toBeTruthy(); + // Verify the day was changed to 5th + expect(getDayFromTimestamp(updateBody.data.start)).toBe(5); + // Verify the month is the previous month + expect(getMonthFromTimestamp(updateBody.data.start)).toBe(expectedMonth); +}); + +test('test that updating the end date via the time entry row range selector works', async ({ + page, +}) => { + await createBareTimeEntry(page, 'End date range test', '1h'); + await goToTimeOverview(page); + + const timeEntryRows = page.locator('[data-testid="time_entry_row"]'); + const newTimeEntry = timeEntryRows.first(); + await expect(newTimeEntry).toBeVisible(); + + // Open the time range popover + const timeEntryRangeElement = newTimeEntry.getByTestId('time_entry_range_selector'); + await timeEntryRangeElement.click(); + + // Verify the range selector dropdown is open + const rangeEnd = page.getByTestId('time_entry_range_end'); + await expect(rangeEnd).toBeVisible(); + + // Click the end date picker button (second date picker) + const datePickers = page.getByRole('button', { name: DATE_DISPLAY_PATTERN }); + const endDatePicker = datePickers.nth(1); + await expect(endDatePicker).toBeVisible(); + await endDatePicker.click(); + + // Wait for the calendar to appear + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + + // Navigate to next month and select the 20th (to ensure end > start) + await page.getByRole('button', { name: /Next/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^20$/ }).first().click(); + + // Get current month to calculate expected month after going to next + const now = new Date(); + const expectedMonth = now.getMonth() === 11 ? 1 : now.getMonth() + 2; // Next month (1-indexed) + + // Verify the time entry update API call succeeds with correct date + const updateResponse = await page.waitForResponse(async (response) => { + return ( + response.status() === 200 && + response.request().method() === 'PUT' && + (await response.headerValue('Content-Type')) === 'application/json' + ); + }); + const updateBody = await updateResponse.json(); + expect(updateBody.data.end).toBeTruthy(); + // Verify the day was changed to 20th + expect(getDayFromTimestamp(updateBody.data.end)).toBe(20); + // Verify the month is the next month + expect(getMonthFromTimestamp(updateBody.data.end)).toBe(expectedMonth); +}); + +test('test that date picker displays date in organization date format', async ({ page }) => { + // First change the organization date format to DD/MM/YYYY + await goToOrganizationSettings(page); + await page.getByLabel('Date Format').click(); + await page.getByRole('option', { name: 'DD/MM/YYYY' }).click(); + await Promise.all([ + page + .locator('form') + .filter({ hasText: 'Date Format' }) + .getByRole('button', { name: 'Save' }) + .click(), + page.waitForResponse( + async (response) => + response.url().includes('/organizations/') && + response.request().method() === 'PUT' && + response.status() === 200 && + (await response.json()).data.date_format === 'slash-separated-dd-mm-yyyy' + ), + ]); + + // Create a time entry and open the edit modal + await createBareTimeEntry(page, 'Date format test', '1h'); + await goToTimeOverview(page); + + const timeEntryRows = page.locator('[data-testid="time_entry_row"]'); + const newTimeEntry = timeEntryRows.first(); + await expect(newTimeEntry).toBeVisible(); + + // Open edit modal + const actionsDropdown = newTimeEntry + .getByRole('button', { name: 'Actions for the time entry' }) + .first(); + await actionsDropdown.click(); + await page.getByTestId('time_entry_edit').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Verify the date picker shows the date in DD/MM/YYYY format + const datePicker = page + .getByRole('dialog') + .getByRole('button', { name: /^\d{2}\/\d{2}\/\d{4}$/ }) + .first(); + await expect(datePicker).toBeVisible(); +}); // TODO: Test that project can be created in the time entry row -// TODO: Add Tests for Mass Update +test('test that editing billable status via the edit modal works', async ({ page }) => { + await goToTimeOverview(page); + await createEmptyTimeEntry(page); + + const timeEntryRows = page.locator('[data-testid="time_entry_row"]'); + const newTimeEntry = timeEntryRows.first(); + await assertThatTimeEntryRowIsStopped(newTimeEntry); + + // Open edit modal via the actions dropdown + const actionsDropdown = newTimeEntry + .getByRole('button', { name: 'Actions for the time entry' }) + .first(); + await actionsDropdown.click(); + await page.getByTestId('time_entry_edit').click(); + + // Verify the edit dialog is visible + await expect(page.getByRole('dialog')).toBeVisible(); + + // Change billable status to Billable + await page + .getByRole('dialog') + .getByRole('combobox') + .filter({ hasText: 'Non-Billable' }) + .click(); + await page.getByRole('option', { name: 'Billable', exact: true }).click(); + + // Save the time entry and verify the response has billable=true + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const updateBody = await updateResponse.json(); + expect(updateBody.data.billable).toBe(true); + + // Verify the dialog closed + await expect(page.getByRole('dialog')).not.toBeVisible(); + + // Re-open the edit modal and verify it now shows "Billable" + await actionsDropdown.click(); + await page.getByTestId('time_entry_edit').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); +}); + +test('test that mass update billable status works', async ({ page }) => { + await goToTimeOverview(page); + await createEmptyTimeEntry(page); + + const timeEntryRows = page.locator('[data-testid="time_entry_row"]'); + await assertThatTimeEntryRowIsStopped(timeEntryRows.first()); + + // Select the time entry via the "Select All" checkbox + await page.getByLabel('Select All').click(); + await expect(page.getByText('1 selected')).toBeVisible(); + + // Open mass update modal via the Edit button in the mass action row + await page.getByRole('button', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Change billable status to Billable + await page + .getByRole('dialog') + .getByRole('combobox') + .filter({ hasText: 'Set billable status' }) + .click(); + await page.getByRole('option', { name: 'Billable', exact: true }).click(); + + // Submit the mass update + const [massUpdateResponse] = await Promise.all([ + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entries' }).click(), + ]); + const massUpdateBody = await massUpdateResponse.json(); + expect(massUpdateBody.success.length).toBeGreaterThan(0); + expect(massUpdateBody.error.length).toBe(0); + + // Verify dialog closes + await expect(page.getByRole('dialog')).not.toBeVisible(); + + // Verify the UI reflects the billable status by re-opening the edit modal + const actionsDropdown = page + .locator('[data-testid="time_entry_row"]') + .first() + .getByRole('button', { name: 'Actions' }); + await actionsDropdown.click(); + await page.getByTestId('time_entry_edit').click(); + await expect(page.getByRole('dialog')).toBeVisible(); + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); +}); + +test('test that setting billable status via the create modal works', async ({ page }) => { + await goToTimeOverview(page); + + // Open the dropdown menu and click "Manual time entry" + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Set description + await page + .getByRole('dialog') + .getByRole('textbox', { name: 'Description' }) + .fill('Billable create test'); + + // Change billable status to Billable + await page + .getByRole('dialog') + .getByRole('combobox') + .filter({ hasText: 'Non-Billable' }) + .click(); + await page.getByRole('option', { name: 'Billable', exact: true }).click(); + + // Set duration + await page.locator('[role="dialog"] input[name="Duration"]').fill('1h'); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + // Submit and verify the time entry was created with billable=true + await Promise.all([ + page.getByRole('button', { name: 'Create Time Entry' }).click(), + page.waitForResponse( + async (response) => + response.url().includes('/time-entries') && + response.status() === 201 && + (await response.json()).data.billable === true + ), + ]); +}); + +/** + * The following tests verify that changing the project on a time entry + * updates the billable status to match the new project's is_billable setting. + * + * Issue: https://github.com/solidtime-io/solidtime/issues/981 + */ + +test('test that changing project on a time entry row from non-billable to billable updates billable status', async ({ + page, +}) => { + const billableProjectName = 'Billable Row Project ' + Math.floor(1 + Math.random() * 10000); + const nonBillableProjectName = + 'NonBillable Row Project ' + Math.floor(1 + Math.random() * 10000); + + await createProject(page, nonBillableProjectName); + await createBillableProject(page, billableProjectName); + await createBareTimeEntry(page, 'Test billable row', '1h'); + + await goToTimeOverview(page); + const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first(); + + // Assign the non-billable project first + await timeEntryRow.getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: nonBillableProjectName }).click(); + await page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ); + + // Now switch to the billable project + await timeEntryRow.getByRole('button', { name: nonBillableProjectName }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + const updateResponse = await page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(true); +}); + +test('test that changing project on a time entry row from billable to non-billable updates billable status', async ({ + page, +}) => { + const billableProjectName = 'Billable Row Rev Project ' + Math.floor(1 + Math.random() * 10000); + const nonBillableProjectName = + 'NonBillable Row Rev Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createProject(page, nonBillableProjectName); + await createBareTimeEntry(page, 'Test billable row reverse', '1h'); + + await goToTimeOverview(page); + const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first(); + + // Assign the billable project first + await timeEntryRow.getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + const firstResponse = await page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ); + const firstBody = await firstResponse.json(); + expect(firstBody.data.billable).toBe(true); + + // Now switch to the non-billable project + await timeEntryRow.getByRole('button', { name: billableProjectName }).click(); + await page.getByRole('option', { name: nonBillableProjectName }).click(); + + const updateResponse = await page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(false); +}); + +test('test that changing project in edit modal from non-billable to billable updates billable status', async ({ + page, +}) => { + const billableProjectName = 'Billable Modal Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createBareTimeEntry(page, 'Test billable modal', '1h'); + + await goToTimeOverview(page); + const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first(); + + // Open edit modal + await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click(); + await page.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // Verify initially non-billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Select the billable project + await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + // Verify the billable dropdown updated to Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); + + // Save and verify + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(true); +}); + +test('test that opening edit modal for a time entry with manually overridden billable status preserves that status', async ({ + page, +}) => { + const billableProjectName = 'Billable Persist Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createBareTimeEntry(page, 'Test persist billable override', '1h'); + + await goToTimeOverview(page); + const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first(); + + // Open edit modal and assign the billable project + await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click(); + await page.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + // Verify it auto-set to Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); + + // Now manually override billable to Non-Billable via the dropdown + await page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }).click(); + await page.getByRole('option', { name: 'Non Billable' }).click(); + + // Verify it shows Non-Billable now + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Save + const [firstSaveResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const firstBody = await firstSaveResponse.json(); + expect(firstBody.data.billable).toBe(false); + + // Re-open the edit modal — the project_id watcher should NOT override billable back to true + await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click(); + await page.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // The billable dropdown should still show Non-Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Save without changes and verify the response still has billable=false + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(false); +}); + +test('test that changing project in edit modal from billable to non-billable updates billable status', async ({ + page, +}) => { + const billableProjectName = + 'Billable Modal Rev Project ' + Math.floor(1 + Math.random() * 10000); + const nonBillableProjectName = + 'NonBillable Modal Rev Project ' + Math.floor(1 + Math.random() * 10000); + + await createBillableProject(page, billableProjectName); + await createProject(page, nonBillableProjectName); + await createBareTimeEntry(page, 'Test billable modal reverse', '1h'); + + await goToTimeOverview(page); + const timeEntryRow = page.locator('[data-testid="time_entry_row"]').first(); + + // Open edit modal + await timeEntryRow.getByRole('button', { name: 'Actions for the time entry' }).first().click(); + await page.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog')).toBeVisible(); + + // First assign the billable project + await page.getByRole('dialog').getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option', { name: billableProjectName }).click(); + + // Verify billable status flipped to Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Billable' }) + ).toBeVisible(); + + // Now switch to the non-billable project + await page.getByRole('dialog').getByRole('button', { name: billableProjectName }).click(); + await page.getByRole('option', { name: nonBillableProjectName }).click(); + + // Verify billable status reverted to Non-Billable + await expect( + page.getByRole('dialog').getByRole('combobox').filter({ hasText: 'Non-Billable' }) + ).toBeVisible(); + + // Save and verify + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries/') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + page.getByRole('button', { name: 'Update Time Entry' }).click(), + ]); + const responseBody = await updateResponse.json(); + expect(responseBody.data.billable).toBe(false); +}); diff --git a/e2e/timetracker.spec.ts b/e2e/timetracker.spec.ts index c7896ca1..7b35ce65 100644 --- a/e2e/timetracker.spec.ts +++ b/e2e/timetracker.spec.ts @@ -7,9 +7,12 @@ import { startOrStopTimerWithButton, stoppedTimeEntryResponse, } from './utils/currentTimeEntry'; -import { Page } from '@playwright/test'; +import type { Page } from '@playwright/test'; import { newTagResponse } from './utils/tags'; +// Date picker button name patterns for different date formats +const DATE_DISPLAY_PATTERN = /^\d{4}-\d{2}-\d{2}$|^\d{2}\/\d{2}\/\d{4}$|^\d{2}\.\d{2}\.\d{4}$/; + async function goToDashboard(page: Page) { await page.goto(PLAYWRIGHT_BASE_URL + '/dashboard'); } @@ -187,10 +190,10 @@ test('test that entering a random value in the time range does not start the tim }) => { await goToDashboard(page); await page.getByTestId('time_entry_time').fill('asdasdasd'); - await page.getByTestId('time_entry_time').press('Tab'), + (await page.getByTestId('time_entry_time').press('Tab'), await page.locator( '[data-testid="dashboard_timer"] [data-testid="timer_button"].bg-accent-300/70' - ); + )); }); test('test that entering a time starts the timer on enter', async ({ page }) => { @@ -218,6 +221,11 @@ test('test that adding a new tag works', async ({ page }) => { page.getByRole('button', { name: 'Create Tag' }).click(), ]); + // Wait for tags query refetch after invalidation + await page.waitForResponse( + (response) => response.url().includes('/tags') && response.status() === 200 + ); + await page.getByTestId('tag_dropdown').click(); await expect(page.getByRole('option', { name: newTagName })).toBeVisible(); }); @@ -249,6 +257,63 @@ test('test that adding a new tag when the timer is running', async ({ page }) => await assertThatTimerIsStopped(page); }); +test('test that setting an end time with a different date via the timetracker range selector works', async ({ + page, +}) => { + await goToDashboard(page); + + // Start a timer + await Promise.all([newTimeEntryResponse(page), startOrStopTimerWithButton(page)]); + await assertThatTimerHasStarted(page); + + // Open the time range dropdown by clicking on the time display + await page.getByTestId('time_entry_time').click(); + const rangeStart = page.getByTestId('time_entry_range_start'); + await expect(rangeStart).toBeVisible(); + + // Click "Set End Time" button + await page.getByRole('button', { name: 'Set End Time' }).click(); + + // The end time picker should now be visible with a Confirm button + const rangeEnd = page.getByTestId('time_entry_range_end'); + await expect(rangeEnd).toBeVisible(); + const confirmButton = page.getByRole('button', { name: 'Confirm' }); + await expect(confirmButton).toBeVisible(); + + // Click the end date picker to change the date + const endDatePickers = page.getByRole('button', { name: DATE_DISPLAY_PATTERN }); + // The second date picker is the end date (first is the start date) + const endDatePicker = endDatePickers.nth(1); + await expect(endDatePicker).toBeVisible(); + await endDatePicker.click(); + + // Calendar should appear + const calendarGrid = page.getByRole('grid'); + await expect(calendarGrid).toBeVisible({ timeout: 5000 }); + + // Navigate to the next month and select a day to ensure end > start + await page.getByRole('button', { name: /Next/i }).click(); + await page.getByRole('gridcell').filter({ hasText: /^15$/ }).first().click(); + + // The dropdown should still be open after selecting a date (not auto-closed) + await expect(rangeEnd).toBeVisible(); + await expect(confirmButton).toBeVisible(); + + // Click Confirm to finalize and verify the API call + const [updateResponse] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/time-entries') && + response.request().method() === 'PUT' && + response.status() === 200 + ), + confirmButton.click(), + ]); + const updateBody = await updateResponse.json(); + expect(updateBody.data.start).toBeTruthy(); + expect(updateBody.data.end).toBeTruthy(); +}); + // test that search is working // test that adding a tag and project and starting the timer afterwards works and sets the project and tag correctly diff --git a/e2e/utils/currentTimeEntry.ts b/e2e/utils/currentTimeEntry.ts index 162975ec..908da317 100644 --- a/e2e/utils/currentTimeEntry.ts +++ b/e2e/utils/currentTimeEntry.ts @@ -1,4 +1,5 @@ -import { expect, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; export async function startOrStopTimerWithButton(page: Page) { await page.locator('[data-testid="dashboard_timer"] [data-testid="timer_button"]').click(); diff --git a/e2e/utils/mailpit.ts b/e2e/utils/mailpit.ts new file mode 100644 index 00000000..27d54d35 --- /dev/null +++ b/e2e/utils/mailpit.ts @@ -0,0 +1,53 @@ +import { expect } from '@playwright/test'; +import type { APIRequestContext } from '@playwright/test'; +import { MAILPIT_BASE_URL } from '../../playwright/config'; + +/** + * Search for emails in Mailpit matching the given query. + */ +export async function searchEmails( + request: APIRequestContext, + query: string +): Promise<{ messages: Array<{ ID: string; Subject: string }> }> { + const response = await request.get(`${MAILPIT_BASE_URL}/api/v1/search?query=${query}`); + return response.json(); +} + +/** + * Get the full email message from Mailpit by ID. + */ +export async function getMessage( + request: APIRequestContext, + messageId: string +): Promise<{ HTML: string; Text: string }> { + const response = await request.get(`${MAILPIT_BASE_URL}/api/v1/message/${messageId}`); + return response.json(); +} + +/** + * Find the invitation acceptance URL from a Mailpit email sent to the given address. + * Retries a few times to allow for email delivery delay. + */ +export async function getInvitationAcceptUrl( + request: APIRequestContext, + recipientEmail: string +): Promise { + let searchResult: { messages: Array<{ ID: string }> } = { messages: [] }; + + // Retry up to 5 times with 500ms delay to allow for email delivery + for (let attempt = 0; attempt < 5; attempt++) { + searchResult = await searchEmails( + request, + `to:${encodeURIComponent(recipientEmail)} subject:"Organization Invitation"` + ); + if (searchResult.messages.length > 0) break; + await new Promise((resolve) => setTimeout(resolve, 500)); + } + expect(searchResult.messages.length).toBeGreaterThan(0); + + const message = await getMessage(request, searchResult.messages[0].ID); + const acceptUrlMatch = message.HTML.match(/href="([^"]*team-invitations[^"]*)"/); + expect(acceptUrlMatch).toBeTruthy(); + + return acceptUrlMatch![1].replace(/&/g, '&'); +} diff --git a/e2e/utils/members.ts b/e2e/utils/members.ts new file mode 100644 index 00000000..7effa8ef --- /dev/null +++ b/e2e/utils/members.ts @@ -0,0 +1,68 @@ +import { expect } from '@playwright/test'; +import type { Browser, Page } from '@playwright/test'; +import { PLAYWRIGHT_BASE_URL } from '../../playwright/config'; +import { getInvitationAcceptUrl } from './mailpit'; + +/** + * Register a new user in a fresh browser context and return the page + context. + */ +export async function registerUser( + browser: Browser, + name: string, + email: string +): Promise<{ page: Page; close: () => Promise }> { + const context = await browser.newContext(); + const page = await context.newPage(); + + await page.goto(PLAYWRIGHT_BASE_URL + '/register'); + await page.getByLabel('Name').fill(name); + await page.getByLabel('Email').fill(email); + await page.getByLabel('Password', { exact: true }).fill('amazingpassword123'); + await page.getByLabel('Confirm Password').fill('amazingpassword123'); + await page.getByLabel('I agree to the Terms of').click(); + await page.getByRole('button', { name: 'Register' }).click(); + await page.waitForURL(PLAYWRIGHT_BASE_URL + '/dashboard'); + + return { page, close: () => context.close() }; +} + +/** + * Invite a user by email from the members page and accept the invitation + * through a second browser session, returning the accepted member to the + * members table as a real (non-placeholder) member. + * + * @param ownerPage – The page of the organization owner who sends the invite + * @param browser – Browser instance used to create a second context + * @param memberName – Display name for the new user + * @param memberEmail – Email address (must not be registered yet) + * @param role – Role button label: 'Employee' | 'Manager' | 'Administrator' + */ +export async function inviteAndAcceptMember( + ownerPage: Page, + browser: Browser, + memberName: string, + memberEmail: string, + role: 'Employee' | 'Manager' | 'Administrator' +): Promise { + // 1. Register the second user + const secondUser = await registerUser(browser, memberName, memberEmail); + + // 2. Send invitation from the owner + await ownerPage.goto(PLAYWRIGHT_BASE_URL + '/members'); + await ownerPage.getByRole('button', { name: 'Invite Member' }).click(); + await expect(ownerPage.getByPlaceholder('Member Email')).toBeVisible(); + await ownerPage.getByLabel('Email').fill(memberEmail); + await ownerPage.getByRole('button', { name: role }).click(); + await Promise.all([ + ownerPage.getByRole('button', { name: 'Invite Member', exact: true }).click(), + expect(ownerPage.getByRole('main')).toContainText(memberEmail), + ]); + + // 3. Retrieve the acceptance link from Mailpit and accept + const acceptUrl = await getInvitationAcceptUrl(secondUser.page.request, memberEmail); + await secondUser.page.goto(acceptUrl); + await secondUser.page.waitForURL(/dashboard/); + + // 4. Clean up + await secondUser.close(); +} diff --git a/e2e/utils/money.ts b/e2e/utils/money.ts index aed8176c..5a2f3c66 100644 --- a/e2e/utils/money.ts +++ b/e2e/utils/money.ts @@ -1,6 +1,6 @@ import { formatCents } from '../../resources/js/packages/ui/src/utils/money'; import type { CurrencyFormat } from '../../resources/js/packages/ui/src/utils/money'; -import { NumberFormat } from '../../resources/js/packages/ui/src/utils/number'; +import type { NumberFormat } from '../../resources/js/packages/ui/src/utils/number'; export function formatCentsWithOrganizationDefaults( cents: number, diff --git a/e2e/utils/reporting.ts b/e2e/utils/reporting.ts new file mode 100644 index 00000000..7e7bf973 --- /dev/null +++ b/e2e/utils/reporting.ts @@ -0,0 +1,320 @@ +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; +import { PLAYWRIGHT_BASE_URL } from '../../playwright/config'; + +// ────────────────────────────────────────────────── +// Navigation +// ────────────────────────────────────────────────── + +export async function goToReporting(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/reporting'); +} + +export async function goToReportingDetailed(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/reporting/detailed'); +} + +// ────────────────────────────────────────────────── +// Entity creation +// ────────────────────────────────────────────────── + +export async function createProject(page: Page, projectName: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); + await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); + await page.getByRole('button', { name: 'Create Project' }).click(); + await page.getByLabel('Project name').fill(projectName); + await Promise.all([ + page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(projectName)).toBeVisible(); +} + +export async function createBillableProject(page: Page, projectName: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); + await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); + await page.getByRole('button', { name: 'Create Project' }).click(); + await page.getByLabel('Project name').fill(projectName); + await page.getByText('Non-Billable').click(); + await page.getByText('Default Rate').click(); + await Promise.all([ + page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(projectName)).toBeVisible(); +} + +export async function createClient(page: Page, clientName: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/clients'); + await expect(page.getByRole('button', { name: 'Create Client' })).toBeVisible(); + await page.getByRole('button', { name: 'Create Client' }).click(); + await page.getByPlaceholder('Client Name').fill(clientName); + await Promise.all([ + page.getByRole('button', { name: 'Create Client' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/clients') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(clientName)).toBeVisible(); +} + +export async function createProjectWithClient(page: Page, projectName: string, clientName: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); + await expect(page.getByRole('button', { name: 'Create Project' })).toBeVisible(); + await page.getByRole('button', { name: 'Create Project' }).click(); + await page.getByLabel('Project name').fill(projectName); + + // Select client in the project create modal + await page.getByRole('dialog').getByRole('button', { name: 'No Client' }).click(); + await page.getByRole('option', { name: clientName }).click(); + + await Promise.all([ + page.getByRole('dialog').getByRole('button', { name: 'Create Project' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/projects') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(projectName)).toBeVisible(); +} + +export async function createTask(page: Page, projectName: string, taskName: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/projects'); + await expect(page.getByText(projectName)).toBeVisible(); + await page.getByText(projectName).click(); + await page.getByRole('button', { name: 'Create Task' }).click(); + await page.getByPlaceholder('Task Name').fill(taskName); + await Promise.all([ + page.getByRole('button', { name: 'Create Task' }).click(), + page.waitForResponse( + (response) => + response.url().includes('/tasks') && + response.request().method() === 'POST' && + response.status() === 201 + ), + ]); + await expect(page.getByText(taskName)).toBeVisible(); +} + +// ────────────────────────────────────────────────── +// Time entry creation +// ────────────────────────────────────────────────── + +export async function createTimeEntryWithProject( + page: Page, + projectName: string, + duration: string +) { + await page.goto(PLAYWRIGHT_BASE_URL + '/time'); + await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + + await page + .getByRole('dialog') + .getByRole('textbox', { name: 'Description' }) + .fill(`Entry for ${projectName}`); + + await page.getByRole('button', { name: 'No Project' }).click(); + await page.getByRole('option').filter({ hasText: projectName }).click(); + + await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + await Promise.all([ + page.getByRole('button', { name: 'Create Time Entry' }).click(), + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 201 + ), + ]); +} + +export async function createTimeEntryWithProjectAndTask( + page: Page, + projectName: string, + taskName: string, + duration: string +) { + await page.goto(PLAYWRIGHT_BASE_URL + '/time'); + await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + + await page + .getByRole('dialog') + .getByRole('textbox', { name: 'Description' }) + .fill(`Entry for ${projectName} - ${taskName}`); + + // Open the project/task dropdown + await page.getByRole('button', { name: 'No Project' }).click(); + + // Expand the project's tasks by clicking the "Tasks" button + const projectOption = page.getByRole('option').filter({ hasText: projectName }); + await projectOption.getByText(/Tasks/).click(); + + // Select the task (this also selects the project and closes the dropdown) + await page.getByText(taskName, { exact: true }).click(); + + await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + await Promise.all([ + page.getByRole('button', { name: 'Create Time Entry' }).click(), + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 201 + ), + ]); +} + +export async function createTimeEntryWithTag(page: Page, tagName: string, duration: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/time'); + await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + + await page + .getByRole('dialog') + .getByRole('textbox', { name: 'Description' }) + .fill(`Entry with tag ${tagName}`); + + // Add tag + await page.getByRole('button', { name: 'Tags' }).click(); + await page.getByText('Create new tag').click(); + await page.getByPlaceholder('Tag Name').fill(tagName); + await Promise.all([ + page.getByRole('button', { name: 'Create Tag' }).click(), + page.waitForResponse( + (response) => response.url().includes('/tags') && response.status() === 201 + ), + ]); + + await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + await Promise.all([ + page.getByRole('button', { name: 'Create Time Entry' }).click(), + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 201 + ), + ]); +} + +export async function createBareTimeEntry(page: Page, description: string, duration: string) { + await page.goto(PLAYWRIGHT_BASE_URL + '/time'); + await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + + await page.getByRole('dialog').getByRole('textbox', { name: 'Description' }).fill(description); + + await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + await Promise.all([ + page.getByRole('button', { name: 'Create Time Entry' }).click(), + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 201 + ), + ]); +} + +export async function createTimeEntryWithBillableStatus( + page: Page, + isBillable: boolean, + duration: string +) { + await page.goto(PLAYWRIGHT_BASE_URL + '/time'); + await expect(page.getByRole('button', { name: 'Time entry actions' })).toBeVisible(); + await page.getByRole('button', { name: 'Time entry actions' }).click(); + await page.getByRole('menuitem', { name: 'Manual time entry' }).click(); + + await page + .getByRole('dialog') + .getByRole('textbox', { name: 'Description' }) + .fill(`Time entry ${isBillable ? 'billable' : 'non-billable'}`); + + if (isBillable) { + await page + .getByRole('dialog') + .getByRole('combobox') + .filter({ hasText: 'Non-Billable' }) + .click(); + await page.getByRole('option', { name: 'Billable', exact: true }).click(); + } + + await page.locator('[role="dialog"] input[name="Duration"]').fill(duration); + await page.locator('[role="dialog"] input[name="Duration"]').press('Tab'); + + await Promise.all([ + page.getByRole('button', { name: 'Create Time Entry' }).click(), + page.waitForResponse( + (response) => response.url().includes('/time-entries') && response.status() === 201 + ), + ]); +} + +// ────────────────────────────────────────────────── +// Wait helpers +// ────────────────────────────────────────────────── + +export async function waitForReportingUpdate(page: Page) { + await page.waitForResponse( + (response) => + response.url().includes('/time-entries/aggregate') && response.status() === 200 + ); +} + +export async function waitForDetailedReportingUpdate(page: Page) { + await page.waitForResponse( + (response) => + response.url().includes('/time-entries') && + !response.url().includes('/aggregate') && + response.request().method() === 'GET' && + response.status() === 200 + ); +} + +// ────────────────────────────────────────────────── +// Shared report helpers +// ────────────────────────────────────────────────── + +export async function goToReportingShared(page: Page) { + await page.goto(PLAYWRIGHT_BASE_URL + '/reporting/shared'); +} + +export async function saveAsSharedReport( + page: Page, + reportName: string +): Promise<{ shareableLink: string }> { + await page.getByRole('button', { name: 'Save Report' }).click(); + await page.getByLabel('Name').fill(reportName); + // "Public" checkbox is checked by default + const [response] = await Promise.all([ + page.waitForResponse( + (response) => + response.url().includes('/reports') && + response.request().method() === 'POST' && + response.status() === 201 + ), + page.getByRole('dialog').getByRole('button', { name: 'Create Report' }).click(), + ]); + const responseBody = await response.json(); + // Wait for navigation to shared reports page + await page.waitForURL('**/reporting/shared'); + return { shareableLink: responseBody.data.shareable_link }; +} diff --git a/e2e/utils/tags.ts b/e2e/utils/tags.ts index 888d6794..f3d0e86e 100644 --- a/e2e/utils/tags.ts +++ b/e2e/utils/tags.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test'; +import type { Page } from '@playwright/test'; export function newTagResponse(page: Page, { name = '' } = {}) { return page.waitForResponse(async (response) => { diff --git a/package-lock.json b/package-lock.json index a481c98a..1af5326c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,13 @@ { - "name": "html", + "name": "solidtime", "lockfileVersion": 3, "requires": true, "packages": { "": { + "workspaces": [ + "resources/js/packages/ui", + "resources/js/packages/api" + ], "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/vue": "^1.0.6", @@ -21,47 +25,50 @@ "@tanstack/vue-table": "^8.21.2", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.3.0", - "@vueuse/core": "^12.8.2", - "@vueuse/integrations": "^12.5.0", + "@vueuse/core": "^14.0.0", + "@vueuse/integrations": "^14.0.0", + "@zodios/core": "^10.9.6", "chroma-js": "3.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dayjs": "^1.11.11", - "echarts": "^5.5.0", - "focus-trap": "^7.6.0", + "echarts": "^6.0.0", + "focus-trap": "^8.0.0", "lucide-vue-next": "^0.487.0", "parse-duration": "^2.0.1", - "pinia": "^2.1.7", + "pinia": "^3.0.0", "radix-vue": "^1.9.6", - "reka-ui": "^2.2.0", + "reka-ui": "^2.7.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "vue-echarts": "^7.0.3" + "vue-echarts": "^8.0.0", + "zod": "^3.23.8" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", - "@inertiajs/vue3": "^1.0.0", + "@inertiajs/vue3": "^2.0.0", "@playwright/test": "^1.41.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", - "@types/chroma-js": "2.4.5", + "@types/chroma-js": "^3.1.0", "@types/node": "^22.10.10", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/tsconfig": "^0.5.1", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.0", "autoprefixer": "^10.4.20", "axios": "^1.6.4", "eslint-plugin-unused-imports": "^4.1.4", - "laravel-vite-plugin": "^1.0.0", + "laravel-vite-plugin": "^2.1.0", "openapi-zod-client": "^1.16.2", "postcss": "^8.4.47", + "postcss-import": "^15.1.0", "postcss-nesting": "^12.1.5", "tailwindcss": "^3.4.13", "typescript": "^5.7.3", - "vite": "^6.0.11", - "vite-plugin-checker": "^0.8.0", + "vite": "^7.0.0", + "vite-plugin-checker": "^0.12.0", "vue": "^3.5.0", - "vue-tsc": "^2.2.0" + "vue-tsc": "^3.0.0" } }, "node_modules/@alloc/quick-lru": { @@ -76,20 +83,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "11.7.2", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", @@ -184,24 +177,24 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -209,22 +202,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -250,16 +243,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -267,14 +260,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -293,30 +286,40 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -326,27 +329,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -354,26 +357,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -383,70 +386,59 @@ } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -457,13 +449,12 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -474,13 +465,12 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -491,13 +481,12 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -508,13 +497,12 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -525,13 +513,12 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -542,13 +529,12 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -559,13 +545,12 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -576,13 +561,12 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -593,13 +577,12 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -610,13 +593,12 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -627,13 +609,12 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -644,13 +625,12 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -661,13 +641,12 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -678,13 +657,12 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -695,13 +673,12 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -712,13 +689,12 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -729,13 +705,12 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,13 +721,12 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -763,13 +737,12 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -780,13 +753,12 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -796,14 +768,29 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -814,13 +801,12 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -831,13 +817,12 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -848,13 +833,12 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -865,9 +849,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.0.tgz", - "integrity": "sha512-WhCn7Z7TauhBtmzhvKpoQs0Wwb/kBcy4CwpuI0/eEIr2Lx2auxmulAzLr91wVZJaz47iUZdkXOK7WlAfxGKCnA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -883,22 +867,22 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -907,19 +891,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "license": "Apache-2.0", "peer": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -930,9 +917,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -941,7 +928,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, @@ -953,18 +940,21 @@ } }, "node_modules/@eslint/js": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", - "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "license": "Apache-2.0", "peer": true, "engines": { @@ -972,114 +962,127 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@eslint/core": "^0.13.0", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.9" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", - "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.9" + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/utils": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", - "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, "node_modules/@floating-ui/vue": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.6.tgz", - "integrity": "sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.10.tgz", + "integrity": "sha512-vdf8f6rHnFPPLRsmL4p12wYl+Ux4mOJOkjzKEMYVnwdf7UFdvBtHlLvQyx8iKG5vhPRbDRgZxdtpmyigDPjzYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.0.0", - "@floating-ui/utils": "^0.2.9", + "@floating-ui/dom": "^1.7.5", + "@floating-ui/utils": "^0.2.10", "vue-demi": ">=0.13.0" } }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@fullcalendar/core": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.18.tgz", - "integrity": "sha512-cD7XtZIZZ87Cg2+itnpsONCsZ89VIfLLDZ22pQX4IQVWlpYUB3bcCf878DhWkqyEen6dhi5ePtBoqYgm5K+0fQ==", + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", + "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", "license": "MIT", "dependencies": { "preact": "~10.12.1" } }, "node_modules/@fullcalendar/daygrid": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.18.tgz", - "integrity": "sha512-s452Zle1SdMEzZDw+pDczm8m3JLIZzS9ANMThXTnqeqJewW1gqNFYas18aHypJSgF9Fh9rDJjTSUw04BpXB/Mg==", + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.20.tgz", + "integrity": "sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.18" + "@fullcalendar/core": "~6.1.20" } }, "node_modules/@fullcalendar/interaction": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.18.tgz", - "integrity": "sha512-f/mD5RTjzw+Q6MGTMZrLCgIrQLIUUO9NV/58aM2J6ZBQZeRlNizDqmqldqyG+j49zj2vFhUfZibPrVKWm5yA4Q==", + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.20.tgz", + "integrity": "sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.18" + "@fullcalendar/core": "~6.1.20" } }, "node_modules/@fullcalendar/timegrid": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.18.tgz", - "integrity": "sha512-T/ouhs+T1tM8JcW7Cjx+KiohL/qQWKqvRITwjol8ktJ1e1N/6noC40/obR1tyolqOxMRWHjJkYoj9fUqfoez9A==", + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.20.tgz", + "integrity": "sha512-4H+/MWbz3ntA50lrPif+7TsvMeX3R1GSYjiLULz0+zEJ7/Yfd9pupZmAwUs/PBpA6aAcFmeRr0laWfcz1a9V1A==", "license": "MIT", "dependencies": { - "@fullcalendar/daygrid": "~6.1.18" + "@fullcalendar/daygrid": "~6.1.20" }, "peerDependencies": { - "@fullcalendar/core": "~6.1.18" + "@fullcalendar/core": "~6.1.20" } }, "node_modules/@fullcalendar/vue3": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.18.tgz", - "integrity": "sha512-YMagwTumxsIx3GFYWLa9Yr73EMA+JuH6S3EeZGS+rEjvG5fDGdf+33rxGMzmw+LdO7SWi3ctbzRnJlv3fnm3RQ==", + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/vue3/-/vue3-6.1.20.tgz", + "integrity": "sha512-8qg6pS27II9QBwFkkJC+7SfflMpWqOe7i3ii5ODq9KpLAjwQAd/zjfq8RvKR1Yryoh5UmMCmvRbMB7i4RGtqog==", "license": "MIT", "peerDependencies": { - "@fullcalendar/core": "~6.1.18", + "@fullcalendar/core": "~6.1.20", "vue": "^3.0.11" } }, @@ -1103,33 +1106,19 @@ } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "license": "Apache-2.0", "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1145,9 +1134,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "license": "Apache-2.0", "peer": true, "engines": { @@ -1159,80 +1148,95 @@ } }, "node_modules/@inertiajs/core": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-1.3.0.tgz", - "integrity": "sha512-TJ8R1eUYY473m9DaKlCPRdHTdznFWTDuy5VvEzXg3t/hohbDQedLj46yn/uAqziJPEUZJrSftZzPI2NMzL9tQA==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.3.13.tgz", + "integrity": "sha512-qMHRnb59k/HehXw/WfQt5kPV0k9RapfFcWJZINJnYMwfHDEJ21iNVZjsJHmDN7yWdZmG1Dxi9FP4xarWWgdosQ==", "dev": true, "license": "MIT", "dependencies": { - "axios": "^1.6.0", - "deepmerge": "^4.0.0", - "nprogress": "^0.2.0", - "qs": "^6.9.0" + "@types/lodash-es": "^4.17.12", + "axios": "^1.13.2", + "laravel-precognition": "^1.0.1", + "lodash-es": "^4.17.23", + "qs": "^6.14.1" } }, "node_modules/@inertiajs/vue3": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-1.3.0.tgz", - "integrity": "sha512-GizqdCM3u4JWunit3uUbW4fEmTLKQTi1W7VvPRdrNy8XDt4Qy2cCmfFjq+aH5tHBSS3fI/ngYuhN7XvwqNaKvw==", + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.3.13.tgz", + "integrity": "sha512-e3uAc4Em6GrUo/51IXmckXyv7GTX0QacTGyP80NVXYhcgUJOH/lp+fzLbAhaiTSoG+4zuT/aKMAJRmXXX6CEGg==", "dev": true, "license": "MIT", "dependencies": { - "@inertiajs/core": "1.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.isequal": "^4.5.0" + "@inertiajs/core": "2.3.13", + "@types/lodash-es": "^4.17.12", + "laravel-precognition": "^1.0.1", + "lodash-es": "^4.17.23" }, "peerDependencies": { "vue": "^3.0.0" } }, "node_modules/@internationalized/date": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.0.tgz", - "integrity": "sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.1.tgz", + "integrity": "sha512-oJrXtQiAXLvT9clCf1K4kxp3eKsQhIaZqxEyowkBcsvZDdZkbWrVmnGknxs5flTD0VGsxrxKgBCZty1EzoiMzA==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, "node_modules/@internationalized/number": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.1.tgz", - "integrity": "sha512-UVsb4bCwbL944E0SX50CHFtWEeZ2uB5VozZ5yDXJdq6iPZsZO5p+bjVMZh2GxHf4Bs/7xtDCcPwEa2NU9DaG/g==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=12" + "node": "20 || >=22" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1244,25 +1248,16 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1287,6 +1282,154 @@ "fs-extra": "^10.1.0" } }, + "node_modules/@microsoft/api-extractor": { + "version": "7.56.0", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.56.0.tgz", + "integrity": "sha512-H0V69QG5jIb9Ayx35NVBv2lOgFSS3q+Eab2oyGEy0POL3ovYPST+rCNPbwYoczOZXNG8IKjWUmmAMxmDTsXlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor-model": "7.32.2", + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.0", + "@rushstack/node-core-library": "5.19.1", + "@rushstack/rig-package": "0.6.0", + "@rushstack/terminal": "0.21.0", + "@rushstack/ts-command-line": "5.1.7", + "diff": "~8.0.2", + "lodash": "~4.17.15", + "minimatch": "10.0.3", + "resolve": "~1.22.1", + "semver": "~7.5.4", + "source-map": "~0.6.1", + "typescript": "5.8.2" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.32.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.32.2.tgz", + "integrity": "sha512-Ussc25rAalc+4JJs9HNQE7TuO9y6jpYQX9nWD1DhqUzYPBr3Lr7O9intf+ZY8kD5HnIqeIRJX7ccCT0QyBy2Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.0", + "@rushstack/node-core-library": "5.19.1" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.0.tgz", + "integrity": "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "ajv": "~8.12.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1322,36 +1465,26 @@ "node": ">= 8" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@pkgr/core": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.2.tgz", - "integrity": "sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/unts" + "url": "https://opencollective.com/pkgr" } }, "node_modules/@playwright/test": { - "version": "1.51.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz", - "integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", + "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.51.1" + "playwright": "1.58.1" }, "bin": { "playwright": "cli.js" @@ -1360,28 +1493,68 @@ "node": ">=18" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", - "cpu": [ - "arm" - ], + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", - "cpu": [ + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1389,13 +1562,12 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1403,13 +1575,12 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1417,13 +1588,12 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1431,13 +1601,12 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1445,13 +1614,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1459,13 +1627,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1473,13 +1640,12 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1487,41 +1653,64 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1529,13 +1718,12 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1543,13 +1731,12 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1557,13 +1744,12 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1571,13 +1757,12 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1585,27 +1770,51 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1613,13 +1822,25 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], - "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], "license": "MIT", "optional": true, "os": [ @@ -1627,13 +1848,12 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1641,15 +1861,223 @@ ] }, "node_modules/@rushstack/eslint-patch": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz", - "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", + "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "license": "MIT" + }, + "node_modules/@rushstack/node-core-library": { + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.19.1.tgz", + "integrity": "sha512-ESpb2Tajlatgbmzzukg6zyAhH+sICqJR2CNXNhXcEbz6UGCQfrKCtkxOpJTftWc8RGouroHG0Nud1SJAszvpmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, + "node_modules/@rushstack/node-core-library/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/@rushstack/problem-matcher": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", + "integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.6.0.tgz", + "integrity": "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "~1.22.1", + "strip-json-comments": "~3.1.1" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.21.0.tgz", + "integrity": "sha512-cLaI4HwCNYmknM5ns4G+drqdEB6q3dCPV423+d3TZeBusYSSm09+nR7CnhzJMjJqeRcdMAaLnrA4M/3xDz4R3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/node-core-library": "5.19.1", + "@rushstack/problem-matcher": "0.1.1", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/terminal/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.7.tgz", + "integrity": "sha512-Ugwl6flarZcL2nqH5IXFYk3UR3mBVDsVFlCQW/Oaqidvdb/5Ota6b/Z3JXWIdqV3rOR2/JrYoAHanWF5rgenXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/terminal": "0.21.0", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@rushstack/ts-command-line/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@solidtime/api": { + "resolved": "resources/js/packages/api", + "link": true + }, + "node_modules/@solidtime/ui": { + "resolved": "resources/js/packages/ui", + "link": true + }, "node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -1671,9 +2099,9 @@ } }, "node_modules/@tailwindcss/forms": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", - "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz", + "integrity": "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==", "dev": true, "license": "MIT", "dependencies": { @@ -1684,28 +2112,40 @@ } }, "node_modules/@tailwindcss/typography": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", - "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", "dev": true, "license": "MIT", "dependencies": { - "lodash.castarray": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.merge": "^4.6.2", "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, + "node_modules/@tanstack/devtools-event-client": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@tanstack/devtools-event-client/-/devtools-event-client-0.4.0.tgz", + "integrity": "sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/form-core": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-1.3.2.tgz", - "integrity": "sha512-hqRLw9EJ8bLJ5zvorGgTI4INcKh1hAtjPRTslwdB529soP8LpguzqWhn7yVV5/c2GcMSlqmpy5NZarkF5Mf54A==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-1.28.0.tgz", + "integrity": "sha512-MX3YveB6SKHAJ2yUwp+Ca/PCguub8bVEnLcLUbFLwdkSRMkP0lMGdaZl+F0JuEgZw56c6iFoRyfILhS7OQpydA==", "license": "MIT", "dependencies": { - "@tanstack/store": "^0.7.0" + "@tanstack/devtools-event-client": "^0.4.0", + "@tanstack/pacer-lite": "^0.1.1", + "@tanstack/store": "^0.7.7" }, "funding": { "type": "github", @@ -1728,10 +2168,23 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/pacer-lite": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@tanstack/pacer-lite/-/pacer-lite-0.1.1.tgz", + "integrity": "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/query-core": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.0.tgz", - "integrity": "sha512-kMqBfXA06INskI06rm+abAY3/Vi/Kq1nRNfLbpwuhuMJ5lMAI5qASretlvjEO5OJoze6w7OB3pNvsbztirIWWQ==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", "license": "MIT", "funding": { "type": "github", @@ -1739,9 +2192,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.73.3", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.73.3.tgz", - "integrity": "sha512-hBQyYwsOuO7QOprK75NzfrWs/EQYjgFA0yykmcvsV62q0t6Ua97CU3sYgjHx0ZvxkXSOMkY24VRJ5uv9f5Ik4w==", + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz", + "integrity": "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==", "license": "MIT", "funding": { "type": "github", @@ -1749,9 +2202,9 @@ } }, "node_modules/@tanstack/store": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.0.tgz", - "integrity": "sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.7.tgz", + "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==", "license": "MIT", "funding": { "type": "github", @@ -1759,9 +2212,9 @@ } }, "node_modules/@tanstack/table-core": { - "version": "8.21.2", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", - "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", "license": "MIT", "engines": { "node": ">=12" @@ -1772,9 +2225,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.6.tgz", - "integrity": "sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==", + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", + "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==", "license": "MIT", "funding": { "type": "github", @@ -1782,13 +2235,13 @@ } }, "node_modules/@tanstack/vue-form": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@tanstack/vue-form/-/vue-form-1.3.2.tgz", - "integrity": "sha512-7DIRyhqbY6nDXKlhQo5OyYFzWjvsX8UCLNiUYb6p4x+W32rBMzJgvVobgH0CHftZuTcXZLGU1GXYlqGHpGQi+g==", + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@tanstack/vue-form/-/vue-form-1.28.0.tgz", + "integrity": "sha512-UBO9DLFHrOyxDRQ0qLd789vgaKRECsNOYupW+ATjmqnOOV6l7JzTjCnjc35GVjIAgYyiZ1un4bPw1qyFM2G1OQ==", "license": "MIT", "dependencies": { - "@tanstack/form-core": "1.3.2", - "@tanstack/vue-store": "^0.7.0" + "@tanstack/form-core": "1.28.0", + "@tanstack/vue-store": "^0.7.7" }, "funding": { "type": "github", @@ -1799,13 +2252,13 @@ } }, "node_modules/@tanstack/vue-query": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.74.0.tgz", - "integrity": "sha512-YP6muhx9R9O5jPu3HV1b5fMLf/X+8PJmv1X1iOTdrBr394RVhMxWzTsyTwdrDLs1ydJBfOnop7ZFFCex5LlozA==", + "version": "5.92.9", + "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.92.9.tgz", + "integrity": "sha512-jjAZcqKveyX0C4w/6zUqbnqk/XzuxNWaFsWjGTJWULVFizUNeLGME2gf9vVSDclIyiBhR13oZJPPs6fJgfpIJQ==", "license": "MIT", "dependencies": { "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/query-core": "5.74.0", + "@tanstack/query-core": "5.90.20", "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" }, @@ -1824,29 +2277,55 @@ } }, "node_modules/@tanstack/vue-query-devtools": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/@tanstack/vue-query-devtools/-/vue-query-devtools-5.74.0.tgz", - "integrity": "sha512-3k3jINbZ22xn0R1dwrutOOB5b88qGf4fTKXmXiVQi98DHCs0g+4w5ItuIxo644eXqc8DWEsFzTxcJ6UFBrwOpg==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/@tanstack/vue-query-devtools/-/vue-query-devtools-5.91.0.tgz", + "integrity": "sha512-Kn6p5ffEKUj+YGEP+BapoPI8kUiPtNnFDyzjTvUx6GHbyfzfefQPhYoRtFiWeyrGHjUB/Mvtnr7Rosbzs/ngVg==", "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.73.3" + "@tanstack/query-devtools": "5.90.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/vue-query": "^5.74.0", + "@tanstack/vue-query": "^5.90.5", "vue": "^3.3.0" } }, - "node_modules/@tanstack/vue-store": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@tanstack/vue-store/-/vue-store-0.7.0.tgz", - "integrity": "sha512-oLB/WuD26caR86rxLz39LvS5YdY0KIThJFEHIW/mXujC2+M/z3GxVZFJsZianAzr3tH56sZQ8kkq4NvwwsOBkQ==", + "node_modules/@tanstack/vue-query/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@tanstack/vue-store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/vue-store/-/vue-store-0.7.7.tgz", + "integrity": "sha512-6iv1Odmreff6TgEjQN11xoddsCnpn+/ul7MZ2DadHT3/RSY1YdoFafK8lCa889MEFi/5K0zAhf8psIkgTrRa9A==", "license": "MIT", "dependencies": { - "@tanstack/store": "0.7.0", + "@tanstack/store": "0.7.7", "vue-demi": "^0.14.10" }, "funding": { @@ -1863,13 +2342,39 @@ } } }, + "node_modules/@tanstack/vue-store/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@tanstack/vue-table": { - "version": "8.21.2", - "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.2.tgz", - "integrity": "sha512-KBgOWxha/x4m1EdhVWxOpqHb661UjqAxzPcmXR3QiA7aShZ547x19Gw0UJX9we+m+tVcPuLRZ61JsYW47QZFfQ==", + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.3.tgz", + "integrity": "sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==", "license": "MIT", "dependencies": { - "@tanstack/table-core": "8.21.2" + "@tanstack/table-core": "8.21.3" }, "engines": { "node": ">=12" @@ -1883,12 +2388,12 @@ } }, "node_modules/@tanstack/vue-virtual": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.6.tgz", - "integrity": "sha512-GYdZ3SJBQPzgxhuCE2fvpiH46qzHiVx5XzBSdtESgiqh4poj8UgckjGWYEhxaBbcVt1oLzh1m3Ql4TyH32TOzQ==", + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.18.tgz", + "integrity": "sha512-6pT8HdHtTU5Z+t906cGdCroUNA5wHjFXsNss9gwk7QAr1VNZtz9IQCs2Nhx0gABK48c+OocHl2As+TMg8+Hy4A==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.6" + "@tanstack/virtual-core": "3.13.18" }, "funding": { "type": "github", @@ -1898,17 +2403,24 @@ "vue": "^2.7.0 || ^3.0.0" } }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/chroma-js": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.5.tgz", - "integrity": "sha512-6ISjhzJViaPCy2q2e6PgK+8HcHQDQ0V2LDiKmYAh+jJlLqDa6HbwDh0wOevHY0kHHUx0iZwjSRbVD47WOUx5EQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-3.1.2.tgz", + "integrity": "sha512-YBTQqArPN8A0niHXCwrO1z5x++a+6l0mLBykncUpr23oIPW7L4h39s6gokdK/bDrPmSh8+TjMmrhBPnyiaWPmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/fs-extra": { @@ -1927,12 +2439,29 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, - "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "dev": true, "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "devOptional": true, + "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } @@ -1944,20 +2473,19 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", - "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/type-utils": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1967,22 +2495,31 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", - "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1993,36 +2530,74 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", - "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2033,13 +2608,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", - "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2050,19 +2625,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2072,13 +2648,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2100,15 +2676,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2119,17 +2695,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", - "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.54.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2140,9 +2716,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2152,96 +2728,95 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", - "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", - "dev": true, + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.53" + }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "vue": "^3.2.25" } }, "node_modules/@volar/language-core": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.12.tgz", - "integrity": "sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==", - "dev": true, + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.12" + "@volar/source-map": "2.4.27" } }, "node_modules/@volar/source-map": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.12.tgz", - "integrity": "sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==", - "dev": true, + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.12.tgz", - "integrity": "sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==", - "dev": true, + "version": "2.4.27", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.12", + "@volar/language-core": "2.4.27", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.13", - "entities": "^4.5.0", + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" + "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.13", - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13", + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.48", - "source-map-js": "^1.2.0" + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" } }, "node_modules/@vue/compiler-vue2": { @@ -2261,6 +2836,30 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "license": "MIT" }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/eslint-config-prettier": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz", @@ -2276,15 +2875,15 @@ } }, "node_modules/@vue/eslint-config-typescript": { - "version": "14.5.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.5.0.tgz", - "integrity": "sha512-5oPOyuwkw++AP5gHDh5YFmST50dPfWOcm3/W7Nbh42IK5O3H74ytWAw0TrCRTaBoD/02khnWXuZf1Bz1xflavQ==", + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz", + "integrity": "sha512-UpiRY/7go4Yps4mYCjkvlIbVWmn9YvPGQDxTAlcKLphyaD77LjIu3plH4Y9zNT0GB4f3K5tMmhhtRhPOgrQ/bQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.26.0", + "@typescript-eslint/utils": "^8.35.1", "fast-glob": "^3.3.3", - "typescript-eslint": "^8.26.0", - "vue-eslint-parser": "^10.1.1" + "typescript-eslint": "^8.35.1", + "vue-eslint-parser": "^10.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2301,137 +2900,126 @@ } }, "node_modules/@vue/language-core": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.8.tgz", - "integrity": "sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==", - "dev": true, + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.2.4.tgz", + "integrity": "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==", "license": "MIT", "dependencies": { - "@volar/language-core": "~2.4.11", + "@volar/language-core": "2.4.27", "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", - "alien-signals": "^1.0.3", - "minimatch": "^9.0.3", + "alien-signals": "^3.0.0", "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" } }, - "node_modules/@vue/language-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, + "node_modules/@vue/language-core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.13" + "@vue/shared": "3.5.27" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/runtime-core": "3.5.13", - "@vue/shared": "3.5.13", - "csstype": "^3.1.3" + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" }, "peerDependencies": { - "vue": "3.5.13" + "vue": "3.5.27" } }, "node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", "license": "MIT" }, "node_modules/@vue/tsconfig": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.5.1.tgz", - "integrity": "sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } }, "node_modules/@vueuse/core": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", - "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.0.tgz", + "integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" + "@vueuse/metadata": "14.2.0", + "@vueuse/shared": "14.2.0" }, "funding": { "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, "node_modules/@vueuse/integrations": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", - "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-14.2.0.tgz", + "integrity": "sha512-Yuo5XbIi6XkfSXOYKd5SBZwyBEyO3Hd41eeG2555hDbE0Maz/P0BfPJDYhgDXjS9xI0jkWUUp1Zh5lXHOgkwLw==", "license": "MIT", "dependencies": { - "@vueuse/core": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" + "@vueuse/core": "14.2.0", + "@vueuse/shared": "14.2.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -2441,14 +3029,15 @@ "axios": "^1", "change-case": "^5", "drauu": "^0.4", - "focus-trap": "^7", + "focus-trap": "^7 || ^8", "fuse.js": "^7", "idb-keyval": "^6", "jwt-decode": "^4", "nprogress": "^0.2", "qrcode": "^1.5", "sortablejs": "^1", - "universal-cookie": "^7" + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" }, "peerDependenciesMeta": { "async-validator": { @@ -2490,31 +3079,30 @@ } }, "node_modules/@vueuse/metadata": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", - "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.0.tgz", + "integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", - "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.0.tgz", + "integrity": "sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==", "license": "MIT", - "dependencies": { - "vue": "^3.5.13" - }, "funding": { "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" } }, "node_modules/@zodios/core": { "version": "10.9.6", "resolved": "https://registry.npmjs.org/@zodios/core/-/core-10.9.6.tgz", "integrity": "sha512-aH4rOdb3AcezN7ws8vDgBfGboZMk2JGGzEq/DtW65MhnRxyTGRuLJRWVQ/2KxDgWvV2F5oTkAS+5pnjKbl0n+A==", - "dev": true, "license": "MIT", "peerDependencies": { "axios": "^0.x || ^1.0.0", @@ -2522,9 +3110,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2558,59 +3146,60 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/alien-signals": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", - "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=8" + "peerDependencies": { + "ajv": "^8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "license": "MIT" + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2653,9 +3242,9 @@ "license": "Python-2.0" }, "node_modules/aria-hidden": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", - "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -2668,13 +3257,12 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "devOptional": true, "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", "dev": true, "funding": [ { @@ -2692,10 +3280,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -2710,14 +3297,13 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", - "devOptional": true, + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -2727,6 +3313,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2739,6 +3335,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2747,9 +3352,9 @@ "peer": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2769,9 +3374,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -2789,10 +3394,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -2815,7 +3421,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2868,9 +3473,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001713", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", - "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -2893,6 +3498,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2972,6 +3578,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", + "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2983,13 +3590,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "devOptional": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -3007,12 +3614,26 @@ "node": ">= 6" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3020,11 +3641,27 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", + "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3047,15 +3684,15 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, "node_modules/de-indent": { @@ -3066,9 +3703,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3089,16 +3726,6 @@ "license": "MIT", "peer": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", @@ -3109,7 +3736,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3121,6 +3747,16 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -3131,7 +3767,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -3142,39 +3777,27 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, "node_modules/echarts": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", - "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", "license": "Apache-2.0", "dependencies": { "tslib": "2.3.0", - "zrender": "5.6.1" + "zrender": "6.0.0" } }, "node_modules/electron-to-chromium": { - "version": "1.5.136", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.136.tgz", - "integrity": "sha512-kL4+wUTD7RSA5FHx5YwWtjDnEEkIIikFgWHR4P6fqjw1PPLlqYkxeOb++wAauAssat0YClCy8Y3C5SxgSkjibQ==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "dev": true, "license": "ISC" }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -3187,7 +3810,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3197,7 +3819,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3207,7 +3828,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3220,7 +3840,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "devOptional": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3233,10 +3852,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "dev": true, + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -3246,31 +3864,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -3297,33 +3916,32 @@ } }, "node_modules/eslint": { - "version": "9.24.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", - "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "license": "MIT", "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.0", - "@eslint/core": "^0.12.0", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.24.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -3358,25 +3976,28 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", - "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-prettier": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz", - "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==", + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.11.0" + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -3400,9 +4021,9 @@ } }, "node_modules/eslint-plugin-unused-imports": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", - "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz", + "integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3416,16 +4037,16 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.0.0.tgz", - "integrity": "sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.7.0.tgz", + "integrity": "sha512-r2XFCK4qlo1sxEoAMIoTTX0PZAdla0JJDt1fmYiworZUX67WeEGqm+JbyAg3M+pGiJ5U6Mp5WQbontXWtIW7TA==", "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.15", + "postcss-selector-parser": "^7.1.0", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, @@ -3433,14 +4054,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" + }, + "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, + "@typescript-eslint/parser": { + "optional": true + } } }, "node_modules/eslint-plugin-vue/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", "peer": true, "dependencies": { @@ -3452,9 +4083,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -3480,9 +4111,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", "peer": true, "engines": { @@ -3493,14 +4124,14 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3510,9 +4141,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3522,9 +4153,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -3571,9 +4202,16 @@ } }, "node_modules/eval-estree-expression": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eval-estree-expression/-/eval-estree-expression-2.1.1.tgz", - "integrity": "sha512-9kNUU4c+kUs5rKR7V5n81Ebp6fId1v01XSHshPuDIQ8N2VKAAzSzN3o/hfzERdNU6ZGh97LYFT7wWrL0cqhV3A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eval-estree-expression/-/eval-estree-expression-3.0.1.tgz", + "integrity": "sha512-zTLKGbiVdQYp4rQkSoXPibrFf5ZoPn6jzExegRLEQ13F+FSxu5iLgaRH6hlDs2kWSUa6vp8yD20cdJi0me6pEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", "dev": true, "license": "MIT" }, @@ -3631,9 +4269,9 @@ "peer": true }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, "funding": [ { @@ -3648,9 +4286,9 @@ "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -3720,19 +4358,18 @@ "peer": true }, "node_modules/focus-trap": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", - "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.0.0.tgz", + "integrity": "sha512-Aa84FOGHs99vVwufDMdq2qgOwXPC2e9U66GcqBhn1/jEHPDhJaP8PYhkIbqG9lhfL5Kddk/567lj46LLHYCRUw==", "license": "MIT", "dependencies": { - "tabbable": "^6.2.0" + "tabbable": "^6.4.0" } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "devOptional": true, + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -3749,32 +4386,16 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "devOptional": true, + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -3782,16 +4403,16 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -3847,7 +4468,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "devOptional": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3872,7 +4492,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "devOptional": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3882,26 +4501,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3914,30 +4513,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -3954,7 +4529,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3970,12 +4544,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "license": "MIT" - }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -4011,7 +4579,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4024,7 +4591,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "devOptional": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4058,6 +4624,12 @@ "he": "bin/he" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4083,6 +4655,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4129,15 +4711,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -4159,26 +4732,24 @@ "node": ">=0.12.0" } }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } + "license": "ISC", + "peer": true }, "node_modules/jiti": { "version": "1.21.7", @@ -4189,6 +4760,13 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4197,9 +4775,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4255,9 +4833,9 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", "dependencies": { @@ -4277,10 +4855,28 @@ "json-buffer": "3.0.1" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/laravel-precognition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-1.0.1.tgz", + "integrity": "sha512-BYaDUjEclKbxuQTG9yZnRjjD7ag1Xh+8hGOXmcrw91/vpbIOJ8cSFADTI0kvwetIr3AM1vOJzYwaoA9+KHhLwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.4.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/laravel-vite-plugin": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.2.0.tgz", - "integrity": "sha512-R0pJ+IcTVeqEMoKz/B2Ij57QVq3sFTABiFmb06gAwFdivbOgsUtuhX6N2MGLEArajrS3U5JbberzwOe7uXHMHQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.1.0.tgz", + "integrity": "sha512-z+ck2BSV6KWtYcoIzk9Y5+p4NEjqM+Y4i8/H+VZRLq0OgNjW2DqyADquwYu5j8qRvaXwzNmfCWl1KrMlV1zpsg==", "dev": true, "license": "MIT", "dependencies": { @@ -4291,10 +4887,10 @@ "clean-orphaned-assets": "bin/clean.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0" + "vite": "^7.0.0" } }, "node_modules/levn": { @@ -4329,6 +4925,24 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4346,37 +4960,16 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.castarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", - "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "dev": true, "license": "MIT" }, @@ -4384,7 +4977,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -4406,19 +5000,18 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4450,7 +5043,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4460,7 +5052,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -4501,13 +5092,42 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, "node_modules/ms": { @@ -4520,7 +5140,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -4566,9 +5185,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -4581,36 +5200,36 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", - "devOptional": true, - "license": "MIT" - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -4770,12 +5389,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4789,9 +5402,9 @@ } }, "node_modules/parse-duration": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-2.1.4.tgz", - "integrity": "sha512-b98m6MsCh+akxfyoz9w9dt0AlH2dfYLOBss5SdDsr9pkhKNvkWBXU/r8A4ahmIGByBOLV2+4YwfCuFxbDDaGyg==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-2.1.5.tgz", + "integrity": "sha512-/IX1KRw6zHDOOJrgIz++gvFASbFl7nc8GEXaLdD7d1t1x/GnrK6hh5Fgk8G3RLpkIEi4tsGj9pupGLWNg0EiJA==", "license": "MIT" }, "node_modules/pastable": { @@ -4824,7 +5437,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, "license": "MIT" }, "node_modules/path-exists": { @@ -4842,6 +5454,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -4852,27 +5465,18 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", @@ -4902,20 +5506,19 @@ } }, "node_modules/pinia": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", - "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", "license": "MIT", "dependencies": { - "@vue/devtools-api": "^6.6.3", - "vue-demi": "^0.14.10" + "@vue/devtools-api": "^7.7.7" }, "funding": { "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "typescript": ">=4.4.4", - "vue": "^2.7.0 || ^3.5.11" + "typescript": ">=4.5.0", + "vue": "^3.5.11" }, "peerDependenciesMeta": { "typescript": { @@ -4923,6 +5526,15 @@ } } }, + "node_modules/pinia/node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -4932,14 +5544,26 @@ "node": ">= 6" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/playwright": { - "version": "1.51.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz", - "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", + "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.51.1" + "playwright-core": "1.58.1" }, "bin": { "playwright": "cli.js" @@ -4952,9 +5576,9 @@ } }, "node_modules/playwright-core": { - "version": "1.51.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz", - "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", + "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4965,9 +5589,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -4984,7 +5608,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5010,28 +5634,34 @@ } }, "node_modules/postcss-js": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", - "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" }, "peerDependencies": { "postcss": "^8.4.21" } }, "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "funding": [ { "type": "opencollective", @@ -5044,21 +5674,28 @@ ], "license": "MIT", "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" + "lilconfig": "^3.1.1" }, "engines": { - "node": ">= 14" + "node": ">= 18" }, "peerDependencies": { + "jiti": ">=1.21.0", "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { + "jiti": { + "optional": true + }, "postcss": { "optional": true }, - "ts-node": { + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } @@ -5230,9 +5867,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "license": "MIT", "peer": true, "bin": { @@ -5246,9 +5883,9 @@ } }, "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" @@ -5261,7 +5898,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "devOptional": true, "license": "MIT" }, "node_modules/punycode": { @@ -5274,9 +5910,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5289,6 +5925,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5352,6 +6005,32 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/radix-vue/node_modules/@vueuse/metadata": { "version": "10.11.1", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", @@ -5373,10 +6052,36 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/radix-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/radix-vue/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "funding": [ { "type": "github", @@ -5413,9 +6118,9 @@ } }, "node_modules/reka-ui": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.2.0.tgz", - "integrity": "sha512-eeRrLI4LwJ6dkdwks6KFNKGs0+beqZlHO3JMHen7THDTh+yJ5Z0KNwONmOhhV/0hZC2uJCEExgG60QPzGstkQg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.8.0.tgz", + "integrity": "sha512-N4JOyIrmDE7w2i06WytqcV2QICubtS2PsK5Uo8FIMAgmO13KhUAgAByP26cXjjm2oF/w7rTyRs8YaqtvaBT+SA==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", @@ -5423,8 +6128,8 @@ "@internationalized/date": "^3.5.0", "@internationalized/number": "^3.5.0", "@tanstack/vue-virtual": "^3.12.0", - "@vueuse/core": "^12.5.0", - "@vueuse/shared": "^12.5.0", + "@vueuse/core": "^14.1.0", + "@vueuse/shared": "^14.1.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "ohash": "^2.0.11" @@ -5450,12 +6155,12 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -5488,14 +6193,19 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", - "dev": true, + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -5505,26 +6215,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, @@ -5552,9 +6267,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5568,6 +6283,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", + "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5580,6 +6296,7 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -5660,18 +6377,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5691,100 +6396,30 @@ "node": ">=0.10.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.6.19" } }, "node_modules/strip-json-comments": { @@ -5800,17 +6435,17 @@ } }, "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { @@ -5821,11 +6456,24 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5846,13 +6494,12 @@ } }, "node_modules/synckit": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz", - "integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.1", - "tslib": "^2.8.1" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -5861,22 +6508,16 @@ "url": "https://opencollective.com/synckit" } }, - "node_modules/synckit/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", "license": "MIT", "funding": { "type": "github", @@ -5884,9 +6525,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.17", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", - "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5897,7 +6538,7 @@ "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.6", + "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", @@ -5906,7 +6547,7 @@ "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", @@ -6002,6 +6643,51 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6015,9 +6701,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "license": "MIT", "engines": { "node": ">=18.12" @@ -6033,9 +6719,9 @@ "license": "Apache-2.0" }, "node_modules/ts-pattern": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.7.0.tgz", - "integrity": "sha512-0/FvIG4g3kNkYgbNwBBW5pZBkfpeYQnH+2AA3xmjkCAit/DSDPKmgwC3fKof4oYUq6gupClVOJlFl+939VRBMg==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.9.0.tgz", + "integrity": "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==", "dev": true, "license": "MIT" }, @@ -6079,9 +6765,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6092,14 +6778,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", - "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@typescript-eslint/utils": "8.29.1" + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6110,9 +6797,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -6131,9 +6825,22 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -6145,9 +6852,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -6191,21 +6898,23 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", - "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", - "dev": true, + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "postcss": "^8.5.3", - "rollup": "^4.30.1" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -6214,14 +6923,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "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" @@ -6263,41 +6972,36 @@ } }, "node_modules/vite-plugin-checker": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.8.0.tgz", - "integrity": "sha512-UA5uzOGm97UvZRTdZHiQVYFnd86AVn8EVaD4L3PoVzxH+IZSfaAw14WGFwX9QS23UW3lV/5bVKZn6l0w+q9P0g==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.12.0.tgz", + "integrity": "sha512-CmdZdDOGss7kdQwv73UyVgLPv0FVYe5czAgnmRX2oKljgEvSrODGuClaV3PDR2+3ou7N/OKGauDDBjy2MB07Rg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "ansi-escapes": "^4.3.0", - "chalk": "^4.1.1", - "chokidar": "^3.5.1", - "commander": "^8.0.0", - "fast-glob": "^3.2.7", - "fs-extra": "^11.1.0", - "npm-run-path": "^4.0.1", - "strip-ansi": "^6.0.0", - "tiny-invariant": "^1.1.0", - "vscode-languageclient": "^7.0.0", - "vscode-languageserver": "^7.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-uri": "^3.0.2" + "@babel/code-frame": "^7.27.1", + "chokidar": "^4.0.3", + "npm-run-path": "^6.0.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.3", + "tiny-invariant": "^1.3.3", + "tinyglobby": "^0.2.15", + "vscode-uri": "^3.1.0" }, "engines": { - "node": ">=14.16" + "node": ">=16.11" }, "peerDependencies": { "@biomejs/biome": ">=1.7", - "eslint": ">=7", - "meow": "^9.0.0", - "optionator": "^0.9.1", - "stylelint": ">=13", + "eslint": ">=9.39.1", + "meow": "^13.2.0", + "optionator": "^0.9.4", + "oxlint": ">=1", + "stylelint": ">=16", "typescript": "*", - "vite": ">=2.0.0", + "vite": ">=5.4.21", "vls": "*", "vti": "*", - "vue-tsc": "~2.1.6" + "vue-tsc": "~2.2.10 || ^3.0.0" }, "peerDependenciesMeta": { "@biomejs/biome": { @@ -6312,6 +7016,9 @@ "optionator": { "optional": true }, + "oxlint": { + "optional": true + }, "stylelint": { "optional": true }, @@ -6329,52 +7036,132 @@ } } }, - "node_modules/vite-plugin-checker/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/vite-plugin-checker/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/vite-plugin-checker/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "node_modules/vite-plugin-checker/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vite-plugin-checker/node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "node_modules/vite-plugin-checker/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/vite-plugin-dts": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", + "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@microsoft/api-extractor": "^7.50.1", + "@rollup/pluginutils": "^5.1.4", + "@volar/typescript": "^2.4.11", + "@vue/language-core": "2.2.0", + "compare-versions": "^6.1.1", + "debug": "^4.4.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17" }, - "engines": { - "node": ">=14.14" + "peerDependencies": { + "typescript": "*", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } } }, - "node_modules/vite-plugin-checker/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/vite-plugin-dts/node_modules/@vue/language-core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", + "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.4.9", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vite-plugin-dts/node_modules/alien-signals": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", + "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-dts/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "balanced-match": "^1.0.0" + } + }, + "node_modules/vite-plugin-dts/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/vite-plugin-full-reload": { @@ -6388,11 +7175,27 @@ "picomatch": "^2.3.1" } }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -6403,87 +7206,35 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/vscode-jsonrpc": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", - "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0 || >=10.0.0" - } - }, - "node_modules/vscode-languageclient": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz", - "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==", - "dev": true, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "dependencies": { - "minimatch": "^3.0.4", - "semver": "^7.3.4", - "vscode-languageserver-protocol": "3.16.0" - }, "engines": { - "vscode": "^1.52.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz", - "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==", - "dev": true, - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.16.0" + "node": ">=12" }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", - "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", - "dev": true, - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "6.0.0", - "vscode-languageserver-types": "3.16.0" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", - "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", - "dev": true, - "license": "MIT" - }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, "license": "MIT" }, "node_modules/vue": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-sfc": "3.5.13", - "@vue/runtime-dom": "3.5.13", - "@vue/server-renderer": "3.5.13", - "@vue/shared": "3.5.13" + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" }, "peerDependencies": { "typescript": "*" @@ -6494,81 +7245,20 @@ } } }, - "node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/vue-echarts": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-7.0.3.tgz", - "integrity": "sha512-/jSxNwOsw5+dYAUcwSfkLwKPuzTQ0Cepz1LxCOpj2QcHrrmUa/Ql0eQqMmc1rTPQVrh2JQ29n2dhq75ZcHvRDw==", - "license": "MIT", - "dependencies": { - "vue-demi": "^0.13.11" - }, - "peerDependencies": { - "@vue/runtime-core": "^3.0.0", - "echarts": "^5.5.1", - "vue": "^2.7.0 || ^3.1.1" - }, - "peerDependenciesMeta": { - "@vue/runtime-core": { - "optional": true - } - } - }, - "node_modules/vue-echarts/node_modules/vue-demi": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", - "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", - "hasInstallScript": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-8.0.1.tgz", + "integrity": "sha512-23rJTFLu1OUEGRWjJGmdGt8fP+8+ja1gVgzMYPIPaHWpXegcO1viIAaeu2H4QHESlVeHzUAHIxKXGrwjsyXAaA==", "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } + "echarts": "^6.0.0", + "vue": "^3.3.0" } }, "node_modules/vue-eslint-parser": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", - "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -6576,7 +7266,6 @@ "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", - "lodash": "^4.17.21", "semver": "^7.6.3" }, "engines": { @@ -6590,9 +7279,9 @@ } }, "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6602,14 +7291,13 @@ } }, "node_modules/vue-tsc": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.8.tgz", - "integrity": "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ==", - "dev": true, + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.2.4.tgz", + "integrity": "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==", "license": "MIT", "dependencies": { - "@volar/typescript": "~2.4.11", - "@vue/language-core": "2.2.8" + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.4" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -6619,14 +7307,14 @@ } }, "node_modules/whence": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/whence/-/whence-2.0.2.tgz", - "integrity": "sha512-dSzQeVdKM4BJ+mVMXD/MOt4LrZxMpjuNmRi7hqkDEmFJMPnLbEc0hSWR3mN/S0xkLlWuWTPg89lrTNFX4lGpdA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/whence/-/whence-2.1.0.tgz", + "integrity": "sha512-4UBPMg5mng5KLzdliVQdQ4fJwCdIMXkE8CkoDmGKRy5r8pV9xq+nVgf/sKXpmNEIOtFp7m7v2bFdb7JoLvh+Hg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.7", - "eval-estree-expression": "^2.1.1" + "@babel/parser": "^7.28.0", + "eval-estree-expression": "^3.0.0" }, "engines": { "node": ">=14" @@ -6637,6 +7325,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6664,94 +7353,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -6770,15 +7371,19 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "devOptional": true, "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yocto-queue": { @@ -6795,23 +7400,62 @@ } }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", - "dev": true, + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zrender": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", - "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", "license": "BSD-3-Clause", "dependencies": { "tslib": "2.3.0" } + }, + "resources/js/packages/api": { + "name": "@solidtime/api", + "version": "0.0.6", + "license": "AGPL-3.0", + "devDependencies": { + "vite-plugin-dts": "^4.0.3" + }, + "peerDependencies": { + "@zodios/core": "^10.9.6", + "axios": "^1.6.4", + "typescript": "^5.5.4", + "vite": "^5.4.1 || ^6.0.0 || ^7.0.0", + "zod": "^3.23.8" + } + }, + "resources/js/packages/ui": { + "name": "@solidtime/ui", + "version": "0.0.15", + "license": "AGPL-3.0", + "devDependencies": { + "vite-plugin-dts": "^4.0.3" + }, + "peerDependencies": { + "@floating-ui/vue": "^1.1.4", + "@heroicons/vue": "^2.1.5", + "@vitejs/plugin-vue": "^5.1.2 || ^6.0.0", + "@vueuse/core": "^12.5.0 || ^14.0.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dayjs": "^1.11.13", + "parse-duration": "^2.0.1", + "reka-ui": "^2.2.0", + "tailwind-merge": "^2.5.2", + "tailwindcss": "^3.1.0", + "typescript": "^5.5.4", + "vite": "^5.4.1 || ^6.0.0 || ^7.0.0", + "vue": "^3.5.0", + "vue-tsc": "^2.2.0 || ^3.0.0" + } } } } diff --git a/package.json b/package.json index f133c166..17a1c0ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "private": true, "type": "module", + "workspaces": [ + "resources/js/packages/ui", + "resources/js/packages/api" + ], "scripts": { "dev": "vite", "build": "vite build", @@ -15,31 +19,33 @@ "devDependencies": { "@eslint/eslintrc": "^3.2.0", "@eslint/js": "^9.19.0", - "@inertiajs/vue3": "^1.0.0", + "@inertiajs/vue3": "^2.0.0", "@playwright/test": "^1.41.1", "@tailwindcss/forms": "^0.5.9", "@tailwindcss/typography": "^0.5.15", - "@types/chroma-js": "2.4.5", + "@types/chroma-js": "^3.1.0", "@types/node": "^22.10.10", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/tsconfig": "^0.5.1", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.0", "autoprefixer": "^10.4.20", "axios": "^1.6.4", "eslint-plugin-unused-imports": "^4.1.4", - "laravel-vite-plugin": "^1.0.0", + "laravel-vite-plugin": "^2.1.0", "openapi-zod-client": "^1.16.2", "postcss": "^8.4.47", + "postcss-import": "^15.1.0", "postcss-nesting": "^12.1.5", "tailwindcss": "^3.4.13", "typescript": "^5.7.3", - "vite": "^6.0.11", - "vite-plugin-checker": "^0.8.0", + "vite": "^7.0.0", + "vite-plugin-checker": "^0.12.0", "vue": "^3.5.0", - "vue-tsc": "^2.2.0" + "vue-tsc": "^3.0.0" }, "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/vue": "^1.0.6", + "@zodios/core": "^10.9.6", "@fullcalendar/core": "^6.1.18", "@fullcalendar/daygrid": "^6.1.18", "@fullcalendar/interaction": "^6.1.18", @@ -54,22 +60,23 @@ "@tanstack/vue-table": "^8.21.2", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.3.0", - "@vueuse/core": "^12.8.2", - "@vueuse/integrations": "^12.5.0", + "@vueuse/core": "^14.0.0", + "@vueuse/integrations": "^14.0.0", "chroma-js": "3.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dayjs": "^1.11.11", - "echarts": "^5.5.0", - "focus-trap": "^7.6.0", + "echarts": "^6.0.0", + "focus-trap": "^8.0.0", "lucide-vue-next": "^0.487.0", "parse-duration": "^2.0.1", - "pinia": "^2.1.7", + "pinia": "^3.0.0", "radix-vue": "^1.9.6", - "reka-ui": "^2.2.0", + "reka-ui": "^2.7.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "vue-echarts": "^7.0.3" + "vue-echarts": "^8.0.0", + "zod": "^3.23.8" }, "overrides": { "vite-plugin-checker": { diff --git a/playwright/config.ts b/playwright/config.ts index 3d12852a..d2c0c280 100644 --- a/playwright/config.ts +++ b/playwright/config.ts @@ -1 +1,2 @@ export const PLAYWRIGHT_BASE_URL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://solidtime.test'; +export const MAILPIT_BASE_URL = process.env.MAILPIT_BASE_URL ?? 'http://mailpit:8025'; diff --git a/resources/js/Components/CommandPalette/CommandPaletteProvider.vue b/resources/js/Components/CommandPalette/CommandPaletteProvider.vue new file mode 100644 index 00000000..c597aa61 --- /dev/null +++ b/resources/js/Components/CommandPalette/CommandPaletteProvider.vue @@ -0,0 +1,268 @@ + + + diff --git a/resources/js/Components/CommandPalette/index.ts b/resources/js/Components/CommandPalette/index.ts new file mode 100644 index 00000000..0e729f1e --- /dev/null +++ b/resources/js/Components/CommandPalette/index.ts @@ -0,0 +1 @@ +export { default as CommandPaletteProvider } from './CommandPaletteProvider.vue'; diff --git a/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue b/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue index c5e381c3..be438107 100644 --- a/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue +++ b/resources/js/Components/Common/Client/ClientMultiselectDropdown.vue @@ -1,11 +1,9 @@