Andrew Wallo 6 maanden geleden
bovenliggende
commit
7c53ce90db

+ 32
- 0
app/Enums/Accounting/BudgetSourceType.php Bestand weergeven

@@ -0,0 +1,32 @@
1
+<?php
2
+
3
+namespace App\Enums\Accounting;
4
+
5
+use App\Enums\Concerns\ParsesEnum;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum BudgetSourceType: string implements HasLabel
9
+{
10
+    use ParsesEnum;
11
+
12
+    case Budget = 'budget';
13
+    case Actuals = 'actuals';
14
+
15
+    public function getLabel(): string
16
+    {
17
+        return match ($this) {
18
+            self::Budget => 'Copy from a previous budget',
19
+            self::Actuals => 'Use historical actuals',
20
+        };
21
+    }
22
+
23
+    public function isBudget(): bool
24
+    {
25
+        return $this === self::Budget;
26
+    }
27
+
28
+    public function isActuals(): bool
29
+    {
30
+        return $this === self::Actuals;
31
+    }
32
+}

+ 50
- 19
app/Filament/Company/Resources/Accounting/BudgetResource.php Bestand weergeven

@@ -255,6 +255,14 @@ class BudgetResource extends Resource
255 255
                                 Header::make('Account')
256 256
                                     ->label('Account')
257 257
                                     ->width('200px'),
258
+                                Header::make('total')
259
+                                    ->label('Total')
260
+                                    ->width('120px')
261
+                                    ->align(Alignment::Right),
262
+                                Header::make('action')
263
+                                    ->label('')
264
+                                    ->width('40px')
265
+                                    ->align(Alignment::Center),
258 266
                             ];
259 267
 
260 268
                             foreach ($periods as $period) {
@@ -264,11 +272,6 @@ class BudgetResource extends Resource
264 272
                                     ->align(Alignment::Right);
265 273
                             }
266 274
 
267
-                            $headers[] = Header::make('total')
268
-                                ->label('Total')
269
-                                ->width('120px')
270
-                                ->align(Alignment::Right);
271
-
272 275
                             return [
273 276
                                 CustomTableRepeater::make('budgetItems')
274 277
                                     ->relationship()
@@ -279,6 +282,48 @@ class BudgetResource extends Resource
279 282
                                             ->hiddenLabel()
280 283
                                             ->content(fn (BudgetItem $record) => $record->account->name ?? ''),
281 284
 
285
+                                        Forms\Components\TextInput::make('total')
286
+                                            ->hiddenLabel()
287
+                                            ->mask(RawJs::make('$money($input)'))
288
+                                            ->stripCharacters(',')
289
+                                            ->numeric()
290
+                                            ->afterStateHydrated(function ($component, $state, BudgetItem $record) use ($periods) {
291
+                                                $total = 0;
292
+                                                // Calculate the total for this budget item across all periods
293
+                                                foreach ($periods as $period) {
294
+                                                    $allocation = $record->allocations->firstWhere('period', $period);
295
+                                                    $total += $allocation ? $allocation->amount : 0;
296
+                                                }
297
+                                                $component->state($total);
298
+                                            })
299
+                                            ->dehydrated(false),
300
+
301
+                                        Forms\Components\Actions::make([
302
+                                            Forms\Components\Actions\Action::make('disperse')
303
+                                                ->label('Disperse')
304
+                                                ->icon('heroicon-m-chevron-double-right')
305
+                                                ->color('primary')
306
+                                                ->iconButton()
307
+                                                ->action(function (Forms\Set $set, Forms\Get $get, BudgetItem $record, $livewire) use ($periods) {
308
+                                                    $total = CurrencyConverter::convertToCents($get('total'));
309
+                                                    ray($total);
310
+                                                    $numPeriods = count($periods);
311
+
312
+                                                    if ($numPeriods === 0) {
313
+                                                        return;
314
+                                                    }
315
+
316
+                                                    $baseAmount = floor($total / $numPeriods);
317
+                                                    $remainder = $total - ($baseAmount * $numPeriods);
318
+
319
+                                                    foreach ($periods as $index => $period) {
320
+                                                        $amount = $baseAmount + ($index === 0 ? $remainder : 0);
321
+                                                        $formattedAmount = CurrencyConverter::convertCentsToFormatSimple($amount);
322
+                                                        $set("allocations.{$period}", $formattedAmount);
323
+                                                    }
324
+                                                }),
325
+                                        ]),
326
+
282 327
                                         // Create a field for each period
283 328
                                         ...collect($periods)->map(function ($period) {
284 329
                                             return Forms\Components\TextInput::make("allocations.{$period}")
@@ -292,20 +337,6 @@ class BudgetResource extends Resource
292 337
                                                 })
293 338
                                                 ->dehydrated(false); // We'll handle saving manually
294 339
                                         })->toArray(),
295
-
296
-                                        Forms\Components\Placeholder::make('total')
297
-                                            ->hiddenLabel()
298
-                                            ->content(function (BudgetItem $record) use ($periods) {
299
-                                                $total = 0;
300
-
301
-                                                // Calculate the total for this budget item across all periods
302
-                                                foreach ($periods as $period) {
303
-                                                    $allocation = $record->allocations->firstWhere('period', $period);
304
-                                                    $total += $allocation ? $allocation->amount : 0;
305
-                                                }
306
-
307
-                                                return CurrencyConverter::formatToMoney($total);
308
-                                            }),
309 340
                                     ])
310 341
                                     ->spreadsheet()
311 342
                                     ->itemLabel(fn (BudgetItem $record) => $record->account->name ?? 'Budget Item')

+ 19
- 20
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/CreateBudget.php Bestand weergeven

@@ -3,12 +3,12 @@
3 3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
4 4
 
5 5
 use App\Enums\Accounting\BudgetIntervalType;
6
+use App\Enums\Accounting\BudgetSourceType;
6 7
 use App\Facades\Accounting;
7 8
 use App\Filament\Company\Resources\Accounting\BudgetResource;
8 9
 use App\Filament\Forms\Components\CustomSection;
9 10
 use App\Models\Accounting\Account;
10 11
 use App\Models\Accounting\Budget;
11
-use App\Models\Accounting\BudgetAllocation;
12 12
 use App\Models\Accounting\BudgetItem;
13 13
 use App\Utilities\Currency\CurrencyConverter;
14 14
 use Filament\Forms;
@@ -36,7 +36,7 @@ class CreateBudget extends CreateRecord
36 36
 
37 37
     public function getAccountsWithActuals(): Collection
38 38
     {
39
-        $fiscalYear = $this->data['actuals_fiscal_year'] ?? null;
39
+        $fiscalYear = $this->data['source_fiscal_year'] ?? null;
40 40
 
41 41
         if (blank($fiscalYear)) {
42 42
             return collect();
@@ -54,7 +54,7 @@ class CreateBudget extends CreateRecord
54 54
 
55 55
     public function getAccountsWithoutActuals(): Collection
56 56
     {
57
-        $fiscalYear = $this->data['actuals_fiscal_year'] ?? null;
57
+        $fiscalYear = $this->data['source_fiscal_year'] ?? null;
58 58
 
59 59
         if (blank($fiscalYear)) {
60 60
             return collect();
@@ -68,7 +68,7 @@ class CreateBudget extends CreateRecord
68 68
 
69 69
     public function getAccountBalances(): Collection
70 70
     {
71
-        $fiscalYear = $this->data['actuals_fiscal_year'] ?? null;
71
+        $fiscalYear = $this->data['source_fiscal_year'] ?? null;
72 72
 
73 73
         if (blank($fiscalYear)) {
74 74
             return collect();
@@ -144,12 +144,9 @@ class CreateBudget extends CreateRecord
144 144
 
145 145
                     Forms\Components\Grid::make(1)
146 146
                         ->schema([
147
-                            Forms\Components\Select::make('prefill_method')
147
+                            Forms\Components\Select::make('source_type')
148 148
                                 ->label('Prefill Method')
149
-                                ->options([
150
-                                    'previous_budget' => 'Copy from a previous budget',
151
-                                    'actuals' => 'Use historical actuals',
152
-                                ])
149
+                                ->options(BudgetSourceType::class)
153 150
                                 ->live()
154 151
                                 ->required(),
155 152
 
@@ -161,11 +158,11 @@ class CreateBudget extends CreateRecord
161 158
                                     ->pluck('name', 'id'))
162 159
                                 ->searchable()
163 160
                                 ->required()
164
-                                ->visible(fn (Forms\Get $get) => $get('prefill_method') === 'previous_budget'),
161
+                                ->visible(fn (Forms\Get $get) => BudgetSourceType::parse($get('source_type'))?->isBudget()),
165 162
 
166 163
                             // If user selects to use historical actuals
167
-                            Forms\Components\Select::make('actuals_fiscal_year')
168
-                                ->label('Reference Fiscal Year')
164
+                            Forms\Components\Select::make('source_fiscal_year')
165
+                                ->label('Fiscal Year')
169 166
                                 ->options(function () {
170 167
                                     $options = [];
171 168
                                     $company = auth()->user()->currentCompany;
@@ -193,7 +190,7 @@ class CreateBudget extends CreateRecord
193 190
                                     // Update the selected_accounts field to exclude accounts without actuals
194 191
                                     $set('selected_accounts', $accountIdsWithoutActuals);
195 192
                                 })
196
-                                ->visible(fn (Forms\Get $get) => $get('prefill_method') === 'actuals'),
193
+                                ->visible(fn (Forms\Get $get) => BudgetSourceType::parse($get('source_type'))?->isActuals()),
197 194
                         ])->visible(fn (Forms\Get $get) => $get('prefill_data') === true),
198 195
 
199 196
                     CustomSection::make('Account Selection')
@@ -226,7 +223,7 @@ class CreateBudget extends CreateRecord
226 223
                                     return $this->getBudgetableAccounts()->pluck('name', 'id')->toArray();
227 224
                                 })
228 225
                                 ->descriptions(function (Forms\Components\CheckboxList $component) {
229
-                                    $fiscalYear = $this->data['actuals_fiscal_year'] ?? null;
226
+                                    $fiscalYear = $this->data['source_fiscal_year'] ?? null;
230 227
 
231 228
                                     if (blank($fiscalYear)) {
232 229
                                         return [];
@@ -284,11 +281,11 @@ class CreateBudget extends CreateRecord
284 281
                                     $set('exclude_accounts_without_actuals', $allAccountsWithoutActualsSelected);
285 282
                                 }),
286 283
                         ])
287
-                        ->visible(function () {
284
+                        ->visible(function (Forms\Get $get) {
288 285
                             // Only show when using actuals with valid fiscal year AND accounts without transactions exist
289
-                            $prefillMethod = $this->data['prefill_method'] ?? null;
286
+                            $prefillSourceType = BudgetSourceType::parse($get('source_type'));
290 287
 
291
-                            if ($prefillMethod !== 'actuals' || blank($this->data['actuals_fiscal_year'] ?? null)) {
288
+                            if ($prefillSourceType !== BudgetSourceType::Actuals || blank($get('source_fiscal_year'))) {
292 289
                                 return false;
293 290
                             }
294 291
 
@@ -306,6 +303,9 @@ class CreateBudget extends CreateRecord
306 303
     {
307 304
         /** @var Budget $budget */
308 305
         $budget = Budget::create([
306
+            'source_budget_id' => $data['source_budget_id'] ?? null,
307
+            'source_fiscal_year' => $data['source_fiscal_year'] ?? null,
308
+            'source_type' => $data['source_type'] ?? null,
309 309
             'name' => $data['name'],
310 310
             'interval_type' => $data['interval_type'],
311 311
             'start_date' => $data['start_date'],
@@ -330,8 +330,8 @@ class CreateBudget extends CreateRecord
330 330
             $budgetEndDate = Carbon::parse($data['end_date']);
331 331
 
332 332
             // Determine amounts based on the prefill method
333
-            $amounts = match ($data['prefill_method'] ?? null) {
334
-                'actuals' => $this->getAmountsFromActuals($account, $data['actuals_fiscal_year'], BudgetIntervalType::parse($data['interval_type'])),
333
+            $amounts = match ($data['source_type'] ?? null) {
334
+                'actuals' => $this->getAmountsFromActuals($account, $data['source_fiscal_year'], BudgetIntervalType::parse($data['interval_type'])),
335 335
                 'previous_budget' => $this->getAmountsFromPreviousBudget($account, $data['source_budget_id'], BudgetIntervalType::parse($data['interval_type'])),
336 336
                 default => $this->generateZeroAmounts($data['start_date'], $data['end_date'], BudgetIntervalType::parse($data['interval_type'])),
337 337
             };
@@ -619,7 +619,6 @@ class CreateBudget extends CreateRecord
619 619
             BudgetIntervalType::Month => $date->format('M Y'),
620 620
             BudgetIntervalType::Quarter => 'Q' . $date->quarter . ' ' . $date->year,
621 621
             BudgetIntervalType::Year => (string) $date->year,
622
-            default => $date->format('Y-m-d'),
623 622
         };
624 623
     }
625 624
 

+ 84
- 11
app/Filament/Company/Resources/Accounting/BudgetResource/RelationManagers/BudgetItemsRelationManager.php Bestand weergeven

@@ -2,11 +2,15 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManagers;
4 4
 
5
+use App\Filament\Tables\Columns\DeferredTextInputColumn;
6
+use App\Models\Accounting\BudgetAllocation;
5 7
 use App\Models\Accounting\BudgetItem;
8
+use Filament\Notifications\Notification;
6 9
 use Filament\Resources\RelationManagers\RelationManager;
10
+use Filament\Tables\Actions\Action;
7 11
 use Filament\Tables\Columns\TextColumn;
8
-use Filament\Tables\Columns\TextInputColumn;
9 12
 use Filament\Tables\Table;
13
+use Livewire\Attributes\On;
10 14
 
11 15
 class BudgetItemsRelationManager extends RelationManager
12 16
 {
@@ -14,12 +18,71 @@ class BudgetItemsRelationManager extends RelationManager
14 18
 
15 19
     protected static bool $isLazy = false;
16 20
 
21
+    // Store changes that are pending
22
+    public array $batchChanges = [];
23
+
24
+    // Listen for events from the custom column
25
+
26
+    #[On('batch-column-changed')]
27
+    public function handleBatchColumnChanged($data): void
28
+    {
29
+        ray($data);
30
+        // Store the changed value
31
+        $key = "{$data['recordKey']}.{$data['name']}";
32
+        $this->batchChanges[$key] = $data['value'];
33
+    }
34
+
35
+    #[On('save-batch-changes')]
36
+    public function saveBatchChanges(): void
37
+    {
38
+        ray('Saving batch changes');
39
+        foreach ($this->batchChanges as $key => $value) {
40
+            // Parse the composite key
41
+            [$recordKey, $column] = explode('.', $key, 2);
42
+
43
+            // Extract period from the column name (e.g., "allocations_by_period.2023-Q1")
44
+            preg_match('/allocations_by_period\.(.+)/', $column, $matches);
45
+            $period = $matches[1] ?? null;
46
+
47
+            if (! $period) {
48
+                continue;
49
+            }
50
+
51
+            // Find the record
52
+            $record = BudgetItem::find($recordKey);
53
+            if (! $record) {
54
+                continue;
55
+            }
56
+
57
+            // Update the allocation
58
+            $allocation = $record->allocations->firstWhere('period', $period);
59
+            if ($allocation) {
60
+                $allocation->update(['amount' => $value]);
61
+            } else {
62
+                $record->allocations()->create([
63
+                    'period' => $period,
64
+                    'amount' => $value,
65
+                    // Add other required fields
66
+                ]);
67
+            }
68
+        }
69
+
70
+        // Clear the batch changes
71
+        $this->batchChanges = [];
72
+
73
+        // Notify the user
74
+        Notification::make()
75
+            ->title('Budget allocations updated')
76
+            ->success()
77
+            ->send();
78
+    }
79
+
17 80
     public function table(Table $table): Table
18 81
     {
19 82
         $budget = $this->getOwnerRecord();
20 83
 
21 84
         // Get distinct periods for this budget
22
-        $periods = \App\Models\Accounting\BudgetAllocation::query()
85
+        $periods = BudgetAllocation::query()
23 86
             ->join('budget_items', 'budget_allocations.budget_item_id', '=', 'budget_items.id')
24 87
             ->where('budget_items.budget_id', $budget->id)
25 88
             ->orderBy('start_date')
@@ -28,30 +91,40 @@ class BudgetItemsRelationManager extends RelationManager
28 91
             ->values()
29 92
             ->toArray();
30 93
 
94
+        ray($this->batchChanges);
95
+
31 96
         return $table
32 97
             ->recordTitleAttribute('account_id')
33 98
             ->paginated(false)
34 99
             ->modifyQueryUsing(
35 100
                 fn ($query) => $query->with(['account', 'allocations'])
36 101
             )
102
+            ->headerActions([
103
+                Action::make('saveBatchChanges')
104
+                    ->label('Save All Changes')
105
+                    ->action('saveBatchChanges')
106
+                    ->color('primary')
107
+                    ->icon('heroicon-o-check-circle'),
108
+            ])
37 109
             ->columns(array_merge([
38 110
                 TextColumn::make('account.name')
39 111
                     ->label('Accounts')
40 112
                     ->sortable()
41 113
                     ->searchable(),
42 114
             ], collect($periods)->map(
43
-                fn ($period) => TextInputColumn::make("allocations_by_period.{$period}")
115
+                fn ($period) => DeferredTextInputColumn::make("allocations_by_period.{$period}")
44 116
                     ->label($period)
45
-                    ->getStateUsing(
46
-                        fn ($record) => $record->allocations->firstWhere('period', $period)?->amount
47
-                    )
48
-                    ->updateStateUsing(function (BudgetItem $record, $state) use ($period) {
49
-                        $allocation = $record->allocations->firstWhere('period', $period);
50
-
51
-                        if ($allocation) {
52
-                            $allocation->update(['amount' => $state]);
117
+                    ->getStateUsing(function ($record, DeferredTextInputColumn $column) use ($period) {
118
+                        $key = "{$record->getKey()}.{$column->getName()}";
119
+
120
+                        // Check if batch change exists
121
+                        if (isset($this->batchChanges[$key])) {
122
+                            return $this->batchChanges[$key];
53 123
                         }
124
+
125
+                        return $record->allocations->firstWhere('period', $period)?->amount;
54 126
                     })
127
+                    ->batchMode(),
55 128
             )->all()));
56 129
     }
57 130
 }

+ 25
- 0
app/Filament/Tables/Columns/DeferredTextInputColumn.php Bestand weergeven

@@ -0,0 +1,25 @@
1
+<?php
2
+
3
+namespace App\Filament\Tables\Columns;
4
+
5
+use Closure;
6
+use Filament\Tables\Columns\TextInputColumn;
7
+
8
+class DeferredTextInputColumn extends TextInputColumn
9
+{
10
+    protected string $view = 'filament.tables.columns.deferred-text-input-column';
11
+
12
+    protected bool | Closure $batchMode = false;
13
+
14
+    public function batchMode(bool | Closure $condition = true): static
15
+    {
16
+        $this->batchMode = $condition;
17
+
18
+        return $this;
19
+    }
20
+
21
+    public function getBatchMode(): bool
22
+    {
23
+        return $this->evaluate($this->batchMode);
24
+    }
25
+}

+ 24
- 3
app/Models/Accounting/Budget.php Bestand weergeven

@@ -5,8 +5,10 @@ namespace App\Models\Accounting;
5 5
 use App\Concerns\Blamable;
6 6
 use App\Concerns\CompanyOwned;
7 7
 use App\Enums\Accounting\BudgetIntervalType;
8
+use App\Enums\Accounting\BudgetSourceType;
8 9
 use App\Enums\Accounting\BudgetStatus;
9 10
 use App\Filament\Company\Resources\Accounting\BudgetResource;
11
+use App\Models\User;
10 12
 use Filament\Actions\Action;
11 13
 use Filament\Actions\MountableAction;
12 14
 use Filament\Actions\ReplicateAction;
@@ -14,6 +16,7 @@ use Illuminate\Database\Eloquent\Builder;
14 16
 use Illuminate\Database\Eloquent\Casts\Attribute;
15 17
 use Illuminate\Database\Eloquent\Factories\HasFactory;
16 18
 use Illuminate\Database\Eloquent\Model;
19
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
17 20
 use Illuminate\Database\Eloquent\Relations\HasMany;
18 21
 use Illuminate\Database\Eloquent\Relations\HasManyThrough;
19 22
 use Illuminate\Support\Carbon;
@@ -26,6 +29,9 @@ class Budget extends Model
26 29
 
27 30
     protected $fillable = [
28 31
         'company_id',
32
+        'source_budget_id',
33
+        'source_fiscal_year',
34
+        'source_type',
29 35
         'name',
30 36
         'start_date',
31 37
         'end_date',
@@ -33,12 +39,15 @@ class Budget extends Model
33 39
         'interval_type', // day, week, month, quarter, year
34 40
         'notes',
35 41
         'approved_at',
42
+        'approved_by_id',
36 43
         'closed_at',
37 44
         'created_by',
38 45
         'updated_by',
39 46
     ];
40 47
 
41 48
     protected $casts = [
49
+        'source_fiscal_year' => 'integer',
50
+        'source_type' => BudgetSourceType::class,
42 51
         'start_date' => 'date',
43 52
         'end_date' => 'date',
44 53
         'status' => BudgetStatus::class,
@@ -47,6 +56,21 @@ class Budget extends Model
47 56
         'closed_at' => 'datetime',
48 57
     ];
49 58
 
59
+    public function sourceBudget(): BelongsTo
60
+    {
61
+        return $this->belongsTo(self::class, 'source_budget_id');
62
+    }
63
+
64
+    public function derivedBudgets(): HasMany
65
+    {
66
+        return $this->hasMany(self::class, 'source_budget_id');
67
+    }
68
+
69
+    public function approvedBy(): BelongsTo
70
+    {
71
+        return $this->belongsTo(User::class, 'approved_by_id');
72
+    }
73
+
50 74
     public function budgetItems(): HasMany
51 75
     {
52 76
         return $this->hasMany(BudgetItem::class);
@@ -59,8 +83,6 @@ class Budget extends Model
59 83
 
60 84
     /**
61 85
      * Get all periods for this budget in chronological order.
62
-     *
63
-     * @return array
64 86
      */
65 87
     public function getPeriods(): array
66 88
     {
@@ -75,7 +97,6 @@ class Budget extends Model
75 97
             ->toArray();
76 98
     }
77 99
 
78
-
79 100
     public function isDraft(): bool
80 101
     {
81 102
         return $this->status === BudgetStatus::Draft;

+ 5
- 0
database/migrations/2025_03_15_191245_create_budgets_table.php Bestand weergeven

@@ -14,6 +14,10 @@ return new class extends Migration
14 14
         Schema::create('budgets', function (Blueprint $table) {
15 15
             $table->id();
16 16
             $table->foreignId('company_id')->constrained()->cascadeOnDelete();
17
+            $table->foreignId('source_budget_id')->nullable()->constrained('budgets')->nullOnDelete();
18
+            // Source fiscal year
19
+            $table->year('source_fiscal_year')->nullable();
20
+            $table->string('source_type')->nullable(); // budget, actuals
17 21
             $table->string('name');
18 22
             $table->date('start_date');
19 23
             $table->date('end_date');
@@ -21,6 +25,7 @@ return new class extends Migration
21 25
             $table->string('interval_type')->default('month'); // day, week, month, quarter, year
22 26
             $table->text('notes')->nullable();
23 27
             $table->timestamp('approved_at')->nullable();
28
+            $table->foreignId('approved_by_id')->nullable()->constrained('users')->nullOnDelete();
24 29
             $table->timestamp('closed_at')->nullable();
25 30
             $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
26 31
             $table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();

+ 6
- 11
resources/css/filament/company/form-fields.css Bestand weergeven

@@ -76,7 +76,6 @@
76 76
     .table-repeater-container {
77 77
         border: 1px solid #e5e7eb !important;
78 78
         border-radius: 0 !important;
79
-        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
80 79
         @apply ring-0 !important;
81 80
     }
82 81
 
@@ -84,13 +83,11 @@
84 83
 
85 84
     .table-repeater-header {
86 85
         background-color: #f8f9fa !important;
87
-        border-radius: 0 !important;
88 86
     }
89 87
 
90 88
     .table-repeater-header-column {
91 89
         border: 1px solid #e5e7eb !important;
92 90
         background-color: #f8f9fa !important;
93
-        border-radius: 0 !important;
94 91
         font-weight: 600 !important;
95 92
         padding: 8px 12px !important;
96 93
     }
@@ -106,7 +103,6 @@
106 103
 
107 104
     .table-repeater-column input {
108 105
         text-align: right !important;
109
-        width: 100% !important;
110 106
     }
111 107
 
112 108
     /* Flatten inputs */
@@ -114,16 +110,15 @@
114 110
     .fi-input-wrapper,
115 111
     .fi-input {
116 112
         padding: 0 !important;
117
-        width: 100% !important;
118 113
     }
119 114
 
120 115
     .fi-input-wrp,
121 116
     .fi-fo-file-upload .filepond--root {
122
-        @apply ring-0 bg-transparent shadow-none !important;
117
+        @apply ring-0 bg-transparent shadow-none rounded-none !important;
123 118
     }
124 119
 
125 120
     .fi-input-wrp input {
126
-        @apply bg-transparent border-0 shadow-none !important;
121
+        @apply bg-transparent !important;
127 122
     }
128 123
 
129 124
     /* Focus states */
@@ -134,10 +129,6 @@
134 129
         z-index: 1 !important;
135 130
     }
136 131
 
137
-    .fi-input-wrp:focus-within {
138
-        @apply ring-0 shadow-none !important;
139
-    }
140
-
141 132
     input:focus,
142 133
     select:focus,
143 134
     textarea:focus {
@@ -195,3 +186,7 @@
195 186
     }
196 187
 }
197 188
 
189
+.is-spreadsheet .table-repeater-column .fi-input-wrp-suffix {
190
+    padding-right: 0 !important;
191
+}
192
+

+ 152
- 0
resources/views/filament/tables/columns/deferred-text-input-column.blade.php Bestand weergeven

@@ -0,0 +1,152 @@
1
+@php
2
+    use Filament\Support\Enums\Alignment;
3
+
4
+    $isDisabled = $isDisabled();
5
+    $state = $getState();
6
+    $mask = $getMask();
7
+    $batchMode = $getBatchMode();
8
+
9
+    $alignment = $getAlignment() ?? Alignment::Start;
10
+
11
+    if (! $alignment instanceof Alignment) {
12
+        $alignment = filled($alignment) ? (Alignment::tryFrom($alignment) ?? $alignment) : null;
13
+    }
14
+
15
+    if (filled($mask)) {
16
+        $type = 'text';
17
+    } else {
18
+        $type = $getType();
19
+    }
20
+@endphp
21
+
22
+<div
23
+    x-data="{
24
+        error: undefined,
25
+
26
+        isEditing: false,
27
+
28
+        isLoading: false,
29
+
30
+        name: @js($getName()),
31
+
32
+        recordKey: @js($recordKey),
33
+
34
+        state: @js($state),
35
+    }"
36
+    x-init="
37
+        () => {
38
+            Livewire.hook('commit', ({ component, commit, succeed, fail, respond }) => {
39
+                succeed(({ snapshot, effect }) => {
40
+                    $nextTick(() => {
41
+                        if (component.id !== @js($this->getId())) {
42
+                            return
43
+                        }
44
+
45
+                        if (isEditing) {
46
+                            return
47
+                        }
48
+
49
+                        if (! $refs.newState) {
50
+                            return
51
+                        }
52
+
53
+                        let newState = $refs.newState.value.replaceAll('\\'+String.fromCharCode(34), String.fromCharCode(34))
54
+
55
+                        if (state === newState) {
56
+                            return
57
+                        }
58
+
59
+                        state = newState
60
+                    })
61
+                })
62
+            })
63
+        }
64
+    "
65
+    {{
66
+        $attributes
67
+            ->merge($getExtraAttributes(), escape: false)
68
+            ->class([
69
+                'fi-ta-text-input w-full min-w-48',
70
+                'px-3 py-4' => ! $isInline(),
71
+            ])
72
+    }}
73
+>
74
+    <input
75
+        type="hidden"
76
+        value="{{ str($state)->replace('"', '\\"') }}"
77
+        x-ref="newState"
78
+    />
79
+
80
+    <x-filament::input.wrapper
81
+        :alpine-disabled="'isLoading || ' . \Illuminate\Support\Js::from($isDisabled)"
82
+        alpine-valid="error === undefined"
83
+        x-tooltip="
84
+            error === undefined
85
+                ? false
86
+                : {
87
+                    content: error,
88
+                    theme: $store.theme,
89
+                }
90
+        "
91
+        x-on:click.stop.prevent=""
92
+    >
93
+        {{-- format-ignore-start --}}
94
+        <x-filament::input
95
+            :disabled="$isDisabled"
96
+            :input-mode="$getInputMode()"
97
+            :placeholder="$getPlaceholder()"
98
+            :step="$getStep()"
99
+            :type="$type"
100
+            :x-bind:disabled="$isDisabled ? null : 'isLoading'"
101
+            x-model="state"
102
+            x-on:blur="isEditing = false"
103
+            x-on:focus="isEditing = true"
104
+            :attributes="
105
+                \Filament\Support\prepare_inherited_attributes(
106
+                    $getExtraInputAttributeBag()
107
+                        ->merge([
108
+                            'x-on:change' . ($type === 'number' ? '.debounce.1s' : null) => $batchMode ? '
109
+                                $wire.dispatch(\'batch-column-changed\', {
110
+                                    data: {
111
+                                        name: name,
112
+                                        recordKey: recordKey,
113
+                                        value: $event.target.value
114
+                                    }
115
+                                })
116
+                            ' : '
117
+                                isLoading = true
118
+
119
+                                const response = await $wire.updateTableColumnState(
120
+                                    name,
121
+                                    recordKey,
122
+                                    $event.target.value,
123
+                                )
124
+
125
+                                error = response?.error ?? undefined
126
+
127
+                                if (! error) {
128
+                                    state = response
129
+                                }
130
+
131
+                                isLoading = false
132
+                            ',
133
+                            'x-on:keydown.enter' => $batchMode ? '$wire.dispatch(\'save-batch-changes\')' : '',
134
+                            'x-mask' . ($mask instanceof \Filament\Support\RawJs ? ':dynamic' : '') => filled($mask) ? $mask : null,
135
+                        ])
136
+                        ->class([
137
+                            match ($alignment) {
138
+                                Alignment::Start => 'text-start',
139
+                                Alignment::Center => 'text-center',
140
+                                Alignment::End => 'text-end',
141
+                                Alignment::Left => 'text-left',
142
+                                Alignment::Right => 'text-right',
143
+                                Alignment::Justify, Alignment::Between => 'text-justify',
144
+                                default => $alignment,
145
+                            },
146
+                        ])
147
+                )
148
+            "
149
+        />
150
+        {{-- format-ignore-end --}}
151
+    </x-filament::input.wrapper>
152
+</div>

Laden…
Annuleren
Opslaan