add tasks driectly from tracker

This commit is contained in:
2026-02-11 13:29:51 +01:00
parent a5a56923ab
commit 05398bdfd6
2 changed files with 194 additions and 21 deletions

42
app.py
View File

@@ -148,11 +148,15 @@ def add_activity():
name = request.form['name'] name = request.form['name']
color = request.form.get('color', '#3498db') color = request.form.get('color', '#3498db')
# Parse subcategories
subcats_str = request.form.get('subcategories_data', '')
subcategories = [s.strip() for s in subcats_str.split(',') if s.strip()]
activity_id = db.activities.insert_one({ activity_id = db.activities.insert_one({
'user_id': get_user_id(), 'user_id': get_user_id(),
'name': name, 'name': name,
'color': color, 'color': color,
'subcategories': [] 'subcategories': subcategories
}).inserted_id }).inserted_id
# Add optional default tasks (Revised from list input) # Add optional default tasks (Revised from list input)
@@ -427,6 +431,42 @@ def create_task():
db.tasks.insert_one(task_doc) db.tasks.insert_one(task_doc)
return redirect(url_for('tasks')) return redirect(url_for('tasks'))
@app.route('/create_task_quick', methods=['POST'])
def create_task_quick():
if not is_logged_in(): return jsonify({'error': 'auth'}), 401
name = request.form['name']
activity_id = request.form.get('activity_id')
subcategory = request.form.get('subcategory', '')
# Check if there is an active time entry to link immediately
current_entry = db.time_entries.find_one({
'user_id': get_user_id(),
'end_time': None
})
task_doc = {
'user_id': get_user_id(),
'name': name,
'status': 'open',
'is_template': False,
'source': 'manual-quick',
'created_at': datetime.now(),
'comments': [],
'subcategory': subcategory,
'activity_id': ObjectId(activity_id) if activity_id else None,
# If we have an active entry matching this activity, link it for this session
'time_entry_id': current_entry['_id'] if current_entry and str(current_entry['activity_id']) == activity_id else None
}
new_id = db.tasks.insert_one(task_doc).inserted_id
return jsonify({
'status': 'success',
'task_id': str(new_id),
'name': name
})
@app.route('/task/<task_id>', methods=['GET', 'POST']) @app.route('/task/<task_id>', methods=['GET', 'POST'])
def task_detail(task_id): def task_detail(task_id):
if not is_logged_in(): return redirect(url_for('login')) if not is_logged_in(): return redirect(url_for('login'))

View File

@@ -184,25 +184,32 @@
<div class="timer-display" id="timer" style="margin: 1rem 0 2rem 0;">00:00:00</div> <div class="timer-display" id="timer" style="margin: 1rem 0 2rem 0;">00:00:00</div>
<!-- Tasks List (Redesigned) --> <!-- Tasks List (Redesigned) -->
{% if tasks %}
<div style="margin-bottom: 2rem;"> <div style="margin-bottom: 2rem;">
<h4 style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 0.05em; border-bottom: 1px solid var(--border-dim); padding-bottom: 5px;">Active Tasks</h4> <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-dim); padding-bottom: 5px; margin-bottom: 10px;">
<h4 style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-secondary); letter-spacing: 0.05em; margin: 0;">Active Tasks</h4>
<!-- Quick Add Button -->
<button type="button" onclick="openQuickTaskModal()" style="background: transparent; border: 1px solid var(--border-dim); cursor: pointer; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); margin-left: 10px;" title="Add Task">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
</button>
</div>
<div class="checklist-container" id="activeTasksList"> <div class="checklist-container" id="activeTasksList">
{% for task in tasks %} {% if tasks %}
<div class="checklist-item {% if task.status == 'completed' %}completed{% endif %}" id="task-item-{{ task._id }}"> {% for task in tasks %}
<input type="checkbox" id="task_{{ task._id }}" <div class="checklist-item {% if task.status == 'completed' %}completed{% endif %}" id="task-item-{{ task._id }}">
{% if task.status == 'completed' %}checked{% endif %} <input type="checkbox" id="task_{{ task._id }}"
onchange="toggleTask('{{ task._id }}', this)"> {% if task.status == 'completed' %}checked{% endif %}
<label for="task_{{ task._id }}"> onchange="toggleTask('{{ task._id }}', this)">
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: inherit;"> <label for="task_{{ task._id }}">
{{ task.name }} <a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: inherit;">
</a> {{ task.name }}
</label> </a>
</div> </label>
{% endfor %} </div>
{% endfor %}
{% endif %}
</div> </div>
</div> </div>
{% endif %}
<form action="{{ url_for('stop_timer') }}" method="POST"> <form action="{{ url_for('stop_timer') }}" method="POST">
<button type="submit" class="btn btn-danger" style="width: 100%; justify-content: center; padding: 12px;">Stop Activity</button> <button type="submit" class="btn btn-danger" style="width: 100%; justify-content: center; padding: 12px;">Stop Activity</button>
@@ -214,6 +221,10 @@
<!-- Scripts (Timer, Tasks, Modal) --> <!-- Scripts (Timer, Tasks, Modal) -->
<script> <script>
// Global vars - MUST be declared before any functions that use them
let currentActivityId = "{{ current_entry.activity_id if current_entry else '' }}";
let currentSubcategory = "{{ current_entry.subcategory if current_entry else '' }}";
// Timer Logic // Timer Logic
let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %}; let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %};
let timerInterval; let timerInterval;
@@ -283,17 +294,30 @@
document.getElementById('startModal').classList.remove('show'); document.getElementById('startModal').classList.remove('show');
} }
function closeQuickTaskModal() {
document.getElementById('quickTaskModal').classList.remove('show');
document.getElementById('quickTaskName').value = ''; // Clean up
}
// Close on backdrop click // Close on backdrop click
document.getElementById('startModal').addEventListener('click', function(e) { document.getElementById('startModal').addEventListener('click', function(e) {
if (e.target === this) closeStartModal(); if (e.target === this) closeStartModal();
}); });
document.getElementById('quickTaskModal').addEventListener('click', function(e) {
if (e.target === this) closeQuickTaskModal();
});
// Start Activity Immediate Function
function startActivityImmediate(element) { function startActivityImmediate(element) {
const id = element.getAttribute('data-id'); const id = element.getAttribute('data-id');
const name = element.getAttribute('data-name'); const name = element.getAttribute('data-name');
const color = element.getAttribute('data-color'); const color = element.getAttribute('data-color');
const subcats = JSON.parse(element.getAttribute('data-subcategories') || '[]'); const subcats = JSON.parse(element.getAttribute('data-subcategories') || '[]');
// Save ID globally for quick add
currentActivityId = id;
// 1. Start Background Timer // 1. Start Background Timer
fetch('/start_timer_bg/' + id, { method: 'POST' }) fetch('/start_timer_bg/' + id, { method: 'POST' })
.then(res => res.json()) .then(res => res.json())
@@ -321,6 +345,14 @@
// Reset Note/Subcat display // Reset Note/Subcat display
document.getElementById('activeNote').style.display = 'none'; document.getElementById('activeNote').style.display = 'none';
document.getElementById('activeSubcatContainer').style.display = 'none'; document.getElementById('activeSubcatContainer').style.display = 'none';
document.getElementById('activeSubcatDisplay').innerText = ''; // Reset text
currentSubcategory = ""; // Reset global
// Clear tasks list visually on new start (will define via sync or reload if templates exist)
// Ideally we'd fetch templates here, but sync will catch them in 2s.
// For smoother UX, we could inject template names if passed from backend.
document.getElementById('activeTasksList').innerHTML = '';
localTaskIds = []; // Reset local known tasks
// 3. Open Modal for Details // 3. Open Modal for Details
openDetailsModal(name, subcats); openDetailsModal(name, subcats);
@@ -328,6 +360,7 @@
}); });
} }
// Hook into form update to set these values when modal is submitted/skipped
function openDetailsModal(name, subcats) { function openDetailsModal(name, subcats) {
document.getElementById('modalTitle').innerText = name + " Started"; document.getElementById('modalTitle').innerText = name + " Started";
@@ -353,6 +386,58 @@
document.querySelector('#startForm input[name="note"]').focus(); document.querySelector('#startForm input[name="note"]').focus();
} }
// Open Quick Task Modal
function openQuickTaskModal() {
if(!currentActivityId) {
alert("No active activity context found. Please start an activity first.");
return;
}
document.getElementById('quickTaskModal').classList.add('show');
document.getElementById('quickTaskName').focus();
}
// Quick Add Form Submit
function submitQuickTask(e) {
e.preventDefault();
const input = document.getElementById('quickTaskName');
const val = input.value.trim();
if(!val) return;
const formData = new FormData();
formData.append('name', val);
formData.append('activity_id', currentActivityId);
formData.append('subcategory', currentSubcategory);
fetch('/create_task_quick', { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if(data.status === 'success') {
input.value = '';
closeQuickTaskModal();
// Add to DOM
const list = document.getElementById('activeTasksList');
const div = document.createElement('div');
div.className = 'checklist-item';
div.id = 'task-item-' + data.task_id;
div.innerHTML = `
<input type="checkbox" id="task_${data.task_id}"
onchange="toggleTask('${data.task_id}', this)">
<label for="task_${data.task_id}">
<a href="/task/${data.task_id}" style="text-decoration: none; color: inherit;">
${data.name}
</a>
</label>
`;
list.insertBefore(div, list.firstChild); // Add to top
// Register ID so sync doesn't reload
localTaskIds.push(data.task_id);
console.log("Quick task added. Sync updated.");
}
});
}
// New Task List Logic for Activity Creation form (Reusing existing script) // New Task List Logic for Activity Creation form (Reusing existing script)
let newActivityTasks = []; let newActivityTasks = [];
function addNewTask() { function addNewTask() {
@@ -395,15 +480,15 @@
<div id="startModal" class="modal-backdrop"> <div id="startModal" class="modal-backdrop">
<div class="modal-card"> <div class="modal-card">
<h3 id="modalTitle" style="margin-bottom: 1.5rem;">Activity Started</h3> <h3 id="modalTitle" style="margin-bottom: 1.5rem;">Activity Started</h3>
<form id="startForm" action="{{ url_for('update_active_entry') }}" method="POST"> <form id="startForm" onsubmit="submitDetailsForm(event)">
<div id="subcategoryContainer" style="display: none; margin-bottom: 1rem;"> <div id="subcategoryContainer" style="display: none; margin-bottom: 1rem;">
<label>Subcategory / Context</label> <label>Subcategory / Context</label>
<select name="subcategory" id="modalSubcatSelect" style="width: 100%; padding: 0.5rem;"> <select name="subcategory" id="modalSubcatSelect" style="width: 100%; padding: 0.5rem;">
<option value="">-- None --</option> <option value="">-- None --</option>
</select> </select>
</div> </div>
<label>Note (Optional)</label> <label>Note (Optional)</label>
<input type="text" name="note" placeholder="What are you working on?" autocomplete="off"> <input type="text" name="note" id="modalNote" placeholder="What are you working on?" autocomplete="off">
<div style="margin-top: 2rem; display: flex; justify-content: space-between;"> <div style="margin-top: 2rem; display: flex; justify-content: space-between;">
<button type="button" class="btn" style="background: transparent; color: var(--text-secondary); border: 1px solid var(--border-dim);" onclick="closeStartModal()">Skip</button> <button type="button" class="btn" style="background: transparent; color: var(--text-secondary); border: 1px solid var(--border-dim);" onclick="closeStartModal()">Skip</button>
<button type="submit" class="btn" style="background: #27ae60;">Update Details</button> <button type="submit" class="btn" style="background: #27ae60;">Update Details</button>
@@ -412,6 +497,53 @@
</div> </div>
</div> </div>
<!-- Quick Task Modal (For adding task to active session) -->
<div id="quickTaskModal" class="modal-backdrop">
<div class="modal-card">
<h3 style="margin-bottom: 1.5rem;">Add Task</h3>
<p style="color: var(--text-secondary); margin-bottom: 1rem; font-size: 0.9rem;">
This task will be linked to the current activity.
</p>
<form onsubmit="submitQuickTask(event)">
<label>Task Name</label>
<input type="text" id="quickTaskName" required placeholder="What needs to be done?" autocomplete="off">
<div style="margin-top: 2rem; display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="btn" style="background: transparent; color: var(--text-secondary); border: 1px solid var(--border-dim);" onclick="closeQuickTaskModal()">Cancel</button>
<button type="submit" class="btn" style="background: #27ae60;">Add Task</button>
</div>
</form>
</div>
</div>
<script>
// Replace standard form submit for details so page doesn't reload and lose state
function submitDetailsForm(e) {
e.preventDefault();
const sub = document.getElementById('modalSubcatSelect').value;
const note = document.getElementById('modalNote').value;
currentSubcategory = sub; // Update global for quick add task
// Update UI immediately
if(sub) {
document.getElementById('activeSubcatContainer').style.display = 'block';
document.getElementById('activeSubcatDisplay').innerText = sub;
}
if(note) {
document.getElementById('activeNote').style.display = 'block';
document.getElementById('activeNote').innerText = '"' + note + '"';
}
const fd = new FormData();
fd.append('subcategory', sub);
fd.append('note', note);
fetch('/update_active_entry', { method: 'POST', body: fd });
closeStartModal();
}
</script>
<!-- Live Sync Script (Optimized for Animation) --> <!-- Live Sync Script (Optimized for Animation) -->
<script> <script>
// Generate initial state hashes based on what Jinja rendered // Generate initial state hashes based on what Jinja rendered
@@ -431,8 +563,9 @@
// Don't poll if page is hidden to save battery/data // Don't poll if page is hidden to save battery/data
if (document.hidden) return; if (document.hidden) return;
// Don't poll or reload if the Start Modal is open (User is interacting) // Don't poll or reload if ANY Modal is open (User is interacting)
if (document.getElementById('startModal').classList.contains('show')) return; if (document.getElementById('startModal').classList.contains('show') ||
document.getElementById('quickTaskModal').classList.contains('show')) return;
fetch('/api/sync_check') fetch('/api/sync_check')
.then(response => { .then(response => {