[object Object]

← back to Dw Activation Calendar

Calendar chips are real hrefs to the staged Shopify product (open/cmd-click/right-click); plain click keeps detail modal

09e4fb6fbd9482cefd0b5e2301456c28cd3af1e9 · 2026-07-15 11:05:13 -0700 · Steve

Files touched

Diff

commit 09e4fb6fbd9482cefd0b5e2301456c28cd3af1e9
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jul 15 11:05:13 2026 -0700

    Calendar chips are real hrefs to the staged Shopify product (open/cmd-click/right-click); plain click keeps detail modal
---
 package.json      |  2 +-
 public/index.html | 39 +++++++++++++++++++++++++++------------
 server.js         |  4 ++--
 3 files changed, 30 insertions(+), 15 deletions(-)

diff --git a/package.json b/package.json
index 975933e..560028d 100644
--- a/package.json
+++ b/package.json
@@ -1 +1 @@
-{"name":"dw-activation-calendar","version":"1.0.0","private":true,"scripts":{"start":"node server.js"}}
+{"name":"dw-activation-calendar","version":"1.0.1","private":true,"scripts":{"start":"node server.js"}}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 68828e0..2b8d0b6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -127,7 +127,7 @@
   .scell .chips{display:flex;flex-wrap:wrap;gap:5px;align-content:flex-start;
       max-height:178px;overflow:hidden}          /* collapsed preview: ~3 chip rows */
   .cell.expanded .chips{max-height:none;overflow:visible}  /* expanded: the whole day column */
-  .chip{position:relative;border-radius:6px;overflow:hidden;background:var(--ground);border:1px solid var(--hairline);width:56px;height:56px;flex:0 0 auto;padding:0;cursor:pointer}
+  .chip{position:relative;display:block;text-decoration:none;color:inherit;border-radius:6px;overflow:hidden;background:var(--ground);border:1px solid var(--hairline);width:56px;height:56px;flex:0 0 auto;padding:0;cursor:pointer}
   .chip:hover{border-color:var(--brass);box-shadow:0 0 0 1px var(--brass)}
   .chip:focus-visible{outline:2px solid var(--plum);outline-offset:1px}
   /* expand / collapse the day column */
@@ -541,12 +541,14 @@ function openSkuModal(s){
   const adminUrl = idNum ? 'https://admin.shopify.com/store/designer-laboratory-sandbox/products/'+idNum : null;
   const siteUrl  = s.handle ? 'https://www.designerwallcoverings.com/products/'+encodeURIComponent(s.handle) : null;
   const fx = s.fix_type==='add-sample' ? 's' : 'r';
+  const label = s.title || skuLabel(s);
+  const abbr  = s.vendor.slice(0,3).toUpperCase();
   const hero = (s.image_url && /^https?:/.test(s.image_url))
-    ? `<img src="${esc(s.image_url)}" alt="${esc(s.title||skuLabel(s))}">`
-    : `<div class="ph" style="background:${vcolor(s.vendor)}">${esc(s.vendor.slice(0,3).toUpperCase())}</div>`;
+    ? `<img src="${esc(s.image_url)}" alt="${esc(label)}">`
+    : `<div class="ph" style="background:${vcolor(s.vendor)}">${esc(abbr)}</div>`;
   const bodyHtml=`<div class="mform skud">
     <div class="mhead">
-      <div class="mtitle">${esc(s.title || skuLabel(s))}</div>
+      <div class="mtitle">${esc(label)}</div>
       <button type="button" class="mclose" id="sk_x" aria-label="Close">✕</button>
     </div>
     <div class="hero">${hero}</div>
@@ -592,19 +594,32 @@ function renderSchedMonth(containerId, monthDate, data){
     if(day){
       const chips=document.createElement('div'); chips.className='chips';
       day.skus.forEach(s=>{
-        // div, not <button>: a button grid-item's aspect-ratio height doesn't
-        // feed the auto row track, so rows collapse and chips overlap.
-        const el=document.createElement('div'); el.className='chip';
-        el.setAttribute('role','button'); el.tabIndex=0;
+        // Every data point is a REAL anchor to its actual staged product (Shopify
+        // admin — staged items 404 on the storefront until active). So a chip is
+        // openable every way: hover shows the URL, cmd/ctrl/middle/right-click opens
+        // the product in a new tab. A plain left-click still opens the rich detail
+        // modal (which also carries the admin + storefront links).
+        const idNum=s.shopify_id?String(s.shopify_id).split('/').pop():null;
+        const prodUrl=idNum?'https://admin.shopify.com/store/designer-laboratory-sandbox/products/'+idNum:null;
+        const el=document.createElement(prodUrl?'a':'div'); el.className='chip';
+        if(prodUrl){ el.href=prodUrl; el.target='_blank'; el.rel='noopener noreferrer'; }
+        else { el.setAttribute('role','button'); }
+        el.tabIndex=0;
         el.onkeydown=(e)=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();openSkuModal(s);}};
-        el.title=`${skuLabel(s)} · ${s.vendor}${s.title?' · '+s.title:''} · ${s.fix_type}\n⏱ goes active ${fmtGoLive(s.go_live_at)}`;
+        el.title=`${skuLabel(s)} · ${s.vendor}${s.title?' · '+s.title:''} · ${s.fix_type}\n⏱ goes active ${fmtGoLive(s.go_live_at)}${prodUrl?'\n↗ opens the staged product in Shopify admin':''}`;
         const fx=s.fix_type==='add-sample'?'s':'r';
+        const abbr=s.vendor.slice(0,3).toUpperCase();
         if(s.image_url && /^https?:/.test(s.image_url)){
-          el.innerHTML=`<span class="fx ${fx}"></span><img loading="lazy" src="${s.image_url}" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'ph',style:'background:${vcolor(s.vendor)}',textContent:'${s.vendor.slice(0,3).toUpperCase()}'}))"><span class="vn">${s.vendor}</span>`;
+          el.innerHTML=`<span class="fx ${fx}"></span><img loading="lazy" src="${s.image_url}" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'ph',style:'background:${vcolor(s.vendor)}',textContent:'${abbr}'}))"><span class="vn">${s.vendor}</span>`;
         }else{
-          el.innerHTML=`<span class="fx ${fx}"></span><div class="ph" style="background:${vcolor(s.vendor)}">${s.vendor.slice(0,3).toUpperCase()}</div><span class="vn">${s.vendor}</span>`;
+          el.innerHTML=`<span class="fx ${fx}"></span><div class="ph" style="background:${vcolor(s.vendor)}">${abbr}</div><span class="vn">${s.vendor}</span>`;
         }
-        el.onclick=(e)=>{e.stopPropagation();openSkuModal(s);};
+        el.onclick=(e)=>{
+          // modified / non-primary clicks follow the real href → straight to the product;
+          // a plain left-click opens the detail modal (same admin link inside).
+          if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button!==0) return;
+          e.preventDefault(); e.stopPropagation(); openSkuModal(s);
+        };
         chips.appendChild(el);
       });
       cell.appendChild(chips);
diff --git a/server.js b/server.js
index dfca6bc..d624b28 100644
--- a/server.js
+++ b/server.js
@@ -16,6 +16,7 @@
 const express = require('express');
 const path = require('path');
 const { Pool } = require('pg');
+const { execFile } = require('child_process');
 const gcal = require('./google-cal');
 
 const app = express();
@@ -70,7 +71,7 @@ async function imagesFor(shopifyIds) {
 // file is absent so the caller can fall back to the legacy on-the-fly compute.
 async function scheduleFromFile() {
   const f = loadScheduleFile();
-  if (!f || !Array.isArray(f.skus) || !f.skus.length) return null;
+  if (!f || !Array.isArray(f.skus) || !f.skus.length || !f.meta) return null;
   const imgs = await imagesFor([...new Set(f.skus.map(s => s.shopify_id).filter(Boolean))]);
   const byDate = new Map(); const vendorTotals = {};
   for (const s of f.skus) {
@@ -190,7 +191,6 @@ app.get('/api/health', (_q, res) => res.json({ ok: true, port: PORT }));
 // Re-project the schedule against the CURRENT worklist (re-stamp every item's
 // date+time). Cheap, local, read-only against dw_unified; writes only the JSON.
 app.post('/api/schedule/regenerate', (_q, res) => {
-  const { execFile } = require('child_process');
   execFile('node', [path.join(__dirname, 'scripts', 'generate-schedule.js')],
     { cwd: __dirname, timeout: 120000 }, (err, stdout, stderr) => {
       if (err) return res.status(500).json({ ok: false, error: (stderr || err.message).slice(0, 500) });

← 53bb7a5 calendar day columns expand to show all SKUs; every chip cli  ·  back to Dw Activation Calendar  ·  Activation calendar: drag SKU chips between days to reschedu ca49220 →