Muhammad Fareed Khan
Muhammad Fareed Khan

Reputation: 29

afterStateUpdate on Repeater not working filament laravel

Hello i am facing an issue in filament v3.I am making an order form using repeater in which there is relation with product table. The thing i want to do is by selecting product in select the other fields that i created outside the repeater should update accordingly.So I am using afterStateUpdated on repeater. I changes the fields inside the repeater but the fields in the other section outside the repeater are not updating. heres the code:

Repeater::make('products')
                    ->relationship()

                    ->schema([
                        Select::make('product_id')
                            ->label("Products")
                            ->relationship('product','name')
                            ->options(
                                $products->mapWithKeys(function (Product $product) {
                                    return [$product->id => sprintf('%s ($%s)', $product->name, $product->price)];
                                })
                            )
                            ->disableOptionWhen(function ($value, $state, Get $get) {
                                return collect($get('../*.product_id'))
                                    ->reject(fn($id) => $id == $state)
                                    ->filter()
                                    ->contains($value);
                            })
                            ->required()

                            ->createOptionForm([
                                Forms\Components\FileUpload::make('image')
                                    ->image()
                                    ->required(),
                                Forms\Components\TextInput::make('name')
                                    ->required()
                                    ->maxLength(255),
                                Forms\Components\RichEditor::make('description')
                                    ->required()
                                    ->maxLength(65535)
                                    ->columnSpanFull(),
                                Forms\Components\TextInput::make('sku')
                                    ->label('SKU')
                                    ->required()
                                    ->maxLength(255),
                                Forms\Components\TextInput::make('price')
                                    ->required()
                                    ->maxLength(255)
                                    ->numeric(),
                                Forms\Components\TextInput::make('qty')
                                    ->required()
                                    ->maxLength(255)
                                    ->numeric(),
                                Forms\Components\Select::make('availablity')
                                    ->options([
                                        "yes" => "Yes",
                                        'no' => "No"
                                    ])
                                    ->required(),
                                Forms\Components\Select::make('brands')
                                    ->relationship('brands', 'name')
                                    ->required()
                                    ->multiple()
                                    ->preload()
                                    ->createOptionForm([
                                        Forms\Components\TextInput::make('name')
                                            ->required()
                                            ->maxLength(255),
                                        Forms\Components\Toggle::make('active')
                                            ->required(),
                                    ]),
                                Forms\Components\Select::make('categories')
                                    ->relationship('categories', 'name')
                                    ->required()
                                    ->multiple()
                                    ->preload()
                                    ->createOptionForm([
                                        Forms\Components\TextInput::make('name')
                                            ->required()
                                            ->maxLength(255),
                                        Forms\Components\TextInput::make('description')
                                            ->required()
                                            ->maxLength(255),
                                        Forms\Components\Toggle::make('active')
                                            ->required(),
                                    ]),
                                Forms\Components\Select::make('warehouse_id')
                                    ->relationship('warehouse', 'name')
                                    ->required()
                                    ->createOptionForm([
                                        Forms\Components\TextInput::make('name')
                                            ->required()
                                            ->maxLength(255),
                                        Toggle::make('active')
                                    ])
                            ])
                            // ->reactive()
                            // ->afterStateUpdated(function ($state, callable $set) {
                            //     $product = Product::find($state);
                            //     if ($product) {

                            //         $set('product_id', $state);
                            //         $set('price', $product->price);
                            //     }
                            // })

                            ->columnSpan([
                                "md" => 5
                            ]),
                        TextInput::make('amount')
                            ->numeric()
                            ->required()
                            ->reactive()
                            ->label('Quantity')
                            // ->afterStateUpdated(function ($state, callable $set, Get $get) {
                            //     $product = Product::find($get('product_id'));
                            //     if(isset($product->price)){
                            //         $set('price', $product->price *$state);
                            //     }

                            // })

                            ->default(1)
                            ->columnSpan([
                                "md" => 3
                            ]),
                        TextInput::make('price')
                            ->disabled()
                            ->numeric()
                            ->minValue('1')
                            ->required()
                            ->default(1)
                            ->dehydrated()
                            ->columnSpan([
                                "md" => 2
                            ]),

                    ])->columns(["md" => 10])
                    ->live()
                    // After adding a new row, we need to update the totals
                    ->afterStateUpdated(fn ($state) => dd($state))
                    // After deleting a row, we need to update the totals
                    ->deleteAction(
                        fn(Action $action) => $action->after(fn(Get $get, Set $set) => self::updateTotals($get, $set)),
                    )
                    // Disable reordering
                    ->reorderable(false)
            ])->collapsible(),

the other section code is here:

Section::make()
            ->columns(1)
            ->maxWidth('1/2')->schema([
                TextInput::make('subtotal')
                ->numeric()
                ->readOnly()
                ->afterStateHydrated(function (Get $get, Set $set) {
                    self::updateTotals($get, $set);
                }),
                // TextInput::make('taxes')->required()->numeric()->default()
                TextInput::make('total')->readOnly()->numeric()
            ])

and the function updateTotals is here:

public static function updateTotals(Get $get, Set $set): void

{ // Retrieve all selected products and remove empty rows $selectedProducts = collect($get('products'))->filter(fn($item) => !empty($item['product_id']) && !empty($item['amount']));

// Retrieve prices for all selected products
$prices = Product::find($selectedProducts->pluck('product_id'))->pluck('price', 'id');

// Calculate subtotal based on the selected products and quantities
$subtotal = $selectedProducts->reduce(function ($subtotal, $product) use ($prices) {
    return $subtotal + ($prices[$product['product_id']] * $product['amount']);
}, 0);

// Update the state with the new values
$set('subtotal', number_format($subtotal, 2, '.', ''));
$set('total', number_format($subtotal + ($subtotal * ($get('taxes') / 100)), 2, '.', ''));

}

i have taken this code from laravel Daily heres the link:code picked from here

Upvotes: 1

Views: 5881

Answers (3)

Maka
Maka

Reputation: 663

I can see that you've picked up same example used on Laravel Daily. I just had same situation and if you check comments on blog post https://laraveldaily.com/post/filament-repeater-live-calculations-on-update you will see suggested example using LiveWire. Issue is mainly in statePath for your livewire state. Check example below and modify to your needs. Check comments on link above for details on where the problem was and how it's resolved. There are always different ways.

LiveWire is way to go:


public static function updateTotals(Get $get, $livewire): void
    {
        // Retrieve the state path of the form. Most likely it's `data` but it could be something else.
        $statePath = $livewire->getFormStatePath();

        $products = data_get($livewire, $statePath . '.quoteProducts');
        if (collect($products)->isEmpty()) {
            return;
        }
        $selectedProducts = collect($products)->filter(fn($item) => !empty($item['product_id']) && !empty($item['quantity']));

        $prices = collect($products)->pluck('price', 'product_id');

        $subtotal = $selectedProducts->reduce(function ($subtotal, $product) use ($prices) {
            return $subtotal + ($prices[$product['product_id']] * $product['quantity']);
        }, 0);

        data_set($livewire, $statePath . '.subtotal', number_format($subtotal, 2, '.', ''));
        data_set($livewire, $statePath . '.total', number_format($subtotal + ($subtotal * (data_get($livewire, $statePath . '.taxes') / 100)), 2, '.', ''));
    }

Assuming that your "Total" field is like this and passing in $livewire as well.


Forms\Components\TextInput::make('subtotal')
                ->numeric()
                // Read-only, because it's calculated
                ->readOnly()
                ->prefix('€')
                // This enables us to display the subtotal on the edit page load
                ->afterStateHydrated(function (Get $get, Set $set, \Livewire\Component $livewire) {
                    self::updateTotals($get, $set, $livewire);
                }),

Upvotes: 0

Muhammad Fareed Khan
Muhammad Fareed Khan

Reputation: 29

Instead of using stateUpdate On repeater i have used placeholder and inside placeholder i am getting repeater fields and achieving the required results as follow

return $form
        ->schema([
            Section::make('Customer Details')->schema([
                Forms\Components\Select::make('customer_id')
                    ->relationship('customer', 'name')
                    ->preload()
                    ->createOptionForm([
                        Forms\Components\TextInput::make('name')
                            ->required()
                            ->maxLength(255),
                        Forms\Components\TextInput::make('phone_no')
                            ->required()
                            ->maxLength(255),
                        Forms\Components\TextInput::make('email')
                            ->email()
                            ->required()
                            ->maxLength(255),
                    ])
                    ->required(),
                Forms\Components\TextInput::make('invoice_no')
                    ->required()
                    ->maxLength(255),
                Forms\Components\DateTimePicker::make('order_date')
                    ->required(),
                Forms\Components\Select::make('order_status')
                    ->options([
                        "received" => "Received",
                        "pending" => "Pending",
                        "ordered" => "Ordered"

                    ])
                    ->required(),

                FileUpload::make('document'),
                TextInput::make('total')->hidden()->disabled()
                ->dehydrated()
            ])->collapsed()->columns(2),

            Section::make('Product Details')->schema([
                Repeater::make('products')
                    ->relationship()
                    ->schema([
                        Select::make('product_id')
                            ->label("Products")
                            ->relationship('product','name')
                            ->live()

                            ->required()

                            ->createOptionForm([
                                Forms\Components\FileUpload::make('image')
                                    ->image()
                                    ->required(),
                                Forms\Components\TextInput::make('name')
                                    ->required()
                                    ->maxLength(255),
                                Forms\Components\RichEditor::make('description')
                                    ->required()
                                    ->maxLength(65535)
                                    ->columnSpanFull(),
                                Forms\Components\TextInput::make('sku')
                                    ->label('SKU')
                                    ->required()
                                    ->maxLength(255),
                                Forms\Components\TextInput::make('price')
                                    ->required()
                                    ->maxLength(255)
                                    ->numeric(),

                                Forms\Components\Select::make('availablity')
                                    ->options([
                                        "yes" => "Yes",
                                        'no' => "No"
                                    ])
                                    ->required(),
                                Forms\Components\Select::make('brands')
                                    ->relationship('brands', 'name')
                                    ->required()
                                    ->multiple()
                                    ->preload()
                                    ->createOptionForm([
                                        Forms\Components\TextInput::make('name')
                                            ->required()
                                            ->maxLength(255),
                                        Forms\Components\Toggle::make('active')
                                            ->required(),
                                    ]),
                                Forms\Components\Select::make('categories')
                                    ->relationship('categories', 'name')
                                    ->required()
                                    ->multiple()
                                    ->preload()
                                    ->createOptionForm([
                                        Forms\Components\TextInput::make('name')
                                            ->required()
                                            ->maxLength(255),
                                        Forms\Components\TextInput::make('description')
                                            ->required()
                                            ->maxLength(255),
                                        Forms\Components\Toggle::make('active')
                                            ->required(),
                                    ]),
                                Forms\Components\Select::make('warehouse_id')
                                    ->relationship('warehouse', 'name')
                                    ->required()
                                    ->createOptionForm([
                                        Forms\Components\TextInput::make('name')
                                            ->required()
                                            ->maxLength(255),
                                        Toggle::make('active')
                                    ])
                            ])
                            ->reactive()
                            ->afterStateUpdated(function ($state, callable $set) {
                                $product = Product::find($state);
                                if ($product) {

                                    $set('product_id', $state);
                                    $set('price', $product->price);
                                }
                            })

                            ->columnSpan([
                                "md" => 5
                            ]),
                        TextInput::make('amount')
                            ->numeric()
                            ->required()
                            ->live(onBlur:true)
                            ->label('Quantity')
                            ->afterStateUpdated(function ($state, callable $set, Get $get) {
                                $product = Product::find($get('product_id'));
                                if(isset($product->price)){
                                    $set('price', $product->price *$state);
                                }

                            })

                            ->default(1)
                            ->columnSpan([
                                "md" => 3
                            ]),
                        TextInput::make('price')
                            ->disabled()
                            ->numeric()
                            ->required()
                            ->dehydrated()
                            ->columnSpan([
                                "md" => 2
                            ]),

                    ])->columns(["md" => 10])
                    ->live()
                    ->defaultItems(0)
            ])->collapsible(),

            Section::make()->schema([
                TextInput::make("amount")
                            ->label("Total Price")
                            ->disabled()
                            ->placeholder(function (Get $get , callable $set) {
                                $fields = $get('products');
                                $sum = 0;
                                foreach($fields as $field){
                                    // foreach ($field as $value){
                                    //     if ($value == ""){
                                    //         $value = 0;
                                    //     }
                                    //     elseif(is_numeric($value)){
                                    //         $sum += $value;
                                    //     }

                                    // }
                                    $sum+=$field['price'];
                                }
                                $set('total',$sum);
                                return $sum;
                            }),
                        //

            ]),

        ]);

Upvotes: 0

Nándor
Nándor

Reputation: 46

On your Repeater you have:

->afterStateUpdated(fn ($state) => dd($state))

Is it the problem?

Upvotes: 1

Related Questions