Spaces:
Running
Running
| <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> |