No time logged for this day.
';
return;
}
entriesForDay.forEach(entry => {
const itemEl = document.createElement('div');
itemEl.className = 'rtwl-entry-item';
const project = entry.projectId ? rtwlProjects.find(p => p.id === entry.projectId) : null;
const projectTagHTML = project ? `
${project.name} ` : '';
itemEl.innerHTML = `
${projectTagHTML}${entry.taskDescription}
${rtwlFormatTimeForDisplay(entry.startTimeISO)} - ${rtwlFormatTimeForDisplay(entry.endTimeISO)}
(Duration: ${rtwlFormatDuration(entry.durationMinutes)})
${entry.notes ? `
${entry.notes.replace(/\n/g, ' ')}
` : ''}
Edit
Delete
`;
rtwlDailyLogEntriesListEl.appendChild(itemEl);
});
}
function rtwlUpdateQuickStartButtons() {
if(!rtwlQuickStartButtonsEl) return;
rtwlQuickStartButtonsEl.innerHTML = '';
const recentTaskMap = new Map();
let count = 0;
for (let i = 0; i < rtwlTimeEntries.length && count < 4; i++) {
const entry = rtwlTimeEntries[i];
const key = entry.projectId ? entry.projectId : `adhoc-${entry.taskDescription}`; // Use task desc for adhoc key
if (!recentTaskMap.has(key)) {
let displayName = entry.taskDescription;
if(entry.projectId){
const proj = rtwlProjects.find(p=>p.id===entry.projectId);
if(proj) displayName = proj.name; // Prefer project name if available
}
recentTaskMap.set(key, {
projectId: entry.projectId,
taskDescription: entry.taskDescription, // Store original adhoc desc if needed
display: displayName
});
count++;
}
}
if (recentTaskMap.size === 0) {
rtwlQuickStartButtonsEl.innerHTML = '
Log time for projects to see them here for quick starting.
';
return;
}
recentTaskMap.forEach((taskInfo) => {
const btn = document.createElement('button');
btn.className = 'rtwl-button rtwl-button-secondary';
btn.textContent = `Start: ${taskInfo.display.substring(0,25)}${taskInfo.display.length > 25 ? '...' : '' }`;
btn.title = `Start timer for ${taskInfo.display}`;
btn.onclick = () => {
if (taskInfo.projectId) {
rtwlTaskSelectTimerEl.value = taskInfo.projectId;
if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = 'none';
} else {
rtwlTaskSelectTimerEl.value = 'adhoc';
if(rtwlAdhocTaskNameTimerEl) rtwlAdhocTaskNameTimerEl.value = taskInfo.taskDescription; // Use original taskDescription for adhoc
if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = 'block';
}
rtwlStartNewTimer();
};
rtwlQuickStartButtonsEl.appendChild(btn);
});
}
// --- Reports ---
window.rtwlGenerateReport = function() { /* ... same as stl, with rtwl prefixes ... */
if (!rtwlReportOutputAreaEl || !rtwlReportPeriodSelectEl || !rtwlReportPeriodDisplayEl || !rtwlReportSummaryEl || !rtwlReportDetailsTableBodyEl) return;
const period = rtwlReportPeriodSelectEl.value;
let startDateObj, endDateObj; // Use actual Date objects for filtering
const todayForReport = new Date(2025, 4, 14);
todayForReport.setHours(0,0,0,0);
let periodDisplayString = "";
switch(period) {
case 'today':
startDateObj = new Date(todayForReport);
endDateObj = new Date(todayForReport);
endDateObj.setHours(23,59,59,999);
periodDisplayString = "Today (" + todayForReport.toLocaleDateString() + ")";
break;
case 'this_week':
startDateObj = new Date(todayForReport);
const dayOfWeek = startDateObj.getDay();
const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
startDateObj.setDate(startDateObj.getDate() + diffToMonday);
endDateObj = new Date(startDateObj);
endDateObj.setDate(startDateObj.getDate() + 6);
endDateObj.setHours(23,59,59,999);
periodDisplayString = `This Week (${startDateObj.toLocaleDateString()} - ${endDateObj.toLocaleDateString()})`;
break;
case 'this_month':
startDateObj = new Date(todayForReport.getFullYear(), todayForReport.getMonth(), 1);
endDateObj = new Date(todayForReport.getFullYear(), todayForReport.getMonth() + 1, 0);
endDateObj.setHours(23,59,59,999);
periodDisplayString = todayForReport.toLocaleDateString(undefined, {month: 'long', year: 'numeric'});
break;
case 'custom_range':
if (!rtwlReportStartDateInputEl.value || !rtwlReportEndDateInputEl.value) {
alert("Please select a start and end date for custom range."); return;
}
startDateObj = new Date(rtwlReportStartDateInputEl.value + "T00:00:00");
endDateObj = new Date(rtwlReportEndDateInputEl.value + "T23:59:59");
if (startDateObj > endDateObj) { alert("End date must be after start date."); return; }
periodDisplayString = `Custom (${startDateObj.toLocaleDateString()} - ${endDateObj.toLocaleDateString()})`;
break;
}
if(rtwlReportPeriodDisplayEl) rtwlReportPeriodDisplayEl.textContent = periodDisplayString;
const entriesInPeriod = rtwlTimeEntries.filter(entry => {
const entryStart = new Date(entry.startTimeISO);
return entryStart >= startDateObj && entryStart <= endDateObj;
});
let totalLoggedMinutes = 0;
const projectTime = {};
if(rtwlReportDetailsTableBodyEl) rtwlReportDetailsTableBodyEl.innerHTML = '';
entriesInPeriod.sort((a,b) => new Date(a.startTimeISO) - new Date(b.startTimeISO)).forEach(entry => {
totalLoggedMinutes += entry.durationMinutes;
const project = entry.projectId ? rtwlProjects.find(p => p.id === entry.projectId) : null;
// For summary, group by project name if available, otherwise use taskDescription as key
const summaryKey = project ? project.name : (entry.taskDescription || "Ad-hoc");
projectTime[summaryKey] = (projectTime[summaryKey] || 0) + entry.durationMinutes;
if(rtwlReportDetailsTableBodyEl){
const row = rtwlReportDetailsTableBodyEl.insertRow();
row.insertCell().textContent = new Date(entry.startTimeISO).toLocaleDateString();
row.insertCell().textContent = project ? project.name : "Ad-hoc";
row.insertCell().textContent = entry.taskDescription;
row.insertCell().textContent = rtwlFormatTimeForDisplay(entry.startTimeISO);
row.insertCell().textContent = rtwlFormatTimeForDisplay(entry.endTimeISO);
row.insertCell().textContent = rtwlFormatDuration(entry.durationMinutes);
row.insertCell().textContent = entry.notes || "";
}
});
let summaryHTML = `
Total Time Logged: ${rtwlFormatDuration(totalLoggedMinutes)}
Time by Project/Task: `;
Object.keys(projectTime).sort((a,b) => projectTime[b] - projectTime[a]).forEach(projName => {
summaryHTML += `${projName}: ${rtwlFormatDuration(projectTime[projName])} `;
});
summaryHTML += ` `;
if(rtwlReportSummaryEl) rtwlReportSummaryEl.innerHTML = summaryHTML;
if(rtwlReportOutputAreaEl) rtwlReportOutputAreaEl.style.display = 'block';
if(rtwlDownloadPdfBtn) rtwlDownloadPdfBtn.style.display = entriesInPeriod.length > 0 ? 'block' : 'none';
}
function handlePdfDownload() { /* ... same as stlHandlePdfDownload, with rtwl prefix ... */
const periodText = rtwlReportPeriodDisplayEl.textContent;
const totalTimeText = rtwlReportSummaryEl.querySelector('p strong')?.parentElement.textContent || "Total Time Logged: N/A";
const projectSummaryData = [];
const projectLis = rtwlReportSummaryEl.querySelectorAll('ul li');
projectLis.forEach(li => projectSummaryData.push(li.textContent));
const detailTableRowsData = [];
const detailTableHtmlRows = rtwlReportDetailsTableBodyEl.querySelectorAll('tr');
detailTableHtmlRows.forEach(row => {
const cells = Array.from(row.cells).map(cell => cell.textContent);
detailTableRowsData.push(cells);
});
if(detailTableRowsData.length === 0 && totalTimeText.includes("N/A") && projectSummaryData.length === 0) {
alert("No data to export for the selected report period."); return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(18);
doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--rtwl-primary-color').trim());
doc.text("Real-Time Work Log Report", 14, 22);
doc.setFontSize(12);
doc.setTextColor(100);
doc.text(`Period: ${periodText}`, 14, 30);
doc.text(totalTimeText, 14, 38);
let yPos = 46;
if (projectSummaryData.length > 0) {
doc.setFontSize(11);
doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--rtwl-text-color').trim());
doc.text("Summary by Project/Task:", 14, yPos);
yPos += 7;
doc.setFontSize(9);
projectSummaryData.forEach(item => {
if (yPos > 270) { doc.addPage(); yPos = 20; }
doc.text(`\u2022 ${item}`, 16, yPos);
yPos += 5;
});
yPos += 5;
}
if (detailTableRowsData.length > 0) {
if (yPos > 250) { doc.addPage(); yPos = 20; }
doc.setFontSize(11);
doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--rtwl-text-color').trim());
doc.text("Detailed Log:", 14, yPos);
yPos += 7;
doc.autoTable({
head: [['Date', 'Project', 'Task Description', 'Start', 'End', 'Duration', 'Notes']],
body: detailTableRowsData,
startY: yPos,
theme: 'striped',
headStyles: { fillColor: getComputedStyle(document.documentElement).getPropertyValue('--rtwl-primary-color').trim() },
styles: { fontSize: 8, cellPadding: 1.5, overflow: 'linebreak' },
columnStyles: {
1: { cellWidth: 30 },
2: { cellWidth: 50 },
6: { cellWidth: 'auto'}
}
});
}
doc.save(`TimeLogReport_${periodText.replace(/[^a-zA-Z0-9]/g, '_')}.pdf`);
}
window.rtwlCloseModal = (modalId) => { /* ... */
const modal = document.getElementById(modalId);
if(modal) modal.style.display = 'none';
// Reset timer-related modal fields if active timer was cleared during modal interaction.
if (!rtwlActiveTimer && modalId === 'rtwlTimeEntryModal' && rtwlTimeEntryModalTitleEl.textContent.includes("Confirm & Log Timed Entry")) {
if(rtwlTaskSelectTimerEl) rtwlTaskSelectTimerEl.value = "";
if(rtwlAdhocTaskNameTimerEl) rtwlAdhocTaskNameTimerEl.value = "";
if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = "none";
}
}
// --- DOMContentLoaded ---
document.addEventListener('DOMContentLoaded', () => {
// Assign DOM elements
rtwlTaskSelectTimerEl = document.getElementById('rtwlTaskSelectTimer');
rtwlAdhocTaskNameGroupEl = document.getElementById('rtwlAdhocTaskNameGroup');
rtwlAdhocTaskNameTimerEl = document.getElementById('rtwlAdhocTaskNameTimer');
rtwlStartNewTimerButtonEl = document.getElementById('rtwlStartNewTimerButton');
rtwlActiveTimerAreaEl = document.getElementById('rtwlActiveTimerArea');
rtwlActiveTaskNameDisplayEl = document.getElementById('rtwlActiveTaskNameDisplay');
rtwlActiveTimerDisplayEl = document.getElementById('rtwlActiveTimerDisplay');
rtwlPauseResumeTimerButtonEl = document.getElementById('rtwlPauseResumeTimerButton');
rtwlStopTimerButtonEl = document.getElementById('rtwlStopTimerButton');
rtwlDiscardTimerButtonEl = document.getElementById('rtwlDiscardTimerButton');
rtwlCurrentDateDisplayEl = document.getElementById('rtwlCurrentDateDisplay');
rtwlLogViewDateEl = document.getElementById('rtwlLogViewDate');
rtwlDailyLogEntriesListEl = document.getElementById('rtwlDailyLogEntriesList');
rtwlQuickStartButtonsEl = document.getElementById('rtwlQuickStartButtons');
rtwlAddProjectForm = document.getElementById('rtwlAddProjectForm');
rtwlProjectNameInputEl = document.getElementById('rtwlProjectNameInput');
rtwlProjectColorInputEl = document.getElementById('rtwlProjectColorInput');
rtwlProjectsListEl = document.getElementById('rtwlProjectsList');
rtwlReportPeriodSelectEl = document.getElementById('rtwlReportPeriodSelect');
rtwlCustomDateRangePickerEl = document.getElementById('rtwlCustomDateRangePicker');
rtwlReportStartDateInputEl = document.getElementById('rtwlReportStartDate');
rtwlReportEndDateInputEl = document.getElementById('rtwlReportEndDate');
rtwlReportOutputAreaEl = document.getElementById('rtwlReportOutputArea');
rtwlReportPeriodDisplayEl = document.getElementById('rtwlReportPeriodDisplay');
rtwlReportSummaryEl = document.getElementById('rtwlReportSummary');
rtwlReportDetailsTableBodyEl = document.querySelector('#rtwlReportDetailsTable tbody'); // Corrected selector
rtwlTimeEntryModalEl = document.getElementById('rtwlTimeEntryModal');
rtwlTimeEntryModalTitleEl = document.getElementById('rtwlTimeEntryModalTitle');
rtwlTimeEntryForm = document.getElementById('rtwlTimeEntryForm');
rtwlEntryIdInput = document.getElementById('rtwlEntryId');
rtwlEntryTaskDescriptionInput = document.getElementById('rtwlEntryTaskDescription');
rtwlEntryProjectIdSelectEl = document.getElementById('rtwlEntryProjectId');
rtwlEntryStartDateInput = document.getElementById('rtwlEntryStartDate');
rtwlEntryStartTimeInput = document.getElementById('rtwlEntryStartTime');
rtwlEntryEndDateInput = document.getElementById('rtwlEntryEndDate');
rtwlEntryEndTimeInput = document.getElementById('rtwlEntryEndTime');
rtwlEntryDurationMinutesInput = document.getElementById('rtwlEntryDurationMinutes');
rtwlEntryNotesInput = document.getElementById('rtwlEntryNotes');
rtwlDownloadPdfBtn = document.getElementById('rtwlDownloadPdfButton');
// Check if all critical elements are found
const criticalElements = [rtwlTaskSelectTimerEl, rtwlAddProjectForm, rtwlTimeEntryForm, rtwlLogViewDateEl, rtwlReportPeriodSelectEl];
if (criticalElements.some(el => !el)) {
console.error("RealTimeWorkHourLoggingTool: Not all critical DOM elements were found. Tool may not function correctly.");
alert("Error initializing the Time Logger: Some UI elements are missing. Please ensure the HTML code is complete.");
return; // Stop further initialization if critical parts are missing
}
// Attach event listeners
if(rtwlAddProjectForm) rtwlAddProjectForm.addEventListener('submit', handleAddProjectFormSubmit);
if(rtwlTimeEntryForm) rtwlTimeEntryForm.addEventListener('submit', handleTimeEntryFormSubmit);
if(rtwlTaskSelectTimerEl) rtwlTaskSelectTimerEl.addEventListener('change', () => {
if(rtwlAdhocTaskNameGroupEl) rtwlAdhocTaskNameGroupEl.style.display = rtwlTaskSelectTimerEl.value === 'adhoc' ? 'block' : 'none';
});
if(rtwlStartNewTimerButtonEl) rtwlStartNewTimerButtonEl.addEventListener('click', rtwlStartNewTimer);
if(rtwlPauseResumeTimerButtonEl) rtwlPauseResumeTimerButtonEl.addEventListener('click', rtwlTogglePauseResumeTimer);
if(rtwlStopTimerButtonEl) rtwlStopTimerButtonEl.addEventListener('click', rtwlStopAndLogTimer);
if(rtwlDiscardTimerButtonEl) rtwlDiscardTimerButtonEl.addEventListener('click', rtwlDiscardActiveTimer);
if(rtwlLogViewDateEl) rtwlLogViewDateEl.addEventListener('change', rtwlRenderDailyLogEntries);
if(rtwlReportPeriodSelectEl) rtwlReportPeriodSelectEl.addEventListener('change', () => {
if(rtwlCustomDateRangePickerEl) rtwlCustomDateRangePickerEl.style.display = rtwlReportPeriodSelectEl.value === 'custom_range' ? 'grid' : 'none';
});
if(rtwlDownloadPdfBtn) rtwlDownloadPdfBtn.addEventListener('click', handlePdfDownload);
[rtwlEntryStartDateInput, rtwlEntryStartTimeInput, rtwlEntryEndDateInput, rtwlEntryEndTimeInput].forEach(el => {
if(el) el.addEventListener('change', rtwlCalculateDurationManual);
});
// Removed rtwlAdjustEndTimeManual listener from duration input as it's now read-only in modal
const today = new Date(2025, 4, 14); // May 14, 2025
if(rtwlLogViewDateEl) rtwlLogViewDateEl.value = rtwlFormatDateKey(today);
if(rtwlCurrentDateDisplayEl) rtwlCurrentDateDisplayEl.textContent = today.toLocaleDateString();
rtwlLoadData();
const firstTabButton = document.querySelector('#realTimeWorkHourLoggingTool .rtwl-tab-button');
if (firstTabButton) firstTabButton.click();
window.addEventListener('click', function(event) {
if (event.target.classList.contains('rtwl-modal')) {
rtwlCloseModal(event.target.id);
}
});
});