Internal
Public Access
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:
@@ -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>© 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>
|
||||
@@ -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>
|
||||
@@ -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' %}✓{% 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>
|
||||
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user