Files
KeepItGoingServer/templates/offline_tasks.html
T
Keith SmithandClaude Sonnet 5 a6f227e931 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>
2026-09-04 23:45:43 -06:00

123 lines
6.1 KiB
HTML

{% load static %}<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline - KeepItGoing</title>
<link rel="icon" type="image/svg+xml" href="{% static 'favicons/favicon.svg' %}">
<link rel="stylesheet" href="{% static 'css/app.css' %}">
<script>
(function() {
var saved = localStorage.getItem('theme');
var systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (saved === 'dark' || (!saved && systemDark)) {
document.documentElement.setAttribute('data-theme', 'dark');
}
})();
</script>
</head>
<body>
<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>
<!-- 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>
<!-- 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>
<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>
<script src="{% static 'js/offline-tasks.js' %}"></script>
</body>
</html>