Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Dev Workflow Tracker</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> | |
.gradient-bg { | |
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); | |
} | |
.card-hover:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
} | |
.transition-all { | |
transition: all 0.3s ease; | |
} | |
.command-box { | |
font-family: 'Courier New', monospace; | |
background-color: #2d3748; | |
color: #f7fafc; | |
} | |
.status-pending { | |
background-color: #f6e05e; | |
color: #975a16; | |
} | |
.status-success { | |
background-color: #68d391; | |
color: #276749; | |
} | |
.status-failed { | |
background-color: #fc8181; | |
color: #9b2c2c; | |
} | |
.status-merged { | |
background-color: #63b3ed; | |
color: #2c5282; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen"> | |
<div class="gradient-bg text-white py-6 shadow-lg"> | |
<div class="container mx-auto px-4"> | |
<div class="flex justify-between items-center"> | |
<h1 class="text-3xl font-bold flex items-center"> | |
<i class="fas fa-code-branch mr-3"></i> Dev Workflow Tracker | |
</h1> | |
<button id="newTaskBtn" class="bg-white text-blue-800 px-4 py-2 rounded-lg font-semibold hover:bg-blue-100 transition-all"> | |
<i class="fas fa-plus mr-2"></i> New Task | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="container mx-auto px-4 py-8"> | |
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
<!-- Tasks Column --> | |
<div class="bg-white rounded-lg shadow-md p-6"> | |
<div class="flex justify-between items-center mb-6"> | |
<h2 class="text-xl font-semibold text-gray-800"> | |
<i class="fas fa-tasks mr-2 text-blue-500"></i> Active Tasks | |
</h2> | |
<span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-medium"> | |
<span id="taskCount">0</span> tasks | |
</span> | |
</div> | |
<div id="tasksContainer" class="space-y-4"> | |
<!-- Tasks will be added here dynamically --> | |
</div> | |
</div> | |
<!-- Merge Status Column --> | |
<div class="bg-white rounded-lg shadow-md p-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-6"> | |
<i class="fas fa-code-merge mr-2 text-green-500"></i> Merge Status | |
</h2> | |
<div id="mergeStatusContainer" class="space-y-4"> | |
<!-- Merge status will be added here dynamically --> | |
</div> | |
</div> | |
<!-- Commands Column --> | |
<div class="bg-white rounded-lg shadow-md p-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-6"> | |
<i class="fas fa-terminal mr-2 text-purple-500"></i> Common Commands | |
</h2> | |
<div class="space-y-4"> | |
<div class="command-box p-4 rounded-lg"> | |
<div class="flex justify-between items-center mb-2"> | |
<span class="font-semibold">Git Branch</span> | |
<button class="copy-btn text-blue-400 hover:text-blue-300" data-clipboard-text="git checkout -b feature/your-feature"> | |
<i class="far fa-copy"></i> | |
</button> | |
</div> | |
<code>git checkout -b feature/your-feature</code> | |
</div> | |
<div class="command-box p-4 rounded-lg"> | |
<div class="flex justify-between items-center mb-2"> | |
<span class="font-semibold">Rebase</span> | |
<button class="copy-btn text-blue-400 hover:text-blue-300" data-clipboard-text="git pull --rebase origin dev"> | |
<i class="far fa-copy"></i> | |
</button> | |
</div> | |
<code>git pull --rebase origin dev</code> | |
</div> | |
<div class="command-box p-4 rounded-lg"> | |
<div class="flex justify-between items-center mb-2"> | |
<span class="font-semibold">Merge</span> | |
<button class="copy-btn text-blue-400 hover:text-blue-300" data-clipboard-text="git merge --no-ff feature/your-feature"> | |
<i class="far fa-copy"></i> | |
</button> | |
</div> | |
<code>git merge --no-ff feature/your-feature</code> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- New Task Modal --> | |
<div id="taskModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> | |
<div class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4"> | |
<div class="gradient-bg text-white rounded-t-lg px-6 py-4"> | |
<h2 class="text-xl font-bold">Add New Task</h2> | |
</div> | |
<div class="p-6"> | |
<form id="taskForm"> | |
<div class="mb-4"> | |
<label class="block text-gray-700 font-medium mb-2" for="taskType">Task Type</label> | |
<select id="taskType" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> | |
<option value="feature">Feature</option> | |
<option value="bug">Bug</option> | |
<option value="hotfix">Hotfix</option> | |
<option value="refactor">Refactor</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 font-medium mb-2" for="taskTitle">Title</label> | |
<input type="text" id="taskTitle" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="What are you working on?"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 font-medium mb-2" for="branchName">Branch Name</label> | |
<input type="text" id="branchName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="feature/your-feature"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 font-medium mb-2">Affected Services</label> | |
<div class="space-y-2"> | |
<div class="flex items-center"> | |
<input type="checkbox" id="service1" class="mr-2" checked> | |
<label for="service1">Frontend</label> | |
</div> | |
<div class="flex items-center"> | |
<input type="checkbox" id="service2" class="mr-2"> | |
<label for="service2">User Service</label> | |
</div> | |
<div class="flex items-center"> | |
<input type="checkbox" id="service3" class="mr-2"> | |
<label for="service3">Payment Service</label> | |
</div> | |
<div class="flex items-center"> | |
<input type="checkbox" id="service4" class="mr-2"> | |
<label for="service4">Order Service</label> | |
</div> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-700 font-medium mb-2" for="mergeCommands">Merge Commands</label> | |
<textarea id="mergeCommands" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" rows="3" placeholder="Any special merge commands or notes"></textarea> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button type="button" id="cancelTaskBtn" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-100">Cancel</button> | |
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Add Task</button> | |
</div> | |
</form> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Sample data for demonstration | |
const sampleTasks = [ | |
{ | |
id: 1, | |
type: 'feature', | |
title: 'Add user profile page', | |
branch: 'feature/user-profile', | |
services: ['Frontend', 'User Service'], | |
commands: 'git pull --rebase origin dev\nnpm run test\n', | |
devStatus: 'pending', | |
masterStatus: 'pending' | |
}, | |
{ | |
id: 2, | |
type: 'bug', | |
title: 'Fix payment validation', | |
branch: 'bugfix/payment-validation', | |
services: ['Payment Service'], | |
commands: 'git fetch origin\n', | |
devStatus: 'merged', | |
masterStatus: 'pending' | |
} | |
]; | |
let tasks = JSON.parse(localStorage.getItem('devTasks')) || sampleTasks; | |
// DOM elements | |
const tasksContainer = document.getElementById('tasksContainer'); | |
const mergeStatusContainer = document.getElementById('mergeStatusContainer'); | |
const taskModal = document.getElementById('taskModal'); | |
const newTaskBtn = document.getElementById('newTaskBtn'); | |
const cancelTaskBtn = document.getElementById('cancelTaskBtn'); | |
const taskForm = document.getElementById('taskForm'); | |
const taskCount = document.getElementById('taskCount'); | |
// Initialize clipboard functionality | |
document.querySelectorAll('.copy-btn').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const text = this.getAttribute('data-clipboard-text'); | |
navigator.clipboard.writeText(text).then(() => { | |
const originalIcon = this.innerHTML; | |
this.innerHTML = '<i class="fas fa-check"></i>'; | |
setTimeout(() => { | |
this.innerHTML = originalIcon; | |
}, 2000); | |
}); | |
}); | |
}); | |
// Modal controls | |
newTaskBtn.addEventListener('click', () => { | |
taskModal.classList.remove('hidden'); | |
}); | |
cancelTaskBtn.addEventListener('click', () => { | |
taskModal.classList.add('hidden'); | |
}); | |
// Form submission | |
taskForm.addEventListener('submit', function(e) { | |
e.preventDefault(); | |
const type = document.getElementById('taskType').value; | |
const title = document.getElementById('taskTitle').value.trim(); | |
const branch = document.getElementById('branchName').value.trim(); | |
if (!title || !branch) { | |
alert('Please fill in all required fields'); | |
return; | |
} | |
// Get selected services | |
const services = []; | |
document.querySelectorAll('input[type="checkbox"]:checked').forEach(checkbox => { | |
services.push(checkbox.nextElementSibling.textContent); | |
}); | |
const commands = document.getElementById('mergeCommands').value.trim(); | |
const newTask = { | |
id: Date.now(), | |
type, | |
title, | |
branch, | |
services, | |
commands, | |
devStatus: 'pending', | |
masterStatus: 'pending' | |
}; | |
tasks.push(newTask); | |
saveTasks(); | |
renderTasks(); | |
renderMergeStatus(); | |
taskModal.classList.add('hidden'); | |
taskForm.reset(); | |
}); | |
// Save tasks to localStorage | |
function saveTasks() { | |
localStorage.setItem('devTasks', JSON.stringify(tasks)); | |
updateTaskCount(); | |
} | |
// Update task count | |
function updateTaskCount() { | |
taskCount.textContent = tasks.length; | |
} | |
// Render tasks | |
function renderTasks() { | |
tasksContainer.innerHTML = ''; | |
tasks.forEach(task => { | |
const taskElement = document.createElement('div'); | |
taskElement.className = 'bg-white border border-gray-200 rounded-lg p-4 shadow-sm card-hover transition-all'; | |
const typeIcon = task.type === 'feature' ? 'fa-star' : | |
task.type === 'bug' ? 'fa-bug' : | |
task.type === 'hotfix' ? 'fa-fire' : 'fa-code'; | |
taskElement.innerHTML = ` | |
<div class="flex justify-between items-start mb-2"> | |
<div class="flex items-center"> | |
<i class="fas ${typeIcon} mr-2 ${task.type === 'feature' ? 'text-blue-500' : 'text-red-500'}"></i> | |
<h3 class="font-semibold text-gray-800">${task.title}</h3> | |
</div> | |
<button class="delete-task text-gray-400 hover:text-red-500" data-id="${task.id}"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="mb-2"> | |
<span class="text-sm font-medium text-gray-600">Branch:</span> | |
<span class="text-sm ml-2 font-mono bg-gray-100 px-2 py-1 rounded">${task.branch}</span> | |
</div> | |
<div class="mb-3"> | |
<span class="text-sm font-medium text-gray-600">Services:</span> | |
<div class="flex flex-wrap gap-1 mt-1"> | |
${task.services.map(service => ` | |
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">${service}</span> | |
`).join('')} | |
</div> | |
</div> | |
${task.commands ? ` | |
<div class="mb-3"> | |
<span class="text-sm font-medium text-gray-600">Commands:</span> | |
<div class="command-box p-2 rounded mt-1 text-xs"> | |
<pre>${task.commands}</pre> | |
</div> | |
</div> | |
` : ''} | |
<div class="flex justify-between items-center pt-2 border-t border-gray-100"> | |
<div> | |
<span class="text-xs text-gray-500">Created: ${new Date(task.id).toLocaleDateString()}</span> | |
</div> | |
<div class="flex space-x-2"> | |
<button class="edit-task text-blue-500 hover:text-blue-700 text-sm" data-id="${task.id}"> | |
<i class="fas fa-edit mr-1"></i>Edit | |
</button> | |
<button class="update-status text-green-500 hover:text-green-700 text-sm" data-id="${task.id}"> | |
<i class="fas fa-sync-alt mr-1"></i>Update Status | |
</button> | |
</div> | |
</div> | |
`; | |
tasksContainer.appendChild(taskElement); | |
}); | |
// Add event listeners for delete buttons | |
document.querySelectorAll('.delete-task').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const taskId = parseInt(this.getAttribute('data-id')); | |
tasks = tasks.filter(task => task.id !== taskId); | |
saveTasks(); | |
renderTasks(); | |
renderMergeStatus(); | |
}); | |
}); | |
// Add event listeners for edit buttons | |
document.querySelectorAll('.edit-task').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const taskId = parseInt(this.getAttribute('data-id')); | |
const task = tasks.find(t => t.id === taskId); | |
if (task) { | |
// Populate the form with task data | |
document.getElementById('taskType').value = task.type; | |
document.getElementById('taskTitle').value = task.title; | |
document.getElementById('branchName').value = task.branch; | |
document.getElementById('mergeCommands').value = task.commands || ''; | |
// Check the appropriate service checkboxes | |
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { | |
const serviceName = checkbox.nextElementSibling.textContent; | |
checkbox.checked = task.services.includes(serviceName); | |
}); | |
// Show the modal | |
taskModal.classList.remove('hidden'); | |
// Remove the task from the array when saved | |
taskForm.onsubmit = function(e) { | |
e.preventDefault(); | |
const type = document.getElementById('taskType').value; | |
const title = document.getElementById('taskTitle').value.trim(); | |
const branch = document.getElementById('branchName').value.trim(); | |
if (!title || !branch) { | |
alert('Please fill in all required fields'); | |
return; | |
} | |
// Get selected services | |
const services = []; | |
document.querySelectorAll('input[type="checkbox"]:checked').forEach(checkbox => { | |
services.push(checkbox.nextElementSibling.textContent); | |
}); | |
const commands = document.getElementById('mergeCommands').value.trim(); | |
// Update the task | |
const updatedTask = { | |
...task, | |
type, | |
title, | |
branch, | |
services, | |
commands | |
}; | |
tasks = tasks.map(t => t.id === task.id ? updatedTask : t); | |
saveTasks(); | |
renderTasks(); | |
renderMergeStatus(); | |
taskModal.classList.add('hidden'); | |
taskForm.reset(); | |
taskForm.onsubmit = originalSubmitHandler; | |
}; | |
} | |
}); | |
}); | |
// Add event listeners for status update buttons | |
document.querySelectorAll('.update-status').forEach(btn => { | |
btn.addEventListener('click', function() { | |
const taskId = parseInt(this.getAttribute('data-id')); | |
const task = tasks.find(t => t.id === taskId); | |
if (task) { | |
if (task.devStatus === 'pending') { | |
task.devStatus = 'merged'; | |
} else if (task.masterStatus === 'pending') { | |
task.masterStatus = 'merged'; | |
} | |
saveTasks(); | |
renderTasks(); | |
renderMergeStatus(); | |
} | |
}); | |
}); | |
updateTaskCount(); | |
} | |
// Render merge status | |
function renderMergeStatus() { | |
mergeStatusContainer.innerHTML = ''; | |
tasks.forEach(task => { | |
const statusElement = document.createElement('div'); | |
statusElement.className = 'bg-white border border-gray-200 rounded-lg p-4 shadow-sm card-hover transition-all'; | |
statusElement.innerHTML = ` | |
<div class="flex justify-between items-start mb-2"> | |
<h3 class="font-semibold text-gray-800">${task.title}</h3> | |
<span class="text-xs font-mono bg-gray-100 px-2 py-1 rounded">${task.branch}</span> | |
</div> | |
<div class="grid grid-cols-2 gap-4"> | |
<div class="p-2 rounded text-center"> | |
<div class="text-xs font-medium text-gray-600 mb-1">DEV</div> | |
<span class="px-3 py-1 rounded-full text-xs font-medium status-${task.devStatus}"> | |
${task.devStatus === 'pending' ? 'Pending' : 'Merged'} | |
</span> | |
</div> | |
<div class="p-2 rounded text-center"> | |
<div class="text-xs font-medium text-gray-600 mb-1">MASTER</div> | |
<span class="px-3 py-1 rounded-full text-xs font-medium status-${task.masterStatus}"> | |
${task.masterStatus === 'pending' ? 'Pending' : 'Merged'} | |
</span> | |
</div> | |
</div> | |
${task.services.length > 0 ? ` | |
<div class="mt-3 pt-2 border-t border-gray-100"> | |
<div class="text-xs font-medium text-gray-600 mb-1">Services:</div> | |
<div class="flex flex-wrap gap-1"> | |
${task.services.map(service => ` | |
<span class="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">${service}</span> | |
`).join('')} | |
</div> | |
</div> | |
` : ''} | |
`; | |
mergeStatusContainer.appendChild(statusElement); | |
}); | |
} | |
// Store the original form submit handler | |
const originalSubmitHandler = taskForm.onsubmit; | |
// Initial render | |
renderTasks(); | |
renderMergeStatus(); | |
}); | |
</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=salmanarshad/tasks-and-bugs" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |