[object Object]

← back to Agent Cabinet

Activity viewer: date-range filter — from/to date pickers + Today/7d/30d/All presets scope the feed by timestamp; composes with search + type chips; 'in range' count + active-preset highlight

c18a8e89f7cf61b11301f39a00804bdc23370259 · 2026-06-16 18:24:15 -0700 · SteveStudio2

Files touched

Diff

commit c18a8e89f7cf61b11301f39a00804bdc23370259
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue Jun 16 18:24:15 2026 -0700

    Activity viewer: date-range filter — from/to date pickers + Today/7d/30d/All presets scope the feed by timestamp; composes with search + type chips; 'in range' count + active-preset highlight
---
 activity.html | 39 +++++++++++++++++++++++++++++++++++----
 1 file changed, 35 insertions(+), 4 deletions(-)

diff --git a/activity.html b/activity.html
index 8bb3040..b3a9417 100644
--- a/activity.html
+++ b/activity.html
@@ -21,6 +21,12 @@
   #search:focus{outline:none;border-color:#34f4ce}
   #search-x{color:var(--dim);font-size:12px;cursor:pointer;display:none}
   #search-x.show{display:inline}
+  .daterow{margin-top:10px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dim)}
+  .daterow input[type=date]{background:#0a0e16;border:1px solid #2b3650;color:#e7ecf3;border-radius:7px;padding:5px 9px;font-size:12px;color-scheme:dark}
+  .daterow input[type=date]:focus{outline:none;border-color:#34f4ce}
+  .pre{cursor:pointer;font-size:11.5px;padding:4px 10px;border-radius:14px;border:1px solid var(--line);background:#121826;color:#aeb8c8}
+  .pre:hover{border-color:#34f4ce;color:#fff}
+  .pre.on{background:#10302b;border-color:#34f4ce;color:#34f4ce}
   main{max-width:1340px;margin:0 auto;padding:18px 28px 70px}
   .cols{display:grid;grid-template-columns:var(--col-w,380px) 14px minmax(0,1fr);align-items:start}
   .col-up{position:sticky;top:132px;max-height:calc(100vh - 150px);overflow:auto;padding-right:6px}
@@ -73,6 +79,9 @@
   <div class="meta"><span class="pulse"></span><span id="meta">loading…</span> · auto-refreshes every 30s · <a href="/pyramid">← cabinet pyramid</a></div>
   <div class="chips" id="chips"></div>
   <div class="searchrow"><input id="search" type="search" placeholder="🔎 filter events &amp; jobs — title, vendor, repo, skill…" autocomplete="off"><span id="search-x" onclick="clearSearch()">✕ clear</span></div>
+  <div class="daterow">📅 <input type="date" id="from"> <span>→</span> <input type="date" id="to">
+    <span class="pre" onclick="preset(0,this)">Today</span><span class="pre" onclick="preset(7,this)">7d</span><span class="pre" onclick="preset(30,this)">30d</span><span class="pre on" onclick="preset(null,this)">All</span>
+    <span id="range-n"></span></div>
 </header>
 <div id="det"><div class="det-head"><span id="det-title">—</span><button class="det-x" onclick="closeDet()">✕</button></div><div id="det-body"></div></div>
 <main class="cols">
@@ -89,15 +98,16 @@ const TYPES={
   tick:{label:'Watch ticks', col:'#60a5fa'},
 };
 const VCOL={FAIL:'#ef4444',WARN:'#fbbf24',PASS:'#34d399',NA:'#6b7280'};
-let DATA=[], OFF_FILTER=new Set(), QUERY='';   // types hidden + search query
+let DATA=[], OFF_FILTER=new Set(), QUERY='', FROM=null, TO=null;   // type/text/date filters
 function matchQ(e){ return !QUERY || ((e.title||'')+' '+(e.detail||'')+' '+(e.type||'')).toLowerCase().includes(QUERY); }
+function inRange(ts){ const t=Date.parse(ts); if(isNaN(t)) return true; return (FROM==null||t>=FROM)&&(TO==null||t<=TO); }
 function colorFor(e){ if(e.type==='canary') return VCOL[e.verdict]||TYPES.canary.col; return (TYPES[e.type]||{}).col||'#2b3650'; }
 function fmtTime(iso){ try{ return new Date(iso).toLocaleTimeString(undefined,{hour:'2-digit',minute:'2-digit'}); }catch(e){ return '--:--'; } }
 function fmtDay(iso){ try{ return new Date(iso).toLocaleDateString(undefined,{weekday:'short',month:'short',day:'numeric',year:'numeric'}); }catch(e){ return iso; } }
 function dayKey(iso){ try{ return new Date(iso).toISOString().slice(0,10); }catch(e){ return iso.slice(0,10); } }
 
 function renderChips(){
-  const set=DATA.filter(matchQ);
+  const set=DATA.filter(e=>matchQ(e)&&inRange(e.ts));
   const counts={}; set.forEach(e=>counts[e.type]=(counts[e.type]||0)+1);
   const c=document.getElementById('chips');
   c.innerHTML='<span class="chip '+(OFF_FILTER.size?'':'on')+'" data-t="__all"><b>All</b> <span class="n">'+set.length+'</span></span>'+
@@ -109,9 +119,9 @@ function renderChips(){
   });
 }
 function renderFeed(){
-  const shown=DATA.filter(e=>!OFF_FILTER.has(e.type) && matchQ(e));
+  const shown=DATA.filter(e=>!OFF_FILTER.has(e.type) && matchQ(e) && inRange(e.ts));
   const feed=document.getElementById('feed');
-  if(!shown.length){ feed.innerHTML='<div class="empty">'+(QUERY?'No matches for “'+esc(QUERY)+'”.':'No events for this filter.')+'</div>'; return; }
+  if(!shown.length){ feed.innerHTML='<div class="empty">'+(QUERY||FROM||TO?'No matches for the current filters.':'No events for this filter.')+'</div>'; return; }
   let html='', curDay=null;
   shown.forEach(e=>{
     const dk=dayKey(e.ts);
@@ -204,6 +214,27 @@ document.addEventListener('keydown', e=>{ if(e.key==='Escape') closeDet(); });
   window.addEventListener('mouseup', ()=>{ if(!dragging) return; dragging=false; sp.classList.remove('drag'); document.body.style.userSelect=''; const w=parseInt(cols.style.getPropertyValue('--col-w'),10); if(w) localStorage.setItem(KEY,w); });
   sp.addEventListener('dblclick', ()=>{ cols.style.setProperty('--col-w','380px'); localStorage.setItem(KEY,'380'); });
 })();
+// date range filter
+function pad2(n){ return ('0'+n).slice(-2); }
+function fmtDI(d){ return d.getFullYear()+'-'+pad2(d.getMonth()+1)+'-'+pad2(d.getDate()); }
+function recomputeDates(){
+  const fv=document.getElementById('from').value, tv=document.getElementById('to').value;
+  FROM = fv ? Date.parse(fv+'T00:00:00') : null;
+  TO   = tv ? Date.parse(tv+'T23:59:59') : null;
+  const set=DATA.filter(e=>matchQ(e)&&inRange(e.ts));
+  document.getElementById('range-n').textContent = (FROM||TO) ? (set.length+' in range') : '';
+  renderChips(); renderFeed();
+}
+function preset(days, el){
+  const f=document.getElementById('from'), t=document.getElementById('to');
+  if(days==null){ f.value=''; t.value=''; }
+  else { const now=new Date(); t.value=fmtDI(now); f.value=fmtDI(new Date(now.getTime()-days*86400000)); }
+  document.querySelectorAll('.pre').forEach(p=>p.classList.remove('on'));
+  if(el) el.classList.add('on');
+  recomputeDates();
+}
+['from','to'].forEach(id=>document.getElementById(id).addEventListener('change', ()=>{ document.querySelectorAll('.pre').forEach(p=>p.classList.remove('on')); recomputeDates(); }));
+
 const _si=document.getElementById('search');
 _si.addEventListener('input', ()=>{ QUERY=_si.value.trim().toLowerCase(); document.getElementById('search-x').classList.toggle('show', !!QUERY); renderChips(); renderFeed(); renderSchedule(); });
 function clearSearch(){ _si.value=''; QUERY=''; document.getElementById('search-x').classList.remove('show'); _si.focus(); renderChips(); renderFeed(); renderSchedule(); }

← c791b73 Activity viewer: live search box — filters the feed AND sche  ·  back to Agent Cabinet  ·  Activity viewer: FIX day-grouping used UTC date while times d170113 →