No projects defined. Add one above.
'; return;
}
rtwlProjects.forEach(proj => {
const itemEl = document.createElement('div');
itemEl.className = 'rtwl-entry-item';
itemEl.innerHTML = `
${proj.name}
×
`;
rtwlProjectsListEl.appendChild(itemEl);
});
}
window.rtwlDeleteProject = function(projId) {
if (confirm("Are you sure you want to delete this project? Existing time entries will retain its name but it won't be available for new selections.")) {
rtwlProjects = rtwlProjects.filter(p => p.id !== projId);
rtwlSaveProjects();
rtwlRenderProjectsList();
rtwlPopulateProjectSelects();
}
}
function rtwlPopulateProjectSelects() {
const selects = [rtwlTaskSelectTimerEl, rtwlEntryProjectIdSelectEl];
selects.forEach(selectEl => {
if(!selectEl) return;
const currentVal = selectEl.value;
let baseOptions = '
-- Select a Project -- ';
if (selectEl === rtwlTaskSelectTimerEl) {
baseOptions = '
-- Select Project or Log Ad-hoc -- ';
} else if (selectEl === rtwlEntryProjectIdSelectEl) {
baseOptions = '
-- Ad-hoc / No Project -- ';
}
selectEl.innerHTML = baseOptions;
rtwlProjects.forEach(proj => {
const option = document.createElement('option');
option.value = proj.id;
option.textContent = proj.name;
selectEl.appendChild(option);
});
if(selectEl === rtwlTaskSelectTimerEl) {
selectEl.innerHTML += '
Log Ad-hoc Task ';
}
if (Array.from(selectEl.options).some(opt => opt.value === currentVal)) {
selectEl.value = currentVal;
}
});
}
// --- Live Timer Functions ---
function rtwlStartNewTimer() {
if (rtwlActiveTimer) {
alert("A timer is already running. Stop or discard it first.");
return;
}
const projectId = rtwlTaskSelectTimerEl.value;
let taskDescription;
let currentProjectNameForDisplay = "";
if (projectId === 'adhoc') {
taskDescription = rtwlAdhocTaskNameTimerEl.value.trim();
if (!taskDescription) {
alert("Please enter a name for the ad-hoc task.");
return;
}
currentProjectNameForDisplay = taskDescription;
} else if (projectId) {
const project = rtwlProjects.find(p => p.id === projectId);
if (!project) { alert("Selected project not found."); return; }
taskDescription = project.name;
currentProjectNameForDisplay = project.name;
} else {
alert("Please select a project or choose 'Log Ad-hoc Task'.");
return;
}
rtwlActiveTimer = {
projectId: projectId === 'adhoc' ? null : projectId,
taskDescription: taskDescription,
currentProjectNameForDisplay: currentProjectNameForDisplay,
startTimeEpoch: Date.now(),
isPaused: false,
pausedDuration: 0,
lastPauseTime: 0,
intervalId: null
};
rtwlActiveTimerAreaEl.style.display = 'block';
rtwlActiveTaskNameDisplayEl.textContent = `Working on: ${rtwlActiveTimer.currentProjectNameForDisplay}`;
rtwlPauseResumeTimerButtonEl.textContent = "Pause";
rtwlUpdateRunningTimerDisplay();
rtwlActiveTimer.intervalId = setInterval(rtwlUpdateRunningTimerDisplay, 1000);
rtwlSaveActiveTimer();
}
function rtwlUpdateRunningTimerDisplay() {
if (!rtwlActiveTimer || rtwlActiveTimer.isPaused || !rtwlActiveTimerDisplayEl) return;
const now = Date.now();
const elapsedMs = now - rtwlActiveTimer.startTimeEpoch - rtwlActiveTimer.pausedDuration;
const totalSeconds = Math.floor(elapsedMs / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
rtwlActiveTimerDisplayEl.textContent =
`${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
document.title = `${rtwlActiveTimerDisplayEl.textContent} - ${rtwlActiveTimer.currentProjectNameForDisplay}`;
}
function rtwlTogglePauseResumeTimer() {
if (!rtwlActiveTimer) return;
rtwlActiveTimer.isPaused = !rtwlActiveTimer.isPaused;
if (rtwlActiveTimer.isPaused) {
if(rtwlActiveTimer.intervalId) clearInterval(rtwlActiveTimer.intervalId);
rtwlActiveTimer.lastPauseTime = Date.now();
rtwlPauseResumeTimerButtonEl.textContent = "Resume";
document.title = `Paused - ${rtwlActiveTimer.currentProjectNameForDisplay}`;
} else {
rtwlActiveTimer.pausedDuration += (Date.now() - rtwlActiveTimer.lastPauseTime);
rtwlActiveTimer.lastPauseTime = 0;
rtwlUpdateRunningTimerDisplay();
rtwlActiveTimer.intervalId = setInterval(rtwlUpdateRunningTimerDisplay, 1000);
rtwlPauseResumeTimerButtonEl.textContent = "Pause";
}
rtwlSaveActiveTimer();
}
function rtwlStopAndLogTimer() {
if (!rtwlActiveTimer) return;
if(rtwlActiveTimer.intervalId) clearInterval(rtwlActiveTimer.intervalId);
const endTimeEpoch = Date.now();
let finalPausedDuration = rtwlActiveTimer.pausedDuration;
if(rtwlActiveTimer.isPaused && rtwlActiveTimer.lastPauseTime > 0) { // If stopped while paused
finalPausedDuration += (endTimeEpoch - rtwlActiveTimer.lastPauseTime);
}
const elapsedMs = endTimeEpoch - rtwlActiveTimer.startTimeEpoch - finalPausedDuration;
const durationMinutes = Math.max(1, Math.round(elapsedMs / (1000 * 60)));
rtwlTimeEntryModalTitleEl.textContent = "Confirm & Log Timed Entry";
rtwlEntryIdInput.value = '';
const startDateObj = new Date(rtwlActiveTimer.startTimeEpoch);
const {date: startDate, time: startTime} = rtwlDateTimeToInputFields(startDateObj.toISOString());
const endDateObj = new Date(endTimeEpoch);
const {date: endDate, time: endTime} = rtwlDateTimeToInputFields(endDateObj.toISOString());
rtwlEntryTaskDescriptionInput.value = rtwlActiveTimer.taskDescription;
rtwlEntryProjectIdSelectEl.value = rtwlActiveTimer.projectId || "";
rtwlEntryStartDateInput.value = startDate;
rtwlEntryStartTimeInput.value = startTime;
rtwlEntryEndDateInput.value = endDate;
rtwlEntryEndTimeInput.value = endTime;
rtwlEntryDurationMinutesInput.value = durationMinutes;
rtwlEntryNotesInput.value = "";
rtwlEntryStartDateInput.readOnly = true; rtwlEntryStartTimeInput.readOnly = true;
rtwlEntryEndDateInput.readOnly = true; rtwlEntryEndTimeInput.readOnly = true;
rtwlEntryDurationMinutesInput.readOnly = true;
rtwlTimeEntryModalEl.style.display = 'block';
// Active timer state is fully cleared when modal's Save is successful
}
function rtwlDiscardActiveTimer() {
if (rtwlActiveTimer && confirm("Are you sure you want to discard this running timer?")) {
if(rtwlActiveTimer.intervalId) clearInterval(rtwlActiveTimer.intervalId);
rtwlActiveTimer = null;
rtwlActiveTimerAreaEl.style.display = 'none';
rtwlSaveActiveTimer();
document.title = "Real-Time Work Hour Logger";
}
}
function rtwlResumeInterruptedTimer() {
if (!rtwlActiveTimer || !rtwlActiveTimer.startTimeEpoch) {
rtwlActiveTimer = null; rtwlSaveActiveTimer(); return;
}
const now = Date.now();
let effectiveStartTime = rtwlActiveTimer.startTimeEpoch;
let totalPausedTime = rtwlActiveTimer.pausedDuration || 0;
if (rtwlActiveTimer.isPaused && rtwlActiveTimer.lastPauseTime > 0) {
totalPausedTime += (now - rtwlActiveTimer.lastPauseTime);
}
const actualRunningTimeMs = (now - effectiveStartTime) - totalPausedTime;
const actualRunningTimeMinutes = Math.round(actualRunningTimeMs / (1000 * 60));
let userConfirmation = false;
if (actualRunningTimeMinutes < 1) {
userConfirmation = confirm(`An active timer for "${rtwlActiveTimer.currentProjectNameForDisplay || rtwlActiveTimer.taskDescription}" was found but ran for less than a minute. Still log or adjust it?`);
} else {
userConfirmation = confirm(`An active timer for "${rtwlActiveTimer.currentProjectNameForDisplay || rtwlActiveTimer.taskDescription}" was found. It appears to have been active for about ${rtwlFormatDuration(actualRunningTimeMinutes)}. Do you want to log or adjust this?`);
}
if (!userConfirmation) {
rtwlActiveTimer = null; rtwlSaveActiveTimer();
if(rtwlActiveTimerAreaEl) rtwlActiveTimerAreaEl.style.display = 'none';
document.title = "Real-Time Work Hour Logger";
return;
}
const endTimeEpoch = now;
const durationMinutes = Math.max(1, actualRunningTimeMinutes);
rtwlTimeEntryModalTitleEl.textContent = "Log Recovered Timer Session";
rtwlEntryIdInput.value = '';
const startDateObj = new Date(rtwlActiveTimer.startTimeEpoch);
const {date: startDate, time: startTime} = rtwlDateTimeToInputFields(startDateObj.toISOString());
const endDateObj = new Date(endTimeEpoch);
const {date: endDate, time: endTime} = rtwlDateTimeToInputFields(endDateObj.toISOString());
rtwlEntryTaskDescriptionInput.value = rtwlActiveTimer.taskDescription;
rtwlEntryProjectIdSelectEl.value = rtwlActiveTimer.projectId || "";
rtwlEntryStartDateInput.value = startDate;
rtwlEntryStartTimeInput.value = startTime;
rtwlEntryEndDateInput.value = endDate;
rtwlEntryEndTimeInput.value = endTime;
rtwlEntryDurationMinutesInput.value = durationMinutes;
rtwlEntryNotesInput.value = "Recovered session (browser closed/refreshed). Please verify times.";
rtwlEntryStartDateInput.readOnly = false; rtwlEntryStartTimeInput.readOnly = false;
rtwlEntryEndDateInput.readOnly = false; rtwlEntryEndTimeInput.readOnly = false;
rtwlEntryDurationMinutesInput.readOnly = true; // Calculated from start/end
rtwlTimeEntryModalEl.style.display = 'block';
// Active timer will be fully cleared when modal is saved (handleTimeEntryFormSubmit)
if(rtwlActiveTimer.intervalId) clearInterval(rtwlActiveTimer.intervalId);
if(rtwlActiveTimerAreaEl) rtwlActiveTimerAreaEl.style.display = 'none';
document.title = "Real-Time Work Hour Logger";
}
// --- Manual Entry & Daily Log ---
window.rtwlOpenManualEntryModal = function(entryId = null) {
if (!rtwlTimeEntryModalEl) return;
rtwlTimeEntryForm.reset();
rtwlPopulateProjectSelects();
rtwlEntryIdInput.value = '';
rtwlEntryStartDateInput.readOnly = false; rtwlEntryStartTimeInput.readOnly = false;
rtwlEntryEndDateInput.readOnly = false; rtwlEntryEndTimeInput.readOnly = false;
rtwlEntryDurationMinutesInput.readOnly = true;
rtwlEntryDurationMinutesInput.style.backgroundColor = "#f0f0f0";
if (entryId) {
rtwlTimeEntryModalTitleEl.textContent = "Edit Time Entry";
const entry = rtwlTimeEntries.find(e => e.id === entryId);
if (entry) {
rtwlEntryIdInput.value = entry.id;
rtwlEntryTaskDescriptionInput.value = entry.taskDescription;
rtwlEntryProjectIdSelectEl.value = entry.projectId || "";
const startDT = rtwlDateTimeToInputFields(entry.startTimeISO);
rtwlEntryStartDateInput.value = startDT.date;
rtwlEntryStartTimeInput.value = startDT.time;
const endDT = rtwlDateTimeToInputFields(entry.endTimeISO);
rtwlEntryEndDateInput.value = endDT.date;
rtwlEntryEndTimeInput.value = endDT.time;
rtwlEntryDurationMinutesInput.value = entry.durationMinutes;
rtwlEntryNotesInput.value = entry.notes || "";
}
} else {
rtwlTimeEntryModalTitleEl.textContent = "Add Manual Time Entry";
const defaultStartDate = rtwlLogViewDateEl.value || rtwlFormatDateKey(new Date(2025, 4, 14));
rtwlEntryStartDateInput.value = defaultStartDate;
rtwlEntryStartTimeInput.value = "09:00";
rtwlEntryEndDateInput.value = defaultStartDate;
rtwlEntryEndTimeInput.value = "10:00";
rtwlCalculateDurationManual();
}
rtwlTimeEntryModalEl.style.display = 'block';
};
function rtwlCalculateDurationManual() {
const startISO = rtwlInputFieldsToISO(rtwlEntryStartDateInput.value, rtwlEntryStartTimeInput.value);
const endISO = rtwlInputFieldsToISO(rtwlEntryEndDateInput.value, rtwlEntryEndTimeInput.value);
if (startISO && endISO) {
const diffMs = new Date(endISO).getTime() - new Date(startISO).getTime();
if (diffMs > 0) {
rtwlEntryDurationMinutesInput.value = Math.round(diffMs / (1000 * 60));
} else {
rtwlEntryDurationMinutesInput.value = 0;
}
} else {
rtwlEntryDurationMinutesInput.value = 0;
}
}
function handleTimeEntryFormSubmit(e) {
e.preventDefault();
// If this save is for a recovered timer, clear the active timer state
if (rtwlActiveTimer && rtwlTimeEntryModalTitleEl.textContent.includes("Recovered")) {
rtwlActiveTimer = null;
rtwlSaveActiveTimer();
}
// If this save is for a timer just stopped (not recovered), clear active timer state
if (rtwlActiveTimer && rtwlTimeEntryModalTitleEl.textContent.includes("Confirm & Log Timed Entry")) {
rtwlActiveTimer = null;
rtwlSaveActiveTimer();
}
const entryId = rtwlEntryIdInput.value;
const taskDescription = rtwlEntryTaskDescriptionInput.value.trim();
const projectId = rtwlEntryProjectIdSelectEl.value;
const startDate = rtwlEntryStartDateInput.value;
const startTime = rtwlEntryStartTimeInput.value;
const endDate = rtwlEntryEndDateInput.value;
const endTime = rtwlEntryEndTimeInput.value;
let durationMinutes; // Recalculate based on final times
const notes = rtwlEntryNotesInput.value.trim();
if (!taskDescription || !startDate || !startTime || !endDate || !endTime) {
alert("Task description, and all start/end date & time fields are required for manual entry."); return;
}
const startTimeISO = rtwlInputFieldsToISO(startDate, startTime);
const endTimeISO = rtwlInputFieldsToISO(endDate, endTime);
if (!startTimeISO || !endTimeISO) { alert("Invalid date/time format."); return; }
const diffMs = new Date(endTimeISO).getTime() - new Date(startTimeISO).getTime();
if (diffMs <=0) { alert("End time must be after start time."); return; }
durationMinutes = Math.max(1, Math.round(diffMs / (1000 * 60)));
const newEntryData = {
projectId: projectId || null,
taskDescription,
startTimeISO,
endTimeISO,
durationMinutes,
notes
};
if (entryId) {
const index = rtwlTimeEntries.findIndex(entry => entry.id === entryId);
if (index > -1) {
rtwlTimeEntries[index] = { ...rtwlTimeEntries[index], ...newEntryData };
}
} else {
rtwlTimeEntries.push({ id: 'entry-' + Date.now(), ...newEntryData });
}
rtwlSaveTimeEntries();
rtwlRenderDailyLogEntries();
rtwlCloseModal('rtwlTimeEntryModal');
rtwlUpdateQuickStartButtons();
}
window.rtwlDeleteLogEntry = function(entryId) {
if (confirm("Are you sure you want to delete this time entry?")) {
rtwlTimeEntries = rtwlTimeEntries.filter(entry => entry.id !== entryId);
rtwlSaveTimeEntries();
rtwlRenderDailyLogEntries();
rtwlUpdateQuickStartButtons();
}
};
function rtwlRenderDailyLogEntries() {
if (!rtwlDailyLogEntriesListEl || !rtwlLogViewDateEl || !rtwlCurrentDateDisplayEl) return;
const selectedDateKey = rtwlLogViewDateEl.value;
if (!selectedDateKey) {
rtwlDailyLogEntriesListEl.innerHTML = '
Select a date to view log entries.
';
if(rtwlCurrentDateDisplayEl) rtwlCurrentDateDisplayEl.textContent = 'N/A';
return;
}
if(rtwlCurrentDateDisplayEl) rtwlCurrentDateDisplayEl.textContent = new Date(selectedDateKey + "T00:00:00").toLocaleDateString();
const entriesForDay = rtwlTimeEntries.filter(entry =>
rtwlFormatDateKey(new Date(entry.startTimeISO)) === selectedDateKey
).sort((a,b) => new Date(a.startTimeISO).getTime() - new Date(b.startTimeISO).getTime());
rtwlDailyLogEntriesListEl.innerHTML = '';
if (entriesForDay.length === 0) {
rtwlDailyLogEntriesListEl.innerHTML = '
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);
}
});
});