[object Object]

← back to Interiordesignershowroom

admin: per-product click analytics column (total, 7d, last-clicked date)

82f2795ad7c76fe9ccde9650fac680de4f51eeb5 · 2026-08-03 10:22:20 -0700 · Steve

Fold click aggregates into the products query via one grouped LEFT JOIN and
render a Clicks column per row — lifetime total, a 7-day sub-count, and the
last-clicked timestamp (dated with the standard when() helper). No-click
products show a muted — instead of fake zeros.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 82f2795ad7c76fe9ccde9650fac680de4f51eeb5
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 3 10:22:20 2026 -0700

    admin: per-product click analytics column (total, 7d, last-clicked date)
    
    Fold click aggregates into the products query via one grouped LEFT JOIN and
    render a Clicks column per row — lifetime total, a 7-day sub-count, and the
    last-clicked timestamp (dated with the standard when() helper). No-click
    products show a muted — instead of fake zeros.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 routes/admin.js | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/routes/admin.js b/routes/admin.js
index 4968b83..5805978 100644
--- a/routes/admin.js
+++ b/routes/admin.js
@@ -128,6 +128,18 @@ function when(ts) {
   return `<span class="when" title="${esc(d.toISOString())}">🕓 ${esc(label)}</span>`;
 }
 
+// Per-product click analytics cell: lifetime total, a 7-day sub-count, and the
+// last-clicked date (dating the analytics). No clicks → a muted "—" so the
+// mostly-quiet catalog reads honestly instead of showing fake zeros everywhere.
+function clickCell(p) {
+  const n = Number(p.clicks) || 0;
+  if (!n) return '<span class="subtle">—</span>';
+  const wk = Number(p.clicks7) || 0;
+  return `<b>${n}</b> click${n === 1 ? '' : 's'}`
+    + (wk ? ` <span class="subtle">(${wk} · 7d)</span>` : '')
+    + (p.last_click ? `<div>${when(p.last_click)}</div>` : '');
+}
+
 const shell = (body) => `<!doctype html><html lang="en"><head><meta charset="utf-8">
 <meta name="viewport" content="width=device-width,initial-scale=1"><title>IDS Admin</title>
 <style>
@@ -188,8 +200,22 @@ router.get('/admin', async (req, res, next) => {
       db.query(`SELECT p.id,p.title,p.advertiser,count(c.*) AS n
         FROM clicks c JOIN products p ON p.id=c.product_id
         GROUP BY p.id,p.title,p.advertiser ORDER BY n DESC LIMIT 10`),
-      db.query(`SELECT id,title,advertiser,network,external_id,brand,category,price,sale_price,image_url,featured,suppressed,in_stock,room,created_at,updated_at
-        FROM products ORDER BY featured DESC, suppressed, created_at DESC LIMIT 200`),
+      // Per-product click analytics folded in via one grouped LEFT JOIN: lifetime
+      // clicks, a 7-day window, and the last-clicked timestamp (the date we stamp
+      // on the analytics). Products with no clicks stay NULL → rendered as "—".
+      db.query(`SELECT p.id,p.title,p.advertiser,p.network,p.external_id,p.brand,p.category,p.price,p.sale_price,p.image_url,p.featured,p.suppressed,p.in_stock,p.room,p.created_at,p.updated_at,
+          COALESCE(cl.clicks,0)  AS clicks,
+          COALESCE(cl.clicks7,0) AS clicks7,
+          cl.last_click
+        FROM products p
+        LEFT JOIN (
+          SELECT product_id,
+                 count(*) AS clicks,
+                 count(*) FILTER (WHERE clicked_at > now() - interval '7 days') AS clicks7,
+                 max(clicked_at) AS last_click
+          FROM clicks GROUP BY product_id
+        ) cl ON cl.product_id = p.id
+        ORDER BY p.featured DESC, p.suppressed, p.created_at DESC LIMIT 200`),
       db.query(`SELECT id,slug,title,published,created_at FROM guides ORDER BY created_at DESC`),
     ]);
     const c = counts.rows[0];
@@ -216,6 +242,7 @@ router.get('/admin', async (req, res, next) => {
       <td>${esc(p.room || '')}</td>
       <td>${money(p.sale_price || p.price)}</td>
       <td>${when(p.created_at)}</td>
+      <td>${clickCell(p)}</td>
       <td><span class="pill ${p.featured ? 'on' : 'off'}">${p.featured ? 'Featured' : '—'}</span>${p.suppressed ? ' <span class="pill off">Hidden</span>' : ''}</td>
       ${details ? `<td class="subtle">${esc(p.brand || '')}</td><td class="subtle">${esc(p.category || '')}</td><td class="subtle">${esc(p.external_id || '')}</td><td class="subtle">${p.in_stock === false ? 'out' : 'in'}</td><td>${when(p.updated_at)}</td>` : ''}
       <td style="white-space:nowrap">
@@ -239,7 +266,7 @@ router.get('/admin', async (req, res, next) => {
       <h2>Products — curate featured (${products.rows.length})
         <a href="/admin?details=${details ? 0 : 1}" style="float:right;font-size:.75rem;font-weight:400;text-decoration:none;color:${details ? '#b1483c' : '#8a7250'}">${details ? '− Hide details' : '+ Show details'}</a>
       </h2>
-      <table><tr><th></th><th>Product</th><th>Room</th><th>Price</th><th>Added</th><th>Status</th>${detailHead}<th></th></tr>${prodRows}</table>
+      <table><tr><th></th><th>Product</th><th>Room</th><th>Price</th><th>Added</th><th>Clicks</th><th>Status</th>${detailHead}<th></th></tr>${prodRows}</table>
       <h2>Guides</h2>
       <table><tr><th>Guide</th><th>Created</th><th>Status</th><th></th></tr>${guideRows}</table>
     `));

← 5b5102f seo: breadcrumbs (nav + BreadcrumbList) across facet/room/gu  ·  back to Interiordesignershowroom  ·  admin: keyword/id suppression rules (Suppress page) + Vacuum 72708fc →