tasksandbugs / index.html
salmanarshad's picture
Add 3 files
e7b3cbb verified
<!DOCTYPE html>
<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;
}
.service-tag {
display: inline-flex;
align-items: center;
background-color: #ebf8ff;
color: #3182ce;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
margin-right: 0.25rem;
margin-bottom: 0.25rem;
}
.service-tag-remove {
margin-left: 0.25rem;
cursor: pointer;
color: #3182ce;
}
.service-input-container {
display: flex;
flex-wrap: wrap;
align-items: center;
min-height: 42px;
padding: 0.25rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
}
.service-input {
flex: 1;
min-width: 150px;
border: none;
outline: none;
padding: 0.25rem;
}
</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="service-input-container">
<div id="serviceTagsContainer" class="flex flex-wrap"></div>
<input type="text" id="serviceInput" class="service-input" placeholder="Type service name and press Enter">
</div>
<div class="mt-1 text-xs text-gray-500">Press Enter to add a service</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;
let currentEditingTaskId = null;
// 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');
const serviceInput = document.getElementById('serviceInput');
const serviceTagsContainer = document.getElementById('serviceTagsContainer');
// 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', () => {
currentEditingTaskId = null;
serviceTagsContainer.innerHTML = '';
taskModal.classList.remove('hidden');
});
cancelTaskBtn.addEventListener('click', () => {
taskModal.classList.add('hidden');
});
// Service input functionality
serviceInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && this.value.trim()) {
e.preventDefault();
addServiceTag(this.value.trim());
this.value = '';
}
});
function addServiceTag(serviceName) {
const tagId = Date.now();
const tagElement = document.createElement('div');
tagElement.className = 'service-tag';
tagElement.innerHTML = `
${serviceName}
<span class="service-tag-remove" data-tag-id="${tagId}">
<i class="fas fa-times"></i>
</span>
`;
serviceTagsContainer.appendChild(tagElement);
// Add remove event listener
tagElement.querySelector('.service-tag-remove').addEventListener('click', function() {
this.parentElement.remove();
});
}
function renderServiceTags(services) {
serviceTagsContainer.innerHTML = '';
services.forEach(service => {
addServiceTag(service);
});
}
function getCurrentServices() {
const serviceTags = Array.from(serviceTagsContainer.querySelectorAll('.service-tag'));
return serviceTags.map(tag => {
return tag.textContent.trim().replace('×', '').trim();
});
}
// 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 services from tags
const services = getCurrentServices();
const commands = document.getElementById('mergeCommands').value.trim();
if (currentEditingTaskId) {
// Update existing task
tasks = tasks.map(task => {
if (task.id === currentEditingTaskId) {
return {
...task,
type,
title,
branch,
services,
commands
};
}
return task;
});
} else {
// Create new task
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();
serviceTagsContainer.innerHTML = '';
currentEditingTaskId = null;
});
// 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="branch-name text-sm ml-2 font-mono bg-gray-100 px-2 py-1 rounded cursor-pointer hover:bg-gray-200" data-id="${task.id}">${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>
</div>
</div>
`;
tasksContainer.appendChild(taskElement);
});
// Add event listeners for branch name clicks (status toggle)
document.querySelectorAll('.branch-name').forEach(branch => {
branch.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';
} else {
// Reset both statuses if both are merged
task.devStatus = 'pending';
task.masterStatus = 'pending';
}
saveTasks();
renderTasks();
renderMergeStatus();
}
});
});
// 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) {
currentEditingTaskId = taskId;
// 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 || '';
// Render service tags
renderServiceTags(task.services);
// Show the modal
taskModal.classList.remove('hidden');
}
});
});
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="branch-name text-xs font-mono bg-gray-100 px-2 py-1 rounded cursor-pointer hover:bg-gray-200" data-id="${task.id}">${task.branch}</span>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="p-2 rounded text-center cursor-pointer status-dev" data-id="${task.id}">
<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 cursor-pointer status-master" data-id="${task.id}">
<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);
});
// Add click handlers for status toggling
document.querySelectorAll('.status-dev').forEach(el => {
el.addEventListener('click', function() {
const taskId = parseInt(this.getAttribute('data-id'));
const task = tasks.find(t => t.id === taskId);
if (task) {
task.devStatus = task.devStatus === 'pending' ? 'merged' : 'pending';
saveTasks();
renderTasks();
renderMergeStatus();
}
});
});
document.querySelectorAll('.status-master').forEach(el => {
el.addEventListener('click', function() {
const taskId = parseInt(this.getAttribute('data-id'));
const task = tasks.find(t => t.id === taskId);
if (task) {
task.masterStatus = task.masterStatus === 'pending' ? 'merged' : 'pending';
saveTasks();
renderTasks();
renderMergeStatus();
}
});
});
}
// 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/tasksandbugs" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>