function initTaskManagement() { const taskContainer = document.getElementById('task-items-container'); const quickAddInput = document.getElementById('task-quick-add-input'); const taskSummaryCount = document.getElementById('task-summary-count'); // State initialization if (typeof window.calendarView === 'undefined') { window.calendarView = 'month'; } if (typeof window.calendarCurrentDate === 'undefined') { window.calendarCurrentDate = new Date(); } // Ensure no mock tasks are loaded in local storage let tasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); if (tasks.length > 0 && tasks.some(t => t.title === 'Upload primary contact data sheets' || t.title === 'Read the learning playbook on MapleMPSS')) { tasks = []; localStorage.setItem('prospectiq-tasks', JSON.stringify(tasks)); } // Install Sourcing Duty Calendar Modal if not present let calendarModal = document.getElementById('calendar-task-modal'); if (!calendarModal) { calendarModal = document.createElement('div'); calendarModal.id = 'calendar-task-modal'; calendarModal.className = 'modal fade'; calendarModal.tabIndex = -1; calendarModal.setAttribute('aria-hidden', 'true'); calendarModal.innerHTML = ` `; document.body.appendChild(calendarModal); const form = document.getElementById('calendar-task-form'); form.onsubmit = function(e) { e.preventDefault(); saveCalendarTask(); }; const delBtn = document.getElementById('cal-task-delete-btn'); delBtn.onclick = function() { deleteCalendarTask(); }; } // Helper functions for dates arithmetic function getDaysInMonth(year, month) { return new Date(year, month + 1, 0).getDate(); } function getFirstDayOfMonthIndex(year, month) { let day = new Date(year, month, 1).getDay(); return day === 0 ? 6 : day - 1; // Mon-Sun (0-6) } function getStartOfWeek(date) { const diff = date.getDate() - date.getDay() + (date.getDay() === 0 ? -6 : 1); return new Date(date.setDate(diff)); } function formatDate(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}-${m}-${d}`; } function formatDisplayTime(timeStr) { if (!timeStr) return ''; const parts = timeStr.split(':'); let hours = parseInt(parts[0]); const minutes = parts[1]; const ampm = hours >= 12 ? 'pm' : 'am'; hours = hours % 12; hours = hours ? hours : 12; return `${hours}:${minutes}${ampm}`; } function formatDisplayDateTime(dateStr, timeStr) { if (!dateStr) return ''; const dateParts = dateStr.split('-'); const d = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); const dateFormatted = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); if (!timeStr) return dateFormatted; return `${dateFormatted} at ${formatDisplayTime(timeStr)}`; } // ── GOOGLE CALENDAR VIEW ENGINE ── function renderHomeCalendar() { const container = document.getElementById('calendar-view-container'); const monthTitle = document.getElementById('calendar-month-title'); if (!container) return; const currentTasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); const year = window.calendarCurrentDate.getFullYear(); const month = window.calendarCurrentDate.getMonth(); // Update Title Header const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; if (window.calendarView === 'month') { monthTitle.textContent = `${monthNames[month]} ${year}`; } else { const startOfWeek = getStartOfWeek(new Date(window.calendarCurrentDate)); const endOfWeek = new Date(startOfWeek); endOfWeek.setDate(startOfWeek.getDate() + 6); monthTitle.textContent = `${monthNames[startOfWeek.getMonth()]} ${startOfWeek.getDate()} – ${monthNames[endOfWeek.getMonth()]} ${endOfWeek.getDate()}, ${endOfWeek.getFullYear()}`; } const todayBtn = document.getElementById('cal-today-btn'); if (todayBtn) { todayBtn.textContent = window.calendarView === 'month' ? 'This Month' : 'This Week'; } // Render Month View Grid if (window.calendarView === 'month') { const cells = []; const daysInMonth = getDaysInMonth(year, month); const firstDayIndex = getFirstDayOfMonthIndex(year, month); // Prev month padding const prevMonthYear = month === 0 ? year - 1 : year; const prevMonth = month === 0 ? 11 : month - 1; const daysInPrevMonth = getDaysInMonth(prevMonthYear, prevMonth); for (let i = firstDayIndex - 1; i >= 0; i--) { const d = daysInPrevMonth - i; cells.push({ dayNum: d, dateStr: `${prevMonthYear}-${String(prevMonth + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`, isCurrentMonth: false }); } // Current month cells for (let d = 1; d <= daysInMonth; d++) { cells.push({ dayNum: d, dateStr: `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`, isCurrentMonth: true }); } // Next month padding const nextMonthYear = month === 11 ? year + 1 : year; const nextMonth = month === 11 ? 0 : month + 1; let nextIndex = 1; while (cells.length < 42) { cells.push({ dayNum: nextIndex, dateStr: `${nextMonthYear}-${String(nextMonth + 1).padStart(2, '0')}-${String(nextIndex).padStart(2, '0')}`, isCurrentMonth: false }); nextIndex++; } let html = `
Mon
Tue
Wed
Thu
Fri
Sat
Sun
`; cells.forEach(cell => { const dayTasks = currentTasks.filter(t => t.date === cell.dateStr); dayTasks.sort((a, b) => (a.time || '00:00').localeCompare(b.time || '00:00')); const isToday = cell.dateStr === formatDate(new Date()); const tasksHtml = dayTasks.map(t => `
${formatDisplayTime(t.time)} ${escapeHtml(t.title)}
`).join(''); html += `
${cell.dayNum}
${tasksHtml}
`; }); html += `
`; container.innerHTML = html; } else { // Render Week View Grid const startOfWeek = getStartOfWeek(new Date(window.calendarCurrentDate)); const weekDays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; let html = `
`; for (let i = 0; i < 7; i++) { const d = new Date(startOfWeek); d.setDate(startOfWeek.getDate() + i); const dateStr = formatDate(d); const isToday = dateStr === formatDate(new Date()); const dayTasks = currentTasks.filter(t => t.date === dateStr); dayTasks.sort((a, b) => (a.time || '00:00').localeCompare(b.time || '00:00')); const tasksHtml = dayTasks.map(t => `
${formatDisplayTime(t.time)} ${escapeHtml(t.title)}
`).join(''); html += `
${weekDays[i].substring(0, 3)}
${d.getDate()}
${tasksHtml || 'No duties'}
`; } html += `
`; container.innerHTML = html; } // Wire click triggers on calendar cell blocks document.querySelectorAll('.calendar-month-cell, .calendar-week-column').forEach(cell => { cell.addEventListener('click', (e) => { // If clicked task card itself, do not open add modal if (e.target.closest('.calendar-task-card')) return; const dStr = cell.getAttribute('data-date'); openCalendarTaskModal(null, dStr); }); }); // Wire click triggers on task cards inside the calendar document.querySelectorAll('.calendar-task-card').forEach(card => { card.addEventListener('click', (e) => { e.stopPropagation(); const taskId = card.getAttribute('data-id'); openCalendarTaskModal(taskId); }); // Make calendar cards draggable to reschedule card.addEventListener('dragstart', (e) => { const id = card.getAttribute('data-id'); e.dataTransfer.setData('text/plain', id); }); }); // Wire dropzones document.querySelectorAll('.calendar-month-cell, .calendar-week-column').forEach(cell => { cell.addEventListener('dragover', (e) => { e.preventDefault(); cell.classList.add('dragover'); }); cell.addEventListener('dragleave', () => { cell.classList.remove('dragover'); }); cell.addEventListener('drop', (e) => { e.preventDefault(); cell.classList.remove('dragover'); const taskId = parseInt(e.dataTransfer.getData('text/plain')); const dateStr = cell.getAttribute('data-date'); if (taskId && dateStr) { assignTaskToDate(taskId, dateStr); } }); }); } window.renderHomeCalendar = renderHomeCalendar; function assignTaskToDate(taskId, dateStr) { let tasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); const index = tasks.findIndex(t => t.id === taskId); if (index !== -1) { tasks[index].date = dateStr; tasks[index].time = tasks[index].time || '09:00'; localStorage.setItem('prospectiq-tasks', JSON.stringify(tasks)); if (typeof addNotification === 'function') { addNotification("Task Scheduled", `Rescheduled "${tasks[index].title}" to ${dateStr}.`); } renderTasks(); renderHomeCalendar(); } } function getTaskChipBorderColor(dateStr) { if (!dateStr) return ''; const today = new Date(); today.setHours(0, 0, 0, 0); const taskDate = new Date(dateStr); taskDate.setHours(0, 0, 0, 0); const diffTime = taskDate - today; const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); if (diffDays <= 1) { return 'border-danger text-danger bg-danger-subtle'; } else if (diffDays <= 3) { return 'border-warning text-warning-emphasis bg-warning-subtle'; } else { return 'border-success text-success bg-success-subtle'; } } // ── RENDER REGULAR TASKS LIST ── function renderTasks() { const currentTasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); const openTasksCount = currentTasks.filter(t => !t.completed).length; if (taskSummaryCount) { taskSummaryCount.textContent = `${openTasksCount} open task${openTasksCount === 1 ? '' : 's'} (${currentTasks.length} total)`; } if (taskContainer) { if (currentTasks.length === 0) { taskContainer.innerHTML = '

No tasks left. Write above to create one.

'; } else { taskContainer.innerHTML = currentTasks.map(task => `
${task.date ? `${escapeHtml(formatDisplayDateTime(task.date, task.time))}` : 'Unassigned'}
|
${escapeHtml(task.title)}
`).join(''); } } // Wire checkbox toggles document.querySelectorAll('.task-checkbox-custom').forEach(cb => { cb.addEventListener('click', () => { const id = parseInt(cb.getAttribute('data-id')); toggleTaskComplete(id); }); }); // Wire inline edit toggle document.querySelectorAll('.edit-task-btn').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.getAttribute('data-id')); enterEditMode(id); }); }); // Wire inline save action document.querySelectorAll('.done-edit-btn').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.getAttribute('data-id')); saveInlineEdit(id); }); }); // Wire enter-key listener for inline inputs document.querySelectorAll('.task-inline-edit-input').forEach(input => { input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { const id = parseInt(input.getAttribute('data-id')); saveInlineEdit(id); } else if (e.key === 'Escape') { const id = parseInt(input.getAttribute('data-id')); exitEditMode(id); } }); }); // Wire delete action document.querySelectorAll('.delete-task-btn').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.getAttribute('data-id')); deleteTask(id); }); }); // Wire drag events on task rows document.querySelectorAll('.task-row').forEach(row => { row.addEventListener('dragstart', (e) => { const id = row.id.replace('task-row-', ''); e.dataTransfer.setData('text/plain', id); row.classList.add('dragging'); }); row.addEventListener('dragend', () => { row.classList.remove('dragging'); }); }); // Wire dropzone on the task list itself if (taskContainer) { taskContainer.addEventListener('dragover', (e) => { e.preventDefault(); taskContainer.classList.add('dragover'); }); taskContainer.addEventListener('dragleave', () => { taskContainer.classList.remove('dragover'); }); taskContainer.addEventListener('drop', (e) => { e.preventDefault(); taskContainer.classList.remove('dragover'); const taskId = parseInt(e.dataTransfer.getData('text/plain')); if (taskId) { unassignTaskFromDate(taskId); } }); } } function unassignTaskFromDate(taskId) { let tasksList = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); const index = tasksList.findIndex(t => t.id === taskId); if (index !== -1) { tasksList[index].date = null; tasksList[index].time = null; localStorage.setItem('prospectiq-tasks', JSON.stringify(tasksList)); if (typeof addNotification === 'function') { addNotification("Task Unscheduled", `Removed "${tasksList[index].title}" from calendar.`); } renderTasks(); renderHomeCalendar(); } } // 1. Quick Add Input handler if (quickAddInput) { quickAddInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { const title = quickAddInput.value.trim(); if (!title) return; const currentTasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); const newTask = { id: Date.now(), title, completed: false, date: null, time: null }; currentTasks.unshift(newTask); localStorage.setItem('prospectiq-tasks', JSON.stringify(currentTasks)); quickAddInput.value = ''; renderTasks(); renderHomeCalendar(); } }); } // Helper functions for toggles & inline edit function toggleTaskComplete(id) { let currentTasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); currentTasks = currentTasks.map(t => t.id === id ? { ...t, completed: !t.completed } : t); localStorage.setItem('prospectiq-tasks', JSON.stringify(currentTasks)); renderTasks(); renderHomeCalendar(); } function enterEditMode(id) { const textSpan = document.getElementById(`task-title-text-${id}`); const inputField = document.getElementById(`task-inline-edit-${id}`); const dropdownMenu = document.getElementById(`dropdown-menu-${id}`); const doneBtn = document.getElementById(`btn-done-action-${id}`); if (textSpan && inputField && dropdownMenu && doneBtn) { textSpan.classList.add('d-none'); inputField.classList.remove('d-none'); dropdownMenu.classList.add('d-none'); doneBtn.classList.remove('d-none'); inputField.focus(); inputField.setSelectionRange(inputField.value.length, inputField.value.length); } } function exitEditMode(id) { const textSpan = document.getElementById(`task-title-text-${id}`); const inputField = document.getElementById(`task-inline-edit-${id}`); const dropdownMenu = document.getElementById(`dropdown-menu-${id}`); const doneBtn = document.getElementById(`btn-done-action-${id}`); if (textSpan && inputField && dropdownMenu && doneBtn) { textSpan.classList.remove('d-none'); inputField.classList.add('d-none'); dropdownMenu.classList.remove('d-none'); doneBtn.classList.add('d-none'); } } function saveInlineEdit(id) { const inputField = document.getElementById(`task-inline-edit-${id}`); if (!inputField) return; const newTitle = inputField.value.trim(); if (!newTitle) return; let currentTasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); currentTasks = currentTasks.map(t => t.id === id ? { ...t, title: newTitle } : t); localStorage.setItem('prospectiq-tasks', JSON.stringify(currentTasks)); renderTasks(); renderHomeCalendar(); } function deleteTask(id) { let currentTasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); currentTasks = currentTasks.filter(t => t.id !== id); localStorage.setItem('prospectiq-tasks', JSON.stringify(currentTasks)); renderTasks(); renderHomeCalendar(); } // ── CALENDAR NAVIGATION WIREUP ── const prevBtn = document.getElementById('cal-prev-btn'); if (prevBtn) { prevBtn.onclick = () => { if (window.calendarView === 'month') { window.calendarCurrentDate.setMonth(window.calendarCurrentDate.getMonth() - 1); } else { window.calendarCurrentDate.setDate(window.calendarCurrentDate.getDate() - 7); } renderHomeCalendar(); }; } const nextBtn = document.getElementById('cal-next-btn'); if (nextBtn) { nextBtn.onclick = () => { if (window.calendarView === 'month') { window.calendarCurrentDate.setMonth(window.calendarCurrentDate.getMonth() + 1); } else { window.calendarCurrentDate.setDate(window.calendarCurrentDate.getDate() + 7); } renderHomeCalendar(); }; } const todayBtn = document.getElementById('cal-today-btn'); if (todayBtn) { todayBtn.onclick = () => { window.calendarCurrentDate = new Date(); renderHomeCalendar(); }; } const toggleMonthBtn = document.getElementById('toggle-month-view-btn'); const toggleWeekBtn = document.getElementById('toggle-week-view-btn'); if (toggleMonthBtn && toggleWeekBtn) { toggleMonthBtn.onclick = () => { window.calendarView = 'month'; toggleMonthBtn.className = 'btn btn-dark'; toggleWeekBtn.className = 'btn btn-outline-secondary'; renderHomeCalendar(); }; toggleWeekBtn.onclick = () => { window.calendarView = 'week'; toggleMonthBtn.className = 'btn btn-outline-secondary'; toggleWeekBtn.className = 'btn btn-dark'; renderHomeCalendar(); }; } const addDutyBtn = document.getElementById('cal-add-duty-btn'); if (addDutyBtn) { addDutyBtn.onclick = () => { openCalendarTaskModal(null, formatDate(new Date())); }; } // Initial render call renderTasks(); renderHomeCalendar(); } // Global Calendar Modal CRUD Methods function openCalendarTaskModal(taskId, dateStr) { const modalTitle = document.getElementById('calendar-modal-title'); const taskIdField = document.getElementById('cal-task-id'); const titleField = document.getElementById('cal-task-title'); const dateField = document.getElementById('cal-task-date'); const timeField = document.getElementById('cal-task-time'); const notesField = document.getElementById('cal-task-notes'); const completedField = document.getElementById('cal-task-completed'); const delBtn = document.getElementById('cal-task-delete-btn'); if (taskId) { modalTitle.textContent = 'Edit Task Details'; taskIdField.value = taskId; const tasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); const task = tasks.find(t => t.id === parseInt(taskId)); if (task) { titleField.value = task.title || ''; dateField.value = task.date || ''; timeField.value = task.time || '09:00'; notesField.value = task.notes || ''; completedField.checked = task.completed || false; } delBtn.style.display = 'block'; } else { modalTitle.textContent = 'Schedule Task'; taskIdField.value = ''; titleField.value = ''; dateField.value = dateStr || ''; timeField.value = '09:00'; notesField.value = ''; completedField.checked = false; delBtn.style.display = 'none'; } const modalEl = document.getElementById('calendar-task-modal'); const modalInst = bootstrap.Modal.getOrCreateInstance(modalEl); modalInst.show(); } window.openCalendarTaskModal = openCalendarTaskModal; function saveCalendarTask() { const taskId = document.getElementById('cal-task-id').value; const title = document.getElementById('cal-task-title').value.trim(); const date = document.getElementById('cal-task-date').value; const time = document.getElementById('cal-task-time').value; const notes = document.getElementById('cal-task-notes').value.trim(); const completed = document.getElementById('cal-task-completed').checked; let tasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); if (taskId) { const idNum = parseInt(taskId); tasks = tasks.map(t => t.id === idNum ? { ...t, title, date, time, notes, completed } : t); } else { const newTask = { id: Date.now(), title, date, time, notes, completed }; tasks.unshift(newTask); if (typeof addNotification === 'function') { addNotification("Task Scheduled", `Task scheduled for ${date} at ${time}.`); } } localStorage.setItem('prospectiq-tasks', JSON.stringify(tasks)); const modalEl = document.getElementById('calendar-task-modal'); const modalInst = bootstrap.Modal.getOrCreateInstance(modalEl); modalInst.hide(); if (typeof initTaskManagement === 'function') { initTaskManagement(); } } window.saveCalendarTask = saveCalendarTask; function deleteCalendarTask() { const taskId = document.getElementById('cal-task-id').value; if (!taskId) return; const idNum = parseInt(taskId); let tasks = JSON.parse(localStorage.getItem('prospectiq-tasks') || '[]'); tasks = tasks.filter(t => t.id !== idNum); localStorage.setItem('prospectiq-tasks', JSON.stringify(tasks)); const modalEl = document.getElementById('calendar-task-modal'); const modalInst = bootstrap.Modal.getOrCreateInstance(modalEl); modalInst.hide(); if (typeof initTaskManagement === 'function') { initTaskManagement(); } } window.deleteCalendarTask = deleteCalendarTask;