Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Task Manager</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: 300px; | |
background-color: #f3f4f6; | |
border-radius: 0.5rem; | |
padding: 1rem; | |
} | |
.task-card { | |
transition: all 0.3s ease; | |
cursor: grab; | |
} | |
.task-card:active { | |
cursor: grabbing; | |
} | |
.task-card.dragging { | |
opacity: 0.5; | |
transform: scale(1.05); | |
} | |
.kanban-column.highlight { | |
background-color: #e5e7eb; | |
} | |
.view-option.active { | |
background-color: #3b82f6; | |
color: white; | |
} | |
.category-work { border-left: 4px solid #3b82f6; } | |
.category-errand { border-left: 4px solid #10b981; } | |
.category-home { border-left: 4px solid #f59e0b; } | |
.category-social { border-left: 4px solid #8b5cf6; } | |
.status-todo { background-color: #f3f4f6; } | |
.status-in-progress { background-color: #bfdbfe; } | |
.status-done { background-color: #dcfce7; } | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8"> | |
<h1 class="text-3xl font-bold text-gray-800">Task Manager</h1> | |
<p class="text-gray-600">Organize your tasks efficiently</p> | |
</header> | |
<div class="bg-white rounded-lg shadow-md p-6 mb-6"> | |
<div class="flex justify-between items-center mb-6"> | |
<div class="flex space-x-2"> | |
<button id="list-view-btn" class="view-option active px-4 py-2 rounded-lg border border-gray-300"> | |
<i class="fas fa-list mr-2"></i>List View | |
</button> | |
<button id="kanban-view-btn" class="view-option px-4 py-2 rounded-lg border border-gray-300"> | |
<i class="fas fa-columns mr-2"></i>Kanban Board | |
</button> | |
<button id="card-view-btn" class="view-option px-4 py-2 rounded-lg border border-gray-300"> | |
<i class="fas fa-grip mr-2"></i>Card View | |
</button> | |
</div> | |
<button id="add-task-btn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg"> | |
<i class="fas fa-plus mr-2"></i>Add Task | |
</button> | |
</div> | |
<div class="mb-6 flex flex-wrap gap-4"> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Filter by Category</label> | |
<select id="category-filter" class="border border-gray-300 rounded-md px-3 py-2 w-full"> | |
<option value="all">All Categories</option> | |
<option value="work">Work</option> | |
<option value="errand">Errand</option> | |
<option value="home">Home</option> | |
<option value="social">Social</option> | |
</select> | |
</div> | |
<div> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Filter by Status</label> | |
<select id="status-filter" class="border border-gray-300 rounded-md px-3 py-2 w-full"> | |
<option value="all">All Statuses</option> | |
<option value="to-do">To Do</option> | |
<option value="in-progress">In Progress</option> | |
<option value="done">Done</option> | |
</select> | |
</div> | |
<div id="kanban-grouping-controls" class="hidden"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Group By</label> | |
<select id="kanban-group-by" class="border border-gray-300 rounded-md px-3 py-2 w-full"> | |
<option value="status">Status</option> | |
<option value="category">Category</option> | |
</select> | |
</div> | |
</div> | |
<!-- List View --> | |
<div id="list-view" class="view-content"> | |
<table class="min-w-full divide-y divide-gray-200"> | |
<thead class="bg-gray-50"> | |
<tr> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Task</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Category</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> | |
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> | |
</tr> | |
</thead> | |
<tbody id="task-list" class="bg-white divide-y divide-gray-200"> | |
<!-- Tasks will be populated here --> | |
</tbody> | |
</table> | |
</div> | |
<!-- Kanban View --> | |
<div id="kanban-view" class="view-content hidden"> | |
<div id="kanban-board" class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
<!-- Kanban columns will be populated here --> | |
</div> | |
</div> | |
<!-- Card View --> | |
<div id="card-view" class="view-content hidden"> | |
<div id="card-container" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> | |
<!-- Cards will be populated here --> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Add/Edit Task Modal --> | |
<div id="task-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 id="modal-title" class="text-xl font-semibold text-gray-800">Add New Task</h3> | |
<button id="close-modal" class="text-gray-500 hover:text-gray-700"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<form id="task-form"> | |
<input type="hidden" id="task-id"> | |
<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="border border-gray-300 rounded-md px-3 py-2 w-full" required></textarea> | |
</div> | |
<div class="grid grid-cols-2 gap-4 mb-4"> | |
<div> | |
<label for="task-category" class="block text-sm font-medium text-gray-700 mb-1">Category</label> | |
<select id="task-category" class="border border-gray-300 rounded-md px-3 py-2 w-full" required> | |
<option value="work">Work</option> | |
<option value="errand">Errand</option> | |
<option value="home">Home</option> | |
<option value="social">Social</option> | |
</select> | |
</div> | |
<div> | |
<label for="task-status" class="block text-sm font-medium text-gray-700 mb-1">Status</label> | |
<select id="task-status" class="border border-gray-300 rounded-md px-3 py-2 w-full" required> | |
<option value="to-do">To Do</option> | |
<option value="in-progress">In Progress</option> | |
<option value="done">Done</option> | |
</select> | |
</div> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button type="button" id="cancel-task" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"> | |
Cancel | |
</button> | |
<button type="submit" class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"> | |
Save Task | |
</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
<script> | |
// Sample initial tasks | |
let tasks = [ | |
{ id: 1, description: "Complete project report", category: "work", status: "in-progress" }, | |
{ id: 2, description: "Buy groceries", category: "errand", status: "to-do" }, | |
{ id: 3, description: "Call mom", category: "social", status: "to-do" }, | |
{ id: 4, description: "Fix leaking faucet", category: "home", status: "done" }, | |
{ id: 5, description: "Prepare presentation for meeting", category: "work", status: "to-do" }, | |
{ id: 6, description: "Go to the gym", category: "social", status: "in-progress" }, | |
{ id: 7, description: "Pay electricity bill", category: "errand", status: "to-do" }, | |
{ id: 8, description: "Organize closet", category: "home", status: "to-do" }, | |
{ id: 9, description: "Team lunch", category: "social", status: "done" }, | |
{ id: 10, description: "Review code changes", category: "work", status: "in-progress" }, | |
{ id: 11, description: "Pick up dry cleaning", category: "errand", status: "done" }, | |
{ id: 12, description: "Water plants", category: "home", status: "to-do" } | |
]; | |
// DOM elements | |
const listView = document.getElementById('list-view'); | |
const kanbanView = document.getElementById('kanban-view'); | |
const cardView = document.getElementById('card-view'); | |
const listViewBtn = document.getElementById('list-view-btn'); | |
const kanbanViewBtn = document.getElementById('kanban-view-btn'); | |
const cardViewBtn = document.getElementById('card-view-btn'); | |
const taskList = document.getElementById('task-list'); | |
const kanbanBoard = document.getElementById('kanban-board'); | |
const cardContainer = document.getElementById('card-container'); | |
const addTaskBtn = document.getElementById('add-task-btn'); | |
const taskModal = document.getElementById('task-modal'); | |
const closeModal = document.getElementById('close-modal'); | |
const cancelTask = document.getElementById('cancel-task'); | |
const taskForm = document.getElementById('task-form'); | |
const modalTitle = document.getElementById('modal-title'); | |
const taskIdInput = document.getElementById('task-id'); | |
const taskDescription = document.getElementById('task-description'); | |
const taskCategory = document.getElementById('task-category'); | |
const taskStatus = document.getElementById('task-status'); | |
const categoryFilter = document.getElementById('category-filter'); | |
const statusFilter = document.getElementById('status-filter'); | |
const kanbanGroupingControls = document.getElementById('kanban-grouping-controls'); | |
const kanbanGroupBy = document.getElementById('kanban-group-by'); | |
// View management | |
function showView(view) { | |
document.querySelectorAll('.view-content').forEach(v => v.classList.add('hidden')); | |
document.querySelectorAll('.view-option').forEach(btn => btn.classList.remove('active')); | |
view.classList.remove('hidden'); | |
if (view === listView) { | |
listViewBtn.classList.add('active'); | |
kanbanGroupingControls.classList.add('hidden'); | |
renderListView(); | |
} else if (view === kanbanView) { | |
kanbanViewBtn.classList.add('active'); | |
kanbanGroupingControls.classList.remove('hidden'); | |
renderKanbanView(); | |
} else if (view === cardView) { | |
cardViewBtn.classList.add('active'); | |
kanbanGroupingControls.classList.add('hidden'); | |
renderCardView(); | |
} | |
} | |
// Render functions | |
function renderListView() { | |
const filteredTasks = filterTasks(); | |
taskList.innerHTML = ''; | |
filteredTasks.forEach(task => { | |
const row = document.createElement('tr'); | |
row.className = `status-${task.status.replace(' ', '-')}`; | |
row.innerHTML = ` | |
<td class="px-6 py-4 whitespace-nowrap"> | |
<div class="text-sm font-medium text-gray-900">${task.description}</div> | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap"> | |
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full | |
${getCategoryColorClass(task.category)}"> | |
${capitalizeFirstLetter(task.category)} | |
</span> | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap"> | |
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full | |
${getStatusColorClass(task.status)}"> | |
${capitalizeFirstLetter(task.status.replace('-', ' '))} | |
</span> | |
</td> | |
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | |
<button class="edit-task text-blue-600 hover:text-blue-900 mr-3" data-id="${task.id}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<button class="delete-task text-red-600 hover:text-red-900" data-id="${task.id}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</td> | |
`; | |
taskList.appendChild(row); | |
}); | |
// Add event listeners to edit and delete buttons | |
document.querySelectorAll('.edit-task').forEach(btn => { | |
btn.addEventListener('click', (e) => editTask(e.target.closest('button').dataset.id)); | |
}); | |
document.querySelectorAll('.delete-task').forEach(btn => { | |
btn.addEventListener('click', (e) => deleteTask(e.target.closest('button').dataset.id)); | |
}); | |
} | |
function renderKanbanView() { | |
const groupBy = kanbanGroupBy.value; | |
const filteredTasks = filterTasks(); | |
// Define groups based on selection | |
let groups = []; | |
if (groupBy === 'status') { | |
groups = ['to-do', 'in-progress', 'done']; | |
} else { | |
groups = ['work', 'errand', 'home', 'social']; | |
} | |
kanbanBoard.innerHTML = ''; | |
groups.forEach(group => { | |
const columnTasks = filteredTasks.filter(task => | |
groupBy === 'status' ? task.status === group : task.category === group | |
); | |
const column = document.createElement('div'); | |
column.className = 'kanban-column'; | |
column.dataset.group = group; | |
const columnTitle = document.createElement('h3'); | |
columnTitle.className = 'text-lg font-semibold mb-4'; | |
columnTitle.textContent = capitalizeFirstLetter(group.replace('-', ' ')); | |
column.appendChild(columnTitle); | |
const tasksContainer = document.createElement('div'); | |
tasksContainer.className = 'space-y-3'; | |
columnTasks.forEach(task => { | |
const taskCard = document.createElement('div'); | |
taskCard.className = `task-card p-3 rounded-md shadow-sm bg-white ${groupBy === 'category' ? `category-${task.category}` : ''}`; | |
taskCard.draggable = true; | |
taskCard.dataset.id = task.id; | |
taskCard.innerHTML = ` | |
<div class="text-sm mb-2">${task.description}</div> | |
<div class="flex justify-between items-center text-xs"> | |
<span class="px-2 py-1 rounded-full ${groupBy === 'status' ? getCategoryColorClass(task.category) : getStatusColorClass(task.status)}"> | |
${capitalizeFirstLetter(groupBy === 'status' ? task.category : task.status.replace('-', ' '))} | |
</span> | |
<div> | |
<button class="edit-task text-blue-600 hover:text-blue-900 mr-2" data-id="${task.id}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<button class="delete-task text-red-600 hover:text-red-900" data-id="${task.id}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
</div> | |
`; | |
tasksContainer.appendChild(taskCard); | |
}); | |
column.appendChild(tasksContainer); | |
kanbanBoard.appendChild(column); | |
}); | |
// Add event listeners to edit and delete buttons | |
document.querySelectorAll('.edit-task').forEach(btn => { | |
btn.addEventListener('click', (e) => editTask(e.target.closest('button').dataset.id)); | |
}); | |
document.querySelectorAll('.delete-task').forEach(btn => { | |
btn.addEventListener('click', (e) => deleteTask(e.target.closest('button').dataset.id)); | |
}); | |
// Setup drag and drop | |
setupDragAndDrop(); | |
} | |
function renderCardView() { | |
const filteredTasks = filterTasks(); | |
cardContainer.innerHTML = ''; | |
filteredTasks.forEach(task => { | |
const card = document.createElement('div'); | |
card.className = `task-card p-4 rounded-lg shadow-sm bg-white ${getStatusClass(task.status)} category-${task.category}`; | |
card.innerHTML = ` | |
<div class="text-sm mb-3">${task.description}</div> | |
<div class="flex justify-between items-center text-xs"> | |
<span class="px-2 py-1 rounded-full ${getCategoryColorClass(task.category)}"> | |
${capitalizeFirstLetter(task.category)} | |
</span> | |
<span class="px-2 py-1 rounded-full ${getStatusColorClass(task.status)}"> | |
${capitalizeFirstLetter(task.status.replace('-', ' '))} | |
</span> | |
</div> | |
<div class="mt-3 flex justify-end space-x-2"> | |
<button class="edit-task text-blue-600 hover:text-blue-900" data-id="${task.id}"> | |
<i class="fas fa-edit"></i> | |
</button> | |
<button class="delete-task text-red-600 hover:text-red-900" data-id="${task.id}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
`; | |
cardContainer.appendChild(card); | |
}); | |
// Add event listeners to edit and delete buttons | |
document.querySelectorAll('.edit-task').forEach(btn => { | |
btn.addEventListener('click', (e) => editTask(e.target.closest('button').dataset.id)); | |
}); | |
document.querySelectorAll('.delete-task').forEach(btn => { | |
btn.addEventListener('click', (e) => deleteTask(e.target.closest('button').dataset.id)); | |
}); | |
} | |
// Task CRUD operations | |
function addTask(task) { | |
task.id = tasks.length > 0 ? Math.max(...tasks.map(t => t.id)) + 1 : 1; | |
tasks.push(task); | |
renderCurrentView(); | |
} | |
function updateTask(updatedTask) { | |
const index = tasks.findIndex(t => t.id === parseInt(updatedTask.id)); | |
if (index !== -1) { | |
tasks[index] = updatedTask; | |
renderCurrentView(); | |
} | |
} | |
function deleteTask(id) { | |
if (confirm('Are you sure you want to delete this task?')) { | |
tasks = tasks.filter(task => task.id !== parseInt(id)); | |
renderCurrentView(); | |
} | |
} | |
function editTask(id) { | |
const task = tasks.find(t => t.id === parseInt(id)); | |
if (task) { | |
modalTitle.textContent = 'Edit Task'; | |
taskIdInput.value = task.id; | |
taskDescription.value = task.description; | |
taskCategory.value = task.category; | |
taskStatus.value = task.status; | |
showModal(); | |
} | |
} | |
// Modal functions | |
function showModal() { | |
taskModal.classList.remove('hidden'); | |
} | |
function hideModal() { | |
taskModal.classList.add('hidden'); | |
taskForm.reset(); | |
taskIdInput.value = ''; | |
} | |
// Helper functions | |
function filterTasks() { | |
const category = categoryFilter.value; | |
const status = statusFilter.value; | |
return tasks.filter(task => { | |
const categoryMatch = category === 'all' || task.category === category; | |
const statusMatch = status === 'all' || task.status === status.replace(' ', '-'); | |
return categoryMatch && statusMatch; | |
}); | |
} | |
function getCategoryColorClass(category) { | |
switch(category) { | |
case 'work': return 'bg-blue-100 text-blue-800'; | |
case 'errand': return 'bg-green-100 text-green-800'; | |
case 'home': return 'bg-yellow-100 text-yellow-800'; | |
case 'social': return 'bg-purple-100 text-purple-800'; | |
default: return 'bg-gray-100 text-gray-800'; | |
} | |
} | |
function getStatusColorClass(status) { | |
switch(status) { | |
case 'to-do': return 'bg-gray-100 text-gray-800'; | |
case 'in-progress': return 'bg-blue-100 text-blue-800'; | |
case 'done': return 'bg-green-100 text-green-800'; | |
default: return 'bg-gray-100 text-gray-800'; | |
} | |
} | |
function getStatusClass(status) { | |
return `status-${status.replace(' ', '-')}`; | |
} | |
function capitalizeFirstLetter(string) { | |
return string.charAt(0).toUpperCase() + string.slice(1); | |
} | |
function renderCurrentView() { | |
if (!listView.classList.contains('hidden')) { | |
renderListView(); | |
} else if (!kanbanView.classList.contains('hidden')) { | |
renderKanbanView(); | |
} else if (!cardView.classList.contains('hidden')) { | |
renderCardView(); | |
} | |
} | |
// Drag and drop functionality | |
function setupDragAndDrop() { | |
const taskCards = document.querySelectorAll('.task-card'); | |
const kanbanColumns = document.querySelectorAll('.kanban-column'); | |
let draggedItem = null; | |
taskCards.forEach(card => { | |
card.addEventListener('dragstart', (e) => { | |
draggedItem = card; | |
setTimeout(() => { | |
card.classList.add('dragging'); | |
}, 0); | |
}); | |
card.addEventListener('dragend', () => { | |
card.classList.remove('dragging'); | |
draggedItem = null; | |
}); | |
}); | |
kanbanColumns.forEach(column => { | |
column.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
column.classList.add('highlight'); | |
}); | |
column.addEventListener('dragleave', () => { | |
column.classList.remove('highlight'); | |
}); | |
column.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
column.classList.remove('highlight'); | |
if (draggedItem) { | |
const taskId = parseInt(draggedItem.dataset.id); | |
const newGroup = column.dataset.group; | |
const groupBy = kanbanGroupBy.value; | |
const task = tasks.find(t => t.id === taskId); | |
if (task) { | |
if (groupBy === 'status') { | |
task.status = newGroup; | |
} else { | |
task.category = newGroup; | |
} | |
renderKanbanView(); | |
} | |
} | |
}); | |
}); | |
} | |
// Event listeners | |
listViewBtn.addEventListener('click', () => showView(listView)); | |
kanbanViewBtn.addEventListener('click', () => showView(kanbanView)); | |
cardViewBtn.addEventListener('click', () => showView(cardView)); | |
addTaskBtn.addEventListener('click', () => { | |
modalTitle.textContent = 'Add New Task'; | |
taskIdInput.value = ''; | |
taskForm.reset(); | |
showModal(); | |
}); | |
closeModal.addEventListener('click', hideModal); | |
cancelTask.addEventListener('click', hideModal); | |
taskForm.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
const taskData = { | |
id: taskIdInput.value ? parseInt(taskIdInput.value) : null, | |
description: taskDescription.value, | |
category: taskCategory.value, | |
status: taskStatus.value | |
}; | |
if (taskData.id) { | |
updateTask(taskData); | |
} else { | |
addTask(taskData); | |
} | |
hideModal(); | |
}); | |
categoryFilter.addEventListener('change', renderCurrentView); | |
statusFilter.addEventListener('change', renderCurrentView); | |
kanbanGroupBy.addEventListener('change', renderKanbanView); | |
// Initialize | |
showView(listView); | |
</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=manyone/manyone-space" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> | |
</html> |