page is better in shop now

This commit is contained in:
Priec
2026-06-25 15:38:18 +02:00
parent ee8ec5c85b
commit 848042c304
4 changed files with 44 additions and 4 deletions

View File

@@ -315,6 +315,7 @@ order-search-placeholder = Search orders…
search-empty = Nothing matched your search:
results-count = { $count } products
sort-label = Sort
per-page-label = Per page
sort-relevance = Relevance
sort-newest = Newest
sort-price_asc = Price: low to high

View File

@@ -315,6 +315,7 @@ order-search-placeholder = Hľadať objednávky…
search-empty = Pre váš výraz sme nič nenašli:
results-count = { $count } produktov
sort-label = Zoradiť
per-page-label = Na stránku
sort-relevance = Relevancia
sort-newest = Najnovšie
sort-price_asc = Cena: od najnižšej

View File

@@ -52,6 +52,24 @@
</select>
</label>
<!-- per-page count -->
<label class="flex items-center gap-2 text-xs font-medium text-on-surface/70 dark:text-on-surface-dark/70">
{{ t(key="per-page-label", lang=L) }}
<select name="per_page"
class="rounded-radius border border-outline bg-surface px-2 py-1.5 text-sm text-on-surface focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
{% for opt in per_page_options %}
<option value="{{ opt }}"{% if per_page == opt %} selected{% endif %}>{{ opt }}</option>
{% endfor %}
</select>
</label>
<!-- in stock only -->
<label class="flex items-center gap-2 text-sm text-on-surface dark:text-on-surface-dark">
<input type="checkbox" name="in_stock" value="1"{% if in_stock %} checked{% endif %}
class="size-4 rounded border-outline text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-outline-dark dark:text-primary-dark" />
{{ t(key="filter-in-stock", lang=L) }}
</label>
<!-- grid / list view toggle -->
<div class="inline-flex gap-0.5 rounded-radius border border-outline p-0.5 dark:border-outline-dark" role="group"
aria-label="{{ t(key='view-grid', lang=L) }} / {{ t(key='view-list', lang=L) }}">

View File

@@ -22,8 +22,21 @@ use crate::{
views::shop as view,
};
/// Results per page in the storefront listing/search.
/// Default results per page in the storefront listing/search.
const PER_PAGE: usize = 24;
/// Allowed per-page choices offered in the toolbar; any other value falls back
/// to [`PER_PAGE`].
const PER_PAGE_OPTIONS: [usize; 3] = [24, 48, 96];
/// Resolve the requested per-page count to one of [`PER_PAGE_OPTIONS`],
/// defaulting to [`PER_PAGE`].
fn resolve_per_page(params: &SearchParams) -> usize {
params
.per_page
.map(|p| p as usize)
.filter(|p| PER_PAGE_OPTIONS.contains(p))
.unwrap_or(PER_PAGE)
}
/// Hard cap on candidates a single text search considers before faceting; well
/// above any realistic page of results for this catalog.
const SEARCH_CAP: u64 = 1000;
@@ -40,6 +53,7 @@ struct SearchParams {
in_stock: Option<String>,
sort: Option<String>,
page: Option<u32>,
per_page: Option<u32>,
}
/// A candidate product with everything the listing needs to filter, sort and
@@ -81,6 +95,9 @@ fn query_base(params: &SearchParams) -> String {
if let Some(s) = params.sort.as_deref().filter(|s| !s.is_empty()) {
ser.append_pair("sort", s);
}
if let Some(p) = params.per_page.filter(|p| *p as usize != PER_PAGE) {
ser.append_pair("per_page", &p.to_string());
}
ser.finish()
}
@@ -198,14 +215,15 @@ async fn run_search(
}
// 7. Paginate.
let per_page = resolve_per_page(params);
let total = items.len();
let pages = total.div_ceil(PER_PAGE).max(1);
let pages = total.div_ceil(per_page).max(1);
let page = params.page.unwrap_or(1).clamp(1, pages as u32);
let start = (page as usize - 1) * PER_PAGE;
let start = (page as usize - 1) * per_page;
// 8. Render only the current page's cards (images fetched per row).
let mut rows = Vec::new();
for item in items.iter().skip(start).take(PER_PAGE) {
for item in items.iter().skip(start).take(per_page) {
let image = product_images::first_for(ctx, item.product.id).await?;
let cat_name = item.product.category_id.and_then(|id| category_name.get(&id).cloned());
rows.push(view::product_card(
@@ -229,6 +247,8 @@ async fn run_search(
"selected_category_id": selected_category.parse::<i32>().unwrap_or(-1),
"uncategorized_count": uncategorized_count,
"sort": sort,
"per_page": per_page,
"per_page_options": PER_PAGE_OPTIONS,
"in_stock": in_stock_only,
"min_price": params.min_price.clone().unwrap_or_default(),
"max_price": params.max_price.clone().unwrap_or_default(),