page is better in shop now
This commit is contained in:
@@ -315,6 +315,7 @@ order-search-placeholder = Search orders…
|
|||||||
search-empty = Nothing matched your search:
|
search-empty = Nothing matched your search:
|
||||||
results-count = { $count } products
|
results-count = { $count } products
|
||||||
sort-label = Sort
|
sort-label = Sort
|
||||||
|
per-page-label = Per page
|
||||||
sort-relevance = Relevance
|
sort-relevance = Relevance
|
||||||
sort-newest = Newest
|
sort-newest = Newest
|
||||||
sort-price_asc = Price: low to high
|
sort-price_asc = Price: low to high
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ order-search-placeholder = Hľadať objednávky…
|
|||||||
search-empty = Pre váš výraz sme nič nenašli:
|
search-empty = Pre váš výraz sme nič nenašli:
|
||||||
results-count = { $count } produktov
|
results-count = { $count } produktov
|
||||||
sort-label = Zoradiť
|
sort-label = Zoradiť
|
||||||
|
per-page-label = Na stránku
|
||||||
sort-relevance = Relevancia
|
sort-relevance = Relevancia
|
||||||
sort-newest = Najnovšie
|
sort-newest = Najnovšie
|
||||||
sort-price_asc = Cena: od najnižšej
|
sort-price_asc = Cena: od najnižšej
|
||||||
|
|||||||
@@ -52,6 +52,24 @@
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</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 -->
|
<!-- 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"
|
<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) }}">
|
aria-label="{{ t(key='view-grid', lang=L) }} / {{ t(key='view-list', lang=L) }}">
|
||||||
|
|||||||
@@ -22,8 +22,21 @@ use crate::{
|
|||||||
views::shop as view,
|
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;
|
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
|
/// Hard cap on candidates a single text search considers before faceting; well
|
||||||
/// above any realistic page of results for this catalog.
|
/// above any realistic page of results for this catalog.
|
||||||
const SEARCH_CAP: u64 = 1000;
|
const SEARCH_CAP: u64 = 1000;
|
||||||
@@ -40,6 +53,7 @@ struct SearchParams {
|
|||||||
in_stock: Option<String>,
|
in_stock: Option<String>,
|
||||||
sort: Option<String>,
|
sort: Option<String>,
|
||||||
page: Option<u32>,
|
page: Option<u32>,
|
||||||
|
per_page: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A candidate product with everything the listing needs to filter, sort and
|
/// 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()) {
|
if let Some(s) = params.sort.as_deref().filter(|s| !s.is_empty()) {
|
||||||
ser.append_pair("sort", s);
|
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()
|
ser.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,14 +215,15 @@ async fn run_search(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 7. Paginate.
|
// 7. Paginate.
|
||||||
|
let per_page = resolve_per_page(params);
|
||||||
let total = items.len();
|
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 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).
|
// 8. Render only the current page's cards (images fetched per row).
|
||||||
let mut rows = Vec::new();
|
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 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());
|
let cat_name = item.product.category_id.and_then(|id| category_name.get(&id).cloned());
|
||||||
rows.push(view::product_card(
|
rows.push(view::product_card(
|
||||||
@@ -229,6 +247,8 @@ async fn run_search(
|
|||||||
"selected_category_id": selected_category.parse::<i32>().unwrap_or(-1),
|
"selected_category_id": selected_category.parse::<i32>().unwrap_or(-1),
|
||||||
"uncategorized_count": uncategorized_count,
|
"uncategorized_count": uncategorized_count,
|
||||||
"sort": sort,
|
"sort": sort,
|
||||||
|
"per_page": per_page,
|
||||||
|
"per_page_options": PER_PAGE_OPTIONS,
|
||||||
"in_stock": in_stock_only,
|
"in_stock": in_stock_only,
|
||||||
"min_price": params.min_price.clone().unwrap_or_default(),
|
"min_price": params.min_price.clone().unwrap_or_default(),
|
||||||
"max_price": params.max_price.clone().unwrap_or_default(),
|
"max_price": params.max_price.clone().unwrap_or_default(),
|
||||||
|
|||||||
Reference in New Issue
Block a user