Andrew Wallo 4 月之前
父節點
當前提交
739432de16

+ 8
- 6
app/Filament/Company/Resources/Accounting/BudgetResource/RelationManagers/BudgetItemsRelationManager.php 查看文件

2
 
2
 
3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManagers;
3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManagers;
4
 
4
 
5
-use App\Filament\Tables\Columns\DeferredTextInputColumn;
5
+use App\Filament\Tables\Columns\CustomTextInputColumn;
6
 use App\Models\Accounting\Budget;
6
 use App\Models\Accounting\Budget;
7
 use App\Models\Accounting\BudgetAllocation;
7
 use App\Models\Accounting\BudgetAllocation;
8
 use App\Models\Accounting\BudgetItem;
8
 use App\Models\Accounting\BudgetItem;
144
                     ->titlePrefixedWithLabel(false)
144
                     ->titlePrefixedWithLabel(false)
145
                     ->collapsible(),
145
                     ->collapsible(),
146
             ])
146
             ])
147
-            ->recordClasses(['budget-items-relation-manager'])
147
+            ->recordClasses(['is-spreadsheet'])
148
             ->defaultGroup('account.category')
148
             ->defaultGroup('account.category')
149
             ->headerActions([
149
             ->headerActions([
150
                 Action::make('saveBatchChanges')
150
                 Action::make('saveBatchChanges')
157
                     ->label('Account')
157
                     ->label('Account')
158
                     ->limit(30)
158
                     ->limit(30)
159
                     ->searchable(),
159
                     ->searchable(),
160
-                DeferredTextInputColumn::make(self::TOTAL_COLUMN)
160
+                CustomTextInputColumn::make(self::TOTAL_COLUMN)
161
                     ->label('Total')
161
                     ->label('Total')
162
                     ->alignRight()
162
                     ->alignRight()
163
                     ->mask(RawJs::make('$money($input)'))
163
                     ->mask(RawJs::make('$money($input)'))
173
 
173
 
174
                         return CurrencyConverter::convertCentsToFormatSimple($total);
174
                         return CurrencyConverter::convertCentsToFormatSimple($total);
175
                     })
175
                     })
176
-                    ->batchMode()
176
+                    ->deferred()
177
+                    ->navigable()
177
                     ->summarize(
178
                     ->summarize(
178
                         Summarizer::make()
179
                         Summarizer::make()
179
                             ->using(function (\Illuminate\Database\Query\Builder $query) {
180
                             ->using(function (\Illuminate\Database\Query\Builder $query) {
258
                 ...$allocationPeriods->map(function (BudgetAllocation $period) {
259
                 ...$allocationPeriods->map(function (BudgetAllocation $period) {
259
                     $alias = $period->start_date->format('Y_m_d');
260
                     $alias = $period->start_date->format('Y_m_d');
260
 
261
 
261
-                    return DeferredTextInputColumn::make($alias)
262
+                    return CustomTextInputColumn::make($alias)
262
                         ->label($period->period)
263
                         ->label($period->period)
263
                         ->alignRight()
264
                         ->alignRight()
264
-                        ->batchMode()
265
+                        ->deferred()
266
+                        ->navigable()
265
                         ->mask(RawJs::make('$money($input)'))
267
                         ->mask(RawJs::make('$money($input)'))
266
                         ->getStateUsing(function ($record) use ($alias) {
268
                         ->getStateUsing(function ($record) use ($alias) {
267
                             $key = "{$record->getKey()}.{$alias}";
269
                             $key = "{$record->getKey()}.{$alias}";

+ 2
- 4
app/Filament/Company/Resources/Purchases/BillResource/Pages/ListBills.php 查看文件

21
     {
21
     {
22
         return [
22
         return [
23
             Actions\Action::make('payBills')
23
             Actions\Action::make('payBills')
24
-                ->label('Pay Bills')
25
-                ->icon('heroicon-o-credit-card')
26
-                ->color('primary')
27
-                ->url(fn () => BillResource::getUrl('pay-bills')),
24
+                ->outlined()
25
+                ->url(BillResource::getUrl('pay-bills')),
28
             Actions\CreateAction::make(),
26
             Actions\CreateAction::make(),
29
         ];
27
         ];
30
     }
28
     }

+ 143
- 80
app/Filament/Company/Resources/Purchases/BillResource/Pages/PayBills.php 查看文件

5
 use App\Enums\Accounting\BillStatus;
5
 use App\Enums\Accounting\BillStatus;
6
 use App\Enums\Accounting\PaymentMethod;
6
 use App\Enums\Accounting\PaymentMethod;
7
 use App\Filament\Company\Resources\Purchases\BillResource;
7
 use App\Filament\Company\Resources\Purchases\BillResource;
8
+use App\Filament\Tables\Columns\CustomTextInputColumn;
8
 use App\Models\Accounting\Bill;
9
 use App\Models\Accounting\Bill;
9
 use App\Models\Accounting\Transaction;
10
 use App\Models\Accounting\Transaction;
10
 use App\Models\Banking\BankAccount;
11
 use App\Models\Banking\BankAccount;
12
+use App\Models\Common\Vendor;
13
+use App\Models\Setting\Currency;
11
 use App\Utilities\Currency\CurrencyAccessor;
14
 use App\Utilities\Currency\CurrencyAccessor;
12
 use App\Utilities\Currency\CurrencyConverter;
15
 use App\Utilities\Currency\CurrencyConverter;
13
 use Filament\Actions;
16
 use Filament\Actions;
17
 use Filament\Resources\Pages\ListRecords;
20
 use Filament\Resources\Pages\ListRecords;
18
 use Filament\Support\RawJs;
21
 use Filament\Support\RawJs;
19
 use Filament\Tables;
22
 use Filament\Tables;
23
+use Filament\Tables\Columns\Summarizers\Summarizer;
20
 use Filament\Tables\Columns\TextColumn;
24
 use Filament\Tables\Columns\TextColumn;
21
-use Filament\Tables\Columns\TextInputColumn;
22
-use Filament\Tables\Enums\FiltersLayout;
23
 use Filament\Tables\Table;
25
 use Filament\Tables\Table;
24
 use Illuminate\Contracts\Support\Htmlable;
26
 use Illuminate\Contracts\Support\Htmlable;
25
 use Illuminate\Database\Eloquent\Collection;
27
 use Illuminate\Database\Eloquent\Collection;
28
+use Illuminate\Database\Query\Builder;
29
+use Illuminate\Support\Str;
26
 use Livewire\Attributes\Computed;
30
 use Livewire\Attributes\Computed;
27
 
31
 
28
 /**
32
 /**
52
     {
56
     {
53
         parent::mount();
57
         parent::mount();
54
 
58
 
55
-        $this->form->fill([
56
-            'bank_account_id' => BankAccount::where('enabled', true)->first()?->id,
57
-            'payment_date' => now(),
58
-            'payment_method' => PaymentMethod::Check,
59
-        ]);
59
+        $this->form->fill();
60
 
60
 
61
         $this->reset('tableFilters');
61
         $this->reset('tableFilters');
62
     }
62
     }
64
     protected function getHeaderActions(): array
64
     protected function getHeaderActions(): array
65
     {
65
     {
66
         return [
66
         return [
67
-            Actions\Action::make('paySelected')
68
-                ->label('Pay Selected Bills')
69
-                ->icon('heroicon-o-credit-card')
67
+            Actions\Action::make('processPayments')
70
                 ->color('primary')
68
                 ->color('primary')
71
                 ->action(function () {
69
                 ->action(function () {
72
                     $data = $this->data;
70
                     $data = $this->data;
73
-                    $selectedRecords = $this->getTableRecords();
71
+                    $tableRecords = $this->getTableRecords();
74
                     $paidCount = 0;
72
                     $paidCount = 0;
75
                     $totalPaid = 0;
73
                     $totalPaid = 0;
76
 
74
 
77
-                    foreach ($selectedRecords as $bill) {
75
+                    /** @var Bill $bill */
76
+                    foreach ($tableRecords as $bill) {
78
                         if (! $bill->canRecordPayment()) {
77
                         if (! $bill->canRecordPayment()) {
79
                             continue;
78
                             continue;
80
                         }
79
                         }
87
                         }
86
                         }
88
 
87
 
89
                         $paymentData = [
88
                         $paymentData = [
90
-                            'posted_at' => $data['payment_date'],
89
+                            'posted_at' => $data['posted_at'],
91
                             'payment_method' => $data['payment_method'],
90
                             'payment_method' => $data['payment_method'],
92
                             'bank_account_id' => $data['bank_account_id'],
91
                             'bank_account_id' => $data['bank_account_id'],
93
                             'amount' => $paymentAmount,
92
                             'amount' => $paymentAmount,
98
                         $totalPaid += $paymentAmount;
97
                         $totalPaid += $paymentAmount;
99
                     }
98
                     }
100
 
99
 
101
-                    $totalFormatted = CurrencyConverter::formatCentsToMoney($totalPaid);
100
+                    $currencyCode = $this->getTableFilterState('currency_code')['value'];
101
+                    $totalFormatted = CurrencyConverter::formatCentsToMoney($totalPaid, $currencyCode, true);
102
 
102
 
103
                     Notification::make()
103
                     Notification::make()
104
                         ->title('Bills paid successfully')
104
                         ->title('Bills paid successfully')
105
-                        ->body("Paid {$paidCount} bill(s) for a total of {$totalFormatted}")
105
+                        ->body("Paid {$paidCount} " . Str::plural('bill', $paidCount) . " for a total of {$totalFormatted}")
106
                         ->success()
106
                         ->success()
107
                         ->send();
107
                         ->send();
108
 
108
 
109
-                    // Clear payment amounts after successful payment
110
-                    foreach ($selectedRecords as $bill) {
111
-                        $this->paymentAmounts[$bill->id] = 0;
112
-                    }
109
+                    $this->reset('paymentAmounts');
113
 
110
 
114
                     $this->resetTable();
111
                     $this->resetTable();
115
                 }),
112
                 }),
129
     public function form(Form $form): Form
126
     public function form(Form $form): Form
130
     {
127
     {
131
         return $form
128
         return $form
129
+            ->live()
132
             ->schema([
130
             ->schema([
133
                 Forms\Components\Grid::make(3)
131
                 Forms\Components\Grid::make(3)
134
                     ->schema([
132
                     ->schema([
135
                         Forms\Components\Select::make('bank_account_id')
133
                         Forms\Components\Select::make('bank_account_id')
136
-                            ->label('Bank Account')
137
-                            ->options(function () {
134
+                            ->label('Account')
135
+                            ->options(static function () {
138
                                 return Transaction::getBankAccountOptionsFlat();
136
                                 return Transaction::getBankAccountOptionsFlat();
139
                             })
137
                             })
138
+                            ->default(fn () => BankAccount::where('enabled', true)->first()?->id)
140
                             ->selectablePlaceholder(false)
139
                             ->selectablePlaceholder(false)
141
                             ->searchable()
140
                             ->searchable()
142
                             ->softRequired(),
141
                             ->softRequired(),
143
-                        Forms\Components\DatePicker::make('payment_date')
144
-                            ->label('Payment Date')
142
+                        Forms\Components\DatePicker::make('posted_at')
143
+                            ->label('Date')
145
                             ->default(now())
144
                             ->default(now())
146
                             ->softRequired(),
145
                             ->softRequired(),
147
                         Forms\Components\Select::make('payment_method')
146
                         Forms\Components\Select::make('payment_method')
148
-                            ->label('Payment Method')
147
+                            ->label('Payment method')
149
                             ->selectablePlaceholder(false)
148
                             ->selectablePlaceholder(false)
150
                             ->options(PaymentMethod::class)
149
                             ->options(PaymentMethod::class)
151
-                            ->default(PaymentMethod::Check)
152
-                            ->softRequired()
153
-                            ->live(),
150
+                            ->default(PaymentMethod::BankPayment)
151
+                            ->softRequired(),
154
                     ]),
152
                     ]),
155
             ])->statePath('data');
153
             ])->statePath('data');
156
     }
154
     }
163
                     ->with(['vendor'])
161
                     ->with(['vendor'])
164
                     ->unpaid()
162
                     ->unpaid()
165
             )
163
             )
166
-            ->selectable()
164
+            ->recordClasses(['is-spreadsheet'])
165
+            ->defaultSort('due_date')
166
+            ->paginated(false)
167
             ->columns([
167
             ->columns([
168
                 TextColumn::make('vendor.name')
168
                 TextColumn::make('vendor.name')
169
                     ->label('Vendor')
169
                     ->label('Vendor')
170
                     ->sortable(),
170
                     ->sortable(),
171
                 TextColumn::make('bill_number')
171
                 TextColumn::make('bill_number')
172
-                    ->label('Bill #')
172
+                    ->label('Bill number')
173
                     ->sortable(),
173
                     ->sortable(),
174
                 TextColumn::make('due_date')
174
                 TextColumn::make('due_date')
175
-                    ->label('Due Date')
176
-                    ->date('M j, Y')
175
+                    ->label('Due date')
176
+                    ->defaultDateFormat()
177
+                    ->sortable(),
178
+                Tables\Columns\TextColumn::make('status')
179
+                    ->badge()
177
                     ->sortable(),
180
                     ->sortable(),
178
                 TextColumn::make('amount_due')
181
                 TextColumn::make('amount_due')
179
-                    ->label('Amount Due')
182
+                    ->label('Amount due')
180
                     ->currency(static fn (Bill $record) => $record->currency_code)
183
                     ->currency(static fn (Bill $record) => $record->currency_code)
181
                     ->alignEnd()
184
                     ->alignEnd()
182
-                    ->sortable(),
183
-                TextInputColumn::make('payment_amount')
184
-                    ->label('Payment Amount')
185
+                    ->sortable()
186
+                    ->summarize([
187
+                        Summarizer::make()
188
+                            ->using(function (Builder $query) {
189
+                                $totalAmountDue = $query->sum('amount_due');
190
+                                $bankAccountCurrency = $this->getSelectedBankAccount()->account->currency_code;
191
+                                $activeCurrency = $this->getTableFilterState('currency_code')['value'] ?? $bankAccountCurrency;
192
+
193
+                                if ($activeCurrency !== $bankAccountCurrency) {
194
+                                    $totalAmountDue = CurrencyConverter::convertBalance($totalAmountDue, $activeCurrency, $bankAccountCurrency);
195
+                                }
196
+
197
+                                return CurrencyConverter::formatCentsToMoney($totalAmountDue, $bankAccountCurrency, true);
198
+                            }),
199
+                        Summarizer::make()
200
+                            ->using(function (Builder $query) {
201
+                                $totalAmountDue = $query->sum('amount_due');
202
+                                $currencyCode = $this->getTableFilterState('currency_code')['value'];
203
+
204
+                                return CurrencyConverter::formatCentsToMoney($totalAmountDue, $currencyCode, true);
205
+                            })
206
+                            ->visible(function () {
207
+                                $activeCurrency = $this->getTableFilterState('currency_code')['value'] ?? null;
208
+                                $bankAccountCurrency = $this->getSelectedBankAccount()->account->currency_code;
209
+
210
+                                return $activeCurrency && $activeCurrency !== $bankAccountCurrency;
211
+                            }),
212
+                    ]),
213
+                CustomTextInputColumn::make('payment_amount')
214
+                    ->label('Payment amount')
185
                     ->alignEnd()
215
                     ->alignEnd()
216
+                    ->navigable()
186
                     ->mask(RawJs::make('$money($input)'))
217
                     ->mask(RawJs::make('$money($input)'))
187
                     ->updateStateUsing(function (Bill $record, $state) {
218
                     ->updateStateUsing(function (Bill $record, $state) {
188
-                        if (empty($state) || $state === '0.00') {
219
+                        if (! CurrencyConverter::isValidAmount($state, 'USD')) {
189
                             $this->paymentAmounts[$record->id] = 0;
220
                             $this->paymentAmounts[$record->id] = 0;
190
 
221
 
191
                             return '0.00';
222
                             return '0.00';
193
 
224
 
194
                         $paymentCents = CurrencyConverter::convertToCents($state, 'USD');
225
                         $paymentCents = CurrencyConverter::convertToCents($state, 'USD');
195
 
226
 
196
-                        // Validate payment doesn't exceed amount due
197
                         if ($paymentCents > $record->amount_due) {
227
                         if ($paymentCents > $record->amount_due) {
198
-                            Notification::make()
199
-                                ->title('Invalid payment amount')
200
-                                ->body('Payment cannot exceed amount due')
201
-                                ->warning()
202
-                                ->send();
203
-
204
-                            $maxAmount = CurrencyConverter::convertCentsToFormatSimple($record->amount_due, 'USD');
205
-                            $this->paymentAmounts[$record->id] = $record->amount_due;
206
-
207
-                            return $maxAmount;
228
+                            $paymentCents = $record->amount_due;
208
                         }
229
                         }
209
 
230
 
210
                         $this->paymentAmounts[$record->id] = $paymentCents;
231
                         $this->paymentAmounts[$record->id] = $paymentCents;
215
                         $paymentAmount = $this->paymentAmounts[$record->id] ?? 0;
236
                         $paymentAmount = $this->paymentAmounts[$record->id] ?? 0;
216
 
237
 
217
                         return CurrencyConverter::convertCentsToFormatSimple($paymentAmount, 'USD');
238
                         return CurrencyConverter::convertCentsToFormatSimple($paymentAmount, 'USD');
218
-                    }),
219
-            ])
220
-            ->actions([
221
-                Tables\Actions\Action::make('setFullAmount')
222
-                    ->label('Pay Full')
223
-                    ->icon('heroicon-o-banknotes')
224
-                    ->color('primary')
225
-                    ->action(function (Bill $record) {
226
-                        $this->paymentAmounts[$record->id] = $record->amount_due;
227
-                    }),
228
-                Tables\Actions\Action::make('clearAmount')
229
-                    ->label('Clear')
230
-                    ->icon('heroicon-o-x-mark')
231
-                    ->color('gray')
232
-                    ->action(function (Bill $record) {
233
-                        $this->paymentAmounts[$record->id] = 0;
234
-                    }),
239
+                    })
240
+                    ->summarize([
241
+                        Summarizer::make()
242
+                            ->using(function () {
243
+                                $total = array_sum($this->paymentAmounts);
244
+                                $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
245
+                                $activeCurrency = $this->getTableFilterState('currency_code')['value'] ?? $defaultCurrency;
246
+
247
+                                if ($activeCurrency !== $defaultCurrency) {
248
+                                    $total = CurrencyConverter::convertBalance($total, $activeCurrency, $defaultCurrency);
249
+                                }
250
+
251
+                                return CurrencyConverter::formatCentsToMoney($total, $defaultCurrency, true);
252
+                            }),
253
+                        Summarizer::make()
254
+                            ->using(fn () => $this->totalPaymentAmount)
255
+                            ->visible(function () {
256
+                                $activeCurrency = $this->getTableFilterState('currency_code')['value'] ?? null;
257
+                                $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
258
+
259
+                                return $activeCurrency && $activeCurrency !== $defaultCurrency;
260
+                            }),
261
+                    ]),
235
             ])
262
             ])
236
             ->bulkActions([
263
             ->bulkActions([
237
                 Tables\Actions\BulkAction::make('setFullAmounts')
264
                 Tables\Actions\BulkAction::make('setFullAmounts')
238
-                    ->label('Set Full Amounts')
265
+                    ->label('Set full amounts')
239
                     ->icon('heroicon-o-banknotes')
266
                     ->icon('heroicon-o-banknotes')
240
                     ->color('primary')
267
                     ->color('primary')
241
                     ->deselectRecordsAfterCompletion()
268
                     ->deselectRecordsAfterCompletion()
245
                         });
272
                         });
246
                     }),
273
                     }),
247
                 Tables\Actions\BulkAction::make('clearAmounts')
274
                 Tables\Actions\BulkAction::make('clearAmounts')
248
-                    ->label('Clear Amounts')
275
+                    ->label('Clear amounts')
249
                     ->icon('heroicon-o-x-mark')
276
                     ->icon('heroicon-o-x-mark')
250
                     ->color('gray')
277
                     ->color('gray')
251
                     ->deselectRecordsAfterCompletion()
278
                     ->deselectRecordsAfterCompletion()
256
                     }),
283
                     }),
257
             ])
284
             ])
258
             ->filters([
285
             ->filters([
259
-                Tables\Filters\SelectFilter::make('currency')
286
+                Tables\Filters\SelectFilter::make('currency_code')
287
+                    ->label('Currency')
260
                     ->selectablePlaceholder(false)
288
                     ->selectablePlaceholder(false)
261
                     ->default(CurrencyAccessor::getDefaultCurrency())
289
                     ->default(CurrencyAccessor::getDefaultCurrency())
262
-                    ->relationship('currency', 'name')
290
+                    ->options(Currency::query()->pluck('name', 'code')->toArray())
263
                     ->searchable()
291
                     ->searchable()
264
-                    ->preload(),
265
-                Tables\Filters\SelectFilter::make('vendor')
266
-                    ->relationship('vendor', 'name')
267
-                    ->searchable()
268
-                    ->preload(),
292
+                    ->resetState([
293
+                        'value' => CurrencyAccessor::getDefaultCurrency(),
294
+                    ])
295
+                    ->indicateUsing(function (Tables\Filters\SelectFilter $filter, array $state) {
296
+                        if (blank($state['value'] ?? null)) {
297
+                            return [];
298
+                        }
299
+
300
+                        $label = collect($filter->getOptions())
301
+                            ->mapWithKeys(fn (string | array $label, string $value): array => is_array($label) ? $label : [$value => $label])
302
+                            ->get($state['value']);
303
+
304
+                        if (blank($label)) {
305
+                            return [];
306
+                        }
307
+
308
+                        $indicator = $filter->getLabel();
309
+
310
+                        return Tables\Filters\Indicator::make("{$indicator}: {$label}")->removable(false);
311
+                    }),
312
+                Tables\Filters\SelectFilter::make('vendor_id')
313
+                    ->label('Vendor')
314
+                    ->options(fn () => Vendor::query()->pluck('name', 'id')->toArray())
315
+                    ->searchable(),
269
                 Tables\Filters\SelectFilter::make('status')
316
                 Tables\Filters\SelectFilter::make('status')
270
                     ->multiple()
317
                     ->multiple()
271
                     ->options(BillStatus::getUnpaidOptions()),
318
                     ->options(BillStatus::getUnpaidOptions()),
272
-            ], layout: FiltersLayout::AboveContent)
273
-            ->defaultSort('due_date')
274
-            ->striped()
275
-            ->paginated(false);
319
+            ]);
276
     }
320
     }
277
 
321
 
278
     protected function getPaymentAmount(Bill $record): int
322
     protected function getPaymentAmount(Bill $record): int
281
     }
325
     }
282
 
326
 
283
     #[Computed]
327
     #[Computed]
284
-    public function totalSelectedPaymentAmount(): string
328
+    public function totalPaymentAmount(): string
285
     {
329
     {
286
         $total = array_sum($this->paymentAmounts);
330
         $total = array_sum($this->paymentAmounts);
287
 
331
 
288
-        $currencyCode = $this->getTableFilterState('currency')['value'];
332
+        $currencyCode = $this->getTableFilterState('currency_code')['value'];
333
+
334
+        return CurrencyConverter::formatCentsToMoney($total, $currencyCode, true);
335
+    }
336
+
337
+    public function getSelectedBankAccount(): BankAccount
338
+    {
339
+        $bankAccountId = $this->data['bank_account_id'];
340
+
341
+        $bankAccount = BankAccount::find($bankAccountId);
342
+
343
+        return $bankAccount ?: BankAccount::where('enabled', true)->first();
344
+    }
345
+
346
+    protected function handleTableFilterUpdates(): void
347
+    {
348
+        parent::handleTableFilterUpdates();
349
+
350
+        $visibleBillIds = $this->getTableRecords()->pluck('id')->toArray();
351
+        $visibleBillKeys = array_flip($visibleBillIds);
289
 
352
 
290
-        return CurrencyConverter::formatCentsToMoney($total, $currencyCode);
353
+        $this->paymentAmounts = array_intersect_key($this->paymentAmounts, $visibleBillKeys);
291
     }
354
     }
292
 }
355
 }

+ 39
- 0
app/Filament/Tables/Columns/CustomTextInputColumn.php 查看文件

1
+<?php
2
+
3
+namespace App\Filament\Tables\Columns;
4
+
5
+use Closure;
6
+use Filament\Tables\Columns\TextInputColumn;
7
+
8
+class CustomTextInputColumn extends TextInputColumn
9
+{
10
+    protected string $view = 'filament.tables.columns.custom-text-input-column';
11
+
12
+    protected bool | Closure $isDeferred = false;
13
+
14
+    protected bool | Closure $isNavigable = false;
15
+
16
+    public function deferred(bool | Closure $condition = true): static
17
+    {
18
+        $this->isDeferred = $condition;
19
+
20
+        return $this;
21
+    }
22
+
23
+    public function navigable(bool | Closure $condition = true): static
24
+    {
25
+        $this->isNavigable = $condition;
26
+
27
+        return $this;
28
+    }
29
+
30
+    public function isDeferred(): bool
31
+    {
32
+        return (bool) $this->evaluate($this->isDeferred);
33
+    }
34
+
35
+    public function isNavigable(): bool
36
+    {
37
+        return (bool) $this->evaluate($this->isNavigable);
38
+    }
39
+}

+ 0
- 25
app/Filament/Tables/Columns/DeferredTextInputColumn.php 查看文件

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
-}

+ 2
- 2
resources/css/filament/company/theme.css 查看文件

66
     z-index: -1;
66
     z-index: -1;
67
 }
67
 }
68
 
68
 
69
-.fi-ta-table:has(.budget-items-relation-manager) {
69
+.fi-ta-table:has(.is-spreadsheet) {
70
     .fi-ta-row {
70
     .fi-ta-row {
71
         @apply divide-x divide-gray-200 dark:divide-gray-700;
71
         @apply divide-x divide-gray-200 dark:divide-gray-700;
72
     }
72
     }
102
         }
102
         }
103
     }
103
     }
104
 
104
 
105
-    .fi-ta-cell:focus-within {
105
+    .fi-ta-cell:has(.fi-ta-text-input):focus-within {
106
         outline: 2px solid #2563eb;
106
         outline: 2px solid #2563eb;
107
         outline-offset: -2px;
107
         outline-offset: -2px;
108
         z-index: 1;
108
         z-index: 1;

+ 18
- 7
resources/views/filament/company/resources/purchases/bill-resource/pages/pay-bills.blade.php 查看文件

1
-<x-filament-panels::page>
2
-    <div class="space-y-6">
3
-        <div class="bg-white rounded-lg border border-gray-200 p-6">
4
-            <div class="flex items-center justify-between mb-4">
1
+<x-filament-panels::page
2
+    @class([
3
+        'fi-resource-list-records-page',
4
+        'fi-resource-' . str_replace('/', '-', $this->getResource()::getSlug()),
5
+    ])
6
+>
7
+    <div class="flex flex-col gap-y-6">
8
+        <x-filament::section>
9
+            <div class="flex items-center justify-between">
5
                 <div>
10
                 <div>
6
                     {{ $this->form }}
11
                     {{ $this->form }}
7
                 </div>
12
                 </div>
8
                 <div class="text-right">
13
                 <div class="text-right">
9
-                    <div class="text-sm text-gray-500">Total Payment Amount</div>
10
-                    <div class="text-xl font-semibold text-gray-900" id="total-amount">{{ $this->totalSelectedPaymentAmount }}</div>
14
+                    <div class="text-sm uppercase text-gray-500 dark:text-gray-400">Total Payment Amount</div>
15
+                    <div class="text-3xl font-semibold text-gray-900 dark:text-white">{{ $this->totalPaymentAmount }}</div>
11
                 </div>
16
                 </div>
12
             </div>
17
             </div>
13
-        </div>
18
+        </x-filament::section>
19
+
20
+        <x-filament-panels::resources.tabs />
21
+
22
+        {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::RESOURCE_PAGES_LIST_RECORDS_TABLE_BEFORE, scopes: $this->getRenderHookScopes()) }}
14
 
23
 
15
         {{ $this->table }}
24
         {{ $this->table }}
25
+
26
+        {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::RESOURCE_PAGES_LIST_RECORDS_TABLE_AFTER, scopes: $this->getRenderHookScopes()) }}
16
     </div>
27
     </div>
17
 </x-filament-panels::page>
28
 </x-filament-panels::page>

resources/views/filament/tables/columns/deferred-text-input-column.blade.php → resources/views/filament/tables/columns/custom-text-input-column.blade.php 查看文件

4
     $isDisabled = $isDisabled();
4
     $isDisabled = $isDisabled();
5
     $state = $getState();
5
     $state = $getState();
6
     $mask = $getMask();
6
     $mask = $getMask();
7
-    $batchMode = $getBatchMode();
7
+    $isDeferred = $isDeferred();
8
+    $isNavigable = $isNavigable();
8
 
9
 
9
     $alignment = $getAlignment() ?? Alignment::Start;
10
     $alignment = $getAlignment() ?? Alignment::Start;
10
 
11
 
32
         recordKey: @js($recordKey),
33
         recordKey: @js($recordKey),
33
 
34
 
34
         state: @js($state),
35
         state: @js($state),
36
+
37
+        navigateToRow(direction) {
38
+            const currentRow = $el.closest('tr');
39
+            const currentCell = $el.closest('td');
40
+            const currentColumnIndex = Array.from(currentRow.children).indexOf(currentCell);
41
+
42
+            const targetRow = direction === 'next'
43
+                ? currentRow.nextElementSibling
44
+                : currentRow.previousElementSibling;
45
+
46
+            if (targetRow && targetRow.children[currentColumnIndex]) {
47
+                const targetInput = targetRow.children[currentColumnIndex].querySelector('input[x-model=\'state\']');
48
+                if (targetInput) {
49
+                    targetInput.focus();
50
+                    targetInput.select();
51
+                }
52
+            }
53
+        },
54
+
55
+        navigateToColumn(direction) {
56
+            const currentCell = $el.closest('td');
57
+            const currentRow = $el.closest('tr');
58
+            const currentColumnIndex = Array.from(currentRow.children).indexOf(currentCell);
59
+
60
+            const targetCell = direction === 'next'
61
+                ? currentRow.children[currentColumnIndex + 1]
62
+                : currentRow.children[currentColumnIndex - 1];
63
+
64
+            if (targetCell) {
65
+                const targetInput = targetCell.querySelector('input[x-model=\'state\']');
66
+                if (targetInput) {
67
+                    targetInput.focus();
68
+                    targetInput.select();
69
+                }
70
+            }
71
+        }
35
     }"
72
     }"
36
     x-init="
73
     x-init="
37
         () => {
74
         () => {
105
                 \Filament\Support\prepare_inherited_attributes(
142
                 \Filament\Support\prepare_inherited_attributes(
106
                     $getExtraInputAttributeBag()
143
                     $getExtraInputAttributeBag()
107
                         ->merge([
144
                         ->merge([
108
-                            'x-on:change' . ($type === 'number' ? '.debounce.1s' : null) => $batchMode ? '
145
+                            'x-on:change' . ($type === 'number' ? '.debounce.1s' : null) => $isDeferred ? '
109
                                 $wire.handleBatchColumnChanged({
146
                                 $wire.handleBatchColumnChanged({
110
                                     name: name,
147
                                     name: name,
111
                                     recordKey: recordKey,
148
                                     recordKey: recordKey,
128
 
165
 
129
                                 isLoading = false
166
                                 isLoading = false
130
                             ',
167
                             ',
131
-                            'x-on:keydown.enter' => $batchMode ? '
168
+                            'x-on:keydown.enter' => $isDeferred ? '
132
                                 $wire.handleBatchColumnChanged({
169
                                 $wire.handleBatchColumnChanged({
133
                                     name: name,
170
                                     name: name,
134
                                     recordKey: recordKey,
171
                                     recordKey: recordKey,
138
                                 $nextTick(() => {
175
                                 $nextTick(() => {
139
                                     $wire.saveBatchChanges();
176
                                     $wire.saveBatchChanges();
140
                                 });
177
                                 });
141
-                            ' : null,
178
+                            ' : ($isNavigable ? 'navigateToRow(\'next\')' : null),
179
+                            'x-on:keydown.arrow-down.prevent' => $isNavigable ? 'navigateToRow(\'next\')' : null,
180
+                            'x-on:keydown.arrow-up.prevent' => $isNavigable ? 'navigateToRow(\'prev\')' : null,
181
+                            'x-on:keydown.arrow-left.prevent' => $isNavigable ? 'navigateToColumn(\'prev\')' : null,
182
+                            'x-on:keydown.arrow-right.prevent' => $isNavigable ? 'navigateToColumn(\'next\')' : null,
142
                             'x-mask' . ($mask instanceof \Filament\Support\RawJs ? ':dynamic' : '') => filled($mask) ? $mask : null,
183
                             'x-mask' . ($mask instanceof \Filament\Support\RawJs ? ':dynamic' : '') => filled($mask) ? $mask : null,
143
                         ])
184
                         ])
144
                         ->class([
185
                         ->class([

Loading…
取消
儲存