← back to Dw Pairs Well
collection-grouped: real sort (hue/style/colorways/az) in feed + show toolbar on load + Hue/Style options
99f1752cbdf21b76f9f119a73fd826e0a7b5cbf2 · 2026-06-26 07:24:30 -0700 · Steve
Files touched
Diff
commit 99f1752cbdf21b76f9f119a73fd826e0a7b5cbf2
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 07:24:30 2026 -0700
collection-grouped: real sort (hue/style/colorways/az) in feed + show toolbar on load + Hue/Style options
---
server.js | 46 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/server.js b/server.js
index a668e51..2158c5e 100644
--- a/server.js
+++ b/server.js
@@ -601,6 +601,32 @@ app.get('/api/colorways', async (req, res) => {
}
});
+// Hue angle (0-360) from a hex; grayscale/low-sat or missing → 999 so it sorts last.
+function hexHue(hex) {
+ if (!hex) return 999;
+ const m = String(hex).replace('#','').match(/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
+ if (!m) return 999;
+ const r = parseInt(m[1],16)/255, g = parseInt(m[2],16)/255, b = parseInt(m[3],16)/255;
+ const max = Math.max(r,g,b), min = Math.min(r,g,b), d = max - min;
+ if (d < 0.06) return 999; // grayscale → bucket at the end
+ let h;
+ if (max === r) h = ((g - b) / d) % 6;
+ else if (max === g) h = (b - r) / d + 2;
+ else h = (r - g) / d + 4;
+ h *= 60; if (h < 0) h += 360;
+ return h;
+}
+// First recognized design-style token from tags → for Style sort; none → 'zzz' (sorts last).
+const STYLE_WORDS = ['abstract','animal','botanical','damask','floral','geometric','ikat','moroccan',
+ 'paisley','plaid','scenic','stripe','striped','toile','traditional','trellis','tropical','modern',
+ 'contemporary','art deco','mid-century','victorian','transitional','grasscloth','textured','solid'];
+function styleOf(tags) {
+ if (!tags) return 'zzz';
+ const low = String(tags).toLowerCase();
+ for (const w of STYLE_WORDS) if (low.indexOf(w) !== -1) return w;
+ return 'zzz';
+}
+
// GET /api/collection-grouped?handle=<collection-handle>&page=N&per=48
// The WS-2 grouped-grid feed. Returns ONE tile per pattern_id for the named
// collection, with a colorway count per tile, correct across pagination
@@ -616,6 +642,7 @@ app.get('/api/collection-grouped', async (req, res) => {
const all = req.query.all === '1';
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
const per = Math.max(1, Math.min(96, parseInt(req.query.per, 10) || 48));
+ const sort = String(req.query.sort || 'newest').slice(0, 24);
if (!vendor && !all) {
return res.status(400).json({ ok: false, error: 'pass ?vendor=<name> or ?all=1 (collection proxy for local testing)' });
}
@@ -625,7 +652,7 @@ app.get('/api/collection-grouped', async (req, res) => {
if (vendor) { params.push(vendor); where += ` AND sp.vendor = $${params.length}`; }
const r = await pool.query(`
- SELECT sp.dw_sku, sp.handle, sp.title, sp.vendor, sp.mfr_sku, sp.pattern_name,
+ SELECT sp.dw_sku, sp.handle, sp.title, sp.vendor, sp.mfr_sku, sp.pattern_name, sp.tags,
sp.image_url, sp.metafields, sp.synced_at, sce.color_family, sce.dominant_hex
FROM shopify_products sp
LEFT JOIN shopify_color_enrichment sce ON sce.shopify_id = sp.shopify_id
@@ -636,6 +663,7 @@ app.get('/api/collection-grouped', async (req, res) => {
// group server-side over the FULL set → counts are correct regardless of paging
const groups = new Map();
+ let seq = 0;
for (const row of r.rows) {
const key = groupKeyForRow(row);
let g = groups.get(key);
@@ -647,7 +675,10 @@ app.get('/api/collection-grouped', async (req, res) => {
vendor: row.vendor || null,
// representative tile = first (newest) member
rep_handle: row.handle, rep_dw_sku: row.dw_sku, rep_image_url: row.image_url,
- count: 0, swatches: []
+ count: 0, swatches: [],
+ _seq: seq++, // newest-first insertion order
+ _hue: hexHue(row.dominant_hex), // for Color (Hue) sort
+ _style: styleOf(row.tags) // for Style sort
};
groups.set(key, g);
}
@@ -656,6 +687,17 @@ app.get('/api/collection-grouped', async (req, res) => {
}
const arr = [...groups.values()];
+ // SORT the grouped tiles (over the whole set) before paging, so order is stable across pages.
+ const byPattern = (a, b) => String(a.pattern).localeCompare(String(b.pattern));
+ const sorters = {
+ newest: (a, b) => a._seq - b._seq,
+ colors_desc: (a, b) => b.count - a.count || byPattern(a, b),
+ pattern_az: byPattern,
+ vendor_az: (a, b) => String(a.vendor||'').localeCompare(String(b.vendor||'')) || byPattern(a, b),
+ hue: (a, b) => (a._hue - b._hue) || byPattern(a, b), // grayscale (hue=999) sorts last
+ style: (a, b) => String(a._style).localeCompare(String(b._style)) || byPattern(a, b)
+ };
+ arr.sort(sorters[sort] || sorters.newest);
const total_tiles = arr.length;
const start = (page - 1) * per;
const slice = arr.slice(start, start + per);
← 901bbdd WS-3: /api/colorways + /api/collection-grouped (interim T2 g
·
back to Dw Pairs Well
·
collection-grouped feed: vendor-FAMILY scope (catches all br af1d385 →