rappasoft / laravel-livewire-tables

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

[Bug]: Getting htmlspecialchars() error on logs #1989

Open NoxzMSTR opened 2 days ago

NoxzMSTR commented 2 days ago

What happened?

htmlspecialchars(): Argument #1 ($string) must be of type string, array given {"view":{"view":"/var/www/vhosts/Laravel/vendor/rappasoft/laravel-livewire-tables/resources/views/components/tools/filter-pills.blade.php","data":[]}

How to reproduce the bug

Dont know why it is creating this error but it is saying this on production not on local

Package Version

No response

PHP Version

None

Laravel Version

No response

Alpine Version

No response

Theme

None

Notes

No response

Error Message

No response

lrljoe commented 18 hours ago

Need the specific versions to be able to assist

Also an example of what you've got set in your table.

I'm not seeing this error in any of my demo tables. nor does it appear in any of the tests, so I'll need to understand how to reproduce it

NoxzMSTR commented 16 hours ago

livewire table vers v3.4.22

class QuoteLogsDatatable extends DataTableComponent {

public $showQuotations = 0;
public  $hideQuotationTypeFilter = true;
public  $status;
public  $categories;
public  $priceTemplate;
public  $priceCategory;

public function mount()
{
    $cat = Category::all();
    foreach ($cat as $key => $value) {
        $this->categories[$value->id] = $value->categoryTitle;
    }
    if (request('table-search') && request('type') == 'staffQuote') {
        $this->showQuotations = 0;
    }
    $this->priceTemplate = OrderPriceTemplate::all();
    foreach ($this->priceTemplate as $key => $value) {
        $this->priceCategory[$value->zoneID] = ['category' => $value->category, 'color' => $value->zoneColor];
    }
}

public function builder(): Builder
{
    if ($this->showQuotations == 1) {
        return Quotation::query();
    } else {
        $quoteLogs = QuotationLogs::query();
        if ($this->status) {
            return $quoteLogs->select([DB::raw('DISTINCT quotation_logs.id'), DB::raw("CAST(JSON_EXTRACT(data, '$.total') AS DECIMAL(10,2)) AS total")])->whereIn('status', explode(',', $this->status));
        } else {
            return $quoteLogs->select([DB::raw('DISTINCT quotation_logs.id'), DB::raw("CAST(JSON_EXTRACT(data, '$.total') AS DECIMAL(10,2)) AS total")])->whereNotIn('status', ['ArchiveQuote']);
        }
    }
}

public function configure(): void
{
    if ($this->showQuotations == 1) {
        $this->setPrimaryKey('id');
    } else {
        $this->setPrimaryKey('id');
    }

    // $this->setConfigurableAreas([
    //     'toolbar-right-end' => 'livewire.admin.order.partials.quotation.list.custom-export-btn',
    // ]);

    $this->setDefaultSort('id', 'desc');

    $this->setFilterLayout('slide-down');
}

public function columns(): array
{
    $this->dispatch('load-quote-detail-script');

    if ($this->showQuotations == 1) {
        return $this->setQuotationColumns();
    } else {
        return $this->setQuotationLogsColumns();
    }
}

private function setQuotationColumns(): array
{
    return [
        Column::make("Id", "id")
            ->sortable(),
        Column::make("Collection PostCode", "collectionPostcode")->searchable()
            ->sortable()
            ->secondaryHeaderFilter('cpostcode')
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'quote', 'type' => 'collectionPostcode', 'value' => $value]);
            }),
        Column::make("Delivery PostCode", "deliveryPostcode")
            ->sortable()
            ->secondaryHeaderFilter('dpostcode')
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'quote', 'type' => 'deliveryPostcode', 'value' => $value]);
            }),
        Column::make("Items", "itemData")
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'quote', 'type' => 'items', 'value' => $value]);
            })->collapseAlways(),
        Column::make("Dates", "collectionDate")
            ->sortable()
            ->format(function ($value, $row, $column) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'quote', 'type' => 'dates', 'row' => $row]);
            })->collapseAlways(),
        Column::make("Status", "status")
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'quote', 'type' => 'status', 'value' => $value]);
            })->collapseOnMobile(),
        DateColumn::make("Created at", "created_at")
            ->sortable()->collapseOnMobile()->outputFormat('Y-m-d H:i:s'),
        Column::make("Action", "id")->searchable()
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'quote', 'type' => 'action', 'id' => $value]);
            }),
    ];
}

private function setQuotationLogsColumns(): array
{
    return [
        Column::make("Id", "id")
            ->sortable(fn(Builder $query, string $direction) => $query->orderBy('quotation_logs.id', $direction))->searchable(fn(Builder $query, $searchTerm) =>  $query->orWhere('quotation_logs.id', 'like', '%' . $searchTerm . '%')->orWhereIn('quotation_logs.id',  explode(',', $searchTerm))),
        Column::make("Collection PostCode", "data")->searchable()
            ->sortable()
            ->secondaryHeaderFilter('cpostcode')
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'collectionPostcode', 'value' => $value, 'ptCategory' =>    $this->priceCategory]);
            }),
        Column::make("Delivery PostCode", "data")
            ->sortable()
            ->secondaryHeaderFilter('dpostcode')
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'deliveryPostcode', 'value' => $value, 'ptCategory' =>    $this->priceCategory]);
            }),
        Column::make("Items", "data")
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'items', 'value' => $value, 'categories' => $this->categories]);
            })->collapseOnMobile(),
        Column::make("Dates", "data")
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'dates', 'value' => $value]);
            })->collapseAlways(),
        Column::make("Status", "status")
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'status', 'value' => $value]);
            })->collapseOnMobile(),
        Column::make("Total Amount")
            ->sortable(fn(Builder $query, string $direction) => $query->orderBy('total', $direction))
            ->label(
                fn($row, Column $column) => view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'totalAmount', 'value' => $row['data']])

            )->html()
            ->collapseOnMobile(),
        Column::make("Submitted By", "user.name")->collapseOnMobile()
            ->sortable(),
        DateColumn::make("Created at", "created_at")
            ->sortable()->collapseOnMobile()->outputFormat('Y-m-d H:i:s'),
        Column::make("Action", "id")->searchable()
            ->sortable()
            ->format(function ($value, $column, $row) {
                // Use a Blade view for custom actions
                return view('livewire.admin.order.partials.quotation.list.list-data-view', ['list' => 'logs', 'type' => 'action', 'id' => $value]);
            }),
    ];
}

public function filters(): array
{
    if ($this->showQuotations == 1) {
        return $this->setQuotationFilter();
    } else {
        return $this->setQuotationLogsFilter();
    }
}

private function setQuotationFilter(): array
{
    $quoteFilter =  $this->setCustomFilterValue(1);
    return [
        MultiSelectFilter::make('Status', 'status')
            ->options(
                Quotation::query()
                    ->orderBy('status')
                    ->get()
                    ->keyBy('status')
                    ->map(fn($logs) => $logs->status)
                    ->toArray()
            )
            ->filter(function (Builder $builder, array $values) {
                $builder->whereIn('status', $values);
            })
            ->setFirstOption('All Status'),
        DateFilter::make('Date From')
            ->config([
                'pillFormat' => 'F j, Y', // An aria-friendly date format
                'min' => '1991-01-01', // The earliest acceptable date
                'max' => Carbon::now()->addYear()->format('Y-m-d'), // The latest acceptable date
                'placeholder' => 'Enter Date From', // A placeholder value
            ])

            ->filter(function (Builder $builder, $value) {
                // Expects an array.
                $builder->whereDate('created_at', '>=', $value);
            }),
        DateFilter::make('Date To')
            ->config([
                'pillFormat' => 'F j, Y', // An aria-friendly date format
                'min' => '1991-01-01', // The earliest acceptable date
                'max' => Carbon::now()->addYear()->format('Y-m-d'), // The latest acceptable date
                'placeholder' => 'Enter Date To', // A placeholder value
            ])

            ->filter(function (Builder $builder, $value) {
                // Expects an array.
                $builder->whereDate('created_at', '<=', $value);
            }),

        SelectFilter::make('Quote Logs')
            ->setFilterPillTitle('Quote Logs')
            ->setFilterPillValues([
                '1' => 'Booking Form',
                '0' => 'Staff Quotes',
            ])
            ->options([
                '' => 'None',
                '1' => 'Booking Form',
                '0' => 'Staff Quotes',

            ])
            ->filter(function (Builder $builder, string $value) {
                $previousStat = $this->showQuotations;
                if ($value === '1') {
                    $this->showQuotations = 1;
                } elseif ($value === '0') {
                    $this->showQuotations = 0;
                }
                $this->dispatch('refresh-quote-table', previousStat: $previousStat, showQuotations: $this->showQuotations);
            }),
        TextFilter::make('cpostcode')
            ->config([
                'placeholder' => 'Search Collection Postcode...',
                'maxlength' => '25',
            ])
            ->filter(function (Builder $builder, $value) {
                $builder->whereRaw("REPLACE(UPPER(`collectionPostcode`), ' ', '') LIKE ?", ['%' . $value . '%']);
            })->hiddenFromAll(),
        TextFilter::make('dpostcode')
            ->config([
                'placeholder' => 'Search Delivery Postcode...',
                'maxlength' => '25',
            ])
            ->filter(function (Builder $builder, $value) {
                $builder->whereRaw("REPLACE(UPPER(`deliveryPostcode`), ' ', '') LIKE ?",  ['%' . $value . '%']);
            })->hiddenFromAll()
    ];
}

private function setQuotationLogsFilter(): array
{
    $quoteLogFilter = $this->setCustomFilterValue(0);
    $ptc = [];
    $ptc[''] = '';
    foreach (range('A', 'Z') as $letter) {
        $ptc[$letter] = $letter;
    }
    $filters =  [
        MultiSelectFilter::make('Status', 'status')
            ->setFirstOption('All Status')
            ->options(
                $quoteLogFilter['status']
            )
            ->filter(function (Builder $builder, array $values) {
                $builder->whereIn('status', $values);
            }),
        DateFilter::make('Date From')
            ->config([
                'pillFormat' => 'F j, Y', // An aria-friendly date format
                'min' => '1991-01-01', // The earliest acceptable date
                'max' => Carbon::now()->addYear()->format('Y-m-d'), // The latest acceptable date
                'placeholder' => 'Enter Date From', // A placeholder value
            ])

            ->filter(function (Builder $builder, $value) {
                // Expects an array.
                $builder->whereDate('quotation_logs.created_at', '>=', $value);
            }),
        DateFilter::make('Date To')
            ->config([
                'pillFormat' => 'F j, Y', // An aria-friendly date format
                'min' => '1991-01-01', // The earliest acceptable date
                'max' => Carbon::now()->addYear()->format('Y-m-d'), // The latest acceptable date
                'placeholder' => 'Enter Date To', // A placeholder value
            ])

            ->filter(function (Builder $builder, $value) {
                // Expects an array.
                $builder->whereDate('quotation_logs.created_at', '<=', $value);
            }),
        SelectFilter::make('Zones')
            ->options($quoteLogFilter['collection_zone'])
            ->setFilterPillTitle('Zone')
            ->setFilterPillValues($quoteLogFilter['collection_zone'])
            ->filter(function (Builder $builder, string $value) {
                $builder->where(function ($q) use ($value) {
                    $q->whereJsonContains('data->collection_zone', (int)$value)->orWhereJsonContains('data->delivery_zone', (int)$value);
                });
            }),
        SelectFilter::make('Routes')
            ->options($quoteLogFilter['collection_route'])
            ->setFilterPillTitle('Route')
            ->setFilterPillValues($quoteLogFilter['collection_route'])
            ->filter(function (Builder $builder, string $value) {
                $builder->where(function ($q) use ($value) {
                    $q->whereJsonContains('data->collection_route', $value)->orWhereJsonContains('data->delivery_route', $value);
                });
            }),
        SelectFilter::make('Categories')
            ->options($quoteLogFilter['categories'])
            ->setFilterPillTitle('Categories')
            ->setFilterPillValues($quoteLogFilter['categories'])
            ->filter(function (Builder $builder, string $value) {
                $builder->join(DB::raw('JSON_TABLE(data, "$.items[*]" COLUMNS (value JSON PATH "$")) AS item'), function ($join) {
                    $join->on('quotation_logs.id', '=', 'quotation_logs.id'); // Adjust the join condition if necessary
                })->where(function ($q) use ($value) {
                    $q->where(DB::raw('JSON_EXTRACT(item.value, "$.item_category")'), '=', (int)$value);
                });
            }),
        SelectFilter::make('Users')
            ->options($quoteLogFilter['user'])
            ->setFilterPillTitle('User')
            ->setFilterPillValues($quoteLogFilter['user'])
            ->filter(function (Builder $builder, string $value) {
                $builder->where('userID', $value);
            }),
        SelectFilter::make('Collection Price Category')
            ->options($ptc)
            ->setFilterPillTitle('Collection Price Category')
            ->setFilterPillValues($ptc)
            ->filter(function (Builder $builder, string $value) use ($quoteLogFilter) {
                if (isset($quoteLogFilter['price_categories'][$value])) {
                    $builder->where(function ($q) use ($value, $quoteLogFilter) {
                        foreach ($quoteLogFilter['price_categories'][$value] as $key => $value) {
                            if ($key == 0) {
                                $q->whereJsonContains('data->collection_zone', (int)$value);
                            } else {
                                $q->orWhereJsonContains('data->collection_zone', (int)$value);
                            }
                        }
                    });
                }
            }),
        SelectFilter::make('Delivery Price Category')
            ->options($ptc)
            ->setFilterPillTitle('Delivery Price Category')
            ->setFilterPillValues($ptc)
            ->filter(function (Builder $builder, string $value) use ($quoteLogFilter) {
                if (isset($quoteLogFilter['price_categories'][$value])) {
                    $builder->where(function ($q) use ($value, $quoteLogFilter) {
                        foreach ($quoteLogFilter['price_categories'][$value] as $key => $value) {
                            if ($key == 0) {
                                $q->whereJsonContains('data->delivery_zone', (int)$value);
                            } else {
                                $q->orWhereJsonContains('data->delivery_zone', (int)$value);
                            }
                        }
                    });
                }
            }),

        TextFilter::make('cpostcode')
            ->config([
                'placeholder' => 'Search Collection Postcode...',
                'maxlength' => '25',
            ])
            ->filter(function (Builder $builder, $value) {
                $value = str_replace(' ', '', strtoupper($value));
                $builder->whereRaw("UPPER(REPLACE(JSON_EXTRACT(data, '$.collection_postcode'), ' ', '')) LIKE '%" . $value . "%'");
            })->hiddenFromAll(),
        TextFilter::make('dpostcode')
            ->config([
                'placeholder' => 'Search Delivery Postcode...',
                'maxlength' => '25',
            ])
            ->filter(function (Builder $builder, $value) {
                $value = str_replace(' ', '', strtoupper($value));
                $builder->whereRaw("UPPER(REPLACE(JSON_EXTRACT(data, '$.delivery_postcode'), ' ', '')) LIKE '%" . $value . "%'");
            })->hiddenFromAll(),

    ];
    if ((bool)$this->hideQuotationTypeFilter == false) {
        $filters[] =
            SelectFilter::make('Quote Logs')
            ->options([
                '' => 'None',
                '1' => 'Booking Form',
                '0' => 'Staff Quotes',
            ])
            ->setFilterPillTitle('Quote Logs')
            ->setFilterPillValues([
                '1' => 'Booking Form',
                '0' => 'Staff Quotes',
            ])
            ->filter(function (Builder $builder, string $value) {
                $previousStat = $this->showQuotations;
                if ($value === '1') {
                    $this->showQuotations = 1;
                } elseif ($value === '0') {
                    $this->showQuotations = 0;
                }
                $this->dispatch('refresh-quote-table', previousStat: $previousStat, showQuotations: $this->showQuotations);
            });
    }

    return $filters;
}
private function setCustomFilterValue($showQuotations)
{
    $exPostCode = [];
    if ($showQuotations == 1) {

        $exPostCode['collection_postcode'] = [];
        $exPostCode['delivery_postcode'] = [];

        return $exPostCode;
    } else {
        $collection_zone = Cache::store('file')->remember('collection_zone', 60 * 60, function () {
            return QuotationLogs::select(['data->collection_zone as collection_zone'])->groupBy(['collection_zone'])->get()->toArray();
        });
        $collection_route = Cache::store('file')->remember('collection_route', 60 * 60, function () {
            return QuotationLogs::select(['data->collection_route as collection_route'])->groupBy(['collection_route'])->get()->toArray();
        });
        $status = Cache::store('file')->remember('status', 60 * 60, function () {
            return QuotationLogs::select(['status'])->groupBy(['status'])->get()->toArray();
        });
        $user = Cache::store('file')->remember('user', 60 * 60, function () {
            return QuotationLogs::select(['userID'])->with(['user'])->groupBy(['userID'])->get()->toArray();
        });

        $categories = Cache::store('file')->remember('category', 60 * 60, function () {
            return Category::select(['id', 'categoryTitle'])->whereNotIn('categoryTitle', ['Default'])->get()->toArray();
        });

        $exPostCode['collection_postcode'] = [];
        $exPostCode['delivery_postcode'] = [];
        $exPostCode['collection_zone'][''] = 'All Zones';
        foreach ($collection_zone as $key => $value) {
            $exPostCode['collection_zone'][cleanString(($value['collection_zone']))] = cleanString(($value['collection_zone']));
        }
        $exPostCode['collection_route'][''] = 'All Routes';
        foreach ($collection_route as $key => $value) {
            $exPostCode['collection_route'][cleanString(($value['collection_route']))] = cleanString(($value['collection_route']));
        }
        foreach ($status as $key => $value) {
            $exPostCode['status'][cleanString(($value['status']))] = cleanString(($value['status']));
        }
        $exPostCode['user'][''] = 'All Users';
        foreach ($user as $key => $value) {
            if (isset($value['user'])) {
                $exPostCode['user'][$value['user']['id']] = cleanString($value['user']['name']);
            }
        }
        $exPostCode['categories'][''] = 'All Categories';
        $exPostCode['categories'][0] = 'No Category Assigned';
        foreach ($categories as $key => $value) {
            if (isset($value['categoryTitle'])) {
                $exPostCode['categories'][$value['id']] = cleanString($value['categoryTitle']);
            }
        }
        if (isset($exPostCode['collection_zone'])) {
            ksort($exPostCode['collection_zone']);
        }
        if (isset($exPostCode['collection_route'])) {
            ksort($exPostCode['collection_route']);
        }
        $exPostCode['price_categories']['None'][] = '';
        foreach ($this->priceTemplate as $key => $value) {
            $exPostCode['price_categories'][$value->category][] = $value->zoneID;
        }

        return $exPostCode;
    }
}

public function setCustomFilter($type, $data)
{
    if ($type == 'collectionPostcode') {
        foreach ($data as $key => $value) {
            if ($key == 0) {
                $this->builder->where('data', 'like', '%' . $value . '%');
            } else {
                $this->builder->orWhere('data', 'like', '%' . $value . '%');
            }
        }
    }
    if ($type == 'deliveryPostcode') {
        foreach ($data as $key => $value) {
            if ($key == 0) {
                $this->builder->where('data', 'like', '%' . $value . '%');
            } else {
                $this->builder->orWhere('data', 'like', '%' . $value . '%');
            }
        }
    }
}

public function bulkActions(): array
{
    return [
        'exportExcel' => 'Export',
    ];
}

public function exportExcel()
{
    $logs = $this->getSelected();

    $this->clearSelected();

    if ($this->showQuotations == 1) {
        return Excel::download(new QuotationsExport($logs), 'quotations.xlsx');
    } else {
        return Excel::download(new QuotationLogsExport($logs), 'quotation-logs.xlsx');
    }
}

}