Feat: Updating Product Cards, Listing Pages and SearchController

This commit is contained in:
2026-09-04 13:07:57 +03:00
parent 1f0612861b
commit 72b9abf92f
3 changed files with 50 additions and 107 deletions
+35 -16
View File
@@ -4,41 +4,60 @@
use Closure;
use Modules\Core\Catalog\Enums\ProductSort;
use Modules\Core\Catalog\Services\ProductSearchService;
use Modules\Core\Catalog\Services\ProductService;
/**
* Assembles the data the shared shop listing body (shop/partials/listing.blade.php)
* needs — the product page, price-slider bounds, sort links and the "clear price"
* link. Used by both the category page (scoped to a collection) and the
* all-products page; the `$url` closure turns a query-param array into a URL for
* whichever page is calling, so this class never has to know the route.
* link. Used by the category page (scoped to a collection), the all-products
* page, and the search page (scoped to a query — see $query below); the `$url`
* closure turns a query-param array into a URL for whichever page is calling,
* so this class never has to know the route.
*/
final class ProductListingPage
{
private const PER_PAGE = 12;
public function __construct(private readonly ProductService $products) {}
public function __construct(
private readonly ProductService $products,
private readonly ProductSearchService $search,
) {}
/**
* @param Closure(array<string, string|int>): string $url
* @param ?int $collectionId scope to a collection, or null for every product
* @param ?string $query scope to a text search — when given, calls
* ProductSearchService::search() instead of ProductService::list().
* ProductSearchService::search() returns the exact same
* Modules\Core\Catalog\DTOs\ProductListingResult list() does, so
* everything below this point is identical either way.
* @return array<string, mixed>
*/
public function build(ProductListing $listing, Closure $url, ?int $collectionId = null): array
public function build(ProductListing $listing, Closure $url, ?int $collectionId = null, ?string $query = null): array
{
$filters = $listing->filters($collectionId);
// Listing reads from the Meilisearch index via ProductService, not
// Eloquent. list() returns a ProductListingResult — the product page
// plus the price-slider bounds from one call; the controller no longer
// stitches list() + priceRange() together itself. Sort/filter/page all
// come from $listing (the query string).
$result = $this->products->list(
filters: $filters,
perPage: self::PER_PAGE,
page: $listing->page,
sort: $listing->sort,
);
// Listing reads from the Meilisearch index via ProductService/
// ProductSearchService, not Eloquent. Both return a
// ProductListingResult — the product page plus the price-slider
// bounds (and available tags) from one call; the controller no
// longer stitches list()/search() + priceRange() together itself.
// Sort/filter/page all come from $listing (the query string).
$result = $query !== null
? $this->search->search(
query: $query,
filters: $filters,
sort: $listing->sort,
perPage: self::PER_PAGE,
page: $listing->page,
)
: $this->products->list(
filters: $filters,
perPage: self::PER_PAGE,
page: $listing->page,
sort: $listing->sort,
);
$products = $result->products
->through(ProductCard::fromIndexed(...))