Spaces:
Runtime error
Runtime error
| 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 = ` | |
| <div class="modal-dialog modal-dialog-centered"> | |
| <div class="modal-content shadow-lg border-0"> | |
| <div class="modal-header bg-dark text-white"> | |
| <h5 class="modal-title" id="calendar-modal-title">Schedule Task</h5> | |
| <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> | |
| </div> | |
| <form id="calendar-task-form"> | |
| <input type="hidden" id="cal-task-id"> | |
| <div class="modal-body p-4"> | |
| <div class="mb-3"> | |
| <label class="form-label fw-semibold text-dark">Task Title *</label> | |
| <input type="text" class="form-control shadow-none" id="cal-task-title" required placeholder="e.g. Review CNC supplier quotes"> | |
| </div> | |
| <div class="row g-2 mb-3"> | |
| <div class="col-6"> | |
| <label class="form-label fw-semibold text-dark">Date *</label> | |
| <input type="date" class="form-control shadow-none" id="cal-task-date" required> | |
| </div> | |
| <div class="col-6"> | |
| <label class="form-label fw-semibold text-dark">Time</label> | |
| <input type="time" class="form-control shadow-none" id="cal-task-time" value="09:00"> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label fw-semibold text-dark">Notes / Description</label> | |
| <textarea class="form-control shadow-none" id="cal-task-notes" rows="3" placeholder="Describe the task or next actions..."></textarea> | |
| </div> | |
| <div class="form-check"> | |
| <input class="form-check-input shadow-none" type="checkbox" id="cal-task-completed"> | |
| <label class="form-check-label fw-semibold text-dark" for="cal-task-completed"> | |
| Mark as Completed | |
| </label> | |
| </div> | |
| </div> | |
| <div class="modal-footer border-0 p-3 bg-light d-flex justify-content-between"> | |
| <button type="button" class="btn btn-danger" id="cal-task-delete-btn" style="display: none;">Delete</button> | |
| <div class="ms-auto"> | |
| <button type="button" class="btn btn-outline-secondary me-2" data-bs-dismiss="modal">Cancel</button> | |
| <button type="submit" class="btn btn-dark">Save Task</button> | |
| </div> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| `; | |
| 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 = ` | |
| <div class="calendar-month-grid"> | |
| <div class="calendar-grid-header">Mon</div> | |
| <div class="calendar-grid-header">Tue</div> | |
| <div class="calendar-grid-header">Wed</div> | |
| <div class="calendar-grid-header">Thu</div> | |
| <div class="calendar-grid-header">Fri</div> | |
| <div class="calendar-grid-header">Sat</div> | |
| <div class="calendar-grid-header">Sun</div> | |
| `; | |
| 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 => ` | |
| <div class="calendar-task-card" draggable="true" data-id="${t.id}" style="${t.completed ? 'text-decoration: line-through; opacity: 0.6;' : ''}"> | |
| <span class="fw-bold">${formatDisplayTime(t.time)}</span> | |
| <span class="calendar-task-title flex-grow-1 ms-1">${escapeHtml(t.title)}</span> | |
| </div> | |
| `).join(''); | |
| html += ` | |
| <div class="calendar-month-cell ${cell.isCurrentMonth ? '' : 'outside'} ${isToday ? 'today' : ''}" data-date="${cell.dateStr}"> | |
| <div class="calendar-cell-date">${cell.dayNum}</div> | |
| <div class="calendar-cell-tasks-container"> | |
| ${tasksHtml} | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| html += `</div>`; | |
| 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 = `<div class="calendar-week-row-grid">`; | |
| 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 => ` | |
| <div class="calendar-task-card mb-2" draggable="true" data-id="${t.id}" style="${t.completed ? 'text-decoration: line-through; opacity: 0.6;' : ''}"> | |
| <span class="fw-bold text-dark me-1">${formatDisplayTime(t.time)}</span> | |
| <span class="calendar-task-title flex-grow-1">${escapeHtml(t.title)}</span> | |
| </div> | |
| `).join(''); | |
| html += ` | |
| <div class="calendar-week-column ${isToday ? 'today' : ''}" data-date="${dateStr}"> | |
| <div class="calendar-week-col-header"> | |
| <div class="day-name">${weekDays[i].substring(0, 3)}</div> | |
| <div class="day-num">${d.getDate()}</div> | |
| </div> | |
| <div class="calendar-week-tasks-container"> | |
| ${tasksHtml || '<span class="text-muted small text-center my-auto w-100 d-block" style="font-size: 0.72rem; font-style: italic;">No duties</span>'} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| html += `</div>`; | |
| 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 = '<p class="text-muted text-center py-4 m-0">No tasks left. Write above to create one.</p>'; | |
| } else { | |
| taskContainer.innerHTML = currentTasks.map(task => ` | |
| <div class="task-row d-flex flex-column py-3 border-bottom ${task.completed ? 'completed' : ''}" id="task-row-${task.id}" draggable="true" style="cursor: grab; gap: 8px;"> | |
| <!-- Top Row: Date and Time Chip (on top left) --> | |
| <div class="d-flex justify-content-between align-items-center w-100"> | |
| <div class="task-date-container"> | |
| ${task.date ? `<span class="badge border ${getTaskChipBorderColor(task.date)}" id="task-date-chip-${task.id}" style="font-size: 0.72rem; font-weight: 500; border-width: 1px !important; white-space: nowrap;">${escapeHtml(formatDisplayDateTime(task.date, task.time))}</span>` : '<span class="badge border border-secondary text-secondary bg-light" style="font-size: 0.72rem; font-weight: 500; border-width: 1px !important;">Unassigned</span>'} | |
| </div> | |
| </div> | |
| <!-- Bottom Row: Checkbox, Separator, Description, and Dropdown --> | |
| <div class="d-flex align-items-center w-100" style="gap: 4px;"> | |
| <!-- Checkbox Circle --> | |
| <div class="task-checkbox-custom flex-shrink-0 ${task.completed ? 'checked' : ''}" data-id="${task.id}"></div> | |
| <!-- Vertical line separator --> | |
| <span class="text-muted px-1" style="font-weight: 300; font-size: 1rem; user-select: none;">|</span> | |
| <!-- Task Description Section (fixed max-width, sentence breaking) --> | |
| <div class="flex-grow-1" style="min-width: 0; max-width: 75%; word-break: break-word; white-space: normal;"> | |
| <span class="task-title-text" id="task-title-text-${task.id}" style="font-weight: 500; display: block; line-height: 1.4; white-space: normal; word-break: break-word; overflow-wrap: break-word;">${escapeHtml(task.title)}</span> | |
| <input type="text" class="task-inline-edit-input d-none" id="task-inline-edit-${task.id}" value="${escapeHtml(task.title)}" data-id="${task.id}" style="width: 100%;"> | |
| </div> | |
| <!-- Three-dot dropdown menu (at last/right end) --> | |
| <div class="ms-auto task-row-actions d-flex gap-2 align-items-center flex-shrink-0"> | |
| <button class="btn btn-sm btn-dark done-edit-btn d-none" id="btn-done-action-${task.id}" data-id="${task.id}" style="font-size: 0.75rem; padding: 2px 8px;">Save</button> | |
| <div class="dropdown" id="dropdown-menu-${task.id}"> | |
| <button class="btn btn-link text-muted p-0 shadow-none border-0" type="button" data-bs-toggle="dropdown" aria-expanded="false" style="font-size: 1.1rem; line-height: 1; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;"> | |
| <i class="bi bi-three-dots-vertical"></i> | |
| </button> | |
| <ul class="dropdown-menu dropdown-menu-end shadow border-0 py-1" style="font-size: 0.82rem; min-width: 100px; border-radius: 8px;"> | |
| <li><button class="dropdown-item edit-task-btn py-1 px-3 d-flex align-items-center gap-2" type="button" data-id="${task.id}"><i class="bi bi-pencil" style="font-size: 0.8rem;"></i> Edit</button></li> | |
| <li><hr class="dropdown-divider my-1"></li> | |
| <li><button class="dropdown-item delete-task-btn text-danger py-1 px-3 d-flex align-items-center gap-2" type="button" data-id="${task.id}"><i class="bi bi-trash" style="font-size: 0.8rem;"></i> Delete</button></li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `).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; | |