feat: start Week Ahead days early (Closes #1224)
This commit is contained in:
1 parent
25597d1f05
commit
f64ce3acc4
8 files changed
+212
-11
No files matched your search
@@ -333,6 +333,8 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
.week-review-item-open { display:block; width:100%; min-width:0; min-height:44px; padding:8px; border:0; border-radius:8px; background:transparent; color:inherit; text-align:left; }
|
||||
.week-review-item-open:hover { background:#173453; }
|
||||
.week-review-item-open:focus-visible { outline:3px solid #93c5fd; outline-offset:2px; }
|
||||
.week-start-early { display:block; width:100%; min-height:44px; margin-top:12px; border-color:#60a5fa; background:#1d4f7a; color:#eff6ff; font-weight:800; }
|
||||
.week-start-early:focus-visible { outline:3px solid #bfdbfe; outline-offset:2px; }
|
||||
.week-review-item-copy strong, .week-review-item-copy small { overflow-wrap:anywhere; }
|
||||
.week-review-item-copy small { color:#a9bdd3; }
|
||||
.week-review-move { display:flex; align-items:end; gap:8px; }
|
||||
|
||||
@@ -457,7 +457,7 @@
|
||||
getItem:weekItem,openItem:openRoutedWork,
|
||||
openPlanner:openPlanToday,setReviewMode:value=>qs('#plan-today-sheet').classList.toggle('week-review-mode',value),
|
||||
escapeHtml,escapeAttribute:escAttr,todayWork,
|
||||
refresh:refreshMyWorkView,warm:warmTodayOffline});
|
||||
t:()=>latestTodayPlan,r:refreshMyWorkView,w:warmTodayOffline});
|
||||
const weekCalendar=StackchainWeekCalendar.mountWeekCalendarHandoff({qs,getItem:weekItem,escapeHtml,escapeAttribute:escAttr,
|
||||
onDone:()=>{weekFlow.finish();taskOverlayHistory.leave();},
|
||||
});
|
||||
@@ -7881,7 +7881,7 @@
|
||||
try {
|
||||
await weekPlan.keepLocal();
|
||||
qs('#mobile-week-summary').textContent=weekPlan.summary();
|
||||
qs('#my-work-action-status').textContent='This phone’s Week Ahead plan is saved to your account. Today was not changed.';
|
||||
qs('#my-work-action-status').textContent='This phone’s Week Ahead plan is saved. Today was not changed.';
|
||||
closePlanToday();
|
||||
} catch(error) {
|
||||
const conflict=weekPlan.conflict();
|
||||
|
||||
+39
-4
@@ -406,6 +406,17 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin,now=()=>D
|
||||
plan_date:due.plan_date,today_revision:todayRevision,
|
||||
})});
|
||||
}
|
||||
async function startEarly(planDate,todayRevision) {
|
||||
if(offlineSnapshot||pending()||conflict()||!Number.isInteger(todayRevision))return false;
|
||||
const day=week.days.find(item=>item.plan_date===planDate&&item.ids.length);
|
||||
if(!day)return false;
|
||||
const promoted=await fetchJson('api/v1/week/start-early',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({
|
||||
promotion_id:`early-${day.plan_date}-r${week.revision}`,week_revision:week.revision,
|
||||
plan_date:day.plan_date,today_revision:todayRevision,
|
||||
})});
|
||||
await load();
|
||||
return promoted;
|
||||
}
|
||||
async function reconcilePromotion(preserved,selection) {
|
||||
if(offlineSnapshot||pending()||conflict()) return false;
|
||||
const due=(preserved?.week?.days||week.days).find(item=>item.plan_date<=localDate()&&item.ids.length);
|
||||
@@ -425,12 +436,13 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin,now=()=>D
|
||||
return label+(pending()?' · sync pending':'');
|
||||
}
|
||||
return {adopt,state,dates,day,pass,load,saveDay,stageDay,stageCapacities,review,move,placement,place,pending,flush,conflict,chooseDay,saveMerged,
|
||||
keepLocal,useRemote,promote,reconcile:reconcilePromotion,summary,rememberItems,rememberPendingItem,
|
||||
keepLocal,useRemote,promote,startEarly,reconcile:reconcilePromotion,summary,rememberItems,rememberPendingItem,
|
||||
item:id=>pendingItems[id]||confirmedItems[id]||null,
|
||||
offline:()=>offlineSnapshot};
|
||||
}
|
||||
function createWeekPlanWorkflow({controller,qs,getItem=()=>null,openItem,openPlanner,setReviewMode=()=>{},escapeHtml,escapeAttribute,
|
||||
todayWork,refresh,warm}={}) {
|
||||
todayWork,refresh:r=()=>{},warm:w=()=>{},today:t=()=>null,r:refresh=r,w:warm=w,t:getTodayPlan=t,
|
||||
confirmEarly=message=>globalThis.confirm?.(message)??false}={}) {
|
||||
let selectedDate=null;
|
||||
let reviewing=false;
|
||||
let overviewing=false;
|
||||
@@ -521,7 +533,10 @@ function createWeekPlanWorkflow({controller,qs,getItem=()=>null,openItem,openPla
|
||||
function renderReview() {
|
||||
const value=controller.review(),root=qs('#week-review-days'),duplicates=qs('#week-review-duplicates');
|
||||
const readOnly=Boolean(controller.offline?.());
|
||||
const pending=Boolean(controller.pending?.());
|
||||
const nextUp=overviewing?value.days.find(day=>day.ids.length):null;
|
||||
const today=getTodayPlan?.();
|
||||
const canStartEarly=Boolean(nextUp&&!readOnly&&!pending&&Number.isInteger(today?.revision)&&!today.ids?.length);
|
||||
const destinations=value.days.map(day=>'<option value="'+escapeAttribute(day.plan_date)+'">'+escapeHtml(day.label)+'</option>').join('');
|
||||
const itemMarkup=(id,day)=>{
|
||||
const item=getItem(id)||controller.item?.(id),estimate=Number(day.estimates?.[id])||0;
|
||||
@@ -540,14 +555,14 @@ function createWeekPlanWorkflow({controller,qs,getItem=()=>null,openItem,openPla
|
||||
const items=day.ids.length?'<ul>'+day.ids.map(id=>'<li>'+itemMarkup(id,day)+move(id)+'</li>').join('')+'</ul>':
|
||||
'<p class="small muted">Nothing planned.</p>';
|
||||
const edit=readOnly?'':'<button type="button" data-week-edit-day="'+escapeAttribute(day.plan_date)+'" aria-label="Edit '+escapeAttribute(day.label)+'">Edit day</button>';
|
||||
const start=day===nextUp&&canStartEarly?'<button type="button" class="week-start-early" data-week-start-early="'+escapeAttribute(day.plan_date)+'">Start this day early</button>':'';
|
||||
return '<article class="week-review-day'+(day.overloaded?' is-overloaded':'')+(day===nextUp?' is-next-up':'')+'"><header><h3>'+escapeHtml(day.label)+(day===nextUp?' <span class="week-next-up">Next up</span>':'')+'</h3>'+edit+'</header><p class="week-review-load">'+
|
||||
escapeHtml(load)+'</p>'+items+'</article>';
|
||||
escapeHtml(load)+'</p>'+items+start+'</article>';
|
||||
}).join('');
|
||||
duplicates.hidden=!value.duplicates.length;
|
||||
duplicates.innerHTML=value.duplicates.length?'<strong>Choose one date for duplicated work before confirming.</strong><ul>'+value.duplicates.map(item=>
|
||||
'<li><code>'+escapeHtml(item.id)+'</code> · '+escapeHtml(item.dates.join(', '))+'</li>').join('')+'</ul>':'';
|
||||
const confirm=qs('#confirm-week-plan');
|
||||
const pending=Boolean(controller.pending?.());
|
||||
confirm.disabled=!value.can_confirm||pending;
|
||||
const blocker=value.blockers?.[0];
|
||||
qs('#week-review-status').textContent=readOnly?'Offline snapshot · viewing only. Retry for live editing.':(overviewing?'Week Ahead overview · '+(pending?'sync pending.':'no changes made.'):(value.duplicates.length?'Duplicate work must be moved to one date.':
|
||||
@@ -569,6 +584,26 @@ function createWeekPlanWorkflow({controller,qs,getItem=()=>null,openItem,openPla
|
||||
const id=button.dataset.weekOpenItem,item=getItem(id)||controller.item?.(id);
|
||||
if(item)openItem?.(item,event.currentTarget);
|
||||
}));
|
||||
root.querySelectorAll('[data-week-start-early]').forEach(button=>button.addEventListener('click',async event=>{
|
||||
const day=value.days.find(item=>item.plan_date===event.currentTarget.dataset.weekStartEarly);
|
||||
const current=getTodayPlan?.();
|
||||
if(!day||!Number.isInteger(current?.revision)||current.ids?.length)return;
|
||||
const capacity=Number(day.capacity_minutes)||0;
|
||||
const detail=day.ids.length+' item'+(day.ids.length===1?'':'s')+' · '+day.planned_minutes+(capacity?' of '+capacity:'')+' min';
|
||||
if(!confirmEarly('Start '+day.label+' early?\n\n'+detail+' will move into Today.'))return;
|
||||
event.currentTarget.disabled=true;
|
||||
try{
|
||||
const promoted=await controller.startEarly(day.plan_date,current.revision);
|
||||
if(!promoted)return;
|
||||
todayWork.replace(promoted.ids);todayWork.replacePlanning(promoted);refresh();warm();
|
||||
qs('#mobile-week-summary').textContent=controller.summary();
|
||||
qs('#my-work-action-status').textContent=day.label+' is now Today.';
|
||||
finish();
|
||||
}catch(error){
|
||||
qs('#my-work-action-status').textContent=(error.message||'Week Ahead changed before it could start.')+' Reopen Week Ahead and review.';
|
||||
event.currentTarget.disabled=false;
|
||||
}
|
||||
}));
|
||||
root.querySelectorAll('[data-week-edit-day]').forEach(button=>button.addEventListener('click',()=>editDay(button.dataset.weekEditDay)));
|
||||
renderPass();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user