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; | |
| } | |
| .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> |