Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,16 @@
.gflegend span{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:700;color:var(--ink);white-space:nowrap}
.gflegend i{width:12px;height:12px;border-radius:4px;flex:none}
.gzoom button{padding:7px 13px;font-size:12.5px;min-height:34px;display:flex;align-items:center;justify-content:center}
/* timeline week navigation — fixed over the chart edges, does not scroll with the grid */
.gnav{position:absolute;left:0;right:0;bottom:10px;height:44px;z-index:22;pointer-events:none}
.gnavbtn{position:absolute;top:0;transform:none;pointer-events:auto;
width:30px;height:44px;border-radius:10px;background:rgba(255,255,255,.94);
border:1px solid var(--line);box-shadow:0 2px 10px rgba(20,24,35,.1);
font-size:22px;font-weight:400;line-height:1;color:var(--ink-2);
display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}
.gnavbtn:hover{background:var(--surface);color:var(--ink)}
.gnavbtn:active{transform:scale(.96)}
.gnav-l{left:6px}.gnav-r{right:6px}
/* owners shown as a compact segmented row of avatars */
.gpeople{padding:3px;gap:2px}
.gpeople button{padding:2px;min-height:0;width:38px;height:34px;border-radius:8px;display:flex;
Expand Down Expand Up @@ -605,6 +615,9 @@
.gpeople button{width:30px;height:28px}
.gpeople .av{width:23px;height:23px;font-size:9px}
.gzoom button{padding:6px 9px;font-size:11.5px;min-height:30px}
.gnav{bottom:6px;height:38px}
.gnavbtn{width:26px;height:38px;font-size:20px}
.gnav-l{left:4px}.gnav-r{right:4px}
.gficon{width:31px;height:31px}
.gficon svg{width:17px;height:17px}
/* capture + team sheets use the full width on a phone */
Expand Down Expand Up @@ -739,6 +752,10 @@
</div>
</div>
</div>
<div class="gnav" id="gnav" aria-label="Timeline navigation">
<button class="gnavbtn gnav-l" title="Previous week" onclick="navTimeline(-1)" aria-label="Previous week">‹</button>
<button class="gnavbtn gnav-r" title="Next week" onclick="navTimeline(1)" aria-label="Next week">›</button>
</div>
<div id="gantt"></div>
</div>
</section>
Expand Down
46 changes: 37 additions & 9 deletions src/app/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
PEOPLE, TODAY, HARDWARE_VOCAB, CLIENTS,
SIZE_PTS, SIZE_NAMES, LEAD, ZOOMS, GBAR_H,
R0G, R1G, SPAN_G, TODAY_PX,
R0G, R1G, SPAN_G, GANTT_PAST, TODAY_PX,
C_LATE, C_TODAY, C_RADAR, C_LATER, C_DONE,
} from "../data/constants.js";
import { inferOwnerByDomain, canonHardware, findClient, buildRespMapText, buildVocabText, norm as _norm } from "../lib/domain.js";
Expand Down Expand Up @@ -231,7 +231,7 @@ function sizeScale(){
bars. Bar height tracks t-shirt size; each bar has a done-dot (left) and owner bubble
(right). The window scrolls horizontally; zoom buttons set how many days fit on screen. */
/* chart starts at today - no dead space on the left */
let ZOOM = 2, showDone = false;
let ZOOM = 1, showDone = false; // default: 3-week view
const {
dayN, dayIso, barSpan, workDays, barColor, barGeom,
rollupSpan, spanFor, leafWeight, progWD, isUrgent, fmtD,
Expand Down Expand Up @@ -290,7 +290,31 @@ function toggleFocus(){ focusToday=!focusToday; const b=$id("gfocusbtn"); if(b)b
let GVIEW="proj"; // "proj" | "tasks" | "subs"
function setGView(v){ GVIEW=v; defer(renderGantt); }
function setZoom(i){ ZOOM=i; setTimeout(renderAll,0); } // drives the scale window and the gantt zoom
/* scroll the timeline by one week without changing the visible day count (1W / 3W / 6W) */
function navTimeline(dir){
const sc=document.querySelector(".gscroll"), inner=sc?.querySelector(".ginner");
if(!sc||!inner) return;
const anchor=scrollAnchorDay(sc,inner);
const target=Math.max(R0G,Math.min(R1G-ZOOMS[ZOOM].v,anchor+dir*7));
sc.scrollLeft=dayScrollPx(target,inner);
pinFlags(); placeFloat();
}
/* pixel offset of a day index on the inner track (inverse of gx) */
function dayScrollPx(d,inner){
return gx(d)/100*inner.clientWidth;
}
/* day index nearest the left edge of the scrolled viewport */
function scrollAnchorDay(sc,inner){
const frac=sc.scrollLeft/Math.max(inner.clientWidth,1)*100;
let best=R0G, dist=Infinity;
for(let d=R0G;d<=R1G;d++){
const g=gx(d); if(Math.abs(g-frac)<dist){ dist=Math.abs(g-frac); best=d; }
}
return best;
}
function renderGantt(){
const scBefore=document.querySelector(".gscroll");
const savedScroll=scBefore?scBefore.scrollLeft:null;
const VIS=ZOOMS[ZOOM].v;
// fixed-width today box: convert TODAY_PX into day units for the current zoom + panel width.
// On phones the box is narrower so the rest of the timeline isn't squeezed off-screen.
Expand All @@ -302,15 +326,15 @@ function renderGantt(){
// calendar axis: month headers + one weekday-letter + number per day (weekly when too tight)
const showDaily=dayPx>=20;
const months=[]; let lastM=-1;
for(let d=0;d<=R1G;d++){ const dt=new Date(dayIso(d)), m=dt.getFullYear()*12+dt.getMonth();
for(let d=R0G;d<=R1G;d++){ const dt=new Date(dayIso(d)), m=dt.getFullYear()*12+dt.getMonth();
if(m!==lastM){ lastM=m; months.push({d,label:dt.toLocaleDateString("en-GB",{month:"short",year:"numeric"})}); } }
const dticks=[];
for(let d=0;d<=R1G;d++){ const dt=new Date(dayIso(d)), wknd=dt.getDay()%6===0;
for(let d=R0G;d<=R1G;d++){ const dt=new Date(dayIso(d)), wknd=dt.getDay()%6===0;
if(showDaily||dt.getDay()===1||d===0) dticks.push({d,wd:"SMTWTFS"[dt.getDay()],num:dt.getDate(),wknd,today:d===0}); }
const zoomEl=document.getElementById("gzoom");
if(zoomEl){ // build once; afterwards only toggle classes (keeps the clicked button alive)
if(!zoomEl.childElementCount)
zoomEl.innerHTML=ZOOMS.map((z,i)=>`<button title="${z.l}" onclick="setZoom(${i})">${["D","1W","3W","6W"][i]}</button>`).join("");
zoomEl.innerHTML=ZOOMS.map((z,i)=>`<button title="${z.l}" onclick="setZoom(${i})">${["1W","3W","6W"][i]}</button>`).join("");
[...zoomEl.children].forEach((b,i)=>b.classList.toggle("active",ZOOM===i));
}
// keep the three toggle pictograms lit in line with their state
Expand All @@ -324,13 +348,13 @@ function renderGantt(){
[...gv.children].forEach((b,i)=>b.classList.toggle("active",GVIEW===GVKEYS[i]));
}
// calendar grid: faint day lines, firmer Monday lines, alternate weeks washed
const wk=[0];
for(let d=1;d<=R1G;d++) if(new Date(dayIso(d)).getDay()===1) wk.push(d);
const wk=[R0G];
for(let d=R0G+1;d<=R1G;d++) if(new Date(dayIso(d)).getDay()===1) wk.push(d);
wk.push(R1G);
const deco=[];
for(let i=0;i<wk.length-1;i++) if(i%2===1)
deco.push(`<div class="gwkband" style="left:${gx(wk[i])}%;width:${gx(wk[i+1])-gx(wk[i])}%"></div>`);
for(let d=1;d<R1G;d++){ const mon=new Date(dayIso(d)).getDay()===1;
for(let d=R0G+1;d<R1G;d++){ const mon=new Date(dayIso(d)).getDay()===1;
deco.push(`<div class="${mon?'gweek':'gday'}" style="left:${gx(d)}%"></div>`); }
months.forEach(m=>{ if(m.d>0) deco.push(`<div class="gmonthline" style="left:${gx(m.d)}%"></div>`); });
const td=new Date(dayIso(0));
Expand Down Expand Up @@ -457,7 +481,10 @@ function renderGantt(){
(any?"":'<div class="grow"><span style="color:var(--ink-3);font-size:13.5px;padding:6px 0">No scheduled tasks for this filter.</span></div>')+
`</div></div>`;
const sc=document.querySelector(".gscroll");
sc.addEventListener("scroll",pinFlags,{passive:true});
const inner=sc.querySelector(".ginner");
if(savedScroll!==null) sc.scrollLeft=savedScroll;
else sc.scrollLeft=dayScrollPx(0,inner);
if(!sc._pinBound){ sc._pinBound=true; sc.addEventListener("scroll",pinFlags,{passive:true}); }
const gpane=document.querySelector(".gantt");
if(gpane&&!gpane._floatBound){ gpane._floatBound=true; gpane.addEventListener("scroll",placeFloat,{passive:true}); }
pinFlags(); placeFloat(); placeOverflowTitles();
Expand Down Expand Up @@ -1527,6 +1554,7 @@ const _globals = {
toggleFlyout, toggleFocus, toggleShowDone, toggleSubs, closeCapture, toggleCapLang, minimizeCapture,
sendTurn, restoreCapture, skipKey, saveKey, clearKey, closeTranscript, runTranscript, closeReview,
closeTeam, closeSheet, setFilter, setScaleView, ding, toggleDone, openDetail, setZoom, setGView,
navTimeline,
toggleExp, updTask, refreshBarMenu, addChild, addProject, deleteTask, addCapTask, barDown, barContext, pickSearch,
uploadPhoto, removePhoto, rvToggle, rvText, rvOwner, rvDue, rvSize, pushApproved, attachTranscript,
doSearch, refreshCard, delCapTask, setTask, setTaskOwner, setTaskSize, setSub, setSubOwner, addSub,
Expand Down
6 changes: 3 additions & 3 deletions src/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ export const SIZE_NAMES = { s: "S", m: "M", l: "L", xl: "XL" };
export const LEAD = { s: 1, m: 3, l: 7, xl: 14 };

export const ZOOMS = [
{ l: "Day", h: 0, v: 3 },
{ l: "Week", h: 7, v: 7 },
{ l: "1 week", h: 7, v: 7 },
{ l: "3 weeks", h: 21, v: 21 },
{ l: "6 weeks", h: 42, v: 42 },
];

export const GBAR_H = { s: 26, m: 34, l: 44, xl: 56 };
export const R0G = 0;
export const GANTT_PAST = 42; // days before today included in the scrollable timeline
export const R0G = -GANTT_PAST;
export const R1G = 90;
export const SPAN_G = R1G - R0G;
export const TODAY_PX = 240;
Expand Down
Loading