diff --git a/frontend/dashboard.css b/frontend/dashboard.css index 01c78c9..b013d2f 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -264,6 +264,26 @@ textarea { resize: vertical; min-height: 120px; } .tomorrow-conflict-plans { grid-template-columns:1fr; } .tomorrow-conflict-actions { flex-direction:column; } } +.week-conflict-review { margin-top:14px; } +.week-conflict-plans { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; } +.week-conflict-plans > section { min-width:0; padding:12px; border:1px solid #31577f; border-radius:12px; background:#10233a; } +.week-conflict-plans h4 { margin:0 0 8px; } +.week-conflict-day { margin:0 0 10px; padding-bottom:10px; border-bottom:1px solid #31577f; } +.week-conflict-day:last-child { margin-bottom:0; padding-bottom:0; border-bottom:0; } +.week-conflict-day strong { display:block; color:#bfdbfe; } +.week-conflict-actions { display:flex; gap:8px; margin-top:14px; } +.week-conflict-actions button { min-height:44px; flex:1 1 0; } +.week-conflict-status { min-height:1.4em; margin-top:8px; } +.plan-today-sheet.week-conflict-mode .week-plan-dates, +.plan-today-sheet.week-conflict-mode .mobile-plan-today-nav, +.plan-today-sheet.week-conflict-mode #plan-today-fit, +.plan-today-sheet.week-conflict-mode #plan-today-selected, +.plan-today-sheet.week-conflict-mode #plan-today-available-work, +.plan-today-sheet.week-conflict-mode .plan-today-actions { display:none; } +@media (max-width:480px) { + .week-conflict-plans { grid-template-columns:1fr; } + .week-conflict-actions { flex-direction:column; } +} .mobile-plan-today-nav { position:sticky; top:0; z-index:5; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:4px; margin:0 -18px 10px; padding:4px 18px; background:rgba(11,21,38,.98); border-block:1px solid #2a496e; } .mobile-plan-today-nav button { min-width:0; min-height:44px; padding:4px; border-color:transparent; font-size:12px; } .mobile-plan-today-nav button[aria-current="location"] { color:#bfdbfe; background:#17365a; border-color:#31577f; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 2189cfb..a409577 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -391,6 +391,7 @@ }); const weekPlan = createWeekPlan({ fetchJson:fetchReviewJson,localDate:todayRollover.localDate,timeZone:todayRollover.timeZone, + storage:localStorage,getLogin:() => planningOwnerLogin, }); function openWeekPlanner(trigger) { planningTomorrow=false; return weekWorkflow.open(trigger); } function renderTomorrowQueueSummary(value) { @@ -511,6 +512,7 @@ documentObject:document, check:() => Promise.all([ syncPendingTomorrow(), + weekPlan.pending() ? weekPlan.flush().catch(() => false) : false, latestTodayPlan ? weekWorkflow.promote(latestTodayPlan) : false, ]), }); @@ -2700,6 +2702,8 @@ weekWorkflow.clear(); qs('#tomorrow-conflict-review').hidden = true; qs('#plan-today-sheet').classList.remove('tomorrow-conflict-mode'); + qs('#week-conflict-review').hidden = true; + qs('#plan-today-sheet').classList.remove('week-conflict-mode'); qs('#plan-today-sheet').hidden = true; document.body.classList.remove('task-overlay-open'); if (planTodayTrigger?.dataset.mobileQueue === 'tomorrow') { @@ -3059,6 +3063,32 @@ requestAnimationFrame(() => keep.focus()); } + function weekConflictMarkup(value) { + const days=value?.days||[]; + if(!days.length)return '

Nothing planned.

'; + return days.map(day=>{ + const estimates=day.estimates||{}; + const minutes=(day.ids||[]).reduce((total,id)=>total+(Number(estimates[id])||0),0); + const capacity=Number(day.capacity_minutes)||0; + const detail=(day.ids||[]).length+' item'+((day.ids||[]).length===1?'':'s')+ + (minutes&&capacity?' · '+minutes+' of '+capacity+' min':''); + return '

'+escapeHtml(day.plan_date)+''+escapeHtml(detail)+'

'; + }).join(''); + } + + function showWeekConflict(conflict) { + qs('#plan-today-title').textContent='Resolve Week Ahead conflict'; + qs('#week-conflict-phone-plan').innerHTML=weekConflictMarkup(conflict.local); + qs('#week-conflict-server-plan').innerHTML=weekConflictMarkup(conflict.remote); + qs('#week-conflict-status').textContent=''; + qs('#week-conflict-review').hidden=false; + qs('#plan-today-sheet').classList.add('week-conflict-mode'); + qs('#plan-today-sheet').hidden=false; + document.body.classList.add('task-overlay-open'); + const keep=qs('#keep-phone-week'); + keep.focus();requestAnimationFrame(()=>keep.focus()); + } + function openPlanToday(trigger, navigate = true, actualMinutes = null) { if (!planningOwnerLogin && !weekWorkflow.active()) { qs('#my-work-action-status').textContent = 'Planning is unavailable until your operator identity is restored.'; @@ -3078,8 +3108,12 @@ showTomorrowConflict(conflict); return; } + const weekConflict=weekWorkflow.active()?weekPlan.conflict():null; + if(weekConflict){showWeekConflict(weekConflict);return;} qs('#tomorrow-conflict-review').hidden = true; qs('#plan-today-sheet').classList.remove('tomorrow-conflict-mode'); + qs('#week-conflict-review').hidden=true; + qs('#plan-today-sheet').classList.remove('week-conflict-mode'); const recommendations = actualMinutes || pendingPlanActualMinutes || todayRecapView.pendingReplan()?.actual_minutes; pendingPlanActualMinutes = null; const protectProposal = pendingProtectToday; @@ -7832,6 +7866,30 @@ qs('#my-work-action-status').textContent = 'Saved account Tomorrow plan selected. Today was not changed.'; closePlanToday(); }); + qs('#keep-phone-week').addEventListener('click', async () => { + const keep=qs('#keep-phone-week'),use=qs('#use-server-week'); + keep.disabled=true;use.disabled=true; + qs('#week-conflict-status').textContent='Saving this phone’s complete week…'; + 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.'; + closePlanToday(); + } catch(error) { + const conflict=weekPlan.conflict(); + if(conflict)showWeekConflict(conflict); + qs('#week-conflict-status').textContent=error?.status===409? + 'Week Ahead changed again. Both latest weeks are preserved; choose again.': + `${error.message||'Week Ahead could not be saved.'} Both weeks are still preserved.`; + } finally {keep.disabled=false;use.disabled=false;} + }); + qs('#use-server-week').addEventListener('click', () => { + const adopted=weekPlan.useRemote(); + if(!adopted)return; + qs('#mobile-week-summary').textContent=weekPlan.summary(); + qs('#my-work-action-status').textContent='Saved account Week Ahead plan selected. Today was not changed.'; + closePlanToday(); + }); qs('#cancel-plan-today').addEventListener('click', closePlanToday); qs('#plan-today-sheet').addEventListener('click', event => { if (event.target === qs('#plan-today-sheet')) closePlanToday(); diff --git a/frontend/index.html b/frontend/index.html index c782d61..4aa15d7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -481,6 +481,26 @@
+