Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Kanban Task Board</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .kanban-column { | |
| min-height: 500px; | |
| } | |
| .task-card { | |
| transition: all 0.2s ease; | |
| } | |
| .task-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| .task-card.dragging { | |
| opacity: 0.5; | |
| transform: scale(1.02); | |
| } | |
| .modal { | |
| transition: opacity 0.2s ease, transform 0.2s ease; | |
| } | |
| .modal.hidden { | |
| opacity: 0; | |
| pointer-events: none; | |
| transform: translateY(20px); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <div class="flex h-screen overflow-hidden"> | |
| <!-- Sidebar --> | |
| <div class="w-64 bg-indigo-800 text-white flex flex-col"> | |
| <div class="p-4 flex items-center justify-center border-b border-indigo-700"> | |
| <h1 class="text-2xl font-bold">TaskFlow</h1> | |
| </div> | |
| <nav class="flex-1 p-4"> | |
| <ul class="space-y-2"> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg bg-indigo-700"> | |
| <i class="fas fa-tachometer-alt mr-3"></i> | |
| <span>Overview</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-indigo-700"> | |
| <i class="fas fa-tasks mr-3"></i> | |
| <span>Tasks</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-indigo-700"> | |
| <i class="fas fa-users mr-3"></i> | |
| <span>Your Team</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-indigo-700"> | |
| <i class="fas fa-envelope mr-3"></i> | |
| <span>Messages</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-indigo-700"> | |
| <i class="fas fa-cog mr-3"></i> | |
| <span>Settings</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </nav> | |
| <div class="p-4 border-t border-indigo-700"> | |
| <div class="flex items-center"> | |
| <img src="https://randomuser.me/api/portraits/women/44.jpg" alt="User" class="w-10 h-10 rounded-full mr-3"> | |
| <div> | |
| <p class="font-medium">Jane Doe</p> | |
| <p class="text-xs text-indigo-300">Admin</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <!-- Header --> | |
| <header class="bg-white border-b border-gray-200 p-4 flex items-center justify-between"> | |
| <div> | |
| <h2 class="text-xl font-semibold">Task Board</h2> | |
| <p class="text-sm text-gray-500">Manage your tasks efficiently</p> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="relative"> | |
| <input type="text" placeholder="Search tasks..." class="pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
| <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <button id="theme-toggle" class="p-2 rounded-full hover:bg-gray-100"> | |
| <i class="fas fa-moon"></i> | |
| </button> | |
| <button class="p-2 rounded-full hover:bg-gray-100"> | |
| <i class="fas fa-bell"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Content --> | |
| <main class="flex-1 overflow-auto p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <div class="flex space-x-2"> | |
| <button class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"> | |
| <i class="fas fa-plus mr-2"></i> Create Task | |
| </button> | |
| <button class="px-4 py-2 bg-white border border-gray-300 rounded-lg hover:bg-gray-50"> | |
| <i class="fas fa-filter mr-2"></i> Filter | |
| </button> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <span class="text-sm text-gray-500">View:</span> | |
| <button class="px-3 py-1 bg-indigo-100 text-indigo-700 rounded-lg">Kanban</button> | |
| <button class="px-3 py-1 bg-white border border-gray-300 rounded-lg hover:bg-gray-50">List</button> | |
| </div> | |
| </div> | |
| <!-- Kanban Board --> | |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> | |
| <!-- To Do Column --> | |
| <div class="kanban-column bg-gray-50 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-semibold text-gray-700">To Do</h3> | |
| <span class="bg-gray-200 text-gray-700 text-xs px-2 py-1 rounded-full">5</span> | |
| </div> | |
| <div class="space-y-4" id="todo-column"> | |
| <!-- Task cards will be added here by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- In Progress Column --> | |
| <div class="kanban-column bg-gray-50 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-semibold text-gray-700">In Progress</h3> | |
| <span class="bg-blue-200 text-blue-700 text-xs px-2 py-1 rounded-full">3</span> | |
| </div> | |
| <div class="space-y-4" id="inprogress-column"> | |
| <!-- Task cards will be added here by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- Review Column --> | |
| <div class="kanban-column bg-gray-50 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-semibold text-gray-700">Review</h3> | |
| <span class="bg-yellow-200 text-yellow-700 text-xs px-2 py-1 rounded-full">2</span> | |
| </div> | |
| <div class="space-y-4" id="review-column"> | |
| <!-- Task cards will be added here by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- Done Column --> | |
| <div class="kanban-column bg-gray-50 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-semibold text-gray-700">Done</h3> | |
| <span class="bg-green-200 text-green-700 text-xs px-2 py-1 rounded-full">4</span> | |
| </div> | |
| <div class="space-y-4" id="done-column"> | |
| <!-- Task cards will be added here by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| </div> | |
| <!-- Task Modal --> | |
| <div id="task-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 modal hidden"> | |
| <div class="bg-white rounded-lg w-full max-w-md"> | |
| <div class="p-4 border-b flex justify-between items-center"> | |
| <h3 class="text-lg font-semibold" id="modal-title">Create New Task</h3> | |
| <button id="close-modal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="p-4"> | |
| <form id="task-form"> | |
| <input type="hidden" id="task-id"> | |
| <div class="mb-4"> | |
| <label for="task-title" class="block text-sm font-medium text-gray-700 mb-1">Task Title</label> | |
| <input type="text" id="task-title" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" required> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="task-description" class="block text-sm font-medium text-gray-700 mb-1">Description</label> | |
| <textarea id="task-description" rows="3" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="task-status" class="block text-sm font-medium text-gray-700 mb-1">Status</label> | |
| <select id="task-status" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
| <option value="todo">To Do</option> | |
| <option value="inprogress">In Progress</option> | |
| <option value="review">Review</option> | |
| <option value="done">Done</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="task-due" class="block text-sm font-medium text-gray-700 mb-1">Due Date</label> | |
| <input type="date" id="task-due" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
| </div> | |
| <div class="flex justify-end space-x-2"> | |
| <button type="button" id="cancel-task" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50">Cancel</button> | |
| <button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">Save Task</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Sample tasks data | |
| let tasks = [ | |
| { id: 1, title: 'Design new homepage', description: 'Create wireframes and mockups for the new homepage design', status: 'todo', due: '2023-06-15', created: '2023-05-20' }, | |
| { id: 2, title: 'Implement user authentication', description: 'Set up login and registration functionality', status: 'inprogress', due: '2023-06-10', created: '2023-05-15' }, | |
| { id: 3, title: 'Write API documentation', description: 'Document all endpoints for the REST API', status: 'review', due: '2023-06-05', created: '2023-05-10' }, | |
| { id: 4, title: 'Fix mobile menu bug', description: 'Menu doesn\'t close properly on mobile devices', status: 'done', due: '2023-05-30', created: '2023-05-01' }, | |
| { id: 5, title: 'Optimize database queries', description: 'Review and optimize slow database queries', status: 'todo', due: '2023-06-20', created: '2023-05-25' }, | |
| { id: 6, title: 'Update dependencies', description: 'Update all project dependencies to latest versions', status: 'inprogress', due: '2023-06-12', created: '2023-05-18' }, | |
| { id: 7, title: 'Create admin dashboard', description: 'Build interface for admin to manage users and content', status: 'review', due: '2023-06-08', created: '2023-05-12' }, | |
| { id: 8, title: 'Setup CI/CD pipeline', description: 'Configure continuous integration and deployment', status: 'done', due: '2023-05-28', created: '2023-04-30' }, | |
| { id: 9, title: 'Write unit tests', description: 'Add unit tests for core functionality', status: 'done', due: '2023-05-25', created: '2023-04-20' }, | |
| { id: 10, title: 'Prepare presentation', description: 'Create slides for the upcoming client meeting', status: 'todo', due: '2023-06-18', created: '2023-05-22' } | |
| ]; | |
| // DOM elements | |
| const todoColumn = document.getElementById('todo-column'); | |
| const inprogressColumn = document.getElementById('inprogress-column'); | |
| const reviewColumn = document.getElementById('review-column'); | |
| const doneColumn = document.getElementById('done-column'); | |
| const taskModal = document.getElementById('task-modal'); | |
| const taskForm = document.getElementById('task-form'); | |
| const closeModal = document.getElementById('close-modal'); | |
| const cancelTask = document.getElementById('cancel-task'); | |
| const createTaskBtn = document.querySelector('main button:first-child'); | |
| const themeToggle = document.getElementById('theme-toggle'); | |
| const modalTitle = document.getElementById('modal-title'); | |
| const taskIdInput = document.getElementById('task-id'); | |
| const taskTitleInput = document.getElementById('task-title'); | |
| const taskDescInput = document.getElementById('task-description'); | |
| const taskStatusInput = document.getElementById('task-status'); | |
| const taskDueInput = document.getElementById('task-due'); | |
| // Initialize the board | |
| function renderTasks() { | |
| // Clear all columns | |
| todoColumn.innerHTML = ''; | |
| inprogressColumn.innerHTML = ''; | |
| reviewColumn.innerHTML = ''; | |
| doneColumn.innerHTML = ''; | |
| // Render tasks in their respective columns | |
| tasks.forEach(task => { | |
| const taskCard = createTaskCard(task); | |
| switch(task.status) { | |
| case 'todo': | |
| todoColumn.appendChild(taskCard); | |
| break; | |
| case 'inprogress': | |
| inprogressColumn.appendChild(taskCard); | |
| break; | |
| case 'review': | |
| reviewColumn.appendChild(taskCard); | |
| break; | |
| case 'done': | |
| doneColumn.appendChild(taskCard); | |
| break; | |
| } | |
| }); | |
| // Update task counts | |
| updateTaskCounts(); | |
| } | |
| // Create a task card element | |
| function createTaskCard(task) { | |
| const card = document.createElement('div'); | |
| card.className = 'task-card bg-white p-4 rounded-lg shadow cursor-move border-l-4 '; | |
| card.draggable = true; | |
| card.dataset.id = task.id; | |
| // Set border color based on status | |
| switch(task.status) { | |
| case 'todo': | |
| card.classList.add('border-gray-400'); | |
| break; | |
| case 'inprogress': | |
| card.classList.add('border-blue-500'); | |
| break; | |
| case 'review': | |
| card.classList.add('border-yellow-500'); | |
| break; | |
| case 'done': | |
| card.classList.add('border-green-500'); | |
| break; | |
| } | |
| // Format due date | |
| const dueDate = new Date(task.due); | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| const isOverdue = dueDate < today && task.status !== 'done'; | |
| const dueDateStr = dueDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); | |
| card.innerHTML = ` | |
| <div class="flex justify-between items-start mb-2"> | |
| <h4 class="font-medium">${task.title}</h4> | |
| <div class="flex space-x-2"> | |
| <button class="edit-task text-gray-400 hover:text-indigo-600" data-id="${task.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete-task text-gray-400 hover:text-red-600" data-id="${task.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mb-3">${task.description}</p> | |
| <div class="flex justify-between items-center text-xs"> | |
| <span class="text-gray-500">Due: <span class="${isOverdue ? 'text-red-500 font-medium' : 'text-gray-700'}">${dueDateStr}</span></span> | |
| <span class="text-gray-500">Created: ${new Date(task.created).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</span> | |
| </div> | |
| `; | |
| // Add drag events | |
| card.addEventListener('dragstart', dragStart); | |
| card.addEventListener('dragend', dragEnd); | |
| // Add edit and delete event listeners | |
| card.querySelector('.edit-task').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| editTask(task.id); | |
| }); | |
| card.querySelector('.delete-task').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| deleteTask(task.id); | |
| }); | |
| return card; | |
| } | |
| // Update task counts in each column | |
| function updateTaskCounts() { | |
| const todoCount = document.querySelector('#todo-column + div span'); | |
| const inprogressCount = document.querySelector('#inprogress-column + div span'); | |
| const reviewCount = document.querySelector('#review-column + div span'); | |
| const doneCount = document.querySelector('#done-column + div span'); | |
| todoCount.textContent = tasks.filter(t => t.status === 'todo').length; | |
| inprogressCount.textContent = tasks.filter(t => t.status === 'inprogress').length; | |
| reviewCount.textContent = tasks.filter(t => t.status === 'review').length; | |
| doneCount.textContent = tasks.filter(t => t.status === 'done').length; | |
| } | |
| // Drag and drop functionality | |
| let draggedItem = null; | |
| function dragStart(e) { | |
| draggedItem = this; | |
| this.classList.add('dragging'); | |
| e.dataTransfer.effectAllowed = 'move'; | |
| e.dataTransfer.setData('text/html', this.innerHTML); | |
| } | |
| function dragEnd() { | |
| this.classList.remove('dragging'); | |
| } | |
| function dragOver(e) { | |
| e.preventDefault(); | |
| e.dataTransfer.dropEffect = 'move'; | |
| } | |
| function dragEnter(e) { | |
| e.preventDefault(); | |
| this.classList.add('bg-gray-100'); | |
| } | |
| function dragLeave() { | |
| this.classList.remove('bg-gray-100'); | |
| } | |
| function drop(e) { | |
| e.preventDefault(); | |
| this.classList.remove('bg-gray-100'); | |
| if (draggedItem !== this) { | |
| const taskId = parseInt(draggedItem.dataset.id); | |
| const newStatus = this.parentElement.id.split('-')[0]; | |
| // Update task status | |
| const taskIndex = tasks.findIndex(t => t.id === taskId); | |
| if (taskIndex !== -1) { | |
| tasks[taskIndex].status = newStatus; | |
| renderTasks(); | |
| } | |
| } | |
| } | |
| // Add drag and drop events to columns | |
| [todoColumn, inprogressColumn, reviewColumn, doneColumn].forEach(column => { | |
| column.addEventListener('dragover', dragOver); | |
| column.addEventListener('dragenter', dragEnter); | |
| column.addEventListener('dragleave', dragLeave); | |
| column.addEventListener('drop', drop); | |
| }); | |
| // Task CRUD operations | |
| function openModal() { | |
| taskModal.classList.remove('hidden'); | |
| } | |
| function closeModalHandler() { | |
| taskModal.classList.add('hidden'); | |
| taskForm.reset(); | |
| taskIdInput.value = ''; | |
| } | |
| function createTask() { | |
| modalTitle.textContent = 'Create New Task'; | |
| openModal(); | |
| } | |
| function editTask(id) { | |
| const task = tasks.find(t => t.id === id); | |
| if (task) { | |
| modalTitle.textContent = 'Edit Task'; | |
| taskIdInput.value = task.id; | |
| taskTitleInput.value = task.title; | |
| taskDescInput.value = task.description; | |
| taskStatusInput.value = task.status; | |
| taskDueInput.value = task.due; | |
| openModal(); | |
| } | |
| } | |
| function deleteTask(id) { | |
| if (confirm('Are you sure you want to delete this task?')) { | |
| tasks = tasks.filter(t => t.id !== id); | |
| renderTasks(); | |
| } | |
| } | |
| function saveTask(e) { | |
| e.preventDefault(); | |
| const taskData = { | |
| title: taskTitleInput.value, | |
| description: taskDescInput.value, | |
| status: taskStatusInput.value, | |
| due: taskDueInput.value, | |
| created: new Date().toISOString().split('T')[0] | |
| }; | |
| if (taskIdInput.value) { | |
| // Update existing task | |
| const taskIndex = tasks.findIndex(t => t.id === parseInt(taskIdInput.value)); | |
| if (taskIndex !== -1) { | |
| tasks[taskIndex] = { ...tasks[taskIndex], ...taskData }; | |
| } | |
| } else { | |
| // Create new task | |
| const newId = tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1; | |
| tasks.push({ id: newId, ...taskData }); | |
| } | |
| closeModalHandler(); | |
| renderTasks(); | |
| } | |
| // Theme toggle | |
| function toggleTheme() { | |
| document.documentElement.classList.toggle('dark'); | |
| const icon = themeToggle.querySelector('i'); | |
| if (document.documentElement.classList.contains('dark')) { | |
| icon.classList.replace('fa-moon', 'fa-sun'); | |
| document.body.classList.add('bg-gray-900'); | |
| document.body.classList.remove('bg-gray-100'); | |
| } else { | |
| icon.classList.replace('fa-sun', 'fa-moon'); | |
| document.body.classList.remove('bg-gray-900'); | |
| document.body.classList.add('bg-gray-100'); | |
| } | |
| } | |
| // Event listeners | |
| createTaskBtn.addEventListener('click', createTask); | |
| closeModal.addEventListener('click', closeModalHandler); | |
| cancelTask.addEventListener('click', closeModalHandler); | |
| taskForm.addEventListener('submit', saveTask); | |
| themeToggle.addEventListener('click', toggleTheme); | |
| // Initialize | |
| renderTasks(); | |
| </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=hharris928/task-manager-2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |