Andrew Wallo 6 ay önce
ebeveyn
işleme
308abe6af5

+ 21
- 1
app/Filament/Company/Resources/Accounting/BudgetResource.php Dosyayı Görüntüle

@@ -9,6 +9,7 @@ use App\Filament\Forms\Components\CustomTableRepeater;
9 9
 use App\Models\Accounting\Account;
10 10
 use App\Models\Accounting\Budget;
11 11
 use App\Models\Accounting\BudgetItem;
12
+use App\Utilities\Currency\CurrencyConverter;
12 13
 use Awcodes\TableRepeater\Header;
13 14
 use Filament\Forms;
14 15
 use Filament\Forms\Form;
@@ -263,6 +264,11 @@ class BudgetResource extends Resource
263 264
                                     ->align(Alignment::Right);
264 265
                             }
265 266
 
267
+                            $headers[] = Header::make('total')
268
+                                ->label('Total')
269
+                                ->width('120px')
270
+                                ->align(Alignment::Right);
271
+
266 272
                             return [
267 273
                                 CustomTableRepeater::make('budgetItems')
268 274
                                     ->relationship()
@@ -279,7 +285,6 @@ class BudgetResource extends Resource
279 285
                                                 ->mask(RawJs::make('$money($input)'))
280 286
                                                 ->stripCharacters(',')
281 287
                                                 ->numeric()
282
-                                                ->extraInputAttributes(['class' => 'text-right'])
283 288
                                                 ->afterStateHydrated(function ($component, $state, BudgetItem $record) use ($period) {
284 289
                                                     // Find the allocation for this period
285 290
                                                     $allocation = $record->allocations->firstWhere('period', $period);
@@ -287,6 +292,20 @@ class BudgetResource extends Resource
287 292
                                                 })
288 293
                                                 ->dehydrated(false); // We'll handle saving manually
289 294
                                         })->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
+                                            }),
290 309
                                     ])
291 310
                                     ->spreadsheet()
292 311
                                     ->itemLabel(fn (BudgetItem $record) => $record->account->name ?? 'Budget Item')
@@ -485,6 +504,7 @@ class BudgetResource extends Resource
485 504
             'create' => Pages\CreateBudget::route('/create'),
486 505
             'view' => Pages\ViewBudget::route('/{record}'),
487 506
             'edit' => Pages\EditBudget::route('/{record}/edit'),
507
+            'allocations' => Pages\ManageAllocations::route('/{record}/allocations'),
488 508
         ];
489 509
     }
490 510
 }

+ 1
- 1
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/CreateBudget.php Dosyayı Görüntüle

@@ -616,7 +616,7 @@ class CreateBudget extends CreateRecord
616 616
     private function determinePeriod(Carbon $date, BudgetIntervalType $intervalType): string
617 617
     {
618 618
         return match ($intervalType) {
619
-            BudgetIntervalType::Month => $date->format('F Y'),
619
+            BudgetIntervalType::Month => $date->format('M Y'),
620 620
             BudgetIntervalType::Quarter => 'Q' . $date->quarter . ' ' . $date->year,
621 621
             BudgetIntervalType::Year => (string) $date->year,
622 622
             default => $date->format('Y-m-d'),

+ 73
- 0
app/Filament/Company/Resources/Accounting/BudgetResource/Pages/ManageAllocations.php Dosyayı Görüntüle

@@ -0,0 +1,73 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Accounting\BudgetResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Accounting\BudgetResource;
6
+use Filament\Forms;
7
+use Filament\Forms\Form;
8
+use Filament\Resources\Pages\ManageRelatedRecords;
9
+use Filament\Tables;
10
+use Filament\Tables\Table;
11
+
12
+class ManageAllocations extends ManageRelatedRecords
13
+{
14
+    protected static string $resource = BudgetResource::class;
15
+
16
+    protected static string $relationship = 'allocations';
17
+
18
+    protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
19
+
20
+    public static function getNavigationLabel(): string
21
+    {
22
+        return 'Allocations';
23
+    }
24
+
25
+    public function form(Form $form): Form
26
+    {
27
+        return $form
28
+            ->schema([
29
+                Forms\Components\TextInput::make('account_id')
30
+                    ->required()
31
+                    ->maxLength(255),
32
+            ]);
33
+    }
34
+
35
+    public function table(Table $table): Table
36
+    {
37
+        return $table
38
+            ->recordTitleAttribute('account_id')
39
+            ->columns([
40
+                Tables\Columns\TextColumn::make('budgetItem.account.name')
41
+                    ->label('Account')
42
+                    ->sortable()
43
+                    ->searchable(),
44
+
45
+                Tables\Columns\TextColumn::make('period')
46
+                    ->label('Period')
47
+                    ->sortable(),
48
+
49
+                Tables\Columns\TextColumn::make('amount')
50
+                    ->label('Amount')
51
+                    ->money()
52
+                    ->sortable(),
53
+            ])
54
+            ->filters([
55
+                //
56
+            ])
57
+            ->headerActions([
58
+                Tables\Actions\CreateAction::make(),
59
+                Tables\Actions\AssociateAction::make(),
60
+            ])
61
+            ->actions([
62
+                Tables\Actions\EditAction::make(),
63
+                Tables\Actions\DissociateAction::make(),
64
+                Tables\Actions\DeleteAction::make(),
65
+            ])
66
+            ->bulkActions([
67
+                Tables\Actions\BulkActionGroup::make([
68
+                    Tables\Actions\DissociateBulkAction::make(),
69
+                    Tables\Actions\DeleteBulkAction::make(),
70
+                ]),
71
+            ]);
72
+    }
73
+}

+ 34
- 65
app/Filament/Company/Resources/Accounting/BudgetResource/RelationManagers/BudgetItemsRelationManager.php Dosyayı Görüntüle

@@ -2,11 +2,10 @@
2 2
 
3 3
 namespace App\Filament\Company\Resources\Accounting\BudgetResource\RelationManagers;
4 4
 
5
-use App\Models\Accounting\BudgetAllocation;
6
-use Filament\Forms\Components\Grid;
7
-use Filament\Forms\Components\TextInput;
5
+use App\Models\Accounting\BudgetItem;
8 6
 use Filament\Resources\RelationManagers\RelationManager;
9
-use Filament\Tables;
7
+use Filament\Tables\Columns\TextColumn;
8
+use Filament\Tables\Columns\TextInputColumn;
10 9
 use Filament\Tables\Table;
11 10
 
12 11
 class BudgetItemsRelationManager extends RelationManager
@@ -17,72 +16,42 @@ class BudgetItemsRelationManager extends RelationManager
17 16
 
18 17
     public function table(Table $table): Table
19 18
     {
19
+        $budget = $this->getOwnerRecord();
20
+
21
+        // Get distinct periods for this budget
22
+        $periods = \App\Models\Accounting\BudgetAllocation::query()
23
+            ->join('budget_items', 'budget_allocations.budget_item_id', '=', 'budget_items.id')
24
+            ->where('budget_items.budget_id', $budget->id)
25
+            ->orderBy('start_date')
26
+            ->pluck('period')
27
+            ->unique()
28
+            ->values()
29
+            ->toArray();
30
+
20 31
         return $table
21 32
             ->recordTitleAttribute('account_id')
22
-            ->columns([
23
-                Tables\Columns\TextColumn::make('account.name')
24
-                    ->label('Account')
33
+            ->paginated(false)
34
+            ->modifyQueryUsing(
35
+                fn ($query) => $query->with(['account', 'allocations'])
36
+            )
37
+            ->columns(array_merge([
38
+                TextColumn::make('account.name')
39
+                    ->label('Accounts')
25 40
                     ->sortable()
26 41
                     ->searchable(),
27
-
28
-                Tables\Columns\TextColumn::make('allocations_sum_amount')
29
-                    ->label('Total Allocations')
30
-                    ->sortable()
31
-                    ->alignEnd()
32
-                    ->sum('allocations', 'amount')
33
-                    ->money(divideBy: 100),
34
-            ])
35
-            ->filters([
36
-                //
37
-            ])
38
-            ->headerActions([
39
-                // Tables\Actions\CreateAction::make(),
40
-            ])
41
-            ->actions([
42
-                Tables\Actions\Action::make('editAllocations')
43
-                    ->label('Edit Allocations')
44
-                    ->icon('heroicon-o-pencil')
45
-                    ->modalHeading(fn ($record) => "Edit Allocations for {$record->account->name}")
46
-                    ->modalWidth('xl')
47
-                    ->form(function ($record) {
48
-                        $fields = [];
49
-
50
-                        // Get allocations ordered by date
51
-                        $allocations = $record->allocations()->orderBy('start_date')->get();
52
-
53
-                        foreach ($allocations as $allocation) {
54
-                            $fields[] = TextInput::make("allocations.{$allocation->id}")
55
-                                ->label($allocation->period)
56
-                                ->numeric()
57
-                                ->default(function () use ($allocation) {
58
-                                    return $allocation->amount;
59
-                                })
60
-                                ->prefix('$')
61
-                                ->live(debounce: 500)
62
-                                ->afterStateUpdated(function (TextInput $component, $state) {
63
-                                    // Format the value as needed
64
-                                    $component->state(number_format($state, 2, '.', ''));
65
-                                });
42
+            ], collect($periods)->map(
43
+                fn ($period) => TextInputColumn::make("allocations_by_period.{$period}")
44
+                    ->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]);
66 53
                         }
67
-
68
-                        return [
69
-                            Grid::make()
70
-                                ->schema($fields)
71
-                                ->columns(3),
72
-                        ];
73 54
                     })
74
-                    ->action(function (array $data, $record) {
75
-                        foreach ($data['allocations'] as $allocationId => $amount) {
76
-                            BudgetAllocation::find($allocationId)->update([
77
-                                'amount' => $amount,
78
-                            ]);
79
-                        }
80
-                    }),
81
-            ])
82
-            ->bulkActions([
83
-                //                Tables\Actions\BulkActionGroup::make([
84
-                //                    Tables\Actions\DeleteBulkAction::make(),
85
-                //                ]),
86
-            ]);
55
+            )->all()));
87 56
     }
88 57
 }

+ 2
- 2
app/Models/Accounting/BudgetAllocation.php Dosyayı Görüntüle

@@ -2,7 +2,7 @@
2 2
 
3 3
 namespace App\Models\Accounting;
4 4
 
5
-use App\Casts\DocumentMoneyCast;
5
+use App\Casts\MoneyCast;
6 6
 use App\Concerns\CompanyOwned;
7 7
 use App\Enums\Accounting\BudgetIntervalType;
8 8
 use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -28,7 +28,7 @@ class BudgetAllocation extends Model
28 28
         'start_date' => 'date',
29 29
         'end_date' => 'date',
30 30
         'interval_type' => BudgetIntervalType::class,
31
-        'amount' => DocumentMoneyCast::class,
31
+        'amount' => MoneyCast::class,
32 32
     ];
33 33
 
34 34
     public function budgetItem(): BelongsTo

+ 7
- 0
app/Models/Accounting/BudgetItem.php Dosyayı Görüntüle

@@ -23,6 +23,13 @@ class BudgetItem extends Model
23 23
         'updated_by',
24 24
     ];
25 25
 
26
+    protected $appends = ['allocations_by_period'];
27
+
28
+    public function getAllocationsByPeriodAttribute(): array
29
+    {
30
+        return $this->allocations->pluck('amount', 'period')->toArray();
31
+    }
32
+
26 33
     public function budget(): BelongsTo
27 34
     {
28 35
         return $this->belongsTo(Budget::class);

+ 79
- 78
resources/css/filament/company/form-fields.css Dosyayı Görüntüle

@@ -53,7 +53,7 @@
53 53
     cursor: pointer;
54 54
 }
55 55
 
56
-/* Base horizontal scrolling for all TableRepeater components */
56
+/* Base horizontal scrolling */
57 57
 .table-repeater-container {
58 58
     overflow-x: auto;
59 59
     max-width: 100%;
@@ -70,100 +70,101 @@
70 70
 }
71 71
 
72 72
 /* Excel/Spreadsheet styling */
73
-.is-spreadsheet .table-repeater-container {
74
-    border-radius: 0 !important;
75
-    border: 1px solid #e5e7eb !important;
76
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
77
-    @apply ring-0 !important;
78
-}
73
+.is-spreadsheet {
74
+    /* Container styles */
75
+
76
+    .table-repeater-container {
77
+        border: 1px solid #e5e7eb !important;
78
+        border-radius: 0 !important;
79
+        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
80
+        @apply ring-0 !important;
81
+    }
79 82
 
80
-.is-spreadsheet .table-repeater-header {
81
-    background-color: #f8f9fa !important;
82
-    border-radius: 0 !important;
83
-}
83
+    /* Header styles */
84 84
 
85
-/* Fix for input alignment with headers */
86
-.is-spreadsheet .table-repeater-header-column,
87
-.is-spreadsheet .table-repeater-column {
88
-    padding: 6px 8px !important;
89
-}
85
+    .table-repeater-header {
86
+        background-color: #f8f9fa !important;
87
+        border-radius: 0 !important;
88
+    }
90 89
 
91
-/* Right-align all inputs */
92
-.is-spreadsheet .table-repeater-column input {
93
-    text-align: right !important;
94
-    width: 100% !important;
95
-}
90
+    .table-repeater-header-column {
91
+        border: 1px solid #e5e7eb !important;
92
+        background-color: #f8f9fa !important;
93
+        border-radius: 0 !important;
94
+        font-weight: 600 !important;
95
+        padding: 8px 12px !important;
96
+    }
96 97
 
97
-/* Remove any additional padding from input containers */
98
-.is-spreadsheet .fi-input-wrapper,
99
-.is-spreadsheet .fi-input {
100
-    padding: 0 !important;
101
-    width: 100% !important;
102
-}
98
+    /* Cell styles */
103 99
 
104
-.is-spreadsheet .table-repeater-header-column {
105
-    border-radius: 0 !important;
106
-    border: 1px solid #e5e7eb !important;
107
-    background-color: #f8f9fa !important;
108
-    font-weight: 600 !important;
109
-    padding: 6px 8px !important;
110
-}
100
+    .table-repeater-column {
101
+        border: 1px solid #e5e7eb !important;
102
+        padding: 8px 12px !important;
103
+    }
111 104
 
112
-.is-spreadsheet .table-repeater-column {
113
-    border: 1px solid #e5e7eb !important;
114
-    padding: 4px 8px !important;
115
-    position: relative !important;
116
-}
105
+    /* Input styles */
117 106
 
118
-/* Incorporate flat input styling from streamlined */
119
-.is-spreadsheet .fi-input-wrp,
120
-.is-spreadsheet .fi-fo-file-upload .filepond--root {
121
-    @apply ring-0 bg-transparent shadow-none !important;
122
-}
107
+    .table-repeater-column input {
108
+        text-align: right !important;
109
+        width: 100% !important;
110
+    }
123 111
 
124
-.is-spreadsheet .fi-input-wrp input {
125
-    @apply bg-transparent border-0 shadow-none !important;
126
-}
112
+    /* Flatten inputs */
127 113
 
128
-.is-spreadsheet .fi-select-trigger {
129
-    @apply ring-0 bg-transparent shadow-none !important;
130
-}
114
+    .fi-input-wrapper,
115
+    .fi-input {
116
+        padding: 0 !important;
117
+        width: 100% !important;
118
+    }
131 119
 
132
-/* Excel-like row styles */
133
-.is-spreadsheet .table-repeater-row:nth-child(even) {
134
-    background-color: #f9fafb;
135
-}
120
+    .fi-input-wrp,
121
+    .fi-fo-file-upload .filepond--root {
122
+        @apply ring-0 bg-transparent shadow-none !important;
123
+    }
136 124
 
137
-/* Excel-like focus/selection styles */
138
-.is-spreadsheet .table-repeater-column:focus-within {
139
-    outline: 2px solid #2563eb !important;
140
-    outline-offset: -2px !important;
141
-    z-index: 1 !important;
142
-}
125
+    .fi-input-wrp input {
126
+        @apply bg-transparent border-0 shadow-none !important;
127
+    }
143 128
 
144
-.is-spreadsheet .fi-input-wrp:focus-within {
145
-    @apply ring-0 shadow-none !important;
146
-}
129
+    /* Focus states */
147 130
 
148
-.is-spreadsheet input:focus,
149
-.is-spreadsheet select:focus,
150
-.is-spreadsheet textarea:focus {
151
-    @apply ring-0 shadow-none !important;
152
-    outline: none !important;
153
-}
131
+    .table-repeater-column:focus-within {
132
+        outline: 2px solid #2563eb !important;
133
+        outline-offset: -2px !important;
134
+        z-index: 1 !important;
135
+    }
154 136
 
155
-/* Spacing for form controls */
156
-.is-spreadsheet .fi-fo-field-wrp:has(.fi-fo-checkbox-list),
157
-.is-spreadsheet .fi-fo-field-wrp:has(.fi-checkbox-input),
158
-.is-spreadsheet .fi-fo-field-wrp:has(.fi-fo-radio) {
159
-    @apply py-2 px-3 !important;
160
-}
137
+    .fi-input-wrp:focus-within {
138
+        @apply ring-0 shadow-none !important;
139
+    }
161 140
 
162
-.is-spreadsheet .fi-fo-field-wrp:has(.fi-fo-toggle) {
163
-    @apply inline-block mt-1 !important;
141
+    input:focus,
142
+    select:focus,
143
+    textarea:focus {
144
+        @apply ring-0 shadow-none !important;
145
+        outline: none !important;
146
+    }
147
+
148
+    /* Row styling */
149
+
150
+    .table-repeater-row:nth-child(even) {
151
+        background-color: #f9fafb;
152
+    }
153
+
154
+    /* Form control spacing */
155
+
156
+    .fi-fo-field-wrp:has(.fi-fo-checkbox-list),
157
+    .fi-fo-field-wrp:has(.fi-checkbox-input),
158
+    .fi-fo-field-wrp:has(.fi-fo-radio) {
159
+        @apply py-2 px-3 !important;
160
+    }
161
+
162
+    .fi-fo-field-wrp:has(.fi-fo-toggle) {
163
+        @apply inline-block mt-1 !important;
164
+    }
164 165
 }
165 166
 
166
-/* Preserve responsive behavior */
167
+/* Responsive behavior */
167 168
 @media (max-width: theme('screens.sm')) {
168 169
     .table-repeater-component.break-point-sm .table-repeater-container {
169 170
         overflow-x: visible;

Loading…
İptal
Kaydet