diff --git a/app.py b/app.py index 5d11e2c..4d18e94 100644 --- a/app.py +++ b/app.py @@ -513,6 +513,12 @@ def logbook(): if not is_logged_in(): return redirect(url_for('login')) user_id = get_user_id() + # Fetch activities for the manual entry dropdown + activities = list(db.activities.find({'user_id': user_id})) + # Ensure keys exist for template using them safely + for a in activities: + if 'subcategories' not in a: a['subcategories'] = [] + # --- Statistics Logic --- time_range = request.args.get('range', '24h') now = datetime.now() @@ -616,7 +622,46 @@ def logbook(): entry['duration_str'] = str(duration).split('.')[0] entry['is_running'] = True - return render_template('logbook.html', log=log, chart_data=chart_data, current_range=time_range, total_time_display=total_time_display) + return render_template('logbook.html', log=log, chart_data=chart_data, current_range=time_range, total_time_display=total_time_display, activities=activities) + +@app.route('/add_manual_entry', methods=['POST']) +def add_manual_entry(): + if not is_logged_in(): return redirect(url_for('login')) + user_id = get_user_id() + + activity_id = request.form['activity_id'] + start_str = request.form['start_time'] + end_str = request.form['end_time'] + note = request.form.get('note', '') + + # Check both inputs (one will be disabled in frontend, but check priority) + subcategory = request.form.get('subcategory_select') + if not subcategory: + subcategory = request.form.get('subcategory', '') + + try: + start_time = datetime.strptime(start_str, '%Y-%m-%dT%H:%M') + end_time = datetime.strptime(end_str, '%Y-%m-%dT%H:%M') if end_str else datetime.now() + + # Validation: End time cannot be before start time + if end_time < start_time: + flash('End time cannot be before start time') + return redirect(url_for('logbook')) + + except ValueError: + flash('Invalid date format') + return redirect(url_for('logbook')) + + db.time_entries.insert_one({ + 'user_id': user_id, + 'activity_id': ObjectId(activity_id), + 'start_time': start_time, + 'end_time': end_time, + 'note': note, + 'subcategory': subcategory + }) + + return redirect(url_for('logbook')) @app.route('/logbook/', methods=['GET', 'POST']) def log_entry_detail(entry_id): diff --git a/templates/logbook.html b/templates/logbook.html index 00c35ea..5733e4e 100644 --- a/templates/logbook.html +++ b/templates/logbook.html @@ -6,6 +6,59 @@

Logbook

+ +
+ + + @@ -139,6 +192,63 @@ }); }, 1000); + // Initialize datetime inputs with safe defaults if needed + window.addEventListener('load', () => { + const now = new Date(); + now.setMinutes(now.getMinutes() - now.getTimezoneOffset()); + const nowStr = now.toISOString().slice(0, 16); + + // Optionally set default values for inputs that are empty + document.querySelectorAll('input[type="datetime-local"].date-input').forEach(input => { + if (!input.value) input.value = nowStr; + }); + + // Initialize subcategories check + updateSubcategories(); + }); + + function updateSubcategories() { + const activitySelect = document.getElementById('activitySelect'); + const subcatInput = document.getElementById('subcatInput'); + const subcatSelect = document.getElementById('subcatSelect'); + + const selectedOption = activitySelect.options[activitySelect.selectedIndex]; + let subcats = []; + + try { + if (selectedOption && selectedOption.dataset.subcategories) { + subcats = JSON.parse(selectedOption.dataset.subcategories); + } + } catch (e) { + console.error("Error parsing subcategories", e); + } + + if (subcats && subcats.length > 0) { + // Show Dropdown + subcatInput.style.display = 'none'; + subcatInput.disabled = true; // Disable so it's not sent + + subcatSelect.style.display = 'block'; + subcatSelect.disabled = false; + + // Clear and populate + subcatSelect.innerHTML = ''; + subcats.forEach(sc => { + const opt = document.createElement('option'); + opt.value = sc; + opt.textContent = sc; + subcatSelect.appendChild(opt); + }); + } else { + // Show Text Input + subcatInput.style.display = 'block'; + subcatInput.disabled = false; + + subcatSelect.style.display = 'none'; + subcatSelect.disabled = true; + } + } + // Background Sync (Polls for new tasks or status changes) let lastContentHash = ""; setInterval(() => {