123 lines
4.6 KiB
JavaScript
123 lines
4.6 KiB
JavaScript
function createTodayWrapUp({ todayWork, laterWork, todaySync }) {
|
||
let items = [];
|
||
const selected = new Set();
|
||
|
||
function open(candidates) {
|
||
const current = new Set(todayWork.read());
|
||
const byIdentity = new Map((candidates || []).map(item => [todayWork.identity(item), item]));
|
||
items = todayWork.read().flatMap(identity =>
|
||
current.has(identity) && byIdentity.has(identity) ? [byIdentity.get(identity)] : []
|
||
);
|
||
selected.clear();
|
||
return snapshot();
|
||
}
|
||
|
||
function choose(identity, schedule) {
|
||
if (!items.some(item => todayWork.identity(item) === identity)) return false;
|
||
if (schedule) selected.add(identity);
|
||
else selected.delete(identity);
|
||
return true;
|
||
}
|
||
|
||
function snapshot() {
|
||
return items.map(item => ({
|
||
item,
|
||
identity:todayWork.identity(item),
|
||
schedule:selected.has(todayWork.identity(item)),
|
||
}));
|
||
}
|
||
|
||
async function finish() {
|
||
const wake = laterWork.presetUntil('tomorrow');
|
||
let scheduled = 0;
|
||
for (const item of items) {
|
||
const identity = todayWork.identity(item);
|
||
if (!selected.has(identity) || !todayWork.contains(item)) continue;
|
||
const deferred = laterWork.defer(item, wake);
|
||
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.');
|
||
}
|
||
if (todayWork.contains(item) && !todayWork.remove(item)) {
|
||
throw new Error('Today could not be updated. Retry wrap-up.');
|
||
}
|
||
scheduled += 1;
|
||
}
|
||
await todaySync.flush();
|
||
return { scheduled, left:items.length - scheduled, wake_at:wake.toISOString() };
|
||
}
|
||
|
||
return { open, choose, snapshot, finish };
|
||
}
|
||
|
||
function createTodayWrapUpView({ controller, qs, escapeHtml, onComplete = () => {}, onClose = () => {} }) {
|
||
let actualMinutes = {};
|
||
|
||
function close() {
|
||
qs('#today-wrap-up-sheet').hidden = true;
|
||
document.body.classList.remove('task-overlay-open');
|
||
onClose();
|
||
}
|
||
|
||
function render() {
|
||
const rows = controller.snapshot();
|
||
qs('#today-wrap-up-items').innerHTML = rows.map(row => {
|
||
const title = String(row.item.title || row.identity).slice(0, 180);
|
||
const context = String(row.item.key || row.item.repository || 'Work item').slice(0, 180);
|
||
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>';
|
||
}).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));
|
||
});
|
||
}
|
||
|
||
function open(items, recapActualMinutes = {}) {
|
||
actualMinutes = { ...recapActualMinutes };
|
||
controller.open(items);
|
||
qs('#today-wrap-up-status').textContent = '';
|
||
render();
|
||
qs('#today-wrap-up-sheet').hidden = false;
|
||
document.body.classList.add('task-overlay-open');
|
||
requestAnimationFrame(() => (qs('#today-wrap-up-items input') || qs('#finish-today-wrap-up')).focus());
|
||
}
|
||
|
||
async function finish(button) {
|
||
button.disabled = true;
|
||
qs('#today-wrap-up-status').textContent = 'Saving tomorrow’s plan…';
|
||
try {
|
||
const result = await controller.finish();
|
||
qs('#my-work-action-status').textContent = result.scheduled + ' scheduled for tomorrow · ' + result.left + ' left in Today.';
|
||
close();
|
||
onComplete(result, actualMinutes);
|
||
} catch (error) {
|
||
qs('#today-wrap-up-status').textContent = error.message || 'Wrap-up could not be saved. Retry when ready.';
|
||
} finally {
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
function bind() {
|
||
qs('#close-today-wrap-up').addEventListener('click', close);
|
||
qs('#finish-today-wrap-up').addEventListener('click', event => finish(event.currentTarget));
|
||
}
|
||
|
||
return { open, close, finish, render, bind };
|
||
}
|
||
|
||
function setupTodayWrapUp(options) {
|
||
const controller = createTodayWrapUp(options);
|
||
const view = createTodayWrapUpView({ ...options, controller });
|
||
view.bind();
|
||
return view;
|
||
}
|
||
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
createTodayWrapUp.createView = createTodayWrapUpView;
|
||
createTodayWrapUp.setup = setupTodayWrapUp;
|
||
module.exports = createTodayWrapUp;
|
||
}
|