Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tasky McTaskface - Your Magical Task Manager</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: '#7C3AED', | |
| secondary: '#10B981' | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| } | |
| .task-card { | |
| transition: all 0.3s ease; | |
| } | |
| .task-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 10px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background: #c4b5fd; | |
| border-radius: 10px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb:hover { | |
| background: #8b5cf6; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-6xl"> | |
| <!-- Header --> | |
| <header class="flex flex-col md:flex-row justify-between items-center mb-10"> | |
| <div> | |
| <h1 class="text-3xl md:text-4xl font-bold text-primary">Tasky McTaskface</h1> | |
| <p class="text-gray-600 mt-1">Your magical task manager ✨</p> | |
| </div> | |
| <div class="mt-4 md:mt-0 flex items-center space-x-4"> | |
| <button id="historyBtn" class="flex items-center text-gray-600 hover:text-primary transition-colors"> | |
| <i data-feather="clock"></i> | |
| <span class="ml-2">History</span> | |
| </button> | |
| <button id="themeToggle" class="p-2 rounded-full bg-gray-200 text-gray-700 hover:bg-gray-300 transition-colors"> | |
| <i data-feather="moon"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <!-- Add Task Section --> | |
| <div class="lg:col-span-1 bg-white p-6 rounded-xl shadow-sm"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Add New Task</h2> | |
| <form id="taskForm" class="space-y-4"> | |
| <div> | |
| <label for="taskTitle" class="block text-sm font-medium text-gray-700 mb-1">Task Title</label> | |
| <input type="text" id="taskTitle" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="What needs to be done?" required> | |
| </div> | |
| <div> | |
| <label for="taskPriority" class="block text-sm font-medium text-gray-700 mb-1">Priority</label> | |
| <select id="taskPriority" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| <option value="low">Low</option> | |
| <option value="medium" selected>Medium</option> | |
| <option value="high">High</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="taskDueDate" class="block text-sm font-medium text-gray-700 mb-1">Due Date</label> | |
| <input type="date" id="taskDueDate" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"> | |
| </div> | |
| <div> | |
| <label for="taskDescription" class="block text-sm font-medium text-gray-700 mb-1">Description</label> | |
| <textarea id="taskDescription" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="Any additional details..."></textarea> | |
| </div> | |
| <button type="submit" class="w-full bg-primary hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg transition-colors flex items-center justify-center"> | |
| <i data-feather="plus" class="mr-2"></i> | |
| Add Task | |
| </button> | |
| </form> | |
| </div> | |
| <!-- Task List Section --> | |
| <div class="lg:col-span-2 space-y-6"> | |
| <div class="flex justify-between items-center"> | |
| <h2 class="text-xl font-semibold text-gray-800">Your Tasks</h2> | |
| <div class="flex space-x-2"> | |
| <button id="filterAll" class="px-3 py-1 text-xs bg-primary text-white rounded-full">All</button> | |
| <button id="filterActive" class="px-3 py-1 text-xs bg-gray-200 text-gray-700 rounded-full hover:bg-gray-300">Active</button> | |
| <button id="filterCompleted" class="px-3 py-1 text-xs bg-gray-200 text-gray-700 rounded-full hover:bg-gray-300">Completed</button> | |
| </div> | |
| </div> | |
| <div id="taskList" class="space-y-4 max-h-[600px] overflow-y-auto custom-scrollbar"> | |
| <!-- Task items will be dynamically inserted here --> | |
| <div class="task-card bg-white p-4 rounded-lg shadow-sm border-l-4 border-primary"> | |
| <div class="flex justify-between items-start"> | |
| <div class="flex items-start space-x-3"> | |
| <button class="task-checkbox mt-1"> | |
| <i data-feather="circle" class="text-gray-300 hover:text-primary"></i> | |
| </button> | |
| <div> | |
| <h3 class="font-medium text-gray-800">Example Task</h3> | |
| <p class="text-sm text-gray-500 mt-1">This is an example task description that shows how tasks will appear.</p> | |
| <div class="flex items-center mt-2 space-x-4"> | |
| <span class="text-xs px-2 py-1 bg-purple-100 text-primary rounded-full">Medium</span> | |
| <span class="text-xs text-gray-500"><i data-feather="calendar" class="inline w-3 h-3 mr-1"></i> Tomorrow</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button class="task-delete text-gray-400 hover:text-red-500"> | |
| <i data-feather="trash-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- History Modal --> | |
| <div id="historyModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden"> | |
| <div class="bg-white rounded-xl w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col"> | |
| <div class="p-6 border-b border-gray-200"> | |
| <div class="flex justify-between items-center"> | |
| <h2 class="text-xl font-semibold text-gray-800">Task History</h2> | |
| <button id="closeHistoryModal" class="text-gray-500 hover:text-gray-700"> | |
| <i data-feather="x"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="historyList" class="overflow-y-auto p-6 space-y-4"> | |
| <!-- History items will be dynamically inserted here --> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="flex justify-between"> | |
| <span class="font-medium text-gray-700 line-through">Example Completed Task</span> | |
| <span class="text-sm text-gray-500">Completed: Today</span> | |
| </div> | |
| <div class="flex items-center mt-2"> | |
| <span class="text-xs px-2 py-1 bg-green-100 text-secondary rounded-full mr-2">Medium</span> | |
| <span class="text-xs text-gray-500">Completed in 2 days</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="p-4 border-t border-gray-200 flex justify-end"> | |
| <button id="clearHistory" class="text-sm text-red-500 hover:text-red-700 flex items-center"> | |
| <i data-feather="trash-2" class="w-4 h-4 mr-1"></i> | |
| Clear History | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize Feather Icons | |
| feather.replace(); | |
| // DOM Elements | |
| const taskForm = document.getElementById('taskForm'); | |
| const taskTitle = document.getElementById('taskTitle'); | |
| const taskPriority = document.getElementById('taskPriority'); | |
| const taskDueDate = document.getElementById('taskDueDate'); | |
| const taskDescription = document.getElementById('taskDescription'); | |
| const taskList = document.getElementById('taskList'); | |
| const historyBtn = document.getElementById('historyBtn'); | |
| const historyModal = document.getElementById('historyModal'); | |
| const closeHistoryModal = document.getElementById('closeHistoryModal'); | |
| const historyList = document.getElementById('historyList'); | |
| const clearHistory = document.getElementById('clearHistory'); | |
| const themeToggle = document.getElementById('themeToggle'); | |
| const filterAll = document.getElementById('filterAll'); | |
| const filterActive = document.getElementById('filterActive'); | |
| const filterCompleted = document.getElementById('filterCompleted'); | |
| // Sample tasks data | |
| let tasks = [ | |
| { | |
| id: 1, | |
| title: 'Complete project proposal', | |
| description: 'Finish the draft and send for review', | |
| priority: 'high', | |
| dueDate: '2023-05-20', | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }, | |
| { | |
| id: 2, | |
| title: 'Buy groceries', | |
| description: 'Milk, eggs, bread, fruits', | |
| priority: 'medium', | |
| dueDate: '2023-05-18', | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| } | |
| ]; | |
| let completedTasks = [ | |
| { | |
| id: 3, | |
| title: 'Call mom', | |
| description: 'Wish her happy birthday', | |
| priority: 'high', | |
| dueDate: '2023-05-15', | |
| completed: true, | |
| completedAt: new Date().toISOString(), | |
| createdAt: new Date('2023-05-10').toISOString() | |
| } | |
| ]; | |
| // Theme management | |
| function toggleTheme() { | |
| document.documentElement.classList.toggle('dark'); | |
| const isDark = document.documentElement.classList.contains('dark'); | |
| localStorage.setItem('darkMode', isDark); | |
| feather.replace(); | |
| } | |
| // Initialize theme | |
| if (localStorage.getItem('darkMode') === 'true') { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| themeToggle.addEventListener('click', toggleTheme); | |
| // Task rendering functions | |
| function renderTasks(filter = 'all') { | |
| taskList.innerHTML = ''; | |
| const filteredTasks = tasks.filter(task => { | |
| if (filter === 'active') return !task.completed; | |
| if (filter === 'completed') return task.completed; | |
| return true; | |
| }); | |
| if (filteredTasks.length === 0) { | |
| taskList.innerHTML = ` | |
| <div class="text-center py-8 text-gray-500"> | |
| <i data-feather="inbox" class="w-12 h-12 mx-auto text-gray-300"></i> | |
| <p class="mt-2">No tasks found</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| return; | |
| } | |
| filteredTasks.forEach(task => { | |
| const taskElement = document.createElement('div'); | |
| taskElement.className = `task-card bg-white p-4 rounded-lg shadow-sm border-l-4 ${task.priority === 'high' ? 'border-red-500' : task.priority === 'medium' ? 'border-primary' : 'border-blue-500'}`; | |
| const priorityColors = { | |
| high: 'bg-red-100 text-red-800', | |
| medium: 'bg-purple-100 text-primary', | |
| low: 'bg-blue-100 text-blue-800' | |
| }; | |
| taskElement.innerHTML = ` | |
| <div class="flex justify-between items-start"> | |
| <div class="flex items-start space-x-3"> | |
| <button class="task-checkbox mt-1" data-id="${task.id}"> | |
| <i data-feather="${task.completed ? 'check-circle' : 'circle'}" class="${task.completed ? 'text-secondary' : 'text-gray-300 hover:text-primary'}"></i> | |
| </button> | |
| <div> | |
| <h3 class="font-medium text-gray-800 ${task.completed ? 'line-through text-gray-500' : ''}">${task.title}</h3> | |
| <p class="text-sm text-gray-500 mt-1 ${task.completed ? 'line-through' : ''}">${task.description}</p> | |
| <div class="flex items-center mt-2 space-x-4"> | |
| <span class="text-xs px-2 py-1 ${priorityColors[task.priority]} rounded-full">${task.priority.charAt(0).toUpperCase() + task.priority.slice(1)}</span> | |
| ${task.dueDate ? `<span class="text-xs text-gray-500"><i data-feather="calendar" class="inline w-3 h-3 mr-1"></i> ${formatDate(task.dueDate)}</span>` : ''} | |
| </div> | |
| </div> | |
| </div> | |
| <button class="task-delete text-gray-400 hover:text-red-500" data-id="${task.id}"> | |
| <i data-feather="trash-2"></i> | |
| </button> | |
| </div> | |
| `; | |
| taskList.appendChild(taskElement); | |
| }); | |
| feather.replace(); | |
| } | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| const today = new Date(); | |
| const tomorrow = new Date(today); | |
| tomorrow.setDate(tomorrow.getDate() + 1); | |
| if (date.toDateString() === today.toDateString()) { | |
| return 'Today'; | |
| } else if (date.toDateString() === tomorrow.toDateString()) { | |
| return 'Tomorrow'; | |
| } else { | |
| return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); | |
| } | |
| } | |
| // Handle form submission | |
| taskForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const newTask = { | |
| id: Date.now(), | |
| title: taskTitle.value, | |
| description: taskDescription.value, | |
| priority: taskPriority.value, | |
| dueDate: taskDueDate.value, | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }; | |
| tasks.unshift(newTask); | |
| renderTasks(); | |
| taskForm.reset(); | |
| }); | |
| // Event delegation for task actions | |
| taskList.addEventListener('click', function(e) { | |
| const target = e.target.closest('.task-checkbox') || e.target.closest('.task-delete'); | |
| if (!target) return; | |
| const taskId = parseInt(target.dataset.id); | |
| const taskIndex = tasks.findIndex(task => task.id === taskId); | |
| if (target.classList.contains('task-checkbox')) { | |
| tasks[taskIndex].completed = !tasks[taskIndex].completed; | |
| if (tasks[taskIndex].completed) { | |
| tasks[taskIndex].completedAt = new Date().toISOString(); | |
| } | |
| renderTasks(); | |
| } else if (target.classList.contains('task-delete')) { | |
| tasks.splice(taskIndex, 1); | |
| renderTasks(); | |
| } | |
| }); | |
| // Filter buttons | |
| filterAll.addEventListener('click', () => { | |
| filterAll.classList.add('bg-primary', 'text-white'); | |
| filterAll.classList.remove('bg-gray-200', 'text-gray-700'); | |
| filterActive.classList.add('bg-gray-200', 'text-gray-700'); | |
| filterActive.classList.remove('bg-primary', 'text-white'); | |
| filterCompleted.classList.add('bg-gray-200', 'text-gray-700'); | |
| filterCompleted.classList.remove('bg-primary', 'text-white'); | |
| renderTasks('all'); | |
| }); | |
| filterActive.addEventListener('click', () => { | |
| filterActive.classList.add('bg-primary', 'text-white'); | |
| filterActive.classList.remove('bg-gray-200', 'text-gray-700'); | |
| filterAll.classList.add('bg-gray-200', 'text-gray-700'); | |
| filterAll.classList.remove('bg-primary', 'text-white'); | |
| filterCompleted.classList.add('bg-gray-200', 'text-gray-700'); | |
| filterCompleted.classList.remove('bg-primary', 'text-white'); | |
| renderTasks('active'); | |
| }); | |
| filterCompleted.addEventListener('click', () => { | |
| filterCompleted.classList.add('bg-primary', 'text-white'); | |
| filterCompleted.classList.remove('bg-gray-200', 'text-gray-700'); | |
| filterAll.classList.add('bg-gray-200', 'text-gray-700'); | |
| filterAll.classList.remove('bg-primary', 'text-white'); | |
| filterActive.classList.add('bg-gray-200', 'text-gray-700'); | |
| filterActive.classList.remove('bg-primary', 'text-white'); | |
| renderTasks('completed'); | |
| }); | |
| // History modal | |
| historyBtn.addEventListener('click', () => { | |
| renderHistory(); | |
| historyModal.classList.remove('hidden'); | |
| }); | |
| closeHistoryModal.addEventListener('click', () => { | |
| historyModal.classList.add('hidden'); | |
| }); | |
| function renderHistory() { | |
| historyList.innerHTML = ''; | |
| if (completedTasks.length === 0) { | |
| historyList.innerHTML = ` | |
| <div class="text-center py-8 text-gray-500"> | |
| <i data-feather="clock" class="w-12 h-12 mx-auto text-gray-300"></i> | |
| <p class="mt-2">No completed tasks yet</p> | |
| </div> | |
| `; | |
| feather.replace(); | |
| return; | |
| } | |
| completedTasks.forEach(task => { | |
| const historyElement = document.createElement('div'); | |
| historyElement.className = 'bg-gray-50 p-4 rounded-lg'; | |
| const completedDate = new Date(task.completedAt); | |
| const createdAt = new Date(task.createdAt); | |
| const daysToComplete = Math.floor((completedDate - createdAt) / (1000 * 60 * 60 * 24)); | |
| historyElement.innerHTML = ` | |
| <div class="flex justify-between"> | |
| <span class="font-medium text-gray-700 line-through">${task.title}</span> | |
| <span class="text-sm text-gray-500">Completed: ${formatDate(task.completedAt)}</span> | |
| </div> | |
| <div class="flex items-center mt-2"> | |
| <span class="text-xs px-2 py-1 ${task.priority === 'high' ? 'bg-red-100 text-red-800' : task.priority === 'medium' ? 'bg-purple-100 text-primary' : 'bg-blue-100 text-blue-800'} rounded-full mr-2"> | |
| ${task.priority.charAt(0).toUpperCase() + task.priority.slice(1)} | |
| </span> | |
| <span class="text-xs text-gray-500">Completed in ${daysToComplete} day${daysToComplete !== 1 ? 's' : ''}</span> | |
| </div> | |
| ${task.description ? `<p class="text-sm text-gray-500 mt-2 line-through">${task.description}</p>` : ''} | |
| `; | |
| historyList.appendChild(historyElement); | |
| }); | |
| feather.replace(); | |
| } | |
| clearHistory.addEventListener('click', () => { | |
| completedTasks = []; | |
| renderHistory(); | |
| }); | |
| // Initialize | |
| renderTasks(); | |
| </script> | |
| </body> | |
| </html> |