Sfoglia il codice sorgente

wip: Added many new functionalities

3.x
wallo 2 anni fa
parent
commit
130432a531
51 ha cambiato i file con 1974 aggiunte e 696 eliminazioni
  1. 80
    0
      app/Abstracts/Forms/EditFormRecord.php
  2. 51
    0
      app/Filament/Pages/CompanyDetails.php
  3. 33
    2
      app/Filament/Pages/DefaultSetting.php
  4. 33
    2
      app/Filament/Pages/Invoice.php
  5. 20
    6
      app/Filament/Pages/Widgets/Companies/Charts/CompanyStatsOverview.php
  6. 2
    2
      app/Filament/Pages/Widgets/Companies/Charts/CumulativeGrowth.php
  7. 1
    1
      app/Filament/Pages/Widgets/Employees/Charts/CumulativeGrowth.php
  8. 67
    1
      app/Filament/Resources/AccountResource.php
  9. 17
    2
      app/Filament/Resources/AccountResource/Pages/EditAccount.php
  10. 15
    2
      app/Filament/Resources/CategoryResource.php
  11. 4
    0
      app/Filament/Resources/CategoryResource/Pages/ListCategories.php
  12. 15
    2
      app/Filament/Resources/CurrencyResource.php
  13. 16
    2
      app/Filament/Resources/CustomerResource.php
  14. 15
    2
      app/Filament/Resources/DiscountResource.php
  15. 16
    2
      app/Filament/Resources/InvoiceResource.php
  16. 15
    2
      app/Filament/Resources/TaxResource.php
  17. 26
    0
      app/Forms/Components/Invoice.php
  18. 89
    0
      app/Http/Livewire/CompanyDetails.php
  19. 10
    13
      app/Http/Livewire/DefaultSetting.php
  20. 173
    88
      app/Http/Livewire/Invoice.php
  21. 0
    6
      app/Models/Banking/Account.php
  22. 10
    0
      app/Models/Company.php
  23. 0
    5
      app/Models/Contact.php
  24. 0
    6
      app/Models/Setting/Category.php
  25. 15
    5
      app/Models/Setting/Currency.php
  26. 0
    5
      app/Models/Setting/DefaultSetting.php
  27. 0
    5
      app/Models/Setting/Discount.php
  28. 72
    29
      app/Models/Setting/DocumentDefault.php
  29. 0
    5
      app/Models/Setting/Tax.php
  30. 2
    1
      app/Providers/SquireServiceProvider.php
  31. 14
    9
      app/Traits/CompanyOwned.php
  32. 9
    11
      app/Traits/HandlesDefaultSettingRecordUpdate.php
  33. 173
    0
      app/View/Models/InvoiceViewModel.php
  34. 1
    0
      composer.json
  35. 408
    280
      composer.lock
  36. 1
    1
      config/filament.php
  37. 1
    1
      config/services.php
  38. 9
    4
      database/factories/DocumentDefaultFactory.php
  39. 10
    5
      database/migrations/2023_05_22_000100_create_document_defaults_table.php
  40. 44
    0
      database/migrations/2023_08_07_144726_add_company_details_to_companies_table.php
  41. 161
    161
      package-lock.json
  42. 21
    0
      resources/css/filament.css
  43. 129
    0
      resources/views/components/invoice-layouts/default.blade.php
  44. 164
    0
      resources/views/components/invoice-layouts/modern.blade.php
  45. 3
    0
      resources/views/filament/pages/company-details.blade.php
  46. 1
    1
      resources/views/filament/pages/default-setting.blade.php
  47. 1
    1
      resources/views/filament/pages/invoice.blade.php
  48. 9
    0
      resources/views/livewire/company-details.blade.php
  49. 8
    12
      resources/views/livewire/default-setting.blade.php
  50. 8
    12
      resources/views/livewire/invoice.blade.php
  51. 2
    2
      tailwind.config.js

+ 80
- 0
app/Abstracts/Forms/EditFormRecord.php Vedi File

@@ -0,0 +1,80 @@
1
+<?php
2
+
3
+namespace App\Abstracts\Forms;
4
+
5
+use Filament\Forms\ComponentContainer;
6
+use Filament\Forms\Concerns\InteractsWithForms;
7
+use Filament\Forms\Contracts\HasForms;
8
+use Filament\Notifications\Notification;
9
+use Illuminate\Database\Eloquent\Model;
10
+use Livewire\Component;
11
+
12
+/**
13
+ * @property ComponentContainer $form
14
+ */
15
+abstract class EditFormRecord extends Component implements HasForms
16
+{
17
+    use InteractsWithForms;
18
+
19
+    public ?array $data = [];
20
+
21
+    abstract protected function getFormModel(): Model|string|null;
22
+
23
+    public function mount(): void
24
+    {
25
+        $this->fillForm();
26
+    }
27
+
28
+    public function fillForm(): void
29
+    {
30
+        $data = $this->getFormModel()->attributesToArray();
31
+
32
+        $data = $this->mutateFormDataBeforeFill($data);
33
+
34
+        $this->form->fill($data);
35
+    }
36
+
37
+    protected function mutateFormDataBeforeFill(array $data): array
38
+    {
39
+        return $data;
40
+    }
41
+
42
+    public function save(): void
43
+    {
44
+        $data = $this->form->getState();
45
+
46
+        $data = $this->mutateFormDataBeforeSave($data);
47
+
48
+        $this->handleRecordUpdate($this->getFormModel(), $data);
49
+
50
+        $this->getSavedNotification()?->send();
51
+    }
52
+
53
+    protected function mutateFormDataBeforeSave(array $data): array
54
+    {
55
+        return $data;
56
+    }
57
+
58
+    protected function handleRecordUpdate(Model $record, array $data): Model
59
+    {
60
+        $record->update($data);
61
+
62
+        return $record;
63
+    }
64
+
65
+    protected function getSavedNotification(): ?Notification
66
+    {
67
+        $title = $this->getSavedNotificationTitle();
68
+        if (blank($title)) {
69
+            return null;
70
+        }
71
+        return Notification::make()
72
+            ->success()
73
+            ->title($title);
74
+    }
75
+
76
+    protected function getSavedNotificationTitle(): ?string
77
+    {
78
+        return __('filament::resources/pages/edit-record.messages.saved');
79
+    }
80
+}

+ 51
- 0
app/Filament/Pages/CompanyDetails.php Vedi File

@@ -0,0 +1,51 @@
1
+<?php
2
+
3
+namespace App\Filament\Pages;
4
+
5
+use Filament\Pages\Page;
6
+use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Gate;
8
+use Wallo\FilamentCompanies\FilamentCompanies;
9
+
10
+class CompanyDetails extends Page
11
+{
12
+    public mixed $company;
13
+
14
+    protected static ?string $navigationIcon = 'heroicon-o-document-text';
15
+
16
+    protected static ?string $navigationLabel = 'Company';
17
+
18
+    protected static ?string $navigationGroup = 'Settings';
19
+
20
+    protected static ?string $title = 'Company';
21
+
22
+    protected static string $view = 'filament.pages.company-details';
23
+
24
+    public function mount($company): void
25
+    {
26
+        $this->company = FilamentCompanies::newCompanyModel()->findOrFail($company);
27
+        $this->authorizeAccess();
28
+    }
29
+
30
+    protected function authorizeAccess(): void
31
+    {
32
+        Gate::authorize('view', $this->company);
33
+    }
34
+
35
+    public static function getSlug(): string
36
+    {
37
+        return '{company}/settings/company';
38
+    }
39
+
40
+    public static function getUrl(array $parameters = [], bool $isAbsolute = true): string
41
+    {
42
+        return route(static::getRouteName(), ['company' => Auth::user()->currentCompany], $isAbsolute);
43
+    }
44
+
45
+    protected function getBreadcrumbs(): array
46
+    {
47
+        return [
48
+            'company' => 'Company',
49
+        ];
50
+    }
51
+}

+ 33
- 2
app/Filament/Pages/DefaultSetting.php Vedi File

@@ -3,9 +3,14 @@
3 3
 namespace App\Filament\Pages;
4 4
 
5 5
 use Filament\Pages\Page;
6
+use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Gate;
8
+use Wallo\FilamentCompanies\FilamentCompanies;
6 9
 
7 10
 class DefaultSetting extends Page
8 11
 {
12
+    public mixed $company;
13
+
9 14
     protected static ?string $navigationIcon = 'heroicon-o-adjustments';
10 15
 
11 16
     protected static ?string $navigationLabel = 'Default';
@@ -14,7 +19,33 @@ class DefaultSetting extends Page
14 19
 
15 20
     protected static ?string $title = 'Default';
16 21
 
17
-    protected static ?string $slug = 'default';
18
-
19 22
     protected static string $view = 'filament.pages.default-setting';
23
+
24
+    public function mount($company): void
25
+    {
26
+        $this->company = FilamentCompanies::newCompanyModel()->findOrFail($company);
27
+        $this->authorizeAccess();
28
+    }
29
+
30
+    protected function authorizeAccess(): void
31
+    {
32
+        Gate::authorize('view', $this->company);
33
+    }
34
+
35
+    public static function getSlug(): string
36
+    {
37
+        return '{company}/settings/default';
38
+    }
39
+
40
+    public static function getUrl(array $parameters = [], bool $isAbsolute = true): string
41
+    {
42
+        return route(static::getRouteName(), ['company' => Auth::user()->currentCompany], $isAbsolute);
43
+    }
44
+
45
+    protected function getBreadcrumbs(): array
46
+    {
47
+        return [
48
+            'default' => 'Default',
49
+        ];
50
+    }
20 51
 }

+ 33
- 2
app/Filament/Pages/Invoice.php Vedi File

@@ -3,9 +3,14 @@
3 3
 namespace App\Filament\Pages;
4 4
 
5 5
 use Filament\Pages\Page;
6
+use Illuminate\Support\Facades\Auth;
7
+use Illuminate\Support\Facades\Gate;
8
+use Wallo\FilamentCompanies\FilamentCompanies;
6 9
 
7 10
 class Invoice extends Page
8 11
 {
12
+    public mixed $company;
13
+
9 14
     protected static ?string $navigationIcon = 'heroicon-o-document-text';
10 15
 
11 16
     protected static ?string $navigationLabel = 'Invoice';
@@ -14,7 +19,33 @@ class Invoice extends Page
14 19
 
15 20
     protected static ?string $title = 'Invoice';
16 21
 
17
-    protected static ?string $slug = 'invoice';
18
-
19 22
     protected static string $view = 'filament.pages.invoice';
23
+
24
+    public function mount($company): void
25
+    {
26
+        $this->company = FilamentCompanies::newCompanyModel()->findOrFail($company);
27
+        $this->authorizeAccess();
28
+    }
29
+
30
+    protected function authorizeAccess(): void
31
+    {
32
+        Gate::authorize('view', $this->company);
33
+    }
34
+
35
+    public static function getSlug(): string
36
+    {
37
+        return '{company}/settings/invoice';
38
+    }
39
+
40
+    public static function getUrl(array $parameters = [], bool $isAbsolute = true): string
41
+    {
42
+        return route(static::getRouteName(), ['company' => Auth::user()->currentCompany], $isAbsolute);
43
+    }
44
+
45
+    protected function getBreadcrumbs(): array
46
+    {
47
+        return [
48
+            'invoice' => 'Invoice',
49
+        ];
50
+    }
20 51
 }

+ 20
- 6
app/Filament/Pages/Widgets/Companies/Charts/CompanyStatsOverview.php Vedi File

@@ -3,7 +3,9 @@
3 3
 namespace App\Filament\Pages\Widgets\Companies\Charts;
4 4
 
5 5
 use App\Models\Company;
6
+use Exception;
6 7
 use Filament\Widgets\StatsOverviewWidget;
8
+use InvalidArgumentException;
7 9
 
8 10
 class CompanyStatsOverview extends StatsOverviewWidget
9 11
 {
@@ -11,9 +13,14 @@ class CompanyStatsOverview extends StatsOverviewWidget
11 13
 
12 14
     /**
13 15
      * Holt's Linear Trend Method
16
+     * @throws Exception
14 17
      */
15 18
     protected function holtLinearTrend($data, $alpha, $beta): array
16 19
     {
20
+        if (count($data) < 2 || array_filter($data, 'is_numeric') !== $data) {
21
+            throw new InvalidArgumentException('Insufficient or invalid data for Holt\'s Linear Trend calculation', 400);
22
+        }
23
+
17 24
         $level = $data[0];
18 25
         $trend = $data[1] - $data[0];
19 26
 
@@ -30,6 +37,7 @@ class CompanyStatsOverview extends StatsOverviewWidget
30 37
 
31 38
     /**
32 39
      * Adjusts the alpha and beta parameters based on the model's performance
40
+     * @throws Exception
33 41
      */
34 42
     protected function adjustTrendParameters($data, $alpha, $beta): array
35 43
     {
@@ -68,6 +76,7 @@ class CompanyStatsOverview extends StatsOverviewWidget
68 76
 
69 77
     /**
70 78
      * Chart Options
79
+     * @throws Exception
71 80
      */
72 81
     protected function getCards(): array
73 82
     {
@@ -106,16 +115,17 @@ class CompanyStatsOverview extends StatsOverviewWidget
106 115
         // Calculate new companies and percentage change per week
107 116
         $newCompanies = [0];
108 117
         $weeklyPercentageChange = [0];
118
+
109 119
         for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
110 120
             $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
111
-            $weeklyPercentageChange[] = ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100;
121
+            $weeklyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
112 122
         }
113 123
 
114
-        // Calculate average weekly growth rate
115
-        $totalWeeks = $startOfYear->diffInWeeks($today);
116
-        $averageWeeklyGrowthRate = round(array_sum($weeklyPercentageChange) / $totalWeeks, 2);
117
-
124
+        // Ensure $weeklyDataArray contains at least two values and all values are numeric
118 125
         $weeklyDataArray = $weeklyData->values()->toArray();
126
+        if (count($weeklyDataArray) < 2 || array_filter($weeklyDataArray, 'is_numeric') !== $weeklyDataArray) {
127
+            throw new InvalidArgumentException('Insufficient or invalid data for Holt\'s Linear Trend calculation', 400);
128
+        }
119 129
 
120 130
         // Adjust alpha and beta parameters
121 131
         [$alpha, $beta] = $this->adjustTrendParameters($weeklyDataArray, $alpha, $beta);
@@ -124,6 +134,10 @@ class CompanyStatsOverview extends StatsOverviewWidget
124 134
         $holtForecast = $this->holtLinearTrend($weeklyDataArray, $alpha, $beta);
125 135
         $expectedNewCompanies = round(end($holtForecast));
126 136
 
137
+        // Calculate average weekly growth rate
138
+        $totalWeeks = $startOfYear->diffInWeeks($today);
139
+        $averageWeeklyGrowthRate = round(array_sum($weeklyPercentageChange) / $totalWeeks, 2);
140
+
127 141
         // Company Stats Overview Cards
128 142
         return [
129 143
             StatsOverviewWidget\Card::make("New Companies Forecast (Holt's Linear Trend)", $expectedNewCompanies),
@@ -131,4 +145,4 @@ class CompanyStatsOverview extends StatsOverviewWidget
131 145
             StatsOverviewWidget\Card::make('Personal Companies', Company::sum('personal_company')),
132 146
         ];
133 147
     }
134
-}
148
+}

+ 2
- 2
app/Filament/Pages/Widgets/Companies/Charts/CumulativeGrowth.php Vedi File

@@ -52,7 +52,7 @@ class CumulativeGrowth extends ApexChartWidget
52 52
 
53 53
         for ($i = 1, $iMax = count($totalCompanies); $i < $iMax; $i++) {
54 54
             $newCompanies[] = $totalCompanies[$i] - $totalCompanies[$i - 1];
55
-            $monthlyPercentageChange[] = ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100;
55
+            $monthlyPercentageChange[] = $totalCompanies[$i - 1] !== 0 ? ($newCompanies[$i] / $totalCompanies[$i - 1]) * 100 : 0;
56 56
         }
57 57
 
58 58
         $labels = collect($months)->keys()->map(static function ($month) {
@@ -151,4 +151,4 @@ class CumulativeGrowth extends ApexChartWidget
151 151
             ],
152 152
         ];
153 153
     }
154
-}
154
+}

+ 1
- 1
app/Filament/Pages/Widgets/Employees/Charts/CumulativeGrowth.php Vedi File

@@ -50,7 +50,7 @@ class CumulativeGrowth extends ApexChartWidget
50 50
 
51 51
         for ($i = 1, $iMax = count($totalEmployees); $i < $iMax; $i++) {
52 52
             $newEmployees[] = $totalEmployees[$i] - $totalEmployees[$i - 1];
53
-            $monthlyPercentageChange[] = ($newEmployees[$i] / $totalEmployees[$i - 1]) * 100;
53
+            $monthlyPercentageChange[] = $totalEmployees[$i - 1] !== 0 ? ($newEmployees[$i] / $totalEmployees[$i - 1]) * 100 : 0;
54 54
         }
55 55
 
56 56
         $labels = collect($months)->keys()->map(static function ($month) {

+ 67
- 1
app/Filament/Resources/AccountResource.php Vedi File

@@ -4,7 +4,6 @@ namespace App\Filament\Resources;
4 4
 
5 5
 use App\Actions\OptionAction\CreateCurrency;
6 6
 use App\Filament\Resources\AccountResource\Pages;
7
-use App\Filament\Resources\AccountResource\RelationManagers;
8 7
 use App\Models\Banking\Account;
9 8
 use App\Models\Setting\Currency;
10 9
 use App\Services\CurrencyService;
@@ -77,6 +76,7 @@ class AccountResource extends Resource
77 76
                                     ->searchable()
78 77
                                     ->reactive()
79 78
                                     ->required()
79
+                                    ->saveRelationshipsUsing(null)
80 80
                                     ->createOptionForm([
81 81
                                         Forms\Components\Select::make('currency.code')
82 82
                                             ->label('Code')
@@ -258,8 +258,59 @@ class AccountResource extends Resource
258 258
                 //
259 259
             ])
260 260
             ->actions([
261
+                Tables\Actions\Action::make('update_balance')
262
+                    ->hidden(static fn (Account $record) => $record->currency_code === Currency::getDefaultCurrency())
263
+                    ->label('Update Balance')
264
+                    ->icon('heroicon-o-currency-dollar')
265
+                    ->requiresConfirmation()
266
+                    ->modalSubheading('Are you sure you want to update the balance with the latest exchange rate?')
267
+                    ->before(static function (Tables\Actions\Action $action, Account $record) {
268
+                        if ($record->currency_code !== Currency::getDefaultCurrency()) {
269
+                            $currencyService = app(CurrencyService::class);
270
+                            $defaultCurrency = Currency::getDefaultCurrency();
271
+                            $cachedExchangeRate = $currencyService->getCachedExchangeRate($defaultCurrency, $record->currency_code);
272
+                            $oldExchangeRate = $record->currency->rate;
273
+
274
+                            if ($cachedExchangeRate === $oldExchangeRate) {
275
+                                Notification::make()
276
+                                    ->warning()
277
+                                    ->title('Balance Already Up to Date')
278
+                                    ->body(__('The :name account balance is already up to date.', ['name' => $record->name]))
279
+                                    ->persistent()
280
+                                    ->send();
281
+
282
+                                $action->cancel();
283
+                            }
284
+                        }
285
+                    })
286
+                    ->action(static function (Account $record) {
287
+                        if ($record->currency_code !== Currency::getDefaultCurrency()) {
288
+                            $currencyService = app(CurrencyService::class);
289
+                            $defaultCurrency = Currency::getDefaultCurrency();
290
+                            $cachedExchangeRate = $currencyService->getCachedExchangeRate($defaultCurrency, $record->currency_code);
291
+                            $oldExchangeRate = $record->currency->rate;
292
+
293
+                            $originalBalanceInOriginalCurrency = $record->opening_balance / $oldExchangeRate;
294
+                            $currencyPrecision = $record->currency->precision;
295
+
296
+                            if ($cachedExchangeRate !== $oldExchangeRate) {
297
+                                $record->opening_balance = round($originalBalanceInOriginalCurrency * $cachedExchangeRate, $currencyPrecision);
298
+                                $record->currency->rate = $cachedExchangeRate;
299
+                                $record->currency->save();
300
+                                $record->save();
301
+                            }
302
+
303
+                            Notification::make()
304
+                                ->success()
305
+                                ->title('Balance Updated Successfully')
306
+                                ->body(__('The :name account balance has been updated to reflect the current exchange rate.', ['name' => $record->name]))
307
+                                ->send();
308
+                        }
309
+                    }),
261 310
                 Tables\Actions\EditAction::make(),
262 311
                 Tables\Actions\DeleteAction::make()
312
+                    ->modalHeading('Delete Account')
313
+                    ->requiresConfirmation()
263 314
                     ->before(static function (Tables\Actions\DeleteAction $action, Account $record) {
264 315
                         if ($record->enabled) {
265 316
                             Notification::make()
@@ -292,6 +343,21 @@ class AccountResource extends Resource
292 343
             ]);
293 344
     }
294 345
 
346
+    public static function getSlug(): string
347
+    {
348
+        return '{company}/banking/accounts';
349
+    }
350
+
351
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
352
+    {
353
+        $routeBaseName = static::getRouteBaseName();
354
+
355
+        return route("{$routeBaseName}.{$name}", [
356
+            'company' => Auth::user()->currentCompany,
357
+            'record' => $params['record'] ?? null,
358
+        ], $isAbsolute);
359
+    }
360
+
295 361
     public static function getRelations(): array
296 362
     {
297 363
         return [

+ 17
- 2
app/Filament/Resources/AccountResource/Pages/EditAccount.php Vedi File

@@ -3,6 +3,8 @@
3 3
 namespace App\Filament\Resources\AccountResource\Pages;
4 4
 
5 5
 use App\Filament\Resources\AccountResource;
6
+use App\Models\Banking\Account;
7
+use App\Models\Setting\Currency;
6 8
 use App\Traits\HandlesResourceRecordUpdate;
7 9
 use Filament\Pages\Actions;
8 10
 use Filament\Resources\Pages\EditRecord;
@@ -38,7 +40,7 @@ class EditAccount extends EditRecord
38 40
     /**
39 41
      * @throws Halt
40 42
      */
41
-    protected function handleRecordUpdate(Model $record, array $data): Model
43
+    protected function handleRecordUpdate(Model|Account $record, array $data): Model|Account
42 44
     {
43 45
         $user = Auth::user();
44 46
 
@@ -46,6 +48,19 @@ class EditAccount extends EditRecord
46 48
             throw new Halt('No authenticated user found.');
47 49
         }
48 50
 
49
-        return $this->handleRecordUpdateWithUniqueField($record, $data, $user);
51
+        $oldCurrency = $record->currency_code;
52
+        $newCurrency = $data['currency_code'];
53
+
54
+        if ($oldCurrency !== $newCurrency) {
55
+            $data['opening_balance'] = Currency::convertBalance(
56
+                $data['opening_balance'],
57
+                $oldCurrency,
58
+                $newCurrency
59
+            );
60
+        }
61
+
62
+        $this->handleRecordUpdateWithUniqueField($record, $data, $user);
63
+
64
+        return parent::handleRecordUpdate($record, $data);
50 65
     }
51 66
 }

+ 15
- 2
app/Filament/Resources/CategoryResource.php Vedi File

@@ -3,8 +3,6 @@
3 3
 namespace App\Filament\Resources;
4 4
 
5 5
 use App\Filament\Resources\CategoryResource\Pages;
6
-use App\Filament\Resources\CategoryResource\RelationManagers;
7
-use Illuminate\Database\Eloquent\Builder;
8 6
 use Illuminate\Support\Facades\Auth;
9 7
 use Wallo\FilamentSelectify\Components\ToggleButton;
10 8
 use App\Models\Setting\Category;
@@ -124,6 +122,21 @@ class CategoryResource extends Resource
124 122
             ]);
125 123
     }
126 124
 
125
+    public static function getSlug(): string
126
+    {
127
+        return '{company}/settings/categories';
128
+    }
129
+
130
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
131
+    {
132
+        $routeBaseName = static::getRouteBaseName();
133
+
134
+        return route("{$routeBaseName}.{$name}", [
135
+                'company' => Auth::user()->currentCompany,
136
+                'record' => $params['record'] ?? null,
137
+        ], $isAbsolute);
138
+    }
139
+
127 140
     public static function getRelations(): array
128 141
     {
129 142
         return [

+ 4
- 0
app/Filament/Resources/CategoryResource/Pages/ListCategories.php Vedi File

@@ -3,8 +3,12 @@
3 3
 namespace App\Filament\Resources\CategoryResource\Pages;
4 4
 
5 5
 use App\Filament\Resources\CategoryResource;
6
+use Closure;
6 7
 use Filament\Pages\Actions;
7 8
 use Filament\Resources\Pages\ListRecords;
9
+use Illuminate\Support\Facades\Auth;
10
+use Illuminate\Support\Facades\Gate;
11
+use Wallo\FilamentCompanies\FilamentCompanies;
8 12
 
9 13
 class ListCategories extends ListRecords
10 14
 {

+ 15
- 2
app/Filament/Resources/CurrencyResource.php Vedi File

@@ -3,7 +3,6 @@
3 3
 namespace App\Filament\Resources;
4 4
 
5 5
 use App\Filament\Resources\CurrencyResource\Pages;
6
-use App\Filament\Resources\CurrencyResource\RelationManagers;
7 6
 use App\Models\Banking\Account;
8 7
 use App\Models\Setting\Currency;
9 8
 use App\Services\CurrencyService;
@@ -15,7 +14,6 @@ use Filament\Resources\Form;
15 14
 use Filament\Resources\Resource;
16 15
 use Filament\Resources\Table;
17 16
 use Filament\Tables;
18
-use Illuminate\Database\Eloquent\Builder;
19 17
 use Illuminate\Support\Collection;
20 18
 use Illuminate\Support\Facades\Auth;
21 19
 use Wallo\FilamentSelectify\Components\ToggleButton;
@@ -220,6 +218,21 @@ class CurrencyResource extends Resource
220 218
             ]);
221 219
     }
222 220
 
221
+    public static function getSlug(): string
222
+    {
223
+        return '{company}/settings/currencies';
224
+    }
225
+
226
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
227
+    {
228
+        $routeBaseName = static::getRouteBaseName();
229
+
230
+        return route("{$routeBaseName}.{$name}", [
231
+            'company' => Auth::user()->currentCompany,
232
+            'record' => $params['record'] ?? null,
233
+        ], $isAbsolute);
234
+    }
235
+
223 236
     public static function getRelations(): array
224 237
     {
225 238
         return [

+ 16
- 2
app/Filament/Resources/CustomerResource.php Vedi File

@@ -4,9 +4,9 @@ namespace App\Filament\Resources;
4 4
 
5 5
 use App\Actions\OptionAction\CreateCurrency;
6 6
 use App\Filament\Resources\CustomerResource\Pages;
7
-use App\Filament\Resources\CustomerResource\RelationManagers;
8 7
 use App\Models\Setting\Currency;
9 8
 use App\Services\CurrencyService;
9
+use Illuminate\Support\Facades\Auth;
10 10
 use Wallo\FilamentSelectify\Components\ButtonGroup;
11 11
 use App\Models\Contact;
12 12
 use Filament\Forms;
@@ -15,7 +15,6 @@ use Filament\Resources\Resource;
15 15
 use Filament\Resources\Table;
16 16
 use Filament\Tables;
17 17
 use Illuminate\Database\Eloquent\Builder;
18
-use Illuminate\Support\Facades\Auth;
19 18
 use Illuminate\Support\Facades\DB;
20 19
 
21 20
 class CustomerResource extends Resource
@@ -221,6 +220,21 @@ class CustomerResource extends Resource
221 220
             ]);
222 221
     }
223 222
 
223
+    public static function getSlug(): string
224
+    {
225
+        return '{company}/sales/customers';
226
+    }
227
+
228
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
229
+    {
230
+        $routeBaseName = static::getRouteBaseName();
231
+
232
+        return route("{$routeBaseName}.{$name}", [
233
+            'company' => Auth::user()->currentCompany,
234
+            'record' => $params['record'] ?? null,
235
+        ], $isAbsolute);
236
+    }
237
+
224 238
     public static function getRelations(): array
225 239
     {
226 240
         return [

+ 15
- 2
app/Filament/Resources/DiscountResource.php Vedi File

@@ -3,7 +3,6 @@
3 3
 namespace App\Filament\Resources;
4 4
 
5 5
 use App\Filament\Resources\DiscountResource\Pages;
6
-use App\Filament\Resources\DiscountResource\RelationManagers;
7 6
 use App\Models\Setting\Discount;
8 7
 use Filament\Forms;
9 8
 use Filament\Forms\Components\TextInput\Mask;
@@ -11,7 +10,6 @@ use Filament\Resources\Form;
11 10
 use Filament\Resources\Resource;
12 11
 use Filament\Resources\Table;
13 12
 use Filament\Tables;
14
-use Illuminate\Database\Eloquent\Builder;
15 13
 use Illuminate\Support\Facades\Auth;
16 14
 use Wallo\FilamentSelectify\Components\ToggleButton;
17 15
 
@@ -171,6 +169,21 @@ class DiscountResource extends Resource
171 169
             ]);
172 170
     }
173 171
 
172
+    public static function getSlug(): string
173
+    {
174
+        return '{company}/settings/discounts';
175
+    }
176
+
177
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
178
+    {
179
+        $routeBaseName = static::getRouteBaseName();
180
+
181
+        return route("{$routeBaseName}.{$name}", [
182
+            'company' => Auth::user()->currentCompany,
183
+            'record' => $params['record'] ?? null,
184
+        ], $isAbsolute);
185
+    }
186
+
174 187
     public static function getRelations(): array
175 188
     {
176 189
         return [

+ 16
- 2
app/Filament/Resources/InvoiceResource.php Vedi File

@@ -5,6 +5,7 @@ namespace App\Filament\Resources;
5 5
 use App\Filament\Resources\InvoiceResource\Pages;
6 6
 use App\Filament\Resources\InvoiceResource\RelationManagers;
7 7
 use App\Models\Setting\Currency;
8
+use Illuminate\Support\Facades\Auth;
8 9
 use Wallo\FilamentSelectify\Components\ButtonGroup;
9 10
 use App\Models\Document\Document;
10 11
 use Filament\Forms;
@@ -13,8 +14,6 @@ use Filament\Resources\Resource;
13 14
 use Filament\Resources\Table;
14 15
 use Filament\Tables;
15 16
 use Illuminate\Database\Eloquent\Builder;
16
-use Illuminate\Database\Eloquent\SoftDeletingScope;
17
-use Illuminate\Support\Facades\Auth;
18 17
 
19 18
 class InvoiceResource extends Resource
20 19
 {
@@ -120,6 +119,21 @@ class InvoiceResource extends Resource
120 119
             ]);
121 120
     }
122 121
 
122
+    public static function getSlug(): string
123
+    {
124
+        return '{company}/sales/invoices';
125
+    }
126
+
127
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
128
+    {
129
+        $routeBaseName = static::getRouteBaseName();
130
+
131
+        return route("{$routeBaseName}.{$name}", [
132
+            'company' => Auth::user()->currentCompany,
133
+            'record' => $params['record'] ?? null,
134
+        ], $isAbsolute);
135
+    }
136
+
123 137
     public static function getRelations(): array
124 138
     {
125 139
         return [

+ 15
- 2
app/Filament/Resources/TaxResource.php Vedi File

@@ -3,7 +3,6 @@
3 3
 namespace App\Filament\Resources;
4 4
 
5 5
 use App\Filament\Resources\TaxResource\Pages;
6
-use App\Filament\Resources\TaxResource\RelationManagers;
7 6
 use App\Models\Setting\Tax;
8 7
 use Exception;
9 8
 use Filament\Forms;
@@ -13,7 +12,6 @@ use Filament\Resources\Form;
13 12
 use Filament\Resources\Resource;
14 13
 use Filament\Resources\Table;
15 14
 use Filament\Tables;
16
-use Illuminate\Database\Eloquent\Builder;
17 15
 use Illuminate\Support\Collection;
18 16
 use Illuminate\Support\Facades\Auth;
19 17
 use Wallo\FilamentSelectify\Components\ToggleButton;
@@ -177,6 +175,21 @@ class TaxResource extends Resource
177 175
             ]);
178 176
     }
179 177
 
178
+    public static function getSlug(): string
179
+    {
180
+        return '{company}/settings/taxes';
181
+    }
182
+
183
+    public static function getUrl($name = 'index', $params = [], $isAbsolute = true): string
184
+    {
185
+        $routeBaseName = static::getRouteBaseName();
186
+
187
+        return route("{$routeBaseName}.{$name}", [
188
+            'company' => Auth::user()->currentCompany,
189
+            'record' => $params['record'] ?? null,
190
+        ], $isAbsolute);
191
+    }
192
+
180 193
     public static function getRelations(): array
181 194
     {
182 195
         return [

+ 26
- 0
app/Forms/Components/Invoice.php Vedi File

@@ -0,0 +1,26 @@
1
+<?php
2
+
3
+namespace App\Forms\Components;
4
+
5
+use Filament\Forms\Components\Field;
6
+
7
+class Invoice extends Field
8
+{
9
+    protected string $view = 'forms.components.invoice';
10
+
11
+    protected ?string $companyName = null;
12
+
13
+    protected ?string $companyAddress = null;
14
+
15
+    protected ?string $companyCity = null;
16
+
17
+    protected ?string $companyState = null;
18
+
19
+    protected ?string $companyZip = null;
20
+
21
+    protected ?string $companyCountry = null;
22
+
23
+    protected ?string $documentNumberPrefix = null;
24
+
25
+    protected ?string $documentNumberDigits = null;
26
+}

+ 89
- 0
app/Http/Livewire/CompanyDetails.php Vedi File

@@ -0,0 +1,89 @@
1
+<?php
2
+
3
+namespace App\Http\Livewire;
4
+
5
+use App\Abstracts\Forms\EditFormRecord;
6
+use App\Models\Company;
7
+use Filament\Forms\ComponentContainer;
8
+use Filament\Forms\Components\FileUpload;
9
+use Filament\Forms\Components\Group;
10
+use Filament\Forms\Components\Section;
11
+use Filament\Forms\Components\TextInput;
12
+use Illuminate\Contracts\View\View;
13
+use Illuminate\Database\Eloquent\Model;
14
+
15
+/**
16
+ * @property ComponentContainer $form
17
+ */
18
+class CompanyDetails extends EditFormRecord
19
+{
20
+    public Company $company;
21
+
22
+    protected function getFormModel(): Model|string|null
23
+    {
24
+        return $this->company;
25
+    }
26
+
27
+    protected function getFormSchema(): array
28
+    {
29
+        return [
30
+            Section::make('General')
31
+                ->schema([
32
+                    Group::make()
33
+                        ->schema([
34
+                            TextInput::make('email')
35
+                                ->label('Email')
36
+                                ->email()
37
+                                ->nullable(),
38
+                            TextInput::make('phone')
39
+                                ->label('Phone')
40
+                                ->tel()
41
+                                ->maxLength(20),
42
+                        ])->columns(1),
43
+                    Group::make()
44
+                        ->schema([
45
+                            FileUpload::make('logo')
46
+                                ->label('Logo')
47
+                                ->disk('public')
48
+                                ->directory('logos/company')
49
+                                ->imageResizeMode('cover')
50
+                                ->imagePreviewHeight('150')
51
+                                ->imageCropAspectRatio('2:1')
52
+                                ->panelAspectRatio('2:1')
53
+                                ->reactive()
54
+                                ->enableOpen()
55
+                                ->preserveFilenames()
56
+                                ->visibility('public')
57
+                                ->image(),
58
+                        ])->columns(1),
59
+                ])->columns(),
60
+            Section::make('Address')
61
+                ->schema([
62
+                    TextInput::make('address')
63
+                        ->label('Address')
64
+                        ->maxLength(100)
65
+                        ->columnSpanFull()
66
+                        ->nullable(),
67
+                    TextInput::make('country')
68
+                        ->label('Country')
69
+                        ->nullable(),
70
+                    TextInput::make('state')
71
+                        ->label('Province/State')
72
+                        ->nullable(),
73
+                    TextInput::make('city')
74
+                        ->label('Town/City')
75
+                        ->maxLength(100)
76
+                        ->nullable(),
77
+                    TextInput::make('zip_code')
78
+                        ->label('Postal/Zip Code')
79
+                        ->maxLength(100)
80
+                        ->nullable(),
81
+                ])->columns(),
82
+        ];
83
+    }
84
+
85
+    public function render(): View
86
+    {
87
+        return view('livewire.company-details');
88
+    }
89
+}

+ 10
- 13
app/Http/Livewire/DefaultSetting.php Vedi File

@@ -15,6 +15,7 @@ use Filament\Forms\Concerns\InteractsWithForms;
15 15
 use Filament\Forms\Contracts\HasForms;
16 16
 use Filament\Notifications\Notification;
17 17
 use Illuminate\Contracts\View\View;
18
+use Illuminate\Database\Eloquent\Model;
18 19
 use Livewire\Component;
19 20
 
20 21
 /**
@@ -24,15 +25,13 @@ class DefaultSetting extends Component implements HasForms
24 25
 {
25 26
     use InteractsWithForms, HandlesDefaultSettingRecordUpdate;
26 27
 
27
-    public Defaults $defaultSetting;
28
-
29 28
     public $data;
30 29
 
31
-    public $record;
30
+    public Defaults $record;
32 31
 
33 32
     public function mount():void
34 33
     {
35
-        $this->defaultSetting = Defaults::firstOrNew();
34
+        $this->record = Defaults::firstOrNew();
36 35
 
37 36
         $this->form->fill([
38 37
             'account_id' => Defaults::getDefaultAccount(),
@@ -113,13 +112,16 @@ class DefaultSetting extends Component implements HasForms
113 112
     {
114 113
         $data = $this->form->getState();
115 114
 
116
-        $this->record = $this->handleRecordUpdate($data);
117
-
118
-        $this->form->model($this->record)->saveRelationships();
115
+        $this->handleRecordUpdate($this->getFormModel(), $data);
119 116
 
120 117
         $this->getSavedNotification()?->send();
121 118
     }
122 119
 
120
+    protected function getFormModel(): Model
121
+    {
122
+        return $this->record;
123
+    }
124
+
123 125
     protected function getRelatedEntities(): array
124 126
     {
125 127
         return [
@@ -134,12 +136,7 @@ class DefaultSetting extends Component implements HasForms
134 136
         ];
135 137
     }
136 138
 
137
-    protected function getFormModel(): string
138
-    {
139
-        return Defaults::class;
140
-    }
141
-
142
-    protected function getSavedNotification():?Notification
139
+    protected function getSavedNotification(): ?Notification
143 140
     {
144 141
         $title = $this->getSavedNotificationTitle();
145 142
 

+ 173
- 88
app/Http/Livewire/Invoice.php Vedi File

@@ -2,48 +2,49 @@
2 2
 
3 3
 namespace App\Http\Livewire;
4 4
 
5
+use App\Abstracts\Forms\EditFormRecord;
5 6
 use App\Models\Setting\DocumentDefault;
6 7
 use Filament\Forms\ComponentContainer;
8
+use Filament\Forms\Components\ColorPicker;
9
+use Filament\Forms\Components\FileUpload;
10
+use Filament\Forms\Components\Group;
11
+use Filament\Forms\Components\Radio;
7 12
 use Filament\Forms\Components\Section;
8 13
 use Filament\Forms\Components\Select;
9 14
 use Filament\Forms\Components\Textarea;
10 15
 use Filament\Forms\Components\TextInput;
11
-use Filament\Forms\Concerns\InteractsWithForms;
12
-use Filament\Notifications\Notification;
16
+use Filament\Forms\Components\ViewField;
13 17
 use Illuminate\Contracts\View\View;
14
-use Illuminate\Support\Facades\Auth;
15
-use Livewire\Component;
16
-use Filament\Forms\Contracts\HasForms;
18
+use Illuminate\Database\Eloquent\Model;
17 19
 
18 20
 /**
19 21
  * @property ComponentContainer $form
20 22
  */
21
-class Invoice extends Component implements HasForms
23
+class Invoice extends EditFormRecord
22 24
 {
23
-    use InteractsWithForms;
24
-
25 25
     public DocumentDefault $invoice;
26 26
 
27
-    public $data;
27
+    protected function getFormModel(): Model|string|null
28
+    {
29
+        $this->invoice = DocumentDefault::where('type', 'invoice')->firstOrNew();
28 30
 
29
-    public $record;
31
+        return $this->invoice;
32
+    }
30 33
 
31 34
     public function mount(): void
32 35
     {
33
-        $this->invoice = DocumentDefault::where('type', 'invoice')->firstOrNew();
36
+        $this->fillForm();
37
+    }
34 38
 
35
-        $this->form->fill([
36
-            'document_number_prefix' => $this->invoice->document_number_prefix,
37
-            'document_number_digits' => $this->invoice->document_number_digits,
38
-            'document_number_next' => $this->invoice->document_number_next,
39
-            'payment_terms' => $this->invoice->payment_terms,
40
-            'template' => $this->invoice->template,
41
-            'title' => $this->invoice->title,
42
-            'subheading' => $this->invoice->subheading,
43
-            'notes' => $this->invoice->notes,
44
-            'footer' => $this->invoice->footer,
45
-            'terms' => $this->invoice->terms,
46
-        ]);
39
+    public function fillForm(): void
40
+    {
41
+        $data = $this->getFormModel()->attributesToArray();
42
+
43
+        unset($data['id']);
44
+
45
+        $data = $this->mutateFormDataBeforeFill($data);
46
+
47
+        $this->form->fill($data);
47 48
     }
48 49
 
49 50
     protected function getFormSchema(): array
@@ -51,110 +52,194 @@ class Invoice extends Component implements HasForms
51 52
         return [
52 53
             Section::make('General')
53 54
                 ->schema([
54
-                    TextInput::make('document_number_prefix')
55
+                    TextInput::make('number_prefix')
55 56
                         ->label('Number Prefix')
56 57
                         ->default('INV-')
58
+                        ->reactive()
57 59
                         ->required(),
58
-                    Select::make('document_number_digits')
60
+                    Select::make('number_digits')
59 61
                         ->label('Number Digits')
60
-                        ->options(DocumentDefault::getDocumentNumberDigits())
61
-                        ->default(DocumentDefault::getDefaultDocumentNumberDigits())
62
+                        ->options($this->invoice->getAvailableNumberDigits())
63
+                        ->default($this->invoice->getDefaultNumberDigits())
62 64
                         ->reactive()
63
-                        ->afterStateUpdated(static function (callable $set, $state) {
65
+                        ->afterStateUpdated(function (callable $set, $state) {
64 66
                             $numDigits = $state;
65
-                            $nextNumber = DocumentDefault::getDefaultDocumentNumberNext($numDigits);
67
+                            $nextNumber = $this->invoice->getNextDocumentNumber($numDigits);
66 68
 
67
-                            return $set('document_number_next', $nextNumber);
69
+                            return $set('number_next', $nextNumber);
68 70
                         })
69
-                        ->searchable()
70
-                        ->required(),
71
-                    TextInput::make('document_number_next')
71
+                        ->required()
72
+                        ->searchable(),
73
+                    TextInput::make('number_next')
72 74
                         ->label('Next Number')
73
-                        ->default(DocumentDefault::getDefaultDocumentNumberNext(DocumentDefault::getDefaultDocumentNumberDigits()))
74
-                        ->required(),
75
+                        ->reactive()
76
+                        ->required()
77
+                        ->default($this->invoice->getNextDocumentNumber($this->invoice->getDefaultNumberDigits())),
75 78
                     Select::make('payment_terms')
76 79
                         ->label('Payment Terms')
77
-                        ->options(DocumentDefault::getPaymentTerms())
78
-                        ->default(DocumentDefault::getDefaultPaymentTerms())
79
-                        ->searchable()
80
-                        ->required(),
81
-                ])->columns(),
82
-            Section::make('Template')
83
-                ->schema([
84
-                    Select::make('template')
85
-                        ->label('Template')
86
-                        ->options([
87
-                            'default' => 'Default',
88
-                            'simple' => 'Simple',
89
-                            'modern' => 'Modern',
90
-                        ])
91
-                        ->default('default')
80
+                        ->options($this->invoice->getPaymentTerms())
81
+                        ->default($this->invoice->getDefaultPaymentTerms())
92 82
                         ->searchable()
83
+                        ->reactive()
93 84
                         ->required(),
94 85
                 ])->columns(),
95 86
             Section::make('Content')
96 87
                 ->schema([
97 88
                     TextInput::make('title')
98 89
                         ->label('Title')
90
+                        ->reactive()
99 91
                         ->default('Invoice')
100 92
                         ->nullable(),
101 93
                     TextInput::make('subheading')
102 94
                         ->label('Subheading')
103
-                        ->nullable(),
104
-                    Textarea::make('notes')
105
-                        ->label('Notes')
95
+                        ->reactive()
106 96
                         ->nullable(),
107 97
                     Textarea::make('footer')
108 98
                         ->label('Footer')
99
+                        ->reactive()
109 100
                         ->nullable(),
110 101
                     Textarea::make('terms')
111
-                        ->label('Terms')
102
+                        ->label('Notes / Terms')
112 103
                         ->nullable()
113
-                        ->columnSpanFull(),
104
+                        ->reactive(),
114 105
                 ])->columns(),
106
+            Section::make('Template Settings')
107
+                ->description('Choose the template and edit the titles of the columns on your invoices.')
108
+                ->schema([
109
+                    Group::make()
110
+                        ->schema([
111
+                            FileUpload::make('document_logo')
112
+                                ->label('Logo')
113
+                                ->disk('public')
114
+                                ->directory('logos/documents')
115
+                                ->imageResizeMode('contain')
116
+                                ->imagePreviewHeight('250')
117
+                                ->imageCropAspectRatio('2:1')
118
+                                ->reactive()
119
+                                ->enableOpen()
120
+                                ->preserveFilenames()
121
+                                ->visibility('public')
122
+                                ->image(),
123
+                            ColorPicker::make('accent_color')
124
+                                ->label('Accent Color')
125
+                                ->reactive()
126
+                                ->default('#d9d9d9'),
127
+                            Select::make('template')
128
+                                ->label('Template')
129
+                                ->options([
130
+                                    'default' => 'Default',
131
+                                    'modern' => 'Modern',
132
+                                ])
133
+                                ->reactive()
134
+                                ->default('modern')
135
+                                ->required(),
136
+                            Radio::make('item_column')
137
+                                ->label('Items')
138
+                                ->options($this->invoice->getItemColumns())
139
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
140
+                                    return $state === 'other' ? $get('custom_item_column') : $state;
141
+                                })
142
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
143
+                                    if (isset($this->invoice->getItemColumns()[$state])) {
144
+                                        $component->state($state);
145
+                                    } else {
146
+                                        $component->state('other');
147
+                                        $set('custom_item_column', $state);
148
+                                    }
149
+                                })
150
+                                ->default($this->invoice->getDefaultItemColumn())
151
+                                ->reactive(),
152
+                            TextInput::make('custom_item_column')
153
+                                ->reactive()
154
+                                ->disableLabel()
155
+                                ->disabled(static fn (callable $get) => $get('item_column') !== 'other')
156
+                                ->nullable(),
157
+                            Radio::make('unit_column')
158
+                                ->label('Units')
159
+                                ->options(DocumentDefault::getUnitColumns())
160
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
161
+                                    return $state === 'other' ? $get('custom_unit_column') : $state;
162
+                                })
163
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
164
+                                    if (isset($this->invoice->getUnitColumns()[$state])) {
165
+                                        $component->state($state);
166
+                                    } else {
167
+                                        $component->state('other');
168
+                                        $set('custom_unit_column', $state);
169
+                                    }
170
+                                })
171
+                                ->default($this->invoice->getDefaultUnitColumn())
172
+                                ->reactive(),
173
+                            TextInput::make('custom_unit_column')
174
+                                ->reactive()
175
+                                ->disableLabel()
176
+                                ->disabled(static fn (callable $get) => $get('unit_column') !== 'other')
177
+                                ->nullable(),
178
+                            Radio::make('price_column')
179
+                                ->label('Price')
180
+                                ->options($this->invoice->getPriceColumns())
181
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
182
+                                    return $state === 'other' ? $get('custom_price_column') : $state;
183
+                                })
184
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
185
+                                    if (isset($this->invoice->getPriceColumns()[$state])) {
186
+                                        $component->state($state);
187
+                                    } else {
188
+                                        $component->state('other');
189
+                                        $set('custom_price_column', $state);
190
+                                    }
191
+                                })
192
+                                ->default($this->invoice->getDefaultPriceColumn())
193
+                                ->reactive(),
194
+                            TextInput::make('custom_price_column')
195
+                                ->reactive()
196
+                                ->disableLabel()
197
+                                ->disabled(static fn (callable $get) => $get('price_column') !== 'other')
198
+                                ->nullable(),
199
+                            Radio::make('amount_column')
200
+                                ->label('Amount')
201
+                                ->options($this->invoice->getAmountColumns())
202
+                                ->dehydrateStateUsing(static function (callable $get, $state) {
203
+                                    return $state === 'other' ? $get('custom_amount_column') : $state;
204
+                                })
205
+                                ->afterStateHydrated(function (callable $set, callable $get, $state, Radio $component) {
206
+                                    if (isset($this->invoice->getAmountColumns()[$state])) {
207
+                                        $component->state($state);
208
+                                    } else {
209
+                                        $component->state('other');
210
+                                        $set('custom_amount_column', $state);
211
+                                    }
212
+                                })
213
+                                ->default($this->invoice->getDefaultAmountColumn())
214
+                                ->reactive(),
215
+                            TextInput::make('custom_amount_column')
216
+                                ->reactive()
217
+                                ->disableLabel()
218
+                                ->disabled(static fn (callable $get) => $get('amount_column') !== 'other')
219
+                                ->nullable(),
220
+                        ])->columns(1),
221
+                    Group::make()
222
+                        ->schema([
223
+                            ViewField::make('preview.default')
224
+                                ->label('Preview')
225
+                                ->visible(static fn (callable $get) => $get('template') === 'default')
226
+                                ->view('components.invoice-layouts.default'),
227
+                            ViewField::make('preview.modern')
228
+                                ->label('Preview')
229
+                                ->visible(static fn (callable $get) => $get('template') === 'modern')
230
+                                ->view('components.invoice-layouts.modern'),
231
+                        ])->columnSpan(2),
232
+                ])->columns(3),
115 233
         ];
116 234
     }
117 235
 
118
-    public function save(): void
119
-    {
120
-        $data = $this->form->getState();
121
-
122
-        $data = $this->mutateFormDataBeforeSave($data);
123
-
124
-        $this->record = $this->invoice->update($data);
125
-
126
-        $this->form->model($this->record)->saveRelationships();
127
-
128
-        $this->getSavedNotification()?->send();
129
-    }
130
-
131 236
     protected function mutateFormDataBeforeSave(array $data): array
132 237
     {
133
-        $data['company_id'] = Auth::user()->currentCompany->id;
134 238
         $data['type'] = 'invoice';
135 239
 
136 240
         return $data;
137 241
     }
138 242
 
139
-    protected function getSavedNotification():?Notification
140
-    {
141
-        $title = $this->getSavedNotificationTitle();
142
-
143
-        if (blank($title)) {
144
-            return null;
145
-        }
146
-
147
-        return Notification::make()
148
-            ->success()
149
-            ->title($title);
150
-    }
151
-
152
-    protected function getSavedNotificationTitle(): ?string
153
-    {
154
-        return __('filament::resources/pages/edit-record.messages.saved');
155
-    }
156
-
157
-
158 243
     public function render(): View
159 244
     {
160 245
         return view('livewire.invoice');

+ 0
- 6
app/Models/Banking/Account.php Vedi File

@@ -2,7 +2,6 @@
2 2
 
3 3
 namespace App\Models\Banking;
4 4
 
5
-use App\Scopes\CurrentCompanyScope;
6 5
 use App\Models\Setting\Currency;
7 6
 use App\Models\Setting\DefaultSetting;
8 7
 use App\Traits\Blamable;
@@ -50,11 +49,6 @@ class Account extends Model
50 49
         'enabled' => 'boolean',
51 50
     ];
52 51
 
53
-    protected static function booted(): void
54
-    {
55
-        static::addGlobalScope(new CurrentCompanyScope);
56
-    }
57
-
58 52
     public function company(): BelongsTo
59 53
     {
60 54
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 10
- 0
app/Models/Company.php Vedi File

@@ -14,6 +14,8 @@ use App\Models\Setting\Tax;
14 14
 use Illuminate\Database\Eloquent\Factories\HasFactory;
15 15
 use Illuminate\Database\Eloquent\Relations\HasMany;
16 16
 use Illuminate\Database\Eloquent\Relations\HasOne;
17
+use Squire\Models\Country;
18
+use Squire\Models\Region;
17 19
 use Wallo\FilamentCompanies\Company as FilamentCompaniesCompany;
18 20
 use Wallo\FilamentCompanies\Events\CompanyCreated;
19 21
 use Wallo\FilamentCompanies\Events\CompanyDeleted;
@@ -40,6 +42,14 @@ class Company extends FilamentCompaniesCompany
40 42
     protected $fillable = [
41 43
         'name',
42 44
         'personal_company',
45
+        'logo',
46
+        'address',
47
+        'city',
48
+        'zip_code',
49
+        'state',
50
+        'country',
51
+        'phone',
52
+        'email',
43 53
     ];
44 54
 
45 55
     /**

+ 0
- 5
app/Models/Contact.php Vedi File

@@ -41,11 +41,6 @@ class Contact extends Model
41 41
         'updated_by',
42 42
     ];
43 43
 
44
-    protected static function booted(): void
45
-    {
46
-        static::addGlobalScope(new CurrentCompanyScope);
47
-    }
48
-
49 44
     public function company(): BelongsTo
50 45
     {
51 46
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 0
- 6
app/Models/Setting/Category.php Vedi File

@@ -4,7 +4,6 @@ namespace App\Models\Setting;
4 4
 
5 5
 use App\Models\Document\Document;
6 6
 use App\Models\Item;
7
-use App\Scopes\CurrentCompanyScope;
8 7
 use App\Traits\Blamable;
9 8
 use App\Traits\CompanyOwned;
10 9
 use Database\Factories\CategoryFactory;
@@ -36,11 +35,6 @@ class Category extends Model
36 35
         'enabled' => 'boolean',
37 36
     ];
38 37
 
39
-    protected static function booted(): void
40
-    {
41
-        static::addGlobalScope(new CurrentCompanyScope);
42
-    }
43
-
44 38
     public static function getCategoryTypes(): array
45 39
     {
46 40
         return [

+ 15
- 5
app/Models/Setting/Currency.php Vedi File

@@ -42,11 +42,6 @@ class Currency extends Model
42 42
         'symbol_first' => 'boolean',
43 43
     ];
44 44
 
45
-    protected static function booted(): void
46
-    {
47
-        static::addGlobalScope(new CurrentCompanyScope);
48
-    }
49
-
50 45
     public function company(): BelongsTo
51 46
     {
52 47
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
@@ -93,6 +88,21 @@ class Currency extends Model
93 88
         return $defaultCurrency->code ?? null;
94 89
     }
95 90
 
91
+    public static function convertBalance($balance, $oldCurrency, $newCurrency): float|int
92
+    {
93
+        $currencies = self::whereIn('code', [$oldCurrency, $newCurrency])->get();
94
+        $oldCurrency = $currencies->firstWhere('code', $oldCurrency);
95
+        $newCurrency = $currencies->firstWhere('code', $newCurrency);
96
+
97
+        $oldRate = $oldCurrency?->rate;
98
+        $newRate = $newCurrency?->rate;
99
+        $precision = $newCurrency?->precision;
100
+
101
+        $baseBalance = $balance / $oldRate;
102
+
103
+        return round($baseBalance * $newRate, $precision);
104
+    }
105
+
96 106
     protected static function newFactory(): Factory
97 107
     {
98 108
         return CurrencyFactory::new();

+ 0
- 5
app/Models/Setting/DefaultSetting.php Vedi File

@@ -31,11 +31,6 @@ class DefaultSetting extends Model
31 31
         'updated_by',
32 32
     ];
33 33
 
34
-    protected static function booted(): void
35
-    {
36
-        static::addGlobalScope(new CurrentCompanyScope);
37
-    }
38
-
39 34
     public function company(): BelongsTo
40 35
     {
41 36
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 0
- 5
app/Models/Setting/Discount.php Vedi File

@@ -43,11 +43,6 @@ class Discount extends Model
43 43
         'end_date' => 'datetime',
44 44
     ];
45 45
 
46
-    protected static function booted(): void
47
-    {
48
-        static::addGlobalScope(new CurrentCompanyScope);
49
-    }
50
-
51 46
     public function company(): BelongsTo
52 47
     {
53 48
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');

+ 72
- 29
app/Models/Setting/DocumentDefault.php Vedi File

@@ -21,27 +21,26 @@ class DocumentDefault extends Model
21 21
     protected $table = 'document_defaults';
22 22
 
23 23
     protected $fillable = [
24
-        'company_id',
25 24
         'type',
26
-        'document_number_prefix',
27
-        'document_number_digits',
28
-        'document_number_next',
25
+        'document_logo',
26
+        'number_prefix',
27
+        'number_digits',
28
+        'number_next',
29 29
         'payment_terms',
30
-        'template',
31 30
         'title',
32 31
         'subheading',
33
-        'notes',
34 32
         'terms',
35 33
         'footer',
34
+        'accent_color',
35
+        'template',
36
+        'item_column',
37
+        'unit_column',
38
+        'price_column',
39
+        'amount_column',
36 40
         'created_by',
37 41
         'updated_by',
38 42
     ];
39 43
 
40
-    protected static function booted(): void
41
-    {
42
-        static::addGlobalScope(new CurrentCompanyScope);
43
-    }
44
-
45 44
     public function company(): BelongsTo
46 45
     {
47 46
         return $this->belongsTo(FilamentCompanies::companyModel(), 'company_id');
@@ -62,31 +61,20 @@ class DocumentDefault extends Model
62 61
         return $this->belongsTo(FilamentCompanies::userModel(), 'updated_by');
63 62
     }
64 63
 
65
-    public static function getDocumentNumberDigits(): array
64
+    public static function getAvailableNumberDigits(): array
66 65
     {
67 66
         return array_combine(range(1, 20), range(1, 20));
68 67
     }
69 68
 
70
-    public static function getDefaultDocumentNumberDigits(string $type = 'invoice'): int
69
+    public static function getDefaultNumberDigits(string $type = 'invoice'): int
71 70
     {
72
-        $documentNumberDigits = self::where('type', $type)->pluck('document_number_digits', 'id')->toArray();
73
-
74
-        return array_key_first($documentNumberDigits) ?? 5;
71
+        return static::where('type', $type)->value('number_digits') ?? 5;
75 72
     }
76 73
 
77
-    public static function getDefaultDocumentNumberNext(int|null $numDigits = null, string $type = 'invoice'): string
74
+    public static function getNextDocumentNumber(int|null $numDigits = null, string $type = 'invoice'): string
78 75
     {
79
-        // Fetch the latest document
80 76
         $latestDocument = Document::where('type', $type)->orderBy('id', 'desc')->first();
81
-
82
-        // If there are no documents yet, start from 1
83
-        if (!$latestDocument) {
84
-            $nextNumber = 1;
85
-        } else {
86
-            // Otherwise, increment the latest document's number
87
-            $nextNumber = (int)$latestDocument->document_number + 1;
88
-        }
89
-
77
+        $nextNumber = $latestDocument ? ((int)$latestDocument->number + 1) : 1;
90 78
         return str_pad($nextNumber, $numDigits, '0', STR_PAD_LEFT);
91 79
     }
92 80
 
@@ -105,9 +93,64 @@ class DocumentDefault extends Model
105 93
 
106 94
     public static function getDefaultPaymentTerms(string $type = 'invoice'): int
107 95
     {
108
-        $paymentTerms = self::where('type', $type)->pluck('payment_terms', 'id')->toArray();
96
+        return static::where('type', $type)->value('payment_terms') ?? 30;
97
+    }
98
+
99
+    public static function getItemColumns(): array
100
+    {
101
+        return [
102
+            'items' => 'Items',
103
+            'products' => 'Products',
104
+            'services' => 'Services',
105
+            'other' => 'Other',
106
+        ];
107
+    }
108
+
109
+    public static function getDefaultItemColumn(string $type = 'invoice'): string
110
+    {
111
+        return static::where('type', $type)->value('item_column') ?? 'items';
112
+    }
109 113
 
110
-        return array_key_first($paymentTerms) ?? 30;
114
+    public static function getUnitColumns(): array
115
+    {
116
+        return [
117
+            'quantity' => 'Quantity',
118
+            'hours' => 'Hours',
119
+            'other' => 'Other',
120
+        ];
121
+    }
122
+
123
+    public static function getDefaultUnitColumn(string $type = 'invoice'): string
124
+    {
125
+        return static::where('type', $type)->value('unit_column') ?? 'quantity';
126
+    }
127
+
128
+    public static function getPriceColumns(): array
129
+    {
130
+        return [
131
+            'price' => 'Price',
132
+            'rate' => 'Rate',
133
+            'other' => 'Other',
134
+        ];
135
+    }
136
+
137
+    public static function getDefaultPriceColumn(string $type = 'invoice'): string
138
+    {
139
+        return static::where('type', $type)->value('price_column') ?? 'price';
140
+    }
141
+
142
+    public static function getAmountColumns(): array
143
+    {
144
+        return [
145
+            'amount' => 'Amount',
146
+            'total' => 'Total',
147
+            'other' => 'Other',
148
+        ];
149
+    }
150
+
151
+    public static function getDefaultAmountColumn(string $type = 'invoice'): string
152
+    {
153
+        return static::where('type', $type)->value('amount_column') ?? 'amount';
111 154
     }
112 155
 
113 156
     public function getDocumentNumberAttribute(): string

+ 0
- 5
app/Models/Setting/Tax.php Vedi File

@@ -40,11 +40,6 @@ class Tax extends Model
40 40
         'enabled' => 'boolean',
41 41
     ];
42 42
 
43
-    protected static function booted(): void
44
-    {
45
-        static::addGlobalScope(new CurrentCompanyScope);
46
-    }
47
-
48 43
     public function company(): BelongsTo
49 44
     {
50 45
         return $this->belongsTo(Company::class, 'company_id');

+ 2
- 1
app/Providers/SquireServiceProvider.php Vedi File

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Providers;
4 4
 
5
+use App\Models\Company;
5 6
 use App\Models\Contact;
6 7
 use Squire\Repository;
7 8
 use Illuminate\Support\ServiceProvider;
@@ -24,4 +25,4 @@ class SquireServiceProvider extends ServiceProvider
24 25
         Repository::registerSource(Contact::class, 'en', base_path('vendor/squirephp/regions-en/resources/data.csv'));
25 26
         Repository::registerSource(Contact::class, 'en', base_path('vendor/squirephp/countries-en/resources/data.csv'));
26 27
     }
27
-}
28
+}

+ 14
- 9
app/Traits/CompanyOwned.php Vedi File

@@ -2,6 +2,7 @@
2 2
 
3 3
 namespace App\Traits;
4 4
 
5
+use App\Scopes\CurrentCompanyScope;
5 6
 use Filament\Notifications\Notification;
6 7
 use Illuminate\Support\Facades\Auth;
7 8
 
@@ -10,16 +11,20 @@ trait CompanyOwned
10 11
     public static function bootCompanyOwned(): void
11 12
     {
12 13
         static::creating(static function ($model) {
13
-            if (Auth::check() && Auth::user()->currentCompany) {
14
-                $model->company_id = Auth::user()->currentCompany->id;
15
-            } else {
16
-                Notification::make()
17
-                    ->danger()
18
-                    ->title('Oops! Unable to Assign Company')
19
-                    ->body('We encountered an issue while creating the record. Please ensure you are logged in and have a valid company associated with your account.')
20
-                    ->persistent()
21
-                    ->send();
14
+            if (empty($model->company_id)) {
15
+                if (Auth::check() && Auth::user()->currentCompany) {
16
+                    $model->company_id = Auth::user()->currentCompany->id;
17
+                } else {
18
+                    Notification::make()
19
+                        ->danger()
20
+                        ->title('Oops! Unable to Assign Company')
21
+                        ->body('We encountered an issue while creating the record. Please ensure you are logged in and have a valid company associated with your account.')
22
+                        ->persistent()
23
+                        ->send();
24
+                }
22 25
             }
23 26
         });
27
+
28
+        static::addGlobalScope(new CurrentCompanyScope);
24 29
     }
25 30
 }

+ 9
- 11
app/Traits/HandlesDefaultSettingRecordUpdate.php Vedi File

@@ -8,34 +8,32 @@ use Illuminate\Support\Facades\Auth;
8 8
 trait HandlesDefaultSettingRecordUpdate
9 9
 {
10 10
     abstract protected function getRelatedEntities(): array;
11
-    abstract protected function getFormModel(): string;
12 11
 
13
-    protected function handleRecordUpdate(array $data): Model
12
+    protected function handleRecordUpdate(Model $record, array $data): Model
14 13
     {
15 14
         $relatedEntities = $this->getRelatedEntities();
16
-        $model = $this->getFormModel();
17 15
 
18
-        $existingRecord = $model::where('company_id', Auth::user()->currentCompany->id)
16
+        $existingRecord = $record::query()->where('company_id', Auth::user()->currentCompany->id)
19 17
             ->latest()
20 18
             ->first();
21 19
 
22 20
         foreach ($relatedEntities as $field => $params) {
23
-            [$class, $key, $type] = array_pad($params, 3, null);
21
+            [$modelClass, $key, $type] = array_pad($params, 3, null);
24 22
 
25 23
             if ($existingRecord === null || !isset($existingRecord->{$field})) {
26 24
                 continue;
27 25
             }
28 26
 
29 27
             if (isset($data[$field]) && $data[$field] !== $existingRecord->{$field}) {
30
-                $this->updateEnabledRecord($class, $key, $existingRecord->{$field}, $type, false);
31
-                $this->updateEnabledRecord($class, $key, $data[$field], $type, true);
28
+                $this->updateEnabledRecord(new $modelClass, $key, $existingRecord->{$field}, $type, false);
29
+                $this->updateEnabledRecord(new $modelClass, $key, $data[$field], $type);
32 30
             }
33 31
         }
34 32
 
35
-        $defaults = $model::where('company_id', Auth::user()->currentCompany->id)->first();
33
+        $defaults = $record::query()->where('company_id', Auth::user()->currentCompany->id)->first();
36 34
 
37 35
         if ($defaults === null) {
38
-            $defaults = $model::create($data);
36
+            $defaults = $record::query()->create($data);
39 37
         } else {
40 38
             $defaults->update($data);
41 39
         }
@@ -43,9 +41,9 @@ trait HandlesDefaultSettingRecordUpdate
43 41
         return $defaults;
44 42
     }
45 43
 
46
-    protected function updateEnabledRecord($class, $key, $value, $type = null, $enabled = true): void
44
+    protected function updateEnabledRecord(Model $record, string $key, mixed $value, ?string $type = null, bool $enabled = true): void
47 45
     {
48
-        $query = $class::where('company_id', Auth::user()->currentCompany->id)
46
+        $query = $record::query()->where('company_id', Auth::user()->currentCompany->id)
49 47
             ->where('enabled', !$enabled);
50 48
 
51 49
         if ($type !== null) {

+ 173
- 0
app/View/Models/InvoiceViewModel.php Vedi File

@@ -0,0 +1,173 @@
1
+<?php
2
+
3
+namespace App\View\Models;
4
+
5
+use App\Models\Setting\DocumentDefault;
6
+use Spatie\ViewModels\ViewModel;
7
+
8
+class InvoiceViewModel extends ViewModel
9
+{
10
+    public DocumentDefault $invoice;
11
+
12
+    public ?array $data = [];
13
+
14
+    public function __construct(DocumentDefault $invoice, ?array $data = null)
15
+    {
16
+        $this->invoice = $invoice;
17
+        $this->data = $data;
18
+    }
19
+
20
+    public function document_logo(): ?string
21
+    {
22
+        return $this->data['document_logo'] ?? $this->invoice->document_logo ?? $this->invoice->company->logo ?? null;
23
+    }
24
+
25
+    // Company related methods
26
+    public function company_name(): string
27
+    {
28
+        return $this->invoice->company->name;
29
+    }
30
+
31
+    public function company_address(): ?string
32
+    {
33
+        return $this->invoice->company->address ?? null;
34
+    }
35
+
36
+    public function company_phone(): ?string
37
+    {
38
+        return $this->invoice->company->phone ?? null;
39
+    }
40
+
41
+    public function company_city(): ?string
42
+    {
43
+        return $this->invoice->company->city ?? null;
44
+    }
45
+
46
+    public function company_state(): ?string
47
+    {
48
+        return $this->invoice->company->state ?? null;
49
+    }
50
+
51
+    public function company_zip(): ?string
52
+    {
53
+        return $this->invoice->company->zip_code ?? null;
54
+    }
55
+
56
+    // Invoice numbering related methods
57
+    public function number_prefix(): string
58
+    {
59
+        return $this->data['number_prefix'] ?? $this->invoice->number_prefix ?? 'INV-';
60
+    }
61
+
62
+    public function number_digits(): int
63
+    {
64
+        return $this->data['number_digits'] ?? $this->invoice->number_digits ?? $this->invoice->getDefaultNumberDigits();
65
+    }
66
+
67
+    public function number_next(): int
68
+    {
69
+        return $this->data['number_next'] ?? $this->invoice->number_next ?? $this->invoice->getNextDocumentNumber($this->number_digits());
70
+    }
71
+
72
+    public function invoice_number(): string
73
+    {
74
+        return $this->number_prefix() . str_pad($this->number_next(), $this->number_digits(), "0", STR_PAD_LEFT);
75
+    }
76
+
77
+    // Invoice date related methods
78
+    public function invoice_date(): string
79
+    {
80
+        return now()->format('M d, Y');
81
+    }
82
+
83
+    public function payment_terms(): string
84
+    {
85
+        return $this->data['payment_terms'] ?? $this->invoice->payment_terms ?? $this->invoice->getDefaultPaymentTerms();
86
+    }
87
+
88
+    public function invoice_due_date(): string
89
+    {
90
+        return now()->addDays($this->payment_terms())->format('M d, Y');
91
+    }
92
+
93
+    // Invoice header related methods
94
+    public function title(): string
95
+    {
96
+        return $this->data['title'] ?? $this->invoice->title ?? 'Invoice';
97
+    }
98
+
99
+    public function subheading(): ?string
100
+    {
101
+        return $this->data['subheading'] ?? $this->invoice->subheading ?? null;
102
+    }
103
+
104
+    // Invoice styling
105
+    public function accent_color(): string
106
+    {
107
+        return $this->data['accent_color'] ?? $this->invoice->accent_color ?? '#6366F1';
108
+    }
109
+
110
+    public function footer(): string
111
+    {
112
+        return $this->data['footer'] ?? $this->invoice->footer ?? 'Thank you for your business!';
113
+    }
114
+
115
+    public function terms(): string
116
+    {
117
+        return $this->data['terms'] ?? $this->invoice->terms ?? 'Payment is due within thirty (30) days from the date of invoice. Any discrepancies should be reported within fourteen (14) days of receipt.';
118
+    }
119
+
120
+    // Invoice column related methods
121
+    public function item_column(): string
122
+    {
123
+        $item_column = $this->data['item_column'] ?? $this->invoice->item_column ?? $this->invoice->getDefaultItemColumn();
124
+        return isset($this->invoice->getItemColumns()[$item_column]) ? ucfirst($item_column) : $item_column;
125
+    }
126
+
127
+    public function unit_column(): string
128
+    {
129
+        $unit_column = $this->data['unit_column'] ?? $this->invoice->unit_column ?? $this->invoice->getDefaultUnitColumn();
130
+        return isset($this->invoice->getUnitColumns()[$unit_column]) ? ucfirst($unit_column) : $unit_column;
131
+    }
132
+
133
+    public function price_column(): string
134
+    {
135
+        $price_column = $this->data['price_column'] ?? $this->invoice->price_column ?? $this->invoice->getDefaultPriceColumn();
136
+        return isset($this->invoice->getPriceColumns()[$price_column]) ? ucfirst($price_column) : $price_column;
137
+    }
138
+
139
+    public function amount_column(): string
140
+    {
141
+        $amount_column = $this->data['amount_column'] ?? $this->invoice->amount_column ?? $this->invoice->getDefaultAmountColumn();
142
+        return isset($this->invoice->getAmountColumns()[$amount_column]) ? ucfirst($amount_column) : $amount_column;
143
+    }
144
+
145
+    public function buildViewData(): array
146
+    {
147
+        return [
148
+            'document_logo' => $this->document_logo(),
149
+            'company_name' => $this->company_name(),
150
+            'company_address' => $this->company_address(),
151
+            'company_phone' => $this->company_phone(),
152
+            'company_city' => $this->company_city(),
153
+            'company_state' => $this->company_state(),
154
+            'company_zip' => $this->company_zip(),
155
+            'number_prefix' => $this->number_prefix(),
156
+            'number_digits' => $this->number_digits(),
157
+            'number_next' => $this->number_next(),
158
+            'invoice_number' => $this->invoice_number(),
159
+            'invoice_date' => $this->invoice_date(),
160
+            'payment_terms' => $this->payment_terms(),
161
+            'invoice_due_date' => $this->invoice_due_date(),
162
+            'title' => $this->title(),
163
+            'subheading' => $this->subheading(),
164
+            'accent_color' => $this->accent_color(),
165
+            'footer' => $this->footer(),
166
+            'terms' => $this->terms(),
167
+            'item_column' => $this->item_column(),
168
+            'unit_column' => $this->unit_column(),
169
+            'price_column' => $this->price_column(),
170
+            'amount_column' => $this->amount_column(),
171
+        ];
172
+    }
173
+}

+ 1
- 0
composer.json Vedi File

@@ -16,6 +16,7 @@
16 16
         "laravel/sanctum": "^3.2",
17 17
         "laravel/tinker": "^2.8",
18 18
         "leandrocfe/filament-apex-charts": "^1.0",
19
+        "spatie/laravel-view-models": "^1.5",
19 20
         "squirephp/countries-en": "^3.4",
20 21
         "squirephp/regions-en": "^3.4",
21 22
         "squirephp/repository": "^3.4"

+ 408
- 280
composer.lock
File diff soppresso perché troppo grande
Vedi File


+ 1
- 1
config/filament.php Vedi File

@@ -25,7 +25,7 @@ return [
25 25
     |
26 26
     */
27 27
 
28
-    'path' => env('FILAMENT_PATH', 'admin'),
28
+    'path' => env('FILAMENT_PATH', 'company'),
29 29
 
30 30
     /*
31 31
     |--------------------------------------------------------------------------

+ 1
- 1
config/services.php Vedi File

@@ -34,7 +34,7 @@ return [
34 34
     'github' => [
35 35
         'client_id' => env('GITHUB_CLIENT_ID'),
36 36
         'client_secret' => env('GITHUB_CLIENT_SECRET'),
37
-        'redirect' => 'https://erpsaas.test/oauth/github/callback',
37
+        'redirect' => 'http://erpsaas.test/oauth/github/callback',
38 38
     ],
39 39
 
40 40
 ];

+ 9
- 4
database/factories/DocumentDefaultFactory.php Vedi File

@@ -25,10 +25,15 @@ class DocumentDefaultFactory extends Factory
25 25
     public function definition(): array
26 26
     {
27 27
         return [
28
-            'document_number_digits' => '5',
29
-            'document_number_next' => '1',
28
+            'number_digits' => '5',
29
+            'number_next' => '1',
30 30
             'payment_terms' => '30',
31
+            'accent_color' => '#007BFF',
31 32
             'template' => 'default',
33
+            'item_column' => 'items',
34
+            'unit_column' => 'quantity',
35
+            'price_column' => 'price',
36
+            'amount_column' => 'amount',
32 37
         ];
33 38
     }
34 39
 
@@ -40,7 +45,7 @@ class DocumentDefaultFactory extends Factory
40 45
         return $this->state(function (array $attributes) {
41 46
             return [
42 47
                 'type' => 'invoice',
43
-                'document_number_prefix' => 'INV-',
48
+                'number_prefix' => 'INV-',
44 49
             ];
45 50
         });
46 51
     }
@@ -53,7 +58,7 @@ class DocumentDefaultFactory extends Factory
53 58
         return $this->state(function (array $attributes) {
54 59
             return [
55 60
                 'type' => 'bill',
56
-                'document_number_prefix' => 'BILL-',
61
+                'number_prefix' => 'BILL-',
57 62
             ];
58 63
         });
59 64
     }

+ 10
- 5
database/migrations/2023_05_22_000100_create_document_defaults_table.php Vedi File

@@ -15,16 +15,21 @@ return new class extends Migration
15 15
             $table->id();
16 16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17 17
             $table->string('type');
18
-            $table->string('document_number_prefix')->nullable();
19
-            $table->string('document_number_digits')->default(5);
20
-            $table->string('document_number_next')->default(1);
18
+            $table->string('document_logo')->nullable();
19
+            $table->string('number_prefix')->nullable();
20
+            $table->unsignedTinyInteger('number_digits')->default(5); // Adjusted this
21
+            $table->unsignedBigInteger('number_next')->default(1);   // Adjusted this to allow larger invoice numbers
21 22
             $table->string('payment_terms')->nullable();
22
-            $table->string('template')->default('default');
23 23
             $table->string('title')->nullable();
24 24
             $table->string('subheading')->nullable();
25
-            $table->text('notes')->nullable();
26 25
             $table->text('terms')->nullable();
27 26
             $table->string('footer')->nullable();
27
+            $table->string('accent_color')->nullable();
28
+            $table->string('template')->default('default');
29
+            $table->string('item_column')->nullable();
30
+            $table->string('unit_column')->nullable();
31
+            $table->string('price_column')->nullable();
32
+            $table->string('amount_column')->nullable();
28 33
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
29 34
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
30 35
             $table->timestamps();

+ 44
- 0
database/migrations/2023_08_07_144726_add_company_details_to_companies_table.php Vedi File

@@ -0,0 +1,44 @@
1
+<?php
2
+
3
+use Illuminate\Database\Migrations\Migration;
4
+use Illuminate\Database\Schema\Blueprint;
5
+use Illuminate\Support\Facades\Schema;
6
+
7
+return new class extends Migration
8
+{
9
+    /**
10
+     * Run the migrations.
11
+     */
12
+    public function up(): void
13
+    {
14
+        Schema::table('companies', function (Blueprint $table) {
15
+            $table->string('logo')->nullable();
16
+            $table->text('address')->nullable();
17
+            $table->string('city')->nullable();
18
+            $table->string('zip_code')->nullable();
19
+            $table->string('state')->nullable();
20
+            $table->string('country')->nullable();
21
+            $table->string('phone')->nullable();
22
+            $table->string('email')->nullable();
23
+        });
24
+    }
25
+
26
+    /**
27
+     * Reverse the migrations.
28
+     */
29
+    public function down(): void
30
+    {
31
+        Schema::table('companies', function (Blueprint $table) {
32
+            $table->dropColumn([
33
+                'logo',
34
+                'address',
35
+                'city',
36
+                'zip_code',
37
+                'state',
38
+                'country',
39
+                'phone',
40
+                'email',
41
+            ]);
42
+        });
43
+    }
44
+};

+ 161
- 161
package-lock.json Vedi File

@@ -32,9 +32,9 @@
32 32
             }
33 33
         },
34 34
         "node_modules/@alpinejs/focus": {
35
-            "version": "3.12.2",
36
-            "resolved": "https://registry.npmjs.org/@alpinejs/focus/-/focus-3.12.2.tgz",
37
-            "integrity": "sha512-Uhu/0uSwgYSyzAeNT/kzrxiVKJQezwhUM1VxYwCpfOMT0OvKJ80Wl+M4R+5JJhsf+15B65rQnsE7no+TxpV5bg==",
35
+            "version": "3.12.3",
36
+            "resolved": "https://registry.npmjs.org/@alpinejs/focus/-/focus-3.12.3.tgz",
37
+            "integrity": "sha512-kIQwvvUPCfCO2REpceQ3uHNdN3oqDLvvvQNaHVgHoYqk+RrL3EcR6uOHyvHJUgOhaIjN5Uc3b7BaRNrKZbDGew==",
38 38
             "dev": true,
39 39
             "dependencies": {
40 40
                 "focus-trap": "^6.6.1"
@@ -47,9 +47,9 @@
47 47
             "dev": true
48 48
         },
49 49
         "node_modules/@esbuild/android-arm": {
50
-            "version": "0.17.19",
51
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
52
-            "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
50
+            "version": "0.18.20",
51
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
52
+            "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
53 53
             "cpu": [
54 54
                 "arm"
55 55
             ],
@@ -63,9 +63,9 @@
63 63
             }
64 64
         },
65 65
         "node_modules/@esbuild/android-arm64": {
66
-            "version": "0.17.19",
67
-            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz",
68
-            "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==",
66
+            "version": "0.18.20",
67
+            "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
68
+            "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
69 69
             "cpu": [
70 70
                 "arm64"
71 71
             ],
@@ -79,9 +79,9 @@
79 79
             }
80 80
         },
81 81
         "node_modules/@esbuild/android-x64": {
82
-            "version": "0.17.19",
83
-            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz",
84
-            "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==",
82
+            "version": "0.18.20",
83
+            "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
84
+            "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
85 85
             "cpu": [
86 86
                 "x64"
87 87
             ],
@@ -95,9 +95,9 @@
95 95
             }
96 96
         },
97 97
         "node_modules/@esbuild/darwin-arm64": {
98
-            "version": "0.17.19",
99
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz",
100
-            "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==",
98
+            "version": "0.18.20",
99
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
100
+            "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
101 101
             "cpu": [
102 102
                 "arm64"
103 103
             ],
@@ -111,9 +111,9 @@
111 111
             }
112 112
         },
113 113
         "node_modules/@esbuild/darwin-x64": {
114
-            "version": "0.17.19",
115
-            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz",
116
-            "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==",
114
+            "version": "0.18.20",
115
+            "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
116
+            "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
117 117
             "cpu": [
118 118
                 "x64"
119 119
             ],
@@ -127,9 +127,9 @@
127 127
             }
128 128
         },
129 129
         "node_modules/@esbuild/freebsd-arm64": {
130
-            "version": "0.17.19",
131
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz",
132
-            "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==",
130
+            "version": "0.18.20",
131
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
132
+            "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
133 133
             "cpu": [
134 134
                 "arm64"
135 135
             ],
@@ -143,9 +143,9 @@
143 143
             }
144 144
         },
145 145
         "node_modules/@esbuild/freebsd-x64": {
146
-            "version": "0.17.19",
147
-            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz",
148
-            "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==",
146
+            "version": "0.18.20",
147
+            "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
148
+            "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
149 149
             "cpu": [
150 150
                 "x64"
151 151
             ],
@@ -159,9 +159,9 @@
159 159
             }
160 160
         },
161 161
         "node_modules/@esbuild/linux-arm": {
162
-            "version": "0.17.19",
163
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz",
164
-            "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==",
162
+            "version": "0.18.20",
163
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
164
+            "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
165 165
             "cpu": [
166 166
                 "arm"
167 167
             ],
@@ -175,9 +175,9 @@
175 175
             }
176 176
         },
177 177
         "node_modules/@esbuild/linux-arm64": {
178
-            "version": "0.17.19",
179
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz",
180
-            "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==",
178
+            "version": "0.18.20",
179
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
180
+            "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
181 181
             "cpu": [
182 182
                 "arm64"
183 183
             ],
@@ -191,9 +191,9 @@
191 191
             }
192 192
         },
193 193
         "node_modules/@esbuild/linux-ia32": {
194
-            "version": "0.17.19",
195
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
196
-            "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
194
+            "version": "0.18.20",
195
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
196
+            "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
197 197
             "cpu": [
198 198
                 "ia32"
199 199
             ],
@@ -207,9 +207,9 @@
207 207
             }
208 208
         },
209 209
         "node_modules/@esbuild/linux-loong64": {
210
-            "version": "0.17.19",
211
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz",
212
-            "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==",
210
+            "version": "0.18.20",
211
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
212
+            "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
213 213
             "cpu": [
214 214
                 "loong64"
215 215
             ],
@@ -223,9 +223,9 @@
223 223
             }
224 224
         },
225 225
         "node_modules/@esbuild/linux-mips64el": {
226
-            "version": "0.17.19",
227
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz",
228
-            "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==",
226
+            "version": "0.18.20",
227
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
228
+            "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
229 229
             "cpu": [
230 230
                 "mips64el"
231 231
             ],
@@ -239,9 +239,9 @@
239 239
             }
240 240
         },
241 241
         "node_modules/@esbuild/linux-ppc64": {
242
-            "version": "0.17.19",
243
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz",
244
-            "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==",
242
+            "version": "0.18.20",
243
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
244
+            "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
245 245
             "cpu": [
246 246
                 "ppc64"
247 247
             ],
@@ -255,9 +255,9 @@
255 255
             }
256 256
         },
257 257
         "node_modules/@esbuild/linux-riscv64": {
258
-            "version": "0.17.19",
259
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz",
260
-            "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==",
258
+            "version": "0.18.20",
259
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
260
+            "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
261 261
             "cpu": [
262 262
                 "riscv64"
263 263
             ],
@@ -271,9 +271,9 @@
271 271
             }
272 272
         },
273 273
         "node_modules/@esbuild/linux-s390x": {
274
-            "version": "0.17.19",
275
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz",
276
-            "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==",
274
+            "version": "0.18.20",
275
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
276
+            "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
277 277
             "cpu": [
278 278
                 "s390x"
279 279
             ],
@@ -287,9 +287,9 @@
287 287
             }
288 288
         },
289 289
         "node_modules/@esbuild/linux-x64": {
290
-            "version": "0.17.19",
291
-            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz",
292
-            "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==",
290
+            "version": "0.18.20",
291
+            "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
292
+            "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
293 293
             "cpu": [
294 294
                 "x64"
295 295
             ],
@@ -303,9 +303,9 @@
303 303
             }
304 304
         },
305 305
         "node_modules/@esbuild/netbsd-x64": {
306
-            "version": "0.17.19",
307
-            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz",
308
-            "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==",
306
+            "version": "0.18.20",
307
+            "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
308
+            "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
309 309
             "cpu": [
310 310
                 "x64"
311 311
             ],
@@ -319,9 +319,9 @@
319 319
             }
320 320
         },
321 321
         "node_modules/@esbuild/openbsd-x64": {
322
-            "version": "0.17.19",
323
-            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz",
324
-            "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==",
322
+            "version": "0.18.20",
323
+            "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
324
+            "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
325 325
             "cpu": [
326 326
                 "x64"
327 327
             ],
@@ -335,9 +335,9 @@
335 335
             }
336 336
         },
337 337
         "node_modules/@esbuild/sunos-x64": {
338
-            "version": "0.17.19",
339
-            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz",
340
-            "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==",
338
+            "version": "0.18.20",
339
+            "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
340
+            "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
341 341
             "cpu": [
342 342
                 "x64"
343 343
             ],
@@ -351,9 +351,9 @@
351 351
             }
352 352
         },
353 353
         "node_modules/@esbuild/win32-arm64": {
354
-            "version": "0.17.19",
355
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz",
356
-            "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==",
354
+            "version": "0.18.20",
355
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
356
+            "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
357 357
             "cpu": [
358 358
                 "arm64"
359 359
             ],
@@ -367,9 +367,9 @@
367 367
             }
368 368
         },
369 369
         "node_modules/@esbuild/win32-ia32": {
370
-            "version": "0.17.19",
371
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz",
372
-            "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==",
370
+            "version": "0.18.20",
371
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
372
+            "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
373 373
             "cpu": [
374 374
                 "ia32"
375 375
             ],
@@ -383,9 +383,9 @@
383 383
             }
384 384
         },
385 385
         "node_modules/@esbuild/win32-x64": {
386
-            "version": "0.17.19",
387
-            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz",
388
-            "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==",
386
+            "version": "0.18.20",
387
+            "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
388
+            "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
389 389
             "cpu": [
390 390
                 "x64"
391 391
             ],
@@ -413,9 +413,9 @@
413 413
             }
414 414
         },
415 415
         "node_modules/@jridgewell/resolve-uri": {
416
-            "version": "3.1.0",
417
-            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
418
-            "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
416
+            "version": "3.1.1",
417
+            "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
418
+            "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
419 419
             "dev": true,
420 420
             "engines": {
421 421
                 "node": ">=6.0.0"
@@ -437,21 +437,15 @@
437 437
             "dev": true
438 438
         },
439 439
         "node_modules/@jridgewell/trace-mapping": {
440
-            "version": "0.3.18",
441
-            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
442
-            "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
440
+            "version": "0.3.19",
441
+            "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
442
+            "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
443 443
             "dev": true,
444 444
             "dependencies": {
445
-                "@jridgewell/resolve-uri": "3.1.0",
446
-                "@jridgewell/sourcemap-codec": "1.4.14"
445
+                "@jridgewell/resolve-uri": "^3.1.0",
446
+                "@jridgewell/sourcemap-codec": "^1.4.14"
447 447
             }
448 448
         },
449
-        "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
450
-            "version": "1.4.14",
451
-            "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
452
-            "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
453
-            "dev": true
454
-        },
455 449
         "node_modules/@nodelib/fs.scandir": {
456 450
             "version": "2.1.5",
457 451
             "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -498,9 +492,9 @@
498 492
             }
499 493
         },
500 494
         "node_modules/@tailwindcss/forms": {
501
-            "version": "0.5.3",
502
-            "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz",
503
-            "integrity": "sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==",
495
+            "version": "0.5.4",
496
+            "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.4.tgz",
497
+            "integrity": "sha512-YAm12D3R7/9Mh4jFbYSMnsd6jG++8KxogWgqs7hbdo/86aWjjlIEvL7+QYdVELmAI0InXTpZqFIg5e7aDVWI2Q==",
504 498
             "dev": true,
505 499
             "dependencies": {
506 500
                 "mini-svg-data-uri": "^1.2.3"
@@ -540,9 +534,9 @@
540 534
             "dev": true
541 535
         },
542 536
         "node_modules/alpinejs": {
543
-            "version": "3.12.2",
544
-            "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.2.tgz",
545
-            "integrity": "sha512-wrUZULm9w6DYwWcUtB/anFLgRaF4riSuPgIJ3gcPUS8st9luAJnAxoIgro/Al97d2McSSz/JypWg/NlmKFIJJA==",
537
+            "version": "3.12.3",
538
+            "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.3.tgz",
539
+            "integrity": "sha512-fLz2dfYQ3xCk7Ip8LiIpV2W+9brUyex2TAE7Z0BCvZdUDklJE+n+a8gCgLWzfZ0GzZNZu7HUP8Z0z6Xbm6fsSA==",
546 540
             "dev": true,
547 541
             "dependencies": {
548 542
                 "@vue/reactivity": "~3.1.1"
@@ -661,9 +655,9 @@
661 655
             }
662 656
         },
663 657
         "node_modules/browserslist": {
664
-            "version": "4.21.9",
665
-            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
666
-            "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
658
+            "version": "4.21.10",
659
+            "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
660
+            "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
667 661
             "dev": true,
668 662
             "funding": [
669 663
                 {
@@ -680,9 +674,9 @@
680 674
                 }
681 675
             ],
682 676
             "dependencies": {
683
-                "caniuse-lite": "^1.0.30001503",
684
-                "electron-to-chromium": "^1.4.431",
685
-                "node-releases": "^2.0.12",
677
+                "caniuse-lite": "^1.0.30001517",
678
+                "electron-to-chromium": "^1.4.477",
679
+                "node-releases": "^2.0.13",
686 680
                 "update-browserslist-db": "^1.0.11"
687 681
             },
688 682
             "bin": {
@@ -702,9 +696,9 @@
702 696
             }
703 697
         },
704 698
         "node_modules/caniuse-lite": {
705
-            "version": "1.0.30001509",
706
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz",
707
-            "integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==",
699
+            "version": "1.0.30001519",
700
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz",
701
+            "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==",
708 702
             "dev": true,
709 703
             "funding": [
710 704
                 {
@@ -821,15 +815,15 @@
821 815
             "dev": true
822 816
         },
823 817
         "node_modules/electron-to-chromium": {
824
-            "version": "1.4.447",
825
-            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.447.tgz",
826
-            "integrity": "sha512-sxX0LXh+uL41hSJsujAN86PjhrV/6c79XmpY0TvjZStV6VxIgarf8SRkUoUTuYmFcZQTemsoqo8qXOGw5npWfw==",
818
+            "version": "1.4.490",
819
+            "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz",
820
+            "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==",
827 821
             "dev": true
828 822
         },
829 823
         "node_modules/esbuild": {
830
-            "version": "0.17.19",
831
-            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
832
-            "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
824
+            "version": "0.18.20",
825
+            "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
826
+            "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
833 827
             "dev": true,
834 828
             "hasInstallScript": true,
835 829
             "bin": {
@@ -839,28 +833,28 @@
839 833
                 "node": ">=12"
840 834
             },
841 835
             "optionalDependencies": {
842
-                "@esbuild/android-arm": "0.17.19",
843
-                "@esbuild/android-arm64": "0.17.19",
844
-                "@esbuild/android-x64": "0.17.19",
845
-                "@esbuild/darwin-arm64": "0.17.19",
846
-                "@esbuild/darwin-x64": "0.17.19",
847
-                "@esbuild/freebsd-arm64": "0.17.19",
848
-                "@esbuild/freebsd-x64": "0.17.19",
849
-                "@esbuild/linux-arm": "0.17.19",
850
-                "@esbuild/linux-arm64": "0.17.19",
851
-                "@esbuild/linux-ia32": "0.17.19",
852
-                "@esbuild/linux-loong64": "0.17.19",
853
-                "@esbuild/linux-mips64el": "0.17.19",
854
-                "@esbuild/linux-ppc64": "0.17.19",
855
-                "@esbuild/linux-riscv64": "0.17.19",
856
-                "@esbuild/linux-s390x": "0.17.19",
857
-                "@esbuild/linux-x64": "0.17.19",
858
-                "@esbuild/netbsd-x64": "0.17.19",
859
-                "@esbuild/openbsd-x64": "0.17.19",
860
-                "@esbuild/sunos-x64": "0.17.19",
861
-                "@esbuild/win32-arm64": "0.17.19",
862
-                "@esbuild/win32-ia32": "0.17.19",
863
-                "@esbuild/win32-x64": "0.17.19"
836
+                "@esbuild/android-arm": "0.18.20",
837
+                "@esbuild/android-arm64": "0.18.20",
838
+                "@esbuild/android-x64": "0.18.20",
839
+                "@esbuild/darwin-arm64": "0.18.20",
840
+                "@esbuild/darwin-x64": "0.18.20",
841
+                "@esbuild/freebsd-arm64": "0.18.20",
842
+                "@esbuild/freebsd-x64": "0.18.20",
843
+                "@esbuild/linux-arm": "0.18.20",
844
+                "@esbuild/linux-arm64": "0.18.20",
845
+                "@esbuild/linux-ia32": "0.18.20",
846
+                "@esbuild/linux-loong64": "0.18.20",
847
+                "@esbuild/linux-mips64el": "0.18.20",
848
+                "@esbuild/linux-ppc64": "0.18.20",
849
+                "@esbuild/linux-riscv64": "0.18.20",
850
+                "@esbuild/linux-s390x": "0.18.20",
851
+                "@esbuild/linux-x64": "0.18.20",
852
+                "@esbuild/netbsd-x64": "0.18.20",
853
+                "@esbuild/openbsd-x64": "0.18.20",
854
+                "@esbuild/sunos-x64": "0.18.20",
855
+                "@esbuild/win32-arm64": "0.18.20",
856
+                "@esbuild/win32-ia32": "0.18.20",
857
+                "@esbuild/win32-x64": "0.18.20"
864 858
             }
865 859
         },
866 860
         "node_modules/escalade": {
@@ -873,9 +867,9 @@
873 867
             }
874 868
         },
875 869
         "node_modules/fast-glob": {
876
-            "version": "3.3.0",
877
-            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
878
-            "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
870
+            "version": "3.3.1",
871
+            "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
872
+            "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
879 873
             "dev": true,
880 874
             "dependencies": {
881 875
                 "@nodelib/fs.stat": "^2.0.2",
@@ -1076,9 +1070,9 @@
1076 1070
             }
1077 1071
         },
1078 1072
         "node_modules/is-core-module": {
1079
-            "version": "2.12.1",
1080
-            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
1081
-            "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
1073
+            "version": "2.13.0",
1074
+            "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
1075
+            "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
1082 1076
             "dev": true,
1083 1077
             "dependencies": {
1084 1078
                 "has": "^1.0.3"
@@ -1118,9 +1112,9 @@
1118 1112
             }
1119 1113
         },
1120 1114
         "node_modules/jiti": {
1121
-            "version": "1.18.2",
1122
-            "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
1123
-            "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
1115
+            "version": "1.19.1",
1116
+            "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
1117
+            "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
1124 1118
             "dev": true,
1125 1119
             "bin": {
1126 1120
                 "jiti": "bin/jiti.js"
@@ -1269,9 +1263,9 @@
1269 1263
             }
1270 1264
         },
1271 1265
         "node_modules/node-releases": {
1272
-            "version": "2.0.12",
1273
-            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
1274
-            "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
1266
+            "version": "2.0.13",
1267
+            "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
1268
+            "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
1275 1269
             "dev": true
1276 1270
         },
1277 1271
         "node_modules/normalize-path": {
@@ -1371,9 +1365,9 @@
1371 1365
             }
1372 1366
         },
1373 1367
         "node_modules/postcss": {
1374
-            "version": "8.4.24",
1375
-            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
1376
-            "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
1368
+            "version": "8.4.27",
1369
+            "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
1370
+            "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
1377 1371
             "dev": true,
1378 1372
             "funding": [
1379 1373
                 {
@@ -1562,12 +1556,12 @@
1562 1556
             }
1563 1557
         },
1564 1558
         "node_modules/resolve": {
1565
-            "version": "1.22.2",
1566
-            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
1567
-            "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
1559
+            "version": "1.22.4",
1560
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz",
1561
+            "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==",
1568 1562
             "dev": true,
1569 1563
             "dependencies": {
1570
-                "is-core-module": "^2.11.0",
1564
+                "is-core-module": "^2.13.0",
1571 1565
                 "path-parse": "^1.0.7",
1572 1566
                 "supports-preserve-symlinks-flag": "^1.0.0"
1573 1567
             },
@@ -1589,9 +1583,9 @@
1589 1583
             }
1590 1584
         },
1591 1585
         "node_modules/rollup": {
1592
-            "version": "3.26.0",
1593
-            "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.0.tgz",
1594
-            "integrity": "sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==",
1586
+            "version": "3.28.0",
1587
+            "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz",
1588
+            "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==",
1595 1589
             "dev": true,
1596 1590
             "bin": {
1597 1591
                 "rollup": "dist/bin/rollup"
@@ -1637,9 +1631,9 @@
1637 1631
             }
1638 1632
         },
1639 1633
         "node_modules/sucrase": {
1640
-            "version": "3.32.0",
1641
-            "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
1642
-            "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==",
1634
+            "version": "3.34.0",
1635
+            "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
1636
+            "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==",
1643 1637
             "dev": true,
1644 1638
             "dependencies": {
1645 1639
                 "@jridgewell/gen-mapping": "^0.3.2",
@@ -1677,9 +1671,9 @@
1677 1671
             "dev": true
1678 1672
         },
1679 1673
         "node_modules/tailwindcss": {
1680
-            "version": "3.3.2",
1681
-            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
1682
-            "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
1674
+            "version": "3.3.3",
1675
+            "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
1676
+            "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
1683 1677
             "dev": true,
1684 1678
             "dependencies": {
1685 1679
                 "@alloc/quick-lru": "^5.2.0",
@@ -1702,7 +1696,6 @@
1702 1696
                 "postcss-load-config": "^4.0.1",
1703 1697
                 "postcss-nested": "^6.0.1",
1704 1698
                 "postcss-selector-parser": "^6.0.11",
1705
-                "postcss-value-parser": "^4.2.0",
1706 1699
                 "resolve": "^1.22.2",
1707 1700
                 "sucrase": "^3.32.0"
1708 1701
             },
@@ -1812,14 +1805,14 @@
1812 1805
             "dev": true
1813 1806
         },
1814 1807
         "node_modules/vite": {
1815
-            "version": "4.3.9",
1816
-            "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
1817
-            "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
1808
+            "version": "4.4.9",
1809
+            "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
1810
+            "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
1818 1811
             "dev": true,
1819 1812
             "dependencies": {
1820
-                "esbuild": "^0.17.5",
1821
-                "postcss": "^8.4.23",
1822
-                "rollup": "^3.21.0"
1813
+                "esbuild": "^0.18.10",
1814
+                "postcss": "^8.4.27",
1815
+                "rollup": "^3.27.1"
1823 1816
             },
1824 1817
             "bin": {
1825 1818
                 "vite": "bin/vite.js"
@@ -1827,12 +1820,16 @@
1827 1820
             "engines": {
1828 1821
                 "node": "^14.18.0 || >=16.0.0"
1829 1822
             },
1823
+            "funding": {
1824
+                "url": "https://github.com/vitejs/vite?sponsor=1"
1825
+            },
1830 1826
             "optionalDependencies": {
1831 1827
                 "fsevents": "~2.3.2"
1832 1828
             },
1833 1829
             "peerDependencies": {
1834 1830
                 "@types/node": ">= 14",
1835 1831
                 "less": "*",
1832
+                "lightningcss": "^1.21.0",
1836 1833
                 "sass": "*",
1837 1834
                 "stylus": "*",
1838 1835
                 "sugarss": "*",
@@ -1845,6 +1842,9 @@
1845 1842
                 "less": {
1846 1843
                     "optional": true
1847 1844
                 },
1845
+                "lightningcss": {
1846
+                    "optional": true
1847
+                },
1848 1848
                 "sass": {
1849 1849
                     "optional": true
1850 1850
                 },

+ 21
- 0
resources/css/filament.css Vedi File

@@ -30,6 +30,27 @@ select,
30 30
     @apply !shadow-none bg-moonlight;
31 31
 }
32 32
 
33
+[type="text"]:disabled,
34
+[type="email"]:disabled,
35
+[type="url"]:disabled,
36
+[type="password"]:disabled,
37
+[type="number"]:disabled,
38
+[type="date"]:disabled,
39
+[type="datetime-local"]:disabled,
40
+[type="month"]:disabled,
41
+[type="search"]:disabled,
42
+[type="tel"]:disabled,
43
+[type="time"]:disabled,
44
+[type="week"]:disabled,
45
+[multiple]:disabled,
46
+textarea:disabled,
47
+select:disabled,
48
+[type="checkbox"]:disabled,
49
+[type="radio"]:disabled,
50
+.filament-forms-repeater-component *:disabled {
51
+    @apply bg-gray-100 dark:bg-gray-700 text-gray-400 border-gray-300 opacity-[0.7];
52
+}
53
+
33 54
 div.p-2.space-y-2.bg-white.rounded-xl {
34 55
     @apply bg-moonlight dark:!bg-gray-800;
35 56
 }

+ 129
- 0
resources/views/components/invoice-layouts/default.blade.php Vedi File

@@ -0,0 +1,129 @@
1
+@php
2
+    $data = $this->form->getState();
3
+    $viewModel = new \App\View\Models\InvoiceViewModel($this->invoice, $data);
4
+    $viewSpecial = $viewModel->buildViewData();
5
+    extract($viewSpecial);
6
+@endphp
7
+
8
+<div class="print-template flex justify-center p-6">
9
+    <div class="paper bg-white dark:bg-gray-900 p-8 rounded-lg shadow-[0_0_10px_rgba(0,0,0,0.1)] w-[612px] h-[791px]">
10
+
11
+        <!-- Header: Logo on the left and Company details on the right -->
12
+        <div class="flex mb-4">
13
+            <div class="w-2/5">
14
+                @if($document_logo)
15
+                    <div class="text-left">
16
+                        <img src="{{ \Illuminate\Support\Facades\URL::asset($document_logo) }}" alt="logo" style="width: 120px; height: auto">
17
+                    </div>
18
+                @endif
19
+            </div>
20
+
21
+            <!-- Company Details -->
22
+            <div class="w-3/5">
23
+                <div class="text-xs text-gray-600 dark:text-gray-200 text-right space-y-1">
24
+                    <h2 class="text-xl font-bold text-gray-800 dark:text-white">{{ $company_name }}</h2>
25
+                    @if($company_address && $company_city && $company_state && $company_zip)
26
+                        <p>{{ $company_address }}</p>
27
+                        <p>{{ $company_city }}, {{ $company_state }} {{ $company_zip }}</p>
28
+                    @endif
29
+                </div>
30
+            </div>
31
+        </div>
32
+
33
+        <!-- Border Line -->
34
+        <div class="border-b-2 my-4" style="border-color: {{ $accent_color }}"></div>
35
+
36
+        <!-- Invoice Details -->
37
+        <div class="flex mb-4">
38
+            <div class="w-2/5">
39
+                <div class="text-left">
40
+                    <h1 class="text-3xl font-semibold text-gray-800 dark:text-white">{{ $title }}</h1>
41
+                    @if ($subheading)
42
+                        <p class="text-sm text-gray-600 dark:text-gray-100">{{ $subheading }}</p>
43
+                    @endif
44
+                </div>
45
+            </div>
46
+
47
+            <div class="w-3/5">
48
+                <div class="text-right">
49
+                    <p>
50
+                        <span class="text-xs font-semibold text-gray-500 dark:text-gray-100">No: </span>
51
+                        <span class="text-xs text-gray-700 dark:text-white">{{ $invoice_number }}</span>
52
+                    </p>
53
+                    <p>
54
+                        <span class="text-xs font-semibold text-gray-500 dark:text-gray-100">Date: </span>
55
+                        <span class="text-xs text-gray-500 dark:text-white">{{ $invoice_date }}</span>
56
+                    </p>
57
+                    <p>
58
+                        <span class="text-xs font-semibold text-gray-500 dark:text-gray-100">Due Date: </span>
59
+                        <span class="text-xs text-gray-500 dark:text-white">{{ $invoice_due_date }}</span>
60
+                    </p>
61
+                </div>
62
+            </div>
63
+        </div>
64
+
65
+        <!-- Billing Details -->
66
+        <div class="text-xs text-gray-600 dark:text-gray-200 mb-4">
67
+            <h3 class="text-base font-semibold text-gray-600 dark:text-gray-200 mb-2">BILL TO</h3>
68
+            <p class="text-sm text-gray-800 dark:text-white font-semibold">John Doe</p>
69
+            <p>123 Main Street</p>
70
+            <p>New York, NY 10001</p>
71
+        </div>
72
+
73
+        <!-- Line Items Table -->
74
+        <div class="mb-4">
75
+            <table class="w-full border-collapse text-sm">
76
+                <thead>
77
+                    <tr style="color: {{ $accent_color }}">
78
+                        <th class="text-left p-2 w-1/2">{{ $item_column }}</th>
79
+                        <th class="text-center p-2 w-1/6">{{ $unit_column }}</th>
80
+                        <th class="text-center p-2 w-1/6">{{ $price_column }}</th>
81
+                        <th class="text-center p-2 w-1/6">{{ $amount_column }}</th>
82
+                    </tr>
83
+                </thead>
84
+                <tbody>
85
+                    <tr class="bg-gray-200/75 dark:bg-gray-800">
86
+                        <td class="p-2">Item 1</td>
87
+                        <td class="p-2 text-center">2</td>
88
+                        <td class="p-2 text-center">$150.00</td>
89
+                        <td class="p-2 text-center">$300.00</td>
90
+                    </tr>
91
+                    <tr>
92
+                        <td class="p-2">Item 2</td>
93
+                        <td class="p-2 text-center">3</td>
94
+                        <td class="p-2 text-center">$200.00</td>
95
+                        <td class="p-2 text-center">$600.00</td>
96
+                    </tr>
97
+                    <tr class="bg-gray-200/75 dark:bg-gray-800">
98
+                        <td class="p-2">Item 3</td>
99
+                        <td class="p-2 text-center">1</td>
100
+                        <td class="p-2 text-center">$200.00</td>
101
+                        <td class="p-2 text-center">$200.00</td>
102
+                    </tr>
103
+                </tbody>
104
+            </table>
105
+        </div>
106
+
107
+        <!-- Total Amount -->
108
+        <div class="text-right mb-8">
109
+            <p class="text-sm text-gray-600 dark:text-gray-200">Subtotal: $1100.00</p>
110
+            <p class="text-sm text-gray-600 dark:text-gray-200">Tax: $110.00</p>
111
+            <p class="text-lg font-semibold text-gray-800 dark:text-white">Total: $1210.00</p>
112
+        </div>
113
+
114
+        <!-- Footer Notes -->
115
+        <div class="pt-6 text-gray-600 dark:text-gray-300 text-xs">
116
+            <p>{{ $footer }}</p>
117
+            <div class="mt-2 border-t-2 border-gray-300 py-2">
118
+                <h4 class="font-semibold text-gray-700 dark:text-gray-100 mb-2">Terms & Conditions:</h4>
119
+                <p>{{ $terms }}</p>
120
+            </div>
121
+        </div>
122
+    </div>
123
+</div>
124
+
125
+
126
+
127
+
128
+
129
+

+ 164
- 0
resources/views/components/invoice-layouts/modern.blade.php Vedi File

@@ -0,0 +1,164 @@
1
+@php
2
+    $data = $this->form->getState();
3
+    $viewModel = new \App\View\Models\InvoiceViewModel($this->invoice, $data);
4
+    $viewSpecial = $viewModel->buildViewData();
5
+    extract($viewSpecial);
6
+@endphp
7
+
8
+<div class="print-template flex justify-center p-6">
9
+    <div class="paper bg-white dark:bg-gray-900 p-8 rounded-lg shadow-[0_0_10px_rgba(0,0,0,0.1)] w-[612px] h-[791px]">
10
+
11
+        <!-- Colored Header with Logo -->
12
+        <div class="flex">
13
+            <div class="text-white py-3 flex items-start justify-start bg-gray-800" style="height: 80px; width: 85%;">
14
+                @if($document_logo)
15
+                    <div class="text-left">
16
+                        <img src="{{ \Illuminate\Support\Facades\URL::asset($document_logo) }}" alt="logo" style="width: 120px; height: auto">
17
+                    </div>
18
+                @endif
19
+            </div>
20
+
21
+            <!-- Ribbon Container -->
22
+            <div class="text-white flex flex-col justify-end p-2" style="background: {{ $accent_color }}; width: 30%; height: 120px; margin-left: -15%;">
23
+                @if($title)
24
+                    <div class="text-center align-bottom">
25
+                        <h1 class="text-3xl font-bold">{{ $title }}</h1>
26
+                    </div>
27
+                @endif
28
+            </div>
29
+        </div>
30
+
31
+        <!-- Company Details -->
32
+        <div class="flex justify-between">
33
+            <div class="text-xs text-gray-600 dark:text-gray-200 space-y-1">
34
+                <h2 class="text-xl font-bold text-gray-800 dark:text-white">{{ $company_name }}</h2>
35
+                @if($company_address && $company_city && $company_state && $company_zip)
36
+                    <p>{{ $company_address }}</p>
37
+                    <p>{{ $company_city }}, {{ $company_state }} {{ $company_zip }}</p>
38
+                @endif
39
+            </div>
40
+
41
+            <table class="mt-6" style="width: 35%; height: 100px">
42
+                <thead class="border-b-2" style="border-color: {{ $accent_color }}">
43
+                <tr class="p-1">
44
+                    <th class="text-xs text text-gray-500 text-right">Total Due</th>
45
+                    <th class="text-xs text-gray-500 text-left">:</th>
46
+                    <th class="text-sm font-semibold text-gray-800 dark:text-white text-right">USD $1100.00</th>
47
+                </tr>
48
+                </thead>
49
+                <tr>
50
+                    <td colspan="3" class="py-1"></td>
51
+                </tr>
52
+                <tbody class="text-xs text-gray-500 dark:text-white">
53
+                <tr class="p-1">
54
+                    <td class="text-right">Invoice No</td>
55
+                    <td class="text-left">:</td>
56
+                    <td class="text-right">{{ $invoice_number }}</td>
57
+                </tr>
58
+                <tr class="p-1">
59
+                    <td class="text-right">Invoice Date</td>
60
+                    <td class="text-left">:</td>
61
+                    <td class="text-right">{{ $invoice_date }}</td>
62
+                </tr>
63
+                <tr class="p-1">
64
+                    <td class="text-right">Invoice Due</td>
65
+                    <td class="text-left">:</td>
66
+                    <td class="text-right">{{ $invoice_due_date }}</td>
67
+                </tr>
68
+                </tbody>
69
+            </table>
70
+        </div>
71
+
72
+        <!-- Billing Details -->
73
+        <div class="text-xs text-gray-600 dark:text-gray-200 mb-4">
74
+            <h3 class="text-base font-semibold text-gray-600 dark:text-gray-200 tracking-tight mb-2">BILL TO</h3>
75
+            <p class="text-lg font-semibold" style="color: {{ $accent_color }}">John Doe</p>
76
+            <p>123 Main Street</p>
77
+            <p>New York, NY 10001</p>
78
+        </div>
79
+
80
+        <!-- Line Items Table -->
81
+        <div class="mb-8">
82
+            <table class="w-full border-collapse text-sm">
83
+                <thead style="background: {{ $accent_color }};">
84
+                <tr class="text-white">
85
+                    <th class="text-left p-2 w-1/12">No</th>
86
+                    <th class="text-left p-2 w-7/12">{{ $item_column }}</th>
87
+                    <th class="text-left p-2 w-1/6">{{ $unit_column }}</th>
88
+                    <th class="text-left p-2 w-1/6">{{ $price_column }}</th>
89
+                    <th class="text-left p-2 w-1/6">{{ $amount_column }}</th>
90
+                </tr>
91
+                </thead>
92
+                <tbody class="text-xs">
93
+                <tr class="border-b border-gray-300">
94
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">01</td>
95
+                    <td class="p-2">Item 1</td>
96
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">2</td>
97
+                    <td class="p-2">$150.00</td>
98
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$300.00</td>
99
+                </tr>
100
+                <tr class="border-b border-gray-300">
101
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">02</td>
102
+                    <td class="p-2">Item 2</td>
103
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">3</td>
104
+                    <td class="p-2">$200.00</td>
105
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$600.00</td>
106
+                </tr>
107
+                <tr class="border-b border-gray-300">
108
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">03</td>
109
+                    <td class="p-2">Item 3</td>
110
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">1</td>
111
+                    <td class="p-2">$180.00</td>
112
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$180.00</td>
113
+                </tr>
114
+                <tr>
115
+                    <td class="p-2"></td>
116
+                    <td class="p-2"></td>
117
+                    <td class="p-2"></td>
118
+                    <td class="p-2">Subtotal:</td>
119
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$1000.00</td>
120
+                </tr>
121
+                <tr>
122
+                    <td class="p-2"></td>
123
+                    <td class="p-2"></td>
124
+                    <td class="p-2"></td>
125
+                    <td class="p-2">Tax:</td>
126
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$100.00</td>
127
+                </tr>
128
+                <tr>
129
+                    <td class="p-2"></td>
130
+                    <td class="p-2"></td>
131
+                    <td class="p-2"></td>
132
+                    <td class="p-2">Total:</td>
133
+                    <td class="p-2 bg-gray-100 dark:bg-gray-800">$1100.00</td>
134
+                </tr>
135
+                </tbody>
136
+            </table>
137
+        </div>
138
+
139
+        <!-- Footer Notes -->
140
+        <div class="text-gray-600 dark:text-gray-300 text-xs">
141
+            <h4 class="font-semibold text-gray-700 dark:text-gray-100 mb-2" style="color: {{ $accent_color }}">Terms & Conditions:</h4>
142
+            <div class="flex mt-2 justify-between py-2 border-t-2 border-gray-300">
143
+                <div class="w-1/2">
144
+                    <p>{{ $terms }}</p>
145
+                </div>
146
+                <div class="w-1/2 text-right">
147
+                    <p>{{ $footer }}</p>
148
+                </div>
149
+            </div>
150
+        </div>
151
+    </div>
152
+</div>
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+

+ 3
- 0
resources/views/filament/pages/company-details.blade.php Vedi File

@@ -0,0 +1,3 @@
1
+<x-filament::page>
2
+    @livewire('company-details', compact('company'))
3
+</x-filament::page>

+ 1
- 1
resources/views/filament/pages/default-setting.blade.php Vedi File

@@ -1,3 +1,3 @@
1 1
 <x-filament::page>
2
-    @livewire('default-setting')
2
+    @livewire('default-setting', compact('company'))
3 3
 </x-filament::page>

+ 1
- 1
resources/views/filament/pages/invoice.blade.php Vedi File

@@ -1,3 +1,3 @@
1 1
 <x-filament::page>
2
-    @livewire('invoice')
2
+    @livewire('invoice', compact('company'))
3 3
 </x-filament::page>

+ 9
- 0
resources/views/livewire/company-details.blade.php Vedi File

@@ -0,0 +1,9 @@
1
+<x-filament::form wire:submit.prevent="save">
2
+    {{ $this->form }}
3
+
4
+    <div class="flex flex-wrap items-center gap-4 justify-start">
5
+        <x-filament::button type="submit" tag="button" wire:target="save">
6
+            {{ __('Save Changes') }}
7
+        </x-filament::button>
8
+    </div>
9
+</x-filament::form>

+ 8
- 12
resources/views/livewire/default-setting.blade.php Vedi File

@@ -1,13 +1,9 @@
1
-<div>
2
-    <form wire:submit.prevent="save">
3
-        {{ $this->form }}
1
+<x-filament::form wire:submit.prevent="save">
2
+    {{ $this->form }}
4 3
 
5
-        <div class="mt-6">
6
-            <div class="flex flex-wrap items-center gap-4 justify-start">
7
-                <x-filament::button type="submit">
8
-                    {{ __('Save Changes') }}
9
-                </x-filament::button>
10
-            </div>
11
-        </div>
12
-    </form>
13
-</div>
4
+    <div class="flex flex-wrap items-center gap-4 justify-start">
5
+        <x-filament::button type="submit" tag="button" wire:target="save">
6
+            {{ __('Save Changes') }}
7
+        </x-filament::button>
8
+    </div>
9
+</x-filament::form>

+ 8
- 12
resources/views/livewire/invoice.blade.php Vedi File

@@ -1,13 +1,9 @@
1
-<div>
2
-    <form wire:submit.prevent="save">
3
-        {{ $this->form }}
1
+<x-filament::form wire:submit.prevent="save">
2
+    {{ $this->form }}
4 3
 
5
-        <div class="mt-6">
6
-            <div class="flex flex-wrap items-center gap-4 justify-start">
7
-                <x-filament::button type="submit">
8
-                    {{ __('Save Changes') }}
9
-                </x-filament::button>
10
-            </div>
11
-        </div>
12
-    </form>
13
-</div>
4
+    <div class="flex flex-wrap items-center gap-4 justify-start">
5
+        <x-filament::button type="submit" tag="button" wire:target="save">
6
+            {{ __('Save Changes') }}
7
+        </x-filament::button>
8
+    </div>
9
+</x-filament::form>

+ 2
- 2
tailwind.config.js Vedi File

@@ -16,8 +16,8 @@ module.exports = {
16 16
         primary: {
17 17
           50: '#F2F3FA',
18 18
           100: '#D8DBF5',
19
-          200: '#B4B9EB',
20
-          300: '#9297E1',
19
+          200: '#d3e8f0',
20
+          300: '#b8d2e6',
21 21
           400: '#7075D7',
22 22
           500: '#454DC8',
23 23
           600: '#27285C',

Loading…
Annulla
Salva