Add Task Archive feature.

This commit is contained in:
2025-07-06 08:49:09 +02:00
parent 8f63817194
commit 397175f38e
5 changed files with 489 additions and 4 deletions

View File

@@ -40,6 +40,7 @@
<option value="IN_PROGRESS">In Progress</option>
<option value="ON_HOLD">On Hold</option>
<option value="COMPLETED">Completed</option>
<option value="ARCHIVED">Archived</option>
</select>
</div>
</div>

View File

@@ -22,6 +22,7 @@
<div class="management-actions task-actions">
<button id="add-task-btn" class="btn btn-primary">+ Add Task</button>
<button id="refresh-tasks" class="btn btn-secondary">🔄 Refresh</button>
<button id="toggle-archived" class="btn btn-outline" title="Show/Hide Archived Tasks">📦 Show Archived</button>
</div>
</div>
</div>
@@ -44,6 +45,10 @@
<div class="stat-number" id="overdue-tasks">0</div>
<div class="stat-label">Overdue</div>
</div>
<div class="stat-card" id="archived-stat-card" style="display: none;">
<div class="stat-number" id="archived-tasks">0</div>
<div class="stat-label">Archived</div>
</div>
</div>
<!-- Task Board -->
@@ -87,6 +92,16 @@
<!-- Task cards will be populated here -->
</div>
</div>
<div class="task-column archived-column" data-status="ARCHIVED" style="display: none;">
<div class="column-header">
<h3>📦 Archived</h3>
<span class="task-count">0</span>
</div>
<div class="column-content" id="column-ARCHIVED">
<!-- Archived task cards will be populated here -->
</div>
</div>
</div>
<!-- Loading and Error States -->
@@ -283,6 +298,88 @@
font-size: 0.8rem;
}
/* Archived Column Styles */
.archived-column {
background: #f1f3f4;
border: 2px dashed #9aa0a6;
opacity: 0.8;
}
.archived-column .column-header {
border-bottom-color: #9aa0a6;
}
.archived-column .task-count {
background: #9aa0a6;
}
/* Archived Task Card Styles */
.archived-column .task-card {
opacity: 0.7;
border-left: 4px solid #9aa0a6;
}
/* Button Styles */
.btn.btn-outline {
background: transparent;
border: 1px solid #6c757d;
color: #6c757d;
}
.btn.btn-outline:hover {
background: #6c757d;
color: white;
}
.btn.btn-outline.active {
background: #6c757d;
color: white;
}
/* Task Action Buttons */
.task-actions {
margin-top: 0.5rem;
display: flex;
justify-content: flex-end;
gap: 0.25rem;
}
.archive-btn, .restore-btn {
background: none;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.25rem 0.5rem;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.archive-btn:hover {
background: #f8f9fa;
border-color: #6c757d;
}
.restore-btn:hover {
background: #e3f2fd;
border-color: #007bff;
}
/* Task Date Displays */
.task-completed-date, .task-archived-date {
font-size: 0.75rem;
color: #6c757d;
margin-top: 0.25rem;
font-style: italic;
}
.task-completed-date {
color: #28a745;
}
.task-archived-date {
color: #9aa0a6;
}
.column-content {
min-height: 400px;
padding: 0.5rem 0;
@@ -879,6 +976,7 @@ class UnifiedTaskManager {
};
this.currentTask = null;
this.sortableInstances = [];
this.showArchived = false;
this.currentUserId = {{ g.user.id|tojson }};
this.smartSearch = new SmartTaskSearch();
this.searchQuery = '';
@@ -905,6 +1003,10 @@ class UnifiedTaskManager {
document.getElementById('refresh-tasks').addEventListener('click', () => {
this.loadTasks();
});
document.getElementById('toggle-archived').addEventListener('click', () => {
this.toggleArchivedView();
});
// Date validation
document.getElementById('task-due-date').addEventListener('blur', () => {
@@ -1287,7 +1389,8 @@ class UnifiedTaskManager {
'NOT_STARTED': [],
'IN_PROGRESS': [],
'ON_HOLD': [],
'COMPLETED': []
'COMPLETED': [],
'ARCHIVED': []
};
filteredTasks.forEach(task => {
@@ -1404,8 +1507,24 @@ class UnifiedTaskManager {
card.addEventListener('click', () => this.openTaskModal(task));
const dueDate = task.due_date ? new Date(task.due_date) : null;
const isOverdue = dueDate && dueDate < new Date() && task.status !== 'COMPLETED';
const isOverdue = dueDate && dueDate < new Date() && task.status !== 'COMPLETED' && task.status !== 'ARCHIVED';
// Add archive/restore buttons for completed and archived tasks
let actionButtons = '';
if (task.status === 'COMPLETED') {
actionButtons = `
<div class="task-actions">
<button class="archive-btn" onclick="taskManager.archiveTask(${task.id}); event.stopPropagation();" title="Archive Task">📦</button>
</div>
`;
} else if (task.status === 'ARCHIVED') {
actionButtons = `
<div class="task-actions">
<button class="restore-btn" onclick="taskManager.restoreTask(${task.id}); event.stopPropagation();" title="Restore Task">↩️</button>
</div>
`;
}
card.innerHTML = `
<div class="task-card-header">
<div class="task-title-section">
@@ -1419,6 +1538,9 @@ class UnifiedTaskManager {
<span class="task-assignee">${task.assigned_to_name || 'Unassigned'}</span>
${dueDate ? `<span class="task-due-date ${isOverdue ? 'overdue' : ''}">${formatUserDate(task.due_date)}</span>` : ''}
</div>
${task.status === 'COMPLETED' ? `<div class="task-completed-date">Completed: ${formatUserDate(task.completed_date)}</div>` : ''}
${task.status === 'ARCHIVED' ? `<div class="task-archived-date">Archived: ${formatUserDate(task.archived_date)}</div>` : ''}
${actionButtons}
`;
return card;
@@ -1432,15 +1554,84 @@ class UnifiedTaskManager {
const total = this.tasks.length;
const completed = this.tasks.filter(t => t.status === 'COMPLETED').length;
const inProgress = this.tasks.filter(t => t.status === 'IN_PROGRESS').length;
const archived = this.tasks.filter(t => t.status === 'ARCHIVED').length;
const overdue = this.tasks.filter(t => {
const dueDate = t.due_date ? new Date(t.due_date) : null;
return dueDate && dueDate < new Date() && t.status !== 'COMPLETED';
return dueDate && dueDate < new Date() && t.status !== 'COMPLETED' && t.status !== 'ARCHIVED';
}).length;
document.getElementById('total-tasks').textContent = total;
document.getElementById('completed-tasks').textContent = completed;
document.getElementById('in-progress-tasks').textContent = inProgress;
document.getElementById('overdue-tasks').textContent = overdue;
document.getElementById('archived-tasks').textContent = archived;
}
toggleArchivedView() {
this.showArchived = !this.showArchived;
const toggleBtn = document.getElementById('toggle-archived');
const archivedColumn = document.querySelector('.archived-column');
const archivedStatCard = document.getElementById('archived-stat-card');
if (this.showArchived) {
toggleBtn.textContent = '📦 Hide Archived';
toggleBtn.classList.add('active');
archivedColumn.style.display = 'block';
archivedStatCard.style.display = 'block';
} else {
toggleBtn.textContent = '📦 Show Archived';
toggleBtn.classList.remove('active');
archivedColumn.style.display = 'none';
archivedStatCard.style.display = 'none';
}
this.renderTasks();
}
async archiveTask(taskId) {
if (confirm('Are you sure you want to archive this task? It will be moved to the archived section.')) {
try {
const response = await fetch(`/api/tasks/${taskId}/archive`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
if (data.success) {
await this.loadTasks();
} else {
alert('Failed to archive task: ' + data.message);
}
} catch (error) {
console.error('Error archiving task:', error);
alert('Failed to archive task: ' + error.message);
}
}
}
async restoreTask(taskId) {
if (confirm('Are you sure you want to restore this task? It will be moved back to completed status.')) {
try {
const response = await fetch(`/api/tasks/${taskId}/restore`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
if (data.success) {
await this.loadTasks();
} else {
alert('Failed to restore task: ' + data.message);
}
} catch (error) {
console.error('Error restoring task:', error);
alert('Failed to restore task: ' + error.message);
}
}
}
async handleTaskMove(evt) {