Haris-83's picture
Upload app.js with huggingface_hub
c873c0d verified
Raw History Blame Contribute Delete
63.7 kB
// ===== Application State =====
let tasks = [];
let categories = [];
let workspaces = [];
let currentView = 'all';
let currentFilter = { priority: 'all', workspace: null, date: null, search: '' };
let currentSort = 'date-desc';
let editingTaskId = null;
let viewMode = 'list';
let sortableInstance = null;
let lastDeletedTask = null;
let focusModeEnabled = false;
let smartFilter = 'all';
let calendarMonth = new Date();
let calendarViewVisible = false;
let streakCount = 0;
let myDayTasks = [];
// Pomodoro
let pomodoroRunning = false;
let pomodoroTimer = null;
let pomodoroTime = 25 * 60;
let pomodoroPhase = 'focus';
let pomodoroSessions = 0;
let pomodoroTodayMinutes = 0;
// ===== Initialize =====
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
document.getElementById('loading-screen').classList.add('hidden');
loadFromLocalStorage();
initializeEventListeners();
setQuickAddDefaults();
renderTasks();
renderCategories();
renderWorkspaces();
updateWorkspaceSelect();
updateCounts();
updateProgress();
updateInsights();
applySettings();
checkDarkMode();
updateUserStats();
loadStreak();
updatePomodoroDisplay();
initParticles();
}, 1200);
});
// ===== Local Storage =====
function saveToLocalStorage() {
localStorage.setItem('tasks', JSON.stringify(tasks));
localStorage.setItem('categories', JSON.stringify(categories));
localStorage.setItem('workspaces', JSON.stringify(workspaces));
localStorage.setItem('pomodoroSessions', pomodoroSessions.toString());
localStorage.setItem('pomodoroTodayMinutes', pomodoroTodayMinutes.toString());
}
function loadFromLocalStorage() {
try {
tasks = JSON.parse(localStorage.getItem('tasks')) || [];
categories = JSON.parse(localStorage.getItem('categories')) || [];
workspaces = JSON.parse(localStorage.getItem('workspaces')) || [];
pomodoroSessions = parseInt(localStorage.getItem('pomodoroSessions')) || 0;
pomodoroTodayMinutes = parseInt(localStorage.getItem('pomodoroTodayMinutes')) || 0;
const savedUserName = localStorage.getItem('userName');
if (savedUserName) {
document.getElementById('user-name').textContent = savedUserName;
document.getElementById('user-name-input').value = savedUserName;
}
} catch (e) {
tasks = [];
categories = [];
workspaces = [];
}
}
// ===== Streak =====
function loadStreak() {
const lastDate = localStorage.getItem('lastCompleteDate');
const today = new Date().toISOString().split('T')[0];
if (lastDate === today) {
streakCount = parseInt(localStorage.getItem('streakCount')) || 0;
} else if (lastDate === getYesterday()) {
streakCount = parseInt(localStorage.getItem('streakCount')) || 0;
} else {
streakCount = 0;
localStorage.setItem('streakCount', '0');
}
updateStreakDisplay();
}
function getYesterday() {
const d = new Date();
d.setDate(d.getDate() - 1);
return d.toISOString().split('T')[0];
}
function updateStreakDisplay() {
const existing = document.querySelector('.streak-badge');
if (existing) existing.remove();
if (streakCount > 0) {
const badge = document.createElement('span');
badge.className = 'streak-badge';
badge.innerHTML = `<i class="fas fa-fire"></i> ${streakCount} day${streakCount > 1 ? 's' : ''}`;
document.querySelector('.user-info h3').after(badge);
}
}
function checkStreak() {
const today = new Date().toISOString().split('T')[0];
const lastDate = localStorage.getItem('lastCompleteDate');
if (lastDate === today) return;
if (lastDate === getYesterday()) {
streakCount++;
} else {
streakCount = 1;
}
localStorage.setItem('lastCompleteDate', today);
localStorage.setItem('streakCount', streakCount.toString());
updateStreakDisplay();
}
// ===== My Day =====
function addToMyDay(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
if (!myDayTasks.includes(taskId)) {
myDayTasks.push(taskId);
localStorage.setItem('myDayTasks', JSON.stringify(myDayTasks));
showToast('Added to My Day', 'success');
}
}
function loadMyDay() {
try {
myDayTasks = JSON.parse(localStorage.getItem('myDayTasks')) || [];
} catch { myDayTasks = []; }
}
// ===== Event Listeners =====
function initializeEventListeners() {
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', (e) => {
switchView(e.currentTarget.dataset.view);
});
});
document.getElementById('add-task-btn').addEventListener('click', () => openTaskModal());
document.getElementById('close-modal').addEventListener('click', closeTaskModal);
document.getElementById('cancel-btn').addEventListener('click', closeTaskModal);
document.getElementById('task-form').addEventListener('submit', saveTask);
document.getElementById('task-recurring').addEventListener('change', (e) => {
document.getElementById('recurring-options').style.display = e.target.checked ? 'block' : 'none';
});
document.getElementById('add-category-btn').addEventListener('click', openCategoryModal);
document.getElementById('add-workspace-btn').addEventListener('click', openWorkspaceModal);
document.getElementById('close-workspace-modal').addEventListener('click', closeWorkspaceModal);
document.getElementById('cancel-workspace-btn').addEventListener('click', closeWorkspaceModal);
document.getElementById('workspace-form').addEventListener('submit', saveWorkspace);
document.getElementById('close-category-modal').addEventListener('click', closeCategoryModal);
document.getElementById('cancel-category-btn').addEventListener('click', closeCategoryModal);
document.getElementById('category-form').addEventListener('submit', saveCategory);
document.querySelectorAll('.color-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.color-option').forEach(o => o.classList.remove('active'));
this.classList.add('active');
});
});
document.getElementById('settings-btn').addEventListener('click', openSettingsModal);
document.getElementById('close-settings-modal').addEventListener('click', closeSettingsModal);
document.getElementById('pomodoro-toggle-btn').addEventListener('click', togglePomodoroPanel);
document.getElementById('pomodoro-start').addEventListener('click', togglePomodoro);
document.getElementById('pomodoro-reset').addEventListener('click', resetPomodoro);
document.getElementById('pomodoro-skip').addEventListener('click', skipPomodoro);
document.getElementById('focus-mode-btn').addEventListener('click', toggleFocusMode);
document.getElementById('template-btn').addEventListener('click', openTemplateModal);
document.getElementById('close-template-modal').addEventListener('click', closeTemplateModal);
document.getElementById('smart-filter-btn').addEventListener('click', cycleSmartFilter);
document.getElementById('stats-btn').addEventListener('click', openStatsModal);
document.getElementById('close-stats-modal').addEventListener('click', closeStatsModal);
document.getElementById('shortcuts-btn').addEventListener('click', openShortcutsModal);
document.getElementById('close-shortcuts-modal').addEventListener('click', closeShortcutsModal);
document.getElementById('user-name-input').addEventListener('change', (e) => {
const name = e.target.value.trim() || 'Welcome Back!';
document.getElementById('user-name').textContent = name;
localStorage.setItem('userName', name);
});
document.getElementById('search-input').addEventListener('input', handleSearch);
document.getElementById('quick-add-form').addEventListener('submit', handleQuickAddSubmit);
document.getElementById('complete-visible-btn').addEventListener('click', completeVisibleTasks);
document.getElementById('clear-completed-btn').addEventListener('click', clearCompletedTasks);
document.getElementById('filter-btn').addEventListener('click', toggleFilterPanel);
document.getElementById('view-toggle').addEventListener('click', toggleViewMode);
document.getElementById('calendar-toggle').addEventListener('click', toggleCalendarView);
document.getElementById('calendar-prev').addEventListener('click', () => {
calendarMonth = new Date(calendarMonth.getFullYear(), calendarMonth.getMonth() - 1, 1);
renderCalendar();
});
document.getElementById('calendar-next').addEventListener('click', () => {
calendarMonth = new Date(calendarMonth.getFullYear(), calendarMonth.getMonth() + 1, 1);
renderCalendar();
});
document.querySelectorAll('.filter-option').forEach(option => {
option.addEventListener('click', handleFilter);
});
document.querySelectorAll('.sort-option').forEach(option => {
option.addEventListener('click', handleSort);
});
document.getElementById('add-subtask-btn').addEventListener('click', (e) => {
e.preventDefault();
addSubtaskInput();
});
document.getElementById('theme-toggle').addEventListener('click', toggleDarkMode);
document.getElementById('dark-mode-toggle').addEventListener('change', toggleDarkMode);
document.getElementById('animations-toggle').addEventListener('change', (e) => {
localStorage.setItem('animationsEnabled', e.target.checked ? 'true' : 'false');
applySettings();
});
document.getElementById('notifications-toggle').addEventListener('change', (e) => {
localStorage.setItem('notificationsEnabled', e.target.checked ? 'true' : 'false');
if (e.target.checked) checkNotifications();
});
document.getElementById('sound-toggle').addEventListener('change', (e) => {
localStorage.setItem('soundEnabled', e.target.checked ? 'true' : 'false');
});
document.getElementById('export-data-btn').addEventListener('click', exportData);
document.getElementById('import-data-btn').addEventListener('click', () => {
document.getElementById('import-file-input').click();
});
document.getElementById('import-file-input').addEventListener('change', importData);
document.getElementById('clear-data-btn').addEventListener('click', clearAllData);
document.getElementById('sidebar-toggle').addEventListener('click', () => {
document.getElementById('sidebar').classList.toggle('active');
});
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.classList.remove('active');
});
});
}
// ===== Pomodoro =====
function togglePomodoroPanel() {
const panel = document.getElementById('pomodoro-container');
panel.classList.toggle('active');
}
function togglePomodoro() {
if (pomodoroRunning) {
clearInterval(pomodoroTimer);
pomodoroRunning = false;
document.getElementById('pomodoro-start').innerHTML = '<i class="fas fa-play"></i>';
document.getElementById('pomodoro-start').classList.remove('active');
} else {
pomodoroRunning = true;
document.getElementById('pomodoro-start').innerHTML = '<i class="fas fa-pause"></i>';
document.getElementById('pomodoro-start').classList.add('active');
pomodoroTimer = setInterval(() => {
pomodoroTime--;
updatePomodoroDisplay();
if (pomodoroTime <= 0) {
clearInterval(pomodoroTimer);
pomodoroRunning = false;
document.getElementById('pomodoro-start').innerHTML = '<i class="fas fa-play"></i>';
document.getElementById('pomodoro-start').classList.remove('active');
pomodoroComplete();
}
}, 1000);
}
}
function resetPomodoro() {
clearInterval(pomodoroTimer);
pomodoroRunning = false;
pomodoroPhase = 'focus';
pomodoroTime = 25 * 60;
document.getElementById('pomodoro-start').innerHTML = '<i class="fas fa-play"></i>';
document.getElementById('pomodoro-start').classList.remove('active');
document.getElementById('pomodoro-status').textContent = 'Focus';
updatePomodoroDisplay();
}
function skipPomodoro() {
clearInterval(pomodoroTimer);
pomodoroRunning = false;
pomodoroComplete();
}
function pomodoroComplete() {
if (pomodoroPhase === 'focus') {
pomodoroSessions++;
pomodoroTodayMinutes += 25;
saveToLocalStorage();
document.getElementById('pomodoro-sessions').textContent = pomodoroSessions;
document.getElementById('pomodoro-today-min').textContent = pomodoroTodayMinutes;
if (pomodoroSessions % 4 === 0) {
pomodoroPhase = 'long-break';
pomodoroTime = 15 * 60;
showToast('Long break time! Great work! 🎉', 'success');
triggerConfetti();
} else {
pomodoroPhase = 'short-break';
pomodoroTime = 5 * 60;
showToast('Good job! Take a short break ☕', 'success');
}
document.getElementById('pomodoro-status').textContent = pomodoroPhase === 'short-break' ? 'Short Break' : 'Long Break';
} else {
pomodoroPhase = 'focus';
pomodoroTime = 25 * 60;
document.getElementById('pomodoro-status').textContent = 'Focus';
showToast('Break over! Time to focus 🎯', 'info');
}
updatePomodoroDisplay();
}
function updatePomodoroDisplay() {
const mins = Math.floor(pomodoroTime / 60);
const secs = pomodoroTime % 60;
document.getElementById('pomodoro-timer').textContent =
`${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
document.getElementById('pomodoro-sessions').textContent = pomodoroSessions;
document.getElementById('pomodoro-today-min').textContent = pomodoroTodayMinutes;
}
// ===== Stats =====
function openStatsModal() {
const total = tasks.length;
const completed = tasks.filter(t => t.completed).length;
const pending = total - completed;
document.getElementById('stats-total').textContent = total;
document.getElementById('stats-completed').textContent = completed;
document.getElementById('stats-pending').textContent = pending;
document.getElementById('stats-streak').textContent = streakCount;
renderChart();
document.getElementById('stats-modal').classList.add('active');
}
function closeStatsModal() {
document.getElementById('stats-modal').classList.remove('active');
}
function renderChart() {
const container = document.getElementById('chart-bars');
container.innerHTML = '';
const days = [];
for (let i = 6; i >= 0; i--) {
const d = new Date();
d.setDate(d.getDate() - i);
days.push(d.toISOString().split('T')[0]);
}
const maxVal = Math.max(1, ...days.map(day =>
tasks.filter(t => {
if (!t.createdAt) return false;
const taskDay = t.createdAt.split('T')[0];
return taskDay === day && t.completed;
}).length
));
days.forEach(day => {
const count = tasks.filter(t => {
if (!t.createdAt) return false;
return t.createdAt.split('T')[0] === day && t.completed;
}).length;
const bar = document.createElement('div');
bar.className = 'chart-bar';
const pct = (count / maxVal) * 100;
bar.style.height = `${Math.max(pct, 4)}%`;
const dateObj = new Date(day + 'T00:00:00');
const label = dateObj.toLocaleDateString('en-US', { weekday: 'short' });
bar.innerHTML = `
<span class="chart-bar-value">${count}</span>
<span class="chart-bar-label">${label}</span>
`;
container.appendChild(bar);
});
}
// ===== Shortcuts Modal =====
function openShortcutsModal() {
document.getElementById('shortcuts-modal').classList.add('active');
}
function closeShortcutsModal() {
document.getElementById('shortcuts-modal').classList.remove('active');
}
// ===== View Management =====
function switchView(view) {
currentView = view;
currentFilter.category = null;
currentFilter.workspace = null;
currentFilter.date = null;
document.querySelectorAll('.nav-item').forEach(item => item.classList.remove('active'));
document.querySelector(`[data-view="${view}"]`).classList.add('active');
document.querySelectorAll('.category-item').forEach(item => item.classList.remove('active'));
const titles = {
all: { title: 'All Tasks', subtitle: 'Manage your tasks efficiently' },
myday: { title: 'My Day', subtitle: 'Tasks you want to focus on today' },
today: { title: 'Today', subtitle: 'Tasks due today' },
upcoming: { title: 'Upcoming', subtitle: 'Tasks coming up' },
overdue: { title: 'Overdue', subtitle: 'Tasks that need attention' },
completed: { title: 'Completed', subtitle: 'Finished tasks' },
important: { title: 'Important', subtitle: 'High priority tasks' }
};
document.getElementById('view-title').textContent = titles[view].title;
document.getElementById('view-subtitle').textContent = titles[view].subtitle;
renderTasks();
updateProgress();
}
function toggleViewMode() {
viewMode = viewMode === 'list' ? 'grid' : 'list';
document.querySelector('#view-toggle i').className = viewMode === 'list' ? 'fas fa-th' : 'fas fa-list';
localStorage.setItem('viewMode', viewMode);
renderTasks();
}
// ===== Quick Actions =====
function setQuickAddDefaults() {
const dateInput = document.getElementById('quick-add-date');
if (dateInput) dateInput.value = new Date().toISOString().split('T')[0];
}
function handleQuickAddSubmit(e) {
e.preventDefault();
const title = document.getElementById('quick-add-title').value.trim();
if (!title) { showToast('Please enter a task title', 'error'); return; }
const parsedTask = parseNaturalLanguageTask(title);
const newTask = {
id: Date.now(),
title: parsedTask.title,
description: parsedTask.description,
dueDate: parsedTask.dueDate || document.getElementById('quick-add-date').value,
dueTime: parsedTask.dueTime || '',
priority: parsedTask.priority || 'medium',
category: parsedTask.category || '',
workspace: '',
tags: parsedTask.tags || [],
important: parsedTask.important || false,
recurring: false,
recurrence: null,
subtasks: [],
completed: false,
createdAt: new Date().toISOString()
};
tasks.unshift(newTask);
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
showToast('Task added!', 'success');
playSound('create');
document.getElementById('quick-add-form').reset();
setQuickAddDefaults();
}
function parseNaturalLanguageTask(input) {
const text = input.toLowerCase();
const result = { title: input.trim(), description: '', dueDate: '', dueTime: '', priority: '', category: '', tags: [], important: false };
if (text.includes('urgent') || text.includes('asap')) {
result.priority = 'high'; result.important = true;
} else if (text.includes('later')) {
result.priority = 'low';
} else if (text.includes('important')) {
result.priority = 'high'; result.important = true;
}
const today = new Date();
const tomorrow = new Date(today); tomorrow.setDate(today.getDate() + 1);
const nextWeek = new Date(today); nextWeek.setDate(today.getDate() + 7);
if (text.includes('today')) result.dueDate = today.toISOString().split('T')[0];
else if (text.includes('tomorrow')) result.dueDate = tomorrow.toISOString().split('T')[0];
else if (text.includes('week')) result.dueDate = nextWeek.toISOString().split('T')[0];
if (text.includes('work')) { result.category = 'work'; result.tags.push('work'); }
else if (text.includes('personal')) { result.category = 'personal'; result.tags.push('personal'); }
const match = input.match(/@([a-zA-Z0-9-_]+)/);
if (match) result.tags.push(match[1]);
return result;
}
function completeVisibleTasks() {
const visibleTasks = getFilteredTasks().filter(task => !task.completed);
if (visibleTasks.length === 0) { showToast('No visible tasks to complete', 'info'); return; }
visibleTasks.forEach(task => { task.completed = true; });
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
showToast(`Completed ${visibleTasks.length} task${visibleTasks.length !== 1 ? 's' : ''}`, 'success');
}
function toggleFocusMode() {
focusModeEnabled = !focusModeEnabled;
document.body.classList.toggle('focus-mode', focusModeEnabled);
document.getElementById('focus-mode-btn').innerHTML =
focusModeEnabled ? '<i class="fas fa-bullseye"></i> Focus On' : '<i class="fas fa-bullseye"></i> Focus Mode';
showToast(focusModeEnabled ? 'Focus mode enabled' : 'Focus mode disabled', 'info');
}
function openTemplateModal() {
const list = document.getElementById('template-list');
list.innerHTML = '';
const templates = [
{ name: 'Deep Work', tasks: ['Block 90 minutes for focused work', 'Close all distractions', 'Review progress'] },
{ name: 'Weekly Review', tasks: ['Review completed tasks', 'Plan next week', 'Update notes'] },
{ name: 'Trip Prep', tasks: ['Pack passport', 'Check travel documents', 'Confirm bookings'] },
{ name: 'Daily Standup', tasks: ['What I did yesterday', 'What I will do today', 'Any blockers'] }
];
templates.forEach(template => {
const card = document.createElement('div');
card.className = 'template-card';
card.innerHTML = `<strong>${template.name}</strong><div>${template.tasks.join('<br>')}</div><button class="btn btn-primary btn-sm" data-template='${JSON.stringify(template.tasks)}'>Apply</button>`;
list.appendChild(card);
});
list.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
const tasksToAdd = JSON.parse(btn.dataset.template);
tasksToAdd.forEach(taskText => {
const parsed = parseNaturalLanguageTask(taskText);
tasks.unshift({
id: Date.now() + Math.floor(Math.random() * 9999),
title: parsed.title,
description: parsed.description,
dueDate: parsed.dueDate || '',
dueTime: parsed.dueTime || '',
priority: parsed.priority || 'medium',
category: parsed.category || '',
workspace: '',
reminder: '',
tags: parsed.tags || [],
important: parsed.important || false,
recurring: false,
recurrence: null,
subtasks: [],
completed: false,
createdAt: new Date().toISOString()
});
});
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
closeTemplateModal();
showToast('Template applied!', 'success');
});
});
document.getElementById('template-modal').classList.add('active');
}
function closeTemplateModal() {
document.getElementById('template-modal').classList.remove('active');
}
function cycleSmartFilter() {
const filters = ['all', 'high', 'today', 'important'];
const index = filters.indexOf(smartFilter);
smartFilter = filters[(index + 1) % filters.length];
showToast(`Smart filter: ${smartFilter}`, 'info');
renderTasks();
}
function clearCompletedTasks() {
const completedCount = tasks.filter(task => task.completed).length;
if (completedCount === 0) { showToast('No completed tasks to clear', 'info'); return; }
tasks = tasks.filter(task => !task.completed);
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
showToast('Completed tasks cleared', 'success');
}
function updateInsights() {
const today = new Date().toISOString().split('T')[0];
const activeTasks = tasks.filter(task => !task.completed).length;
const overdueTasks = tasks.filter(task => task.dueDate && task.dueDate < today && !task.completed).length;
const dueSoonTasks = tasks.filter(task => {
if (!task.dueDate || task.completed) return false;
const diffDays = Math.round((new Date(`${task.dueDate}T00:00:00`) - new Date(`${today}T00:00:00`)) / (86400000));
return diffDays >= 0 && diffDays <= 2;
}).length;
document.getElementById('insight-active').textContent = `${activeTasks} task${activeTasks !== 1 ? 's' : ''}`;
document.getElementById('insight-overdue').textContent = `${overdueTasks} task${overdueTasks !== 1 ? 's' : ''}`;
document.getElementById('insight-soon').textContent = `${dueSoonTasks} task${dueSoonTasks !== 1 ? 's' : ''}`;
}
function getTaskDueState(task) {
if (task.completed || !task.dueDate) return { className: '', label: '' };
const today = new Date();
const todayStart = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const dueDate = new Date(`${task.dueDate}T00:00:00`);
const diffDays = Math.round((dueDate - todayStart) / 86400000);
if (diffDays < 0) return { className: 'overdue', label: 'Overdue' };
if (diffDays <= 2) return { className: 'due-soon', label: diffDays === 0 ? 'Due today' : `${diffDays} day${diffDays === 1 ? '' : 's'} left` };
return { className: '', label: '' };
}
// ===== Task Management =====
function openTaskModal(taskId = null) {
const modal = document.getElementById('task-modal');
const form = document.getElementById('task-form');
const modalTitle = document.getElementById('modal-title');
form.reset();
document.getElementById('subtasks-container').innerHTML = '';
document.getElementById('recurring-options').style.display = 'none';
if (taskId) {
editingTaskId = taskId;
const task = tasks.find(t => t.id === taskId);
if (!task) return;
modalTitle.textContent = 'Edit Task';
document.getElementById('task-title').value = task.title;
document.getElementById('task-description').value = task.description || '';
document.getElementById('task-date').value = task.dueDate || '';
document.getElementById('task-time').value = task.dueTime || '';
document.getElementById('task-priority').value = task.priority;
document.getElementById('task-category').value = task.category || '';
document.getElementById('task-workspace').value = task.workspace || '';
document.getElementById('task-reminder').value = task.reminder || '';
document.getElementById('task-tags').value = task.tags ? task.tags.join(', ') : '';
document.getElementById('task-important').checked = task.important || false;
document.getElementById('task-recurring').checked = task.recurring || false;
if (task.recurring) {
document.getElementById('recurring-options').style.display = 'block';
document.getElementById('task-recurrence').value = task.recurrence || 'daily';
}
if (task.subtasks && task.subtasks.length > 0) {
task.subtasks.forEach(subtask => addSubtaskInput(subtask.text));
}
} else {
editingTaskId = null;
modalTitle.textContent = 'Add New Task';
}
updateCategorySelect();
updateWorkspaceSelect();
modal.classList.add('active');
}
function closeTaskModal() {
document.getElementById('task-modal').classList.remove('active');
editingTaskId = null;
}
function saveTask(e) {
e.preventDefault();
const title = document.getElementById('task-title').value.trim();
if (!title) { showToast('Please enter a task title', 'error'); return; }
const taskData = {
title,
description: document.getElementById('task-description').value.trim(),
dueDate: document.getElementById('task-date').value,
dueTime: document.getElementById('task-time').value,
priority: document.getElementById('task-priority').value,
category: document.getElementById('task-category').value,
workspace: document.getElementById('task-workspace').value,
reminder: document.getElementById('task-reminder').value,
tags: document.getElementById('task-tags').value.trim().split(',').map(t => t.trim()).filter(Boolean),
important: document.getElementById('task-important').checked,
recurring: document.getElementById('task-recurring').checked,
recurrence: document.getElementById('task-recurring').checked ? document.getElementById('task-recurrence').value : null,
subtasks: [],
completed: false
};
document.querySelectorAll('.subtask-item input[type="text"]').forEach(input => {
if (input.value.trim()) {
taskData.subtasks.push({ id: Date.now() + Math.floor(Math.random() * 9999), text: input.value.trim(), completed: false });
}
});
if (editingTaskId) {
const taskIndex = tasks.findIndex(t => t.id === editingTaskId);
if (taskIndex !== -1) {
tasks[taskIndex] = { ...tasks[taskIndex], ...taskData };
showToast('Task updated!', 'success');
}
} else {
tasks.unshift({ id: Date.now(), createdAt: new Date().toISOString(), ...taskData });
showToast('Task created!', 'success');
playSound('create');
}
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
closeTaskModal();
}
function deleteTask(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
lastDeletedTask = { task: { ...task }, index: tasks.findIndex(t => t.id === taskId) };
tasks = tasks.filter(t => t.id !== taskId);
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
showToast('Task deleted!', 'success', 'Undo', restoreLastDeletedTask);
playSound('delete');
}
function restoreLastDeletedTask() {
if (!lastDeletedTask) return;
const { task, index } = lastDeletedTask;
if (index >= 0) tasks.splice(index, 0, task);
else tasks.unshift(task);
lastDeletedTask = null;
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
showToast('Task restored!', 'info');
}
function toggleTaskComplete(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
task.completed = !task.completed;
if (task.completed) {
showToast('Task completed! 🎉', 'success');
playSound('complete');
triggerConfetti();
checkStreak();
if (task.recurring && task.recurrence) createRecurringTask(task);
} else {
showToast('Task reopened!', 'info');
}
saveToLocalStorage();
renderTasks();
updateCounts();
updateProgress();
updateInsights();
renderCategories();
updateUserStats();
}
function createRecurringTask(originalTask) {
const newTask = { ...originalTask };
newTask.id = Date.now();
newTask.completed = false;
newTask.createdAt = new Date().toISOString();
if (newTask.dueDate) {
const currentDate = new Date(newTask.dueDate);
if (newTask.recurrence === 'daily') currentDate.setDate(currentDate.getDate() + 1);
else if (newTask.recurrence === 'weekly') currentDate.setDate(currentDate.getDate() + 7);
else if (newTask.recurrence === 'monthly') currentDate.setMonth(currentDate.getMonth() + 1);
newTask.dueDate = currentDate.toISOString().split('T')[0];
}
tasks.unshift(newTask);
saveToLocalStorage();
}
function toggleTaskImportant(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
task.important = !task.important;
saveToLocalStorage();
renderTasks();
updateCounts();
updateInsights();
showToast(task.important ? 'Marked as important!' : 'Removed from important!', 'info');
}
function toggleSubtask(taskId, subtaskId) {
const task = tasks.find(t => t.id === taskId);
if (!task || !task.subtasks) return;
const subtask = task.subtasks.find(s => s.id === subtaskId);
if (!subtask) return;
subtask.completed = !subtask.completed;
saveToLocalStorage();
renderTasks();
}
// ===== Render =====
function renderTasks() {
const container = document.getElementById('tasks-container');
const emptyState = document.getElementById('empty-state');
if (!container || !emptyState) return;
container.classList.remove('list-view', 'grid-view');
container.classList.add(viewMode === 'grid' ? 'grid-view' : 'list-view');
let filteredTasks = getFilteredTasks();
if (filteredTasks.length === 0) {
emptyState.style.display = 'flex';
container.querySelectorAll('.task-card').forEach(card => card.remove());
if (sortableInstance) { sortableInstance.destroy(); sortableInstance = null; }
return;
}
emptyState.style.display = 'none';
container.innerHTML = '';
filteredTasks.forEach(task => {
container.appendChild(createTaskCard(task));
});
if (currentView === 'all' && !sortableInstance) {
sortableInstance = Sortable.create(container, {
animation: 200,
ghostClass: 'dragging',
onEnd: function(evt) {
const movedTask = filteredTasks[evt.oldIndex];
filteredTasks.splice(evt.oldIndex, 1);
filteredTasks.splice(evt.newIndex, 0, movedTask);
const allTaskIds = filteredTasks.map(t => t.id);
tasks.sort((a, b) => {
const ai = allTaskIds.indexOf(a.id);
const bi = allTaskIds.indexOf(b.id);
if (ai === -1) return 1;
if (bi === -1) return -1;
return ai - bi;
});
saveToLocalStorage();
}
});
} else if (currentView !== 'all' && sortableInstance) {
sortableInstance.destroy();
sortableInstance = null;
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function createTaskCard(task) {
const card = document.createElement('div');
const dueState = getTaskDueState(task);
card.className = `task-card ${task.completed ? 'completed' : ''} ${dueState.className}`;
const category = categories.find(c => c.id === task.category);
const workspace = workspaces.find(w => w.id === task.workspace);
const categoryBadge = category ?
`<span class="task-category-badge" style="background:${category.color}15;color:${category.color}"><i class="fas ${category.icon}"></i>${escapeHtml(category.name)}</span>` : '';
const tagsHtml = task.tags && task.tags.length > 0 ?
`<div class="task-tags">${task.tags.map(tag => `<span class="task-tag">${escapeHtml(tag)}</span>`).join('')}</div>` : '';
const dueDateHtml = task.dueDate ?
`<div class="task-meta-item"><i class="fas fa-calendar"></i><span>${formatDate(task.dueDate)}</span></div>` : '';
const dueTimeHtml = task.dueTime ?
`<div class="task-meta-item"><i class="fas fa-clock"></i><span>${task.dueTime}</span></div>` : '';
const workspaceBadge = workspace ?
`<div class="task-meta-item"><i class="fas fa-folder"></i><span>${escapeHtml(workspace.name)}</span></div>` : '';
const recurringBadge = task.recurring ?
`<div class="task-meta-item"><i class="fas fa-redo"></i><span>${task.recurrence}</span></div>` : '';
const dueStateBadge = dueState.label ?
`<div class="task-meta-item due-state ${dueState.className}"><i class="fas fa-bolt"></i><span>${escapeHtml(dueState.label)}</span></div>` : '';
let subtasksHtml = '';
if (task.subtasks && task.subtasks.length > 0) {
const completedSubtasks = task.subtasks.filter(s => s.completed).length;
const subtasksItems = task.subtasks.map(subtask => {
const checked = subtask.completed ? 'checked' : '';
return `<div class="task-subtask"><input type="checkbox" ${checked} data-task-id="${task.id}" data-subtask-id="${subtask.id}" class="subtask-checkbox"><span style="${subtask.completed ? 'text-decoration:line-through' : ''}">${escapeHtml(subtask.text)}</span></div>`;
}).join('');
subtasksHtml = `<div class="task-subtasks"><div style="font-size:12px;color:var(--text-tertiary);margin-bottom:8px">${completedSubtasks}/${task.subtasks.length} subtasks</div>${subtasksItems}</div>`;
}
const isMyDay = myDayTasks.includes(task.id);
card.innerHTML = `
<div class="task-header">
<div class="task-checkbox ${task.completed ? 'checked' : ''}" data-task-id="${task.id}"></div>
<div class="task-content">
<h3 class="task-title">${escapeHtml(task.title)}</h3>
${task.description ? `<p class="task-description">${escapeHtml(task.description)}</p>` : ''}
<div class="task-meta">
<span class="task-priority ${task.priority}">${task.priority}</span>
${categoryBadge}
${dueDateHtml}
${dueTimeHtml}
${workspaceBadge}
${recurringBadge}
${dueStateBadge}
${isMyDay ? '<span class="task-priority" style="background:rgba(245,158,11,0.1);color:var(--warning);font-size:10px"><i class="fas fa-sun"></i> My Day</span>' : ''}
</div>
${tagsHtml}
${subtasksHtml}
</div>
<div class="task-actions">
<button class="task-action-btn" data-task-id="${task.id}" data-action="myday" title="Add to My Day"><i class="fas fa-sun"></i></button>
<button class="task-action-btn ${task.important ? 'important' : ''}" data-task-id="${task.id}" data-action="important" title="Important"><i class="fas fa-star"></i></button>
<button class="task-action-btn" data-task-id="${task.id}" data-action="edit" title="Edit"><i class="fas fa-edit"></i></button>
<button class="task-action-btn delete" data-task-id="${task.id}" data-action="delete" title="Delete"><i class="fas fa-trash"></i></button>
</div>
</div>
`;
card.querySelector('.task-checkbox').addEventListener('click', function() {
toggleTaskComplete(Number(this.dataset.taskId));
});
card.querySelectorAll('.task-action-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const action = btn.dataset.action;
const taskId = Number(btn.dataset.taskId);
if (action === 'important') toggleTaskImportant(taskId);
else if (action === 'edit') openTaskModal(taskId);
else if (action === 'delete') deleteTask(taskId);
else if (action === 'myday') addToMyDay(taskId);
});
});
card.querySelectorAll('.subtask-checkbox').forEach(cb => {
cb.addEventListener('change', (e) => {
toggleSubtask(Number(e.target.dataset.taskId), parseFloat(e.target.dataset.subtaskId));
});
});
return card;
}
function getFilteredTasks() {
let filtered = [...tasks];
const today = new Date().toISOString().split('T')[0];
if (smartFilter === 'high') filtered = filtered.filter(task => task.priority === 'high' || task.important);
else if (smartFilter === 'today') filtered = filtered.filter(task => task.dueDate === today || !task.dueDate);
else if (smartFilter === 'important') filtered = filtered.filter(task => task.important);
loadMyDay();
switch (currentView) {
case 'myday':
filtered = filtered.filter(t => myDayTasks.includes(t.id));
break;
case 'today':
filtered = filtered.filter(t => t.dueDate === today && !t.completed);
break;
case 'upcoming':
filtered = filtered.filter(t => t.dueDate && t.dueDate > today && !t.completed);
break;
case 'overdue':
filtered = filtered.filter(t => t.dueDate && t.dueDate < today && !t.completed);
break;
case 'completed':
filtered = filtered.filter(t => t.completed);
break;
case 'important':
filtered = filtered.filter(t => t.important && !t.completed);
break;
}
if (currentFilter.priority !== 'all') filtered = filtered.filter(t => t.priority === currentFilter.priority);
if (currentFilter.category) filtered = filtered.filter(t => t.category === currentFilter.category);
if (currentFilter.workspace) filtered = filtered.filter(t => t.workspace === currentFilter.workspace);
if (currentFilter.date) filtered = filtered.filter(t => t.dueDate === currentFilter.date);
if (currentFilter.search) {
const search = currentFilter.search.toLowerCase();
filtered = filtered.filter(t =>
t.title.toLowerCase().includes(search) ||
(t.description && t.description.toLowerCase().includes(search)) ||
(t.tags && t.tags.some(tag => tag.toLowerCase().includes(search)))
);
}
filtered.sort((a, b) => {
switch (currentSort) {
case 'date-desc': return new Date(b.createdAt) - new Date(a.createdAt);
case 'date-asc': return new Date(a.createdAt) - new Date(b.createdAt);
case 'priority': return ({ high: 3, medium: 2, low: 1 })[b.priority] - ({ high: 3, medium: 2, low: 1 })[a.priority];
case 'alphabetical': return a.title.localeCompare(b.title);
case 'duedate':
if (!a.dueDate) return 1;
if (!b.dueDate) return -1;
return new Date(a.dueDate) - new Date(b.dueDate);
default: return 0;
}
});
return filtered;
}
// ===== Categories =====
function openCategoryModal() {
document.getElementById('category-modal').classList.add('active');
}
function closeCategoryModal() {
document.getElementById('category-modal').classList.remove('active');
document.getElementById('category-form').reset();
document.querySelectorAll('.color-option').forEach(o => o.classList.remove('active'));
document.querySelector('.color-option').classList.add('active');
}
function saveCategory(e) {
e.preventDefault();
const name = document.getElementById('category-name').value.trim();
if (!name) { showToast('Please enter a category name', 'error'); return; }
categories.push({
id: Date.now().toString(),
name,
icon: document.getElementById('category-icon').value,
color: document.querySelector('.color-option.active').dataset.color
});
saveToLocalStorage();
renderCategories();
updateCategorySelect();
closeCategoryModal();
showToast('Category created!', 'success');
}
function renderCategories() {
const container = document.getElementById('categories-list');
container.innerHTML = '';
categories.forEach(category => {
const item = document.createElement('button');
item.className = 'category-item';
item.dataset.categoryId = category.id;
item.addEventListener('click', function() { filterByCategory(category.id, this); });
const count = tasks.filter(t => t.category === category.id && !t.completed).length;
item.innerHTML = `<div class="category-icon" style="background:${category.color}15;color:${category.color}"><i class="fas ${category.icon}"></i></div><span>${escapeHtml(category.name)}</span><span class="count">${count}</span>`;
container.appendChild(item);
});
}
function filterByCategory(categoryId, element) {
currentFilter.category = categoryId;
document.querySelectorAll('.category-item').forEach(item => item.classList.remove('active'));
if (element) element.classList.add('active');
renderTasks();
}
function renderWorkspaces() {
const container = document.getElementById('workspaces-list');
container.innerHTML = '';
workspaces.forEach(workspace => {
const item = document.createElement('button');
item.className = 'category-item';
item.innerHTML = `<span>${escapeHtml(workspace.name)}</span><span class="count">${tasks.filter(t => t.workspace === workspace.id).length}</span>`;
item.addEventListener('click', () => {
currentFilter.workspace = workspace.id;
currentFilter.category = null;
document.querySelectorAll('.category-item').forEach(el => el.classList.remove('active'));
renderTasks();
});
container.appendChild(item);
});
}
function openWorkspaceModal() { document.getElementById('workspace-modal').classList.add('active'); }
function closeWorkspaceModal() { document.getElementById('workspace-modal').classList.remove('active'); document.getElementById('workspace-form').reset(); }
function saveWorkspace(e) {
e.preventDefault();
const name = document.getElementById('workspace-name').value.trim();
if (!name) return;
workspaces.push({ id: Date.now().toString(), name });
saveToLocalStorage();
renderWorkspaces();
updateWorkspaceSelect();
closeWorkspaceModal();
showToast('Workspace created!', 'success');
}
function updateWorkspaceSelect() {
const select = document.getElementById('task-workspace');
const currentValue = select.value;
select.innerHTML = '<option value="">No Workspace</option>';
workspaces.forEach(w => {
const o = document.createElement('option');
o.value = w.id; o.textContent = w.name; select.appendChild(o);
});
if (currentValue) select.value = currentValue;
}
function updateCategorySelect() {
const select = document.getElementById('task-category');
const currentValue = select.value;
select.innerHTML = '<option value="">No Category</option>';
categories.forEach(c => {
const o = document.createElement('option');
o.value = c.id; o.textContent = c.name; select.appendChild(o);
});
if (currentValue) select.value = currentValue;
}
// ===== Filter & Sort =====
function toggleFilterPanel() { document.getElementById('filter-panel').classList.toggle('active'); }
function handleFilter(e) {
const filterType = e.currentTarget.dataset.filter;
const filterValue = e.currentTarget.dataset.value;
currentFilter[filterType] = filterValue;
e.currentTarget.parentElement.querySelectorAll('.filter-option').forEach(o => o.classList.remove('active'));
e.currentTarget.classList.add('active');
renderTasks();
}
function handleSort(e) {
currentSort = e.currentTarget.dataset.sort;
document.querySelectorAll('.sort-option').forEach(o => o.classList.remove('active'));
e.currentTarget.classList.add('active');
renderTasks();
}
function handleSearch(e) {
currentFilter.search = e.target.value.trim();
renderTasks();
}
// ===== Subtasks =====
function addSubtaskInput(subtaskText = '') {
const container = document.getElementById('subtasks-container');
const item = document.createElement('div');
item.className = 'subtask-item';
const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'Enter subtask';
input.value = subtaskText;
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.innerHTML = '<i class="fas fa-times"></i>';
removeBtn.addEventListener('click', () => item.remove());
item.appendChild(input);
item.appendChild(removeBtn);
container.appendChild(item);
}
// ===== Settings =====
function openSettingsModal() { document.getElementById('settings-modal').classList.add('active'); }
function closeSettingsModal() { document.getElementById('settings-modal').classList.remove('active'); }
function toggleDarkMode() {
document.body.classList.toggle('dark-mode');
const isDark = document.body.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDark ? 'enabled' : 'disabled');
document.getElementById('dark-mode-toggle').checked = isDark;
document.querySelector('#theme-toggle i').className = isDark ? 'fas fa-sun' : 'fas fa-moon';
}
function applySettings() {
const animationsEnabled = localStorage.getItem('animationsEnabled') !== 'false';
const notificationsEnabled = localStorage.getItem('notificationsEnabled') !== 'false';
const soundEnabled = localStorage.getItem('soundEnabled') !== 'false';
const savedViewMode = localStorage.getItem('viewMode');
document.body.classList.toggle('no-animations', !animationsEnabled);
document.getElementById('animations-toggle').checked = animationsEnabled;
document.getElementById('notifications-toggle').checked = notificationsEnabled;
document.getElementById('sound-toggle').checked = soundEnabled;
if (savedViewMode === 'grid' || savedViewMode === 'list') {
viewMode = savedViewMode;
const icon = document.querySelector('#view-toggle i');
if (icon) icon.className = viewMode === 'list' ? 'fas fa-th' : 'fas fa-list';
}
}
function checkDarkMode() {
if (localStorage.getItem('darkMode') === 'enabled') {
document.body.classList.add('dark-mode');
document.getElementById('dark-mode-toggle').checked = true;
document.querySelector('#theme-toggle i').className = 'fas fa-sun';
}
}
function exportData() {
const data = { tasks, categories, workspaces, exportDate: new Date().toISOString(), version: '3.0' };
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `taskmaster-backup-${Date.now()}.json`;
link.click();
URL.revokeObjectURL(url);
showToast('Data exported!', 'success');
}
function importData(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = JSON.parse(event.target.result);
if (data.tasks && data.categories) {
tasks = data.tasks;
categories = data.categories;
workspaces = data.workspaces || [];
saveToLocalStorage();
renderTasks();
renderCategories();
renderWorkspaces();
updateWorkspaceSelect();
updateCounts();
updateProgress();
updateInsights();
updateUserStats();
showToast('Data imported!', 'success');
} else {
showToast('Invalid data format!', 'error');
}
} catch (err) {
showToast('Error: ' + err.message, 'error');
}
};
reader.readAsText(file);
e.target.value = '';
}
function clearAllData() {
if (confirm('Are you sure you want to clear all data? This cannot be undone!')) {
tasks = [];
categories = [];
workspaces = [];
saveToLocalStorage();
renderTasks();
renderCategories();
updateCounts();
updateProgress();
updateInsights();
updateUserStats();
showToast('All data cleared!', 'success');
}
}
// ===== Utility =====
function toggleCalendarView() {
calendarViewVisible = !calendarViewVisible;
document.getElementById('calendar-view-panel').hidden = !calendarViewVisible;
document.getElementById('tasks-container').hidden = calendarViewVisible;
if (calendarViewVisible) renderCalendar();
}
function renderCalendar() {
const grid = document.getElementById('calendar-grid');
const label = document.getElementById('calendar-label');
label.textContent = calendarMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
const year = calendarMonth.getFullYear();
const month = calendarMonth.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const cells = [];
for (let i = 0; i < firstDay; i++) cells.push('<div class="calendar-cell muted"></div>');
for (let day = 1; day <= daysInMonth; day++) {
const dateKey = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const dayTasks = tasks.filter(task => task.dueDate === dateKey && !task.completed);
const isSelected = currentFilter.date === dateKey;
cells.push(`<div class="calendar-cell ${isSelected ? 'active' : ''}" data-date="${dateKey}"><strong>${day}</strong><div class="calendar-task-count">${dayTasks.length} task${dayTasks.length === 1 ? '' : 's'}</div></div>`);
}
grid.innerHTML = cells.join('');
grid.querySelectorAll('.calendar-cell[data-date]').forEach(cell => {
cell.addEventListener('click', () => {
currentFilter.date = cell.dataset.date;
renderCalendar();
renderTasks();
});
});
}
function updateCounts() {
const today = new Date().toISOString().split('T')[0];
loadMyDay();
document.getElementById('all-count').textContent = tasks.filter(t => !t.completed).length;
document.getElementById('myday-count').textContent = myDayTasks.length;
document.getElementById('today-count').textContent = tasks.filter(t => t.dueDate === today && !t.completed).length;
document.getElementById('upcoming-count').textContent = tasks.filter(t => t.dueDate && t.dueDate > today && !t.completed).length;
document.getElementById('overdue-count').textContent = tasks.filter(t => t.dueDate && t.dueDate < today && !t.completed).length;
document.getElementById('completed-count').textContent = tasks.filter(t => t.completed).length;
document.getElementById('important-count').textContent = tasks.filter(t => t.important && !t.completed).length;
document.getElementById('stat-completed').textContent = tasks.filter(t => t.completed).length;
document.getElementById('stat-pending').textContent = tasks.filter(t => !t.completed).length;
}
function updateProgress() {
const today = new Date().toISOString().split('T')[0];
const todayTasks = tasks.filter(t => t.dueDate === today);
const completedToday = todayTasks.filter(t => t.completed).length;
const totalToday = todayTasks.length;
const percentage = totalToday > 0 ? Math.round((completedToday / totalToday) * 100) : 0;
document.getElementById('progress-text').textContent = `${percentage}%`;
document.getElementById('progress-fill').style.width = `${percentage}%`;
}
function updateUserStats() {
const today = new Date().toISOString().split('T')[0];
const todayTasks = tasks.filter(t => t.dueDate === today && !t.completed).length;
document.getElementById('user-stats').textContent = `${todayTasks} task${todayTasks !== 1 ? 's' : ''} today`;
}
function formatDate(dateString) {
const date = new Date(dateString);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const todayStr = today.toISOString().split('T')[0];
const tomorrowStr = tomorrow.toISOString().split('T')[0];
if (dateString === todayStr) return 'Today';
else if (dateString === tomorrowStr) return 'Tomorrow';
else if (dateString < todayStr) return '⚠️ Overdue';
else return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
function showToast(message, type = 'success', actionLabel = '', actionHandler = null) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toast-message');
const toastIcon = toast.querySelector('i');
const toastAction = document.getElementById('toast-action');
toastMessage.textContent = message;
if (actionLabel && actionHandler) {
toastAction.textContent = actionLabel;
toastAction.style.display = 'inline-flex';
toastAction.onclick = actionHandler;
} else {
toastAction.textContent = '';
toastAction.style.display = 'none';
toastAction.onclick = null;
}
toastIcon.className = type === 'success' ? 'fas fa-check-circle' : type === 'error' ? 'fas fa-exclamation-circle' : 'fas fa-info-circle';
toast.style.background = type === 'success' ? 'var(--success)' : type === 'error' ? 'var(--danger)' : 'var(--info)';
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 3500);
}
function scheduleReminders() {
const now = new Date();
tasks.filter(task => task.reminder && !task.completed).forEach(task => {
const reminderTime = new Date(task.reminder);
if (reminderTime > now) {
setTimeout(() => {
if (Notification.permission === 'granted') {
new Notification('Task Reminder', { body: task.title });
}
}, reminderTime - now);
}
});
}
function playSound(type) {
if (localStorage.getItem('soundEnabled') === 'false') return;
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
if (type === 'complete') { osc.frequency.value = 800; gain.gain.value = 0.1; osc.start(); osc.stop(audioCtx.currentTime + 0.1); }
else if (type === 'create') { osc.frequency.value = 600; gain.gain.value = 0.05; osc.start(); osc.stop(audioCtx.currentTime + 0.05); }
else if (type === 'delete') { osc.frequency.value = 400; gain.gain.value = 0.05; osc.start(); osc.stop(audioCtx.currentTime + 0.05); }
} catch (e) {}
}
function triggerConfetti() {
const canvas = document.getElementById('confetti-canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const confetti = [];
const count = 60;
const colors = ['#6366f1', '#a855f7', '#ec4899', '#f59e0b', '#22c55e', '#06b6d4'];
for (let i = 0; i < count; i++) {
confetti.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height - canvas.height,
r: Math.random() * 6 + 3,
d: Math.random() * count,
color: colors[Math.floor(Math.random() * colors.length)],
tilt: Math.random() * 10 - 10,
tiltAngleIncremental: Math.random() * 0.07 + 0.05,
tiltAngle: 0
});
}
let frameCount = 0;
function draw() {
frameCount++;
ctx.clearRect(0, 0, canvas.width, canvas.height);
confetti.forEach((c, i) => {
ctx.beginPath();
ctx.lineWidth = c.r / 2;
ctx.strokeStyle = c.color;
c.tiltAngle += c.tiltAngleIncremental;
c.y += (Math.cos(c.d) + 3 + c.r / 2) / 2;
c.tilt = Math.sin(c.tiltAngle - i / 3) * 15;
ctx.moveTo(c.x + c.tilt + c.r, c.y);
ctx.lineTo(c.x + c.tilt, c.y + c.tilt + c.r);
ctx.stroke();
if (c.y > canvas.height) confetti.splice(i, 1);
});
if (confetti.length > 0 && frameCount < 150) {
requestAnimationFrame(draw);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
draw();
}
function initParticles() {
const canvas = document.getElementById('particles-canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particles = [];
const count = 40;
for (let i = 0; i < count; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
r: Math.random() * 2 + 1,
opacity: Math.random() * 0.5 + 0.1
});
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const isDark = document.body.classList.contains('dark-mode');
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) p.x = canvas.width;
if (p.x > canvas.width) p.x = 0;
if (p.y < 0) p.y = canvas.height;
if (p.y > canvas.height) p.y = 0;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = isDark ? `rgba(255,255,255,${p.opacity * 0.3})` : `rgba(99,102,241,${p.opacity * 0.3})`;
ctx.fill();
});
requestAnimationFrame(animate);
}
animate();
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
}
// ===== Keyboard =====
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'n') { e.preventDefault(); openTaskModal(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'd') { e.preventDefault(); toggleDarkMode(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'f') { e.preventDefault(); document.getElementById('search-input').focus(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'p') { e.preventDefault(); togglePomodoroPanel(); }
if ((e.ctrlKey || e.metaKey) && e.key === 'Shift' && e.key === 'F') { e.preventDefault(); toggleFocusMode(); }
if (e.key === 'Escape') {
document.querySelectorAll('.modal.active').forEach(modal => modal.classList.remove('active'));
}
});
// ===== Notifications =====
function checkNotifications() {
if (localStorage.getItem('notificationsEnabled') === 'false') return;
if (!('Notification' in window)) return;
if (Notification.permission === 'granted') {
const today = new Date().toISOString().split('T')[0];
const todayTasks = tasks.filter(t => t.dueDate === today && !t.completed);
if (todayTasks.length > 0) {
new Notification('TaskMaster Pro', {
body: `You have ${todayTasks.length} task${todayTasks.length !== 1 ? 's' : ''} due today!`
});
}
} else if (Notification.permission !== 'denied') {
Notification.requestPermission();
}
}
setTimeout(() => {
checkNotifications();
scheduleReminders();
loadMyDay();
}, 2000);