added a button group for quick filtering by productLine

This commit is contained in:
Zach Harding
2026-03-17 11:27:16 -04:00
parent f72d479c1d
commit 7b4e06733f
4 changed files with 65 additions and 43 deletions

View File

@@ -18,11 +18,6 @@ const facetFields:any = {
}
// ── Allowed sort values ───────────────────────────────────────────────────
// Maps the client-supplied key to the actual Typesense sort_by string.
// Never pass raw user input directly to sort_by.
// Note: price sorting uses nmMarketPrice — a field you need to denormalize
// onto your card document in your Typesense indexing step (NM market price
// as an integer in cents, e.g. nmMarketPrice: 499 = $4.99).
const sortMap: Record<string, string> = {
'releaseDate:desc,number:asc': '_text_match:asc,releaseDate:desc,number:asc',
'releaseDate:asc,number:asc': '_text_match:asc,releaseDate:asc,number:asc',
@@ -40,8 +35,16 @@ const start = Number(formData.get('start')?.toString() || '0');
const sortKey = formData.get('sort')?.toString() || '';
const resolvedSort = sortMap[sortKey] ?? DEFAULT_SORT;
// ── Language filter ───────────────────────────────────────────────────────
// Expects a `language` field on your card documents in Typesense.
// Valid values: 'en', 'jp' — anything else (or 'all') means no filter.
const language = formData.get('language')?.toString() || 'all';
const languageFilter = language === 'en' ? " && productLineName:=`Pokemon`"
: language === 'jp' ? " && productLineName:=`Pokemon Japan`"
: '';
const filters = Array.from(formData.entries())
.filter(([key, value]) => key !== 'q' && key !== 'start' && key !== 'sort')
.filter(([key]) => key !== 'q' && key !== 'start' && key !== 'sort' && key !== 'language')
.reduce((acc, [key, value]) => {
if (!acc[key]) {
acc[key] = [];
@@ -63,14 +66,15 @@ const facetFilter = (facet:string) => {
.filter(([field]) => field !== facet)
.map(([field, values]) => `${field}:=[${values.map(v => '`'+v+'`').join(',')}]`)
.join(' && ');
return `sealed:false${otherFilters ? ` && ${otherFilters}` : ''}`;
// Language filter is always included so facet counts stay accurate
return `sealed:false${languageFilter}${otherFilters ? ` && ${otherFilters}` : ''}`;
};
// primary search values (for cards)
let searchArray = [{
collection: 'cards',
filter_by: `sealed:false${filterBy ? ` && ${filterBy}` : ''}`,
filter_by: `sealed:false${languageFilter}${filterBy ? ` && ${filterBy}` : ''}`,
per_page: 20,
facet_by: '',
max_facet_values: 0,
@@ -135,8 +139,8 @@ const facetNames = (name:string) => {
}
const facets = searchResults.results.slice(1).map((result: any) => {
const facet = result.facet_counts[0];
if (!facet) return facet;
const facet = result.facet_counts?.[0];
if (!facet) return null;
// Sort: checked items first, then alphabetically
facet.counts = facet.counts.sort((a: any, b: any) => {
@@ -148,7 +152,7 @@ const facets = searchResults.results.slice(1).map((result: any) => {
});
return facet;
});
}).filter(Boolean);
---
@@ -178,19 +182,24 @@ const facets = searchResults.results.slice(1).map((result: any) => {
</div>
))}
</div>
<div id="sortBy" class="d-flex flex-fill align-items-center me-auto" hx-swap-oob="true">
<div id="sortBy" class="d-flex flex-fill align-items-center me-auto gap-2 small" hx-swap-oob="true">
<div class="btn-group btn-group-sm ms-2 flex-shrink-0" role="group" aria-label="Language filter">
<button type="button" class={`btn btn-outline-secondary language-btn${language === 'all' ? ' active' : ''}`} data-lang="all">All</button>
<button type="button" class={`btn btn-outline-secondary language-btn${language === 'en' ? ' active' : ''}`} data-lang="en">EN</button>
<button type="button" class={`btn btn-outline-secondary language-btn${language === 'jp' ? ' active' : ''}`} data-lang="jp">JP</button>
</div>
<div class="dropdown">
<button class="btn btn-sm btn-dark dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Sort by</button>
<ul class="dropdown-menu dropdown-menu-dark">
<li><a class="dropdown-item sort-option" href="#" data-sort="releaseDate:desc,number:asc" data-label="Set: Newest to Oldest">Set: Newest to Oldest</a></li>
<li><a class="dropdown-item sort-option" href="#" data-sort="releaseDate:asc,number:asc" data-label="Set: Oldest to Newest">Set: Oldest to Newest</a></li>
<li><a class="dropdown-item sort-option" href="#" data-sort="marketPrice:desc" data-label="Price: High to Low">Price: High to Low</a></li>
<li><a class="dropdown-item sort-option" href="#" data-sort="marketPrice:asc" data-label="Price: Low to High">Price: Low to High</a></li>
<li><a class="dropdown-item sort-option" href="#" data-sort="number:asc" data-label="Card Number: Ascending">Card Number: Ascending</a></li>
<li><a class="dropdown-item sort-option" href="#" data-sort="number:desc" data-label="Card Number: Descending">Card Number: Descending</a></li>
<li><a class="dropdown-item sort-option small" href="#" data-sort="releaseDate:desc,number:asc" data-label="Set: Newest to Oldest">Set: Newest to Oldest</a></li>
<li><a class="dropdown-item sort-option small" href="#" data-sort="releaseDate:asc,number:asc" data-label="Set: Oldest to Newest">Set: Oldest to Newest</a></li>
<li><a class="dropdown-item sort-option small" href="#" data-sort="marketPrice:desc" data-label="Price: High to Low">Price: High to Low</a></li>
<li><a class="dropdown-item sort-option small" href="#" data-sort="marketPrice:asc" data-label="Price: Low to High">Price: Low to High</a></li>
<li><a class="dropdown-item sort-option small" href="#" data-sort="number:asc" data-label="Card Number: Ascending">Card Number: Ascending</a></li>
<li><a class="dropdown-item sort-option small" href="#" data-sort="number:desc" data-label="Card Number: Descending">Card Number: Descending</a></li>
</ul>
</div>
<span id="sortLabel" class="ms-1 text-secondary small">{sortKey ? ({"releaseDate:desc,number:asc":"Set: Newest to Oldest","releaseDate:asc,number:asc":"Set: Oldest to Newest","marketPrice:desc":"Price: High to Low","marketPrice:asc":"Price: Low to High","number:asc":"Card Number: Ascending","number:desc":"Card Number: Descending"}[sortKey] ?? '') : ''}</span>
<span id="sortLabel" class="text-secondary">{sortKey ? ({"releaseDate:desc,number:asc":"Set: Newest to Oldest","releaseDate:asc,number:asc":"Set: Oldest to Newest","marketPrice:desc":"Price: High to Low","marketPrice:asc":"Price: Low to High","number:asc":"Card Number: Ascending","number:desc":"Card Number: Descending"}[sortKey] ?? '') : ''}</span>
</div>
<div id="totalResults" class="d-flex text-secondary small d-none align-items-center" hx-swap-oob="true">
{totalHits} {totalHits === 1 ? ' result' : ' results'}