feat: carry wrap-up work into Tomorrow (Closes #1174)
This commit is contained in:
1 parent
58c2d3f5d8
commit
c99d03b9bc
5 files changed
+167
-40
No files matched your search
@@ -2044,11 +2044,13 @@
|
||||
enqueueDurably:message => authoredOutbox.enqueueDurably(message),
|
||||
});
|
||||
todaySummaryView.resume();
|
||||
const todayWrapUpView = setupTodayWrapUp({ todayWork, laterWork, todaySync, qs, escapeHtml,
|
||||
const todayWrapUpView = setupTodayWrapUp({ todayWork, tomorrowPlan, todaySync, qs, escapeHtml,
|
||||
onComplete:(_result, _actualMinutes, workedItems, tomorrowItems) => {
|
||||
todayRecapView['completeReplan']();
|
||||
refreshMyWorkView();
|
||||
warmTodayOffline();
|
||||
renderTomorrowQueueSummary();
|
||||
syncPendingTomorrow();
|
||||
todaySummaryView.open(workedItems, tomorrowItems);
|
||||
},
|
||||
});
|
||||
|
||||
+1
-1
@@ -567,7 +567,7 @@
|
||||
<div><div class="small">End your day intentionally</div><h2 id="today-wrap-up-title">Wrap up Today</h2></div>
|
||||
<button id="close-today-wrap-up" type="button">Not now</button>
|
||||
</div>
|
||||
<p class="small muted">Choose unfinished work to schedule for tomorrow at 09:00. Unchecked work stays in Today.</p>
|
||||
<p class="small muted">Carry selected work into the ordered Tomorrow plan. Unselected work stays in Today.</p>
|
||||
<div id="today-wrap-up-status" class="small" role="status" aria-live="polite"></div>
|
||||
<div id="today-wrap-up-items" class="today-wrap-up-items"></div>
|
||||
<div class="today-wrap-up-actions">
|
||||
|
||||
+42
-12
@@ -1,4 +1,4 @@
|
||||
function createTodayWrapUp({ todayWork, laterWork, todaySync }) {
|
||||
function createTodayWrapUp({ todayWork, tomorrowPlan, todaySync, limit = 5 }) {
|
||||
let items = [];
|
||||
const selected = new Set();
|
||||
|
||||
@@ -28,16 +28,39 @@ function createTodayWrapUp({ todayWork, laterWork, todaySync }) {
|
||||
}
|
||||
|
||||
async function finish() {
|
||||
const wake = laterWork.presetUntil('tomorrow');
|
||||
let scheduled = 0;
|
||||
for (const item of items) {
|
||||
const loaded = await tomorrowPlan.load();
|
||||
const currentIds = Array.isArray(loaded?.ids) ? [...loaded.ids] : [];
|
||||
const carried = items.filter(item => selected.has(todayWork.identity(item)) && todayWork.contains(item));
|
||||
if (!carried.length) {
|
||||
return {
|
||||
scheduled:0,
|
||||
left:items.filter(item => todayWork.contains(item)).length,
|
||||
plan_count:currentIds.length,
|
||||
plan_date:loaded?.plan_date,
|
||||
sync_pending:Boolean(loaded?.sync_pending),
|
||||
};
|
||||
}
|
||||
const combinedIds = [...currentIds];
|
||||
for (const item of carried) {
|
||||
const identity = todayWork.identity(item);
|
||||
if (!combinedIds.includes(identity)) combinedIds.push(identity);
|
||||
}
|
||||
if (combinedIds.length > limit) {
|
||||
throw new Error(`Tomorrow can hold ${limit} items. Keep ${combinedIds.length - limit} in Today or edit Tomorrow first.`);
|
||||
}
|
||||
const estimates = {...(loaded?.estimates || {})};
|
||||
const capacityMinutes = loaded?.capacity_minutes ?? null;
|
||||
const plannedMinutes = combinedIds.reduce((total, identity) => total + (Number(estimates[identity]) || 0), 0);
|
||||
if (capacityMinutes !== null && plannedMinutes > capacityMinutes) {
|
||||
throw new Error('Tomorrow is over capacity. Edit the plan before finishing wrap-up.');
|
||||
}
|
||||
const staged = tomorrowPlan.stage({ids:combinedIds, capacity_minutes:capacityMinutes, estimates});
|
||||
if (!staged) throw new Error('Tomorrow could not be saved on this device. Your Today plan is unchanged.');
|
||||
let scheduled = 0;
|
||||
for (const item of carried) {
|
||||
const identity = todayWork.identity(item);
|
||||
if (!selected.has(identity) || !todayWork.contains(item)) continue;
|
||||
const deferred = laterWork.defer(item, wake, {handoff:'today'});
|
||||
if (deferred !== 'deferred') throw new Error('Tomorrow could not be saved. Your Today plan is unchanged.');
|
||||
if (!todaySync.enqueue('remove', identity)) {
|
||||
laterWork.restore?.(item);
|
||||
throw new Error('Today sync could not be queued. Your Today plan is unchanged.');
|
||||
throw new Error('Today sync could not be queued. The item remains in Today.');
|
||||
}
|
||||
if (todayWork.contains(item) && !todayWork.remove(item)) {
|
||||
throw new Error('Today could not be updated. Retry wrap-up.');
|
||||
@@ -45,7 +68,13 @@ function createTodayWrapUp({ todayWork, laterWork, todaySync }) {
|
||||
scheduled += 1;
|
||||
}
|
||||
await todaySync.flush();
|
||||
return { scheduled, left:items.length - scheduled, wake_at:wake.toISOString() };
|
||||
return {
|
||||
scheduled,
|
||||
left:items.length - scheduled,
|
||||
plan_count:combinedIds.length,
|
||||
plan_date:staged.plan_date,
|
||||
sync_pending:Boolean(staged.sync_pending),
|
||||
};
|
||||
}
|
||||
|
||||
return { open, choose, snapshot, finish };
|
||||
@@ -69,7 +98,7 @@ function createTodayWrapUpView({ controller, qs, escapeHtml, onComplete = () =>
|
||||
return '<div class="today-wrap-up-item"><span><strong>' + escapeHtml(title) + '</strong>' +
|
||||
'<span class="small muted">' + escapeHtml(context) + '</span></span><label><input type="checkbox" ' +
|
||||
'data-wrap-up-identity="' + escapeHtml(row.identity) + '"' + (row.schedule ? ' checked' : '') +
|
||||
'> Tomorrow 09:00</label></div>';
|
||||
'> Carry to Tomorrow</label></div>';
|
||||
}).join('') || '<p class="small">Nothing unfinished remains in Today.</p>';
|
||||
qs('#today-wrap-up-items').querySelectorAll('[data-wrap-up-identity]').forEach(input => {
|
||||
input.addEventListener('change', () => controller.choose(input.dataset.wrapUpIdentity, input.checked));
|
||||
@@ -97,7 +126,8 @@ function createTodayWrapUpView({ controller, qs, escapeHtml, onComplete = () =>
|
||||
context:String(row.item.key || row.item.repository || 'Work item').slice(0, 180),
|
||||
}));
|
||||
const result = await controller.finish();
|
||||
qs('#my-work-action-status').textContent = result.scheduled + ' scheduled for tomorrow · ' + result.left + ' left in Today.';
|
||||
qs('#my-work-action-status').textContent = result.scheduled + ' carried to Tomorrow · ' + result.left +
|
||||
' left in Today' + (result.sync_pending ? ' · sync pending.' : '.');
|
||||
close();
|
||||
onComplete(result, actualMinutes, workedItems, tomorrowItems);
|
||||
} catch (error) {
|
||||
|
||||
Reference in new issue
Block a user