Skip to content

Commit 0d1cacd

Browse files
committed
feat: implement product bulk editing
1 parent c804fb7 commit 0d1cacd

3 files changed

Lines changed: 377 additions & 54 deletions

File tree

src/Filament/Resources/ProductResource.php

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BezhanSalleh\FilamentShield\Contracts\HasShieldPermissions;
66
use Eclipse\Catalogue\Filament\Forms\Components\ImageManager;
77
use Eclipse\Catalogue\Filament\Resources\ProductResource\Pages;
8+
use Eclipse\Catalogue\Filament\Tables\Actions\BulkUpdateProductsAction;
89
use Eclipse\Catalogue\Forms\Components\GenericTenantFieldsComponent;
910
use Eclipse\Catalogue\Models\Category;
1011
use Eclipse\Catalogue\Models\Group;
@@ -24,11 +25,9 @@
2425
use Filament\Forms\Components\TextInput;
2526
use Filament\Forms\Form;
2627
use Filament\Forms\Get;
27-
use Filament\Notifications\Notification;
2828
use Filament\Resources\Concerns\Translatable;
2929
use Filament\Resources\Resource;
3030
use Filament\Tables\Actions\ActionGroup;
31-
use Filament\Tables\Actions\BulkAction;
3231
use Filament\Tables\Actions\BulkActionGroup;
3332
use Filament\Tables\Actions\DeleteAction;
3433
use Filament\Tables\Actions\DeleteBulkAction;
@@ -610,58 +609,7 @@ public static function table(Table $table): Table
610609
])
611610
->bulkActions([
612611
BulkActionGroup::make([
613-
BulkAction::make('add_to_group')
614-
->label('Add to Group')
615-
->icon('heroicon-o-plus')
616-
->form([
617-
Select::make('group_id')
618-
->label('Group')
619-
->options(fn () => Group::query()->active()->forCurrentTenant()->pluck('name', 'id')->toArray())
620-
->required()
621-
->searchable(),
622-
])
623-
->action(function (array $data, $records) {
624-
$group = Group::find($data['group_id']);
625-
$addedCount = 0;
626-
627-
foreach ($records as $product) {
628-
if (! $group->hasProduct($product)) {
629-
$group->addProduct($product);
630-
$addedCount++;
631-
}
632-
}
633-
634-
Notification::make()
635-
->title("Added {$addedCount} products to group \"{$group->name}\"")
636-
->success()
637-
->send();
638-
}),
639-
BulkAction::make('remove_from_group')
640-
->label('Remove from Group')
641-
->icon('heroicon-o-minus')
642-
->form([
643-
Select::make('group_id')
644-
->label('Group')
645-
->options(fn () => Group::query()->active()->forCurrentTenant()->pluck('name', 'id')->toArray())
646-
->required()
647-
->searchable(),
648-
])
649-
->action(function (array $data, $records) {
650-
$group = Group::find($data['group_id']);
651-
$removedCount = 0;
652-
653-
foreach ($records as $product) {
654-
if ($group->hasProduct($product)) {
655-
$group->removeProduct($product);
656-
$removedCount++;
657-
}
658-
}
659-
660-
Notification::make()
661-
->title("Removed {$removedCount} products from group \"{$group->name}\"")
662-
->success()
663-
->send();
664-
}),
612+
BulkUpdateProductsAction::make(),
665613
DeleteBulkAction::make(),
666614
RestoreBulkAction::make(),
667615
ForceDeleteBulkAction::make(),
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<?php
2+
3+
namespace Eclipse\Catalogue\Filament\Tables\Actions;
4+
5+
use Eclipse\Catalogue\Models\Category;
6+
use Eclipse\Catalogue\Models\Group;
7+
use Eclipse\Catalogue\Models\ProductType;
8+
use Eclipse\Catalogue\Services\ProductBulkUpdater;
9+
use Filament\Forms\Components\FileUpload;
10+
use Filament\Forms\Components\Section;
11+
use Filament\Forms\Components\Select;
12+
use Filament\Forms\Components\Toggle;
13+
use Filament\Notifications\Notification;
14+
use Filament\Tables\Actions\BulkAction;
15+
use Illuminate\Support\Facades\App;
16+
17+
class BulkUpdateProductsAction extends BulkAction
18+
{
19+
/**
20+
* Make the bulk update action.
21+
*
22+
* @param string|null $name The name of the action.
23+
* @return static The bulk update action.
24+
*/
25+
public static function make(?string $name = null): static
26+
{
27+
$action = parent::make($name ?? 'bulk_update')
28+
->label('Bulk Update')
29+
->icon('heroicon-o-wrench')
30+
->deselectRecordsAfterCompletion()
31+
->form([
32+
Section::make('Product Type')
33+
->collapsible()
34+
->collapsed()
35+
->schema([
36+
Toggle::make('update_product_type')
37+
->label('Update product type')
38+
->helperText('Select a product type to assign to selected products.'),
39+
Select::make('product_type_id')
40+
->label('Product Type')
41+
->options(function () {
42+
$tenantFK = config('eclipse-catalogue.tenancy.foreign_key');
43+
$currentTenant = \Filament\Facades\Filament::getTenant();
44+
45+
$query = ProductType::query();
46+
47+
if ($tenantFK && $currentTenant) {
48+
$query->whereHas('productTypeData', function ($q) use ($tenantFK, $currentTenant) {
49+
$q->where($tenantFK, $currentTenant->id)
50+
->where('is_active', true);
51+
});
52+
} else {
53+
$query->whereHas('productTypeData', function ($q) {
54+
$q->where('is_active', true);
55+
});
56+
}
57+
58+
return $query->pluck('name', 'id')->toArray();
59+
})
60+
->searchable()
61+
->nullable(),
62+
]),
63+
Section::make('Free delivery')
64+
->collapsible()
65+
->collapsed()
66+
->schema([
67+
Toggle::make('update_free_delivery')
68+
->label('Update free delivery')
69+
->helperText('Set the free delivery flag for the current tenant.'),
70+
Toggle::make('free_delivery_value')
71+
->label('Has free delivery'),
72+
]),
73+
Section::make('Categories')
74+
->collapsible()
75+
->collapsed()
76+
->schema([
77+
Toggle::make('update_categories')
78+
->label('Update categories')
79+
->helperText('Choose a category to assign to selected products for the current tenant.'),
80+
Select::make('category_id')
81+
->label('Category')
82+
->options(function () {
83+
$tenantFK = config('eclipse-catalogue.tenancy.foreign_key', 'site_id');
84+
$currentTenant = \Filament\Facades\Filament::getTenant();
85+
$query = Category::query()->withoutGlobalScopes();
86+
if ($tenantFK && $currentTenant) {
87+
$query->where($tenantFK, $currentTenant->id);
88+
}
89+
90+
return $query->orderBy('name')->pluck('name', 'id')->toArray();
91+
})
92+
->searchable()
93+
->nullable(),
94+
]),
95+
Section::make('Groups')
96+
->collapsible()
97+
->collapsed()
98+
->schema([
99+
Toggle::make('update_groups')
100+
->label('Update groups')
101+
->helperText('Enable to add or remove selected products to/from groups.'),
102+
Select::make('groups_add_ids')
103+
->label('Add to groups')
104+
->multiple()
105+
->options(fn () => Group::query()->active()->forCurrentTenant()->pluck('name', 'id')->toArray())
106+
->searchable(),
107+
Select::make('groups_remove_ids')
108+
->label('Remove from groups')
109+
->multiple()
110+
->options(fn () => Group::query()->active()->forCurrentTenant()->pluck('name', 'id')->toArray())
111+
->searchable(),
112+
]),
113+
Section::make('Images')
114+
->collapsible()
115+
->collapsed()
116+
->schema([
117+
Toggle::make('update_images')
118+
->label('Update images')
119+
->helperText('You can add new product images here. Any image that already exists on the specified position will be replaced.'),
120+
FileUpload::make('cover_image')
121+
->label('Cover image')
122+
->image()
123+
->imageEditor(false)
124+
->directory('temp-images')
125+
->visibility('public')
126+
->storeFiles(true)
127+
->preserveFilenames()
128+
->nullable(),
129+
FileUpload::make('image_1')
130+
->label('Image #1')
131+
->image()
132+
->imageEditor(false)
133+
->directory('temp-images')
134+
->visibility('public')
135+
->storeFiles(true)
136+
->preserveFilenames()
137+
->nullable(),
138+
FileUpload::make('image_2')
139+
->label('Image #2')
140+
->image()
141+
->imageEditor(false)
142+
->directory('temp-images')
143+
->visibility('public')
144+
->storeFiles(true)
145+
->preserveFilenames()
146+
->nullable(),
147+
FileUpload::make('image_3')
148+
->label('Image #3')
149+
->image()
150+
->imageEditor(false)
151+
->directory('temp-images')
152+
->visibility('public')
153+
->storeFiles(true)
154+
->preserveFilenames()
155+
->nullable(),
156+
]),
157+
])
158+
->action(function (array $data, $records) {
159+
/** @var ProductBulkUpdater $updater */
160+
$updater = App::make(ProductBulkUpdater::class);
161+
$result = $updater->apply($data, $records);
162+
163+
$notification = Notification::make();
164+
if (($result['successCount'] ?? 0) > 0) {
165+
$title = $result['successCount'] === 1
166+
? 'Updated 1 product'
167+
: "Updated {$result['successCount']} products";
168+
$notification->title($title)->success();
169+
} else {
170+
$notification->title('No changes applied')->warning();
171+
}
172+
173+
if (! empty($result['errors'] ?? [])) {
174+
$notification->body('Some updates failed: '.implode(' | ', array_unique($result['errors'])));
175+
}
176+
177+
$notification->send();
178+
});
179+
180+
return $action;
181+
}
182+
}

0 commit comments

Comments
 (0)