Andrew Wallo преди 10 месеца
родител
ревизия
f55b828bdf

+ 19
- 0
app/Enums/Common/ContractorType.php Целия файл

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Enums\Common;
4
+
5
+use App\Enums\Concerns\ParsesEnum;
6
+use Filament\Support\Contracts\HasLabel;
7
+
8
+enum ContractorType: string implements HasLabel
9
+{
10
+    use ParsesEnum;
11
+
12
+    case Individual = 'individual';
13
+    case Business = 'business';
14
+
15
+    public function getLabel(): ?string
16
+    {
17
+        return $this->name;
18
+    }
19
+}

+ 31
- 0
app/Enums/Common/VendorType.php Целия файл

@@ -0,0 +1,31 @@
1
+<?php
2
+
3
+namespace App\Enums\Common;
4
+
5
+use App\Enums\Concerns\ParsesEnum;
6
+use Filament\Support\Contracts\HasDescription;
7
+use Filament\Support\Contracts\HasLabel;
8
+
9
+enum VendorType: string implements HasDescription, HasLabel
10
+{
11
+    use ParsesEnum;
12
+
13
+    case Regular = 'regular';
14
+    case Contractor = 'contractor';
15
+
16
+    public function getLabel(): ?string
17
+    {
18
+        return match ($this) {
19
+            self::Regular => 'Regular',
20
+            self::Contractor => '1099-NEC Contractor',
21
+        };
22
+    }
23
+
24
+    public function getDescription(): ?string
25
+    {
26
+        return match ($this) {
27
+            self::Regular => 'Vendors who supply goods or services to your business, such as office supplies, utilities, or equipment.',
28
+            self::Contractor => 'Independent contractors providing services to your business, typically requiring 1099-NEC reporting for tax purposes.',
29
+        };
30
+    }
31
+}

+ 5
- 1
app/Enums/Concerns/ParsesEnum.php Целия файл

@@ -4,8 +4,12 @@ namespace App\Enums\Concerns;
4 4
 
5 5
 trait ParsesEnum
6 6
 {
7
-    public static function parse(string | self $value): self
7
+    public static function parse(string | self | null $value): ?self
8 8
     {
9
+        if ($value === null) {
10
+            return null;
11
+        }
12
+
9 13
         if ($value instanceof self) {
10 14
             return $value;
11 15
         }

+ 215
- 181
app/Filament/Company/Resources/Common/ClientResource.php Целия файл

@@ -4,6 +4,7 @@ namespace App\Filament\Company\Resources\Common;
4 4
 
5 5
 use App\Filament\Company\Resources\Common\ClientResource\Pages;
6 6
 use App\Filament\Forms\Components\CreateCurrencySelect;
7
+use App\Filament\Forms\Components\CustomSection;
7 8
 use App\Filament\Forms\Components\PhoneBuilder;
8 9
 use App\Models\Common\Client;
9 10
 use Filament\Forms;
@@ -22,193 +23,228 @@ class ClientResource extends Resource
22 23
             ->schema([
23 24
                 Forms\Components\Section::make('General Information')
24 25
                     ->schema([
25
-                        Forms\Components\TextInput::make('name')
26
-                            ->label('Client Name')
27
-                            ->required()
28
-                            ->maxLength(255),
29
-                        CreateCurrencySelect::make('currency_code')
30
-                            ->disabledOn('edit')
31
-                            ->relationship('currency', 'name'),
32
-                        Forms\Components\TextInput::make('account_number')
33
-                            ->maxLength(255),
34
-                        Forms\Components\TextInput::make('website')
35
-                            ->maxLength(255),
36
-                        Forms\Components\Textarea::make('notes')
37
-                            ->columnSpanFull(),
38
-                    ])->columns(),
39
-                Forms\Components\Section::make('Primary Contact')
40
-                    ->relationship('primaryContact')
41
-                    ->schema([
42
-                        Forms\Components\Hidden::make('is_primary')
43
-                            ->default(true),
44
-                        Forms\Components\TextInput::make('first_name')
45
-                            ->label('First Name')
46
-                            ->required()
47
-                            ->maxLength(255),
48
-                        Forms\Components\TextInput::make('last_name')
49
-                            ->label('Last Name')
50
-                            ->required()
51
-                            ->maxLength(255),
52
-                        Forms\Components\TextInput::make('email')
53
-                            ->label('Email')
54
-                            ->required()
55
-                            ->email()
56
-                            ->maxLength(255),
57
-                        PhoneBuilder::make('phones')
26
+                        Forms\Components\Group::make()
27
+                            ->columns()
28
+                            ->schema([
29
+                                Forms\Components\TextInput::make('name')
30
+                                    ->label('Client Name')
31
+                                    ->required()
32
+                                    ->maxLength(255),
33
+                                Forms\Components\TextInput::make('account_number')
34
+                                    ->maxLength(255)
35
+                                    ->columnStart(1),
36
+                                Forms\Components\TextInput::make('website')
37
+                                    ->maxLength(255),
38
+                                Forms\Components\Textarea::make('notes')
39
+                                    ->columnSpanFull(),
40
+                            ]),
41
+                        CustomSection::make('Primary Contact')
42
+                            ->relationship('primaryContact')
43
+                            ->contained(false)
44
+                            ->schema([
45
+                                Forms\Components\Hidden::make('is_primary')
46
+                                    ->default(true),
47
+                                Forms\Components\TextInput::make('first_name')
48
+                                    ->label('First Name')
49
+                                    ->required()
50
+                                    ->maxLength(255),
51
+                                Forms\Components\TextInput::make('last_name')
52
+                                    ->label('Last Name')
53
+                                    ->required()
54
+                                    ->maxLength(255),
55
+                                Forms\Components\TextInput::make('email')
56
+                                    ->label('Email')
57
+                                    ->required()
58
+                                    ->email()
59
+                                    ->columnSpanFull()
60
+                                    ->maxLength(255),
61
+                                PhoneBuilder::make('phones')
62
+                                    ->hiddenLabel()
63
+                                    ->blockLabels(false)
64
+                                    ->default([
65
+                                        ['type' => 'primary'],
66
+                                    ])
67
+                                    ->columnSpanFull()
68
+                                    ->blocks([
69
+                                        Forms\Components\Builder\Block::make('primary')
70
+                                            ->schema([
71
+                                                Forms\Components\TextInput::make('number')
72
+                                                    ->label('Phone')
73
+                                                    ->required()
74
+                                                    ->maxLength(15),
75
+                                            ])->maxItems(1),
76
+                                        Forms\Components\Builder\Block::make('mobile')
77
+                                            ->schema([
78
+                                                Forms\Components\TextInput::make('number')
79
+                                                    ->label('Mobile')
80
+                                                    ->required()
81
+                                                    ->maxLength(15),
82
+                                            ])->maxItems(1),
83
+                                        Forms\Components\Builder\Block::make('toll_free')
84
+                                            ->schema([
85
+                                                Forms\Components\TextInput::make('number')
86
+                                                    ->label('Toll Free')
87
+                                                    ->required()
88
+                                                    ->maxLength(15),
89
+                                            ])->maxItems(1),
90
+                                        Forms\Components\Builder\Block::make('fax')
91
+                                            ->schema([
92
+                                                Forms\Components\TextInput::make('number')
93
+                                                    ->label('Fax')
94
+                                                    ->live()
95
+                                                    ->maxLength(15),
96
+                                            ])->maxItems(1),
97
+                                    ])
98
+                                    ->deletable(fn (PhoneBuilder $builder) => $builder->getItemsCount() > 1)
99
+                                    ->reorderable(false)
100
+                                    ->blockNumbers(false)
101
+                                    ->addActionLabel('Add Phone'),
102
+                            ])->columns(),
103
+                        Forms\Components\Repeater::make('secondaryContacts')
104
+                            ->relationship()
58 105
                             ->hiddenLabel()
59
-                            ->blockLabels(false)
60
-                            ->default([
61
-                                ['type' => 'office'],
106
+                            ->extraAttributes([
107
+                                'class' => 'uncontained',
62 108
                             ])
63
-                            ->blocks([
64
-                                Forms\Components\Builder\Block::make('office')
65
-                                    ->label('Office')
66
-                                    ->schema([
67
-                                        Forms\Components\TextInput::make('number')
68
-                                            ->label('Office')
69
-                                            ->required()
70
-                                            ->maxLength(15),
71
-                                    ])->maxItems(1),
72
-                                Forms\Components\Builder\Block::make('mobile')
73
-                                    ->label('Mobile')
74
-                                    ->schema([
75
-                                        Forms\Components\TextInput::make('number')
76
-                                            ->label('Mobile')
77
-                                            ->required()
78
-                                            ->maxLength(15),
79
-                                    ])->maxItems(1),
80
-                                Forms\Components\Builder\Block::make('toll_free')
81
-                                    ->label('Toll Free')
82
-                                    ->schema([
83
-                                        Forms\Components\TextInput::make('number')
84
-                                            ->label('Toll Free')
85
-                                            ->required()
86
-                                            ->maxLength(15),
87
-                                    ])->maxItems(1),
88
-                                Forms\Components\Builder\Block::make('fax')
89
-                                    ->label('Fax')
90
-                                    ->schema([
91
-                                        Forms\Components\TextInput::make('number')
92
-                                            ->label('Fax')
93
-                                            ->live()
94
-                                            ->maxLength(15),
95
-                                    ])->maxItems(1),
96
-                            ])
97
-                            ->deletable(fn (PhoneBuilder $builder) => $builder->getItemsCount() > 1)
98
-                            ->reorderable(false)
99
-                            ->blockNumbers(false)
100
-                            ->addActionLabel('Add Phone'),
101
-                    ]),
102
-                Forms\Components\Repeater::make('secondaryContacts')
103
-                    ->relationship()
104
-                    ->columnSpanFull()
105
-                    ->hiddenLabel()
106
-                    ->defaultItems(0)
107
-                    ->addActionLabel('Add Contact')
108
-                    ->schema([
109
-                        Forms\Components\TextInput::make('first_name')
110
-                            ->label('First Name')
111
-                            ->required()
112
-                            ->maxLength(255),
113
-                        Forms\Components\TextInput::make('last_name')
114
-                            ->label('Last Name')
115
-                            ->required()
116
-                            ->maxLength(255),
117
-                        Forms\Components\TextInput::make('email')
118
-                            ->label('Email')
119
-                            ->required()
120
-                            ->email()
121
-                            ->maxLength(255),
122
-                        PhoneBuilder::make('phones')
123
-                            ->hiddenLabel()
124
-                            ->blockLabels(false)
125
-                            ->default([
126
-                                ['type' => 'office'],
127
-                            ])
128
-                            ->blocks([
129
-                                Forms\Components\Builder\Block::make('office')
130
-                                    ->label('Office')
131
-                                    ->schema([
132
-                                        Forms\Components\TextInput::make('number')
133
-                                            ->label('Office')
134
-                                            ->required()
135
-                                            ->maxLength(255),
136
-                                    ])->maxItems(1),
137
-                            ])
138
-                            ->addable(false)
139
-                            ->deletable(false)
140
-                            ->reorderable(false)
141
-                            ->blockNumbers(false),
142
-                    ]),
109
+                            ->columns()
110
+                            ->defaultItems(0)
111
+                            ->maxItems(3)
112
+                            ->itemLabel(function (Forms\Components\Repeater $component, array $state): ?string {
113
+                                if ($component->getItemsCount() === 1) {
114
+                                    return 'Secondary Contact';
115
+                                }
116
+
117
+                                $firstName = $state['first_name'] ?? null;
118
+                                $lastName = $state['last_name'] ?? null;
119
+
120
+                                if ($firstName && $lastName) {
121
+                                    return "{$firstName} {$lastName}";
122
+                                }
123
+
124
+                                if ($firstName) {
125
+                                    return $firstName;
126
+                                }
127
+
128
+                                return 'Secondary Contact';
129
+                            })
130
+                            ->addActionLabel('Add Contact')
131
+                            ->schema([
132
+                                Forms\Components\TextInput::make('first_name')
133
+                                    ->label('First Name')
134
+                                    ->required()
135
+                                    ->live(onBlur: true)
136
+                                    ->maxLength(255),
137
+                                Forms\Components\TextInput::make('last_name')
138
+                                    ->label('Last Name')
139
+                                    ->required()
140
+                                    ->live(onBlur: true)
141
+                                    ->maxLength(255),
142
+                                Forms\Components\TextInput::make('email')
143
+                                    ->label('Email')
144
+                                    ->required()
145
+                                    ->email()
146
+                                    ->maxLength(255),
147
+                                PhoneBuilder::make('phones')
148
+                                    ->hiddenLabel()
149
+                                    ->blockLabels(false)
150
+                                    ->default([
151
+                                        ['type' => 'primary'],
152
+                                    ])
153
+                                    ->blocks([
154
+                                        Forms\Components\Builder\Block::make('primary')
155
+                                            ->schema([
156
+                                                Forms\Components\TextInput::make('number')
157
+                                                    ->label('Phone')
158
+                                                    ->required()
159
+                                                    ->maxLength(255),
160
+                                            ])->maxItems(1),
161
+                                    ])
162
+                                    ->addable(false)
163
+                                    ->deletable(false)
164
+                                    ->reorderable(false)
165
+                                    ->blockNumbers(false),
166
+                            ]),
167
+                    ])->columns(1),
143 168
                 Forms\Components\Section::make('Billing')
144
-                    ->relationship('billingAddress')
145 169
                     ->schema([
146
-                        Forms\Components\Hidden::make('type')
147
-                            ->default('billing'),
148
-                        Forms\Components\TextInput::make('address_line_1')
149
-                            ->label('Address Line 1')
150
-                            ->required()
151
-                            ->maxLength(255),
152
-                        Forms\Components\TextInput::make('address_line_2')
153
-                            ->label('Address Line 2')
154
-                            ->maxLength(255),
155
-                        Forms\Components\TextInput::make('city')
156
-                            ->label('City')
157
-                            ->required()
158
-                            ->maxLength(255),
159
-                        Forms\Components\TextInput::make('state')
160
-                            ->label('State')
161
-                            ->required()
162
-                            ->maxLength(255),
163
-                        Forms\Components\TextInput::make('postal_code')
164
-                            ->label('Postal Code / Zip Code')
165
-                            ->required()
166
-                            ->maxLength(255),
167
-                        Forms\Components\TextInput::make('country')
168
-                            ->label('Country')
169
-                            ->required()
170
-                            ->maxLength(255),
171
-                    ])->columns(),
170
+                        CreateCurrencySelect::make('currency_code')
171
+                            ->relationship('currency', 'name'),
172
+                        CustomSection::make('Billing Address')
173
+                            ->relationship('billingAddress')
174
+                            ->contained(false)
175
+                            ->schema([
176
+                                Forms\Components\Hidden::make('type')
177
+                                    ->default('billing'),
178
+                                Forms\Components\TextInput::make('address_line_1')
179
+                                    ->label('Address Line 1')
180
+                                    ->required()
181
+                                    ->maxLength(255),
182
+                                Forms\Components\TextInput::make('address_line_2')
183
+                                    ->label('Address Line 2')
184
+                                    ->maxLength(255),
185
+                                Forms\Components\TextInput::make('city')
186
+                                    ->label('City')
187
+                                    ->required()
188
+                                    ->maxLength(255),
189
+                                Forms\Components\TextInput::make('state')
190
+                                    ->label('State')
191
+                                    ->required()
192
+                                    ->maxLength(255),
193
+                                Forms\Components\TextInput::make('postal_code')
194
+                                    ->label('Postal Code / Zip Code')
195
+                                    ->required()
196
+                                    ->maxLength(255),
197
+                                Forms\Components\TextInput::make('country')
198
+                                    ->label('Country')
199
+                                    ->required()
200
+                                    ->maxLength(255),
201
+                            ])->columns(),
202
+                    ])
203
+                    ->columns(1),
172 204
                 Forms\Components\Section::make('Shipping')
173 205
                     ->relationship('shippingAddress')
174 206
                     ->schema([
175
-                        Forms\Components\Hidden::make('type')
176
-                            ->default('shipping'),
177 207
                         Forms\Components\TextInput::make('recipient')
178 208
                             ->label('Recipient')
179 209
                             ->required()
180 210
                             ->maxLength(255),
181
-                        Forms\Components\TextInput::make('address_line_1')
182
-                            ->label('Address Line 1')
183
-                            ->required()
184
-                            ->maxLength(255),
185
-                        Forms\Components\TextInput::make('address_line_2')
186
-                            ->label('Address Line 2')
187
-                            ->maxLength(255),
188
-                        Forms\Components\TextInput::make('city')
189
-                            ->label('City')
190
-                            ->required()
191
-                            ->maxLength(255),
192
-                        Forms\Components\TextInput::make('state')
193
-                            ->label('State')
194
-                            ->required()
195
-                            ->maxLength(255),
196
-                        Forms\Components\TextInput::make('postal_code')
197
-                            ->label('Postal Code / Zip Code')
198
-                            ->required()
199
-                            ->maxLength(255),
200
-                        Forms\Components\TextInput::make('country')
201
-                            ->label('Country')
202
-                            ->required()
203
-                            ->maxLength(255),
211
+                        Forms\Components\Hidden::make('type')
212
+                            ->default('shipping'),
204 213
                         Forms\Components\TextInput::make('phone')
205 214
                             ->label('Phone')
206 215
                             ->required()
207 216
                             ->maxLength(255),
208
-                        Forms\Components\Textarea::make('notes')
209
-                            ->label('Delivery Instructions')
210
-                            ->maxLength(255)
211
-                            ->columnSpanFull(),
217
+                        CustomSection::make('Shipping Address')
218
+                            ->contained(false)
219
+                            ->schema([
220
+                                Forms\Components\TextInput::make('address_line_1')
221
+                                    ->label('Address Line 1')
222
+                                    ->required()
223
+                                    ->maxLength(255),
224
+                                Forms\Components\TextInput::make('address_line_2')
225
+                                    ->label('Address Line 2')
226
+                                    ->maxLength(255),
227
+                                Forms\Components\TextInput::make('city')
228
+                                    ->label('City')
229
+                                    ->required()
230
+                                    ->maxLength(255),
231
+                                Forms\Components\TextInput::make('state')
232
+                                    ->label('State')
233
+                                    ->required()
234
+                                    ->maxLength(255),
235
+                                Forms\Components\TextInput::make('postal_code')
236
+                                    ->label('Postal Code / Zip Code')
237
+                                    ->required()
238
+                                    ->maxLength(255),
239
+                                Forms\Components\TextInput::make('country')
240
+                                    ->label('Country')
241
+                                    ->required()
242
+                                    ->maxLength(255),
243
+                                Forms\Components\Textarea::make('notes')
244
+                                    ->label('Delivery Instructions')
245
+                                    ->maxLength(255)
246
+                                    ->columnSpanFull(),
247
+                            ])->columns(),
212 248
                     ])->columns(),
213 249
             ]);
214 250
     }
@@ -217,17 +253,15 @@ class ClientResource extends Resource
217 253
     {
218 254
         return $table
219 255
             ->columns([
220
-                Tables\Columns\TextColumn::make('company.name')
221
-                    ->numeric()
222
-                    ->sortable(),
223 256
                 Tables\Columns\TextColumn::make('name')
257
+                    ->searchable()
258
+                    ->description(fn (Client $client) => $client->primaryContact->full_name),
259
+                Tables\Columns\TextColumn::make('primaryContact.email')
260
+                    ->label('Email')
224 261
                     ->searchable(),
225
-                Tables\Columns\TextColumn::make('currency_code')
226
-                    ->searchable(),
227
-                Tables\Columns\TextColumn::make('account_number')
228
-                    ->searchable(),
229
-                Tables\Columns\TextColumn::make('website')
230
-                    ->searchable(),
262
+                Tables\Columns\TextColumn::make('primaryContact.phones')
263
+                    ->label('Phone')
264
+                    ->state(fn (Client $client) => $client->primaryContact->primary_phone),
231 265
             ])
232 266
             ->filters([
233 267
                 //

+ 213
- 0
app/Filament/Company/Resources/Common/VendorResource.php Целия файл

@@ -0,0 +1,213 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Common;
4
+
5
+use App\Enums\Common\ContractorType;
6
+use App\Enums\Common\VendorType;
7
+use App\Filament\Company\Resources\Common\VendorResource\Pages;
8
+use App\Filament\Forms\Components\CreateCurrencySelect;
9
+use App\Filament\Forms\Components\CustomSection;
10
+use App\Filament\Forms\Components\PhoneBuilder;
11
+use App\Models\Common\Vendor;
12
+use Filament\Forms;
13
+use Filament\Forms\Form;
14
+use Filament\Resources\Resource;
15
+use Filament\Tables;
16
+use Filament\Tables\Table;
17
+
18
+class VendorResource extends Resource
19
+{
20
+    protected static ?string $model = Vendor::class;
21
+
22
+    protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
23
+
24
+    public static function form(Form $form): Form
25
+    {
26
+        return $form
27
+            ->schema([
28
+                Forms\Components\Section::make('General Information')
29
+                    ->schema([
30
+                        Forms\Components\Group::make()
31
+                            ->columns(2)
32
+                            ->schema([
33
+                                Forms\Components\TextInput::make('name')
34
+                                    ->label('Vendor Name')
35
+                                    ->required()
36
+                                    ->maxLength(255),
37
+                                Forms\Components\Radio::make('type')
38
+                                    ->label('Vendor Type')
39
+                                    ->required()
40
+                                    ->live()
41
+                                    ->options(VendorType::class)
42
+                                    ->default(VendorType::Regular)
43
+                                    ->columnSpanFull(),
44
+                                CreateCurrencySelect::make('currency_code')
45
+                                    ->relationship('currency', 'name')
46
+                                    ->visible(fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Regular),
47
+                                Forms\Components\Select::make('contractor_type')
48
+                                    ->label('Contractor Type')
49
+                                    ->required()
50
+                                    ->live()
51
+                                    ->visible(fn (Forms\Get $get) => VendorType::parse($get('type')) === VendorType::Contractor)
52
+                                    ->options(ContractorType::class),
53
+                                Forms\Components\TextInput::make('ssn')
54
+                                    ->label('Social Security Number')
55
+                                    ->required()
56
+                                    ->live()
57
+                                    ->visible(fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Individual)
58
+                                    ->maxLength(255),
59
+                                Forms\Components\TextInput::make('ein')
60
+                                    ->label('Employer Identification Number')
61
+                                    ->required()
62
+                                    ->live()
63
+                                    ->visible(fn (Forms\Get $get) => ContractorType::parse($get('contractor_type')) === ContractorType::Business)
64
+                                    ->maxLength(255),
65
+                                Forms\Components\TextInput::make('account_number')
66
+                                    ->maxLength(255),
67
+                                Forms\Components\TextInput::make('website')
68
+                                    ->maxLength(255),
69
+                                Forms\Components\Textarea::make('notes')
70
+                                    ->columnSpanFull(),
71
+                            ]),
72
+                        CustomSection::make('Primary Contact')
73
+                            ->relationship('contact')
74
+                            ->contained(false)
75
+                            ->schema([
76
+                                Forms\Components\Hidden::make('is_primary')
77
+                                    ->default(true),
78
+                                Forms\Components\TextInput::make('first_name')
79
+                                    ->label('First Name')
80
+                                    ->required()
81
+                                    ->maxLength(255),
82
+                                Forms\Components\TextInput::make('last_name')
83
+                                    ->label('Last Name')
84
+                                    ->required()
85
+                                    ->maxLength(255),
86
+                                Forms\Components\TextInput::make('email')
87
+                                    ->label('Email')
88
+                                    ->required()
89
+                                    ->email()
90
+                                    ->columnSpanFull()
91
+                                    ->maxLength(255),
92
+                                PhoneBuilder::make('phones')
93
+                                    ->hiddenLabel()
94
+                                    ->blockLabels(false)
95
+                                    ->default([
96
+                                        ['type' => 'primary'],
97
+                                    ])
98
+                                    ->columnSpanFull()
99
+                                    ->blocks([
100
+                                        Forms\Components\Builder\Block::make('primary')
101
+                                            ->schema([
102
+                                                Forms\Components\TextInput::make('number')
103
+                                                    ->label('Phone')
104
+                                                    ->required()
105
+                                                    ->maxLength(15),
106
+                                            ])->maxItems(1),
107
+                                        Forms\Components\Builder\Block::make('mobile')
108
+                                            ->schema([
109
+                                                Forms\Components\TextInput::make('number')
110
+                                                    ->label('Mobile')
111
+                                                    ->required()
112
+                                                    ->maxLength(15),
113
+                                            ])->maxItems(1),
114
+                                        Forms\Components\Builder\Block::make('toll_free')
115
+                                            ->schema([
116
+                                                Forms\Components\TextInput::make('number')
117
+                                                    ->label('Toll Free')
118
+                                                    ->required()
119
+                                                    ->maxLength(15),
120
+                                            ])->maxItems(1),
121
+                                        Forms\Components\Builder\Block::make('fax')
122
+                                            ->schema([
123
+                                                Forms\Components\TextInput::make('number')
124
+                                                    ->label('Fax')
125
+                                                    ->live()
126
+                                                    ->maxLength(15),
127
+                                            ])->maxItems(1),
128
+                                    ])
129
+                                    ->deletable(fn (PhoneBuilder $builder) => $builder->getItemsCount() > 1)
130
+                                    ->reorderable(false)
131
+                                    ->blockNumbers(false)
132
+                                    ->addActionLabel('Add Phone'),
133
+                            ])->columns(),
134
+                    ])->columns(1),
135
+                Forms\Components\Section::make('Address Information')
136
+                    ->relationship('address')
137
+                    ->schema([
138
+                        Forms\Components\Hidden::make('type')
139
+                            ->default('general'),
140
+                        Forms\Components\TextInput::make('address_line_1')
141
+                            ->label('Address Line 1')
142
+                            ->required()
143
+                            ->maxLength(255),
144
+                        Forms\Components\TextInput::make('address_line_2')
145
+                            ->label('Address Line 2')
146
+                            ->maxLength(255),
147
+                        Forms\Components\TextInput::make('city')
148
+                            ->label('City')
149
+                            ->required()
150
+                            ->maxLength(255),
151
+                        Forms\Components\TextInput::make('state')
152
+                            ->label('State')
153
+                            ->required()
154
+                            ->maxLength(255),
155
+                        Forms\Components\TextInput::make('postal_code')
156
+                            ->label('Postal Code / Zip Code')
157
+                            ->required()
158
+                            ->maxLength(255),
159
+                        Forms\Components\TextInput::make('country')
160
+                            ->label('Country')
161
+                            ->required()
162
+                            ->maxLength(255),
163
+                    ])
164
+                    ->columns(2),
165
+            ]);
166
+    }
167
+
168
+    public static function table(Table $table): Table
169
+    {
170
+        return $table
171
+            ->columns([
172
+                Tables\Columns\TextColumn::make('type')
173
+                    ->badge()
174
+                    ->searchable(),
175
+                Tables\Columns\TextColumn::make('name')
176
+                    ->searchable()
177
+                    ->description(fn (Vendor $vendor) => $vendor->contact->full_name),
178
+                Tables\Columns\TextColumn::make('contact.email')
179
+                    ->label('Email')
180
+                    ->searchable(),
181
+                Tables\Columns\TextColumn::make('primaryContact.phones')
182
+                    ->label('Phone')
183
+                    ->state(fn (Vendor $vendor) => $vendor->contact->primary_phone),
184
+            ])
185
+            ->filters([
186
+                //
187
+            ])
188
+            ->actions([
189
+                Tables\Actions\EditAction::make(),
190
+            ])
191
+            ->bulkActions([
192
+                Tables\Actions\BulkActionGroup::make([
193
+                    Tables\Actions\DeleteBulkAction::make(),
194
+                ]),
195
+            ]);
196
+    }
197
+
198
+    public static function getRelations(): array
199
+    {
200
+        return [
201
+            //
202
+        ];
203
+    }
204
+
205
+    public static function getPages(): array
206
+    {
207
+        return [
208
+            'index' => Pages\ListVendors::route('/'),
209
+            'create' => Pages\CreateVendor::route('/create'),
210
+            'edit' => Pages\EditVendor::route('/{record}/edit'),
211
+        ];
212
+    }
213
+}

+ 17
- 0
app/Filament/Company/Resources/Common/VendorResource/Pages/CreateVendor.php Целия файл

@@ -0,0 +1,17 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Common\VendorResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Common\VendorResource;
6
+use Filament\Resources\Pages\CreateRecord;
7
+use Filament\Support\Enums\MaxWidth;
8
+
9
+class CreateVendor extends CreateRecord
10
+{
11
+    protected static string $resource = VendorResource::class;
12
+
13
+    public function getMaxContentWidth(): MaxWidth | string | null
14
+    {
15
+        return MaxWidth::FiveExtraLarge;
16
+    }
17
+}

+ 25
- 0
app/Filament/Company/Resources/Common/VendorResource/Pages/EditVendor.php Целия файл

@@ -0,0 +1,25 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Common\VendorResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Common\VendorResource;
6
+use Filament\Actions;
7
+use Filament\Resources\Pages\EditRecord;
8
+use Filament\Support\Enums\MaxWidth;
9
+
10
+class EditVendor extends EditRecord
11
+{
12
+    protected static string $resource = VendorResource::class;
13
+
14
+    protected function getHeaderActions(): array
15
+    {
16
+        return [
17
+            Actions\DeleteAction::make(),
18
+        ];
19
+    }
20
+
21
+    public function getMaxContentWidth(): MaxWidth | string | null
22
+    {
23
+        return MaxWidth::FiveExtraLarge;
24
+    }
25
+}

+ 19
- 0
app/Filament/Company/Resources/Common/VendorResource/Pages/ListVendors.php Целия файл

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+namespace App\Filament\Company\Resources\Common\VendorResource\Pages;
4
+
5
+use App\Filament\Company\Resources\Common\VendorResource;
6
+use Filament\Actions;
7
+use Filament\Resources\Pages\ListRecords;
8
+
9
+class ListVendors extends ListRecords
10
+{
11
+    protected static string $resource = VendorResource::class;
12
+
13
+    protected function getHeaderActions(): array
14
+    {
15
+        return [
16
+            Actions\CreateAction::make(),
17
+        ];
18
+    }
19
+}

+ 13
- 0
app/Filament/Forms/Components/CustomSection.php Целия файл

@@ -0,0 +1,13 @@
1
+<?php
2
+
3
+namespace App\Filament\Forms\Components;
4
+
5
+use Filament\Forms\Components\Section;
6
+use Filament\Support\Concerns\CanBeContained;
7
+
8
+class CustomSection extends Section
9
+{
10
+    use CanBeContained;
11
+
12
+    protected string $view = 'filament.forms.components.custom-section';
13
+}

+ 15
- 0
app/Forms/Components/CustomSection.php Целия файл

@@ -0,0 +1,15 @@
1
+<?php
2
+
3
+namespace App\Forms\Components;
4
+
5
+use Filament\Forms\Components\Component;
6
+
7
+class CustomSection extends Component
8
+{
9
+    protected string $view = 'forms.components.custom-section';
10
+
11
+    public static function make(): static
12
+    {
13
+        return app(static::class);
14
+    }
15
+}

+ 51
- 0
app/Models/Common/Contact.php Целия файл

@@ -6,6 +6,7 @@ use App\Concerns\Blamable;
6 6
 use App\Concerns\CompanyOwned;
7 7
 use App\Enums\Common\ContactType;
8 8
 use Database\Factories\Common\ContactFactory;
9
+use Illuminate\Database\Eloquent\Casts\Attribute;
9 10
 use Illuminate\Database\Eloquent\Factories\Factory;
10 11
 use Illuminate\Database\Eloquent\Factories\HasFactory;
11 12
 use Illuminate\Database\Eloquent\Model;
@@ -41,6 +42,56 @@ class Contact extends Model
41 42
         return $this->morphTo();
42 43
     }
43 44
 
45
+    protected function fullName(): Attribute
46
+    {
47
+        return Attribute::get(function () {
48
+            return trim("{$this->first_name} {$this->last_name}");
49
+        });
50
+    }
51
+
52
+    protected function primaryPhone(): Attribute
53
+    {
54
+        return Attribute::get(function () {
55
+            return $this->getPhoneByType('primary');
56
+        });
57
+    }
58
+
59
+    protected function mobilePhone(): Attribute
60
+    {
61
+        return Attribute::get(function () {
62
+            return $this->getPhoneByType('mobile');
63
+        });
64
+    }
65
+
66
+    protected function faxPhone(): Attribute
67
+    {
68
+        return Attribute::get(function () {
69
+            return $this->getPhoneByType('fax');
70
+        });
71
+    }
72
+
73
+    protected function tollFreePhone(): Attribute
74
+    {
75
+        return Attribute::get(function () {
76
+            return $this->getPhoneByType('toll_free');
77
+        });
78
+    }
79
+
80
+    private function getPhoneByType(string $type): ?string
81
+    {
82
+        if (! is_array($this->phones)) {
83
+            return null;
84
+        }
85
+
86
+        foreach ($this->phones as $phone) {
87
+            if ($phone['type'] === $type) {
88
+                return $phone['data']['number'] ?? null;
89
+            }
90
+        }
91
+
92
+        return null;
93
+    }
94
+
44 95
     protected static function newFactory(): Factory
45 96
     {
46 97
         return ContactFactory::new();

+ 10
- 0
app/Models/Common/Vendor.php Целия файл

@@ -4,6 +4,8 @@ namespace App\Models\Common;
4 4
 
5 5
 use App\Concerns\Blamable;
6 6
 use App\Concerns\CompanyOwned;
7
+use App\Enums\Common\ContractorType;
8
+use App\Enums\Common\VendorType;
7 9
 use App\Models\Setting\Currency;
8 10
 use Illuminate\Database\Eloquent\Factories\HasFactory;
9 11
 use Illuminate\Database\Eloquent\Model;
@@ -24,6 +26,7 @@ class Vendor extends Model
24 26
         'type',
25 27
         'contractor_type',
26 28
         'ssn',
29
+        'ein',
27 30
         'currency_code',
28 31
         'account_number',
29 32
         'website',
@@ -32,6 +35,13 @@ class Vendor extends Model
32 35
         'updated_by',
33 36
     ];
34 37
 
38
+    protected $casts = [
39
+        'type' => VendorType::class,
40
+        'contractor_type' => ContractorType::class,
41
+        'ssn' => 'encrypted',
42
+        'ein' => 'encrypted',
43
+    ];
44
+
35 45
     public function currency(): BelongsTo
36 46
     {
37 47
         return $this->belongsTo(Currency::class, 'currency_code', 'code');

+ 5
- 0
app/Models/Company.php Целия файл

@@ -156,4 +156,9 @@ class Company extends FilamentCompaniesCompany implements HasAvatar
156 156
     {
157 157
         return $this->hasMany(Offering::class, 'company_id');
158 158
     }
159
+
160
+    public function vendors(): HasMany
161
+    {
162
+        return $this->hasMany(Common\Vendor::class, 'company_id');
163
+    }
159 164
 }

+ 2
- 3
app/Providers/FilamentCompaniesServiceProvider.php Целия файл

@@ -28,7 +28,7 @@ use App\Filament\Company\Pages\Service\LiveCurrency;
28 28
 use App\Filament\Company\Resources\Banking\AccountResource;
29 29
 use App\Filament\Company\Resources\Common\ClientResource;
30 30
 use App\Filament\Company\Resources\Common\OfferingResource;
31
-use App\Filament\Company\Resources\Purchases\BuyableOfferingResource;
31
+use App\Filament\Company\Resources\Common\VendorResource;
32 32
 use App\Filament\Components\PanelShiftDropdown;
33 33
 use App\Filament\User\Clusters\Account;
34 34
 use App\Http\Middleware\ConfigureCurrentCompany;
@@ -113,7 +113,6 @@ class FilamentCompaniesServiceProvider extends PanelProvider
113 113
             )
114 114
             ->colors([
115 115
                 'primary' => Color::Indigo,
116
-                'gray' => Color::Gray,
117 116
             ])
118 117
             ->navigation(function (NavigationBuilder $builder): NavigationBuilder {
119 118
                 return $builder
@@ -131,7 +130,7 @@ class FilamentCompaniesServiceProvider extends PanelProvider
131 130
                         NavigationGroup::make('Purchases')
132 131
                             ->label('Purchases')
133 132
                             ->icon('heroicon-o-shopping-cart')
134
-                            ->items(BuyableOfferingResource::getNavigationItems()),
133
+                            ->items(VendorResource::getNavigationItems()),
135 134
                         NavigationGroup::make('Accounting')
136 135
                             ->localizeLabel()
137 136
                             ->icon('heroicon-o-clipboard-document-list')

+ 6
- 6
composer.lock Целия файл

@@ -497,16 +497,16 @@
497 497
         },
498 498
         {
499 499
             "name": "aws/aws-sdk-php",
500
-            "version": "3.329.0",
500
+            "version": "3.330.0",
501 501
             "source": {
502 502
                 "type": "git",
503 503
                 "url": "https://github.com/aws/aws-sdk-php.git",
504
-                "reference": "37249799204e04cf3686d2b361cfd417d3dab123"
504
+                "reference": "dd1b65a4329f91d5e282a92fab2be7bdf6e2adea"
505 505
             },
506 506
             "dist": {
507 507
                 "type": "zip",
508
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/37249799204e04cf3686d2b361cfd417d3dab123",
509
-                "reference": "37249799204e04cf3686d2b361cfd417d3dab123",
508
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/dd1b65a4329f91d5e282a92fab2be7bdf6e2adea",
509
+                "reference": "dd1b65a4329f91d5e282a92fab2be7bdf6e2adea",
510 510
                 "shasum": ""
511 511
             },
512 512
             "require": {
@@ -589,9 +589,9 @@
589 589
             "support": {
590 590
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
591 591
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
592
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.329.0"
592
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.330.0"
593 593
             },
594
-            "time": "2024-11-21T19:16:09+00:00"
594
+            "time": "2024-11-22T19:10:26+00:00"
595 595
         },
596 596
         {
597 597
             "name": "aws/aws-sdk-php-laravel",

+ 2
- 1
database/migrations/2024_11_13_214406_create_vendors_table.php Целия файл

@@ -17,7 +17,8 @@ return new class extends Migration
17 17
             $table->string('name');
18 18
             $table->string('type');
19 19
             $table->string('contractor_type')->nullable();
20
-            $table->string('ssn')->nullable();
20
+            $table->text('ssn')->nullable();
21
+            $table->text('ein')->nullable();
21 22
             $table->string('currency_code')->nullable();
22 23
             $table->string('account_number')->nullable();
23 24
             $table->string('website')->nullable();

+ 20
- 31
resources/css/filament/company/theme.css Целия файл

@@ -4,6 +4,22 @@
4 4
 
5 5
 @config './tailwind.config.js';
6 6
 
7
+.fi-fo-repeater.uncontained .fi-fo-repeater-item {
8
+    @apply divide-y-0 rounded-none bg-none shadow-none ring-0 ring-gray-950/5 dark:divide-white/10 dark:bg-white/5 dark:ring-white/10;
9
+
10
+    .fi-fo-repeater-item-header {
11
+        @apply px-0;
12
+
13
+        > h4 {
14
+            @apply text-base font-semibold leading-6 text-gray-950 dark:text-white;
15
+        }
16
+    }
17
+
18
+    .fi-fo-repeater-item-content {
19
+        @apply py-4 px-0;
20
+    }
21
+}
22
+
7 23
 .fi-fo-field-wrp.report-hidden-label > div.grid.gap-y-2 > div.flex.items-center {
8 24
     @apply hidden;
9 25
 }
@@ -142,13 +158,13 @@
142 158
     @apply bg-platinum;
143 159
 }
144 160
 
145
-.fi-body {
161
+:not(.dark) .fi-body {
146 162
     position: relative;
147 163
     background-color: #E8E9EB;
148 164
     z-index: 1;
149 165
 }
150 166
 
151
-.fi-body::before {
167
+:not(.dark) .fi-body::before {
152 168
     content: '';
153 169
     position: fixed;
154 170
     top: 0;
@@ -165,33 +181,6 @@
165 181
     z-index: -1;
166 182
 }
167 183
 
168
-:is(.dark .fi-body) {
169
-    position: relative;
170
-    background-color: rgb(3, 7, 18);
171
-    z-index: 1;
172
-}
173
-
174
-:is(.dark .fi-body)::before {
175
-    content: '';
176
-    position: fixed;
177
-    top: 0;
178
-    right: 0;
179
-    background-image: radial-gradient(
180
-        ellipse at top right,
181
-        rgba(var(--primary-950), 1) 0%,
182
-        rgba(var(--primary-950), 0.9) 15%,
183
-        rgba(var(--primary-900), 0.7) 30%,
184
-        rgba(var(--primary-900), 0.5) 45%,
185
-        rgba(var(--primary-950), 0.3) 60%,
186
-        rgba(var(--primary-950), 0.1) 75%,
187
-        rgba(3, 7, 18, 0) 100%
188
-    );
189
-    width: 100%;
190
-    height: 100%;
191
-    pointer-events: none;
192
-    z-index: -1;
193
-}
194
-
195 184
 .fi-topbar > nav, .fi-sidebar-header {
196 185
     @apply bg-transparent ring-0 shadow-none !important;
197 186
     transition: background-color 0.3s, top 0.3s;
@@ -202,7 +191,7 @@
202 191
 }
203 192
 
204 193
 :is(.dark .fi-topbar > nav.topbar-hovered, .dark .fi-sidebar-header.topbar-hovered) {
205
-    background-color: rgba(10, 16, 33, 0.75) !important;
194
+    @apply bg-gray-900/75 !important;
206 195
 }
207 196
 
208 197
 .fi-topbar > nav.topbar-scrolled, .fi-sidebar-header.topbar-scrolled {
@@ -210,7 +199,7 @@
210 199
 }
211 200
 
212 201
 :is(.dark .fi-topbar > nav.topbar-scrolled, .dark .fi-sidebar-header.topbar-scrolled) {
213
-    background-color: rgba(10, 16, 33, 0.5) !important;
202
+    @apply bg-gray-900/50 !important;
214 203
 }
215 204
 
216 205
 .fi-badge {

+ 6
- 1
resources/data/lang/en.json Целия файл

@@ -202,5 +202,10 @@
202 202
     "Purchase Information": "Purchase Information",
203 203
     "Billing": "Billing",
204 204
     "Shipping": "Shipping",
205
-    "General Information": "General Information"
205
+    "General Information": "General Information",
206
+    "Primary Contact": "Primary Contact",
207
+    "Billing Address": "Billing Address",
208
+    "Shipping Address": "Shipping Address",
209
+    "Secondary Contacts": "Secondary Contacts",
210
+    "Address Information": "Address Information"
206 211
 }

+ 212
- 0
resources/views/components/custom-section.blade.php Целия файл

@@ -0,0 +1,212 @@
1
+@php
2
+    use Filament\Support\Enums\Alignment;
3
+    use Filament\Support\Enums\IconSize;
4
+@endphp
5
+
6
+@props([
7
+    'aside' => false,
8
+    'collapsed' => false,
9
+    'collapsible' => false,
10
+    'compact' => false,
11
+    'contained' => true,
12
+    'contentBefore' => false,
13
+    'description' => null,
14
+    'footerActions' => [],
15
+    'footerActionsAlignment' => Alignment::Start,
16
+    'headerActions' => [],
17
+    'headerEnd' => null,
18
+    'heading' => null,
19
+    'icon' => null,
20
+    'iconColor' => 'gray',
21
+    'iconSize' => IconSize::Large,
22
+    'persistCollapsed' => false,
23
+])
24
+
25
+@php
26
+    $hasDescription = filled((string) $description);
27
+    $hasHeading = filled($heading);
28
+    $hasIcon = filled($icon);
29
+
30
+    if (is_array($headerActions)) {
31
+        $headerActions = array_filter(
32
+            $headerActions,
33
+            fn ($headerAction): bool => $headerAction->isVisible(),
34
+        );
35
+    }
36
+
37
+    if (is_array($footerActions)) {
38
+        $footerActions = array_filter(
39
+            $footerActions,
40
+            fn ($footerAction): bool => $footerAction->isVisible(),
41
+        );
42
+    }
43
+
44
+    $hasHeaderActions = $headerActions instanceof \Illuminate\Contracts\Support\Htmlable
45
+        ? ! \Filament\Support\is_slot_empty($headerActions)
46
+        : filled($headerActions);
47
+
48
+    $hasFooterActions = $footerActions instanceof \Illuminate\Contracts\Support\Htmlable
49
+        ? ! \Filament\Support\is_slot_empty($footerActions)
50
+        : filled($footerActions);
51
+
52
+    $hasHeader = $hasIcon || $hasHeading || $hasDescription || $collapsible || $hasHeaderActions || filled((string) $headerEnd);
53
+@endphp
54
+
55
+<section
56
+    {{-- TODO: Investigate Livewire bug - https://github.com/filamentphp/filament/pull/8511 --}}
57
+    x-data="{
58
+        isCollapsed: @if ($persistCollapsed) $persist(@js($collapsed)).as(`section-${$el.id}-isCollapsed`) @else @js($collapsed) @endif,
59
+    }"
60
+    @if ($collapsible)
61
+        x-on:collapse-section.window="if ($event.detail.id == $el.id) isCollapsed = true"
62
+        x-on:expand="isCollapsed = false"
63
+        x-on:open-section.window="if ($event.detail.id == $el.id) isCollapsed = false"
64
+        x-on:toggle-section.window="if ($event.detail.id == $el.id) isCollapsed = ! isCollapsed"
65
+        x-bind:class="isCollapsed && 'fi-collapsed'"
66
+    @endif
67
+    {{
68
+        $attributes->class([
69
+            'fi-section',
70
+            'fi-aside grid grid-cols-1 items-start gap-x-6 gap-y-4 md:grid-cols-3' => $aside && $contained,
71
+            'fi-aside grid grid-cols-1 items-start gap-x-6 gap-y-4 md:grid-cols-3 pt-4' => $aside && ! $contained,
72
+            'rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10' => $contained && ! $aside,
73
+        ])
74
+    }}
75
+>
76
+    @if ($hasHeader)
77
+        <header
78
+            @if ($collapsible)
79
+                x-on:click="isCollapsed = ! isCollapsed"
80
+            @endif
81
+            @class([
82
+                'fi-section-header flex flex-col gap-3',
83
+                'cursor-pointer' => $collapsible,
84
+                'px-6 py-4' => $contained && ! $aside,
85
+                'px-4 py-2.5' => $compact && ! $aside,
86
+                'py-4' => ! $compact && ! $aside,
87
+            ])
88
+        >
89
+            <div class="flex items-center gap-3">
90
+                @if ($hasIcon)
91
+                    <x-filament::icon
92
+                        :icon="$icon"
93
+                        @class([
94
+                            'fi-section-header-icon self-start',
95
+                            match ($iconColor) {
96
+                                'gray' => 'text-gray-400 dark:text-gray-500',
97
+                                default => 'fi-color-custom text-custom-500 dark:text-custom-400',
98
+                            },
99
+                            is_string($iconColor) ? "fi-color-{$iconColor}" : null,
100
+                            match ($iconSize) {
101
+                                IconSize::Small, 'sm' => 'h-4 w-4 mt-1',
102
+                                IconSize::Medium, 'md' => 'h-5 w-5 mt-0.5',
103
+                                IconSize::Large, 'lg' => 'h-6 w-6',
104
+                                default => $iconSize,
105
+                            },
106
+                        ])
107
+                        @style([
108
+                            \Filament\Support\get_color_css_variables(
109
+                                $iconColor,
110
+                                shades: [400, 500],
111
+                                alias: 'section.header.icon',
112
+                            ) => $iconColor !== 'gray',
113
+                        ])
114
+                    />
115
+                @endif
116
+
117
+                @if ($hasHeading || $hasDescription)
118
+                    <div class="grid flex-1 gap-y-1">
119
+                        @if ($hasHeading)
120
+                            <x-filament::section.heading>
121
+                                {{ $heading }}
122
+                            </x-filament::section.heading>
123
+                        @endif
124
+
125
+                        @if ($hasDescription)
126
+                            <x-filament::section.description>
127
+                                {{ $description }}
128
+                            </x-filament::section.description>
129
+                        @endif
130
+                    </div>
131
+                @endif
132
+
133
+                @if ($hasHeaderActions)
134
+                    <div class="hidden sm:block">
135
+                        <x-filament::actions
136
+                            :actions="$headerActions"
137
+                            :alignment="\Filament\Support\Enums\Alignment::Start"
138
+                            x-on:click.stop=""
139
+                        />
140
+                    </div>
141
+                @endif
142
+
143
+                {{ $headerEnd }}
144
+
145
+                @if ($collapsible)
146
+                    <x-filament::icon-button
147
+                        color="gray"
148
+                        icon="heroicon-m-chevron-down"
149
+                        icon-alias="section.collapse-button"
150
+                        x-on:click.stop="isCollapsed = ! isCollapsed"
151
+                        x-bind:class="{ 'rotate-180': ! isCollapsed }"
152
+                    />
153
+                @endif
154
+            </div>
155
+
156
+            @if ($hasHeaderActions)
157
+                <div class="sm:hidden">
158
+                    <x-filament::actions
159
+                        :actions="$headerActions"
160
+                        :alignment="\Filament\Support\Enums\Alignment::Start"
161
+                        x-on:click.stop=""
162
+                    />
163
+                </div>
164
+            @endif
165
+        </header>
166
+    @endif
167
+
168
+    <div
169
+        @if ($collapsible)
170
+            x-bind:aria-expanded="(! isCollapsed).toString()"
171
+            @if ($collapsed || $persistCollapsed)
172
+                x-cloak
173
+            @endif
174
+            x-bind:class="{ 'invisible h-0 overflow-y-hidden border-none': isCollapsed }"
175
+        @endif
176
+        @class([
177
+            'fi-section-content-ctn',
178
+            'border-t border-gray-200 dark:border-white/10' => $hasHeader && ! $aside && $contained,
179
+            'rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:bg-gray-900 dark:ring-white/10 md:col-span-2' => $aside && $contained,
180
+            'md:col-span-2' => $aside && ! $contained,
181
+            'md:order-first' => $contentBefore,
182
+        ])
183
+    >
184
+        <div
185
+            @class([
186
+                'fi-section-content',
187
+                'pt-4' => ! $contained && ! $aside,
188
+                'p-4' => $compact && $contained,
189
+                'p-6' => ! $compact && $contained,
190
+            ])
191
+        >
192
+            {{ $slot }}
193
+        </div>
194
+
195
+        @if ($hasFooterActions)
196
+            <footer
197
+                @class([
198
+                    'fi-section-footer',
199
+                    'border-t border-gray-200 dark:border-white/10' => $contained,
200
+                    'mt-6' => ! $contained,
201
+                    'px-6 py-4' => ! $compact && $contained,
202
+                    'px-4 py-2.5' => $compact && $contained,
203
+                ])
204
+            >
205
+                <x-filament::actions
206
+                    :actions="$footerActions"
207
+                    :alignment="$footerActionsAlignment"
208
+                />
209
+            </footer>
210
+        @endif
211
+    </div>
212
+</section>

+ 31
- 0
resources/views/filament/forms/components/custom-section.blade.php Целия файл

@@ -0,0 +1,31 @@
1
+@php
2
+    $isAside = $isAside();
3
+@endphp
4
+
5
+<x-custom-section
6
+    :aside="$isAside"
7
+    :collapsed="$isCollapsed()"
8
+    :collapsible="$isCollapsible() && (! $isAside)"
9
+    :compact="$isCompact()"
10
+    :contained="$isContained()"
11
+    :content-before="$isFormBefore()"
12
+    :description="$getDescription()"
13
+    :footer-actions="$getFooterActions()"
14
+    :footer-actions-alignment="$getFooterActionsAlignment()"
15
+    :header-actions="$getHeaderActions()"
16
+    :heading="$getHeading()"
17
+    :icon="$getIcon()"
18
+    :icon-color="$getIconColor()"
19
+    :icon-size="$getIconSize()"
20
+    :persist-collapsed="$shouldPersistCollapsed()"
21
+    :attributes="
22
+        \Filament\Support\prepare_inherited_attributes($attributes)
23
+            ->merge([
24
+                'id' => $getId(),
25
+            ], escape: false)
26
+            ->merge($getExtraAttributes(), escape: false)
27
+            ->merge($getExtraAlpineAttributes(), escape: false)
28
+    "
29
+>
30
+    {{ $getChildComponentContainer() }}
31
+</x-custom-section>

Loading…
Отказ
Запис