Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('pim_product_relations', function (Blueprint $table) {
$table->id();
$table->foreignId('parent_id')
->constrained('pim_products', 'id')
->cascadeOnUpdate()
->cascadeOnDelete();
$table->foreignId('child_id')
->constrained('pim_products', 'id')
->cascadeOnUpdate()
->cascadeOnDelete();
$table->string('type', 50);
$table->tinyInteger('sort')->nullable();
$table->timestamps();
$table->unique(['parent_id', 'child_id', 'type']);
$table->index(['parent_id', 'type']);
});
}

public function down(): void
{
Schema::dropIfExists('pim_product_relations');
}
};
1 change: 1 addition & 0 deletions resources/views/livewire/product-relations-table.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<livewire:eclipse.catalogue.livewire.product-relations-table :product-id="$productId" :type="$type" />
7 changes: 4 additions & 3 deletions src/CatalogueServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Eclipse\Catalogue\Filament\Resources\PropertyResource;
use Eclipse\Catalogue\Filament\Resources\PropertyValueResource;
use Eclipse\Catalogue\Filament\Resources\TaxClassResource;
use Eclipse\Catalogue\Livewire\TenantSwitcher;
use Eclipse\Catalogue\Models\Category;
use Eclipse\Catalogue\Models\Product;
use Filament\Support\Assets\Css;
Expand Down Expand Up @@ -176,8 +175,10 @@ public function boot()
});

// Register Livewire components
if (class_exists(Livewire::class)) {
Livewire::component('eclipse-catalogue::tenant-switcher', TenantSwitcher::class);
if (class_exists(\Livewire\Livewire::class)) {
\Livewire\Livewire::component('eclipse-catalogue::tenant-switcher', \Eclipse\Catalogue\Livewire\TenantSwitcher::class);
\Livewire\Livewire::component('eclipse.catalogue.livewire.product-selector-table', \Eclipse\Catalogue\Livewire\ProductSelectorTable::class);
\Livewire\Livewire::component('eclipse.catalogue.livewire.product-relations-table', \Eclipse\Catalogue\Livewire\ProductRelationsTable::class);
}

FilamentAsset::register([
Expand Down
36 changes: 36 additions & 0 deletions src/Enums/ProductRelationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Eclipse\Catalogue\Enums;

use Filament\Support\Contracts\HasLabel;

enum ProductRelationType: string implements HasLabel
{
case RELATED = 'related';
case CROSS_SELL = 'cross_sell';
case UPSELL = 'upsell';

/**
* Get the label for the product relation type.
*/
public function getLabel(): ?string
{
return match ($this) {
self::RELATED => 'Related',
self::CROSS_SELL => 'Cross-sell',
self::UPSELL => 'Upsell',
};
}

/**
* Get the description for the product relation type.
*/
public function getDescription(): string
{
return match ($this) {
self::RELATED => 'Similar products that customers might be interested in',
self::CROSS_SELL => 'Complementary products or add-ons that enhance the main product',
self::UPSELL => 'Higher-priced or premium versions with more features',
};
}
}
74 changes: 74 additions & 0 deletions src/Filament/Resources/ProductResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Eclipse\Catalogue\Filament\Resources;

use Eclipse\Catalogue\Enums\ProductRelationType;
use Eclipse\Catalogue\Enums\PropertyInputType;
use Eclipse\Catalogue\Filament\Filters\CustomPropertyConstraint;
use Eclipse\Catalogue\Filament\Forms\Components\ImageManager;
Expand Down Expand Up @@ -566,6 +567,47 @@ function ($query) {
->acceptedFileTypes(['image/jpeg', 'image/png', 'image/gif', 'image/webp'])
->columnSpanFull(),
]),

Tabs\Tab::make('Related Products')
->schema([
Tabs::make('product_relations_tabs')
->tabs([
Tabs\Tab::make('related')
->label('Related')
->icon('heroicon-o-link')
->schema([
View::make('eclipse-catalogue::livewire.product-relations-table')
->viewData(fn (?Product $record) => [
'productId' => $record?->id,
'type' => ProductRelationType::RELATED->value,
]),
]),

Tabs\Tab::make('cross_sell')
->label('Cross-sell')
->icon('heroicon-o-plus-circle')
->schema([
View::make('eclipse-catalogue::livewire.product-relations-table')
->viewData(fn (?Product $record) => [
'productId' => $record?->id,
'type' => ProductRelationType::CROSS_SELL->value,
]),
]),

Tabs\Tab::make('upsell')
->label('Upsell')
->icon('heroicon-o-arrow-trending-up')
->schema([
View::make('eclipse-catalogue::livewire.product-relations-table')
->viewData(fn (?Product $record) => [
'productId' => $record?->id,
'type' => ProductRelationType::UPSELL->value,
]),
]),
])
->columnSpanFull(),
])
->visible(fn (?Product $record) => $record && $record->exists),
])
->columnSpanFull(),
]);
Expand Down Expand Up @@ -1032,6 +1074,38 @@ protected static function getCustomPropertyColumns(): array
return $columns;
}

protected static function getRelationsColumns(): array
{
return [
TextColumn::make('related_count')
->label('Related')
->getStateUsing(function (Product $record) {
return $record->related()->count();
})
->badge()
->color('gray')
->toggleable(isToggledHiddenByDefault: true),

TextColumn::make('cross_sell_count')
->label('Cross-sell')
->getStateUsing(function (Product $record) {
return $record->crossSell()->count();
})
->badge()
->color('blue')
->toggleable(isToggledHiddenByDefault: true),

TextColumn::make('upsell_count')
->label('Upsell')
->getStateUsing(function (Product $record) {
return $record->upsell()->count();
})
->badge()
->color('green')
->toggleable(isToggledHiddenByDefault: true),
];
}

protected static function getCustomPropertyConstraints(): array
{
$constraints = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected function getFormMutuallyExclusiveFlagSets(): array
return [];
}

public function form(Schema $schema): Schema
public function schema(Schema $schema): Schema
{
return $schema;
}
Expand Down
67 changes: 66 additions & 1 deletion src/Filament/Resources/ProductResource/Pages/EditProduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Eclipse\Catalogue\Filament\Resources\ProductResource\Pages;

use Eclipse\Catalogue\Enums\ProductRelationType;
use Eclipse\Catalogue\Filament\Resources\ProductResource;
use Eclipse\Catalogue\Models\Group;
use Eclipse\Catalogue\Models\ProductRelation;
use Eclipse\Catalogue\Models\Property;
use Eclipse\Catalogue\Models\PropertyValue;
use Eclipse\Catalogue\Traits\HandlesTenantData;
Expand All @@ -17,6 +19,7 @@
use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use LaraZeus\SpatieTranslatable\Actions\LocaleSwitcher;
use LaraZeus\SpatieTranslatable\Resources\Pages\EditRecord\Concerns\Translatable;
use Nben\FilamentRecordNav\Actions\NextRecordAction;
Expand Down Expand Up @@ -146,6 +149,13 @@ protected function afterSave(): void
{
if ($this->record) {
$state = $this->form->getRawState();

$this->saveProductRelations([
'related_products' => $this->data['related_products'] ?? [],
'cross_sell_products' => $this->data['cross_sell_products'] ?? [],
'upsell_products' => $this->data['upsell_products'] ?? [],
]);

$propertyData = [];
foreach ($state as $key => $value) {
if (is_string($key) && str_starts_with($key, 'property_values_')) {
Expand Down Expand Up @@ -217,7 +227,7 @@ protected function getFormMutuallyExclusiveFlagSets(): array
return [];
}

public function form(Schema $schema): Schema
public function schema(Schema $schema): Schema
{
return $schema;
}
Expand Down Expand Up @@ -335,4 +345,59 @@ public function reorderImages(string $statePath, array $uuids): void
->values()
->toArray();
}

protected function saveProductRelations(array $state): void
{
if (! $this->record) {
return;
}

$relationTypes = [
'related_products' => ProductRelationType::RELATED,
'cross_sell_products' => ProductRelationType::CROSS_SELL,
'upsell_products' => ProductRelationType::UPSELL,
];

foreach ($relationTypes as $fieldName => $relationType) {
$relationItems = $state[$fieldName] ?? [];

if (empty($relationItems)) {
continue;
}

ProductRelation::where('parent_id', $this->record->id)
->where('type', $relationType->value)
->delete();

$position = 1;
foreach ($relationItems as $item) {
if (! is_array($item) || ! isset($item['product_id']) || empty($item['product_id'])) {
continue;
}

$childId = is_numeric($item['product_id']) ? (int) $item['product_id'] : null;

if ($childId && $childId !== $this->record->id) {
try {
ProductRelation::create([
'parent_id' => $this->record->id,
'child_id' => $childId,
'type' => $relationType->value,
'sort' => $position,
]);
$position++;
} catch (\Exception $e) {
Log::error('Failed to create product relation', [
'parent_id' => $this->record->id,
'child_id' => $childId,
'type' => $relationType->value,
'sort' => $position,
'item' => $item,
'error' => $e->getMessage(),
]);
}
}
}
}
}
}
Loading