Timesheet Helper (SYEP)

Adds a draggable and minimizable UI to generate a schedule and slowly autofill the timesheet.

// ==UserScript==
// @name         Timesheet Helper (SYEP)
// @namespace    http://tampermonkey.net/
// @version      4.1
// @description  Adds a draggable and minimizable UI to generate a schedule and slowly autofill the timesheet.
// @author       AI Assistant
// @match        https://participant.yepsonline.org/Pages/TimeSheetDetail.aspx*
// @license      MIT
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // --- TWEAKABLE SETTINGS ---
    const config = {
        DAYS_TO_FILL: ['monday', 'tuesday', 'wednesday', 'thursday', 'sunday'],
        TOTAL_HOURS_GOAL: 25,
        MAX_DAILY_HOURS: 8,
        MIN_DAILY_HOURS: 4,
        MIN_START_HOUR: 8,
        MAX_START_HOUR: 10,
        MIN_LUNCH_START_HOUR: 12,
        MAX_LUNCH_START_HOUR: 14,
    };
    // --- END OF SETTINGS ---

    const dayMap = { 'sunday': 1, 'monday': 2, 'tuesday': 3, 'wednesday': 4, 'thursday': 5, 'friday': 6, 'saturday': 7 };

    // --- CORE LOGIC FUNCTIONS ---
    function generateDailyHours() {
        const numDays = config.DAYS_TO_FILL.length;
        if (numDays === 0) { console.error("Config Error: DAYS_TO_FILL is empty."); return null; }
        let hours = Array(numDays).fill(config.MIN_DAILY_HOURS);
        let remainingHours = config.TOTAL_HOURS_GOAL - (config.MIN_DAILY_HOURS * numDays);
        if (remainingHours < 0) { console.error("Config Error: Total minimum hours exceeds goal."); return null; }

        let attempts = 0;
        while (remainingHours > 0 && attempts < 2000) {
            let randomIndex = Math.floor(Math.random() * numDays);
            if (hours[randomIndex] < config.MAX_DAILY_HOURS) {
                hours[randomIndex] = parseFloat((hours[randomIndex] + 0.5).toFixed(2));
                remainingHours = parseFloat((remainingHours - 0.5).toFixed(2));
            }
            attempts++;
        }
        if (remainingHours > 0) {
            for (let i = 0; remainingHours > 0 && i < numDays; i++) {
                let addAmount = Math.min(config.MAX_DAILY_HOURS - hours[i], remainingHours);
                hours[i] += addAmount; remainingHours -= addAmount;
            }
        }
        return hours;
    }

    function getRandomTime(minHour, maxHour) {
        const hour = Math.floor(Math.random() * (maxHour - minHour + 1)) + minHour;
        const minutes = [0, 15, 30, 45];
        const minute = minutes[Math.floor(Math.random() * minutes.length)];
        const date = new Date();
        date.setHours(hour, minute, 0, 0);
        return date;
    }

    function formatTime(date) {
        if (!date) return '---';
        const hh = date.getHours(); const mm = ('0' + date.getMinutes()).slice(-2);
        const ampm = hh >= 12 ? 'PM' : 'AM';
        let displayHour = hh % 12; if (displayHour === 0) displayHour = 12;
        return `${displayHour}:${mm} ${ampm}`;
    }

    // --- UI & EVENT FUNCTIONS ---
    function generateAndDisplaySchedule() {
        const dailyHours = generateDailyHours();
        if (!dailyHours) return;
        const tableBody = document.getElementById('schedule-table-body');
        tableBody.innerHTML = '';

        dailyHours.forEach((hours, index) => {
            const dayName = config.DAYS_TO_FILL[index];
            const startTime = getRandomTime(config.MIN_START_HOUR, config.MAX_START_HOUR);
            let lunchMinutes = 0;
            if (hours > 7) lunchMinutes = 60; else if (hours > 5) lunchMinutes = 30;
            let lunchStartTime = lunchMinutes > 0 ? getRandomTime(config.MIN_LUNCH_START_HOUR, config.MAX_LUNCH_START_HOUR) : null;
            let lunchEndTime = lunchMinutes > 0 ? new Date(lunchStartTime.getTime() + lunchMinutes * 60000) : null;
            const totalDurationMinutes = (hours * 60) + lunchMinutes;
            const endTime = new Date(startTime.getTime() + totalDurationMinutes * 60000);

            const row = document.createElement('tr');
            row.innerHTML = `<td>${dayName.charAt(0).toUpperCase() + dayName.slice(1)}</td><td>${formatTime(startTime)}</td><td>${formatTime(lunchStartTime)}</td><td>${formatTime(lunchEndTime)}</td><td>${formatTime(endTime)}</td><td>${hours.toFixed(2)}</td>`;
            tableBody.appendChild(row);
        });
        document.getElementById('autofill-go-btn').disabled = false;
    }

    async function slowTypeValue(element, value) {
        element.focus(); element.value = '';
        for (const char of value) {
            element.value += char;
            element.dispatchEvent(new Event('input', { bubbles: true }));
            await new Promise(resolve => setTimeout(resolve, 50));
        }
        element.dispatchEvent(new Event('blur', { bubbles: true }));
    }

    async function startAutofill() {
        const autofillBtn = document.getElementById('autofill-go-btn');
        autofillBtn.disabled = true; autofillBtn.textContent = 'Filling...';

        const rows = document.querySelectorAll('#schedule-table-body tr');
        for (const row of rows) {
            const cells = row.getElementsByTagName('td');
            const dayName = cells[0].textContent.toLowerCase();
            const dayIndex = dayMap[dayName];
            const timeData = { TimeIn: cells[1].textContent, LunchOut: cells[2].textContent, LunchIn: cells[3].textContent, TimeOut: cells[4].textContent };

            for (const type in timeData) {
                const [timeValue, ampmValue] = timeData[type].split(' ');
                if (timeValue !== '---') {
                    const timeBox = document.getElementById(`ctl00_MainContent_txtTC${type}${dayIndex}_tcTimeBox`);
                    const ampmBox = document.getElementById(`ctl00_MainContent_txtTC${type}${dayIndex}_tcAMPMBox`);
                    if (timeBox && ampmBox) {
                        await slowTypeValue(timeBox, timeValue);
                        if (ampmValue) {
                           ampmBox.value = ampmValue;
                           ampmBox.dispatchEvent(new Event('change', { bubbles: true }));
                        }
                    }
                }
            }
        }
        autofillBtn.textContent = 'Autofill Complete!';
        setTimeout(() => { autofillBtn.textContent = 'Autofill from Table'; autofillBtn.disabled = false; }, 3000);
        if (typeof showHoursWorked === 'function') { showHoursWorked(); }
    }

    // --- UI CREATION & STYLING ---
    const container = document.createElement('div'); container.id = 'autofill-container';
    const header = document.createElement('div'); header.id = 'autofill-header';
    const title = document.createElement('span'); title.textContent = 'Timesheet Helper';
    const minimizeBtn = document.createElement('button'); minimizeBtn.id = 'minimize-btn'; minimizeBtn.innerHTML = '—';
    header.appendChild(title); header.appendChild(minimizeBtn);
    const content = document.createElement('div'); content.id = 'autofill-content';
    const buttonContainer = document.createElement('div'); buttonContainer.className = 'button-container';
    const generateBtn = document.createElement('button'); generateBtn.innerHTML = 'Generate Schedule'; generateBtn.id = 'generate-btn';
    const autofillBtn = document.createElement('button'); autofillBtn.innerHTML = 'Autofill from Table'; autofillBtn.id = 'autofill-go-btn'; autofillBtn.disabled = true;
    buttonContainer.appendChild(generateBtn); buttonContainer.appendChild(autofillBtn);
    const table = document.createElement('table'); table.id = 'schedule-table';
    table.innerHTML = `<thead><tr><th>Day</th><th>In</th><th>Lunch Out</th><th>Lunch In</th><th>Out</th><th>Hrs</th></tr></thead><tbody id="schedule-table-body"></tbody>`;
    content.appendChild(buttonContainer); content.appendChild(table);
    container.appendChild(header); container.appendChild(content);
    document.body.appendChild(container);

    // --- EVENT LISTENERS ---
    generateBtn.addEventListener('click', generateAndDisplaySchedule);
    autofillBtn.addEventListener('click', startAutofill);
    minimizeBtn.addEventListener('click', () => {
        const isMinimized = content.style.display === 'none';
        content.style.display = isMinimized ? 'block' : 'none';
        minimizeBtn.innerHTML = isMinimized ? '—' : '▢';
    });

    let isDragging = false, offsetX, offsetY;
    header.addEventListener('mousedown', (e) => {
        isDragging = true; offsetX = e.clientX - container.offsetLeft; offsetY = e.clientY - container.offsetTop;
        document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault();
    });
    function onMouseMove(e) { if (!isDragging) return; container.style.left = `${e.clientX - offsetX}px`; container.style.top = `${e.clientY - offsetY}px`; }
    function onMouseUp() { isDragging = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }

    // --- STYLES ---
    GM_addStyle(`
        #autofill-container { position: fixed; top: 100px; right: 20px; z-index: 9999; width: 550px; background: #282a36; color: #f8f8f2; border: 1px solid #44475a; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-family: Segoe UI, sans-serif; }
        #autofill-header { padding: 10px 15px; background: #44475a; cursor: move; border-top-left-radius: 11px; border-top-right-radius: 11px; display: flex; justify-content: space-between; align-items: center; }
        #autofill-header span { font-weight: bold; }
        #autofill-content { padding: 15px; border-top: 1px solid #44475a; }
        #minimize-btn { background: none; border: none; color: #f8f8f2; font-size: 20px; font-weight: bold; cursor: pointer; line-height: 1; padding: 0 5px; }
        .button-container { display: flex; gap: 10px; margin-bottom: 15px; }
        #generate-btn, #autofill-go-btn { flex: 1; padding: 10px; font-size: 14px; font-weight: bold; color: white; border: none; border-radius: 8px; cursor: pointer; transition: background-color 0.2s, transform 0.1s; }
        #generate-btn { background-color: #6272a4; } #generate-btn:hover { background-color: #7184c2; }
        #autofill-go-btn { background-color: #50fa7b; color: #282a36; } #autofill-go-btn:hover { background-color: #69ff91; }
        #autofill-go-btn:disabled { background-color: #555; cursor: not-allowed; opacity: 0.6; }
        #schedule-table { width: 100%; border-collapse: collapse; font-size: 13px; }
        #schedule-table th, #schedule-table td { border: 1px solid #44475a; padding: 6px; text-align: center; }
        #schedule-table th { background-color: #44475a; }
        #schedule-table td { background-color: #3b3d4d; }
    `);
})();