[object Object]

← back to Zuber Internal

Revert "Add Activation Calendar tab (go-live date+time per staged SKU)"

746d63585a3781032ee913240aadc5672c6a5b3a · 2026-07-14 07:56:27 -0700 · Steve

This reverts commit 8879146472a5ea957b2746e35f4a33761ed4c724.

Files touched

Diff

commit 746d63585a3781032ee913240aadc5672c6a5b3a
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 14 07:56:27 2026 -0700

    Revert "Add Activation Calendar tab (go-live date+time per staged SKU)"
    
    This reverts commit 8879146472a5ea957b2746e35f4a33761ed4c724.
---
 public/activation-calendar.js | 174 ------------------------------------------
 public/index.html             |  19 +----
 2 files changed, 3 insertions(+), 190 deletions(-)

diff --git a/public/activation-calendar.js b/public/activation-calendar.js
deleted file mode 100644
index 8db99ba..0000000
--- a/public/activation-calendar.js
+++ /dev/null
@@ -1,174 +0,0 @@
-/* activation-calendar.js — reusable "SKU Activation" calendar drop-in for any
- * DW internal line viewer. Client-side only, zero dependencies, zero server
- * changes: it reads the viewer's OWN product array and assigns every staged SKU
- * (waiting to go active) a durable go-live DATE + TIME, then renders a month
- * calendar of the rollout — the same surface as the company-wide tool on :9765.
- *
- * Usage:
- *   ActivationCalendar.render({
- *     mount:   document.getElementById('view-calendar'),
- *     products: ALL,                       // the line's product array
- *     lineLabel: 'Cork · Phillipe Romano', // shown in the note
- *     perDay: 50,                          // publish cadence (default 50/day)
- *     staged: p => p.online_published === false || p.online_published === 'false',
- *   });
- *
- * "Staged waiting to go active" defaults to products not yet published to the
- * storefront (online_published falsy / shop_status not live). If that yields 0,
- * it falls back to the whole line (a pre-launch line is entirely staged).
- */
-(function () {
-  'use strict';
-  var CSS = `
-  .ac-wrap{padding:14px 18px;color:var(--ink,#e7e4dc)}
-  .ac-note{font-size:12px;line-height:1.5;color:var(--mut,#9aa0aa);max-width:900px;margin:0 0 12px}
-  .ac-note b{color:var(--ink,#e7e4dc)}
-  .ac-sum{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 14px}
-  .ac-k{border:1px solid var(--hair,#2a2d36);border-radius:8px;padding:7px 11px;min-width:96px;background:var(--card,#14161c)}
-  .ac-k span{display:block;font-size:10px;letter-spacing:.4px;text-transform:uppercase;color:var(--mut,#9aa0aa)}
-  .ac-k b{font-size:16px;font-variant-numeric:tabular-nums}
-  .ac-bar{display:flex;align-items:center;gap:10px;margin:0 0 10px}
-  .ac-bar button{background:var(--card,#14161c);border:1px solid var(--hair,#2a2d36);color:var(--ink,#e7e4dc);border-radius:7px;padding:5px 11px;cursor:pointer;font-size:14px}
-  .ac-bar button:hover{border-color:var(--acc,#5eead4)}
-  .ac-ml{font-weight:700;min-width:150px;text-align:center;font-size:14px}
-  .ac-dow{display:grid;grid-template-columns:repeat(7,1fr);gap:5px;margin-bottom:5px}
-  .ac-dow div{font-size:10px;letter-spacing:.5px;text-transform:uppercase;color:var(--mut,#9aa0aa);text-align:center}
-  .ac-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:5px}
-  .ac-cell{border:1px solid var(--hair,#2a2d36);border-radius:8px;min-height:96px;padding:5px;display:flex;flex-direction:column;background:var(--card,#14161c);overflow:hidden}
-  .ac-cell.out{opacity:.32}
-  .ac-cell.today{outline:1.5px solid var(--acc,#5eead4)}
-  .ac-dh{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}
-  .ac-dn{font-size:11px;font-weight:600;color:var(--mut,#9aa0aa)}
-  .ac-dhr{display:flex;align-items:baseline;gap:4px}
-  .ac-cnt{font-size:10px;font-weight:700;color:var(--acc,#5eead4);font-variant-numeric:tabular-nums}
-  .ac-span{font-size:8px;font-weight:600;color:var(--mut,#9aa0aa);white-space:nowrap}
-  .ac-chips{display:grid;grid-template-columns:repeat(auto-fill,minmax(30px,1fr));gap:3px;overflow-y:auto;align-content:start}
-  .ac-chip{position:relative;border-radius:5px;overflow:hidden;background:var(--bg,#0c0d12);border:1px solid var(--hair,#2a2d36);aspect-ratio:1/1}
-  .ac-chip img{width:100%;height:100%;object-fit:cover;display:block}
-  .ac-chip .ph{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:700;color:#fff;text-align:center;line-height:1}
-  .ac-chips::-webkit-scrollbar{width:6px}.ac-chips::-webkit-scrollbar-thumb{background:var(--hair,#2a2d36);border-radius:4px}
-  `;
-
-  function injectCSS() {
-    if (document.getElementById('ac-style')) return;
-    var s = document.createElement('style'); s.id = 'ac-style'; s.textContent = CSS;
-    document.head.appendChild(s);
-  }
-
-  var pad = function (n) { return String(n).padStart(2, '0'); };
-  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]; }); }
-  function field(p, keys) { for (var i = 0; i < keys.length; i++) { if (p[keys[i]] != null && p[keys[i]] !== '') return p[keys[i]]; } return ''; }
-  function img(p) { var v = field(p, ['image', 'image_url', 'img', 'thumbnail']); if (!v && Array.isArray(p.images)) v = p.images[0]; return v; }
-  function label(p) { return field(p, ['dw_sku', 'mfr_sku', 'sku', 'handle']) || '(no sku)'; }
-  function title(p) { return field(p, ['title', 'name', 'pattern']); }
-  function slotLabel(h) { if (h == null) return ''; var ap = h < 12 ? 'AM' : 'PM'; var hh = h % 12; if (hh === 0) hh = 12; return hh + ' ' + ap; }
-  function fmtGoLive(iso) { try { return new Date(iso).toLocaleString(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return iso; } }
-  function fmtMonth(d) { return d.toLocaleString(undefined, { month: 'long', year: 'numeric' }); }
-
-  // assign a durable go_live_at to every staged item: perDay across hourly :10
-  // slots, front-loaded, starting tomorrow. Deterministic → 100% coverage.
-  function schedule(items, perDay) {
-    var start = new Date(); start.setHours(0, 0, 0, 0); start.setDate(start.getDate() + 1);
-    var slotCap = Math.max(1, Math.ceil(perDay / 24));
-    var out = items.map(function (p, i) {
-      var day = Math.floor(i / perDay), pos = i % perDay;
-      var hour = Math.min(23, Math.floor(pos / slotCap));
-      var dt = new Date(start.getFullYear(), start.getMonth(), start.getDate() + day, hour, 10, 0, 0);
-      return {
-        p: p, label: label(p), title: title(p), vendor: field(p, ['vendor']) || '',
-        image: img(p), slot_hour: hour,
-        go_live_date: dt.getFullYear() + '-' + pad(dt.getMonth() + 1) + '-' + pad(dt.getDate()),
-        go_live_at: dt.toISOString(),
-      };
-    });
-    return out;
-  }
-
-  function vcolor(s) { var h = 0; s = String(s || ''); for (var i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360; return 'hsl(' + h + ',42%,42%)'; }
-
-  function render(opts) {
-    injectCSS();
-    var mount = opts.mount; if (!mount) return;
-    var perDay = opts.perDay || 50;
-    var products = (opts.products || []).slice();
-    var isStaged = opts.staged || function (p) { return p.online_published === false || p.online_published === 'false' || (p.shop_status && String(p.shop_status).toLowerCase() !== 'active'); };
-    var staged = products.filter(isStaged);
-    if (!staged.length) staged = products;                 // whole line is pre-launch
-    // stable order: created_at asc then dw_sku
-    staged.sort(function (a, b) {
-      var ca = a.created_at || '', cb = b.created_at || '';
-      if (ca !== cb) return ca < cb ? -1 : 1;
-      return String(label(a)) < String(label(b)) ? -1 : 1;
-    });
-    var rows = schedule(staged, perDay);
-    var total = rows.length;
-
-    // group by date
-    var byDate = {}; var vt = {};
-    rows.forEach(function (r) { (byDate[r.go_live_date] = byDate[r.go_live_date] || []).push(r); if (r.vendor) vt[r.vendor] = (vt[r.vendor] || 0) + 1; });
-    var dates = Object.keys(byDate).sort();
-    var endDate = dates.length ? dates[dates.length - 1] : '—';
-    var days = Math.ceil(total / perDay);
-
-    var viewMonth = dates.length ? new Date(dates[0] + 'T00:00:00') : new Date();
-    viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), 1);
-
-    mount.innerHTML =
-      '<div class="ac-wrap">' +
-      '<p class="ac-note">Every SKU in <b>' + esc(opts.lineLabel || 'this line') + '</b> that is <b>waiting to go active</b> carries a durable go-live <b>date AND time</b>, published at <b>' + perDay + '/day</b> across hourly :10 slots, in order, until the line is live. Hover any chip for its go-active date+time.</p>' +
-      '<div class="ac-sum">' +
-      '<div class="ac-k"><span>Waiting to go active</span><b>' + total.toLocaleString() + '</b></div>' +
-      '<div class="ac-k"><span>Days to clear</span><b>' + days + '</b></div>' +
-      '<div class="ac-k"><span>Cadence</span><b>' + perDay + '/day</b></div>' +
-      '<div class="ac-k"><span>Finishes</span><b>' + endDate + '</b></div>' +
-      '<div class="ac-k"><span>Date+time assigned</span><b title="every staged SKU has a durable go-live date and time">' + total + '/' + total + ' ✓</b></div>' +
-      '</div>' +
-      '<div class="ac-bar"><button class="ac-prev">‹</button><div class="ac-ml"></div><button class="ac-next">›</button></div>' +
-      '<div class="ac-cal"></div></div>';
-
-    var calEl = mount.querySelector('.ac-cal');
-    var mlEl = mount.querySelector('.ac-ml');
-    function paint() {
-      mlEl.textContent = fmtMonth(viewMonth);
-      var y = viewMonth.getFullYear(), m = viewMonth.getMonth();
-      var todayISO = new Date().toISOString().slice(0, 10);
-      var html = '<div class="ac-dow">' + ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(function (d) { return '<div>' + d + '</div>'; }).join('') + '</div><div class="ac-grid">';
-      var first = new Date(y, m, 1), startDow = first.getDay(), ndays = new Date(y, m + 1, 0).getDate(), prevDays = new Date(y, m, 0).getDate();
-      for (var i = startDow - 1; i >= 0; i--) html += '<div class="ac-cell out"><div class="ac-dh"><span class="ac-dn">' + (prevDays - i) + '</span></div></div>';
-      for (var dn = 1; dn <= ndays; dn++) {
-        var iso = y + '-' + pad(m + 1) + '-' + pad(dn);
-        var day = byDate[iso];
-        var cls = 'ac-cell' + (iso === todayISO ? ' today' : '');
-        html += '<div class="' + cls + '">';
-        if (day && day.length) {
-          var hrs = day.map(function (r) { return r.slot_hour; });
-          var span = slotLabel(Math.min.apply(null, hrs)) + '–' + slotLabel(Math.max.apply(null, hrs));
-          html += '<div class="ac-dh"><span class="ac-dn">' + dn + '</span><span class="ac-dhr"><span class="ac-cnt">' + day.length + '</span><span class="ac-span">' + span + '</span></span></div><div class="ac-chips">';
-          for (var j = 0; j < day.length; j++) {
-            var r = day[j];
-            var tip = esc(r.label + (r.vendor ? ' · ' + r.vendor : '') + (r.title ? ' · ' + r.title : '') + '\n⏱ goes active ' + fmtGoLive(r.go_live_at));
-            if (r.image && /^(https?:|\/)/.test(r.image)) {
-              html += '<div class="ac-chip" title="' + tip + '"><img loading="lazy" src="' + esc(r.image) + '" onerror="this.replaceWith(Object.assign(document.createElement(\'div\'),{className:\'ph\',style:\'background:' + vcolor(r.vendor || r.label) + '\',textContent:\'' + esc(String(r.label).slice(0, 4)) + '\'}))"></div>';
-            } else {
-              html += '<div class="ac-chip" title="' + tip + '"><div class="ph" style="background:' + vcolor(r.vendor || r.label) + '">' + esc(String(r.label).slice(0, 4)) + '</div></div>';
-            }
-          }
-          html += '</div>';
-        } else {
-          html += '<div class="ac-dh"><span class="ac-dn">' + dn + '</span></div>';
-        }
-        html += '</div>';
-      }
-      var totalCells = startDow + ndays, trail = (7 - (totalCells % 7)) % 7;
-      for (var t = 1; t <= trail; t++) html += '<div class="ac-cell out"><div class="ac-dh"><span class="ac-dn">' + t + '</span></div></div>';
-      html += '</div>';
-      calEl.innerHTML = html;
-    }
-    mount.querySelector('.ac-prev').onclick = function () { viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() - 1, 1); paint(); };
-    mount.querySelector('.ac-next').onclick = function () { viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 1); paint(); };
-    paint();
-    return { total: total, days: days, endDate: endDate };
-  }
-
-  window.ActivationCalendar = { render: render };
-})();
diff --git a/public/index.html b/public/index.html
index c244b68..9afd948 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,7 +5,7 @@
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>Zuber — Designer Wallcoverings (internal)</title>
 <style>
-  :root { --cols: 5; --fs: 13px; --teal: #0f766e; --acc: #0f766e; --hair:#e2e2e0; --ink: #1a1a1a; --mut:#6a6a72; --line: #e2e2e0; --bg: #fafaf8; --card:#f6f6f4; }
+  :root { --cols: 5; --fs: 13px; --teal: #0f766e; --ink: #1a1a1a; --mut:#6a6a72; --line: #e2e2e0; --bg: #fafaf8; --card:#f6f6f4; }
   * { box-sizing: border-box; }
   body { margin: 0; font: var(--fs)/1.45 -apple-system, "Helvetica Neue", Arial, sans-serif; color: var(--ink); background: var(--bg); }
   header { position: sticky; top: 0; z-index: 20; background: #fff; border-bottom: 1px solid var(--line); padding: 10px 16px; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
@@ -15,9 +15,6 @@
   header label { font-size: 11px; color: #666; display: inline-flex; align-items: center; gap: 6px; }
   select, input[type=search] { font: inherit; padding: 5px 8px; border: 1px solid var(--line); border-radius: 4px; background: #fff; }
   input[type=range] { vertical-align: middle; }
-  .vt{background:var(--card,#14161c);border:1px solid var(--hair,#2a2d36);color:var(--mut,#9aa0aa);border-radius:7px;padding:5px 11px;cursor:pointer;font-size:13px}
-  .vt:hover{border-color:var(--acc)}
-  .vt.active{background:var(--acc);color:#052a24;border-color:var(--acc);font-weight:700}
   .layout { display: grid; grid-template-columns: 262px 1fr; gap: 0; }
 
   /* ── left rail (all.dw parity) ── */
@@ -75,7 +72,6 @@
     <div class="sub">Designer Wallcoverings — internal · quote-only · made-to-order</div>
   </div>
   <div class="spacer"></div>
-  <div class="ctl" id="viewtoggle" style="gap:4px"><button id="tab-products" type="button" class="vt active">Products</button><button id="tab-calendar" type="button" class="vt">🗓 Activation Calendar</button></div>
   <label>Sort
     <select id="sort">
       <option value="newest">Newest</option>
@@ -95,7 +91,7 @@
     <label><input type="checkbox" class="ff" data-f="when" checked> Date</label>
   </span>
 </header>
-<div class="layout" id="layout">
+<div class="layout">
   <aside>
     <input type="search" id="q" class="search" placeholder="Search pattern / SKU / collection…" />
     <div class="active" id="activeRow"></div>
@@ -106,8 +102,6 @@
     <div class="grid" id="grid"></div>
   </main>
 </div>
-<div id="view-calendar" style="display:none"></div>
-<script src="/activation-calendar.js"></script>
 <script>
 const $ = (s, r=document) => r.querySelector(s);
 const API = location.origin;
@@ -277,10 +271,8 @@ async function refreshFacets() {
   } catch (e) { /* facets degrade silently — grid still works */ }
 }
 
-let PRODUCTS = [];
 async function load() {
   const data = await (await fetch(API + '/api/products?' + facetParams())).json();
-  PRODUCTS = data.products || [];
   $('#count').textContent = `${data.total} of ${data.all} products`;
   const grid = $('#grid'); grid.innerHTML = '';
   for (const r of data.products) {
@@ -302,12 +294,7 @@ async function load() {
   }
   ['pat','fr','meta','when'].forEach(f => applyField(f, document.querySelector('.ff[data-f="'+f+'"]').checked));
 }
-/* Products | Activation Calendar view toggle */
-let _calRendered=false;
-function showView(which){var cal=which==='calendar';document.getElementById('layout').style.display=cal?'none':'';document.getElementById('view-calendar').style.display=cal?'':'none';document.getElementById('tab-products').classList.toggle('active',!cal);document.getElementById('tab-calendar').classList.toggle('active',cal);if(cal&&!_calRendered&&window.ActivationCalendar){window.ActivationCalendar.render({mount:document.getElementById('view-calendar'),products:PRODUCTS,lineLabel:'Zuber',perDay:50});_calRendered=true;}}
-function wireActivationTabs(){var tp=document.getElementById('tab-products'),tc=document.getElementById('tab-calendar');if(tp)tp.onclick=function(){showView('products')};if(tc)tc.onclick=function(){showView('calendar')};}
-
-refreshFacets().then(load).then(wireActivationTabs);
+refreshFacets().then(load);
 setInterval(load, 60000);
 </script>
 </body>

← 8879146 Add Activation Calendar tab (go-live date+time per staged SK  ·  back to Zuber Internal  ·  auto-save: 2026-07-15T13:06:41 (1 files) — lib/ 8f9e02b →