Internal
Public Access
Bring offline mode to full parity with the online dashboard
Fixes subtasks appearing flattened into the main task list (missing parent filter), and adds everything else needed for full offline functionality: tag chips/assignment/creation, full field editing (description, due time, recurrence presets), time tracking with a live-updating timer, and properly nested subtasks -- all queued locally and synced via the existing /api/sync/ protocol. The offline page now reuses the real dashboard's app-layout/header/ sidebar/detail-pane structure instead of a bespoke layout, with an amber-tinted header and banner so it's unmistakable which mode you're in. The detail panel mirrors _task_detail.html's fields and actions. Also fixes a real backend gap this feature depends on: apply_conflict_data() in sync/views.py silently no-op'd on time_entry conflicts. And computes duration_seconds client-side on merge, since TimeEntrySyncSerializer doesn't include it in the wire format at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
fc045a0c4b
commit
a6f227e931
@@ -17,38 +17,103 @@
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="max-width: 640px; margin: 0 auto; padding: var(--space-lg);">
|
||||
<div class="task-pane-header">
|
||||
<h1 class="task-pane-title">Tasks (Offline)</h1>
|
||||
<select id="offline-sort-select" class="form-select" style="width: auto;">
|
||||
<option value="due_date">Due Date (Earliest)</option>
|
||||
<option value="due_date_desc">Due Date (Latest)</option>
|
||||
<option value="priority">Priority (High to Low)</option>
|
||||
<option value="priority_low">Priority (Low to High)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="app-layout detail-closed" id="app">
|
||||
<!-- Header (offline-tinted so it's unmistakable which mode you're in) -->
|
||||
<header class="app-header" style="background-color: #d97706; color: #fff; border-bottom-color: #b45309;">
|
||||
<div style="display: flex; align-items: center; gap: var(--space-md);">
|
||||
<button class="sidebar-toggle" onclick="toggleOfflineSidebar()" aria-label="Toggle sidebar" style="color: #fff;">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 12h18M3 6h18M3 18h18"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="app-brand" style="color: #fff;">KeepItGoing</span>
|
||||
<span style="font-size: var(--font-sm); font-weight: 600;">⚠ Offline — changes sync automatically once you're back online</span>
|
||||
</div>
|
||||
<div class="app-header-actions">
|
||||
<button class="theme-toggle" onclick="toggleOfflineTheme()" aria-label="Toggle theme" title="Toggle dark/light mode" style="color: #fff;">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"/>
|
||||
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<p class="text-muted" style="margin-bottom: var(--space-lg);">
|
||||
You're offline. Changes you make here will sync automatically once you're back online.
|
||||
</p>
|
||||
<!-- Sidebar -->
|
||||
<aside class="app-sidebar" id="sidebar">
|
||||
<div class="sidebar-section">
|
||||
<div class="sidebar-section-title">Filters</div>
|
||||
<nav class="sidebar-nav" id="offline-filter-nav">
|
||||
<a href="#" class="sidebar-item active" data-filter="all">
|
||||
<svg class="sidebar-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 12h18M3 6h18M3 18h18"/>
|
||||
</svg>
|
||||
All Tasks
|
||||
</a>
|
||||
<a href="#" class="sidebar-item" data-filter="today">
|
||||
<svg class="sidebar-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v6l4 2"/>
|
||||
</svg>
|
||||
Today
|
||||
</a>
|
||||
<a href="#" class="sidebar-item" data-filter="upcoming">
|
||||
<svg class="sidebar-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
||||
<path d="M16 2v4M8 2v4M3 10h18"/>
|
||||
</svg>
|
||||
Upcoming
|
||||
</a>
|
||||
<a href="#" class="sidebar-item" data-filter="overdue">
|
||||
<svg class="sidebar-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 8v4M12 16h.01"/>
|
||||
</svg>
|
||||
Overdue
|
||||
</a>
|
||||
<a href="#" class="sidebar-item" data-filter="completed">
|
||||
<svg class="sidebar-item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
||||
<path d="M22 4L12 14.01l-3-3"/>
|
||||
</svg>
|
||||
Completed
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div id="offline-filter-tabs" style="display: flex; flex-wrap: wrap; gap: var(--space-sm); margin-bottom: var(--space-lg);">
|
||||
<button type="button" class="btn btn-sm btn-primary" data-filter="all">All Tasks</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-filter="today">Today</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-filter="upcoming">Upcoming</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-filter="overdue">Overdue</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-filter="completed">Completed</button>
|
||||
</div>
|
||||
<!-- Main Task Pane -->
|
||||
<main class="task-pane">
|
||||
<div class="task-pane-header">
|
||||
<h1 class="task-pane-title" id="offline-page-title">All Tasks</h1>
|
||||
<select id="offline-sort-select" class="form-select" style="width: auto;">
|
||||
<option value="due_date">Due Date (Earliest)</option>
|
||||
<option value="due_date_desc">Due Date (Latest)</option>
|
||||
<option value="priority">Priority (High to Low)</option>
|
||||
<option value="priority_low">Priority (Low to High)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<form id="offline-add-task-form" class="quick-add">
|
||||
<input type="text" id="offline-task-title" class="quick-add-input" placeholder="Add a new task..." required>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</form>
|
||||
<form id="offline-add-task-form" class="quick-add">
|
||||
<input type="text" id="offline-task-title" class="quick-add-input" placeholder="Add a new task..." required>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</form>
|
||||
|
||||
<div id="offline-empty-state" class="empty-state" hidden>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div class="task-list" id="offline-task-list"></div>
|
||||
<div id="offline-empty-state" class="empty-state" hidden>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div class="task-list" id="offline-task-list"></div>
|
||||
</main>
|
||||
|
||||
<!-- Detail Panel -->
|
||||
<aside class="detail-pane hidden" id="detail-pane">
|
||||
<div class="empty-state">
|
||||
<p>Select a task to view details</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Mobile overlay -->
|
||||
<div class="mobile-overlay" id="mobile-overlay" onclick="closeOfflineMobileMenus()"></div>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'js/offline-db.js' %}"></script>
|
||||
|
||||
Reference in New Issue
Block a user