Microsoft To-Do Weekly Planner

To-Do widget that generates report of "Planned" tasks sorted by date, then by list, then by title

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name          Microsoft To-Do Weekly Planner
// @namespace     http://tampermonkey.net/
// @version       2026-01-16.20
// @description   To-Do widget that generates report of "Planned" tasks sorted by date, then by list, then by title
// @author        Donnie Hires
// @match         *://to-do.office.com/*
// @license       MIT
// @grant         none
// ==/UserScript==

(function() {
    'use strict';

    // Inject CSS for readability and specialized print formatting
    const style = document.createElement('style');
    style.innerHTML = `
        #roadmap-day-input::placeholder {
            color: #bbbbbb !important;
            opacity: 1 !important;
        }
        #roadmap-day-input::-webkit-input-placeholder { color: #bbbbbb !important; }
        #roadmap-day-input::-moz-placeholder { color: #bbbbbb !important; opacity: 1 !important; }

        @media print {
            body > *:not(#course-roadmap-overlay) {
                display: none !important;
            }
            #course-roadmap-overlay {
                position: absolute !important;
                top: 0 !important;
                left: 0 !important;
                width: 100% !important;
                height: auto !important;
                border: none !important;
                box-shadow: none !important;
                padding: 0 !important;
                margin: 0 !important;
                overflow: visible !important;
            }
            #close-report-btn, #print-report-btn {
                display: none !important;
            }
            h2 {
                -webkit-print-color-adjust: exact !important;
                print-color-adjust: exact !important;
            }
        }
    `;
    document.head.appendChild(style);

    function createButton() {
        const isPlannedTab = window.location.href.toLowerCase().includes('planned');
        let container = document.getElementById('roadmap-container');

        if (!isPlannedTab) {
            if (container) container.style.display = 'none';
            return;
        }

        if (container) {
            container.style.display = 'flex';
            return;
        }

        container = document.createElement('div');
        container.id = 'roadmap-container';
        container.style = `
            position: fixed; bottom: 20px; left: 20px; z-index: 9999999;
            display: flex; flex-direction: column; gap: 10px; background: #ffffff;
            padding: 16px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.2);
            border: 2px solid #0078d4; width: 200px; font-family: 'Segoe UI', sans-serif;
            cursor: move; user-select: none; align-items: center;
        `;

        const title = document.createElement('div');
        title.style = "font-size: 14px; font-weight: 800; color: #0078d4; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 5px;";
        title.innerText = "Planning Widget";

        const dayInput = document.createElement('input');
        dayInput.type = 'text';
        dayInput.placeholder = '# of days';
        dayInput.id = 'roadmap-day-input';
        dayInput.style = `
            width: 100%; height: 40px; border: 2px solid #0078d4; border-radius: 6px;
            text-align: center; font-size: 14px; font-weight: bold; outline: none;
            cursor: text; box-sizing: border-box; background: white; color: black;
        `;
        dayInput.onmousedown = (e) => e.stopPropagation();

        const planBtn = document.createElement('button');
        planBtn.style = `
            width: 100%; height: 45px; background: #0078d4; color: white; border: none;
            border-radius: 6px; font-weight: 700; cursor: pointer; font-size: 14px;
            display: flex; align-items: center; justify-content: center; gap: 8px;
        `;
        planBtn.innerHTML = 'Plan My Week 📅';

        planBtn.onclick = () => {
            const inputVal = document.getElementById('roadmap-day-input').value;
            const parsedDays = parseInt(inputVal);
            const now = new Date();
            now.setHours(0,0,0,0);

            if (isNaN(parsedDays)) {
                // FALLBACK: Until Saturday
                const dayOfWeek = now.getDay();
                let diff = 6 - dayOfWeek;
                if (diff < 0) diff = 6;

                // Calculate the Sunday before that Saturday
                const satDate = new Date(now);
                satDate.setDate(now.getDate() + diff);
                const sunDate = new Date(satDate);
                sunDate.setDate(satDate.getDate() - 6);

                const titleStr = `Week of ${sunDate.toLocaleDateString()} Report`;
                generateReport(diff, titleStr);
            } else {
                // CUSTOM: Range from today to last day
                const endDate = new Date(now);
                endDate.setDate(now.getDate() + parsedDays);
                const titleStr = `Custom Report: ${now.toLocaleDateString()} - ${endDate.toLocaleDateString()}`;
                generateReport(parsedDays, titleStr);
            }
        };

        container.appendChild(title);
        container.appendChild(dayInput);
        container.appendChild(planBtn);
        document.body.appendChild(container);

        let active = false, initialX, initialY;
        container.onmousedown = (e) => {
            if (e.target === container || e.target === title) {
                active = true;
                initialX = e.clientX - container.offsetLeft;
                initialY = e.clientY - container.offsetTop;
            }
        };
        document.onmousemove = (e) => {
            if (active) {
                e.preventDefault();
                container.style.bottom = 'auto';
                container.style.left = (e.clientX - initialX) + "px";
                container.style.top = (e.clientY - initialY) + "px";
            }
        };
        document.onmouseup = () => { active = false; };
    }

    function parseDate(dateStr) {
        const now = new Date();
        now.setHours(0, 0, 0, 0);
        const lower = dateStr.toLowerCase();
        if (lower.includes('today')) return new Date(now);
        if (lower.includes('tomorrow')) {
            const d = new Date(now); d.setDate(now.getDate() + 1); return d;
        }
        const clean = dateStr.replace(/^later,\s+/i, '');
        const parsed = Date.parse(clean + ", " + now.getFullYear());
        if (isNaN(parsed)) return null;
        const d = new Date(parsed); d.setHours(0, 0, 0, 0); return d;
    }

    function generateReport(limitDays, titleText) {
        const tasks = document.querySelectorAll('.taskItem');
        let reportData = {};
        const now = new Date();
        now.setHours(0, 0, 0, 0);
        const endDate = new Date(now);
        endDate.setDate(now.getDate() + limitDays);
        endDate.setHours(23, 59, 59, 999);

        tasks.forEach(task => {
            const title = task.querySelector('.taskItem-title')?.innerText || "Unknown Task";
            const listEl = task.querySelector('.taskItemInfo-title');
            let category = listEl ? listEl.innerText.replace(/^in\s+/i, '').replace(/^\(GT\)\s+/i, '').trim() : "General";
            const dateEl = task.querySelector('.taskItemInfo-date');
            let dateStr = dateEl ? dateEl.innerText.trim() : "";

            if (dateStr) {
                const dateObj = parseDate(dateStr);
                if (dateObj && dateObj >= now && dateObj <= endDate) {
                    const fullDateKey = dateObj.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
                    if (!reportData[fullDateKey]) reportData[fullDateKey] = [];
                    reportData[fullDateKey].push({ title, category });
                }
            }
        });
        renderOverlay(reportData, titleText);
    }

    function renderOverlay(data, titleText) {
        const existing = document.getElementById('course-roadmap-overlay');
        if (existing) existing.remove();
        const overlay = document.createElement('div');
        overlay.id = 'course-roadmap-overlay';
        overlay.style = "position:fixed; top:30px; left:30px; right:30px; bottom:30px; background:white; z-index:99999999; padding:40px; border-radius:15px; border: 3px solid #0078d4; overflow-y:auto; color: black; font-family: 'Segoe UI', sans-serif; box-shadow: 0 15px 50px rgba(0,0,0,0.5);";

        const sortedDates = Object.keys(data).sort((a, b) => new Date(a) - new Date(b));
        let content = `
            <div style="float:right; display:flex; gap:12px;">
                <button id="print-report-btn" style="height:40px; width:120px; background:#0078d4; color:white; border:none; border-radius:6px; cursor:pointer; font-weight:bold; font-size: 14px; display: flex; align-items: center; justify-content: center; gap: 8px;">Print 🖨️</button>
                <button id="close-report-btn" style="height:40px; width:120px; background:#f3f2f1; border:1px solid #ccc; border-radius:6px; cursor:pointer; font-weight:bold; font-size: 14px; display: flex; align-items: center; justify-content: center; gap: 8px;">Close ❌</button>
            </div>
            <h1 style="color:#0078d4; margin-top:0; font-size: 28px; border-bottom: 2px solid #eee; padding-bottom: 10px;">${titleText}</h1>`;

        sortedDates.forEach(dateKey => {
            content += `<div style="margin-top:30px;">
                        <h2 style="background:#0078d4; color:white; padding:12px; border-radius:6px; font-size:1.3em;">${dateKey}</h2>
                        <table style="width:100%; border-collapse: collapse; font-size: 16px;">`;

            const dayTasks = data[dateKey].sort((a, b) => {
                const catCompare = a.category.localeCompare(b.category);
                return catCompare !== 0 ? catCompare : a.title.localeCompare(b.title);
            });

            dayTasks.forEach(t => {
                content += `<tr style="border-bottom: 1px solid #eee;">
                            <td style="padding:12px; width:30px;"><input type="checkbox" style="width: 18px; height: 18px;"></td>
                            <td style="padding:12px; font-weight:bold; color:#0078d4; width:150px;">[${t.category}]</td>
                            <td style="padding:12px;">${t.title}</td>
                            </tr>`;
            });
            content += `</table></div>`;
        });

        overlay.innerHTML = content;
        document.body.appendChild(overlay);
        document.getElementById('close-report-btn').onclick = () => overlay.remove();
        document.getElementById('print-report-btn').onclick = () => window.print();
    }

    setInterval(createButton, 2000);
})();