/** * Admin Calendar - Task Management * * Manages scheduled tasks with filtering, CRUD operations, and session-init integration */ (function() { 'use strict'; // State let currentView = 'list'; let currentFilters = { status: '', category: '', priority: '', projectId: '', includeCompleted: false }; let currentPage = 0; const pageSize = 50; let allTasks = []; // DOM Elements const tasksContainer = document.getElementById('tasks-container'); const addTaskBtn = document.getElementById('add-task-btn'); const taskModal = document.getElementById('task-modal'); const taskDetailModal = document.getElementById('task-detail-modal'); const taskForm = document.getElementById('task-form'); const paginationContainer = document.getElementById('pagination-container'); // Initialize document.addEventListener('DOMContentLoaded', init); function init() { setupEventListeners(); loadStats(); loadTasks(); } function setupEventListeners() { // Add task button addTaskBtn.addEventListener('click', () => openTaskModal()); // Modal controls document.getElementById('modal-close').addEventListener('click', closeTaskModal); document.getElementById('modal-cancel').addEventListener('click', closeTaskModal); document.getElementById('detail-modal-close').addEventListener('click', closeDetailModal); // Task form submission taskForm.addEventListener('submit', handleTaskSubmit); // Filters document.getElementById('filter-status').addEventListener('change', handleFilterChange); document.getElementById('filter-category').addEventListener('change', handleFilterChange); document.getElementById('filter-priority').addEventListener('change', handleFilterChange); document.getElementById('filter-project').addEventListener('change', handleFilterChange); document.getElementById('include-completed').addEventListener('change', handleFilterChange); document.getElementById('clear-filters-btn').addEventListener('click', clearFilters); // View toggle document.getElementById('view-list').addEventListener('click', () => setView('list')); document.getElementById('view-timeline').addEventListener('click', () => setView('timeline')); // Pagination document.getElementById('pagination-prev').addEventListener('click', () => changePage(-1)); document.getElementById('pagination-next').addEventListener('click', () => changePage(1)); // Event delegation for task actions tasksContainer.addEventListener('click', handleTaskAction); document.getElementById('task-detail-content').addEventListener('click', handleDetailAction); } function handleTaskAction(e) { const button = e.target.closest('button[data-action]'); if (!button) return; const action = button.dataset.action; const taskId = button.dataset.taskId; if (action === 'view') viewTask(taskId); else if (action === 'complete') completeTask(taskId); else if (action === 'edit') editTask(taskId); else if (action === 'dismiss') dismissTask(taskId); } function handleDetailAction(e) { const button = e.target.closest('button[data-action]'); if (!button) return; const action = button.dataset.action; const taskId = button.dataset.taskId; if (action === 'complete') completeTask(taskId); else if (action === 'edit') editTask(taskId); else if (action === 'dismiss') dismissTask(taskId); else if (action === 'delete') deleteTask(taskId); } async function loadStats() { try { const token = localStorage.getItem('admin_token'); const response = await fetch('/api/calendar/stats', { cache: 'no-store', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { const errorText = await response.text(); console.error('Stats API error:', response.status, errorText); throw new Error(`Failed to load stats: ${response.status}`); } const data = await response.json(); const stats = data.stats; document.getElementById('stat-total').textContent = stats.total || 0; document.getElementById('stat-pending').textContent = stats.pending || 0; document.getElementById('stat-overdue').textContent = stats.overdue || 0; document.getElementById('stat-due-soon').textContent = stats.dueSoon || 0; document.getElementById('stat-completed').textContent = stats.completed || 0; } catch (error) { console.error('Error loading stats:', error); showError('Failed to load statistics: ' + error.message); } } async function loadTasks() { try { const token = localStorage.getItem('admin_token'); // Build query parameters const params = new URLSearchParams({ limit: pageSize, skip: currentPage * pageSize }); if (currentFilters.status) params.append('status', currentFilters.status); if (currentFilters.category) params.append('category', currentFilters.category); if (currentFilters.priority) params.append('priority', currentFilters.priority); if (currentFilters.projectId) params.append('projectId', currentFilters.projectId); if (currentFilters.includeCompleted) params.append('includeCompleted', 'true'); const response = await fetch(`/api/calendar/tasks?${params}`, { cache: 'no-store', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) { const errorText = await response.text(); console.error('Tasks API error:', response.status, errorText); throw new Error(`Failed to load tasks: ${response.status}`); } const data = await response.json(); allTasks = data.tasks; renderTasks(allTasks); updatePagination(data.pagination); } catch (error) { console.error('Error loading tasks:', error); tasksContainer.innerHTML = `

Failed to load tasks: ${error.message}

Check console for details

`; } } function renderTasks(tasks) { if (!tasks || tasks.length === 0) { tasksContainer.innerHTML = `

No tasks found

`; return; } if (currentView === 'list') { renderListView(tasks); } else { renderTimelineView(tasks); } } function renderListView(tasks) { const html = tasks.map(task => { const statusColor = getStatusColor(task.status); const priorityColor = getPriorityColor(task.priority); const dueDate = new Date(task.dueDate); const isOverdue = task.status === 'overdue' || (task.status === 'pending' && dueDate < new Date()); return `
${task.status} ${task.priority} ${task.category} ${task.recurrence !== 'once' ? `↻ ${task.recurrence}` : ''}

${escapeHtml(task.title)}

${escapeHtml(task.description.substring(0, 150))}${task.description.length > 150 ? '...' : ''}

${formatDate(dueDate)}
${task.assignedTo ? `
${escapeHtml(task.assignedTo)}
` : ''} ${task.documentRef ? `
Document linked
` : ''}
${task.status === 'pending' || task.status === 'overdue' ? ` ` : ''} ${task.status === 'pending' ? ` ` : ''}
`; }).join(''); tasksContainer.innerHTML = html; } function renderTimelineView(tasks) { // Group tasks by due date const grouped = {}; tasks.forEach(task => { const date = new Date(task.dueDate).toDateString(); if (!grouped[date]) grouped[date] = []; grouped[date].push(task); }); const html = Object.keys(grouped).sort((a, b) => new Date(a) - new Date(b)).map(date => { const dateTasks = grouped[date]; return `

${formatDateHeader(new Date(date))}

${dateTasks.map(task => `
${task.status} ${task.priority}

${escapeHtml(task.title)}

${escapeHtml(task.category)}

${task.status === 'pending' || task.status === 'overdue' ? ` ` : ''}
`).join('')}
`; }).join(''); tasksContainer.innerHTML = html || '
No tasks to display
'; } function updatePagination(pagination) { if (!pagination) return; const showing = Math.min(pagination.skip + pagination.limit, pagination.total); document.getElementById('pagination-showing').textContent = showing; document.getElementById('pagination-total').textContent = pagination.total; document.getElementById('pagination-prev').disabled = pagination.skip === 0; document.getElementById('pagination-next').disabled = !pagination.hasMore; if (pagination.total > 0) { paginationContainer.classList.remove('hidden'); } else { paginationContainer.classList.add('hidden'); } } function changePage(delta) { currentPage += delta; if (currentPage < 0) currentPage = 0; loadTasks(); } function setView(view) { currentView = view; // Update button styles document.getElementById('view-list').className = view === 'list' ? 'px-3 py-2 text-sm font-medium rounded-md bg-blue-100 text-blue-700' : 'px-3 py-2 text-sm font-medium rounded-md text-gray-600 hover:bg-gray-100'; document.getElementById('view-timeline').className = view === 'timeline' ? 'px-3 py-2 text-sm font-medium rounded-md bg-blue-100 text-blue-700' : 'px-3 py-2 text-sm font-medium rounded-md text-gray-600 hover:bg-gray-100'; renderTasks(allTasks); } function handleFilterChange(e) { const id = e.target.id; if (id === 'filter-status') currentFilters.status = e.target.value; else if (id === 'filter-category') currentFilters.category = e.target.value; else if (id === 'filter-priority') currentFilters.priority = e.target.value; else if (id === 'filter-project') currentFilters.projectId = e.target.value; else if (id === 'include-completed') currentFilters.includeCompleted = e.target.checked; currentPage = 0; loadTasks(); } function clearFilters() { currentFilters = { status: '', category: '', priority: '', projectId: '', includeCompleted: false }; document.getElementById('filter-status').value = ''; document.getElementById('filter-category').value = ''; document.getElementById('filter-priority').value = ''; document.getElementById('filter-project').value = ''; document.getElementById('include-completed').checked = false; currentPage = 0; loadTasks(); } function openTaskModal(taskId = null) { document.getElementById('modal-title').textContent = taskId ? 'Edit Task' : 'Add Task'; if (taskId) { loadTaskForEdit(taskId); } else { taskForm.reset(); document.getElementById('task-id').value = ''; document.getElementById('task-show-in-session-init').checked = true; document.getElementById('task-reminder-days').value = '7'; document.getElementById('task-assigned-to').value = 'PM'; } taskModal.classList.remove('hidden'); } function closeTaskModal() { taskModal.classList.add('hidden'); taskForm.reset(); } function closeDetailModal() { taskDetailModal.classList.add('hidden'); } async function loadTaskForEdit(taskId) { try { const token = localStorage.getItem('admin_token'); const response = await fetch(`/api/calendar/tasks/${taskId}`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to load task'); const data = await response.json(); const task = data.task; document.getElementById('task-id').value = task.id; document.getElementById('task-title').value = task.title; document.getElementById('task-description').value = task.description; document.getElementById('task-due-date').value = formatDateTimeLocal(new Date(task.dueDate)); document.getElementById('task-priority').value = task.priority; document.getElementById('task-category').value = task.category; document.getElementById('task-recurrence').value = task.recurrence; document.getElementById('task-document-ref').value = task.documentRef || ''; document.getElementById('task-assigned-to').value = task.assignedTo || 'PM'; document.getElementById('task-show-in-session-init').checked = task.showInSessionInit; document.getElementById('task-reminder-days').value = task.reminderDaysBefore; } catch (error) { console.error('Error loading task:', error); showError('Failed to load task details'); closeTaskModal(); } } async function handleTaskSubmit(e) { e.preventDefault(); const taskId = document.getElementById('task-id').value; const taskData = { title: document.getElementById('task-title').value, description: document.getElementById('task-description').value, dueDate: document.getElementById('task-due-date').value, priority: document.getElementById('task-priority').value, category: document.getElementById('task-category').value, recurrence: document.getElementById('task-recurrence').value, documentRef: document.getElementById('task-document-ref').value || null, assignedTo: document.getElementById('task-assigned-to').value, showInSessionInit: document.getElementById('task-show-in-session-init').checked, reminderDaysBefore: parseInt(document.getElementById('task-reminder-days').value) }; try { const token = localStorage.getItem('admin_token'); const url = taskId ? `/api/calendar/tasks/${taskId}` : '/api/calendar/tasks'; const method = taskId ? 'PUT' : 'POST'; const response = await fetch(url, { method, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(taskData) }); if (!response.ok) throw new Error('Failed to save task'); closeTaskModal(); loadTasks(); loadStats(); showSuccess(taskId ? 'Task updated successfully' : 'Task created successfully'); } catch (error) { console.error('Error saving task:', error); showError('Failed to save task. Please try again.'); } } async function viewTask(taskId) { try { const token = localStorage.getItem('admin_token'); const response = await fetch(`/api/calendar/tasks/${taskId}`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to load task'); const data = await response.json(); const task = data.task; const html = `
${task.status} ${task.priority} ${task.category}

${escapeHtml(task.title)}

${escapeHtml(task.description)}

Due Date: ${formatDateTime(new Date(task.dueDate))}
Recurrence: ${task.recurrence}
Assigned To: ${escapeHtml(task.assignedTo || 'Unassigned')}
Session-Init: ${task.showInSessionInit ? 'Yes' : 'No'}
${task.documentRef ? `

Linked Document

${escapeHtml(task.documentRef)}

` : ''}
${task.status === 'pending' || task.status === 'overdue' ? ` ` : ''} ${task.status === 'pending' ? ` ` : ''}
`; document.getElementById('task-detail-content').innerHTML = html; taskDetailModal.classList.remove('hidden'); } catch (error) { console.error('Error viewing task:', error); showError('Failed to load task details'); } } async function completeTask(taskId) { if (!confirm('Mark this task as completed?')) return; try { const token = localStorage.getItem('admin_token'); const response = await fetch(`/api/calendar/tasks/${taskId}/complete`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to complete task'); closeDetailModal(); loadTasks(); loadStats(); showSuccess('Task marked as completed'); } catch (error) { console.error('Error completing task:', error); showError('Failed to complete task'); } } async function dismissTask(taskId) { const reason = prompt('Reason for dismissing this task?'); if (!reason) return; try { const token = localStorage.getItem('admin_token'); const response = await fetch(`/api/calendar/tasks/${taskId}/dismiss`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ reason }) }); if (!response.ok) throw new Error('Failed to dismiss task'); closeDetailModal(); loadTasks(); loadStats(); showSuccess('Task dismissed'); } catch (error) { console.error('Error dismissing task:', error); showError('Failed to dismiss task'); } } async function deleteTask(taskId) { if (!confirm('Are you sure you want to delete this task? This action cannot be undone.')) return; try { const token = localStorage.getItem('admin_token'); const response = await fetch(`/api/calendar/tasks/${taskId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to delete task'); closeDetailModal(); loadTasks(); loadStats(); showSuccess('Task deleted successfully'); } catch (error) { console.error('Error deleting task:', error); showError('Failed to delete task'); } } function editTask(taskId) { closeDetailModal(); openTaskModal(taskId); } // Utility functions function getStatusColor(status) { const colors = { pending: 'bg-yellow-100 text-yellow-800', in_progress: 'bg-blue-100 text-blue-800', completed: 'bg-green-100 text-green-800', dismissed: 'bg-gray-100 text-gray-800', overdue: 'bg-red-100 text-red-800' }; return colors[status] || 'bg-gray-100 text-gray-800'; } function getPriorityColor(priority) { const colors = { LOW: 'bg-gray-100 text-gray-700', MEDIUM: 'bg-blue-100 text-blue-700', HIGH: 'bg-orange-100 text-orange-700', CRITICAL: 'bg-red-100 text-red-700' }; return colors[priority] || 'bg-gray-100 text-gray-700'; } function formatDate(date) { const options = { year: 'numeric', month: 'short', day: 'numeric' }; return date.toLocaleDateString('en-US', options); } function formatDateTime(date) { const options = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }; return date.toLocaleDateString('en-US', options); } function formatDateHeader(date) { const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); if (date.toDateString() === today.toDateString()) return 'Today'; if (date.toDateString() === tomorrow.toDateString()) return 'Tomorrow'; return formatDate(date); } function formatDateTimeLocal(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day}T${hours}:${minutes}`; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function showSuccess(message) { alert(message); } function showError(message) { alert('Error: ' + message); } })();