Goals working well
This commit is contained in:
142
app.py
142
app.py
@@ -2,7 +2,7 @@ from flask import Flask, render_template, request, redirect, url_for, session, f
|
|||||||
from flask_bcrypt import Bcrypt
|
from flask_bcrypt import Bcrypt
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
import os
|
import os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
@@ -109,18 +109,23 @@ def add_activity():
|
|||||||
'user_id': get_user_id(),
|
'user_id': get_user_id(),
|
||||||
'name': name,
|
'name': name,
|
||||||
'color': color,
|
'color': color,
|
||||||
'subcategories': [] # Initialize empty list
|
'subcategories': []
|
||||||
}).inserted_id
|
}).inserted_id
|
||||||
|
|
||||||
# Add optional tasks
|
# Add optional default tasks (Revised from list input)
|
||||||
tasks_text = request.form.get('tasks', '')
|
tasks_str = request.form.get('tasks_list_data', '')
|
||||||
if tasks_text:
|
if tasks_str:
|
||||||
tasks = [t.strip() for t in tasks_text.split(',') if t.strip()]
|
tasks = [t.strip() for t in tasks_str.split(',') if t.strip()]
|
||||||
for t in tasks:
|
for t in tasks:
|
||||||
db.task_templates.insert_one({
|
db.tasks.insert_one({
|
||||||
'activity_id': activity_id,
|
|
||||||
'user_id': get_user_id(),
|
'user_id': get_user_id(),
|
||||||
'name': t
|
'name': t,
|
||||||
|
'activity_id': ObjectId(activity_id),
|
||||||
|
'is_template': True,
|
||||||
|
'status': 'open',
|
||||||
|
'time_entry_id': None,
|
||||||
|
'created_at': datetime.now(),
|
||||||
|
'comments': []
|
||||||
})
|
})
|
||||||
|
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
@@ -183,7 +188,7 @@ def start_timer_bg(activity_id):
|
|||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
'name': t['name'],
|
'name': t['name'],
|
||||||
'activity_id': ObjectId(activity_id),
|
'activity_id': ObjectId(activity_id),
|
||||||
'time_entry_id': new_entry_id,
|
'time_entry_id': new_entry_id, # Link to this specific session
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
'created_at': datetime.now(),
|
'created_at': datetime.now(),
|
||||||
@@ -498,5 +503,122 @@ def log_entry_detail(entry_id):
|
|||||||
|
|
||||||
return render_template('logbook_detail.html', entry=entry, tasks=tasks)
|
return render_template('logbook_detail.html', entry=entry, tasks=tasks)
|
||||||
|
|
||||||
|
@app.route('/goals', methods=['GET', 'POST'])
|
||||||
|
def goals():
|
||||||
|
if not is_logged_in(): return redirect(url_for('login'))
|
||||||
|
user_id = get_user_id()
|
||||||
|
|
||||||
|
# Handle creating new goal
|
||||||
|
if request.method == 'POST':
|
||||||
|
name = request.form['name']
|
||||||
|
target_hours = float(request.form['target_hours'])
|
||||||
|
frequency = request.form['frequency'] # daily, weekly, monthly, yearly
|
||||||
|
activity_id = request.form.get('activity_id') # Required now
|
||||||
|
subcategory = request.form.get('subcategory', '')
|
||||||
|
|
||||||
|
if not activity_id:
|
||||||
|
flash("You must select an activity for this goal.")
|
||||||
|
return redirect(url_for('goals'))
|
||||||
|
|
||||||
|
db.goals.insert_one({
|
||||||
|
'user_id': user_id,
|
||||||
|
'name': name,
|
||||||
|
'target_hours': target_hours,
|
||||||
|
'frequency': frequency,
|
||||||
|
'activity_id': ObjectId(activity_id),
|
||||||
|
'subcategory': subcategory,
|
||||||
|
'created_at': datetime.now()
|
||||||
|
})
|
||||||
|
return redirect(url_for('goals'))
|
||||||
|
|
||||||
|
# Fetch goals and calculate progress
|
||||||
|
goals_list = list(db.goals.find({'user_id': user_id}))
|
||||||
|
activities = list(db.activities.find({'user_id': user_id}))
|
||||||
|
|
||||||
|
today = datetime.now()
|
||||||
|
start_of_today = today.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
|
||||||
|
# Determine date ranges
|
||||||
|
ranges = {
|
||||||
|
'daily': start_of_today,
|
||||||
|
'weekly': start_of_today - timedelta(days=today.weekday()), # Monday
|
||||||
|
'monthly': start_of_today.replace(day=1),
|
||||||
|
'yearly': start_of_today.replace(month=1, day=1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for goal in goals_list:
|
||||||
|
start_date = ranges.get(goal['frequency'], start_of_today)
|
||||||
|
|
||||||
|
query = {
|
||||||
|
'user_id': user_id,
|
||||||
|
'start_time': {'$gte': start_date},
|
||||||
|
'end_time': {'$ne': None}
|
||||||
|
}
|
||||||
|
|
||||||
|
if goal.get('activity_id'):
|
||||||
|
query['activity_id'] = goal['activity_id']
|
||||||
|
|
||||||
|
if goal.get('subcategory'):
|
||||||
|
query['subcategory'] = goal['subcategory']
|
||||||
|
|
||||||
|
entries = list(db.time_entries.find(query))
|
||||||
|
|
||||||
|
total_seconds = sum([(e['end_time'] - e['start_time']).total_seconds() for e in entries])
|
||||||
|
current_hours = total_seconds / 3600
|
||||||
|
|
||||||
|
# Display formatting: Show minutes if < 1 hour, else show hours
|
||||||
|
if current_hours > 0 and current_hours < 1:
|
||||||
|
minutes = int(total_seconds / 60)
|
||||||
|
goal['display_progress'] = f"{minutes}m"
|
||||||
|
else:
|
||||||
|
goal['display_progress'] = f"{round(current_hours, 1)}h"
|
||||||
|
|
||||||
|
goal['current_hours'] = round(current_hours, 1) # Keep purely numeric for math/sorting if needed
|
||||||
|
goal['percent'] = min(100, int((current_hours / goal['target_hours']) * 100))
|
||||||
|
|
||||||
|
# Link activity name for display
|
||||||
|
if goal.get('activity_id'):
|
||||||
|
act = db.activities.find_one({'_id': goal['activity_id']})
|
||||||
|
goal['activity_name'] = act['name'] if act else 'Unknown'
|
||||||
|
goal['activity_color'] = act['color'] if act else '#ccc'
|
||||||
|
|
||||||
|
return render_template('goals.html', goals=goals_list, activities=activities)
|
||||||
|
|
||||||
|
@app.route('/edit_goal/<goal_id>', methods=['GET', 'POST'])
|
||||||
|
def edit_goal(goal_id):
|
||||||
|
if not is_logged_in(): return redirect(url_for('login'))
|
||||||
|
user_id = get_user_id()
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
name = request.form['name']
|
||||||
|
target_hours = float(request.form['target_hours'])
|
||||||
|
frequency = request.form['frequency']
|
||||||
|
activity_id = request.form.get('activity_id')
|
||||||
|
subcategory = request.form.get('subcategory', '')
|
||||||
|
|
||||||
|
db.goals.update_one(
|
||||||
|
{'_id': ObjectId(goal_id), 'user_id': user_id},
|
||||||
|
{'$set': {
|
||||||
|
'name': name,
|
||||||
|
'target_hours': target_hours,
|
||||||
|
'frequency': frequency,
|
||||||
|
'activity_id': ObjectId(activity_id),
|
||||||
|
'subcategory': subcategory
|
||||||
|
}}
|
||||||
|
)
|
||||||
|
return redirect(url_for('goals'))
|
||||||
|
|
||||||
|
goal = db.goals.find_one({'_id': ObjectId(goal_id), 'user_id': user_id})
|
||||||
|
if not goal: return "Goal not found", 404
|
||||||
|
|
||||||
|
activities = list(db.activities.find({'user_id': user_id}))
|
||||||
|
return render_template('edit_goal.html', goal=goal, activities=activities)
|
||||||
|
|
||||||
|
@app.route('/delete_goal/<goal_id>')
|
||||||
|
def delete_goal(goal_id):
|
||||||
|
if not is_logged_in(): return redirect(url_for('login'))
|
||||||
|
db.goals.delete_one({'_id': ObjectId(goal_id), 'user_id': get_user_id()})
|
||||||
|
return redirect(url_for('goals'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|||||||
@@ -189,18 +189,83 @@
|
|||||||
<!-- Create Activity Form -->
|
<!-- Create Activity Form -->
|
||||||
<div id="newActivityForm" class="card" style="display:none; margin-top: 2rem;">
|
<div id="newActivityForm" class="card" style="display:none; margin-top: 2rem;">
|
||||||
<h3>Create New Activity Category</h3>
|
<h3>Create New Activity Category</h3>
|
||||||
<form action="{{ url_for('add_activity') }}" method="POST">
|
<form action="{{ url_for('add_activity') }}" method="POST" onsubmit="prepareTasksList()">
|
||||||
<label>Name</label>
|
<label>Name</label>
|
||||||
<input type="text" name="name" required placeholder="e.g. Household">
|
<input type="text" name="name" required placeholder="e.g. Household">
|
||||||
|
|
||||||
<label>Color</label>
|
<label>Color</label>
|
||||||
<input type="color" name="color" value="#3498db" style="width:100%; height:40px; border:none;">
|
<input type="color" name="color" value="#3498db" style="width:100%; height:40px; border:none;">
|
||||||
|
|
||||||
<label>Default Tasks (comma separated)</label>
|
<label>Default Tasks (Template)</label>
|
||||||
<input type="text" name="tasks" placeholder="e.g. Laundry, Dishes, Trash">
|
<p style="font-size: 0.8rem; color: var(--text-secondary); margin-top: -10px;">
|
||||||
|
These tasks will be created automatically every time you start this activity.
|
||||||
|
</p>
|
||||||
|
|
||||||
<button type="submit" class="btn">Create</button>
|
<div style="display: flex; gap: 5px; margin-bottom: 10px;">
|
||||||
<button type="button" class="btn" style="background: #7f8c8d" onclick="this.parentElement.parentElement.style.display='none'">Cancel</button>
|
<input type="text" id="newTaskInput" placeholder="Add task name..." style="margin-bottom: 0;">
|
||||||
|
<button type="button" class="btn" style="background: #27ae60;" onclick="addNewTask()">Add</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul id="newTasksListDisplay" style="list-style: none; padding: 0; margin-bottom: 1rem;">
|
||||||
|
<!-- Items will be injected here -->
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<input type="hidden" name="tasks_list_data" id="tasksListData">
|
||||||
|
|
||||||
|
<div style="margin-top: 1rem;">
|
||||||
|
<button type="submit" class="btn">Create</button>
|
||||||
|
<button type="button" class="btn" style="background: var(--text-secondary)" onclick="document.getElementById('newActivityForm').style.display='none'">Cancel</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Existing timer scripts...
|
||||||
|
// ...existing code...
|
||||||
|
|
||||||
|
// New Task List Logic for Activity Creation
|
||||||
|
let newActivityTasks = [];
|
||||||
|
|
||||||
|
function addNewTask() {
|
||||||
|
const input = document.getElementById('newTaskInput');
|
||||||
|
const val = input.value.trim();
|
||||||
|
if (val) {
|
||||||
|
newActivityTasks.push(val);
|
||||||
|
input.value = '';
|
||||||
|
renderNewActivityTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeNewTask(index) {
|
||||||
|
newActivityTasks.splice(index, 1);
|
||||||
|
renderNewActivityTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNewActivityTasks() {
|
||||||
|
const list = document.getElementById('newTasksListDisplay');
|
||||||
|
list.innerHTML = '';
|
||||||
|
newActivityTasks.forEach((item, index) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.style.background = '#f7f7f5';
|
||||||
|
li.style.border = '1px solid var(--border-dim)';
|
||||||
|
li.style.margin = '5px 0';
|
||||||
|
li.style.padding = '8px';
|
||||||
|
li.style.borderRadius = '4px';
|
||||||
|
li.style.display = 'flex';
|
||||||
|
li.style.justifyContent = 'space-between';
|
||||||
|
li.style.alignItems = 'center';
|
||||||
|
li.style.fontSize = '0.9rem';
|
||||||
|
|
||||||
|
li.innerHTML = `
|
||||||
|
<span>${item}</span>
|
||||||
|
<span onclick="removeNewTask(${index})" style="cursor: pointer; color: var(--danger-color); font-weight: bold; padding: 0 5px;">×</span>
|
||||||
|
`;
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareTasksList() {
|
||||||
|
document.getElementById('tasksListData').value = newActivityTasks.join(',');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
84
templates/edit_goal.html
Normal file
84
templates/edit_goal.html
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="card" style="max-width: 500px; margin: auto;">
|
||||||
|
<h2>Edit Goal</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<label>Goal Name</label>
|
||||||
|
<input type="text" name="name" value="{{ goal.name }}" required>
|
||||||
|
|
||||||
|
<label>Activity</label>
|
||||||
|
<select name="activity_id" id="activitySelect" required onchange="updateSubcategories()">
|
||||||
|
<option value="">-- Select Activity --</option>
|
||||||
|
{% for act in activities %}
|
||||||
|
<option value="{{ act._id }}"
|
||||||
|
data-subcats='{{ act.subcategories|default([])|tojson }}'
|
||||||
|
{% if act._id|string == goal.activity_id|string %}selected{% endif %}>
|
||||||
|
{{ act.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div id="subcatWrapper" style="display:none;">
|
||||||
|
<label>Subcategory (Optional)</label>
|
||||||
|
<select name="subcategory" id="subcategorySelect">
|
||||||
|
<option value="">-- All --</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>Frequency</label>
|
||||||
|
<select name="frequency">
|
||||||
|
<option value="daily" {% if goal.frequency == 'daily' %}selected{% endif %}>Daily</option>
|
||||||
|
<option value="weekly" {% if goal.frequency == 'weekly' %}selected{% endif %}>Weekly</option>
|
||||||
|
<option value="monthly" {% if goal.frequency == 'monthly' %}selected{% endif %}>Monthly</option>
|
||||||
|
<option value="yearly" {% if goal.frequency == 'yearly' %}selected{% endif %}>Yearly</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Target Hours</label>
|
||||||
|
<input type="number" name="target_hours" step="0.1" value="{{ goal.target_hours }}" required>
|
||||||
|
|
||||||
|
<div style="margin-top: 1rem; display: flex; gap: 10px;">
|
||||||
|
<button type="submit" class="btn">Save Changes</button>
|
||||||
|
<a href="{{ url_for('goals') }}" class="btn" style="background: var(--text-secondary); text-decoration: none;">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Logic to populate subcategories and set current value
|
||||||
|
const currentSubcat = "{{ goal.subcategory }}";
|
||||||
|
|
||||||
|
function updateSubcategories() {
|
||||||
|
const actSelect = document.getElementById('activitySelect');
|
||||||
|
const subWrapper = document.getElementById('subcatWrapper');
|
||||||
|
const subSelect = document.getElementById('subcategorySelect');
|
||||||
|
|
||||||
|
const selectedOption = actSelect.options[actSelect.selectedIndex];
|
||||||
|
if (!selectedOption.value) {
|
||||||
|
subWrapper.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcats = JSON.parse(selectedOption.getAttribute('data-subcats') || '[]');
|
||||||
|
|
||||||
|
subSelect.innerHTML = '<option value="">-- All --</option>';
|
||||||
|
|
||||||
|
if (subcats && subcats.length > 0) {
|
||||||
|
subWrapper.style.display = 'block';
|
||||||
|
subcats.forEach(s => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = s;
|
||||||
|
opt.innerText = s;
|
||||||
|
if (s === currentSubcat && actSelect.value === "{{ goal.activity_id }}") {
|
||||||
|
opt.selected = true;
|
||||||
|
}
|
||||||
|
subSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subWrapper.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on load
|
||||||
|
updateSubcategories();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
111
templates/goals.html
Normal file
111
templates/goals.html
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h2>My Goals</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem;">
|
||||||
|
{% for goal in goals %}
|
||||||
|
<div style="border: 1px solid var(--border-dim); border-radius: 8px; padding: 1.5rem; position: relative;">
|
||||||
|
<div style="position: absolute; top: 10px; right: 10px;">
|
||||||
|
<a href="{{ url_for('edit_goal', goal_id=goal._id) }}" style="color: var(--text-secondary); text-decoration: none; font-size: 0.9rem; margin-right: 8px;">Edit</a>
|
||||||
|
<a href="{{ url_for('delete_goal', goal_id=goal._id) }}" style="color: var(--danger-color); text-decoration: none; font-size: 1.2rem;">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="font-size: 1.2rem; margin-bottom: 0.5rem; padding-right: 40px;">{{ goal.name }}</h3>
|
||||||
|
<div style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 1rem;">
|
||||||
|
{{ goal.frequency|capitalize }} Target: {{ goal.target_hours }}h
|
||||||
|
<br>
|
||||||
|
<div style="margin-top: 5px;">
|
||||||
|
<span style="background: {{ goal.activity_color }}; color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.8rem;">
|
||||||
|
{{ goal.activity_name }}
|
||||||
|
</span>
|
||||||
|
{% if goal.subcategory %}
|
||||||
|
<span style="color: var(--text-secondary); font-size: 0.8rem;"> > {{ goal.subcategory }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 5px;">
|
||||||
|
<!-- Use smart formatted progress (mins or hours) -->
|
||||||
|
<span style="font-weight: bold; font-size: 1.5rem; color: var(--primary-color);">{{ goal.display_progress }}</span>
|
||||||
|
<span style="font-size: 0.9rem; color: var(--text-secondary);">{{ goal.percent }}%</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<div style="width: 100%; height: 8px; background: var(--bg-input); border-radius: 4px; overflow: hidden;">
|
||||||
|
<div style="width: {{ goal.percent }}%; height: 100%; background: {% if goal.percent >= 100 %}#27ae60{% else %}var(--primary-color){% endif %}; transition: width 0.5s;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: var(--text-secondary);">No goals set yet.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="max-width: 500px;">
|
||||||
|
<h3>Set New Goal</h3>
|
||||||
|
<form method="POST">
|
||||||
|
<label>Goal Name</label>
|
||||||
|
<input type="text" name="name" placeholder="e.g. Learn Python" required>
|
||||||
|
|
||||||
|
<label>Activity (Required)</label>
|
||||||
|
<select name="activity_id" id="activitySelect" required onchange="updateSubcategories()">
|
||||||
|
<option value="">-- Select Activity --</option>
|
||||||
|
{% for act in activities %}
|
||||||
|
<option value="{{ act._id }}" data-subcats='{{ act.subcategories|default([])|tojson }}'>{{ act.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div id="subcatWrapper" style="display:none;">
|
||||||
|
<label>Subcategory (Optional)</label>
|
||||||
|
<select name="subcategory" id="subcategorySelect">
|
||||||
|
<!-- Javascript will populate this -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>Frequency</label>
|
||||||
|
<select name="frequency">
|
||||||
|
<option value="daily">Daily</option>
|
||||||
|
<option value="weekly">Weekly</option>
|
||||||
|
<option value="monthly">Monthly</option>
|
||||||
|
<option value="yearly">Yearly</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label>Target Hours</label>
|
||||||
|
<input type="number" name="target_hours" step="0.5" placeholder="e.g. 1.0" required>
|
||||||
|
|
||||||
|
<button type="submit" class="btn" style="margin-top: 1rem;">Create Goal</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateSubcategories() {
|
||||||
|
const actSelect = document.getElementById('activitySelect');
|
||||||
|
const subWrapper = document.getElementById('subcatWrapper');
|
||||||
|
const subSelect = document.getElementById('subcategorySelect');
|
||||||
|
|
||||||
|
const selectedOption = actSelect.options[actSelect.selectedIndex];
|
||||||
|
|
||||||
|
// Handle case where no activity is selected
|
||||||
|
if (!selectedOption.value) {
|
||||||
|
subWrapper.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcats = JSON.parse(selectedOption.getAttribute('data-subcats') || '[]');
|
||||||
|
|
||||||
|
subSelect.innerHTML = '<option value="">-- All --</option>';
|
||||||
|
|
||||||
|
if (subcats && subcats.length > 0) {
|
||||||
|
subWrapper.style.display = 'block';
|
||||||
|
subcats.forEach(s => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = s;
|
||||||
|
opt.innerText = s;
|
||||||
|
subSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subWrapper.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -160,6 +160,7 @@
|
|||||||
{% if session.user_id %}
|
{% if session.user_id %}
|
||||||
<a href="{{ url_for('index') }}">Tracker</a>
|
<a href="{{ url_for('index') }}">Tracker</a>
|
||||||
<a href="{{ url_for('tasks') }}">Tasks</a>
|
<a href="{{ url_for('tasks') }}">Tasks</a>
|
||||||
|
<a href="{{ url_for('goals') }}">Goals</a>
|
||||||
<a href="{{ url_for('logbook') }}">Logbook</a>
|
<a href="{{ url_for('logbook') }}">Logbook</a>
|
||||||
<a href="{{ url_for('logout') }}">Logout</a>
|
<a href="{{ url_for('logout') }}">Logout</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
Reference in New Issue
Block a user