← back to Bleachresistantfabrics
fix /api/rails facet counts to honor active q/vendor filters via shared filterProducts() drill-down (was counting whole niche catalog)
e8ec1f42bb33acb88288a9d98c100c7b5a85401d · 2026-06-01 07:20:56 -0700 · Steve
Files touched
Diff
commit e8ec1f42bb33acb88288a9d98c100c7b5a85401d
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jun 1 07:20:56 2026 -0700
fix /api/rails facet counts to honor active q/vendor filters via shared filterProducts() drill-down (was counting whole niche catalog)
---
server.js | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/server.js b/server.js
index 87a6b4f..ba47622 100644
--- a/server.js
+++ b/server.js
@@ -116,6 +116,20 @@ function matchesRail(p, key) {
return re.test(blob);
}
+// Apply the active query/rail/vendor filters to a product list. `skip` lets a
+// facet exclude its own dimension so its counts reflect the rest of the active
+// filter set (standard drill-down facet behaviour), not the whole catalog.
+function filterProducts(list, { q, rail, vendor } = {}, skip = {}) {
+ let out = list;
+ if (q && !skip.q) {
+ const needle = String(q).toLowerCase();
+ out = out.filter(p => (p.title || '').toLowerCase().includes(needle) || (p.tags || []).some(t => String(t).toLowerCase().includes(needle)));
+ }
+ if (rail && rail !== 'all' && RAIL_MATCHERS[rail] && !skip.rail) out = out.filter(p => matchesRail(p, rail));
+ if (vendor && vendor !== 'all' && !skip.vendor) out = out.filter(p => p.vendor === vendor);
+ return out;
+}
+
const app = express();
app.use(helmet({ contentSecurityPolicy: false }));
app.use(express.json({ limit: '256kb' }));
@@ -135,13 +149,7 @@ app.use(express.static(path.join(__dirname, 'public')));
app.get('/api/products', (req, res) => {
const { q, rail, vendor, page = 1, limit = 24, sort = 'newest' } = req.query;
- let list = PRODUCTS;
- if (q) {
- const needle = q.toLowerCase();
- list = list.filter(p => (p.title || '').toLowerCase().includes(needle) || (p.tags || []).some(t => t.toLowerCase().includes(needle)));
- }
- if (rail && rail !== 'all' && RAIL_MATCHERS[rail]) list = list.filter(p => matchesRail(p, rail));
- if (vendor && vendor !== 'all') list = list.filter(p => p.vendor === vendor);
+ let list = filterProducts(PRODUCTS, { q, rail, vendor });
list = sortProducts(list, sort);
const total = list.length;
const pageNum = Math.max(1, parseInt(page) || 1);
@@ -151,9 +159,14 @@ app.get('/api/products', (req, res) => {
});
app.get('/api/rails', (req, res) => {
+ const { q, vendor } = req.query;
+ // Count each rail over the active filter set MINUS the rail dimension itself,
+ // so the numbers tell the user "how many match if I pick this rail" given
+ // their current query/vendor selection — not raw whole-catalog totals.
+ const base = filterProducts(PRODUCTS, { q, vendor }, { rail: true });
const counts = {};
- for (const k of Object.keys(RAIL_MATCHERS)) counts[k] = PRODUCTS.filter(p => matchesRail(p, k)).length;
- res.json({ total: PRODUCTS.length, counts });
+ for (const k of Object.keys(RAIL_MATCHERS)) counts[k] = base.filter(p => matchesRail(p, k)).length;
+ res.json({ total: base.length, counts });
});
app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS.length }));
← 9001c5d fix: wire IntersectionObserver infinite scroll so all 600 pr
·
back to Bleachresistantfabrics
·
Remove Big Red widget — never UX-worked, collided with Help e21c312 →