Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Task Kanban 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 4px 6px rgba(0,0,0,0.1); | |
| } | |
| .task-card.dragging { | |
| opacity: 0.5; | |
| background: #f0f4ff; | |
| } | |
| .avatar-small { | |
| width: 24px; | |
| height: 24px; | |
| } | |
| .priority-high { | |
| border-left: 4px solid #ef4444; | |
| } | |
| .priority-medium { | |
| border-left: 4px solid #f59e0b; | |
| } | |
| .priority-low { | |
| border-left: 4px solid #10b981; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <div class="min-h-screen"> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm"> | |
| <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8 flex justify-between items-center"> | |
| <h1 class="text-2xl font-bold text-gray-900"> | |
| <i class="fas fa-tasks mr-2 text-blue-500"></i> Task Kanban Board | |
| </h1> | |
| <div class="flex items-center space-x-4"> | |
| <button id="filterBtn" class="flex items-center text-gray-600 hover:text-gray-900"> | |
| <i class="fas fa-filter mr-1"></i> Filter | |
| </button> | |
| <button id="addTaskBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md flex items-center"> | |
| <i class="fas fa-plus mr-2"></i> Add Task | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Filter Panel (hidden by default) --> | |
| <div id="filterPanel" class="hidden bg-white shadow-sm p-4 mb-4"> | |
| <div class="max-w-7xl mx-auto"> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Priority</label> | |
| <select id="priorityFilter" class="w-full border border-gray-300 rounded-md p-2"> | |
| <option value="">All Priorities</option> | |
| <option value="high">High</option> | |
| <option value="medium">Medium</option> | |
| <option value="low">Low</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Assignee</label> | |
| <select id="assigneeFilter" class="w-full border border-gray-300 rounded-md p-2"> | |
| <option value="">All Assignees</option> | |
| <!-- Will be populated with employees from the database --> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Due Date</label> | |
| <select id="dueDateFilter" class="w-full border border-gray-300 rounded-md p-2"> | |
| <option value="">All Dates</option> | |
| <option value="today">Today</option> | |
| <option value="week">This Week</option> | |
| <option value="overdue">Overdue</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-end"> | |
| <button id="applyFilters" class="bg-blue-500 text-white px-4 py-2 rounded-md mr-2"> | |
| Apply Filters | |
| </button> | |
| <button id="clearFilters" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-md"> | |
| Clear All | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <main class="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8"> | |
| <!-- Kanban Board --> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <!-- To Do Column --> | |
| <div class="kanban-column bg-gray-100 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="font-semibold text-lg text-gray-800"> | |
| <i class="fas fa-circle-notch mr-2 text-gray-500"></i> To Do | |
| </h2> | |
| <span class="bg-gray-200 text-gray-700 text-xs font-medium px-2 py-1 rounded-full"> | |
| <span id="todo-count">0</span> tasks | |
| </span> | |
| </div> | |
| <div id="todo-tasks" class="space-y-3"> | |
| <!-- Tasks will be added here dynamically --> | |
| </div> | |
| </div> | |
| <!-- In Progress Column --> | |
| <div class="kanban-column bg-gray-100 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="font-semibold text-lg text-gray-800"> | |
| <i class="fas fa-spinner mr-2 text-blue-500"></i> In Progress | |
| </h2> | |
| <span class="bg-gray-200 text-gray-700 text-xs font-medium px-2 py-1 rounded-full"> | |
| <span id="inprogress-count">0</span> tasks | |
| </span> | |
| </div> | |
| <div id="inprogress-tasks" class="space-y-3"> | |
| <!-- Tasks will be added here dynamically --> | |
| </div> | |
| </div> | |
| <!-- Done Column --> | |
| <div class="kanban-column bg-gray-100 rounded-lg p-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="font-semibold text-lg text-gray-800"> | |
| <i class="fas fa-check-circle mr-2 text-green-500"></i> Done | |
| </h2> | |
| <span class="bg-gray-200 text-gray-700 text-xs font-medium px-2 py-1 rounded-full"> | |
| <span id="done-count">0</span> tasks | |
| </span> | |
| </div> | |
| <div id="done-tasks" class="space-y-3"> | |
| <!-- Tasks will be added here dynamically --> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Add/Edit Task Modal --> | |
| <div id="taskModal" class="fixed inset-0 overflow-y-auto hidden z-50"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | |
| <div class="absolute inset-0 bg-gray-500 opacity-75"></div> | |
| </div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full"> | |
| <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
| <div class="sm:flex sm:items-start"> | |
| <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full"> | |
| <div class="flex justify-between items-center"> | |
| <h3 class="text-lg leading-6 font-medium text-gray-900" id="modalTitle"> | |
| Add New Task | |
| </h3> | |
| <button id="closeModal" class="text-gray-400 hover:text-gray-500"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="mt-6"> | |
| <form id="taskForm" class="space-y-6"> | |
| <input type="hidden" id="taskId"> | |
| <div class="grid grid-cols-1 gap-6 sm:grid-cols-2"> | |
| <div class="sm:col-span-2"> | |
| <label for="taskTitle" class="block text-sm font-medium text-gray-700">Task Title*</label> | |
| <input type="text" id="taskTitle" name="taskTitle" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label for="taskPriority" class="block text-sm font-medium text-gray-700">Priority*</label> | |
| <select id="taskPriority" name="taskPriority" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="">Select priority</option> | |
| <option value="high">High</option> | |
| <option value="medium">Medium</option> | |
| <option value="low">Low</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="taskDueDate" class="block text-sm font-medium text-gray-700">Due Date</label> | |
| <input type="date" id="taskDueDate" name="taskDueDate" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| </div> | |
| <div> | |
| <label for="taskAssignee" class="block text-sm font-medium text-gray-700">Assignee</label> | |
| <select id="taskAssignee" name="taskAssignee" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="">Unassigned</option> | |
| <!-- Will be populated with employees from the database --> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="taskStatus" class="block text-sm font-medium text-gray-700">Status*</label> | |
| <select id="taskStatus" name="taskStatus" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="todo">To Do</option> | |
| <option value="inprogress">In Progress</option> | |
| <option value="done">Done</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="taskDescription" class="block text-sm font-medium text-gray-700">Description</label> | |
| <textarea id="taskDescription" name="taskDescription" rows="4" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500"></textarea> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | |
| <button type="button" id="saveTask" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-500 text-base font-medium text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Save Task | |
| </button> | |
| <button type="button" id="cancelModal" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Cancel | |
| </button> | |
| <button type="button" id="deleteTask" class="hidden mt-3 w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-500 text-base font-medium text-white hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Delete | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Sample employee data (would normally come from your employee database) | |
| const employees = [ | |
| { id: 1, name: "Sarah Johnson", avatar: "https://randomuser.me/api/portraits/women/44.jpg" }, | |
| { id: 2, name: "Michael Chen", avatar: "https://randomuser.me/api/portraits/men/32.jpg" }, | |
| { id: 3, name: "David Wilson", avatar: "https://randomuser.me/api/portraits/men/75.jpg" }, | |
| { id: 4, name: "Emily Davis", avatar: "https://randomuser.me/api/portraits/women/65.jpg" }, | |
| { id: 5, name: "James Brown", avatar: "https://randomuser.me/api/portraits/men/55.jpg" } | |
| ]; | |
| // Sample initial task data | |
| let tasks = [ | |
| { | |
| id: 1, | |
| title: "Implement user authentication", | |
| description: "Create login and registration pages with JWT authentication", | |
| priority: "high", | |
| status: "inprogress", | |
| assignee: 1, | |
| dueDate: "2023-12-15", | |
| createdAt: new Date().toISOString() | |
| }, | |
| { | |
| id: 2, | |
| title: "Design dashboard UI", | |
| description: "Create wireframes and mockups for the new dashboard", | |
| priority: "medium", | |
| status: "todo", | |
| assignee: 2, | |
| dueDate: "2023-12-20", | |
| createdAt: new Date().toISOString() | |
| }, | |
| { | |
| id: 3, | |
| title: "Database optimization", | |
| description: "Review and optimize database queries for better performance", | |
| priority: "high", | |
| status: "todo", | |
| assignee: 3, | |
| dueDate: "2023-12-10", | |
| createdAt: new Date().toISOString() | |
| }, | |
| { | |
| id: 4, | |
| title: "Write API documentation", | |
| description: "Document all API endpoints with examples", | |
| priority: "low", | |
| status: "done", | |
| assignee: 4, | |
| dueDate: "2023-11-30", | |
| createdAt: new Date().toISOString() | |
| } | |
| ]; | |
| // DOM elements | |
| const filterPanel = document.getElementById('filterPanel'); | |
| const filterBtn = document.getElementById('filterBtn'); | |
| const addTaskBtn = document.getElementById('addTaskBtn'); | |
| const taskModal = document.getElementById('taskModal'); | |
| const closeModal = document.getElementById('closeModal'); | |
| const cancelModal = document.getElementById('cancelModal'); | |
| const saveTask = document.getElementById('saveTask'); | |
| const deleteTask = document.getElementById('deleteTask'); | |
| const taskForm = document.getElementById('taskForm'); | |
| const priorityFilter = document.getElementById('priorityFilter'); | |
| const assigneeFilter = document.getElementById('assigneeFilter'); | |
| const dueDateFilter = document.getElementById('dueDateFilter'); | |
| const applyFilters = document.getElementById('applyFilters'); | |
| const clearFilters = document.getElementById('clearFilters'); | |
| // Current task being edited | |
| let currentTask = null; | |
| // Initialize the app | |
| function init() { | |
| populateAssigneeDropdowns(); | |
| renderTasks(); | |
| setupEventListeners(); | |
| updateTaskCounts(); | |
| } | |
| // Set up event listeners | |
| function setupEventListeners() { | |
| // Filter panel | |
| filterBtn.addEventListener('click', () => filterPanel.classList.toggle('hidden')); | |
| applyFilters.addEventListener('click', applyTaskFilters); | |
| clearFilters.addEventListener('click', clearTaskFilters); | |
| // Task modal | |
| addTaskBtn.addEventListener('click', () => openTaskModal()); | |
| closeModal.addEventListener('click', closeTaskModal); | |
| cancelModal.addEventListener('click', closeTaskModal); | |
| saveTask.addEventListener('click', saveTaskHandler); | |
| deleteTask.addEventListener('click', deleteTaskHandler); | |
| // Drag and drop setup | |
| setupDragAndDrop(); | |
| } | |
| // Populate assignee dropdowns with employees | |
| function populateAssigneeDropdowns() { | |
| const assigneeDropdowns = [ | |
| document.getElementById('taskAssignee'), | |
| document.getElementById('assigneeFilter') | |
| ]; | |
| assigneeDropdowns.forEach(dropdown => { | |
| dropdown.innerHTML = '<option value="">Unassigned</option>' + | |
| employees.map(employee => | |
| `<option value="${employee.id}">${employee.name}</option>` | |
| ).join(''); | |
| }); | |
| } | |
| // Render tasks to the Kanban board | |
| function renderTasks(filteredTasks = null) { | |
| const tasksToRender = filteredTasks || tasks; | |
| // Clear all columns | |
| document.getElementById('todo-tasks').innerHTML = ''; | |
| document.getElementById('inprogress-tasks').innerHTML = ''; | |
| document.getElementById('done-tasks').innerHTML = ''; | |
| if (tasksToRender.length === 0) { | |
| updateTaskCounts(); | |
| return; | |
| } | |
| tasksToRender.forEach(task => { | |
| const assignee = employees.find(emp => emp.id === task.assignee); | |
| const assigneeInfo = assignee ? | |
| `<div class="flex items-center mt-2"> | |
| <img src="${assignee.avatar}" alt="${assignee.name}" class="avatar-small rounded-full mr-2"> | |
| <span class="text-xs text-gray-600">${assignee.name}</span> | |
| </div>` : | |
| '<div class="text-xs text-gray-400 mt-2">Unassigned</div>'; | |
| const dueDateInfo = task.dueDate ? | |
| `<div class="flex items-center text-xs mt-2 ${isOverdue(task.dueDate) ? 'text-red-500' : 'text-gray-500'}"> | |
| <i class="far fa-calendar-alt mr-1"></i> | |
| ${formatDueDate(task.dueDate)} | |
| </div>` : ''; | |
| const priorityClass = `priority-${task.priority || 'medium'}`; | |
| const taskCard = ` | |
| <div class="task-card bg-white rounded-md shadow p-4 cursor-move ${priorityClass}" | |
| draggable="true" data-task-id="${task.id}"> | |
| <div class="flex justify-between items-start"> | |
| <h3 class="font-medium text-gray-800">${task.title}</h3> | |
| <span class="text-xs px-2 py-1 rounded-full ${getPriorityBadgeClass(task.priority)}"> | |
| ${task.priority || 'medium'} | |
| </span> | |
| </div> | |
| ${task.description ? `<p class="text-sm text-gray-600 mt-2">${task.description}</p>` : ''} | |
| ${assigneeInfo} | |
| ${dueDateInfo} | |
| <div class="flex justify-end mt-3 space-x-2"> | |
| <button class="edit-task-btn text-gray-400 hover:text-gray-600 text-sm" data-task-id="${task.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| // Add to appropriate column | |
| switch(task.status) { | |
| case 'todo': | |
| document.getElementById('todo-tasks').insertAdjacentHTML('beforeend', taskCard); | |
| break; | |
| case 'inprogress': | |
| document.getElementById('inprogress-tasks').insertAdjacentHTML('beforeend', taskCard); | |
| break; | |
| case 'done': | |
| document.getElementById('done-tasks').insertAdjacentHTML('beforeend', taskCard); | |
| break; | |
| } | |
| }); | |
| // Add event listeners to edit buttons | |
| document.querySelectorAll('.edit-task-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| const taskId = parseInt(e.currentTarget.getAttribute('data-task-id')); | |
| openTaskModal(taskId); | |
| }); | |
| }); | |
| updateTaskCounts(); | |
| } | |
| // Get priority badge class | |
| function getPriorityBadgeClass(priority) { | |
| switch(priority) { | |
| case 'high': return 'bg-red-100 text-red-800'; | |
| case 'medium': return 'bg-yellow-100 text-yellow-800'; | |
| case 'low': return 'bg-green-100 text-green-800'; | |
| default: return 'bg-gray-100 text-gray-800'; | |
| } | |
| } | |
| // Format due date for display | |
| function formatDueDate(dateString) { | |
| const options = { year: 'numeric', month: 'short', day: 'numeric' }; | |
| return new Date(dateString).toLocaleDateString(undefined, options); | |
| } | |
| // Check if a task is overdue | |
| function isOverdue(dueDate) { | |
| if (!dueDate) return false; | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| return new Date(dueDate) < today; | |
| } | |
| // Update task count badges | |
| function updateTaskCounts() { | |
| const todoCount = document.getElementById('todo-tasks').children.length; | |
| const inprogressCount = document.getElementById('inprogress-tasks').children.length; | |
| const doneCount = document.getElementById('done-tasks').children.length; | |
| document.getElementById('todo-count').textContent = todoCount; | |
| document.getElementById('inprogress-count').textContent = inprogressCount; | |
| document.getElementById('done-count').textContent = doneCount; | |
| } | |
| // Set up drag and drop functionality | |
| function setupDragAndDrop() { | |
| const taskCards = document.querySelectorAll('.task-card'); | |
| const columns = document.querySelectorAll('.kanban-column > div:last-child'); | |
| taskCards.forEach(task => { | |
| task.addEventListener('dragstart', dragStart); | |
| task.addEventListener('dragend', dragEnd); | |
| }); | |
| columns.forEach(column => { | |
| column.addEventListener('dragover', dragOver); | |
| column.addEventListener('dragenter', dragEnter); | |
| column.addEventListener('dragleave', dragLeave); | |
| column.addEventListener('drop', drop); | |
| }); | |
| } | |
| // Drag and drop event handlers | |
| function dragStart(e) { | |
| e.currentTarget.classList.add('dragging'); | |
| e.dataTransfer.setData('text/plain', e.currentTarget.getAttribute('data-task-id')); | |
| } | |
| function dragEnd(e) { | |
| e.currentTarget.classList.remove('dragging'); | |
| } | |
| function dragOver(e) { | |
| e.preventDefault(); | |
| } | |
| function dragEnter(e) { | |
| e.preventDefault(); | |
| e.currentTarget.classList.add('bg-blue-50'); | |
| } | |
| function dragLeave(e) { | |
| e.currentTarget.classList.remove('bg-blue-50'); | |
| } | |
| function drop(e) { | |
| e.preventDefault(); | |
| e.currentTarget.classList.remove('bg-blue-50'); | |
| const taskId = e.dataTransfer.getData('text/plain'); | |
| const draggedTask = document.querySelector(`.task-card[data-task-id="${taskId}"]`); | |
| // Determine the new status based on the column | |
| let newStatus; | |
| if (e.currentTarget.id === 'todo-tasks') newStatus = 'todo'; | |
| else if (e.currentTarget.id === 'inprogress-tasks') newStatus = 'inprogress'; | |
| else if (e.currentTarget.id === 'done-tasks') newStatus = 'done'; | |
| // Update the task in our data | |
| const taskIndex = tasks.findIndex(t => t.id == taskId); | |
| if (taskIndex !== -1) { | |
| tasks[taskIndex].status = newStatus; | |
| } | |
| // Re-render the tasks to reflect the change | |
| renderTasks(); | |
| } | |
| // Apply task filters | |
| function applyTaskFilters() { | |
| const priority = priorityFilter.value; | |
| const assignee = assigneeFilter.value; | |
| const dueDate = dueDateFilter.value; | |
| let filtered = tasks; | |
| if (priority) { | |
| filtered = filtered.filter(task => task.priority === priority); | |
| } | |
| if (assignee) { | |
| filtered = filtered.filter(task => task.assignee == assignee); | |
| } | |
| if (dueDate) { | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| filtered = filtered.filter(task => { | |
| if (!task.dueDate) return false; | |
| const taskDate = new Date(task.dueDate); | |
| switch(dueDate) { | |
| case 'today': | |
| return taskDate.toDateString() === today.toDateString(); | |
| case 'week': | |
| const endOfWeek = new Date(today); | |
| endOfWeek.setDate(today.getDate() + 7); | |
| return taskDate >= today && taskDate <= endOfWeek; | |
| case 'overdue': | |
| return taskDate < today; | |
| default: | |
| return true; | |
| } | |
| }); | |
| } | |
| renderTasks(filtered); | |
| filterPanel.classList.add('hidden'); | |
| } | |
| // Clear task filters | |
| function clearTaskFilters() { | |
| priorityFilter.value = ''; | |
| assigneeFilter.value = ''; | |
| dueDateFilter.value = ''; | |
| renderTasks(); | |
| filterPanel.classList.add('hidden'); | |
| } | |
| // Open task modal for adding or editing | |
| function openTaskModal(id = null) { | |
| currentTask = id ? tasks.find(task => task.id === id) : null; | |
| if (currentTask) { | |
| // Edit mode | |
| document.getElementById('modalTitle').textContent = 'Edit Task'; | |
| document.getElementById('taskId').value = currentTask.id; | |
| document.getElementById('taskTitle').value = currentTask.title; | |
| document.getElementById('taskDescription').value = currentTask.description || ''; | |
| document.getElementById('taskPriority').value = currentTask.priority || 'medium'; | |
| document.getElementById('taskStatus').value = currentTask.status || 'todo'; | |
| document.getElementById('taskAssignee').value = currentTask.assignee || ''; | |
| document.getElementById('taskDueDate').value = currentTask.dueDate || ''; | |
| deleteTask.classList.remove('hidden'); | |
| } else { | |
| // Add mode | |
| document.getElementById('modalTitle').textContent = 'Add New Task'; | |
| taskForm.reset(); | |
| document.getElementById('taskPriority').value = 'medium'; | |
| document.getElementById('taskStatus').value = 'todo'; | |
| deleteTask.classList.add('hidden'); | |
| } | |
| taskModal.classList.remove('hidden'); | |
| } | |
| // Close task modal | |
| function closeTaskModal() { | |
| taskModal.classList.add('hidden'); | |
| currentTask = null; | |
| } | |
| // Save task (add or update) | |
| function saveTaskHandler() { | |
| const formData = new FormData(taskForm); | |
| const title = formData.get('taskTitle'); | |
| const description = formData.get('taskDescription'); | |
| const priority = formData.get('taskPriority'); | |
| const status = formData.get('taskStatus'); | |
| const assignee = formData.get('taskAssignee'); | |
| const dueDate = formData.get('taskDueDate'); | |
| const id = formData.get('taskId'); | |
| if (!title || !priority || !status) { | |
| alert('Please fill in all required fields'); | |
| return; | |
| } | |
| const taskData = { | |
| id: id ? parseInt(id) : tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1, | |
| title, | |
| description, | |
| priority, | |
| status, | |
| assignee: assignee ? parseInt(assignee) : null, | |
| dueDate, | |
| createdAt: id ? currentTask.createdAt : new Date().toISOString() | |
| }; | |
| if (currentTask) { | |
| // Update existing task | |
| const index = tasks.findIndex(t => t.id === currentTask.id); | |
| tasks[index] = taskData; | |
| } else { | |
| // Add new task | |
| tasks.push(taskData); | |
| } | |
| renderTasks(); | |
| closeTaskModal(); | |
| } | |
| // Delete task | |
| function deleteTaskHandler() { | |
| if (currentTask) { | |
| tasks = tasks.filter(task => task.id !== currentTask.id); | |
| renderTasks(); | |
| } | |
| closeTaskModal(); | |
| } | |
| // Initialize the app | |
| init(); | |
| </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=johngoad/a-task-board" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |