rappasoft / laravel-livewire-tables

A dynamic table component for Laravel Livewire
https://rappasoft.com/docs/laravel-livewire-tables/v2/introduction
MIT License
1.74k stars 330 forks source link

Table not reloading on the First reload #1092

Closed BenOussama180 closed 1 year ago

BenOussama180 commented 1 year ago

so i have a table with a add to cart button, when the button is clicked and the product is added to cart , it should switch to a gray bag ( so it means that the button can no longer be clicked and that the product is already in cart ) on the first click ( request ) the table doesnt get Updated, but the second click updates the UI why is that & how can i fix it ?

dont activate sound on the video theres a loud music forgot about it :)

https://user-images.githubusercontent.com/81111919/223729773-02cf877e-45dd-43cc-9c6b-1497394986c6.mp4

lrljoe commented 1 year ago

If you click once, then change the sort order (or activate a filter), does the colour change? If so, you need to emit a refresh to the datatable after processing the click.

BenOussama180 commented 1 year ago

i tried emiting an event to refresh, but when i do the table refreshes twice

lrljoe commented 1 year ago

In your DataTableComponent, try adding something like the following

    /**
     * @var array<mixed>
     */
    protected $listeners = ['refreshCustomerLinksTable' => '$refresh'];

Then emit "refreshCustomerLinksTable", and it should just refresh the DataTable the one time!

BenOussama180 commented 1 year ago

yes thats what i have , the thing is i have the function add to cart inside the table component so i shouldnt be needing to refresh manually, but it only blocks on first request after that the color changes, even in the first request i can see the table going into the loading state it drops opacity, but the color only changes from 2nd request

lrljoe commented 1 year ago

Right, looking at what's happening with your table, I think there's something slightly odd going on.

How have you configured your add-to-cart column? Is it a Component Column, a Button Column? I'm assuming you're using a custom view for it regardless.

Can you post your DataTableComponent here, please encapsulate it in code tags, and I'll see if I can replicate it on my end. If you have a public-available component, then a link to that will help massively too!

BenOussama180 commented 1 year ago

Right, looking at what's happening with your table, I think there's something slightly odd going on. How have you configured your add-to-cart column? Is it a Component Column, a Button Column? I'm assuming you're using a custom view for it regardless. Can you post your DataTableComponent here, please encapsulate it in code tags, and I'll see if I can replicate it on my end. If you have a public-available component, then a link to that will help massively too!

<?php

namespace App\Http\Livewire\Customers\Tables\New;

use App\Models\Link;
use App\Models\Project;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Database\Eloquent\Builder;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Filters\{DateFilter, NumberFilter, SelectFilter, TextFilter, MultiSelectDropdownFilter};

class CustomerLinksPage extends DataTableComponent
{
    protected $model = Link::class;

    public array $arrayOfCountries = [];
    public array $arrayOfProjects = [];

    protected $listeners = [
        'addToCart',
        'link-removed' => '$refresh',
        // 'link-added-to-cart-counter' => '$refresh',
    ];

    public function mount()
    {
        $this->arrayOfCountries = Link::select('country')->distinct()->pluck('country')->toArray();
        $this->arrayOfProjects = Project::forAuthCustomer()->select('project_name')->distinct()->pluck('project_name')->toArray();
    }

    public function configure(): void
    {
        $this->setPrimaryKey('id')
            ->setSingleSortingDisabled()
            ->setOfflineIndicatorEnabled()
            ->setQueryStringDisabled()
            ->setEagerLoadAllRelationsEnabled()
            ->setFilterLayoutSlideDown()
            ->setAdditionalSelects(['links.id as id'])
            ->setTrAttributes(function ($row, $index) {
                return [
                    'class' => 'hover:bg-gray-100',
                ];
            })
            ->setThAttributes(function (Column $column) {
                return [
                    // 'class' => 'text-center',
                ];
            })
            ->setTdAttributes(function (Column $column, $row, $columnIndex, $rowIndex) {
                return [
                    // 'class' => 'text-center',
                ];
            });
    }

    public function addToCart(Link $link)
    {
        if (Cart::search(function ($cartItem, $rowId) use ($link) {
            return $cartItem->id === $link->id;
        })->isNotEmpty()) {
            $this->dispatchBrowserEvent('bottom_toast', ['message' => 'Link Already in Cart!']);
            return;
        }
        Cart::add(
            $link->id,
            $link->site,
            1,
            marginCustomerPrice($link->price, $link->currency),
        );
        //reset cart quantity to 1 even after multiple clicks
        Cart::search(function ($cartItem, $rowId) use ($link) {
            if ($cartItem->id === $link->id) {
                Cart::update($rowId, 1);
            };
        });
        $this->dispatchBrowserEvent('bottom_toast', ['message' => "<b>{$link->site}</b> Added To Cart"]);
        $this->emit('link-added-to-cart-counter');
        session()->forget('orders');
    }

    public function builder(): Builder
    {
        return Link::query()
            ->with(['orders', 'orders.projects'])
            ->groupBy('site');
    }

    public function columns(): array
    {
        $cart = Cart::content();
        return [
            Column::make("Cart")
                ->label(
                    function ($row, Column $column) use ($cart) {
                        if ($cart->where('id', $row->id)->isEmpty()) {
                            return '<svg wire:click="addToCart(' . $row->id . ')" class="w-5 h-5 text-blue-600 cursor-pointer" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 00-16.536-1.84M7.5 14.25L5.106 5.272M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" />
                          </svg>';
                        } else {
                            return '<svg  class="w-5 h-5 text-gray-600 cursor-pointer" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5V6a3.75 3.75 0 10-7.5 0v4.5m11.356-1.993l1.263 12c.07.665-.45 1.243-1.119 1.243H4.25a1.125 1.125 0 01-1.12-1.243l1.264-12A1.125 1.125 0 015.513 7.5h12.974c.576 0 1.059.435 1.119 1.007zM8.625 10.5a.375.375 0 11-.75 0 .375.375 0 01.75 0zm7.5 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
                          </svg>';
                        }
                    }
                )
                ->html(),

            Column::make("Site", "site")
                ->sortable()
                ->searchable(),

            Column::make("Price", "price")
                ->sortable()
                ->format(function ($value, $column, $row) {
                    return marginCustomerPrice($value) . ' €';
                }),

            Column::make("Authortiy Score", "authority_score")
                ->sortable(),

            Column::make("Organic Search Traffic", "organic_search_traffic")
                ->sortable(),

            Column::make("Purchased Before")
                ->sortable()
                ->eagerLoadRelations()
                ->label(
                    function ($row, Column $column) {
                        if ($row->orders->where('customer_id', auth()->id())->isEmpty()) {
                            return '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-red-500">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                          </svg>';
                        } else {
                            $stringList = getPurchasedProjectsNames($row->orders->where('customer_id', auth()->id()));

                            return '<svg x-tooltip.raw="' . $stringList . '" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-green-500">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12c0 1.268-.63 2.39-1.593 3.068a3.745 3.745 0 01-1.043 3.296 3.745 3.745 0 01-3.296 1.043A3.745 3.745 0 0112 21c-1.268 0-2.39-.63-3.068-1.593a3.746 3.746 0 01-3.296-1.043 3.745 3.745 0 01-1.043-3.296A3.745 3.745 0 013 12c0-1.268.63-2.39 1.593-3.068a3.745 3.745 0 011.043-3.296 3.746 3.746 0 013.296-1.043A3.746 3.746 0 0112 3c1.268 0 2.39.63 3.068 1.593a3.746 3.746 0 013.296 1.043 3.746 3.746 0 011.043 3.296A3.745 3.745 0 0121 12z" />
                          </svg>';
                        }
                    }
                )
                ->html(),

            Column::make("Country", "country")
                ->sortable()
                ->searchable(),

            Column::make("Industry", "industry")
                ->sortable()
                ->searchable(),
        ];
    }
    public function filters(): array
    {
        return [
            MultiSelectDropdownFilter::make('Country', 'country')
                ->options(
                    $this->arrayOfCountries
                )
                ->filter(function (Builder $builder, array  $value) {
                    $values = array_map(function ($value) {
                        return $this->arrayOfCountries[$value];
                    }, $value);
                    $builder->whereIn('country', $values);
                }),

            //AS
            NumberFilter::make('Min. AS')
                ->config([
                    'min' => '0',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('authority_score', '>=', (int)$value);
                }),
            NumberFilter::make('Max. AS')
                ->config([
                    'min' => '0',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('authority_score', '<=', (int)$value);
                }),

            //Price
            NumberFilter::make('Min. Price')
                ->config([
                    'min' => '0',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('price', '>=', (int)$value);
                }),
            NumberFilter::make('Max. Price')
                ->config([
                    'min' => '0',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('price', '<=', (int)$value);
                }),

            TextFilter::make('Website Category')
                ->config([
                    'placeholder' => 'Select category',
                    'maxlength' => '25',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('industry', 'like', '%' . $value . '%');
                }),

            //Traffic
            NumberFilter::make('Min. Traffic')
                ->config([
                    'min' => '0',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('organic_search_traffic', '>=', (int)$value);
                }),
            NumberFilter::make('Max. Traffic')
                ->config([
                    'min' => '0',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('organic_search_traffic', '<=', (int)$value);
                }),

            TextFilter::make('Website')
                ->config([
                    'placeholder' => 'Search Site',
                    'maxlength' => '25',
                ])
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('site', 'like', '%' . $value . '%');
                }),

            // add a date from and a date to filter
            DateFilter::make('Date From', 'date_from')
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('created_at', '>=', $value);
                }),
            DateFilter::make('Date To', 'date_to')
                ->filter(function (Builder $builder, string $value) {
                    $builder->where('created_at', '<=', $value);
                }),

            SelectFilter::make('Purchased Before')
                ->options([
                    '' => 'All',
                    'yes' => 'Yes',
                    'no' => 'No',
                ])
                ->filter(function (Builder $builder, string $value) {
                    if ($value === 'yes') {
                        $builder->whereHas('orders', function ($query) {
                            $query->where('customer_id', auth()->id());
                        });
                    } elseif ($value === 'no') {
                        $builder->whereDoesntHave('orders', function ($query) {
                            $query->where('customer_id', auth()->id());
                        });
                    }
                }),
            MultiSelectDropdownFilter::make('Exclude Projects', 'exclude_projects')
                ->options(
                    $this->arrayOfProjects
                )
                ->filter(function (Builder $builder, array  $value) {
                    $values = array_map(function ($value) {
                        return $this->arrayOfProjects[$value];
                    }, $value);

                    $builder->whereDoesntHave('orders', function ($query) use ($values) {
                        $query->whereHas('projects', function ($query) use ($values) {
                            $query->whereIn('project_name', $values);
                        });
                    });
                }),

        ];
    }
}
lrljoe commented 1 year ago

A few options here!

One is to add in an emitSelf to a refresh listener at the end of your addToCart to get the component to refresh.

Slightly more complex - toggle the icon as part of the click action.

More complex - but probably the cleanest approach, is to add an x-data to the table, wire entangle it against a public variable on the component (set to an array of "added" IDs), and use that to determine which cart icon to use. This way, state is maintained, cart icon will change instantly when it is added to the cart, you are only querying the cart once for a list of IDs, so your server-side memory usage is reduced. This will also give a faster UX but it is slightly more complex to implement!

So steps needed are: 1) Have a public array containing the valid IDs 2) Wire this to an AlpineJS element 3) Use a view (or similar) for the cart icon, and use x-show or x-if to determine which icon to display.

Let me know if this needs a bit more clarification!

BenOussama180 commented 1 year ago

i would go for the slightly more complex , but i dont really know what should i do , should i listen to an event with alpine js on click and switch icon on it ? or do you have any better idea

lrljoe commented 1 year ago

Entangle the cart IDs, and use an x-template.

I'll add an example into your demo repo when I get a mo (probably tomorrow), but it'll just be a poc as I don't think the cart elements are properly in the demo.

BenOussama180 commented 1 year ago

yea i tried to add the cart package that m using in prod at first but its not compatible with laravel 10 yet so ijust did a random thing

lrljoe commented 1 year ago

Example in your repo for you to take a look at.

lrljoe commented 1 year ago

@BenOussama180 did this get sorted? If so can you close this one otherwise let me know what the issue is please 😀

BenOussama180 commented 1 year ago

my bad forgot abt it !