Initial commit: KeepItGoing task management server

Features:
- Django-based REST API with web interface
- Task management with tags, priorities, and due dates
- Time tracking with start/stop timers
- Subtasks support
- Task filtering (all, today, upcoming, overdue, completed)
- Tag-based organization with color coding
- Sorting by due date and priority
- Auto-assign tags when filtering
- Responsive 3-pane layout (sidebar, task list, detail panel)
- Task sharing between users
- Mobile-responsive design with dark mode support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Keith Smith
2025-12-18 17:41:29 -07:00
co-authored by Claude Sonnet 4.5
commit 6a0b35c39c
84 changed files with 7763 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
{% 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>{% block title %}KeepItGoing{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/app.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
{% block body %}
{% if user.is_authenticated %}
<div class="app-layout {% if not selected_task %}detail-closed{% endif %}" id="app">
<!-- Header -->
<header class="app-header">
<div style="display: flex; align-items: center; gap: var(--space-md);">
<button class="sidebar-toggle" onclick="toggleSidebar()" aria-label="Toggle sidebar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12h18M3 6h18M3 18h18"/>
</svg>
</button>
<a href="{% url 'dashboard' %}" class="app-brand">KeepItGoing</a>
</div>
<div class="app-header-actions">
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme" title="Toggle dark/light mode">
<svg id="theme-icon-light" 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>
<svg id="theme-icon-dark" style="display: none;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
</button>
<div class="header-user">
<span>{{ user.email }}</span>
<a href="{% url 'logout' %}" class="btn btn-secondary btn-sm">Logout</a>
</div>
</div>
</header>
<!-- Sidebar -->
<aside class="app-sidebar" id="sidebar">
{% include 'tasks/_sidebar.html' %}
</aside>
<!-- Main Task Pane -->
<main class="task-pane">
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% block content %}{% endblock %}
</main>
<!-- Detail Panel -->
<aside class="detail-pane {% if not selected_task %}hidden{% endif %}" id="detail-pane">
{% block detail %}
{% if selected_task %}
{% include 'tasks/_task_detail.html' with task=selected_task %}
{% else %}
<div class="empty-state">
<p>Select a task to view details</p>
</div>
{% endif %}
{% endblock %}
</aside>
<!-- Mobile overlay -->
<div class="mobile-overlay" id="mobile-overlay" onclick="closeMobileMenus()"></div>
</div>
<!-- Tag Edit/Create Modal (outside app-layout for proper fixed positioning) -->
<div class="modal-backdrop" id="tag-modal-backdrop">
<div class="modal" onclick="event.stopPropagation()">
<div class="modal-header">
<h2 class="modal-title" id="tag-modal-title">New Tag</h2>
<button type="button" class="modal-close" onclick="closeTagModal()">
<svg style="width: 20px; height: 20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
<form id="tag-form" method="post">
{% csrf_token %}
<input type="hidden" id="tag-id" name="group_id" value="">
<div class="form-group">
<label class="form-label" for="tag-name">Name</label>
<input type="text" class="form-input" id="tag-name" name="name" required placeholder="Tag name">
</div>
<div class="form-group">
<label class="form-label">Color</label>
<div class="color-presets">
<button type="button" class="color-preset" data-color="#3b82f6" style="background-color: #3b82f6"></button>
<button type="button" class="color-preset" data-color="#ef4444" style="background-color: #ef4444"></button>
<button type="button" class="color-preset" data-color="#10b981" style="background-color: #10b981"></button>
<button type="button" class="color-preset" data-color="#f59e0b" style="background-color: #f59e0b"></button>
<button type="button" class="color-preset" data-color="#8b5cf6" style="background-color: #8b5cf6"></button>
<button type="button" class="color-preset" data-color="#ec4899" style="background-color: #ec4899"></button>
<button type="button" class="color-preset" data-color="#06b6d4" style="background-color: #06b6d4"></button>
<button type="button" class="color-preset" data-color="#84cc16" style="background-color: #84cc16"></button>
</div>
<div class="color-custom-row">
<label class="color-custom-label">Custom:</label>
<input type="color" id="tag-color" name="color" value="#3b82f6" class="color-custom-input">
</div>
<input type="hidden" id="tag-color-hidden" name="color" value="#3b82f6">
</div>
<div class="modal-actions">
<button type="button" class="btn btn-secondary" onclick="closeTagModal()">Cancel</button>
<button type="button" class="btn btn-danger" id="tag-delete-btn" onclick="deleteTag()" style="display: none;">Delete</button>
<button type="submit" class="btn btn-primary" id="tag-submit-btn">Create</button>
</div>
</form>
</div>
</div>
{% else %}
<!-- Non-authenticated layout (auth pages) -->
<div class="auth-layout">
{% block auth_content %}{% endblock %}
</div>
<footer class="footer">
<p>&copy; 2025 Firebug IT. All rights reserved.</p>
</footer>
{% endif %}
{% endblock %}
<script src="{% static 'js/app.js' %}?v=3"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
+89
View File
@@ -0,0 +1,89 @@
<!-- Filters Section -->
<div class="sidebar-section">
<div class="sidebar-section-title">Filters</div>
<nav class="sidebar-nav">
<a href="{% url 'dashboard' %}?filter=all{% if current_tag_id %}&tag={{ current_tag_id }}{% endif %}"
class="sidebar-item {% if current_filter == 'all' or not current_filter %}active{% endif %}">
<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
{% if task_counts.all %}<span class="sidebar-item-count">{{ task_counts.all }}</span>{% endif %}
</a>
<a href="{% url 'dashboard' %}?filter=today{% if current_tag_id %}&tag={{ current_tag_id }}{% endif %}"
class="sidebar-item {% if current_filter == 'today' %}active{% endif %}">
<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
{% if task_counts.today %}<span class="sidebar-item-count">{{ task_counts.today }}</span>{% endif %}
</a>
<a href="{% url 'dashboard' %}?filter=upcoming{% if current_tag_id %}&tag={{ current_tag_id }}{% endif %}"
class="sidebar-item {% if current_filter == 'upcoming' %}active{% endif %}">
<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
{% if task_counts.upcoming %}<span class="sidebar-item-count">{{ task_counts.upcoming }}</span>{% endif %}
</a>
<a href="{% url 'dashboard' %}?filter=overdue{% if current_tag_id %}&tag={{ current_tag_id }}{% endif %}"
class="sidebar-item {% if current_filter == 'overdue' %}active{% endif %}">
<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
{% if task_counts.overdue %}<span class="sidebar-item-count text-danger">{{ task_counts.overdue }}</span>{% endif %}
</a>
<a href="{% url 'dashboard' %}?filter=completed{% if current_tag_id %}&tag={{ current_tag_id }}{% endif %}"
class="sidebar-item {% if current_filter == 'completed' %}active{% endif %}">
<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
{% if task_counts.completed %}<span class="sidebar-item-count">{{ task_counts.completed }}</span>{% endif %}
</a>
</nav>
</div>
<!-- Tags Section -->
<div class="sidebar-section">
<div class="sidebar-section-title">Tags</div>
<nav class="sidebar-nav">
{% for tag in tags %}
<div class="sidebar-group-row">
<a href="{% url 'dashboard' %}?{% if current_filter %}filter={{ current_filter }}&{% endif %}tag={{ tag.id }}"
class="sidebar-item sidebar-group-item {% if current_tag_id == tag.id|stringformat:'s' %}active{% endif %}">
<span class="group-color-dot" style="background-color: {{ tag.color }}"></span>
{{ tag.name }}
{% if tag.task_count %}<span class="sidebar-item-count">{{ tag.task_count }}</span>{% endif %}
</a>
<button type="button" class="sidebar-group-edit" title="Edit tag"
onclick="openTagModal('{{ tag.id }}', '{{ tag.name|escapejs }}', '{{ tag.color }}')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
{% empty %}
<div class="sidebar-item text-muted" style="font-size: var(--font-sm); cursor: default;">
No tags yet
</div>
{% endfor %}
<a href="{% url 'dashboard' %}?{% if current_filter %}filter={{ current_filter }}{% endif %}"
class="sidebar-item sidebar-group-item {% if not current_tag_id %}active{% endif %}">
<span class="group-color-dot" style="background-color: var(--text-muted)"></span>
All Tags
</a>
</nav>
<button type="button" class="sidebar-add-btn" onclick="openTagModal()">
<svg style="width: 14px; height: 14px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 5v14M5 12h14"/>
</svg>
Add Tag
</button>
</div>
+162
View File
@@ -0,0 +1,162 @@
<!-- Task Detail Panel -->
<div class="detail-header">
<h2 style="font-size: var(--font-lg); font-weight: 600;">Task Details</h2>
<button class="detail-close" onclick="closeDetail()" aria-label="Close detail panel">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
<form method="post" action="{% url 'task-detail' task.id %}" id="task-detail-form">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}?selected={{ task.id }}{% if request.GET.filter %}&filter={{ request.GET.filter }}{% endif %}{% if request.GET.tag %}&tag={{ request.GET.tag }}{% endif %}">
<!-- Title -->
<div class="form-group">
<label class="form-label" for="detail-title">Title</label>
<input type="text" class="form-input" id="detail-title" name="title" value="{{ task.title }}" required>
</div>
<!-- Description -->
<div class="form-group">
<label class="form-label" for="detail-description">Description</label>
<textarea class="form-textarea" id="detail-description" name="description" rows="3">{{ task.description }}</textarea>
</div>
<!-- Status & Priority -->
<div class="form-row">
<div class="form-group">
<label class="form-label" for="detail-status">Status</label>
<select class="form-select" id="detail-status" name="status">
<option value="pending" {% if task.status == 'pending' %}selected{% endif %}>Pending</option>
<option value="in_progress" {% if task.status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="completed" {% if task.status == 'completed' %}selected{% endif %}>Completed</option>
<option value="cancelled" {% if task.status == 'cancelled' %}selected{% endif %}>Cancelled</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="detail-priority">Priority</label>
<select class="form-select" id="detail-priority" name="priority">
<option value="low" {% if task.priority == 'low' %}selected{% endif %}>Low</option>
<option value="medium" {% if task.priority == 'medium' %}selected{% endif %}>Medium</option>
<option value="high" {% if task.priority == 'high' %}selected{% endif %}>High</option>
<option value="urgent" {% if task.priority == 'urgent' %}selected{% endif %}>Urgent</option>
</select>
</div>
</div>
<!-- Due Date & Time -->
<div class="form-row">
<div class="form-group">
<label class="form-label" for="detail-due-date">Due Date</label>
<input type="date" class="form-input" id="detail-due-date" name="due_date" value="{{ task.due_date|date:'Y-m-d' }}">
</div>
<div class="form-group">
<label class="form-label" for="detail-due-time">Due Time</label>
<input type="time" class="form-input" id="detail-due-time" name="due_time" value="{{ task.due_time|time:'H:i' }}">
</div>
</div>
<!-- Tags -->
{% if tags %}
<div class="form-group">
<label class="form-label">Tags</label>
<div style="display: flex; flex-wrap: wrap; gap: var(--space-sm);">
{% for tag in tags %}
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer;">
<input type="checkbox" name="tags" value="{{ tag.id }}" {% if tag in task.tags.all %}checked{% endif %}>
<span style="color: {{ tag.color }}">{{ tag.name }}</span>
</label>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Recurrence -->
<div class="form-group">
<label class="form-label" for="detail-recurrence">Recurrence</label>
<select class="form-select" id="detail-recurrence" name="recurrence">
<option value="none" {% if task.recurrence == 'none' %}selected{% endif %}>None</option>
<option value="daily" {% if task.recurrence == 'daily' %}selected{% endif %}>Daily</option>
<option value="weekly" {% if task.recurrence == 'weekly' %}selected{% endif %}>Weekly</option>
<option value="biweekly" {% if task.recurrence == 'biweekly' %}selected{% endif %}>Bi-weekly</option>
<option value="monthly" {% if task.recurrence == 'monthly' %}selected{% endif %}>Monthly</option>
<option value="yearly" {% if task.recurrence == 'yearly' %}selected{% endif %}>Yearly</option>
</select>
</div>
<!-- Save Button (moved inside form, before subtasks) -->
<div class="detail-actions" style="margin-bottom: var(--space-lg);">
<button type="submit" class="btn btn-primary" style="flex: 1;">Save Changes</button>
</div>
</form>
<!-- Subtasks Section (OUTSIDE the main form) -->
<div style="background: var(--bg); border-radius: var(--radius-sm); padding: var(--space-md); margin-bottom: var(--space-lg);">
<div style="font-size: var(--font-sm); font-weight: 500; color: var(--text-secondary); margin-bottom: var(--space-sm);">Subtasks</div>
<!-- Add subtask form (now separate) -->
<form method="post" action="{% url 'subtask-create' task.id %}" style="display: flex; gap: var(--space-sm); margin-bottom: var(--space-sm);">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}?selected={{ task.id }}{% if request.GET.filter %}&filter={{ request.GET.filter }}{% endif %}{% if request.GET.tag %}&tag={{ request.GET.tag }}{% endif %}">
<input type="text" name="title" class="form-input" placeholder="Add a subtask..." style="flex: 1; padding: var(--space-xs) var(--space-sm); font-size: var(--font-sm);" required>
<button type="submit" class="btn btn-primary btn-sm">Add</button>
</form>
<!-- Subtask list -->
{% if task.subtasks.all %}
<div style="display: flex; flex-direction: column; gap: var(--space-xs);">
{% for subtask in task.subtasks.all %}
<div style="display: flex; align-items: center; gap: var(--space-sm); padding: var(--space-xs); border-radius: var(--radius-xs); background: var(--surface);">
<form method="post" action="{% url 'task-toggle' subtask.id %}" style="display: flex;">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}?selected={{ task.id }}{% if request.GET.filter %}&filter={{ request.GET.filter }}{% endif %}{% if request.GET.tag %}&tag={{ request.GET.tag }}{% endif %}">
<button type="submit" class="task-checkbox {% if subtask.status == 'completed' %}checked{% endif %}" style="width: 16px; height: 16px; font-size: 10px;">
{% if subtask.status == 'completed' %}&#10003;{% endif %}
</button>
</form>
<span style="font-size: var(--font-sm); {% if subtask.status == 'completed' %}text-decoration: line-through; color: var(--text-muted);{% endif %}">
{{ subtask.title }}
</span>
</div>
{% endfor %}
</div>
{% else %}
<div style="font-size: var(--font-sm); color: var(--text-muted);">No subtasks</div>
{% endif %}
</div>
<!-- Time Tracker (OUTSIDE the main form) -->
<div class="time-tracker">
<div class="time-tracker-header">
<span class="time-tracker-label">Time Spent</span>
<span class="time-tracker-total">{{ task.total_time_formatted|default:"0:00:00" }}</span>
</div>
<div style="display: flex; gap: var(--space-sm); margin-top: var(--space-sm);">
{% if running_timer and running_timer.task_id == task.id %}
<form method="post" action="{% url 'timer-stop' task.id %}" style="flex: 1;">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}?selected={{ task.id }}{% if request.GET.filter %}&filter={{ request.GET.filter }}{% endif %}{% if request.GET.tag %}&tag={{ request.GET.tag }}{% endif %}">
<button type="submit" class="btn btn-danger btn-full btn-sm">Stop Timer</button>
</form>
{% else %}
<form method="post" action="{% url 'timer-start' task.id %}" style="flex: 1;">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}?selected={{ task.id }}{% if request.GET.filter %}&filter={{ request.GET.filter }}{% endif %}{% if request.GET.tag %}&tag={{ request.GET.tag }}{% endif %}">
<button type="submit" class="btn btn-secondary btn-full btn-sm">Start Timer</button>
</form>
{% endif %}
</div>
</div>
<!-- Delete Button (separate form) -->
<div class="detail-actions" style="margin-top: var(--space-lg);">
<form method="post" action="{% url 'task-delete' task.id %}" onsubmit="return confirm('Delete this task?')">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}">
<button type="submit" class="btn btn-danger btn-sm">Delete Task</button>
</form>
</div>
+46
View File
@@ -0,0 +1,46 @@
<div class="task-item priority-{{ task.priority }} {% if task.is_overdue %}overdue{% endif %} {% if task.status == 'completed' %}completed{% endif %} {% if selected_task_id == task.id|stringformat:'s' %}selected{% endif %}"
data-task-id="{{ task.id }}"
onclick="selectTask('{{ task.id }}')">
<!-- Checkbox -->
<form method="post" action="{% url 'task-toggle' task.id %}" class="task-toggle" onclick="event.stopPropagation();">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.get_full_path }}">
<button type="submit" class="task-checkbox {% if task.status == 'completed' %}checked{% endif %}">
{% if task.status == 'completed' %}
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<path d="M5 13l4 4L19 7"/>
</svg>
{% endif %}
</button>
</form>
<!-- Content -->
<div class="task-content">
<div class="task-title">{{ task.title }}</div>
<div class="task-meta">
{% if task.due_date %}
<span class="task-due {% if task.is_overdue %}overdue{% endif %}">
📅 {{ task.due_date|date:"M j, Y" }}{% if task.due_time %} 🕐 {{ task.due_time|time:"g:i A" }}{% endif %}
</span>
{% endif %}
{% if task.due_date and task.tags.all %}
<span class="task-meta-separator">·</span>
{% endif %}
{% for tag in task.tags.all %}
<span class="task-group-label" style="background-color: {{ tag.color }}20; color: {{ tag.color }}">
{{ tag.name }}
</span>
{% endfor %}
{% if task.total_time_spent > 0 %}
{% if task.due_date or task.tags.all %}
<span class="task-meta-separator">·</span>
{% endif %}
<span class="task-time-spent">⏱️ {{ task.total_time_formatted }}</span>
{% endif %}
</div>
</div>
<!-- Priority Badge -->
<span class="priority-badge {{ task.priority }}">{{ task.priority }}</span>
</div>
+87
View File
@@ -0,0 +1,87 @@
{% extends 'base.html' %}
{% block title %}{{ page_title|default:"All Tasks" }} - KeepItGoing{% endblock %}
{% block content %}
<!-- Header -->
<div class="task-pane-header">
<h1 class="task-pane-title">{{ page_title|default:"All Tasks" }}</h1>
<div style="display: flex; align-items: center; gap: var(--space-md);">
<span class="task-count">{{ tasks|length }} task{{ tasks|length|pluralize }}</span>
<select id="sort-select" onchange="updateSort()" style="padding: 4px 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); color: var(--text);">
<option value="default" {% if current_sort == 'default' %}selected{% endif %}>Default</option>
<option value="due_date" {% if current_sort == 'due_date' %}selected{% endif %}>Due Date (Earliest)</option>
<option value="due_date_desc" {% if current_sort == 'due_date_desc' %}selected{% endif %}>Due Date (Latest)</option>
<option value="priority" {% if current_sort == 'priority' %}selected{% endif %}>Priority (High to Low)</option>
<option value="priority_low" {% if current_sort == 'priority_low' %}selected{% endif %}>Priority (Low to High)</option>
</select>
</div>
</div>
<!-- Quick Add -->
<form method="post" action="{% url 'task-quick-add' %}" class="quick-add">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.get_full_path }}">
{% if current_tag_id %}
<input type="hidden" name="tag" value="{{ current_tag_id }}">
{% endif %}
<input type="text" name="title" class="quick-add-input" placeholder="Add a new task..." required>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<!-- Running Timer -->
{% if running_timer %}
<div class="time-tracker" style="background-color: var(--accent); color: var(--text-on-accent); margin-bottom: var(--space-lg);">
<div class="time-tracker-header">
<span class="time-tracker-label" style="color: var(--text-on-accent); opacity: 0.9;">Timer running for: <strong>{{ running_timer.task.title }}</strong></span>
<span class="time-tracker-total" id="running-timer" data-started="{{ running_timer.started_at.isoformat }}">00:00:00</span>
</div>
</div>
{% endif %}
<!-- Task List -->
{% if tasks %}
<div class="task-list" id="task-list">
{% for task in tasks %}
{% include 'tasks/_task_item.html' %}
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>No tasks yet.</p>
<p class="text-muted">Add one above!</p>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script>
// Running timer display
const timerDisplay = document.getElementById('running-timer');
if (timerDisplay) {
const startedAt = new Date(timerDisplay.dataset.started);
function updateTimer() {
const now = new Date();
const diff = Math.floor((now - startedAt) / 1000);
const hours = Math.floor(diff / 3600).toString().padStart(2, '0');
const minutes = Math.floor((diff % 3600) / 60).toString().padStart(2, '0');
const seconds = (diff % 60).toString().padStart(2, '0');
timerDisplay.textContent = `${hours}:${minutes}:${seconds}`;
}
updateTimer();
setInterval(updateTimer, 1000);
}
// Sort change handler
function updateSort() {
const sortValue = document.getElementById('sort-select').value;
const url = new URL(window.location.href);
if (sortValue === 'default') {
url.searchParams.delete('sort');
} else {
url.searchParams.set('sort', sortValue);
}
window.location.href = url.toString();
}
</script>
{% endblock %}
+61
View File
@@ -0,0 +1,61 @@
{% extends 'base.html' %}
{% block title %}Edit {{ tag.name }} - KeepItGoing{% endblock %}
{% block content %}
<!-- Header -->
<div class="task-pane-header">
<h1 class="task-pane-title">Edit Tag</h1>
</div>
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-xl); max-width: 500px;">
<form method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'tag-list' %}">
<div class="form-group">
<label class="form-label" for="name">Name</label>
<input type="text" class="form-input" id="name" name="name" value="{{ tag.name }}" required>
</div>
<div class="form-group">
<label class="form-label" for="color">Color</label>
<div style="display: flex; align-items: center; gap: var(--space-md);">
<input type="color" id="color" name="color" value="{{ tag.color|default:'#3B82F6' }}"
style="height: 40px; width: 80px; padding: 2px; border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer;">
<div style="width: 24px; height: 24px; border-radius: 50%; background: {{ tag.color|default:'#3B82F6' }};" id="colorPreview"></div>
</div>
</div>
<div class="form-group">
<label class="form-label" for="description">Description</label>
<textarea class="form-input" id="description" name="description" rows="3" placeholder="Optional description">{{ tag.description|default:'' }}</textarea>
</div>
<div style="display: flex; gap: var(--space-md); margin-top: var(--space-xl);">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="{% url 'tag-list' %}" class="btn btn-secondary">Cancel</a>
</div>
</form>
<!-- Delete Section -->
<div style="margin-top: var(--space-xl); padding-top: var(--space-xl); border-top: 1px solid var(--border);">
<h3 style="color: var(--danger); margin-bottom: var(--space-md);">Danger Zone</h3>
<p style="color: var(--text-secondary); font-size: var(--font-sm); margin-bottom: var(--space-md);">
Deleting this tag will not delete the tasks with it. Tasks will simply lose this tag.
</p>
<form method="post" action="{% url 'tag-delete' tag.id %}" onsubmit="return confirm('Are you sure you want to delete this tag?');">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'tag-list' %}">
<button type="submit" class="btn" style="background: var(--danger); color: white;">Delete Tag</button>
</form>
</div>
</div>
<script>
// Update color preview when color picker changes
document.getElementById('color').addEventListener('input', function(e) {
document.getElementById('colorPreview').style.background = e.target.value;
});
</script>
{% endblock %}
+59
View File
@@ -0,0 +1,59 @@
{% extends 'base.html' %}
{% block title %}Tags - KeepItGoing{% endblock %}
{% block content %}
<!-- Header -->
<div class="task-pane-header">
<h1 class="task-pane-title">Tags</h1>
<span class="task-count">{{ tags|length }} tag{{ tags|length|pluralize }}</span>
</div>
<!-- Create Tag -->
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-lg); margin-bottom: var(--space-lg);">
<h2 style="font-size: var(--font-lg); margin-bottom: var(--space-md);">Create New Tag</h2>
<form method="post" style="display: flex; gap: var(--space-md); flex-wrap: wrap; align-items: flex-end;">
{% csrf_token %}
<div class="form-group" style="flex: 1; min-width: 200px; margin-bottom: 0;">
<label class="form-label" for="name">Name</label>
<input type="text" class="form-input" id="name" name="name" required placeholder="Enter tag name">
</div>
<div class="form-group" style="margin-bottom: 0;">
<label class="form-label" for="color">Color</label>
<input type="color" id="color" name="color" value="#3B82F6" style="height: 38px; width: 60px; padding: 2px; border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer;">
</div>
<button type="submit" class="btn btn-primary">Create Tag</button>
</form>
</div>
<!-- Tag List -->
{% if tags %}
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-lg);">
{% for tag in tags %}
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-lg); border-left: 4px solid {{ tag.color }};">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-sm);">
<h3 style="font-size: var(--font-lg); color: {{ tag.color }}; margin: 0;">{{ tag.name }}</h3>
{% if tag.is_archived %}
<span style="font-size: var(--font-xs); background: var(--text-muted); color: var(--bg); padding: 2px 8px; border-radius: 10px;">Archived</span>
{% endif %}
</div>
<p style="color: var(--text-secondary); font-size: var(--font-sm); margin-bottom: var(--space-md);">
{{ tag.description|default:"No description" }}
</p>
<div style="display: flex; gap: var(--space-lg); font-size: var(--font-sm); color: var(--text-muted); margin-bottom: var(--space-md);">
<span>{{ tag.tasks.count }} tasks</span>
</div>
<div style="display: flex; gap: var(--space-sm);">
<a href="{% url 'dashboard' %}?tag={{ tag.id }}" class="btn btn-secondary btn-sm">View Tasks</a>
<a href="{% url 'tag-detail' tag.id %}" class="btn btn-secondary btn-sm" style="cursor: pointer; pointer-events: auto;">Edit</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>No tags created yet.</p>
<p class="text-muted">Create your first tag above!</p>
</div>
{% endif %}
{% endblock %}
+87
View File
@@ -0,0 +1,87 @@
{% extends 'base.html' %}
{% block title %}New Task - KeepItGoing{% endblock %}
{% block content %}
<!-- Header -->
<div class="task-pane-header">
<h1 class="task-pane-title">Create New Task</h1>
<a href="{% url 'dashboard' %}" class="btn btn-secondary">Cancel</a>
</div>
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-xl); max-width: 600px;">
<form method="post">
{% csrf_token %}
<div class="form-group">
<label class="form-label" for="title">Title *</label>
<input type="text" class="form-input" id="title" name="title" required autofocus placeholder="Enter task title">
</div>
<div class="form-group">
<label class="form-label" for="description">Description</label>
<textarea class="form-textarea" id="description" name="description" rows="3" placeholder="Add a description..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="status">Status</label>
<select class="form-select" id="status" name="status">
<option value="pending" selected>Pending</option>
<option value="in_progress">In Progress</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="priority">Priority</label>
<select class="form-select" id="priority" name="priority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="urgent">Urgent</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="due_date">Due Date</label>
<input type="date" class="form-input" id="due_date" name="due_date">
</div>
<div class="form-group">
<label class="form-label" for="due_time">Due Time</label>
<input type="time" class="form-input" id="due_time" name="due_time">
</div>
</div>
<div class="form-group">
<label class="form-label" for="recurrence">Recurrence</label>
<select class="form-select" id="recurrence" name="recurrence">
<option value="none" selected>None</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="biweekly">Bi-weekly</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
</select>
</div>
{% if tags %}
<div class="form-group">
<label class="form-label">Tags</label>
<div style="display: flex; flex-wrap: wrap; gap: var(--space-sm);">
{% for tag in tags %}
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer;">
<input type="checkbox" name="tags" value="{{ tag.id }}">
<span style="color: {{ tag.color }}">{{ tag.name }}</span>
</label>
{% endfor %}
</div>
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Create Task</button>
</form>
</div>
{% endblock %}
+151
View File
@@ -0,0 +1,151 @@
{% extends 'base.html' %}
{% block title %}{{ task.title }} - KeepItGoing{% endblock %}
{% block content %}
<!-- Header -->
<div class="task-pane-header">
<h1 class="task-pane-title">Edit Task</h1>
<div style="display: flex; gap: var(--space-sm);">
<a href="{% url 'dashboard' %}" class="btn btn-secondary">Back</a>
<form method="post" action="{% url 'task-delete' task.id %}" style="display: inline;" onsubmit="return confirm('Delete this task?')">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'dashboard' %}">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 350px; gap: var(--space-lg);">
<!-- Main Form -->
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-xl);">
<form method="post">
{% csrf_token %}
<div class="form-group">
<label class="form-label" for="title">Title</label>
<input type="text" class="form-input" id="title" name="title" value="{{ task.title }}" required>
</div>
<div class="form-group">
<label class="form-label" for="description">Description</label>
<textarea class="form-textarea" id="description" name="description" rows="4">{{ task.description }}</textarea>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="status">Status</label>
<select class="form-select" id="status" name="status">
<option value="pending" {% if task.status == 'pending' %}selected{% endif %}>Pending</option>
<option value="in_progress" {% if task.status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="completed" {% if task.status == 'completed' %}selected{% endif %}>Completed</option>
<option value="cancelled" {% if task.status == 'cancelled' %}selected{% endif %}>Cancelled</option>
</select>
</div>
<div class="form-group">
<label class="form-label" for="priority">Priority</label>
<select class="form-select" id="priority" name="priority">
<option value="low" {% if task.priority == 'low' %}selected{% endif %}>Low</option>
<option value="medium" {% if task.priority == 'medium' %}selected{% endif %}>Medium</option>
<option value="high" {% if task.priority == 'high' %}selected{% endif %}>High</option>
<option value="urgent" {% if task.priority == 'urgent' %}selected{% endif %}>Urgent</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="due_date">Due Date</label>
<input type="date" class="form-input" id="due_date" name="due_date" value="{{ task.due_date|date:'Y-m-d' }}">
</div>
<div class="form-group">
<label class="form-label" for="due_time">Due Time</label>
<input type="time" class="form-input" id="due_time" name="due_time" value="{{ task.due_time|time:'H:i' }}">
</div>
</div>
<!-- Tags -->
{% if tags %}
<div class="form-group">
<label class="form-label">Tags</label>
<div style="display: flex; flex-wrap: wrap; gap: var(--space-sm);">
{% for tag in tags %}
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer;">
<input type="checkbox" name="tags" value="{{ tag.id }}" {% if tag in task.tags.all %}checked{% endif %}>
<span style="color: {{ tag.color }}">{{ tag.name }}</span>
</label>
{% endfor %}
</div>
</div>
{% endif %}
<div class="form-group">
<label class="form-label" for="recurrence">Recurrence</label>
<select class="form-select" id="recurrence" name="recurrence">
<option value="none" {% if task.recurrence == 'none' %}selected{% endif %}>None</option>
<option value="daily" {% if task.recurrence == 'daily' %}selected{% endif %}>Daily</option>
<option value="weekly" {% if task.recurrence == 'weekly' %}selected{% endif %}>Weekly</option>
<option value="biweekly" {% if task.recurrence == 'biweekly' %}selected{% endif %}>Bi-weekly</option>
<option value="monthly" {% if task.recurrence == 'monthly' %}selected{% endif %}>Monthly</option>
<option value="yearly" {% if task.recurrence == 'yearly' %}selected{% endif %}>Yearly</option>
</select>
</div>
{% if tags %}
<div class="form-group">
<label class="form-label">Tags</label>
<div style="display: flex; flex-wrap: wrap; gap: var(--space-sm);">
{% for tag in tags %}
<label style="display: flex; align-items: center; gap: var(--space-xs); cursor: pointer;">
<input type="checkbox" name="tags" value="{{ tag.id }}" {% if tag in task.tags.all %}checked{% endif %}>
<span style="color: {{ tag.color }}">{{ tag.name }}</span>
</label>
{% endfor %}
</div>
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
</div>
<!-- Sidebar: Time Tracking & Subtasks -->
<div>
<!-- Time Tracking -->
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-lg); margin-bottom: var(--space-lg);">
<h2 style="font-size: var(--font-lg); margin-bottom: var(--space-md);">Time Tracking</h2>
<div style="font-size: var(--font-xxl); font-weight: 600; font-family: monospace; margin-bottom: var(--space-md);">
{{ task.total_time_formatted }}
</div>
{% if time_entries %}
<div style="max-height: 200px; overflow-y: auto;">
{% for entry in time_entries %}
<div style="display: flex; justify-content: space-between; padding: var(--space-sm); background: var(--bg); border-radius: var(--radius-sm); margin-bottom: var(--space-xs); font-size: var(--font-sm);">
<span>{{ entry.started_at|date:"M j, H:i" }}</span>
<span>{{ entry.duration_seconds|default:"Running" }}s</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted" style="font-size: var(--font-sm);">No time entries yet</p>
{% endif %}
</div>
<!-- Subtasks -->
<div style="background: var(--surface); border-radius: var(--radius-md); padding: var(--space-lg);">
<h2 style="font-size: var(--font-lg); margin-bottom: var(--space-md);">Subtasks</h2>
{% if task.subtasks.count > 0 %}
<div class="task-list">
{% for subtask in task.subtasks.all %}
{% include 'tasks/_task_item.html' with task=subtask %}
{% endfor %}
</div>
{% else %}
<p class="text-muted" style="font-size: var(--font-sm);">No subtasks</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% extends 'base.html' %}
{% block title %}Tasks - KeepItGoing{% endblock %}
{% block content %}
<!-- Header -->
<div class="task-pane-header">
<h1 class="task-pane-title">All Tasks</h1>
<span class="task-count">{{ tasks|length }} task{{ tasks|length|pluralize }}</span>
</div>
<!-- Filters -->
<div style="display: flex; gap: var(--space-sm); flex-wrap: wrap; margin-bottom: var(--space-lg);">
<form method="get" style="display: flex; gap: var(--space-sm); flex-wrap: wrap; flex: 1;">
<select name="status" class="form-select" onchange="this.form.submit()" style="width: auto;">
<option value="">All Status</option>
<option value="pending" {% if current_status == 'pending' %}selected{% endif %}>Pending</option>
<option value="in_progress" {% if current_status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="completed" {% if current_status == 'completed' %}selected{% endif %}>Completed</option>
<option value="cancelled" {% if current_status == 'cancelled' %}selected{% endif %}>Cancelled</option>
</select>
<select name="priority" class="form-select" onchange="this.form.submit()" style="width: auto;">
<option value="">All Priority</option>
<option value="urgent" {% if current_priority == 'urgent' %}selected{% endif %}>Urgent</option>
<option value="high" {% if current_priority == 'high' %}selected{% endif %}>High</option>
<option value="medium" {% if current_priority == 'medium' %}selected{% endif %}>Medium</option>
<option value="low" {% if current_priority == 'low' %}selected{% endif %}>Low</option>
</select>
<select name="tag" class="form-select" onchange="this.form.submit()" style="width: auto;">
<option value="">All Tags</option>
{% for tag in tags %}
<option value="{{ tag.id }}" {% if current_tag == tag.id|stringformat:'s' %}selected{% endif %}>
{{ tag.name }}
</option>
{% endfor %}
</select>
<a href="{% url 'task-list' %}" class="btn btn-secondary btn-sm">Clear Filters</a>
</form>
<a href="{% url 'task-create' %}" class="btn btn-primary">New Task</a>
</div>
<!-- Task List -->
{% if tasks %}
<div class="task-list">
{% for task in tasks %}
{% include 'tasks/_task_item.html' %}
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>No tasks found</p>
<p class="text-muted">Try adjusting your filters or create a new task.</p>
</div>
{% endif %}
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
{% extends 'base.html' %}
{% block title %}Login - KeepItGoing{% endblock %}
{% block auth_content %}
<div class="auth-card">
<h1 class="auth-title">Login</h1>
{% if error %}
<div class="alert alert-error">{{ error }}</div>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label class="form-label" for="email">Email</label>
<input type="email" class="form-input" id="email" name="email" required autofocus>
</div>
<div class="form-group">
<label class="form-label" for="password">Password</label>
<input type="password" class="form-input" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary btn-full">Login</button>
</form>
<p class="auth-footer">
Don't have an account? <a href="{% url 'register' %}">Register</a>
</p>
</div>
{% endblock %}
+77
View File
@@ -0,0 +1,77 @@
{% extends 'base.html' %}
{% block title %}Profile - KeepItGoing{% endblock %}
{% block content %}
<div class="page-header">
<h1>Profile Settings</h1>
</div>
{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}
<div class="card">
<form method="post">
{% csrf_token %}
<div class="form-row">
<div class="form-group">
<label for="first_name">First Name</label>
<input type="text" id="first_name" name="first_name" value="{{ user.first_name }}">
</div>
<div class="form-group">
<label for="last_name">Last Name</label>
<input type="text" id="last_name" name="last_name" value="{{ user.last_name }}">
</div>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" value="{{ user.email }}" disabled>
<small>Email cannot be changed</small>
</div>
<div class="form-group">
<label for="timezone">Timezone</label>
<select id="timezone" name="timezone">
<option value="UTC" {% if user.timezone == 'UTC' %}selected{% endif %}>UTC</option>
<option value="America/New_York" {% if user.timezone == 'America/New_York' %}selected{% endif %}>Eastern Time</option>
<option value="America/Chicago" {% if user.timezone == 'America/Chicago' %}selected{% endif %}>Central Time</option>
<option value="America/Denver" {% if user.timezone == 'America/Denver' %}selected{% endif %}>Mountain Time</option>
<option value="America/Los_Angeles" {% if user.timezone == 'America/Los_Angeles' %}selected{% endif %}>Pacific Time</option>
<option value="Europe/London" {% if user.timezone == 'Europe/London' %}selected{% endif %}>London</option>
<option value="Europe/Paris" {% if user.timezone == 'Europe/Paris' %}selected{% endif %}>Paris</option>
<option value="Asia/Tokyo" {% if user.timezone == 'Asia/Tokyo' %}selected{% endif %}>Tokyo</option>
</select>
</div>
<div class="form-group">
<label for="default_reminder_minutes">Default Reminder (minutes before due)</label>
<select id="default_reminder_minutes" name="default_reminder_minutes">
<option value="0" {% if user.default_reminder_minutes == 0 %}selected{% endif %}>No reminder</option>
<option value="5" {% if user.default_reminder_minutes == 5 %}selected{% endif %}>5 minutes</option>
<option value="15" {% if user.default_reminder_minutes == 15 %}selected{% endif %}>15 minutes</option>
<option value="30" {% if user.default_reminder_minutes == 30 %}selected{% endif %}>30 minutes</option>
<option value="60" {% if user.default_reminder_minutes == 60 %}selected{% endif %}>1 hour</option>
<option value="1440" {% if user.default_reminder_minutes == 1440 %}selected{% endif %}>1 day</option>
</select>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="email_notifications" {% if user.email_notifications %}checked{% endif %}>
Email Notifications
</label>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="push_notifications" {% if user.push_notifications %}checked{% endif %}>
Push Notifications
</label>
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
</div>
{% endblock %}
+42
View File
@@ -0,0 +1,42 @@
{% extends 'base.html' %}
{% block title %}Register - KeepItGoing{% endblock %}
{% block auth_content %}
<div class="auth-card">
<h1 class="auth-title">Create Account</h1>
{% if errors %}
<div class="alert alert-error">
{% for field, error in errors.items %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<form method="post">
{% csrf_token %}
<div class="form-group">
<label class="form-label" for="email">Email</label>
<input type="email" class="form-input" id="email" name="email" required autofocus>
</div>
<div class="form-group">
<label class="form-label" for="username">Username</label>
<input type="text" class="form-input" id="username" name="username" required>
</div>
<div class="form-group">
<label class="form-label" for="password">Password</label>
<input type="password" class="form-input" id="password" name="password" required>
</div>
<div class="form-group">
<label class="form-label" for="password_confirm">Confirm Password</label>
<input type="password" class="form-input" id="password_confirm" name="password_confirm" required>
</div>
<button type="submit" class="btn btn-primary btn-full">Register</button>
</form>
<p class="auth-footer">
Already have an account? <a href="{% url 'login' %}">Login</a>
</p>
</div>
{% endblock %}