feat: plan Week Ahead in one mobile pass (Closes #1186)
This commit is contained in:
1 parent
6718160381
commit
55e1c3d6dd
6 files changed
+106
-10
No files matched your search
@@ -241,6 +241,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
.plan-today-header { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
|
||||
.plan-today-header h2, .plan-today-header p { margin-top:0; }
|
||||
.plan-today-header button { min-width:44px; min-height:44px; }
|
||||
.week-plan-progress { margin:8px 0 4px; color:#bfdbfe; font-weight:700; }
|
||||
.week-plan-dates { display:flex; gap:8px; margin:8px 0 12px; padding:2px 0 8px; overflow-x:auto; overscroll-behavior-inline:contain; }
|
||||
.week-plan-dates[hidden] { display:none; }
|
||||
.week-plan-dates button { min-height:44px; min-width:86px; flex:0 0 auto; padding:6px 10px; }
|
||||
|
||||
@@ -3120,8 +3120,8 @@
|
||||
qs('#plan-today-error').textContent = '';
|
||||
qs('#plan-today-sheet').hidden = false;
|
||||
document.body.classList.add('task-overlay-open');
|
||||
weekWorkflow.renderDates();
|
||||
renderPlanToday();
|
||||
weekWorkflow.renderDates();
|
||||
if (protectProposal) qs('#plan-today-build-status').textContent = protectProposal.summary +
|
||||
(protectProposal.displaced.length ? '. Displaced work remains unchanged until you save.' : '. Review estimates and capacity before saving.');
|
||||
qs('#cancel-plan-today').focus();
|
||||
@@ -7920,7 +7920,7 @@
|
||||
const result = planToday.commit({ start, confirmOverCapacity: button.dataset.confirmOverCapacity === 'true' });
|
||||
if (result === 'saved') {
|
||||
button.dataset.confirmOverCapacity = '';
|
||||
taskOverlayHistory.leave();
|
||||
if (!weekWorkflow.active() || !weekWorkflow.advance()) taskOverlayHistory.leave();
|
||||
} else if (result === 'confirm-over-capacity') {
|
||||
button.dataset.confirmOverCapacity = 'true';
|
||||
qs('#plan-today-error').textContent = 'This plan exceeds your available time. Press again to save over capacity.';
|
||||
|
||||
@@ -460,6 +460,7 @@
|
||||
<div><h2 id="plan-today-title">Plan Today</h2><p class="small muted">Choose and order the work you want to finish next.</p></div>
|
||||
<button id="cancel-plan-today" type="button">Cancel</button>
|
||||
</div>
|
||||
<p class="week-plan-progress" id="week-plan-progress" aria-live="polite" hidden></p>
|
||||
<nav class="week-plan-dates" id="week-plan-dates" aria-label="Week Ahead dates" hidden></nav>
|
||||
<section class="tomorrow-conflict-review" id="tomorrow-conflict-review" aria-labelledby="tomorrow-conflict-title" hidden>
|
||||
<div class="small">Cross-device change</div>
|
||||
|
||||
+33
-4
@@ -49,6 +49,12 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin}={}) {
|
||||
const found=week.days.find(item=>item.plan_date===planDate);
|
||||
return found?cloneDay(found):{plan_date:planDate,ids:[],capacity_minutes:null,estimates:{}};
|
||||
}
|
||||
function pass(planDate) {
|
||||
const available=dates(),index=available.findIndex(item=>item.date===planDate);
|
||||
if(index<0) return null;
|
||||
return {position:index+1,total:available.length,planned:week.days.filter(item=>item.ids.length).length,
|
||||
next_date:available[index+1]?.date||null,last:index===available.length-1};
|
||||
}
|
||||
async function load() {
|
||||
const queued=pending();
|
||||
if(queued){week=queued;return state();}
|
||||
@@ -231,13 +237,21 @@ function createWeekPlan({fetchJson,localDate,timeZone,storage,getLogin}={}) {
|
||||
const label=planned.length?`${items} item${items===1?'':'s'} across ${planned.length} day${planned.length===1?'':'s'}`:'Nothing planned';
|
||||
return label+(pending()?' · sync pending':'');
|
||||
}
|
||||
return {adopt,state,dates,day,load,saveDay,stageDay,pending,flush,conflict,chooseDay,saveMerged,
|
||||
return {adopt,state,dates,day,pass,load,saveDay,stageDay,pending,flush,conflict,chooseDay,saveMerged,
|
||||
keepLocal,useRemote,promote,summary};
|
||||
}
|
||||
function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,escapeAttribute,
|
||||
todayWork,refresh,warm}={}) {
|
||||
let selectedDate=null;
|
||||
let blockedReviewOpen=false;
|
||||
function renderPass() {
|
||||
const progress=qs('#week-plan-progress'),save=qs('#save-today-plan');
|
||||
if(!selectedDate){progress.hidden=true;return;}
|
||||
const current=controller.pass(selectedDate);
|
||||
progress.hidden=false;
|
||||
progress.textContent=`Day ${current.position} of ${current.total} · ${current.planned} planned`;
|
||||
save.textContent=current.last?'Save week':'Save & next';
|
||||
}
|
||||
function conflictDetail(day) {
|
||||
const estimates=day.estimates||{},ids=day.ids||[];
|
||||
const minutes=ids.reduce((total,id)=>total+(Number(estimates[id])||0),0),capacity=Number(day.capacity_minutes)||0;
|
||||
@@ -273,6 +287,7 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,e
|
||||
function renderDates() {
|
||||
const root=qs('#week-plan-dates');
|
||||
root.hidden=!selectedDate;
|
||||
renderPass();
|
||||
if(!selectedDate)return;
|
||||
root.innerHTML=controller.dates().map(item=>'<button type="button" data-week-plan-date="'+
|
||||
escapeAttribute(item.date)+'"'+(item.date===selectedDate?' aria-current="date"':'')+'><strong>'+escapeHtml(item.label)+
|
||||
@@ -284,7 +299,7 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,e
|
||||
async function open(trigger) {
|
||||
trigger.disabled=true;selectedDate=controller.dates()[0].date;renderDates();openPlanner(trigger);
|
||||
qs('#mobile-week-summary').textContent='Loading Week Ahead…';
|
||||
try{await controller.load();qs('#mobile-week-summary').textContent=controller.summary();openPlanner(null,false);return true;}
|
||||
try{await controller.load();qs('#mobile-week-summary').textContent=controller.summary();renderDates();openPlanner(null,false);return true;}
|
||||
catch(error){qs('#mobile-week-summary').textContent='Unavailable · tap to retry';qs('#my-work-action-status').textContent=(error.message||'Week Ahead is unavailable.')+' Retry when connected.';return false;}
|
||||
finally{trigger.disabled=false;}
|
||||
}
|
||||
@@ -295,7 +310,21 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,e
|
||||
qs('#mobile-week-summary').textContent=controller.summary();
|
||||
qs('#my-work-action-status').textContent='Week Ahead saved on this phone · sync pending.';
|
||||
controller.flush().then(()=>{qs('#mobile-week-summary').textContent=controller.summary();qs('#my-work-action-status').textContent=(normalized.ids.length?'Week Ahead saved for ':'Week Ahead cleared for ')+date+'.';})
|
||||
.catch(error=>{qs('#mobile-week-summary').textContent=controller.conflict()?'Conflict · review required':controller.summary();qs('#my-work-action-status').textContent=controller.conflict()?'Another device changed Week Ahead. Both versions are preserved; open Week Ahead to choose one.':(error.message||'Week Ahead sync is unavailable.')+' Saved on this phone · sync pending.';});
|
||||
.catch(error=>{
|
||||
const conflict=controller.conflict();
|
||||
qs('#mobile-week-summary').textContent=conflict?'Conflict · review required':controller.summary();
|
||||
qs('#my-work-action-status').textContent=conflict?'Another device changed Week Ahead. Both versions are preserved; choose each changed day.':
|
||||
(error.message||'Week Ahead sync is unavailable.')+' Saved on this phone · sync pending.';
|
||||
if(conflict) openPlanner(null,false);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
function advance() {
|
||||
const current=selectedDate&&controller.pass(selectedDate);
|
||||
if(!current?.next_date) return false;
|
||||
selectedDate=current.next_date;renderDates();openPlanner(null,false);
|
||||
const selected=qs('#week-plan-dates').querySelector(`[data-week-plan-date="${selectedDate}"]`);
|
||||
selected?.scrollIntoView({block:'nearest',inline:'center'});selected?.focus?.();
|
||||
return true;
|
||||
}
|
||||
async function promote(plan){
|
||||
@@ -310,7 +339,7 @@ function createWeekPlanWorkflow({controller,qs,getLogin,openPlanner,escapeHtml,e
|
||||
qs('#my-work-action-status').textContent=(error.message||'Week Ahead needs review before promotion.')+' Open Week Ahead to review.';return false;
|
||||
}
|
||||
}
|
||||
return {open,save,promote,renderDates,renderConflict,active:()=>Boolean(selectedDate),selectedDate:()=>selectedDate,
|
||||
return {open,save,advance,promote,renderDates,renderConflict,active:()=>Boolean(selectedDate),selectedDate:()=>selectedDate,
|
||||
day:()=>selectedDate?controller.day(selectedDate):null,
|
||||
copy:()=>selectedDate?{title:'Plan Week Ahead',heading:selectedDate+', in order',available:'Available this day',build:'Build this day'}:null,
|
||||
clear(){selectedDate=null;renderDates();}};
|
||||
|
||||
Reference in new issue
Block a user