Download script.js from Wathuta/taskflow-calendar-commander: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/spaces/Wathuta/taskflow-calendar-commander/resolve/main/script.js
- Command line
-
hf download hf://spaces/Wathuta/taskflow-calendar-commander/script.js
-
curl -L -o script.js https://huggingface.co/spaces/Wathuta/taskflow-calendar-commander/resolve/main/script.js
11.1 kB
| // Global variables | |
| let currentDateRange = 'this-week'; | |
| let selectedCells = new Set(); | |
| let isCommandPanelOpen = false; | |
| let teamMembers = [ | |
| { | |
| id: 'alice', | |
| name: 'Alice Smith', | |
| avatar: 'http://static.photos/people/200x200/1', | |
| capacity: 8, | |
| role: 'Frontend Developer', | |
| status: 'available' | |
| }, | |
| { | |
| id: 'bob', | |
| name: 'Bob Wilson', | |
| avatar: 'http://static.photos/people/200x200/2', | |
| capacity: 6, | |
| role: 'Backend Developer', | |
| status: 'available' | |
| }, | |
| { | |
| id: 'charlie', | |
| name: 'Charlie Brown', | |
| avatar: 'http://static.photos/people/200x200/3', | |
| capacity: 8, | |
| role: 'QA Engineer', | |
| status: 'available' | |
| }, | |
| { | |
| id: 'diana', | |
| name: 'Diana Prince', | |
| avatar: 'http://static.photos/people/200x200/4', | |
| capacity: 7, | |
| role: 'Product Manager', | |
| status: 'available' | |
| } | |
| ]; | |
| // Column management | |
| let columnOrder = teamMembers.map(member => member.id); | |
| let columnWidths = {}; | |
| teamMembers.forEach(member => { | |
| columnWidths[member.id] = 200; // Default width | |
| }); | |
| let tasks = [ | |
| { id: 1, title: 'API Integration', assignee: 'alice', date: getDateString(new Date()), status: 'in-progress', priority: 'high', timeEstimate: 4, links: 2 }, | |
| { id: 2, title: 'Database Migration', assignee: 'bob', date: getDateString(new Date()), status: 'not-started', priority: 'medium', timeEstimate: 6, links: 0 }, | |
| { id: 3, title: 'Frontend Refactor', assignee: 'charlie', date: getDateString(new Date()), status: 'complete', priority: 'low', timeEstimate: 8, links: 1 }, | |
| { id: 4, title: 'Security Audit', assignee: 'diana', date: getDateString(new Date()), status: 'blocked', priority: 'high', timeEstimate: 3, links: 3 } | |
| ]; | |
| // Sprint boundaries | |
| let sprintBoundaries = [ | |
| { name: 'Sprint 1', start: new Date(), end: new Date(new Date().setDate(new Date().getDate() + 14)) } | |
| ]; | |
| // Utility functions | |
| function getDateString(date) { | |
| return date.toISOString().split('T')[0]; | |
| } | |
| function formatDateDisplay(date) { | |
| const options = { weekday: 'short', month: 'numeric', day: 'numeric' }; | |
| return date.toLocaleDateString('en-US', options); | |
| } | |
| function getDateRange(rangeType) { | |
| const today = new Date(); | |
| const start = new Date(today); | |
| const end = new Date(today); | |
| switch(rangeType) { | |
| case 'this-week': | |
| start.setDate(today.getDate() - today.getDay() + 1); | |
| end.setDate(start.getDate() + 4); | |
| break; | |
| case 'next-2-weeks': | |
| start.setDate(today.getDate() - today.getDay() + 1); | |
| end.setDate(start.getDate() + 9); | |
| break; | |
| case 'this-quarter': | |
| const quarter = Math.floor(today.getMonth() / 3); | |
| start.setMonth(quarter * 3, 1); | |
| end.setMonth((quarter + 1) * 3, 0); | |
| break; | |
| default: | |
| start.setDate(today.getDate() - today.getDay() + 1); | |
| end.setDate(start.getDate() + 4); | |
| } | |
| return { start, end }; | |
| } | |
| function generateDates(startDate, endDate) { | |
| const dates = []; | |
| const current = new Date(startDate); | |
| while (current <= endDate) { | |
| dates.push(new Date(current)); | |
| current.setDate(current.getDate() + 1); | |
| } | |
| return dates; | |
| } | |
| // Calendar initialization | |
| function initializeCalendar() { | |
| const calendarGrid = document.getElementById('calendar-grid'); | |
| const { start, end } = getDateRange(currentDateRange); | |
| const dates = generateDates(start, end); | |
| // Create calendar structure | |
| let calendarHTML = ` | |
| <div class="calendar-grid grid" style=" | |
| grid-template-columns: 120px repeat(${teamMembers.length}, 200px) 180px; | |
| min-width: ${120 + (teamMembers.length * 200) + 180}px; | |
| "> | |
| <!-- Date Column Header --> | |
| <div class="sticky left-0 z-20 bg-gray-50 border-r border-gray-200 p-3 font-semibold text-gray-700"> | |
| Date | |
| </div> | |
| `; | |
| // Team Member Column Headers | |
| teamMembers.forEach(member => { | |
| calendarHTML += ` | |
| <div class="team-member-header bg-white border-r border-gray-200 p-3 flex items-center justify-between sticky top-0 z-10"> | |
| <div class="flex items-center space-x-3"> | |
| <img src="${member.avatar}" alt="${member.name}" class="w-8 h-8 rounded-full"> | |
| <span class="font-semibold text-gray-800">${member.id}</span> | |
| </div> | |
| <button class="text-gray-400 hover:text-gray-600 transition-colors"> | |
| <i data-feather="x" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| `; | |
| }); | |
| // Milestone Column Header | |
| calendarHTML += ` | |
| <div class="sticky right-0 z-20 bg-blue-50 border-l border-blue-200 p-3 font-semibold text-blue-800"> | |
| Milestones | |
| </div> | |
| </div> | |
| `; | |
| // Date Rows | |
| dates.forEach((date, index) => { | |
| const dateStr = getDateString(date); | |
| const isWeekend = date.getDay() === 0 || date.getDay() === 6; | |
| const isSprintBoundary = sprintBoundaries.some(sprint => | |
| getDateString(sprint.start) === dateStr || getDateString(sprint.end) === dateStr | |
| ); | |
| // Add sprint boundary class if needed | |
| const rowClasses = isSprintBoundary ? 'sprint-boundary' : ''; | |
| calendarHTML += ` | |
| <!-- Date Cell --> | |
| <div class="sticky left-0 z-10 bg-gray-50 border-r border-gray-200 p-2 text-sm text-gray-600 ${isWeekend ? 'bg-gray-100' : ''} ${rowClasses}"> | |
| ${formatDateDisplay(date)} | |
| </div> | |
| `; | |
| // Team Member Cells | |
| teamMembers.forEach(member => { | |
| const cellTasks = tasks.filter(task => | |
| task.assignee === member.id && task.date === dateStr | |
| ); | |
| calendarHTML += ` | |
| <div class="cell bg-white border-r border-gray-200 p-1 min-h-16 relative ${isWeekend ? 'bg-gray-50' : ''} ${rowClasses}" | |
| data-date="${dateStr}" | |
| data-assignee="${member.id}" | |
| onclick="handleCellClick(event, '${dateStr}', '${member.id}')" | |
| ondragover="handleDragOver(event)" | |
| ondrop="handleDrop(event, '${dateStr}', '${member.id}')" | |
| > | |
| ${renderCellContent(cellTasks)} | |
| </div> | |
| `; | |
| }); | |
| // Milestone Cell | |
| calendarHTML += ` | |
| <div class="sticky right-0 z-10 bg-blue-50 border-l border-blue-200 p-2 ${isWeekend ? 'bg-blue-100' : ''} ${rowClasses}"> | |
| <!-- Milestone content --> | |
| </div> | |
| `; | |
| }); | |
| calendarGrid.innerHTML = calendarHTML; | |
| feather.replace(); | |
| } | |
| function renderCellContent(cellTasks) { | |
| if (cellTasks.length === 0) return ''; | |
| let content = ''; | |
| const visibleTasks = cellTasks.slice(0, 3); | |
| const hiddenCount = cellTasks.length - 3; | |
| visibleTasks.forEach(task => { | |
| content += ` | |
| <div class="task-item bg-white border border-gray-200 rounded p-1 mb-1 text-xs cursor-pointer" draggable="true" | |
| ondragstart="handleDragStart(event, ${task.id})" | |
| > | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center space-x-1"> | |
| <div class="status-indicator w-2 h-2 rounded-full status-${task.status}"></div> | |
| <span class="task-title flex-1 truncate">${task.title}</span> | |
| </div> | |
| </div> | |
| <div class="flex items-center justify-between mt-1"> | |
| <span class="time-estimate text-gray-500">${task.timeEstimate}h</span> | |
| ${task.links > 0 ? `<span class="link-count text-blue-500">🔗 ${task.links}</span>` : ''} | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| if (hiddenCount > 0) { | |
| content += ` | |
| <div class="more-indicator text-center text-gray-400 text-xs py-1"> | |
| +${hiddenCount} more | |
| </div> | |
| `; | |
| } | |
| return content; | |
| } | |
| // Event Handlers | |
| function handleCellClick(event, date, assignee) { | |
| event.stopPropagation(); | |
| const cell = event.currentTarget; | |
| const cellKey = `${date}-${assignee}`; | |
| if (event.ctrlKey || event.metaKey) { | |
| // Multi-select | |
| if (selectedCells.has(cellKey)) { | |
| selectedCells.delete(cellKey); | |
| cell.classList.remove('cell-selected'); | |
| } else { | |
| selectedCells.add(cellKey); | |
| cell.classList.add('cell-selected'); | |
| } | |
| } else if (event.shiftKey && selectedCells.size > 0) { | |
| // Range select | |
| // Implementation for range selection | |
| } else { | |
| // Single select | |
| clearCellSelection(); | |
| selectedCells.add(cellKey); | |
| cell.classList.add('cell-selected'); | |
| } | |
| } | |
| function clearCellSelection() { | |
| document.querySelectorAll('.cell').forEach(cell => { | |
| cell.classList.remove('cell-selected'); | |
| }); | |
| selectedCells.clear(); | |
| } | |
| function handleDragStart(event, taskId) { | |
| event.dataTransfer.setData('text/plain', taskId.toString()); | |
| event.currentTarget.classList.add('dragging'); | |
| } | |
| function handleDragOver(event) { | |
| event.preventDefault(); | |
| event.currentTarget.classList.add('drag-over-valid'); | |
| } | |
| function handleDrop(event, date, assignee) { | |
| event.preventDefault(); | |
| event.currentTarget.classList.remove('drag-over-valid', 'dragging'); | |
| const taskId = parseInt(event.dataTransfer.getData('text/plain')); | |
| const task = tasks.find(t => t.id === taskId); | |
| if (task) { | |
| // Update task assignment and date | |
| task.assignee = assignee; | |
| task.date = date; | |
| initializeCalendar(); // Refresh calendar | |
| } | |
| } | |
| // Quick Add Button | |
| document.getElementById('quick-add-btn')?.addEventListener('click', function() { | |
| // Open command panel with task creation template | |
| const commandPanel = document.querySelector('custom-command-panel'); | |
| if (commandPanel && commandPanel.openPanel) { | |
| commandPanel.openPanel('task-create'); | |
| } | |
| }); | |
| // Keyboard shortcuts | |
| document.addEventListener('keydown', function(event) { | |
| // Ctrl+K or Cmd+K to open command panel | |
| if ((event.ctrlKey || event.metaKey) && event.key === 'k') { | |
| event.preventDefault(); | |
| const commandPanel = document.querySelector('custom-command-panel'); | |
| if (commandPanel && commandPanel.togglePanel) { | |
| commandPanel.togglePanel(); | |
| } | |
| } | |
| // Escape to clear selection | |
| if (event.key === 'Escape') { | |
| clearCellSelection(); | |
| } | |
| }); | |
| // Export functions for web components | |
| window.CalendarUtils = { | |
| getDateString, | |
| formatDateDisplay, | |
| getDateRange, | |
| generateDates, | |
| teamMembers, | |
| tasks, | |
| selectedCells, | |
| clearCellSelection | |
| }; |