task-manager / index.html
TKS165's picture
Add 2 files
055c69d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Task Manager</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
dark: {
100: '#1E293B',
200: '#0F172A',
300: '#0B1120',
}
}
}
}
}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.task-item:hover .task-actions {
opacity: 1;
}
.calendar-day:hover {
transform: scale(1.05);
}
.priority-high {
border-left: 4px solid #ef4444;
}
.priority-medium {
border-left: 4px solid #f59e0b;
}
.priority-low {
border-left: 4px solid #10b981;
}
.pending-task {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.calendar-day.has-tasks {
background-color: #e0e7ff;
}
.dark .calendar-day.has-tasks {
background-color: #1e40af;
}
.calendar-day.has-tasks:hover {
background-color: #c7d2fe;
}
.dark .calendar-day.has-tasks:hover {
background-color: #1e3a8a;
}
.calendar-day.today {
border: 2px solid #6366f1;
}
.task-dot {
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-block;
margin-right: 2px;
}
.dot-high {
background-color: #ef4444;
}
.dot-medium {
background-color: #f59e0b;
}
.dot-low {
background-color: #10b981;
}
.dark-mode-toggle {
transition: all 0.3s ease;
}
</style>
</head>
<body class="bg-gray-100 dark:bg-dark-200 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8 flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-indigo-800 dark:text-indigo-400">TaskMaster Pro</h1>
<p class="text-gray-600 dark:text-gray-400">Your ultimate productivity companion</p>
</div>
<button id="darkModeToggle" class="dark-mode-toggle bg-gray-200 dark:bg-dark-100 text-gray-700 dark:text-gray-200 p-2 rounded-full hover:bg-gray-300 dark:hover:bg-dark-300">
<i class="fas fa-moon dark:hidden"></i>
<i class="fas fa-sun hidden dark:block"></i>
</button>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Task Input Section -->
<div class="lg:col-span-2">
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-indigo-700 dark:text-indigo-400">Add New Task</h2>
<form id="taskForm" class="space-y-4">
<div>
<label for="taskTitle" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Task Title</label>
<input type="text" id="taskTitle" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2 border dark:bg-dark-200 dark:border-gray-600 dark:text-white" required>
</div>
<div>
<label for="taskDescription" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
<textarea id="taskDescription" rows="2" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2 border dark:bg-dark-200 dark:border-gray-600 dark:text-white"></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="taskDueDate" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Due Date</label>
<input type="date" id="taskDueDate" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2 border dark:bg-dark-200 dark:border-gray-600 dark:text-white">
</div>
<div>
<label for="taskPriority" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Priority</label>
<select id="taskPriority" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 p-2 border dark:bg-dark-200 dark:border-gray-600 dark:text-white">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
</div>
<div class="flex items-center">
<input id="pinTask" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:border-gray-600">
<label for="pinTask" class="ml-2 block text-sm text-gray-700 dark:text-gray-300">Pin this task</label>
</div>
<button type="submit" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition duration-200 flex items-center justify-center">
<i class="fas fa-plus-circle mr-2"></i> Add Task
</button>
</form>
</div>
<!-- Pinned Tasks Section -->
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-md p-6 mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-indigo-700 dark:text-indigo-400">
<i class="fas fa-thumbtack mr-2 text-red-500"></i> Pinned Tasks
</h2>
<span id="pinnedCount" class="bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200 text-xs font-medium px-2.5 py-0.5 rounded-full">0</span>
</div>
<div id="pinnedTasks" class="space-y-3">
<!-- Pinned tasks will appear here -->
<div class="text-center text-gray-500 dark:text-gray-400 py-4">
No pinned tasks yet. Pin important tasks to see them here.
</div>
</div>
</div>
<!-- All Tasks Section -->
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-indigo-700 dark:text-indigo-400">
<i class="fas fa-tasks mr-2"></i> All Tasks
</h2>
<div class="flex space-x-2">
<button id="filterAll" class="text-xs bg-indigo-600 text-white px-3 py-1 rounded-full">All</button>
<button id="filterPending" class="text-xs bg-gray-200 dark:bg-dark-200 text-gray-700 dark:text-gray-300 px-3 py-1 rounded-full">Pending</button>
<button id="filterCompleted" class="text-xs bg-gray-200 dark:bg-dark-200 text-gray-700 dark:text-gray-300 px-3 py-1 rounded-full">Completed</button>
</div>
</div>
<div id="allTasks" class="space-y-3">
<!-- All tasks will appear here -->
</div>
</div>
</div>
<!-- Calendar and Stats Section -->
<div class="lg:col-span-1">
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-md p-6 mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-indigo-700 dark:text-indigo-400">
<i class="fas fa-calendar-alt mr-2"></i> Schedule
</h2>
<div class="flex space-x-2">
<button id="prevMonth" class="text-gray-600 dark:text-gray-300 hover:text-indigo-700 dark:hover:text-indigo-400">
<i class="fas fa-chevron-left"></i>
</button>
<span id="currentMonth" class="font-medium dark:text-gray-300">June 2023</span>
<button id="nextMonth" class="text-gray-600 dark:text-gray-300 hover:text-indigo-700 dark:hover:text-indigo-400">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<div class="grid grid-cols-7 gap-1 mb-2">
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Sun</div>
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Mon</div>
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Tue</div>
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Wed</div>
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Thu</div>
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Fri</div>
<div class="text-center text-xs font-medium text-gray-500 dark:text-gray-400">Sat</div>
</div>
<div id="calendarDays" class="grid grid-cols-7 gap-1">
<!-- Calendar days will be generated here -->
</div>
</div>
<!-- Upcoming Tasks Preview -->
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-md p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-indigo-700 dark:text-indigo-400">
<i class="fas fa-calendar-day mr-2"></i> Upcoming Tasks
</h2>
<div id="upcomingTasks" class="space-y-3">
<!-- Upcoming tasks will appear here -->
</div>
</div>
<!-- Stats Section -->
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4 text-indigo-700 dark:text-indigo-400">
<i class="fas fa-chart-pie mr-2"></i> Productivity Stats
</h2>
<div class="space-y-4">
<div>
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Tasks Completed</span>
<span id="completedPercentage" class="text-sm font-medium text-indigo-700 dark:text-indigo-400">0%</span>
</div>
<div class="w-full bg-gray-200 dark:bg-dark-200 rounded-full h-2.5">
<div id="completedProgress" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
</div>
<div>
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">High Priority Tasks</span>
<span id="highPriorityCount" class="text-sm font-medium text-red-600 dark:text-red-400">0</span>
</div>
</div>
<div>
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Pending Tasks</span>
<span id="pendingTasksCount" class="text-sm font-medium text-yellow-600 dark:text-yellow-400">0</span>
</div>
</div>
<div>
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Total Tasks</span>
<span id="totalTasksCount" class="text-sm font-medium text-indigo-700 dark:text-indigo-400">0</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Task Details Modal -->
<div id="taskModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-xl p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h3 id="modalTaskTitle" class="text-xl font-semibold text-indigo-700 dark:text-indigo-400"></h3>
<button id="closeModal" class="text-gray-500 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-100">
<i class="fas fa-times"></i>
</button>
</div>
<div id="modalTaskDetails" class="space-y-3 dark:text-gray-300">
<!-- Task details will appear here -->
</div>
<div class="mt-4 flex justify-end space-x-3">
<button id="completeTaskBtn" class="bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600">
<i class="fas fa-check mr-2"></i>Complete
</button>
<button id="deleteTaskBtn" class="bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600">
<i class="fas fa-trash mr-2"></i>Delete
</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize tasks array
let tasks = [];
// Sample tasks for demonstration with random dates in current month
const currentDate = new Date();
const currentMonth = currentDate.getMonth();
const currentYear = currentDate.getFullYear();
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
function getRandomDate() {
const day = Math.floor(Math.random() * daysInMonth) + 1;
return `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}
const sampleTasks = [
{
id: Date.now() + 1,
title: 'Complete project proposal',
description: 'Finish the client proposal document and send for review',
dueDate: getRandomDate(),
priority: 'high',
pinned: true,
completed: false,
createdAt: new Date()
},
{
id: Date.now() + 2,
title: 'Team meeting',
description: 'Weekly team sync up at 10 AM',
dueDate: getRandomDate(),
priority: 'medium',
pinned: false,
completed: false,
createdAt: new Date()
},
{
id: Date.now() + 3,
title: 'Gym session',
description: 'Cardio and weight training',
dueDate: getRandomDate(),
priority: 'low',
pinned: false,
completed: true,
createdAt: new Date()
},
{
id: Date.now() + 4,
title: 'Read new book',
description: 'Atomic Habits - Chapter 5',
dueDate: getRandomDate(),
priority: 'low',
pinned: false,
completed: false,
createdAt: new Date()
},
{
id: Date.now() + 5,
title: 'Client call',
description: 'Discuss project requirements with ABC Corp',
dueDate: getRandomDate(),
priority: 'high',
pinned: true,
completed: false,
createdAt: new Date()
},
{
id: Date.now() + 6,
title: 'Submit monthly report',
description: 'Financial and progress report for management',
dueDate: getRandomDate(),
priority: 'medium',
pinned: false,
completed: false,
createdAt: new Date()
},
{
id: Date.now() + 7,
title: 'Dentist appointment',
description: 'Regular checkup at 3 PM',
dueDate: getRandomDate(),
priority: 'medium',
pinned: false,
completed: false,
createdAt: new Date()
},
{
id: Date.now() + 8,
title: 'Buy groceries',
description: 'Milk, eggs, bread, fruits',
dueDate: getRandomDate(),
priority: 'low',
pinned: false,
completed: true,
createdAt: new Date()
}
];
// Add sample tasks if no tasks exist in localStorage
if (!localStorage.getItem('tasks')) {
tasks = sampleTasks;
saveTasks();
} else {
tasks = JSON.parse(localStorage.getItem('tasks'));
}
// DOM elements
const taskForm = document.getElementById('taskForm');
const allTasksContainer = document.getElementById('allTasks');
const pinnedTasksContainer = document.getElementById('pinnedTasks');
const pinnedCount = document.getElementById('pinnedCount');
const calendarDays = document.getElementById('calendarDays');
const currentMonthEl = document.getElementById('currentMonth');
const prevMonthBtn = document.getElementById('prevMonth');
const nextMonthBtn = document.getElementById('nextMonth');
const filterAll = document.getElementById('filterAll');
const filterPending = document.getElementById('filterPending');
const filterCompleted = document.getElementById('filterCompleted');
const upcomingTasksContainer = document.getElementById('upcomingTasks');
const taskModal = document.getElementById('taskModal');
const closeModal = document.getElementById('closeModal');
const modalTaskTitle = document.getElementById('modalTaskTitle');
const modalTaskDetails = document.getElementById('modalTaskDetails');
const completeTaskBtn = document.getElementById('completeTaskBtn');
const deleteTaskBtn = document.getElementById('deleteTaskBtn');
const darkModeToggle = document.getElementById('darkModeToggle');
// Stats elements
const completedPercentage = document.getElementById('completedPercentage');
const completedProgress = document.getElementById('completedProgress');
const highPriorityCount = document.getElementById('highPriorityCount');
const pendingTasksCount = document.getElementById('pendingTasksCount');
const totalTasksCount = document.getElementById('totalTasksCount');
// Current date and calendar view
let currentViewDate = new Date();
let currentViewMonth = currentViewDate.getMonth();
let currentViewYear = currentViewDate.getFullYear();
let selectedTaskId = null;
// Initialize the app
renderAllTasks();
renderPinnedTasks();
updateStats();
renderCalendar();
renderUpcomingTasks();
// Check for saved dark mode preference
if (localStorage.getItem('darkMode') === 'enabled') {
document.documentElement.classList.add('dark');
darkModeToggle.querySelector('.fa-moon').classList.add('hidden');
darkModeToggle.querySelector('.fa-sun').classList.remove('hidden');
}
// Event listeners
taskForm.addEventListener('submit', addTask);
prevMonthBtn.addEventListener('click', () => {
currentViewMonth--;
if (currentViewMonth < 0) {
currentViewMonth = 11;
currentViewYear--;
}
renderCalendar();
});
nextMonthBtn.addEventListener('click', () => {
currentViewMonth++;
if (currentViewMonth > 11) {
currentViewMonth = 0;
currentViewYear++;
}
renderCalendar();
});
filterAll.addEventListener('click', () => {
filterAll.classList.remove('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
filterAll.classList.add('bg-indigo-600', 'text-white');
filterPending.classList.remove('bg-indigo-600', 'text-white');
filterPending.classList.add('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
filterCompleted.classList.remove('bg-indigo-600', 'text-white');
filterCompleted.classList.add('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
renderAllTasks();
});
filterPending.addEventListener('click', () => {
filterPending.classList.remove('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
filterPending.classList.add('bg-indigo-600', 'text-white');
filterAll.classList.remove('bg-indigo-600', 'text-white');
filterAll.classList.add('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
filterCompleted.classList.remove('bg-indigo-600', 'text-white');
filterCompleted.classList.add('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
renderAllTasks('pending');
});
filterCompleted.addEventListener('click', () => {
filterCompleted.classList.remove('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
filterCompleted.classList.add('bg-indigo-600', 'text-white');
filterAll.classList.remove('bg-indigo-600', 'text-white');
filterAll.classList.add('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
filterPending.classList.remove('bg-indigo-600', 'text-white');
filterPending.classList.add('bg-gray-200', 'text-gray-700', 'dark:bg-dark-200', 'dark:text-gray-300');
renderAllTasks('completed');
});
closeModal.addEventListener('click', () => {
taskModal.classList.add('hidden');
});
completeTaskBtn.addEventListener('click', () => {
if (selectedTaskId) {
toggleTaskComplete(selectedTaskId);
taskModal.classList.add('hidden');
}
});
deleteTaskBtn.addEventListener('click', () => {
if (selectedTaskId) {
deleteTask(selectedTaskId);
taskModal.classList.add('hidden');
}
});
darkModeToggle.addEventListener('click', toggleDarkMode);
// Functions
function toggleDarkMode() {
const html = document.documentElement;
html.classList.toggle('dark');
const isDark = html.classList.contains('dark');
localStorage.setItem('darkMode', isDark ? 'enabled' : 'disabled');
const moonIcon = darkModeToggle.querySelector('.fa-moon');
const sunIcon = darkModeToggle.querySelector('.fa-sun');
if (isDark) {
moonIcon.classList.add('hidden');
sunIcon.classList.remove('hidden');
} else {
moonIcon.classList.remove('hidden');
sunIcon.classList.add('hidden');
}
}
function addTask(e) {
e.preventDefault();
const title = document.getElementById('taskTitle').value;
const description = document.getElementById('taskDescription').value;
const dueDate = document.getElementById('taskDueDate').value;
const priority = document.getElementById('taskPriority').value;
const pinned = document.getElementById('pinTask').checked;
const newTask = {
id: Date.now(),
title,
description,
dueDate,
priority,
pinned,
completed: false,
createdAt: new Date()
};
tasks.push(newTask);
saveTasks();
renderAllTasks();
renderPinnedTasks();
updateStats();
renderCalendar();
renderUpcomingTasks();
// Reset form
taskForm.reset();
}
function renderAllTasks(filter = 'all') {
allTasksContainer.innerHTML = '';
let filteredTasks = [...tasks];
if (filter === 'pending') {
filteredTasks = filteredTasks.filter(task => !task.completed);
} else if (filter === 'completed') {
filteredTasks = filteredTasks.filter(task => task.completed);
}
if (filteredTasks.length === 0) {
allTasksContainer.innerHTML = `
<div class="text-center text-gray-500 dark:text-gray-400 py-4">
No tasks found. Add a new task to get started!
</div>
`;
return;
}
// Sort tasks by priority and due date
filteredTasks.sort((a, b) => {
// Sort by priority first
const priorityOrder = { high: 3, medium: 2, low: 1 };
if (priorityOrder[b.priority] !== priorityOrder[a.priority]) {
return priorityOrder[b.priority] - priorityOrder[a.priority];
}
// Then sort by due date (earlier dates first)
if (a.dueDate && b.dueDate) {
return new Date(a.dueDate) - new Date(b.dueDate);
}
return 0;
});
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
allTasksContainer.appendChild(taskElement);
});
}
function renderPinnedTasks() {
pinnedTasksContainer.innerHTML = '';
const pinnedTasks = tasks.filter(task => task.pinned && !task.completed);
if (pinnedTasks.length === 0) {
pinnedTasksContainer.innerHTML = `
<div class="text-center text-gray-500 dark:text-gray-400 py-4">
No pinned tasks yet. Pin important tasks to see them here.
</div>
`;
pinnedCount.textContent = '0';
return;
}
pinnedCount.textContent = pinnedTasks.length;
pinnedTasks.forEach(task => {
const taskElement = createTaskElement(task);
pinnedTasksContainer.appendChild(taskElement);
});
}
function renderUpcomingTasks() {
upcomingTasksContainer.innerHTML = '';
// Get today's date and 7 days from now
const today = new Date();
today.setHours(0, 0, 0, 0);
const nextWeek = new Date(today);
nextWeek.setDate(today.getDate() + 7);
// Filter tasks due in the next 7 days
const upcomingTasks = tasks.filter(task => {
if (task.completed || !task.dueDate) return false;
const dueDate = new Date(task.dueDate);
dueDate.setHours(0, 0, 0, 0);
return dueDate >= today && dueDate <= nextWeek;
});
if (upcomingTasks.length === 0) {
upcomingTasksContainer.innerHTML = `
<div class="text-center text-gray-500 dark:text-gray-400 py-4">
No upcoming tasks in the next 7 days.
</div>
`;
return;
}
// Sort by due date
upcomingTasks.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
upcomingTasks.forEach(task => {
const dueDate = new Date(task.dueDate);
const options = { weekday: 'short', month: 'short', day: 'numeric' };
const dueDateStr = dueDate.toLocaleDateString('en-US', options);
const taskElement = document.createElement('div');
taskElement.className = `bg-white dark:bg-dark-200 rounded-md shadow-sm p-3 border-l-4 priority-${task.priority} cursor-pointer hover:bg-gray-50 dark:hover:bg-dark-300`;
taskElement.dataset.id = task.id;
taskElement.addEventListener('click', () => showTaskDetails(task.id));
taskElement.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h3 class="text-sm font-medium dark:text-gray-300">${task.title}</h3>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">${dueDateStr}</p>
</div>
<span class="text-xs font-medium ${task.priority === 'high' ? 'text-red-500 dark:text-red-400' : task.priority === 'medium' ? 'text-yellow-500 dark:text-yellow-400' : 'text-green-500 dark:text-green-400'}">
${task.priority.charAt(0).toUpperCase() + task.priority.slice(1)}
</span>
</div>
`;
upcomingTasksContainer.appendChild(taskElement);
});
}
function createTaskElement(task) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const dueDate = task.dueDate ? new Date(task.dueDate) : null;
if (dueDate) dueDate.setHours(0, 0, 0, 0);
const isOverdue = dueDate && dueDate < today && !task.completed;
const isDueToday = dueDate && dueDate.getTime() === today.getTime() && !task.completed;
const taskElement = document.createElement('div');
taskElement.className = `task-item bg-white dark:bg-dark-200 rounded-md shadow-sm p-4 border-l-4 priority-${task.priority} ${isOverdue ? 'pending-task' : ''} ${task.completed ? 'opacity-70' : ''} cursor-pointer`;
taskElement.dataset.id = task.id;
taskElement.addEventListener('click', () => showTaskDetails(task.id));
let dueDateText = '';
if (task.dueDate) {
if (isDueToday) {
dueDateText = `<span class="text-red-500 font-medium">Today</span>`;
} else if (isOverdue) {
const daysOverdue = Math.floor((today - dueDate) / (1000 * 60 * 60 * 24));
dueDateText = `<span class="text-red-500 font-medium">${daysOverdue} day${daysOverdue !== 1 ? 's' : ''} overdue</span>`;
} else {
const options = { month: 'short', day: 'numeric' };
dueDateText = `<span class="text-gray-600 dark:text-gray-400">Due: ${new Date(task.dueDate).toLocaleDateString('en-US', options)}</span>`;
}
}
taskElement.innerHTML = `
<div class="flex justify-between items-start">
<div class="flex items-start">
<input type="checkbox" ${task.completed ? 'checked' : ''} class="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:border-gray-600 task-completed">
<div class="ml-3">
<h3 class="text-sm font-medium ${task.completed ? 'line-through text-gray-500 dark:text-gray-400' : 'text-gray-900 dark:text-gray-300'}">${task.title}</h3>
${task.description ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">${task.description}</p>` : ''}
${dueDateText ? `<p class="text-xs mt-1">${dueDateText}</p>` : ''}
</div>
</div>
<div class="task-actions opacity-0 transition-opacity duration-200 flex space-x-2">
<button class="task-pin text-gray-400 hover:text-yellow-500" title="${task.pinned ? 'Unpin' : 'Pin'}">
<i class="fas ${task.pinned ? 'fa-thumbtack text-yellow-500' : 'fa-thumbtack'}"></i>
</button>
<button class="task-edit text-gray-400 hover:text-blue-500" title="Edit">
<i class="fas fa-edit"></i>
</button>
<button class="task-delete text-gray-400 hover:text-red-500" title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
`;
// Add event listeners to the buttons
const completeBtn = taskElement.querySelector('.task-completed');
const pinBtn = taskElement.querySelector('.task-pin');
const editBtn = taskElement.querySelector('.task-edit');
const deleteBtn = taskElement.querySelector('.task-delete');
completeBtn.addEventListener('change', (e) => {
e.stopPropagation();
toggleTaskComplete(task.id);
});
pinBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleTaskPin(task.id);
});
editBtn.addEventListener('click', (e) => {
e.stopPropagation();
editTask(task.id);
});
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
deleteTask(task.id);
});
return taskElement;
}
function showTaskDetails(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
selectedTaskId = taskId;
modalTaskTitle.textContent = task.title;
const today = new Date();
today.setHours(0, 0, 0, 0);
const dueDate = task.dueDate ? new Date(task.dueDate) : null;
if (dueDate) dueDate.setHours(0, 0, 0, 0);
let dueDateText = 'No due date';
if (task.dueDate) {
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
dueDateText = dueDate.toLocaleDateString('en-US', options);
if (dueDate.getTime() === today.getTime()) {
dueDateText += ' (Today)';
} else if (dueDate < today) {
const daysOverdue = Math.floor((today - dueDate) / (1000 * 60 * 60 * 24));
dueDateText += ` (${daysOverdue} day${daysOverdue !== 1 ? 's' : ''} overdue)`;
}
}
modalTaskDetails.innerHTML = `
<div class="flex justify-between">
<div>
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">Status</span>
<p class="mt-1 dark:text-gray-300">${task.completed ? 'Completed' : 'Pending'}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">Priority</span>
<p class="mt-1 ${task.priority === 'high' ? 'text-red-500 dark:text-red-400' : task.priority === 'medium' ? 'text-yellow-500 dark:text-yellow-400' : 'text-green-500 dark:text-green-400'}">
${task.priority.charAt(0).toUpperCase() + task.priority.slice(1)}
</p>
</div>
</div>
<div>
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">Due Date</span>
<p class="mt-1 dark:text-gray-300">${dueDateText}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">Created</span>
<p class="mt-1 dark:text-gray-300">${new Date(task.createdAt).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}</p>
</div>
${task.description ? `
<div>
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">Description</span>
<p class="mt-1 dark:text-gray-300">${task.description}</p>
</div>
` : ''}
`;
completeTaskBtn.style.display = task.completed ? 'none' : 'block';
completeTaskBtn.textContent = task.completed ? 'Completed' : 'Mark Complete';
taskModal.classList.remove('hidden');
}
function toggleTaskComplete(taskId) {
const taskIndex = tasks.findIndex(task => task.id === taskId);
if (taskIndex !== -1) {
tasks[taskIndex].completed = !tasks[taskIndex].completed;
saveTasks();
renderAllTasks();
renderPinnedTasks();
updateStats();
renderCalendar();
renderUpcomingTasks();
}
}
function toggleTaskPin(taskId) {
const taskIndex = tasks.findIndex(task => task.id === taskId);
if (taskIndex !== -1) {
tasks[taskIndex].pinned = !tasks[taskIndex].pinned;
saveTasks();
renderAllTasks();
renderPinnedTasks();
renderUpcomingTasks();
}
}
function editTask(taskId) {
const task = tasks.find(task => task.id === taskId);
if (task) {
document.getElementById('taskTitle').value = task.title;
document.getElementById('taskDescription').value = task.description || '';
document.getElementById('taskDueDate').value = task.dueDate || '';
document.getElementById('taskPriority').value = task.priority;
document.getElementById('pinTask').checked = task.pinned;
// Remove the task from the array
tasks = tasks.filter(t => t.id !== taskId);
saveTasks();
renderAllTasks();
renderPinnedTasks();
updateStats();
renderCalendar();
renderUpcomingTasks();
// Scroll to form
document.getElementById('taskForm').scrollIntoView({ behavior: 'smooth' });
}
}
function deleteTask(taskId) {
if (confirm('Are you sure you want to delete this task?')) {
tasks = tasks.filter(task => task.id !== taskId);
saveTasks();
renderAllTasks();
renderPinnedTasks();
updateStats();
renderCalendar();
renderUpcomingTasks();
}
}
function updateStats() {
const totalTasks = tasks.length;
const completedTasks = tasks.filter(task => task.completed).length;
const highPriority = tasks.filter(task => task.priority === 'high' && !task.completed).length;
const pendingTasks = tasks.filter(task => !task.completed).length;
const completionPercentage = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
totalTasksCount.textContent = totalTasks;
completedPercentage.textContent = `${completionPercentage}%`;
completedProgress.style.width = `${completionPercentage}%`;
highPriorityCount.textContent = highPriority;
pendingTasksCount.textContent = pendingTasks;
}
function renderCalendar() {
calendarDays.innerHTML = '';
// Update month/year display
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
currentMonthEl.textContent = `${monthNames[currentViewMonth]} ${currentViewYear}`;
// Get first day of month and total days in month
const firstDay = new Date(currentViewYear, currentViewMonth, 1).getDay();
const daysInMonth = new Date(currentViewYear, currentViewMonth + 1, 0).getDate();
// Get days from previous month to display
const prevMonthDays = new Date(currentViewYear, currentViewMonth, 0).getDate();
// Create day elements for previous month
for (let i = 0; i < firstDay; i++) {
const dayElement = document.createElement('div');
dayElement.className = 'text-center text-gray-400 dark:text-gray-500 text-sm py-1';
dayElement.textContent = prevMonthDays - firstDay + i + 1;
calendarDays.appendChild(dayElement);
}
// Create current month days
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let i = 1; i <= daysInMonth; i++) {
const dayDate = new Date(currentViewYear, currentViewMonth, i);
dayDate.setHours(0, 0, 0, 0);
// Check if this day has any tasks
const dayTasks = tasks.filter(task => {
if (task.completed || !task.dueDate) return false;
const taskDueDate = new Date(task.dueDate);
taskDueDate.setHours(0, 0, 0, 0);
return taskDueDate.getTime() === dayDate.getTime();
});
const isToday = dayDate.getTime() === today.getTime();
const dayElement = document.createElement('div');
dayElement.className = `calendar-day text-center text-sm py-1 rounded-md cursor-pointer ${isToday ? 'today font-bold' : ''} ${dayTasks.length > 0 ? 'has-tasks' : ''} dark:text-gray-300`;
dayElement.textContent = i;
dayElement.dataset.date = dayDate.toISOString().split('T')[0];
if (dayTasks.length > 0) {
// Add task indicators
const indicators = document.createElement('div');
indicators.className = 'flex justify-center mt-1';
// Show up to 3 indicators
const tasksToShow = dayTasks.slice(0, 3);
tasksToShow.forEach(task => {
const dot = document.createElement('span');
dot.className = `task-dot dot-${task.priority}`;
indicators.appendChild(dot);
});
// If more than 3 tasks, show a + indicator
if (dayTasks.length > 3) {
const more = document.createElement('span');
more.className = 'text-xs text-gray-500 dark:text-gray-400';
more.textContent = `+${dayTasks.length - 3}`;
indicators.appendChild(more);
}
dayElement.appendChild(indicators);
}
dayElement.addEventListener('click', () => {
showTasksForDate(dayDate);
});
calendarDays.appendChild(dayElement);
}
// Calculate how many more days we need to complete the grid (42 cells total)
const totalCells = 42; // 6 rows x 7 days
const daysSoFar = firstDay + daysInMonth;
const remainingDays = totalCells - daysSoFar;
// Add days from next month if needed
for (let i = 1; i <= remainingDays; i++) {
const dayElement = document.createElement('div');
dayElement.className = 'text-center text-gray-400 dark:text-gray-500 text-sm py-1';
dayElement.textContent = i;
calendarDays.appendChild(dayElement);
}
}
function showTasksForDate(date) {
const tasksForDate = tasks.filter(task => {
if (!task.dueDate) return false;
const taskDueDate = new Date(task.dueDate);
taskDueDate.setHours(0, 0, 0, 0);
date.setHours(0, 0, 0, 0);
return taskDueDate.getTime() === date.getTime();
});
if (tasksForDate.length === 0) {
alert(`No tasks scheduled for ${date.toLocaleDateString()}`);
return;
}
// Create a modal or popup to show tasks for this date
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
const dateStr = date.toLocaleDateString('en-US', options);
let tasksHTML = `
<div class="mb-4">
<h3 class="text-lg font-semibold text-indigo-700 dark:text-indigo-400">Tasks for ${dateStr}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">${tasksForDate.length} task${tasksForDate.length !== 1 ? 's' : ''}</p>
</div>
<div class="space-y-3 max-h-96 overflow-y-auto">
`;
tasksForDate.forEach(task => {
tasksHTML += `
<div class="task-item bg-white dark:bg-dark-200 rounded-md shadow-sm p-3 border-l-4 priority-${task.priority} ${task.completed ? 'opacity-70' : ''}">
<div class="flex justify-between items-start">
<div class="flex items-start">
<input type="checkbox" ${task.completed ? 'checked' : ''} class="mt-1 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 dark:border-gray-600 task-completed">
<div class="ml-3">
<h3 class="text-sm font-medium ${task.completed ? 'line-through text-gray-500 dark:text-gray-400' : 'text-gray-900 dark:text-gray-300'}">${task.title}</h3>
${task.description ? `<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">${task.description}</p>` : ''}
</div>
</div>
<div class="flex space-x-2">
<button class="task-complete-btn text-gray-400 hover:text-green-500" title="Complete">
<i class="fas fa-check"></i>
</button>
<button class="task-delete-btn text-gray-400 hover:text-red-500" title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
</div>
`;
});
tasksHTML += `</div>`;
// Create a modal
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
modal.innerHTML = `
<div class="bg-white dark:bg-dark-100 rounded-lg shadow-xl p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold text-indigo-700 dark:text-indigo-400">Tasks for ${dateStr}</h3>
<button class="close-modal text-gray-500 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-100">
<i class="fas fa-times"></i>
</button>
</div>
${tasksHTML}
</div>
`;
document.body.appendChild(modal);
// Add event listeners
modal.querySelector('.close-modal').addEventListener('click', () => {
document.body.removeChild(modal);
});
// Add event listeners to task buttons
tasksForDate.forEach((task, index) => {
const taskElement = modal.querySelectorAll('.task-item')[index];
const completeBtn = taskElement.querySelector('.task-complete-btn');
const deleteBtn = taskElement.querySelector('.task-delete-btn');
const checkbox = taskElement.querySelector('.task-completed');
completeBtn.addEventListener('click', () => {
toggleTaskComplete(task.id);
document.body.removeChild(modal);
});
deleteBtn.addEventListener('click', () => {
deleteTask(task.id);
document.body.removeChild(modal);
});
checkbox.addEventListener('change', () => {
toggleTaskComplete(task.id);
document.body.removeChild(modal);
});
});
}
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=TKS165/task-manager" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>