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,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 %}
|
||||
Reference in New Issue
Block a user