Internal
Public Access
Match dashboard filter/sort behavior in offline task app
The offline task app was showing every cached task including completed ones, with no sorting -- unlike the online dashboard, which hides completed tasks by default and sorts by due date. Ports the same filter tabs (All/Today/Upcoming/Overdue/Completed) and sort options (due date asc/desc, priority high/low) from tasks/views.py's DashboardView so offline mode isn't a degraded view. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
19697fbc01
commit
fc045a0c4b
@@ -2,15 +2,73 @@
|
||||
* KeepItGoing - Offline mini task app
|
||||
* Renders and edits tasks straight from IndexedDB while there's no network.
|
||||
* Reuses the .task-item/.task-checkbox/.priority-badge classes from
|
||||
* templates/tasks/_task_item.html for visual consistency with the real app.
|
||||
* templates/tasks/_task_item.html for visual consistency with the real app,
|
||||
* and mirrors the dashboard's default filter/sort behavior (tasks/views.py
|
||||
* DashboardView) so offline mode isn't a degraded, unsorted, unfiltered view.
|
||||
*/
|
||||
|
||||
const PRIORITY_ORDER = { urgent: 4, high: 3, medium: 2, low: 1 };
|
||||
|
||||
let currentFilter = 'all';
|
||||
let currentSort = 'due_date';
|
||||
|
||||
function todayLocalStr() {
|
||||
const d = new Date();
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${day}`;
|
||||
}
|
||||
|
||||
function formatDueDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const date = new Date(dateStr + 'T00:00:00');
|
||||
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
}
|
||||
|
||||
function applyFilter(tasks, filter) {
|
||||
const today = todayLocalStr();
|
||||
if (filter === 'completed') {
|
||||
return tasks.filter((t) => t.status === 'completed');
|
||||
}
|
||||
// Every other filter excludes completed by default, matching the
|
||||
// online dashboard's default behavior.
|
||||
const notCompleted = tasks.filter((t) => t.status !== 'completed');
|
||||
switch (filter) {
|
||||
case 'today':
|
||||
return notCompleted.filter((t) => t.due_date === today);
|
||||
case 'upcoming':
|
||||
return notCompleted.filter((t) => t.due_date && t.due_date > today);
|
||||
case 'overdue':
|
||||
return notCompleted.filter((t) => t.due_date && t.due_date < today && t.status !== 'cancelled');
|
||||
default:
|
||||
return notCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
function applySort(tasks, sort) {
|
||||
const sorted = [...tasks];
|
||||
const dueOrDefault = (t) => t.due_date || '9999-99-99';
|
||||
const priorityOf = (t) => PRIORITY_ORDER[t.priority] || 0;
|
||||
|
||||
switch (sort) {
|
||||
case 'due_date_desc':
|
||||
sorted.sort((a, b) => dueOrDefault(b).localeCompare(dueOrDefault(a)) || priorityOf(b) - priorityOf(a));
|
||||
break;
|
||||
case 'priority':
|
||||
sorted.sort((a, b) => priorityOf(b) - priorityOf(a) || dueOrDefault(a).localeCompare(dueOrDefault(b)));
|
||||
break;
|
||||
case 'priority_low':
|
||||
sorted.sort((a, b) => priorityOf(a) - priorityOf(b) || dueOrDefault(a).localeCompare(dueOrDefault(b)));
|
||||
break;
|
||||
case 'due_date':
|
||||
default:
|
||||
sorted.sort((a, b) => dueOrDefault(a).localeCompare(dueOrDefault(b)) || priorityOf(b) - priorityOf(a));
|
||||
break;
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function renderTaskItem(task) {
|
||||
const item = document.createElement('div');
|
||||
item.className = `task-item priority-${task.priority}${task.status === 'completed' ? ' completed' : ''}`;
|
||||
@@ -98,17 +156,18 @@ async function renderOfflineTasks() {
|
||||
if (!listEl) return;
|
||||
|
||||
const allTasks = await getAllTasks();
|
||||
const visibleTasks = allTasks
|
||||
.filter((t) => !t.is_deleted)
|
||||
.sort((a, b) => (a.due_date || '9999').localeCompare(b.due_date || '9999'));
|
||||
const nonDeleted = allTasks.filter((t) => !t.is_deleted);
|
||||
const visibleTasks = applySort(applyFilter(nonDeleted, currentFilter), currentSort);
|
||||
|
||||
listEl.innerHTML = '';
|
||||
|
||||
if (visibleTasks.length === 0) {
|
||||
const lastSyncToken = await getMeta('last_sync_token');
|
||||
emptyEl.textContent = lastSyncToken
|
||||
? 'No tasks yet.'
|
||||
: "No offline data yet — connect once while online to sync your tasks.";
|
||||
if (!lastSyncToken) {
|
||||
emptyEl.textContent = "No offline data yet — connect once while online to sync your tasks.";
|
||||
} else {
|
||||
emptyEl.textContent = 'No tasks here.';
|
||||
}
|
||||
emptyEl.hidden = false;
|
||||
listEl.hidden = true;
|
||||
return;
|
||||
@@ -121,6 +180,14 @@ async function renderOfflineTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveFilterButton(filter) {
|
||||
document.querySelectorAll('#offline-filter-tabs button').forEach((btn) => {
|
||||
const isActive = btn.dataset.filter === filter;
|
||||
btn.classList.toggle('btn-primary', isActive);
|
||||
btn.classList.toggle('btn-secondary', !isActive);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderOfflineTasks();
|
||||
|
||||
@@ -136,4 +203,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('#offline-filter-tabs button').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
currentFilter = btn.dataset.filter;
|
||||
setActiveFilterButton(currentFilter);
|
||||
renderOfflineTasks();
|
||||
});
|
||||
});
|
||||
|
||||
const sortSelect = document.getElementById('offline-sort-select');
|
||||
if (sortSelect) {
|
||||
sortSelect.value = currentSort;
|
||||
sortSelect.addEventListener('change', () => {
|
||||
currentSort = sortSelect.value;
|
||||
renderOfflineTasks();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user