collections->getById($collection); abort_if($collectionData === null, Response::HTTP_NOT_FOUND); $listing = CategoryListing::fromRequest(request()); $filters = $listing->filters($collectionData['id']); $perPage = 12; // Listing/filtering reads from the Meilisearch index via ProductService, // not Eloquent — see Modules\Core\Catalog\Services\ProductService. list() returns a // real LengthAwarePaginator of plain arrays (already localized/flattened), // not Product models. Sort/filter/page all come from the query string via // CategoryListing, which is the single source of truth for that state. $products = $this->products->list( filters: $filters, perPage: $perPage, page: $listing->page, sort: $listing->sort, )->through(fn (array $product) => [ 'name' => $product['name'], 'price' => $product['price'], 'image' => $product['media'][0]['url'] ?? null, 'href' => route('product.show', ['id' => $product['id']]), ])->appends($listing->query(['page' => null])); // Slider bounds — the price span of everything matching the *other* // filters (priceRange() drops the price filter itself, so the handles // don't collapse to whatever's already selected). Whole euros. $priceRange = $this->products->priceRange($filters); $priceFloor = $priceRange['min'] !== null ? (int) floor($priceRange['min']) : null; $priceCeil = $priceRange['max'] !== null ? (int) ceil($priceRange['max']) : null; // A price param is only a real filter if it's tighter than the bounds — // drives whether the "clear" link shows. $priceFiltered = ($listing->minPrice !== null && $listing->minPrice > ($priceFloor ?? PHP_INT_MIN)) || ($listing->maxPrice !== null && $listing->maxPrice < ($priceCeil ?? PHP_INT_MAX)); return view('category.show', [ 'collection' => $collectionData, 'products' => $products, 'listing' => $listing, 'priceFloor' => $priceFloor, 'priceCeil' => $priceCeil, 'priceFiltered' => $priceFiltered, ]); } }