filament icon indicating copy to clipboard operation
filament copied to clipboard

Nesting repeaters for BelongsToMany relationships

Open marc31 opened this issue 1 year ago • 6 comments

Package

filament/filament

Package Version

v3.2

Laravel Version

v11.9

Livewire Version

v3.5

PHP Version

PHP 8.3

Problem description

Following the example of using a repeater to fill BelongsToMany relationships [https://filamentphp.com/docs/3.x/forms/fields/repeater], I want to nest two repeaters with BelongsToMany relationships to handle a scenario like this:

I have Orders that have Products, which in turn have Colors.

I'm encountering a bug during the initial save: the color is not saved correctly because the order_product_id column in the color_order_product table is null. However, if I add new colors to a product in the order afterward, the modification is saved correctly.

But if I add a new product, the same issue arises.

Repeater::make('order_product')
    ->relationship('orderProducts')
    ->schema([
        Select::make('product_id')
            ->relationship('product', 'title')
            ->required()
            ->live(),
        Repeater::make('colorOrderProducts')
            ->relationship('colorOrderProducts')
            ->schema([
                Select::make('color_id')
                    ->options(
                        function ($get) {
                            $selectedProduct = Product::find($get('../../product_id'));
                            if ($selectedProduct) {
                                return $selectedProduct->colors()->get()->pluck('title', 'id');
                            }
                        }
                    )
                    ->required(),
            ])
    ])

Expected behavior

When creating an order, adding products, and assigning colors to those products using the nested repeater, all entries should be saved correctly.

Steps to reproduce

  1. Create an order with products and assign colors to those products using the nested repeater.

  2. Save the order.

  3. Check the color_order_product table and observe that the order_product_id column is null for the initially added colors.

  4. Add new colors to the existing product and save again. Observe that these new colors are saved correctly.

  5. Add a new product to the order and assign colors. Save and observe the issue reoccurs.

Reproduction repository

https://github.com/marc31/filament-nested-repeater-w-relationship

Relevant log output

No response

marc31 avatar Jun 03 '24 08:06 marc31

that was the same for me if you have a solution for this ,pldas

My Resource

<?php

namespace App\Filament\Resources;

use App\Filament\Enums\AdvantageLayoutTypeEnum;
use App\Filament\Resources\ProductCategoryResource\Pages;
use App\Models\ProductCategory;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Concerns\Translatable;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Support\Str;

class ProductCategoryResource extends Resource
{
    use Translatable;

    protected static ?string $model = ProductCategory::class;

    protected static ?string $navigationIcon = 'heroicon-o-queue-list';

    protected static ?string $navigationGroup = 'Manage Products';

    public static function form(Form $form): Form
    {
        return $form
            ->schema([
                Forms\Components\TextInput::make('name')
                    ->required()
                    ->maxLength(255)
                    ->live(onBlur: true)
                    ->afterStateUpdated(function (string $operation, $state, Forms\Set $set): void {
                        if ($operation !== 'create') {
                            return;
                        }

                        $set('slug', Str::slug($state));
                    }),

                Forms\Components\TextInput::make('slug')
                    ->disabled()
                    ->dehydrated()
                    ->required()
                    ->maxLength(255)
                    ->unique(ProductCategory::class, 'slug', ignoreRecord: true),
                Forms\Components\FileUpload::make('icon')
                    ->image(),
                Forms\Components\FileUpload::make('image')
                    ->image(),
                Forms\Components\Repeater::make('advantages')
                    ->schema([
                        Forms\Components\TextInput::make('title')
                            ->required(),
                        Forms\Components\Repeater::make('contents')
                            ->schema([
                                Forms\Components\TextInput::make('order')
                                    ->numeric()
                                    ->required(),
                                Forms\Components\TextInput::make('title')
                                    ->required(),
                                Forms\Components\FileUpload::make('img')
                                    ->disk('public')
                                    ->directory('advantages')
                                    ->image(),
                                Forms\Components\MarkdownEditor::make('desc')
                                    ->required(),
                                Forms\Components\Select::make('type')
                                    ->options(AdvantageLayoutTypeEnum::class)
                                    ->required(),
                            ])
                            ->minItems(1)
                            ->collapsible()
                            ->required(),
                    ])
                    ->minItems(1)
                    ->addable(false)
                    ->deletable(false)
                    ->required()
                    ->columnSpanFull(),

            ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('name')
                    ->searchable(),
                Tables\Columns\TextColumn::make('slug')
                    ->searchable()
                    ->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\ImageColumn::make('icon'),
                Tables\Columns\ImageColumn::make('image'),
                // Tables\Columns\TextColumn::make('advantages.title')
                //     ->searchable(),
                Tables\Columns\TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable()
                    ->toggleable(isToggledHiddenByDefault: true),
                Tables\Columns\TextColumn::make('updated_at')
                    ->dateTime()
                    ->sortable()
                    ->toggleable(isToggledHiddenByDefault: true),
            ])
            ->filters([
                //
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getRelations(): array
    {
        return [
            //
        ];
    }

    public static function getPages(): array
    {
        return [
            'index'  => Pages\ListProductCategories::route('/'),
            'create' => Pages\CreateProductCategory::route('/create'),
            'edit'   => Pages\EditProductCategory::route('/{record}/edit'),
        ];
    }
}

my migration


Schema::create('product_categories', function (Blueprint $table): void {
            $table->id();
            $table->text('name');
            $table->string('slug')->unique();
            $table->string('icon')->nullable()->default(null);
            $table->string('image')->nullable()->default(null);
            $table->json('advantages')->nullable();
            $table->timestamps();
        });


my seeder

$category = ProductCategory::create([
                    'name'       => $categoriesName,
                    'slug'       => Str::slug($categoriesName),
                    'icon'       => 'advantages/01HZE5794C7ADXYKC9HPXHFHS2.png',
                    'image'      => 'advantages/01HZE5794A1KQQYGAQQ9V7Y8B6.png',
                    'advantages' => [
                        [
                            'title'    => 'Advantages of Bio Stimulant',
                            'contents' => [
                                [
                                    'order' => 1,
                                    'title' => 'BioStimulants Assits In Combating The Effects Of Enveromental Stresses',
                                    'img'   => 'advantages/01HZE5794A1KQQYGAQQ9V7Y8B6.png',
                                    'desc'  => fake()->paragraph(),
                                    'type'  => 'right section',
                                ], [
                                    'order' => 2,
                                    'title' => 'Qui aute corporis no',
                                    'img'   => 'advantages/01HZE5794C7ADXYKC9HPXHFHS2.png',
                                    'desc'  => fake()->paragraph(),
                                    'type'  => 'left section',
                                ],
                                [
                                    'order' => 3,
                                    'title' => 'Advantage 1',
                                    'img'   => 'advantage1.png',
                                    'desc'  => fake()->paragraph(),
                                    'type'  => 'right section',
                                ],
                                [
                                    'order' => 4,
                                    'title' => 'Advantage 2',
                                    'img'   => 'advantage2.png',
                                    'desc'  => fake()->paragraph(),
                                    'type'  => 'final section',
                                ],
                            ],
                        ],
                    ],
                ]);

Khant-Nyar avatar Jun 05 '24 10:06 Khant-Nyar

Not sure if I’m having the same issue, but here:

app/Models/Guide.php

public function items(): BelongsToMany
{
    return $this->belongsToMany(Item::class);
}
Forms\Components\Repeater::make('steps')
    ->schema([
        Forms\Components\Select::make('item')
            ->relationship(name: 'items', titleAttribute: 'title')
            ->disableOptionsWhenSelectedInSiblingRepeaterItems()
            ->required(),
        Forms\Components\Textarea::make('description')
            ->autosize(),
    ]),

Desired behaviour: To sync all repeater step items into the guide_item table.

Current behaviour: It removes all current relations and attaches only the last repeater step item.

balu-lt avatar Jun 26 '24 13:06 balu-lt

I have a similar problem, I have the tables:

purchase_request -> purchase_order -> receipt_materials_register

and I need get objects from purchase_request table. I tried a create method thats returns from purchase_order but withont success

IgorDePaula avatar Jul 25 '24 14:07 IgorDePaula

When u edit the page in order selected color is not preloaded what to do for that?

himamshah avatar Oct 10 '24 07:10 himamshah

This issue also seems to be solved by setting the $incrementing property to true on your Pivot models (OrderProduct, ColorOrderProduct). See issue #15766

yuters avatar Mar 04 '25 16:03 yuters

Hey @polar-sh[bot]! We're sorry to hear that you've hit this issue. 💛

However, it looks like you forgot to fill in the reproduction repository URL. Can you edit your original post and then we'll look at your issue?

We need a public GitHub repository which contains a Laravel app with the minimal amount of Filament code to reproduce the problem. Please do not link to your actual project, what we need instead is a minimal reproduction in a fresh project without any unnecessary code. This means it doesn't matter if your real project is private / confidential, since we want a link to a separate, isolated reproduction. That would allow us to download it and review your bug much easier, so it can be fixed quicker. Please make sure to include a database seeder with everything we need to set the app up quickly.

github-actions[bot] avatar Mar 20 '25 17:03 github-actions[bot]

Thanks! As a reminder: any records displayed in repeaters need to have unique primary keys for us to be able to track them, including pivot records

danharrin avatar Jun 21 '25 13:06 danharrin