Selaa lähdekoodia

transfer transactions to resource

3.x
Andrew Wallo 4 kuukautta sitten
vanhempi
commit
daff2e248f

+ 49
- 26
app/Concerns/HasJournalEntryActions.php Näytä tiedosto

@@ -4,6 +4,7 @@ namespace App\Concerns;
4 4
 
5 5
 use App\Enums\Accounting\JournalEntryType;
6 6
 use App\Utilities\Currency\CurrencyAccessor;
7
+use Filament\Tables\Actions\Action;
7 8
 
8 9
 trait HasJournalEntryActions
9 10
 {
@@ -104,7 +105,8 @@ trait HasJournalEntryActions
104 105
      */
105 106
     public function resetJournalEntryAmounts(): void
106 107
     {
107
-        $this->reset(['debitAmount', 'creditAmount']);
108
+        $this->debitAmount = 0;
109
+        $this->creditAmount = 0;
108 110
     }
109 111
 
110 112
     public function adjustJournalEntryAmountsForTypeChange(JournalEntryType $newType, JournalEntryType $oldType, ?string $amount): void
@@ -113,19 +115,29 @@ trait HasJournalEntryActions
113 115
             return;
114 116
         }
115 117
 
116
-        $amountComplete = $this->ensureCompleteDecimal($amount);
117
-        $normalizedAmount = $this->convertAmountToCents($amountComplete);
118
-
119
-        if ($normalizedAmount === 0) {
120
-            return;
121
-        }
122
-
123
-        if ($oldType->isDebit() && $newType->isCredit()) {
124
-            $this->debitAmount -= $normalizedAmount;
125
-            $this->creditAmount += $normalizedAmount;
126
-        } elseif ($oldType->isCredit() && $newType->isDebit()) {
127
-            $this->debitAmount += $normalizedAmount;
128
-            $this->creditAmount -= $normalizedAmount;
118
+        $entries = $this instanceof Action
119
+            ? ($this->getLivewire()->mountedTableActionsData[0]['journalEntries'] ?? [])
120
+            : ($this->getLivewire()->mountedActionsData[0]['journalEntries'] ?? []);
121
+
122
+        // Reset the totals
123
+        $this->debitAmount = 0;
124
+        $this->creditAmount = 0;
125
+
126
+        // Recalculate totals from all entries
127
+        foreach ($entries as $entry) {
128
+            if (empty($entry['type']) || empty($entry['amount'])) {
129
+                continue;
130
+            }
131
+
132
+            $entryType = JournalEntryType::parse($entry['type']);
133
+            $entryAmount = $this->ensureCompleteDecimal($entry['amount']);
134
+            $formattedAmount = $this->convertAmountToCents($entryAmount);
135
+
136
+            if ($entryType->isDebit()) {
137
+                $this->debitAmount += $formattedAmount;
138
+            } else {
139
+                $this->creditAmount += $formattedAmount;
140
+            }
129 141
         }
130 142
     }
131 143
 
@@ -140,18 +152,29 @@ trait HasJournalEntryActions
140 152
             return;
141 153
         }
142 154
 
143
-        $newAmountComplete = $this->ensureCompleteDecimal($newAmount);
144
-        $oldAmountComplete = $this->ensureCompleteDecimal($oldAmount);
145
-
146
-        $formattedNewAmount = $this->convertAmountToCents($newAmountComplete);
147
-        $formattedOldAmount = $this->convertAmountToCents($oldAmountComplete);
148
-
149
-        $difference = $formattedNewAmount - $formattedOldAmount;
150
-
151
-        if ($journalEntryType->isDebit()) {
152
-            $this->debitAmount += $difference;
153
-        } else {
154
-            $this->creditAmount += $difference;
155
+        $entries = $this instanceof Action
156
+            ? ($this->getLivewire()->mountedTableActionsData[0]['journalEntries'] ?? [])
157
+            : ($this->getLivewire()->mountedActionsData[0]['journalEntries'] ?? []);
158
+
159
+        // Reset the totals
160
+        $this->debitAmount = 0;
161
+        $this->creditAmount = 0;
162
+
163
+        // Recalculate totals from all entries
164
+        foreach ($entries as $entry) {
165
+            if (empty($entry['type']) || empty($entry['amount'])) {
166
+                continue;
167
+            }
168
+
169
+            $entryType = JournalEntryType::parse($entry['type']);
170
+            $entryAmount = $this->ensureCompleteDecimal($entry['amount']);
171
+            $formattedAmount = $this->convertAmountToCents($entryAmount);
172
+
173
+            if ($entryType->isDebit()) {
174
+                $this->debitAmount += $formattedAmount;
175
+            } else {
176
+                $this->creditAmount += $formattedAmount;
177
+            }
155 178
         }
156 179
     }
157 180
 

+ 405
- 0
app/Concerns/HasTransactionAction.php Näytä tiedosto

@@ -0,0 +1,405 @@
1
+<?php
2
+
3
+namespace App\Concerns;
4
+
5
+use App\Enums\Accounting\AccountCategory;
6
+use App\Enums\Accounting\JournalEntryType;
7
+use App\Enums\Accounting\TransactionType;
8
+use App\Filament\Forms\Components\CustomTableRepeater;
9
+use App\Models\Accounting\Account;
10
+use App\Models\Accounting\JournalEntry;
11
+use App\Models\Accounting\Transaction;
12
+use App\Models\Banking\BankAccount;
13
+use App\Utilities\Currency\CurrencyAccessor;
14
+use App\Utilities\Currency\CurrencyConverter;
15
+use Awcodes\TableRepeater\Header;
16
+use Filament\Forms;
17
+use Filament\Forms\Components\Actions\Action as FormAction;
18
+use Filament\Forms\Form;
19
+use Filament\Support\Enums\IconPosition;
20
+use Filament\Support\Enums\IconSize;
21
+use Illuminate\Contracts\View\View;
22
+use Illuminate\Support\Str;
23
+
24
+trait HasTransactionAction
25
+{
26
+    use HasJournalEntryActions;
27
+
28
+    protected ?TransactionType $transactionType = null;
29
+
30
+    public function type(TransactionType $type): static
31
+    {
32
+        $this->transactionType = $type;
33
+
34
+        return $this;
35
+    }
36
+
37
+    protected function getFormDefaultsForType(TransactionType $type): array
38
+    {
39
+        $commonDefaults = [
40
+            'posted_at' => today(),
41
+        ];
42
+
43
+        return match ($type) {
44
+            TransactionType::Deposit, TransactionType::Withdrawal, TransactionType::Transfer => array_merge($commonDefaults, $this->transactionDefaults($type)),
45
+            TransactionType::Journal => array_merge($commonDefaults, $this->journalEntryDefaults()),
46
+        };
47
+    }
48
+
49
+    protected function journalEntryDefaults(): array
50
+    {
51
+        return [
52
+            'journalEntries' => [
53
+                $this->defaultEntry(JournalEntryType::Debit),
54
+                $this->defaultEntry(JournalEntryType::Credit),
55
+            ],
56
+        ];
57
+    }
58
+
59
+    protected function defaultEntry(JournalEntryType $journalEntryType): array
60
+    {
61
+        return [
62
+            'type' => $journalEntryType,
63
+            'account_id' => static::getUncategorizedAccountByType($journalEntryType->isDebit() ? TransactionType::Withdrawal : TransactionType::Deposit)?->id,
64
+            'amount' => '0.00',
65
+        ];
66
+    }
67
+
68
+    protected function transactionDefaults(TransactionType $type): array
69
+    {
70
+        return [
71
+            'type' => $type,
72
+            'bank_account_id' => BankAccount::where('enabled', true)->first()?->id,
73
+            'amount' => '0.00',
74
+            'account_id' => ! $type->isTransfer() ? static::getUncategorizedAccountByType($type)->id : null,
75
+        ];
76
+    }
77
+
78
+    public function transactionForm(Form $form): Form
79
+    {
80
+        return $form
81
+            ->schema([
82
+                Forms\Components\DatePicker::make('posted_at')
83
+                    ->label('Date')
84
+                    ->required(),
85
+                Forms\Components\TextInput::make('description')
86
+                    ->label('Description'),
87
+                Forms\Components\Select::make('bank_account_id')
88
+                    ->label('Account')
89
+                    ->options(fn (?Transaction $transaction) => Transaction::getBankAccountOptions(currentBankAccountId: $transaction?->bank_account_id))
90
+                    ->live()
91
+                    ->searchable()
92
+                    ->afterStateUpdated(function (Forms\Set $set, $state, $old, Forms\Get $get) {
93
+                        $amount = CurrencyConverter::convertAndSet(
94
+                            BankAccount::find($state)->account->currency_code,
95
+                            BankAccount::find($old)->account->currency_code ?? CurrencyAccessor::getDefaultCurrency(),
96
+                            $get('amount')
97
+                        );
98
+
99
+                        if ($amount !== null) {
100
+                            $set('amount', $amount);
101
+                        }
102
+                    })
103
+                    ->required(),
104
+                Forms\Components\Select::make('type')
105
+                    ->label('Type')
106
+                    ->live()
107
+                    ->options([
108
+                        TransactionType::Deposit->value => TransactionType::Deposit->getLabel(),
109
+                        TransactionType::Withdrawal->value => TransactionType::Withdrawal->getLabel(),
110
+                    ])
111
+                    ->required()
112
+                    ->afterStateUpdated(static fn (Forms\Set $set, $state) => $set('account_id', static::getUncategorizedAccountByType(TransactionType::parse($state))?->id)),
113
+                Forms\Components\TextInput::make('amount')
114
+                    ->label('Amount')
115
+                    ->money(static fn (Forms\Get $get) => BankAccount::find($get('bank_account_id'))?->account?->currency_code ?? CurrencyAccessor::getDefaultCurrency())
116
+                    ->required(),
117
+                Forms\Components\Select::make('account_id')
118
+                    ->label('Category')
119
+                    ->options(fn (Forms\Get $get, ?Transaction $transaction) => Transaction::getChartAccountOptions(type: TransactionType::parse($get('type')), nominalAccountsOnly: true, currentAccountId: $transaction?->account_id))
120
+                    ->searchable()
121
+                    ->preload()
122
+                    ->required(),
123
+                Forms\Components\Textarea::make('notes')
124
+                    ->label('Notes')
125
+                    ->autosize()
126
+                    ->rows(10)
127
+                    ->columnSpanFull(),
128
+            ])
129
+            ->columns();
130
+    }
131
+
132
+    public function transferForm(Form $form): Form
133
+    {
134
+        return $form
135
+            ->schema([
136
+                Forms\Components\DatePicker::make('posted_at')
137
+                    ->label('Date')
138
+                    ->required(),
139
+                Forms\Components\TextInput::make('description')
140
+                    ->label('Description'),
141
+                Forms\Components\Select::make('bank_account_id')
142
+                    ->label('From account')
143
+                    ->options(fn (Forms\Get $get, ?Transaction $transaction) => Transaction::getBankAccountOptions(excludedAccountId: $get('account_id'), currentBankAccountId: $transaction?->bank_account_id))
144
+                    ->live()
145
+                    ->searchable()
146
+                    ->afterStateUpdated(function (Forms\Set $set, $state, $old, Forms\Get $get) {
147
+                        $amount = CurrencyConverter::convertAndSet(
148
+                            BankAccount::find($state)->account->currency_code,
149
+                            BankAccount::find($old)->account->currency_code ?? CurrencyAccessor::getDefaultCurrency(),
150
+                            $get('amount')
151
+                        );
152
+
153
+                        if ($amount !== null) {
154
+                            $set('amount', $amount);
155
+                        }
156
+                    })
157
+                    ->required(),
158
+                Forms\Components\Select::make('type')
159
+                    ->label('Type')
160
+                    ->options([
161
+                        TransactionType::Transfer->value => TransactionType::Transfer->getLabel(),
162
+                    ])
163
+                    ->disabled()
164
+                    ->dehydrated()
165
+                    ->required(),
166
+                Forms\Components\TextInput::make('amount')
167
+                    ->label('Amount')
168
+                    ->money(static fn (Forms\Get $get) => BankAccount::find($get('bank_account_id'))?->account?->currency_code ?? CurrencyAccessor::getDefaultCurrency())
169
+                    ->required(),
170
+                Forms\Components\Select::make('account_id')
171
+                    ->label('To account')
172
+                    ->live()
173
+                    ->options(fn (Forms\Get $get, ?Transaction $transaction) => Transaction::getBankAccountAccountOptions(excludedBankAccountId: $get('bank_account_id'), currentAccountId: $transaction?->account_id))
174
+                    ->searchable()
175
+                    ->required(),
176
+                Forms\Components\Textarea::make('notes')
177
+                    ->label('Notes')
178
+                    ->autosize()
179
+                    ->rows(10)
180
+                    ->columnSpanFull(),
181
+            ])
182
+            ->columns();
183
+    }
184
+
185
+    public function journalTransactionForm(Form $form): Form
186
+    {
187
+        return $form
188
+            ->schema([
189
+                Forms\Components\Tabs::make('Tabs')
190
+                    ->contained(false)
191
+                    ->tabs([
192
+                        $this->getJournalTransactionFormEditTab(),
193
+                        $this->getJournalTransactionFormNotesTab(),
194
+                    ]),
195
+            ])
196
+            ->columns(1);
197
+    }
198
+
199
+    protected function getJournalTransactionFormEditTab(): Forms\Components\Tabs\Tab
200
+    {
201
+        return Forms\Components\Tabs\Tab::make('Edit')
202
+            ->label('Edit')
203
+            ->icon('heroicon-o-pencil-square')
204
+            ->schema([
205
+                $this->getTransactionDetailsGrid(),
206
+                $this->getJournalEntriesTableRepeater(),
207
+            ]);
208
+    }
209
+
210
+    protected function getJournalTransactionFormNotesTab(): Forms\Components\Tabs\Tab
211
+    {
212
+        return Forms\Components\Tabs\Tab::make('Notes')
213
+            ->label('Notes')
214
+            ->icon('heroicon-o-clipboard')
215
+            ->id('notes')
216
+            ->schema([
217
+                $this->getTransactionDetailsGrid(),
218
+                Forms\Components\Textarea::make('notes')
219
+                    ->label('Notes')
220
+                    ->rows(10)
221
+                    ->autosize(),
222
+            ]);
223
+    }
224
+
225
+    protected function getTransactionDetailsGrid(): Forms\Components\Grid
226
+    {
227
+        return Forms\Components\Grid::make(8)
228
+            ->schema([
229
+                Forms\Components\DatePicker::make('posted_at')
230
+                    ->label('Date')
231
+                    ->softRequired()
232
+                    ->displayFormat('Y-m-d'),
233
+                Forms\Components\TextInput::make('description')
234
+                    ->label('Description')
235
+                    ->columnSpan(2),
236
+            ]);
237
+    }
238
+
239
+    protected function getJournalEntriesTableRepeater(): CustomTableRepeater
240
+    {
241
+        return CustomTableRepeater::make('journalEntries')
242
+            ->relationship('journalEntries')
243
+            ->hiddenLabel()
244
+            ->columns(4)
245
+            ->headers($this->getJournalEntriesTableRepeaterHeaders())
246
+            ->schema($this->getJournalEntriesTableRepeaterSchema())
247
+            ->deletable(fn (CustomTableRepeater $repeater) => $repeater->getItemsCount() > 2)
248
+            ->deleteAction(function (Forms\Components\Actions\Action $action) {
249
+                return $action
250
+                    ->action(function (array $arguments, CustomTableRepeater $component): void {
251
+                        $items = $component->getState();
252
+
253
+                        $amount = $items[$arguments['item']]['amount'];
254
+                        $type = $items[$arguments['item']]['type'];
255
+
256
+                        $this->updateJournalEntryAmount(JournalEntryType::parse($type), '0.00', $amount);
257
+
258
+                        unset($items[$arguments['item']]);
259
+
260
+                        $component->state($items);
261
+
262
+                        $component->callAfterStateUpdated();
263
+                    });
264
+            })
265
+            ->rules([
266
+                function () {
267
+                    return function (string $attribute, $value, \Closure $fail) {
268
+                        if (empty($value) || ! is_array($value)) {
269
+                            $fail('Journal entries are required.');
270
+
271
+                            return;
272
+                        }
273
+
274
+                        $hasDebit = false;
275
+                        $hasCredit = false;
276
+
277
+                        foreach ($value as $entry) {
278
+                            if (! isset($entry['type'])) {
279
+                                continue;
280
+                            }
281
+
282
+                            if (JournalEntryType::parse($entry['type'])->isDebit()) {
283
+                                $hasDebit = true;
284
+                            } elseif (JournalEntryType::parse($entry['type'])->isCredit()) {
285
+                                $hasCredit = true;
286
+                            }
287
+
288
+                            if ($hasDebit && $hasCredit) {
289
+                                break;
290
+                            }
291
+                        }
292
+
293
+                        if (! $hasDebit) {
294
+                            $fail('At least one debit entry is required.');
295
+                        }
296
+
297
+                        if (! $hasCredit) {
298
+                            $fail('At least one credit entry is required.');
299
+                        }
300
+                    };
301
+                },
302
+            ])
303
+            ->minItems(2)
304
+            ->defaultItems(2)
305
+            ->addable(false)
306
+            ->footerItem(fn (): View => $this->getJournalTransactionModalFooter())
307
+            ->extraActions([
308
+                $this->buildAddJournalEntryAction(JournalEntryType::Debit),
309
+                $this->buildAddJournalEntryAction(JournalEntryType::Credit),
310
+            ]);
311
+    }
312
+
313
+    protected function getJournalEntriesTableRepeaterHeaders(): array
314
+    {
315
+        return [
316
+            Header::make('type')
317
+                ->width('150px')
318
+                ->label('Type'),
319
+            Header::make('description')
320
+                ->width('320px')
321
+                ->label('Description'),
322
+            Header::make('account_id')
323
+                ->width('320px')
324
+                ->label('Account'),
325
+            Header::make('amount')
326
+                ->width('192px')
327
+                ->label('Amount'),
328
+        ];
329
+    }
330
+
331
+    protected function getJournalEntriesTableRepeaterSchema(): array
332
+    {
333
+        return [
334
+            Forms\Components\Select::make('type')
335
+                ->label('Type')
336
+                ->options(JournalEntryType::class)
337
+                ->live()
338
+                ->afterStateUpdated(function (Forms\Get $get, Forms\Set $set, $state, $old) {
339
+                    $this->adjustJournalEntryAmountsForTypeChange(JournalEntryType::parse($state), JournalEntryType::parse($old), $get('amount'));
340
+                })
341
+                ->softRequired(),
342
+            Forms\Components\TextInput::make('description')
343
+                ->label('Description'),
344
+            Forms\Components\Select::make('account_id')
345
+                ->label('Account')
346
+                ->options(fn (?JournalEntry $journalEntry): array => Transaction::getChartAccountOptions(currentAccountId: $journalEntry?->account_id))
347
+                ->softRequired()
348
+                ->searchable(),
349
+            Forms\Components\TextInput::make('amount')
350
+                ->label('Amount')
351
+                ->live()
352
+                ->mask(moneyMask(CurrencyAccessor::getDefaultCurrency()))
353
+                ->afterStateUpdated(function (Forms\Get $get, Forms\Set $set, ?string $state, ?string $old) {
354
+                    $this->updateJournalEntryAmount(JournalEntryType::parse($get('type')), $state, $old);
355
+                })
356
+                ->softRequired(),
357
+        ];
358
+    }
359
+
360
+    protected function buildAddJournalEntryAction(JournalEntryType $type): FormAction
361
+    {
362
+        $typeLabel = $type->getLabel();
363
+
364
+        return FormAction::make("add{$typeLabel}Entry")
365
+            ->label("Add {$typeLabel} entry")
366
+            ->button()
367
+            ->outlined()
368
+            ->color($type->isDebit() ? 'primary' : 'gray')
369
+            ->iconSize(IconSize::Small)
370
+            ->iconPosition(IconPosition::Before)
371
+            ->action(function (CustomTableRepeater $component) use ($type) {
372
+                $state = $component->getState();
373
+                $newUuid = (string) Str::uuid();
374
+                $state[$newUuid] = $this->defaultEntry($type);
375
+
376
+                $component->state($state);
377
+            });
378
+    }
379
+
380
+    public function getJournalTransactionModalFooter(): View
381
+    {
382
+        return view(
383
+            'filament.company.components.actions.journal-entry-footer',
384
+            [
385
+                'debitAmount' => $this->getFormattedDebitAmount(),
386
+                'creditAmount' => $this->getFormattedCreditAmount(),
387
+                'difference' => $this->getFormattedBalanceDifference(),
388
+                'isJournalBalanced' => $this->isJournalEntryBalanced(),
389
+            ],
390
+        );
391
+    }
392
+
393
+    public static function getUncategorizedAccountByType(TransactionType $type): ?Account
394
+    {
395
+        [$category, $accountName] = match ($type) {
396
+            TransactionType::Deposit => [AccountCategory::Revenue, 'Uncategorized Income'],
397
+            TransactionType::Withdrawal => [AccountCategory::Expense, 'Uncategorized Expense'],
398
+            default => [null, null],
399
+        };
400
+
401
+        return Account::where('category', $category)
402
+            ->where('name', $accountName)
403
+            ->first();
404
+    }
405
+}

+ 86
- 0
app/Filament/Actions/CreateTransactionAction.php Näytä tiedosto

@@ -0,0 +1,86 @@
1
+<?php
2
+
3
+namespace App\Filament\Actions;
4
+
5
+use App\Concerns\HasTransactionAction;
6
+use App\Enums\Accounting\TransactionType;
7
+use App\Models\Accounting\Transaction;
8
+use Filament\Actions\CreateAction;
9
+use Filament\Actions\StaticAction;
10
+use Filament\Forms\Form;
11
+use Filament\Support\Enums\MaxWidth;
12
+
13
+class CreateTransactionAction extends CreateAction
14
+{
15
+    use HasTransactionAction;
16
+
17
+    protected function setUp(): void
18
+    {
19
+        parent::setUp();
20
+
21
+        $this->modalWidth(function (): MaxWidth {
22
+            return match ($this->transactionType) {
23
+                TransactionType::Journal => MaxWidth::Screen,
24
+                default => MaxWidth::ThreeExtraLarge,
25
+            };
26
+        });
27
+
28
+        $this->extraModalWindowAttributes(function (): array {
29
+            if ($this->transactionType === TransactionType::Journal) {
30
+                return ['class' => 'journal-transaction-modal'];
31
+            }
32
+
33
+            return [];
34
+        });
35
+
36
+        $this->modalHeading(function (): string {
37
+            return match ($this->transactionType) {
38
+                TransactionType::Journal => 'Journal Entry',
39
+                TransactionType::Deposit => 'Add Income',
40
+                TransactionType::Withdrawal => 'Add Expense',
41
+                TransactionType::Transfer => 'Add Transfer',
42
+                default => 'Add Transaction',
43
+            };
44
+        });
45
+
46
+        $this->fillForm(fn (): array => $this->getFormDefaultsForType($this->transactionType));
47
+
48
+        $this->form(function (Form $form) {
49
+            return match ($this->transactionType) {
50
+                TransactionType::Transfer => $this->transferForm($form),
51
+                TransactionType::Journal => $this->journalTransactionForm($form),
52
+                default => $this->transactionForm($form),
53
+            };
54
+        });
55
+
56
+        $this->afterFormFilled(function () {
57
+            if ($this->transactionType === TransactionType::Journal) {
58
+                $this->resetJournalEntryAmounts();
59
+            }
60
+        });
61
+
62
+        $this->modalSubmitAction(function (StaticAction $action) {
63
+            if ($this->transactionType === TransactionType::Journal) {
64
+                $action->disabled(! $this->isJournalEntryBalanced());
65
+            }
66
+
67
+            return $action;
68
+        });
69
+
70
+        $this->after(function (Transaction $transaction) {
71
+            if ($this->transactionType === TransactionType::Journal) {
72
+                $transaction->updateAmountIfBalanced();
73
+            }
74
+        });
75
+
76
+        $this->mutateFormDataUsing(function (array $data) {
77
+            if ($this->transactionType === TransactionType::Journal) {
78
+                $data['type'] = TransactionType::Journal;
79
+            }
80
+
81
+            return $data;
82
+        });
83
+
84
+        $this->outlined(fn () => ! $this->getGroup());
85
+    }
86
+}

+ 80
- 0
app/Filament/Actions/EditTransactionAction.php Näytä tiedosto

@@ -0,0 +1,80 @@
1
+<?php
2
+
3
+namespace App\Filament\Actions;
4
+
5
+use App\Concerns\HasTransactionAction;
6
+use App\Enums\Accounting\TransactionType;
7
+use App\Models\Accounting\Transaction;
8
+use Filament\Actions\EditAction;
9
+use Filament\Actions\StaticAction;
10
+use Filament\Forms\Form;
11
+use Filament\Support\Enums\MaxWidth;
12
+
13
+class EditTransactionAction extends EditAction
14
+{
15
+    use HasTransactionAction;
16
+
17
+    protected function setUp(): void
18
+    {
19
+        parent::setUp();
20
+
21
+        $this->transactionType = $this->getRecord()->type;
22
+
23
+        $this->visible(static fn (Transaction $transaction) => ! $transaction->transactionable_id);
24
+
25
+        $this->modalWidth(function (): MaxWidth {
26
+            return match ($this->transactionType) {
27
+                TransactionType::Journal => MaxWidth::Screen,
28
+                default => MaxWidth::ThreeExtraLarge,
29
+            };
30
+        });
31
+
32
+        $this->extraModalWindowAttributes(function (): array {
33
+            if ($this->transactionType === TransactionType::Journal) {
34
+                return ['class' => 'journal-transaction-modal'];
35
+            }
36
+
37
+            return [];
38
+        });
39
+
40
+        $this->modalHeading(function (): string {
41
+            return match ($this->transactionType) {
42
+                TransactionType::Journal => 'Journal Entry',
43
+                TransactionType::Transfer => 'Edit Transfer',
44
+                default => 'Edit Transaction',
45
+            };
46
+        });
47
+
48
+        $this->form(function (Form $form) {
49
+            return match ($this->transactionType) {
50
+                TransactionType::Transfer => $this->transferForm($form),
51
+                TransactionType::Journal => $this->journalTransactionForm($form),
52
+                default => $this->transactionForm($form),
53
+            };
54
+        });
55
+
56
+        $this->afterFormFilled(function (Transaction $record) {
57
+            if ($this->transactionType === TransactionType::Journal) {
58
+                $debitAmounts = $record->journalEntries->sumDebits()->getAmount();
59
+                $creditAmounts = $record->journalEntries->sumCredits()->getAmount();
60
+
61
+                $this->setDebitAmount($debitAmounts);
62
+                $this->setCreditAmount($creditAmounts);
63
+            }
64
+        });
65
+
66
+        $this->modalSubmitAction(function (StaticAction $action) {
67
+            if ($this->transactionType === TransactionType::Journal) {
68
+                $action->disabled(! $this->isJournalEntryBalanced());
69
+            }
70
+
71
+            return $action;
72
+        });
73
+
74
+        $this->after(function (Transaction $transaction) {
75
+            if ($this->transactionType === TransactionType::Journal) {
76
+                $transaction->updateAmountIfBalanced();
77
+            }
78
+        });
79
+    }
80
+}

+ 309
- 0
app/Filament/Company/Resources/Accounting/TransactionResource.php Näytä tiedosto

@@ -0,0 +1,309 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Accounting;
4
+
5
+use App\Enums\Accounting\TransactionType;
6
+use App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
7
+use App\Filament\Forms\Components\DateRangeSelect;
8
+use App\Filament\Tables\Actions\EditTransactionAction;
9
+use App\Filament\Tables\Actions\ReplicateBulkAction;
10
+use App\Models\Accounting\JournalEntry;
11
+use App\Models\Accounting\Transaction;
12
+use Exception;
13
+use Filament\Forms\Components\DatePicker;
14
+use Filament\Forms\Components\Grid;
15
+use Filament\Forms\Form;
16
+use Filament\Forms\Set;
17
+use Filament\Notifications\Notification;
18
+use Filament\Resources\Resource;
19
+use Filament\Support\Colors\Color;
20
+use Filament\Support\Enums\FontWeight;
21
+use Filament\Support\Enums\MaxWidth;
22
+use Filament\Tables;
23
+use Filament\Tables\Table;
24
+use Illuminate\Database\Eloquent\Builder;
25
+use Illuminate\Database\Eloquent\Collection;
26
+use Illuminate\Support\Carbon;
27
+
28
+class TransactionResource extends Resource
29
+{
30
+    protected static ?string $model = Transaction::class;
31
+
32
+    protected static ?string $navigationGroup = 'Accounting';
33
+
34
+    protected static ?string $navigationIcon = 'heroicon-o-banknotes';
35
+
36
+    protected static ?string $recordTitleAttribute = 'description';
37
+
38
+    protected static bool $isGloballySearchable = false;
39
+
40
+    public static function form(Form $form): Form
41
+    {
42
+        return $form
43
+            ->schema([]);
44
+    }
45
+
46
+    public static function table(Table $table): Table
47
+    {
48
+        return $table
49
+            ->modifyQueryUsing(function (Builder $query) {
50
+                $query->with([
51
+                    'account',
52
+                    'bankAccount.account',
53
+                    'journalEntries.account',
54
+                ])
55
+                    ->where(function (Builder $query) {
56
+                        $query->whereNull('transactionable_id')
57
+                            ->orWhere('is_payment', true);
58
+                    });
59
+            })
60
+            ->columns([
61
+                Tables\Columns\TextColumn::make('posted_at')
62
+                    ->label('Date')
63
+                    ->sortable()
64
+                    ->defaultDateFormat(),
65
+                Tables\Columns\TextColumn::make('type')
66
+                    ->label('Type')
67
+                    ->sortable()
68
+                    ->toggleable(isToggledHiddenByDefault: true),
69
+                Tables\Columns\TextColumn::make('description')
70
+                    ->label('Description')
71
+                    ->limit(50)
72
+                    ->toggleable(),
73
+                Tables\Columns\TextColumn::make('bankAccount.account.name')
74
+                    ->label('Account')
75
+                    ->toggleable(),
76
+                Tables\Columns\TextColumn::make('account.name')
77
+                    ->label('Category')
78
+                    ->prefix(static fn (Transaction $transaction) => $transaction->type->isTransfer() ? 'Transfer to ' : null)
79
+                    ->toggleable()
80
+                    ->state(static fn (Transaction $transaction) => $transaction->account->name ?? 'Journal Entry'),
81
+                Tables\Columns\TextColumn::make('amount')
82
+                    ->label('Amount')
83
+                    ->weight(static fn (Transaction $transaction) => $transaction->reviewed ? null : FontWeight::SemiBold)
84
+                    ->color(
85
+                        static fn (Transaction $transaction) => match ($transaction->type) {
86
+                            TransactionType::Deposit => Color::rgb('rgb(' . Color::Green[700] . ')'),
87
+                            TransactionType::Journal => 'primary',
88
+                            default => null,
89
+                        }
90
+                    )
91
+                    ->sortable()
92
+                    ->currency(static fn (Transaction $transaction) => $transaction->bankAccount?->account->currency_code),
93
+            ])
94
+            ->recordClasses(static fn (Transaction $transaction) => $transaction->reviewed ? 'bg-primary-300/10' : null)
95
+            ->defaultSort('posted_at', 'desc')
96
+            ->filters([
97
+                Tables\Filters\SelectFilter::make('bank_account_id')
98
+                    ->label('Account')
99
+                    ->searchable()
100
+                    ->options(fn () => Transaction::getBankAccountOptions(false)),
101
+                Tables\Filters\SelectFilter::make('account_id')
102
+                    ->label('Category')
103
+                    ->multiple()
104
+                    ->options(fn () => Transaction::getChartAccountOptions(nominalAccountsOnly: false)),
105
+                Tables\Filters\TernaryFilter::make('reviewed')
106
+                    ->label('Status')
107
+                    ->native(false)
108
+                    ->trueLabel('Reviewed')
109
+                    ->falseLabel('Not Reviewed'),
110
+                Tables\Filters\SelectFilter::make('type')
111
+                    ->label('Type')
112
+                    ->native(false)
113
+                    ->options(TransactionType::class),
114
+                static::buildDateRangeFilter('posted_at', 'Posted', true),
115
+                static::buildDateRangeFilter('updated_at', 'Last modified'),
116
+            ])
117
+            ->filtersFormSchema(fn (array $filters): array => [
118
+                Grid::make()
119
+                    ->schema([
120
+                        $filters['bank_account_id'],
121
+                        $filters['account_id'],
122
+                        $filters['reviewed'],
123
+                        $filters['type'],
124
+                    ])
125
+                    ->columnSpanFull()
126
+                    ->extraAttributes(['class' => 'border-b border-gray-200 dark:border-white/10 pb-8']),
127
+                $filters['posted_at'],
128
+                $filters['updated_at'],
129
+            ])
130
+            ->filtersFormWidth(MaxWidth::ThreeExtraLarge)
131
+            ->actions([
132
+                Tables\Actions\Action::make('markAsReviewed')
133
+                    ->label('Mark as reviewed')
134
+                    ->view('filament.company.components.tables.actions.mark-as-reviewed')
135
+                    ->icon(static fn (Transaction $transaction) => $transaction->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
136
+                    ->color(static fn (Transaction $transaction, Tables\Actions\Action $action) => match (static::determineTransactionState($transaction, $action)) {
137
+                        'reviewed' => 'primary',
138
+                        'unreviewed' => Color::rgb('rgb(' . Color::Gray[600] . ')'),
139
+                        'uncategorized' => 'gray',
140
+                    })
141
+                    ->tooltip(static fn (Transaction $transaction, Tables\Actions\Action $action) => match (static::determineTransactionState($transaction, $action)) {
142
+                        'reviewed' => 'Reviewed',
143
+                        'unreviewed' => 'Mark as reviewed',
144
+                        'uncategorized' => 'Categorize first to mark as reviewed',
145
+                    })
146
+                    ->disabled(fn (Transaction $transaction): bool => $transaction->isUncategorized())
147
+                    ->action(fn (Transaction $transaction) => $transaction->update(['reviewed' => ! $transaction->reviewed])),
148
+                Tables\Actions\ActionGroup::make([
149
+                    Tables\Actions\ActionGroup::make([
150
+                        EditTransactionAction::make('editTransaction')
151
+                            ->label('Edit transaction')
152
+                            ->visible(static fn (Transaction $transaction) => $transaction->type->isStandard() && ! $transaction->transactionable_id),
153
+                        EditTransactionAction::make('editTransfer')
154
+                            ->label('Edit transfer')
155
+                            ->type(TransactionType::Transfer)
156
+                            ->visible(static fn (Transaction $transaction) => $transaction->type->isTransfer()),
157
+                        EditTransactionAction::make('editJournalTransaction')
158
+                            ->label('Edit journal transaction')
159
+                            ->type(TransactionType::Journal)
160
+                            ->visible(static fn (Transaction $transaction) => $transaction->type->isJournal() && ! $transaction->transactionable_id),
161
+                        Tables\Actions\ReplicateAction::make()
162
+                            ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
163
+                            ->modal(false)
164
+                            ->beforeReplicaSaved(static function (Transaction $replica) {
165
+                                $replica->description = '(Copy of) ' . $replica->description;
166
+                            })
167
+                            ->hidden(static fn (Transaction $transaction) => $transaction->transactionable_id)
168
+                            ->after(static function (Transaction $original, Transaction $replica) {
169
+                                $original->journalEntries->each(function (JournalEntry $entry) use ($replica) {
170
+                                    $entry->replicate([
171
+                                        'transaction_id',
172
+                                    ])->fill([
173
+                                        'transaction_id' => $replica->id,
174
+                                    ])->save();
175
+                                });
176
+                            }),
177
+                    ])->dropdown(false),
178
+                    Tables\Actions\DeleteAction::make(),
179
+                ]),
180
+            ])
181
+            ->bulkActions([
182
+                Tables\Actions\BulkActionGroup::make([
183
+                    Tables\Actions\DeleteBulkAction::make(),
184
+                    ReplicateBulkAction::make()
185
+                        ->label('Replicate')
186
+                        ->modalWidth(MaxWidth::Large)
187
+                        ->modalDescription('Replicating transactions will also replicate their journal entries. Are you sure you want to proceed?')
188
+                        ->successNotificationTitle('Transactions replicated successfully')
189
+                        ->failureNotificationTitle('Failed to replicate transactions')
190
+                        ->deselectRecordsAfterCompletion()
191
+                        ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
192
+                        ->beforeReplicaSaved(static function (Transaction $replica) {
193
+                            $replica->description = '(Copy of) ' . $replica->description;
194
+                        })
195
+                        ->before(function (Collection $records, ReplicateBulkAction $action) {
196
+                            $isInvalid = $records->contains(fn (Transaction $record) => $record->transactionable_id);
197
+
198
+                            if ($isInvalid) {
199
+                                Notification::make()
200
+                                    ->title('Cannot replicate transactions')
201
+                                    ->body('You cannot replicate transactions associated with bills or invoices')
202
+                                    ->persistent()
203
+                                    ->danger()
204
+                                    ->send();
205
+
206
+                                $action->cancel(true);
207
+                            }
208
+                        })
209
+                        ->withReplicatedRelationships(['journalEntries']),
210
+                ]),
211
+            ]);
212
+    }
213
+
214
+    public static function getRelations(): array
215
+    {
216
+        return [
217
+            //
218
+        ];
219
+    }
220
+
221
+    public static function getPages(): array
222
+    {
223
+        return [
224
+            'index' => Pages\ListTransactions::route('/'),
225
+            'view' => Pages\ViewTransaction::route('/{record}'),
226
+        ];
227
+    }
228
+
229
+    /**
230
+     * @throws Exception
231
+     */
232
+    public static function buildDateRangeFilter(string $fieldPrefix, string $label, bool $hasBottomBorder = false): Tables\Filters\Filter
233
+    {
234
+        return Tables\Filters\Filter::make($fieldPrefix)
235
+            ->columnSpanFull()
236
+            ->form([
237
+                Grid::make()
238
+                    ->live()
239
+                    ->schema([
240
+                        DateRangeSelect::make("{$fieldPrefix}_date_range")
241
+                            ->label($label)
242
+                            ->selectablePlaceholder(false)
243
+                            ->placeholder('Select a date range')
244
+                            ->startDateField("{$fieldPrefix}_start_date")
245
+                            ->endDateField("{$fieldPrefix}_end_date"),
246
+                        DatePicker::make("{$fieldPrefix}_start_date")
247
+                            ->label("{$label} from")
248
+                            ->columnStart(1)
249
+                            ->afterStateUpdated(static function (Set $set) use ($fieldPrefix) {
250
+                                $set("{$fieldPrefix}_date_range", 'Custom');
251
+                            }),
252
+                        DatePicker::make("{$fieldPrefix}_end_date")
253
+                            ->label("{$label} to")
254
+                            ->afterStateUpdated(static function (Set $set) use ($fieldPrefix) {
255
+                                $set("{$fieldPrefix}_date_range", 'Custom');
256
+                            }),
257
+                    ])
258
+                    ->extraAttributes($hasBottomBorder ? ['class' => 'border-b border-gray-200 dark:border-white/10 pb-8'] : []),
259
+            ])
260
+            ->query(function (Builder $query, array $data) use ($fieldPrefix): Builder {
261
+                $query
262
+                    ->when($data["{$fieldPrefix}_start_date"], fn (Builder $query, $startDate) => $query->whereDate($fieldPrefix, '>=', $startDate))
263
+                    ->when($data["{$fieldPrefix}_end_date"], fn (Builder $query, $endDate) => $query->whereDate($fieldPrefix, '<=', $endDate));
264
+
265
+                return $query;
266
+            })
267
+            ->indicateUsing(function (array $data) use ($fieldPrefix, $label): array {
268
+                $indicators = [];
269
+
270
+                static::addIndicatorForDateRange($data, "{$fieldPrefix}_start_date", "{$fieldPrefix}_end_date", $label, $indicators);
271
+
272
+                return $indicators;
273
+            });
274
+
275
+    }
276
+
277
+    public static function addIndicatorForDateRange($data, $startKey, $endKey, $labelPrefix, &$indicators): void
278
+    {
279
+        $formattedStartDate = filled($data[$startKey]) ? Carbon::parse($data[$startKey])->toFormattedDateString() : null;
280
+        $formattedEndDate = filled($data[$endKey]) ? Carbon::parse($data[$endKey])->toFormattedDateString() : null;
281
+        if ($formattedStartDate && $formattedEndDate) {
282
+            // If both start and end dates are set, show the combined date range as the indicator, no specific field needs to be removed since the entire filter will be removed
283
+            $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix}: {$formattedStartDate} - {$formattedEndDate}");
284
+        } else {
285
+            if ($formattedStartDate) {
286
+                $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix} After: {$formattedStartDate}")
287
+                    ->removeField($startKey);
288
+            }
289
+
290
+            if ($formattedEndDate) {
291
+                $indicators[] = Tables\Filters\Indicator::make("{$labelPrefix} Before: {$formattedEndDate}")
292
+                    ->removeField($endKey);
293
+            }
294
+        }
295
+    }
296
+
297
+    protected static function determineTransactionState(Transaction $transaction, Tables\Actions\Action $action): string
298
+    {
299
+        if ($transaction->reviewed) {
300
+            return 'reviewed';
301
+        }
302
+
303
+        if ($transaction->reviewed === false && $action->isEnabled()) {
304
+            return 'unreviewed';
305
+        }
306
+
307
+        return 'uncategorized';
308
+    }
309
+}

+ 60
- 0
app/Filament/Company/Resources/Accounting/TransactionResource/Pages/ListTransactions.php Näytä tiedosto

@@ -0,0 +1,60 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
4
+
5
+use App\Concerns\HasJournalEntryActions;
6
+use App\Enums\Accounting\TransactionType;
7
+use App\Filament\Actions\CreateTransactionAction;
8
+use App\Filament\Company\Pages\Service\ConnectedAccount;
9
+use App\Filament\Company\Resources\Accounting\TransactionResource;
10
+use App\Services\PlaidService;
11
+use Filament\Actions;
12
+use Filament\Resources\Pages\ListRecords;
13
+use Filament\Support\Enums\IconPosition;
14
+use Filament\Support\Enums\IconSize;
15
+use Filament\Support\Enums\MaxWidth;
16
+
17
+class ListTransactions extends ListRecords
18
+{
19
+    use HasJournalEntryActions;
20
+
21
+    protected static string $resource = TransactionResource::class;
22
+
23
+    public function getMaxContentWidth(): MaxWidth | string | null
24
+    {
25
+        return 'max-w-8xl';
26
+    }
27
+
28
+    protected function getHeaderActions(): array
29
+    {
30
+        return [
31
+            CreateTransactionAction::make('addIncome')
32
+                ->label('Add income')
33
+                ->type(TransactionType::Deposit),
34
+            CreateTransactionAction::make('addExpense')
35
+                ->label('Add expense')
36
+                ->type(TransactionType::Withdrawal),
37
+            CreateTransactionAction::make('addTransfer')
38
+                ->label('Add transfer')
39
+                ->type(TransactionType::Transfer),
40
+            Actions\ActionGroup::make([
41
+                CreateTransactionAction::make('addJournalTransaction')
42
+                    ->label('Add journal transaction')
43
+                    ->type(TransactionType::Journal)
44
+                    ->groupedIcon(null),
45
+                Actions\Action::make('connectBank')
46
+                    ->label('Connect your bank')
47
+                    ->visible(app(PlaidService::class)->isEnabled())
48
+                    ->url(ConnectedAccount::getUrl()),
49
+            ])
50
+                ->label('More')
51
+                ->button()
52
+                ->outlined()
53
+                ->dropdownWidth('max-w-fit')
54
+                ->dropdownPlacement('bottom-end')
55
+                ->icon('heroicon-c-chevron-down')
56
+                ->iconSize(IconSize::Small)
57
+                ->iconPosition(IconPosition::After),
58
+        ];
59
+    }
60
+}

+ 137
- 0
app/Filament/Company/Resources/Accounting/TransactionResource/Pages/ViewTransaction.php Näytä tiedosto

@@ -0,0 +1,137 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Accounting\TransactionResource\Pages;
4
+
5
+use App\Filament\Actions\EditTransactionAction;
6
+use App\Filament\Company\Resources\Accounting\TransactionResource;
7
+use App\Models\Accounting\JournalEntry;
8
+use App\Models\Accounting\Transaction;
9
+use Filament\Actions;
10
+use Filament\Infolists\Components\Grid;
11
+use Filament\Infolists\Components\RepeatableEntry;
12
+use Filament\Infolists\Components\Section;
13
+use Filament\Infolists\Components\TextEntry;
14
+use Filament\Infolists\Infolist;
15
+use Filament\Resources\Pages\ViewRecord;
16
+use Filament\Support\Enums\FontWeight;
17
+use Filament\Support\Enums\IconPosition;
18
+use Filament\Support\Enums\IconSize;
19
+
20
+class ViewTransaction extends ViewRecord
21
+{
22
+    protected static string $resource = TransactionResource::class;
23
+
24
+    protected function getHeaderActions(): array
25
+    {
26
+        return [
27
+            EditTransactionAction::make()
28
+                ->label('Edit transaction')
29
+                ->outlined(),
30
+            Actions\ActionGroup::make([
31
+                Actions\ActionGroup::make([
32
+                    Actions\Action::make('markAsReviewed')
33
+                        ->label(static fn (Transaction $record) => $record->reviewed ? 'Mark as unreviewed' : 'Mark as reviewed')
34
+                        ->icon(static fn (Transaction $record) => $record->reviewed ? 'heroicon-s-check-circle' : 'heroicon-o-check-circle')
35
+                        ->disabled(fn (Transaction $record): bool => $record->isUncategorized())
36
+                        ->action(fn (Transaction $record) => $record->update(['reviewed' => ! $record->reviewed])),
37
+                    Actions\ReplicateAction::make()
38
+                        ->excludeAttributes(['created_by', 'updated_by', 'created_at', 'updated_at'])
39
+                        ->modal(false)
40
+                        ->beforeReplicaSaved(static function (Transaction $replica) {
41
+                            $replica->description = '(Copy of) ' . $replica->description;
42
+                        })
43
+                        ->hidden(static fn (Transaction $transaction) => $transaction->transactionable_id)
44
+                        ->after(static function (Transaction $original, Transaction $replica) {
45
+                            $original->journalEntries->each(function (JournalEntry $entry) use ($replica) {
46
+                                $entry->replicate([
47
+                                    'transaction_id',
48
+                                ])->fill([
49
+                                    'transaction_id' => $replica->id,
50
+                                ])->save();
51
+                            });
52
+                        }),
53
+                ])->dropdown(false),
54
+                Actions\DeleteAction::make(),
55
+            ])
56
+                ->label('Actions')
57
+                ->button()
58
+                ->outlined()
59
+                ->dropdownPlacement('bottom-end')
60
+                ->icon('heroicon-c-chevron-down')
61
+                ->iconSize(IconSize::Small)
62
+                ->iconPosition(IconPosition::After),
63
+        ];
64
+    }
65
+
66
+    public function infolist(Infolist $infolist): Infolist
67
+    {
68
+        return $infolist
69
+            ->schema([
70
+                Section::make('Transaction Details')
71
+                    ->columns(2)
72
+                    ->schema([
73
+                        TextEntry::make('posted_at')
74
+                            ->label('Date')
75
+                            ->date(),
76
+                        TextEntry::make('type')
77
+                            ->badge(),
78
+                        TextEntry::make('description')
79
+                            ->label('Description'),
80
+                        TextEntry::make('bankAccount.account.name')
81
+                            ->label('Account'),
82
+                        TextEntry::make('account.name')
83
+                            ->label('Category')
84
+                            ->prefix(fn (Transaction $record) => $record->type->isTransfer() ? 'Transfer to ' : null)
85
+                            ->state(fn (Transaction $record) => $record->account->name ?? 'Journal Entry'),
86
+                        TextEntry::make('amount')
87
+                            ->label('Amount')
88
+                            ->weight(fn (Transaction $record) => $record->reviewed ? null : FontWeight::SemiBold)
89
+                            ->color(
90
+                                fn (Transaction $record) => match ($record->type) {
91
+                                    \App\Enums\Accounting\TransactionType::Deposit => 'success',
92
+                                    \App\Enums\Accounting\TransactionType::Journal => 'primary',
93
+                                    default => null,
94
+                                }
95
+                            )
96
+                            ->currency(fn (Transaction $record) => $record->bankAccount?->account->currency_code),
97
+                        TextEntry::make('reviewed')
98
+                            ->label('Status')
99
+                            ->badge()
100
+                            ->formatStateUsing(fn (bool $state): string => $state ? 'Reviewed' : 'Not Reviewed')
101
+                            ->color(fn (bool $state): string => $state ? 'success' : 'warning'),
102
+                        TextEntry::make('notes')
103
+                            ->label('Notes')
104
+                            ->columnSpanFull(),
105
+                    ]),
106
+                Section::make('Journal Entries')
107
+                    ->visible(fn (Transaction $record) => $record->type->isJournal())
108
+                    ->schema([
109
+                        RepeatableEntry::make('journalEntries')
110
+                            ->hiddenLabel()
111
+                            ->schema([
112
+                                Grid::make(4)
113
+                                    ->schema([
114
+                                        TextEntry::make('type')
115
+                                            ->label('Type')
116
+                                            ->badge(),
117
+                                        TextEntry::make('description')
118
+                                            ->label('Description'),
119
+                                        TextEntry::make('account.name')
120
+                                            ->label('Account'),
121
+                                        TextEntry::make('amount')
122
+                                            ->label('Amount')
123
+                                            ->currency(fn (JournalEntry $record) => $record->transaction->bankAccount?->account->currency_code)
124
+                                            ->alignEnd(),
125
+                                    ]),
126
+                            ]),
127
+                    ]),
128
+            ]);
129
+    }
130
+
131
+    protected function getAllRelationManagers(): array
132
+    {
133
+        return [
134
+            TransactionResource\RelationManagers\JournalEntriesRelationManager::class,
135
+        ];
136
+    }
137
+}

+ 69
- 0
app/Filament/Company/Resources/Accounting/TransactionResource/RelationManagers/JournalEntriesRelationManager.php Näytä tiedosto

@@ -0,0 +1,69 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Accounting\TransactionResource\RelationManagers;
4
+
5
+use App\Utilities\Currency\CurrencyAccessor;
6
+use Filament\Forms\Form;
7
+use Filament\Resources\RelationManagers\RelationManager;
8
+use Filament\Support\Enums\FontWeight;
9
+use Filament\Tables;
10
+use Filament\Tables\Table;
11
+
12
+class JournalEntriesRelationManager extends RelationManager
13
+{
14
+    protected static string $relationship = 'journalEntries';
15
+
16
+    public function form(Form $form): Form
17
+    {
18
+        return $form
19
+            ->schema([]);
20
+    }
21
+
22
+    public function table(Table $table): Table
23
+    {
24
+        return $table
25
+            ->columns([
26
+                Tables\Columns\TextColumn::make('type')
27
+                    ->label('Type'),
28
+                Tables\Columns\TextColumn::make('account.name')
29
+                    ->label('Account')
30
+                    ->searchable()
31
+                    ->sortable(),
32
+                Tables\Columns\TextColumn::make('account.category')
33
+                    ->label('Category')
34
+                    ->badge(),
35
+                Tables\Columns\TextColumn::make('description')
36
+                    ->label('Description')
37
+                    ->searchable()
38
+                    ->limit(50),
39
+                Tables\Columns\TextColumn::make('amount')
40
+                    ->label('Amount')
41
+                    ->weight(FontWeight::SemiBold)
42
+                    ->sortable()
43
+                    ->alignRight()
44
+                    ->currency(CurrencyAccessor::getDefaultCurrency()),
45
+                Tables\Columns\TextColumn::make('created_at')
46
+                    ->label('Created at')
47
+                    ->dateTime()
48
+                    ->toggleable(isToggledHiddenByDefault: true)
49
+                    ->sortable(),
50
+                Tables\Columns\TextColumn::make('updated_at')
51
+                    ->label('Updated at')
52
+                    ->dateTime()
53
+                    ->toggleable(isToggledHiddenByDefault: true)
54
+                    ->sortable(),
55
+            ])
56
+            ->filters([
57
+                //
58
+            ])
59
+            ->headerActions([
60
+                //
61
+            ])
62
+            ->actions([
63
+                //
64
+            ])
65
+            ->bulkActions([
66
+                //
67
+            ]);
68
+    }
69
+}

+ 76
- 0
app/Filament/Tables/Actions/EditTransactionAction.php Näytä tiedosto

@@ -0,0 +1,76 @@
1
+<?php
2
+
3
+namespace App\Filament\Tables\Actions;
4
+
5
+use App\Concerns\HasTransactionAction;
6
+use App\Enums\Accounting\TransactionType;
7
+use App\Models\Accounting\Transaction;
8
+use Filament\Actions\StaticAction;
9
+use Filament\Forms\Form;
10
+use Filament\Support\Enums\MaxWidth;
11
+use Filament\Tables\Actions\EditAction;
12
+
13
+class EditTransactionAction extends EditAction
14
+{
15
+    use HasTransactionAction;
16
+
17
+    protected function setUp(): void
18
+    {
19
+        parent::setUp();
20
+
21
+        $this->modalWidth(function (): MaxWidth {
22
+            return match ($this->transactionType) {
23
+                TransactionType::Journal => MaxWidth::Screen,
24
+                default => MaxWidth::ThreeExtraLarge,
25
+            };
26
+        });
27
+
28
+        $this->extraModalWindowAttributes(function (): array {
29
+            if ($this->transactionType === TransactionType::Journal) {
30
+                return ['class' => 'journal-transaction-modal'];
31
+            }
32
+
33
+            return [];
34
+        });
35
+
36
+        $this->modalHeading(function (): string {
37
+            return match ($this->transactionType) {
38
+                TransactionType::Journal => 'Journal Entry',
39
+                TransactionType::Transfer => 'Edit Transfer',
40
+                default => 'Edit Transaction',
41
+            };
42
+        });
43
+
44
+        $this->form(function (Form $form) {
45
+            return match ($this->transactionType) {
46
+                TransactionType::Transfer => $this->transferForm($form),
47
+                TransactionType::Journal => $this->journalTransactionForm($form),
48
+                default => $this->transactionForm($form),
49
+            };
50
+        });
51
+
52
+        $this->afterFormFilled(function (Transaction $record) {
53
+            if ($this->transactionType === TransactionType::Journal) {
54
+                $debitAmounts = $record->journalEntries->sumDebits()->getAmount();
55
+                $creditAmounts = $record->journalEntries->sumCredits()->getAmount();
56
+
57
+                $this->setDebitAmount($debitAmounts);
58
+                $this->setCreditAmount($creditAmounts);
59
+            }
60
+        });
61
+
62
+        $this->modalSubmitAction(function (StaticAction $action) {
63
+            if ($this->transactionType === TransactionType::Journal) {
64
+                $action->disabled(! $this->isJournalEntryBalanced());
65
+            }
66
+
67
+            return $action;
68
+        });
69
+
70
+        $this->after(function (Transaction $transaction) {
71
+            if ($this->transactionType === TransactionType::Journal) {
72
+                $transaction->updateAmountIfBalanced();
73
+            }
74
+        });
75
+    }
76
+}

+ 64
- 0
app/Models/Accounting/Transaction.php Näytä tiedosto

@@ -5,6 +5,7 @@ namespace App\Models\Accounting;
5 5
 use App\Casts\TransactionAmountCast;
6 6
 use App\Concerns\Blamable;
7 7
 use App\Concerns\CompanyOwned;
8
+use App\Enums\Accounting\AccountCategory;
8 9
 use App\Enums\Accounting\PaymentMethod;
9 10
 use App\Enums\Accounting\TransactionType;
10 11
 use App\Models\Banking\BankAccount;
@@ -12,12 +13,14 @@ use App\Models\Common\Contact;
12 13
 use App\Observers\TransactionObserver;
13 14
 use Database\Factories\Accounting\TransactionFactory;
14 15
 use Illuminate\Database\Eloquent\Attributes\ObservedBy;
16
+use Illuminate\Database\Eloquent\Builder;
15 17
 use Illuminate\Database\Eloquent\Factories\Factory;
16 18
 use Illuminate\Database\Eloquent\Factories\HasFactory;
17 19
 use Illuminate\Database\Eloquent\Model;
18 20
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
19 21
 use Illuminate\Database\Eloquent\Relations\HasMany;
20 22
 use Illuminate\Database\Eloquent\Relations\MorphTo;
23
+use Illuminate\Support\Collection;
21 24
 
22 25
 #[ObservedBy(TransactionObserver::class)]
23 26
 class Transaction extends Model
@@ -96,6 +99,67 @@ class Transaction extends Model
96 99
         }
97 100
     }
98 101
 
102
+    public static function getBankAccountOptions(?int $excludedAccountId = null, ?int $currentBankAccountId = null): array
103
+    {
104
+        return BankAccount::query()
105
+            ->whereHas('account', function (Builder $query) {
106
+                $query->where('archived', false);
107
+            })
108
+            ->with(['account' => function ($query) {
109
+                $query->where('archived', false);
110
+            }, 'account.subtype' => function ($query) {
111
+                $query->select(['id', 'name']);
112
+            }])
113
+            ->when($excludedAccountId, fn (Builder $query) => $query->where('account_id', '!=', $excludedAccountId))
114
+            ->when($currentBankAccountId, fn (Builder $query) => $query->orWhere('id', $currentBankAccountId))
115
+            ->get()
116
+            ->groupBy('account.subtype.name')
117
+            ->map(fn (Collection $bankAccounts, string $subtype) => $bankAccounts->pluck('account.name', 'id'))
118
+            ->toArray();
119
+    }
120
+
121
+    public static function getBankAccountAccountOptions(?int $excludedBankAccountId = null, ?int $currentAccountId = null): array
122
+    {
123
+        return Account::query()
124
+            ->whereHas('bankAccount', function (Builder $query) use ($excludedBankAccountId) {
125
+                // Exclude the specific bank account if provided
126
+                if ($excludedBankAccountId) {
127
+                    $query->whereNot('id', $excludedBankAccountId);
128
+                }
129
+            })
130
+            ->where(function (Builder $query) use ($currentAccountId) {
131
+                $query->where('archived', false)
132
+                    ->orWhere('id', $currentAccountId);
133
+            })
134
+            ->get()
135
+            ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
136
+            ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
137
+            ->toArray();
138
+    }
139
+
140
+    public static function getChartAccountOptions(?TransactionType $type = null, ?bool $nominalAccountsOnly = null, ?int $currentAccountId = null): array
141
+    {
142
+        $nominalAccountsOnly ??= false;
143
+
144
+        $excludedCategory = match ($type) {
145
+            TransactionType::Deposit => AccountCategory::Expense,
146
+            TransactionType::Withdrawal => AccountCategory::Revenue,
147
+            default => null,
148
+        };
149
+
150
+        return Account::query()
151
+            ->when($nominalAccountsOnly, fn (Builder $query) => $query->doesntHave('bankAccount'))
152
+            ->when($excludedCategory, fn (Builder $query) => $query->whereNot('category', $excludedCategory))
153
+            ->where(function (Builder $query) use ($currentAccountId) {
154
+                $query->where('archived', false)
155
+                    ->orWhere('id', $currentAccountId);
156
+            })
157
+            ->get()
158
+            ->groupBy(fn (Account $account) => $account->category->getPluralLabel())
159
+            ->map(fn (Collection $accounts, string $category) => $accounts->pluck('name', 'id'))
160
+            ->toArray();
161
+    }
162
+
99 163
     protected static function newFactory(): Factory
100 164
     {
101 165
         return TransactionFactory::new();

+ 2
- 0
app/Providers/Filament/CompanyPanelProvider.php Näytä tiedosto

@@ -26,6 +26,7 @@ use App\Filament\Company\Pages\Reports;
26 26
 use App\Filament\Company\Pages\Service\ConnectedAccount;
27 27
 use App\Filament\Company\Pages\Service\LiveCurrency;
28 28
 use App\Filament\Company\Resources\Accounting\BudgetResource;
29
+use App\Filament\Company\Resources\Accounting\TransactionResource;
29 30
 use App\Filament\Company\Resources\Banking\AccountResource;
30 31
 use App\Filament\Company\Resources\Common\OfferingResource;
31 32
 use App\Filament\Company\Resources\Purchases\BillResource;
@@ -159,6 +160,7 @@ class CompanyPanelProvider extends PanelProvider
159 160
                                 // ...BudgetResource::getNavigationItems(),
160 161
                                 ...AccountChart::getNavigationItems(),
161 162
                                 ...Transactions::getNavigationItems(),
163
+                                ...TransactionResource::getNavigationItems(),
162 164
                             ]),
163 165
                         NavigationGroup::make('Banking')
164 166
                             ->localizeLabel()

+ 18
- 18
composer.lock Näytä tiedosto

@@ -368,16 +368,16 @@
368 368
         },
369 369
         {
370 370
             "name": "awcodes/filament-table-repeater",
371
-            "version": "v3.1.3",
371
+            "version": "v3.1.4",
372 372
             "source": {
373 373
                 "type": "git",
374 374
                 "url": "https://github.com/awcodes/filament-table-repeater.git",
375
-                "reference": "fd8df8fbb94a41d0a031a75ef739538290a14a8c"
375
+                "reference": "275de32e2123a2f7e586404352ee4c794f019a09"
376 376
             },
377 377
             "dist": {
378 378
                 "type": "zip",
379
-                "url": "https://api.github.com/repos/awcodes/filament-table-repeater/zipball/fd8df8fbb94a41d0a031a75ef739538290a14a8c",
380
-                "reference": "fd8df8fbb94a41d0a031a75ef739538290a14a8c",
379
+                "url": "https://api.github.com/repos/awcodes/filament-table-repeater/zipball/275de32e2123a2f7e586404352ee4c794f019a09",
380
+                "reference": "275de32e2123a2f7e586404352ee4c794f019a09",
381 381
                 "shasum": ""
382 382
             },
383 383
             "require": {
@@ -431,7 +431,7 @@
431 431
             ],
432 432
             "support": {
433 433
                 "issues": "https://github.com/awcodes/filament-table-repeater/issues",
434
-                "source": "https://github.com/awcodes/filament-table-repeater/tree/v3.1.3"
434
+                "source": "https://github.com/awcodes/filament-table-repeater/tree/v3.1.4"
435 435
             },
436 436
             "funding": [
437 437
                 {
@@ -439,7 +439,7 @@
439 439
                     "type": "github"
440 440
                 }
441 441
             ],
442
-            "time": "2025-05-03T14:59:55+00:00"
442
+            "time": "2025-05-15T15:46:52+00:00"
443 443
         },
444 444
         {
445 445
             "name": "aws/aws-crt-php",
@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.343.9",
500
+            "version": "3.343.13",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "6ca5eb1c60b879cf516e5fadefec87afc6219e74"
504
+                "reference": "eb50d111a09ef39675358e74801260ac129ee346"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6ca5eb1c60b879cf516e5fadefec87afc6219e74",
509
-                "reference": "6ca5eb1c60b879cf516e5fadefec87afc6219e74",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb50d111a09ef39675358e74801260ac129ee346",
509
+                "reference": "eb50d111a09ef39675358e74801260ac129ee346",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -588,9 +588,9 @@
588 588
             "support": {
589 589
                 "forum": "https://github.com/aws/aws-sdk-php/discussions",
590 590
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
591
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.343.9"
591
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.343.13"
592 592
             },
593
-            "time": "2025-05-12T18:11:31+00:00"
593
+            "time": "2025-05-16T18:24:39+00:00"
594 594
         },
595 595
         {
596 596
             "name": "aws/aws-sdk-php-laravel",
@@ -1029,7 +1029,7 @@
1029 1029
         },
1030 1030
         {
1031 1031
             "name": "codewithdennis/filament-simple-alert",
1032
-            "version": "v3.0.17",
1032
+            "version": "v3.0.18",
1033 1033
             "source": {
1034 1034
                 "type": "git",
1035 1035
                 "url": "https://github.com/CodeWithDennis/filament-simple-alert.git",
@@ -9861,16 +9861,16 @@
9861 9861
         },
9862 9862
         {
9863 9863
             "name": "laravel/sail",
9864
-            "version": "v1.42.0",
9864
+            "version": "v1.43.0",
9865 9865
             "source": {
9866 9866
                 "type": "git",
9867 9867
                 "url": "https://github.com/laravel/sail.git",
9868
-                "reference": "2edaaf77f3c07a4099965bb3d7dfee16e801c0f6"
9868
+                "reference": "71a509b14b2621ce58574274a74290f933c687f7"
9869 9869
             },
9870 9870
             "dist": {
9871 9871
                 "type": "zip",
9872
-                "url": "https://api.github.com/repos/laravel/sail/zipball/2edaaf77f3c07a4099965bb3d7dfee16e801c0f6",
9873
-                "reference": "2edaaf77f3c07a4099965bb3d7dfee16e801c0f6",
9872
+                "url": "https://api.github.com/repos/laravel/sail/zipball/71a509b14b2621ce58574274a74290f933c687f7",
9873
+                "reference": "71a509b14b2621ce58574274a74290f933c687f7",
9874 9874
                 "shasum": ""
9875 9875
             },
9876 9876
             "require": {
@@ -9920,7 +9920,7 @@
9920 9920
                 "issues": "https://github.com/laravel/sail/issues",
9921 9921
                 "source": "https://github.com/laravel/sail"
9922 9922
             },
9923
-            "time": "2025-04-29T14:26:46+00:00"
9923
+            "time": "2025-05-13T13:34:34+00:00"
9924 9924
         },
9925 9925
         {
9926 9926
             "name": "mockery/mockery",

+ 303
- 212
package-lock.json Näytä tiedosto

@@ -30,9 +30,9 @@
30 30
             }
31 31
         },
32 32
         "node_modules/@esbuild/aix-ppc64": {
33
-            "version": "0.25.1",
34
-            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
35
-            "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
33
+            "version": "0.25.4",
34
+            "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
35
+            "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
36 36
             "cpu": [
37 37
                 "ppc64"
38 38
             ],
@@ -47,9 +47,9 @@
47 47
             }
48 48
         },
49 49
         "node_modules/@esbuild/android-arm": {
50
-            "version": "0.25.1",
51
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
52
-            "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
50
+            "version": "0.25.4",
51
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
52
+            "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
53 53
             "cpu": [
54 54
                 "arm"
55 55
             ],
@@ -64,9 +64,9 @@
64 64
             }
65 65
         },
66 66
         "node_modules/@esbuild/android-arm64": {
67
-            "version": "0.25.1",
68
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
69
-            "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
67
+            "version": "0.25.4",
68
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
69
+            "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
70 70
             "cpu": [
71 71
                 "arm64"
72 72
             ],
@@ -81,9 +81,9 @@
81 81
             }
82 82
         },
83 83
         "node_modules/@esbuild/android-x64": {
84
-            "version": "0.25.1",
85
-            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
86
-            "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
84
+            "version": "0.25.4",
85
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
86
+            "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
87 87
             "cpu": [
88 88
                 "x64"
89 89
             ],
@@ -98,9 +98,9 @@
98 98
             }
99 99
         },
100 100
         "node_modules/@esbuild/darwin-arm64": {
101
-            "version": "0.25.1",
102
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
103
-            "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
101
+            "version": "0.25.4",
102
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
103
+            "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
104 104
             "cpu": [
105 105
                 "arm64"
106 106
             ],
@@ -115,9 +115,9 @@
115 115
             }
116 116
         },
117 117
         "node_modules/@esbuild/darwin-x64": {
118
-            "version": "0.25.1",
119
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
120
-            "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
118
+            "version": "0.25.4",
119
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
120
+            "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
121 121
             "cpu": [
122 122
                 "x64"
123 123
             ],
@@ -132,9 +132,9 @@
132 132
             }
133 133
         },
134 134
         "node_modules/@esbuild/freebsd-arm64": {
135
-            "version": "0.25.1",
136
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
137
-            "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
135
+            "version": "0.25.4",
136
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
137
+            "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
138 138
             "cpu": [
139 139
                 "arm64"
140 140
             ],
@@ -149,9 +149,9 @@
149 149
             }
150 150
         },
151 151
         "node_modules/@esbuild/freebsd-x64": {
152
-            "version": "0.25.1",
153
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
154
-            "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
152
+            "version": "0.25.4",
153
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
154
+            "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
155 155
             "cpu": [
156 156
                 "x64"
157 157
             ],
@@ -166,9 +166,9 @@
166 166
             }
167 167
         },
168 168
         "node_modules/@esbuild/linux-arm": {
169
-            "version": "0.25.1",
170
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
171
-            "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
169
+            "version": "0.25.4",
170
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
171
+            "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
172 172
             "cpu": [
173 173
                 "arm"
174 174
             ],
@@ -183,9 +183,9 @@
183 183
             }
184 184
         },
185 185
         "node_modules/@esbuild/linux-arm64": {
186
-            "version": "0.25.1",
187
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
188
-            "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
186
+            "version": "0.25.4",
187
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
188
+            "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
189 189
             "cpu": [
190 190
                 "arm64"
191 191
             ],
@@ -200,9 +200,9 @@
200 200
             }
201 201
         },
202 202
         "node_modules/@esbuild/linux-ia32": {
203
-            "version": "0.25.1",
204
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
205
-            "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
203
+            "version": "0.25.4",
204
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
205
+            "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
206 206
             "cpu": [
207 207
                 "ia32"
208 208
             ],
@@ -217,9 +217,9 @@
217 217
             }
218 218
         },
219 219
         "node_modules/@esbuild/linux-loong64": {
220
-            "version": "0.25.1",
221
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
222
-            "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
220
+            "version": "0.25.4",
221
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
222
+            "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
223 223
             "cpu": [
224 224
                 "loong64"
225 225
             ],
@@ -234,9 +234,9 @@
234 234
             }
235 235
         },
236 236
         "node_modules/@esbuild/linux-mips64el": {
237
-            "version": "0.25.1",
238
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
239
-            "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
237
+            "version": "0.25.4",
238
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
239
+            "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
240 240
             "cpu": [
241 241
                 "mips64el"
242 242
             ],
@@ -251,9 +251,9 @@
251 251
             }
252 252
         },
253 253
         "node_modules/@esbuild/linux-ppc64": {
254
-            "version": "0.25.1",
255
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
256
-            "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
254
+            "version": "0.25.4",
255
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
256
+            "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
257 257
             "cpu": [
258 258
                 "ppc64"
259 259
             ],
@@ -268,9 +268,9 @@
268 268
             }
269 269
         },
270 270
         "node_modules/@esbuild/linux-riscv64": {
271
-            "version": "0.25.1",
272
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
273
-            "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
271
+            "version": "0.25.4",
272
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
273
+            "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
274 274
             "cpu": [
275 275
                 "riscv64"
276 276
             ],
@@ -285,9 +285,9 @@
285 285
             }
286 286
         },
287 287
         "node_modules/@esbuild/linux-s390x": {
288
-            "version": "0.25.1",
289
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
290
-            "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
288
+            "version": "0.25.4",
289
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
290
+            "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
291 291
             "cpu": [
292 292
                 "s390x"
293 293
             ],
@@ -302,9 +302,9 @@
302 302
             }
303 303
         },
304 304
         "node_modules/@esbuild/linux-x64": {
305
-            "version": "0.25.1",
306
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
307
-            "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
305
+            "version": "0.25.4",
306
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
307
+            "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
308 308
             "cpu": [
309 309
                 "x64"
310 310
             ],
@@ -319,9 +319,9 @@
319 319
             }
320 320
         },
321 321
         "node_modules/@esbuild/netbsd-arm64": {
322
-            "version": "0.25.1",
323
-            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
324
-            "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
322
+            "version": "0.25.4",
323
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
324
+            "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
325 325
             "cpu": [
326 326
                 "arm64"
327 327
             ],
@@ -336,9 +336,9 @@
336 336
             }
337 337
         },
338 338
         "node_modules/@esbuild/netbsd-x64": {
339
-            "version": "0.25.1",
340
-            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
341
-            "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
339
+            "version": "0.25.4",
340
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
341
+            "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
342 342
             "cpu": [
343 343
                 "x64"
344 344
             ],
@@ -353,9 +353,9 @@
353 353
             }
354 354
         },
355 355
         "node_modules/@esbuild/openbsd-arm64": {
356
-            "version": "0.25.1",
357
-            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
358
-            "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
356
+            "version": "0.25.4",
357
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
358
+            "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
359 359
             "cpu": [
360 360
                 "arm64"
361 361
             ],
@@ -370,9 +370,9 @@
370 370
             }
371 371
         },
372 372
         "node_modules/@esbuild/openbsd-x64": {
373
-            "version": "0.25.1",
374
-            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
375
-            "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
373
+            "version": "0.25.4",
374
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
375
+            "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
376 376
             "cpu": [
377 377
                 "x64"
378 378
             ],
@@ -387,9 +387,9 @@
387 387
             }
388 388
         },
389 389
         "node_modules/@esbuild/sunos-x64": {
390
-            "version": "0.25.1",
391
-            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
392
-            "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
390
+            "version": "0.25.4",
391
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
392
+            "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
393 393
             "cpu": [
394 394
                 "x64"
395 395
             ],
@@ -404,9 +404,9 @@
404 404
             }
405 405
         },
406 406
         "node_modules/@esbuild/win32-arm64": {
407
-            "version": "0.25.1",
408
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
409
-            "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
407
+            "version": "0.25.4",
408
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
409
+            "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
410 410
             "cpu": [
411 411
                 "arm64"
412 412
             ],
@@ -421,9 +421,9 @@
421 421
             }
422 422
         },
423 423
         "node_modules/@esbuild/win32-ia32": {
424
-            "version": "0.25.1",
425
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
426
-            "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
424
+            "version": "0.25.4",
425
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
426
+            "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
427 427
             "cpu": [
428 428
                 "ia32"
429 429
             ],
@@ -438,9 +438,9 @@
438 438
             }
439 439
         },
440 440
         "node_modules/@esbuild/win32-x64": {
441
-            "version": "0.25.1",
442
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
443
-            "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
441
+            "version": "0.25.4",
442
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
443
+            "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
444 444
             "cpu": [
445 445
                 "x64"
446 446
             ],
@@ -575,9 +575,9 @@
575 575
             }
576 576
         },
577 577
         "node_modules/@rollup/rollup-android-arm-eabi": {
578
-            "version": "4.36.0",
579
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
580
-            "integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==",
578
+            "version": "4.40.2",
579
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz",
580
+            "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==",
581 581
             "cpu": [
582 582
                 "arm"
583 583
             ],
@@ -589,9 +589,9 @@
589 589
             ]
590 590
         },
591 591
         "node_modules/@rollup/rollup-android-arm64": {
592
-            "version": "4.36.0",
593
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz",
594
-            "integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==",
592
+            "version": "4.40.2",
593
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz",
594
+            "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==",
595 595
             "cpu": [
596 596
                 "arm64"
597 597
             ],
@@ -603,9 +603,9 @@
603 603
             ]
604 604
         },
605 605
         "node_modules/@rollup/rollup-darwin-arm64": {
606
-            "version": "4.36.0",
607
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz",
608
-            "integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==",
606
+            "version": "4.40.2",
607
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz",
608
+            "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==",
609 609
             "cpu": [
610 610
                 "arm64"
611 611
             ],
@@ -617,9 +617,9 @@
617 617
             ]
618 618
         },
619 619
         "node_modules/@rollup/rollup-darwin-x64": {
620
-            "version": "4.36.0",
621
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz",
622
-            "integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==",
620
+            "version": "4.40.2",
621
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz",
622
+            "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==",
623 623
             "cpu": [
624 624
                 "x64"
625 625
             ],
@@ -631,9 +631,9 @@
631 631
             ]
632 632
         },
633 633
         "node_modules/@rollup/rollup-freebsd-arm64": {
634
-            "version": "4.36.0",
635
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz",
636
-            "integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==",
634
+            "version": "4.40.2",
635
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz",
636
+            "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==",
637 637
             "cpu": [
638 638
                 "arm64"
639 639
             ],
@@ -645,9 +645,9 @@
645 645
             ]
646 646
         },
647 647
         "node_modules/@rollup/rollup-freebsd-x64": {
648
-            "version": "4.36.0",
649
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz",
650
-            "integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==",
648
+            "version": "4.40.2",
649
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz",
650
+            "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==",
651 651
             "cpu": [
652 652
                 "x64"
653 653
             ],
@@ -659,9 +659,9 @@
659 659
             ]
660 660
         },
661 661
         "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
662
-            "version": "4.36.0",
663
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz",
664
-            "integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==",
662
+            "version": "4.40.2",
663
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz",
664
+            "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==",
665 665
             "cpu": [
666 666
                 "arm"
667 667
             ],
@@ -673,9 +673,9 @@
673 673
             ]
674 674
         },
675 675
         "node_modules/@rollup/rollup-linux-arm-musleabihf": {
676
-            "version": "4.36.0",
677
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz",
678
-            "integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==",
676
+            "version": "4.40.2",
677
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz",
678
+            "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==",
679 679
             "cpu": [
680 680
                 "arm"
681 681
             ],
@@ -687,9 +687,9 @@
687 687
             ]
688 688
         },
689 689
         "node_modules/@rollup/rollup-linux-arm64-gnu": {
690
-            "version": "4.36.0",
691
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz",
692
-            "integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==",
690
+            "version": "4.40.2",
691
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz",
692
+            "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==",
693 693
             "cpu": [
694 694
                 "arm64"
695 695
             ],
@@ -701,9 +701,9 @@
701 701
             ]
702 702
         },
703 703
         "node_modules/@rollup/rollup-linux-arm64-musl": {
704
-            "version": "4.36.0",
705
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz",
706
-            "integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==",
704
+            "version": "4.40.2",
705
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz",
706
+            "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==",
707 707
             "cpu": [
708 708
                 "arm64"
709 709
             ],
@@ -715,9 +715,9 @@
715 715
             ]
716 716
         },
717 717
         "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
718
-            "version": "4.36.0",
719
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz",
720
-            "integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==",
718
+            "version": "4.40.2",
719
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz",
720
+            "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==",
721 721
             "cpu": [
722 722
                 "loong64"
723 723
             ],
@@ -729,9 +729,9 @@
729 729
             ]
730 730
         },
731 731
         "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
732
-            "version": "4.36.0",
733
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz",
734
-            "integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==",
732
+            "version": "4.40.2",
733
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz",
734
+            "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==",
735 735
             "cpu": [
736 736
                 "ppc64"
737 737
             ],
@@ -743,9 +743,23 @@
743 743
             ]
744 744
         },
745 745
         "node_modules/@rollup/rollup-linux-riscv64-gnu": {
746
-            "version": "4.36.0",
747
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz",
748
-            "integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==",
746
+            "version": "4.40.2",
747
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz",
748
+            "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==",
749
+            "cpu": [
750
+                "riscv64"
751
+            ],
752
+            "dev": true,
753
+            "license": "MIT",
754
+            "optional": true,
755
+            "os": [
756
+                "linux"
757
+            ]
758
+        },
759
+        "node_modules/@rollup/rollup-linux-riscv64-musl": {
760
+            "version": "4.40.2",
761
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz",
762
+            "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==",
749 763
             "cpu": [
750 764
                 "riscv64"
751 765
             ],
@@ -757,9 +771,9 @@
757 771
             ]
758 772
         },
759 773
         "node_modules/@rollup/rollup-linux-s390x-gnu": {
760
-            "version": "4.36.0",
761
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz",
762
-            "integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==",
774
+            "version": "4.40.2",
775
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz",
776
+            "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==",
763 777
             "cpu": [
764 778
                 "s390x"
765 779
             ],
@@ -771,9 +785,9 @@
771 785
             ]
772 786
         },
773 787
         "node_modules/@rollup/rollup-linux-x64-gnu": {
774
-            "version": "4.36.0",
775
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz",
776
-            "integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==",
788
+            "version": "4.40.2",
789
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz",
790
+            "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==",
777 791
             "cpu": [
778 792
                 "x64"
779 793
             ],
@@ -785,9 +799,9 @@
785 799
             ]
786 800
         },
787 801
         "node_modules/@rollup/rollup-linux-x64-musl": {
788
-            "version": "4.36.0",
789
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz",
790
-            "integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==",
802
+            "version": "4.40.2",
803
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz",
804
+            "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==",
791 805
             "cpu": [
792 806
                 "x64"
793 807
             ],
@@ -799,9 +813,9 @@
799 813
             ]
800 814
         },
801 815
         "node_modules/@rollup/rollup-win32-arm64-msvc": {
802
-            "version": "4.36.0",
803
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz",
804
-            "integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==",
816
+            "version": "4.40.2",
817
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz",
818
+            "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==",
805 819
             "cpu": [
806 820
                 "arm64"
807 821
             ],
@@ -813,9 +827,9 @@
813 827
             ]
814 828
         },
815 829
         "node_modules/@rollup/rollup-win32-ia32-msvc": {
816
-            "version": "4.36.0",
817
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz",
818
-            "integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==",
830
+            "version": "4.40.2",
831
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz",
832
+            "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==",
819 833
             "cpu": [
820 834
                 "ia32"
821 835
             ],
@@ -827,9 +841,9 @@
827 841
             ]
828 842
         },
829 843
         "node_modules/@rollup/rollup-win32-x64-msvc": {
830
-            "version": "4.36.0",
831
-            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz",
832
-            "integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==",
844
+            "version": "4.40.2",
845
+            "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz",
846
+            "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==",
833 847
             "cpu": [
834 848
                 "x64"
835 849
             ],
@@ -870,9 +884,9 @@
870 884
             }
871 885
         },
872 886
         "node_modules/@types/estree": {
873
-            "version": "1.0.6",
874
-            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
875
-            "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
887
+            "version": "1.0.7",
888
+            "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
889
+            "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
876 890
             "dev": true,
877 891
             "license": "MIT"
878 892
         },
@@ -976,9 +990,9 @@
976 990
             }
977 991
         },
978 992
         "node_modules/axios": {
979
-            "version": "1.8.3",
980
-            "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
981
-            "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
993
+            "version": "1.9.0",
994
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
995
+            "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
982 996
             "dev": true,
983 997
             "license": "MIT",
984 998
             "dependencies": {
@@ -1031,9 +1045,9 @@
1031 1045
             }
1032 1046
         },
1033 1047
         "node_modules/browserslist": {
1034
-            "version": "4.24.4",
1035
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
1036
-            "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
1048
+            "version": "4.24.5",
1049
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
1050
+            "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
1037 1051
             "dev": true,
1038 1052
             "funding": [
1039 1053
                 {
@@ -1051,10 +1065,10 @@
1051 1065
             ],
1052 1066
             "license": "MIT",
1053 1067
             "dependencies": {
1054
-                "caniuse-lite": "^1.0.30001688",
1055
-                "electron-to-chromium": "^1.5.73",
1068
+                "caniuse-lite": "^1.0.30001716",
1069
+                "electron-to-chromium": "^1.5.149",
1056 1070
                 "node-releases": "^2.0.19",
1057
-                "update-browserslist-db": "^1.1.1"
1071
+                "update-browserslist-db": "^1.1.3"
1058 1072
             },
1059 1073
             "bin": {
1060 1074
                 "browserslist": "cli.js"
@@ -1088,9 +1102,9 @@
1088 1102
             }
1089 1103
         },
1090 1104
         "node_modules/caniuse-lite": {
1091
-            "version": "1.0.30001706",
1092
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz",
1093
-            "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==",
1105
+            "version": "1.0.30001718",
1106
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
1107
+            "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
1094 1108
             "dev": true,
1095 1109
             "funding": [
1096 1110
                 {
@@ -1264,9 +1278,9 @@
1264 1278
             "license": "MIT"
1265 1279
         },
1266 1280
         "node_modules/electron-to-chromium": {
1267
-            "version": "1.5.120",
1268
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.120.tgz",
1269
-            "integrity": "sha512-oTUp3gfX1gZI+xfD2djr2rzQdHCwHzPQrrK0CD7WpTdF0nPdQ/INcRVjWgLdCT4a9W3jFObR9DAfsuyFQnI8CQ==",
1281
+            "version": "1.5.155",
1282
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz",
1283
+            "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==",
1270 1284
             "dev": true,
1271 1285
             "license": "ISC"
1272 1286
         },
@@ -1327,9 +1341,9 @@
1327 1341
             }
1328 1342
         },
1329 1343
         "node_modules/esbuild": {
1330
-            "version": "0.25.1",
1331
-            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
1332
-            "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
1344
+            "version": "0.25.4",
1345
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
1346
+            "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
1333 1347
             "dev": true,
1334 1348
             "hasInstallScript": true,
1335 1349
             "license": "MIT",
@@ -1340,31 +1354,31 @@
1340 1354
                 "node": ">=18"
1341 1355
             },
1342 1356
             "optionalDependencies": {
1343
-                "@esbuild/aix-ppc64": "0.25.1",
1344
-                "@esbuild/android-arm": "0.25.1",
1345
-                "@esbuild/android-arm64": "0.25.1",
1346
-                "@esbuild/android-x64": "0.25.1",
1347
-                "@esbuild/darwin-arm64": "0.25.1",
1348
-                "@esbuild/darwin-x64": "0.25.1",
1349
-                "@esbuild/freebsd-arm64": "0.25.1",
1350
-                "@esbuild/freebsd-x64": "0.25.1",
1351
-                "@esbuild/linux-arm": "0.25.1",
1352
-                "@esbuild/linux-arm64": "0.25.1",
1353
-                "@esbuild/linux-ia32": "0.25.1",
1354
-                "@esbuild/linux-loong64": "0.25.1",
1355
-                "@esbuild/linux-mips64el": "0.25.1",
1356
-                "@esbuild/linux-ppc64": "0.25.1",
1357
-                "@esbuild/linux-riscv64": "0.25.1",
1358
-                "@esbuild/linux-s390x": "0.25.1",
1359
-                "@esbuild/linux-x64": "0.25.1",
1360
-                "@esbuild/netbsd-arm64": "0.25.1",
1361
-                "@esbuild/netbsd-x64": "0.25.1",
1362
-                "@esbuild/openbsd-arm64": "0.25.1",
1363
-                "@esbuild/openbsd-x64": "0.25.1",
1364
-                "@esbuild/sunos-x64": "0.25.1",
1365
-                "@esbuild/win32-arm64": "0.25.1",
1366
-                "@esbuild/win32-ia32": "0.25.1",
1367
-                "@esbuild/win32-x64": "0.25.1"
1357
+                "@esbuild/aix-ppc64": "0.25.4",
1358
+                "@esbuild/android-arm": "0.25.4",
1359
+                "@esbuild/android-arm64": "0.25.4",
1360
+                "@esbuild/android-x64": "0.25.4",
1361
+                "@esbuild/darwin-arm64": "0.25.4",
1362
+                "@esbuild/darwin-x64": "0.25.4",
1363
+                "@esbuild/freebsd-arm64": "0.25.4",
1364
+                "@esbuild/freebsd-x64": "0.25.4",
1365
+                "@esbuild/linux-arm": "0.25.4",
1366
+                "@esbuild/linux-arm64": "0.25.4",
1367
+                "@esbuild/linux-ia32": "0.25.4",
1368
+                "@esbuild/linux-loong64": "0.25.4",
1369
+                "@esbuild/linux-mips64el": "0.25.4",
1370
+                "@esbuild/linux-ppc64": "0.25.4",
1371
+                "@esbuild/linux-riscv64": "0.25.4",
1372
+                "@esbuild/linux-s390x": "0.25.4",
1373
+                "@esbuild/linux-x64": "0.25.4",
1374
+                "@esbuild/netbsd-arm64": "0.25.4",
1375
+                "@esbuild/netbsd-x64": "0.25.4",
1376
+                "@esbuild/openbsd-arm64": "0.25.4",
1377
+                "@esbuild/openbsd-x64": "0.25.4",
1378
+                "@esbuild/sunos-x64": "0.25.4",
1379
+                "@esbuild/win32-arm64": "0.25.4",
1380
+                "@esbuild/win32-ia32": "0.25.4",
1381
+                "@esbuild/win32-x64": "0.25.4"
1368 1382
             }
1369 1383
         },
1370 1384
         "node_modules/escalade": {
@@ -2067,9 +2081,9 @@
2067 2081
             }
2068 2082
         },
2069 2083
         "node_modules/pirates": {
2070
-            "version": "4.0.6",
2071
-            "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
2072
-            "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
2084
+            "version": "4.0.7",
2085
+            "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
2086
+            "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
2073 2087
             "dev": true,
2074 2088
             "license": "MIT",
2075 2089
             "engines": {
@@ -2412,13 +2426,13 @@
2412 2426
             }
2413 2427
         },
2414 2428
         "node_modules/rollup": {
2415
-            "version": "4.36.0",
2416
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz",
2417
-            "integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==",
2429
+            "version": "4.40.2",
2430
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz",
2431
+            "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==",
2418 2432
             "dev": true,
2419 2433
             "license": "MIT",
2420 2434
             "dependencies": {
2421
-                "@types/estree": "1.0.6"
2435
+                "@types/estree": "1.0.7"
2422 2436
             },
2423 2437
             "bin": {
2424 2438
                 "rollup": "dist/bin/rollup"
@@ -2428,25 +2442,26 @@
2428 2442
                 "npm": ">=8.0.0"
2429 2443
             },
2430 2444
             "optionalDependencies": {
2431
-                "@rollup/rollup-android-arm-eabi": "4.36.0",
2432
-                "@rollup/rollup-android-arm64": "4.36.0",
2433
-                "@rollup/rollup-darwin-arm64": "4.36.0",
2434
-                "@rollup/rollup-darwin-x64": "4.36.0",
2435
-                "@rollup/rollup-freebsd-arm64": "4.36.0",
2436
-                "@rollup/rollup-freebsd-x64": "4.36.0",
2437
-                "@rollup/rollup-linux-arm-gnueabihf": "4.36.0",
2438
-                "@rollup/rollup-linux-arm-musleabihf": "4.36.0",
2439
-                "@rollup/rollup-linux-arm64-gnu": "4.36.0",
2440
-                "@rollup/rollup-linux-arm64-musl": "4.36.0",
2441
-                "@rollup/rollup-linux-loongarch64-gnu": "4.36.0",
2442
-                "@rollup/rollup-linux-powerpc64le-gnu": "4.36.0",
2443
-                "@rollup/rollup-linux-riscv64-gnu": "4.36.0",
2444
-                "@rollup/rollup-linux-s390x-gnu": "4.36.0",
2445
-                "@rollup/rollup-linux-x64-gnu": "4.36.0",
2446
-                "@rollup/rollup-linux-x64-musl": "4.36.0",
2447
-                "@rollup/rollup-win32-arm64-msvc": "4.36.0",
2448
-                "@rollup/rollup-win32-ia32-msvc": "4.36.0",
2449
-                "@rollup/rollup-win32-x64-msvc": "4.36.0",
2445
+                "@rollup/rollup-android-arm-eabi": "4.40.2",
2446
+                "@rollup/rollup-android-arm64": "4.40.2",
2447
+                "@rollup/rollup-darwin-arm64": "4.40.2",
2448
+                "@rollup/rollup-darwin-x64": "4.40.2",
2449
+                "@rollup/rollup-freebsd-arm64": "4.40.2",
2450
+                "@rollup/rollup-freebsd-x64": "4.40.2",
2451
+                "@rollup/rollup-linux-arm-gnueabihf": "4.40.2",
2452
+                "@rollup/rollup-linux-arm-musleabihf": "4.40.2",
2453
+                "@rollup/rollup-linux-arm64-gnu": "4.40.2",
2454
+                "@rollup/rollup-linux-arm64-musl": "4.40.2",
2455
+                "@rollup/rollup-linux-loongarch64-gnu": "4.40.2",
2456
+                "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2",
2457
+                "@rollup/rollup-linux-riscv64-gnu": "4.40.2",
2458
+                "@rollup/rollup-linux-riscv64-musl": "4.40.2",
2459
+                "@rollup/rollup-linux-s390x-gnu": "4.40.2",
2460
+                "@rollup/rollup-linux-x64-gnu": "4.40.2",
2461
+                "@rollup/rollup-linux-x64-musl": "4.40.2",
2462
+                "@rollup/rollup-win32-arm64-msvc": "4.40.2",
2463
+                "@rollup/rollup-win32-ia32-msvc": "4.40.2",
2464
+                "@rollup/rollup-win32-x64-msvc": "4.40.2",
2450 2465
                 "fsevents": "~2.3.2"
2451 2466
             }
2452 2467
         },
@@ -2735,6 +2750,51 @@
2735 2750
                 "node": ">=0.8"
2736 2751
             }
2737 2752
         },
2753
+        "node_modules/tinyglobby": {
2754
+            "version": "0.2.13",
2755
+            "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
2756
+            "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
2757
+            "dev": true,
2758
+            "license": "MIT",
2759
+            "dependencies": {
2760
+                "fdir": "^6.4.4",
2761
+                "picomatch": "^4.0.2"
2762
+            },
2763
+            "engines": {
2764
+                "node": ">=12.0.0"
2765
+            },
2766
+            "funding": {
2767
+                "url": "https://github.com/sponsors/SuperchupuDev"
2768
+            }
2769
+        },
2770
+        "node_modules/tinyglobby/node_modules/fdir": {
2771
+            "version": "6.4.4",
2772
+            "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
2773
+            "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
2774
+            "dev": true,
2775
+            "license": "MIT",
2776
+            "peerDependencies": {
2777
+                "picomatch": "^3 || ^4"
2778
+            },
2779
+            "peerDependenciesMeta": {
2780
+                "picomatch": {
2781
+                    "optional": true
2782
+                }
2783
+            }
2784
+        },
2785
+        "node_modules/tinyglobby/node_modules/picomatch": {
2786
+            "version": "4.0.2",
2787
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
2788
+            "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
2789
+            "dev": true,
2790
+            "license": "MIT",
2791
+            "engines": {
2792
+                "node": ">=12"
2793
+            },
2794
+            "funding": {
2795
+                "url": "https://github.com/sponsors/jonschlinkert"
2796
+            }
2797
+        },
2738 2798
         "node_modules/to-regex-range": {
2739 2799
             "version": "5.0.1",
2740 2800
             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -2794,15 +2854,18 @@
2794 2854
             "license": "MIT"
2795 2855
         },
2796 2856
         "node_modules/vite": {
2797
-            "version": "6.2.2",
2798
-            "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.2.tgz",
2799
-            "integrity": "sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==",
2857
+            "version": "6.3.5",
2858
+            "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
2859
+            "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
2800 2860
             "dev": true,
2801 2861
             "license": "MIT",
2802 2862
             "dependencies": {
2803 2863
                 "esbuild": "^0.25.0",
2864
+                "fdir": "^6.4.4",
2865
+                "picomatch": "^4.0.2",
2804 2866
                 "postcss": "^8.5.3",
2805
-                "rollup": "^4.30.1"
2867
+                "rollup": "^4.34.9",
2868
+                "tinyglobby": "^0.2.13"
2806 2869
             },
2807 2870
             "bin": {
2808 2871
                 "vite": "bin/vite.js"
@@ -2876,6 +2939,34 @@
2876 2939
                 "picomatch": "^2.3.1"
2877 2940
             }
2878 2941
         },
2942
+        "node_modules/vite/node_modules/fdir": {
2943
+            "version": "6.4.4",
2944
+            "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
2945
+            "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
2946
+            "dev": true,
2947
+            "license": "MIT",
2948
+            "peerDependencies": {
2949
+                "picomatch": "^3 || ^4"
2950
+            },
2951
+            "peerDependenciesMeta": {
2952
+                "picomatch": {
2953
+                    "optional": true
2954
+                }
2955
+            }
2956
+        },
2957
+        "node_modules/vite/node_modules/picomatch": {
2958
+            "version": "4.0.2",
2959
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
2960
+            "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
2961
+            "dev": true,
2962
+            "license": "MIT",
2963
+            "engines": {
2964
+                "node": ">=12"
2965
+            },
2966
+            "funding": {
2967
+                "url": "https://github.com/sponsors/jonschlinkert"
2968
+            }
2969
+        },
2879 2970
         "node_modules/which": {
2880 2971
             "version": "2.0.2",
2881 2972
             "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -2991,16 +3082,16 @@
2991 3082
             }
2992 3083
         },
2993 3084
         "node_modules/yaml": {
2994
-            "version": "2.7.0",
2995
-            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
2996
-            "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
3085
+            "version": "2.8.0",
3086
+            "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
3087
+            "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
2997 3088
             "dev": true,
2998 3089
             "license": "ISC",
2999 3090
             "bin": {
3000 3091
                 "yaml": "bin.mjs"
3001 3092
             },
3002 3093
             "engines": {
3003
-                "node": ">= 14"
3094
+                "node": ">= 14.6"
3004 3095
             }
3005 3096
         }
3006 3097
     }

Loading…
Peruuta
Tallenna