show already completed tasks
This commit is contained in:
19
app.py
19
app.py
@@ -188,9 +188,10 @@ 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, # Link to this specific session
|
'time_entry_id': new_entry_id,
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
|
'source': 'auto', # Mark as auto-generated
|
||||||
'created_at': datetime.now(),
|
'created_at': datetime.now(),
|
||||||
'comments': []
|
'comments': []
|
||||||
})
|
})
|
||||||
@@ -266,9 +267,10 @@ def toggle_timer(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, # Link to this specific session
|
'time_entry_id': new_entry_id,
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
|
'source': 'auto', # Mark as auto-generated
|
||||||
'created_at': datetime.now(),
|
'created_at': datetime.now(),
|
||||||
'comments': []
|
'comments': []
|
||||||
})
|
})
|
||||||
@@ -327,13 +329,11 @@ def tasks():
|
|||||||
activities = list(db.activities.find({'user_id': user_id}))
|
activities = list(db.activities.find({'user_id': user_id}))
|
||||||
|
|
||||||
# Categorize tasks
|
# Categorize tasks
|
||||||
# 1. Standalone / Manual Tasks
|
# Shows Manual Tasks (Open OR Completed). Hides "source: auto" (Session checklists)
|
||||||
tasks_list = list(db.tasks.find({
|
tasks_list = list(db.tasks.find({
|
||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
'is_template': False,
|
'is_template': False,
|
||||||
'time_entry_id': None, # Don't show session-specific generated tasks here to avoid clutter? or show all?
|
'source': {'$ne': 'auto'} # Show everything except explicitly auto-generated tasks
|
||||||
# Let's show manual tasks only. Session tasks are transient.
|
|
||||||
'status': 'open'
|
|
||||||
}).sort('due_date', 1))
|
}).sort('due_date', 1))
|
||||||
|
|
||||||
# 2. Templates
|
# 2. Templates
|
||||||
@@ -364,6 +364,7 @@ def create_task():
|
|||||||
'name': name,
|
'name': name,
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'is_template': is_template,
|
'is_template': is_template,
|
||||||
|
'source': 'manual', # Mark as manually created
|
||||||
'created_at': datetime.now(),
|
'created_at': datetime.now(),
|
||||||
'comments': []
|
'comments': []
|
||||||
}
|
}
|
||||||
@@ -454,8 +455,12 @@ def log_entry_detail(entry_id):
|
|||||||
new_note = request.form.get('note')
|
new_note = request.form.get('note')
|
||||||
start_str = request.form.get('start_time')
|
start_str = request.form.get('start_time')
|
||||||
end_str = request.form.get('end_time')
|
end_str = request.form.get('end_time')
|
||||||
|
subcategory = request.form.get('subcategory', '')
|
||||||
|
|
||||||
update_fields = {'note': new_note}
|
update_fields = {
|
||||||
|
'note': new_note,
|
||||||
|
'subcategory': subcategory
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if start_str:
|
if start_str:
|
||||||
|
|||||||
@@ -1,271 +1,293 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Active Timer Section -->
|
<div style="display: flex; flex-wrap: wrap; gap: 2rem; align-items: flex-start;">
|
||||||
<div class="card active-section" id="activeSection" style="{% if not current_entry %}display:none;{% endif %}">
|
|
||||||
<h3 id="activeName" style="text-align: center; color: {{ current_entry.activity_color if current_entry else '#333' }}">
|
|
||||||
Currently Tracking: {{ current_entry.activity_name if current_entry else '' }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div id="activeSubcatContainer" style="text-align: center; {% if not current_entry or not current_entry.subcategory %}display:none;{% endif %} margin: 0 auto 10px; display: table;">
|
<!-- Left Column: Activity Grid (Main Content) -->
|
||||||
<div id="activeSubcatDisplay" style="background: #eee; padding: 2px 10px; border-radius: 10px;">
|
<div style="flex: 1; min-width: 300px;">
|
||||||
{{ current_entry.subcategory if current_entry else '' }}
|
<h2 style="margin-bottom: 1.5rem;">Start Tracking</h2>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p id="activeNote" style="text-align: center; color: #666; font-style: italic; {% if not current_entry or not current_entry.note %}display:none;{% endif %}">
|
<div class="activity-grid">
|
||||||
"{{ current_entry.note if current_entry else '' }}"
|
{% for act in activities %}
|
||||||
</p>
|
<div style="position: relative;">
|
||||||
|
<div class="activity-card"
|
||||||
<div class="timer-display" id="timer">00:00:00</div>
|
style="background-color: {{ act.color }}"
|
||||||
|
data-name="{{ act.name }}"
|
||||||
<!-- Tasks for current activity -->
|
data-id="{{ act._id }}"
|
||||||
{% if tasks %}
|
data-color="{{ act.color }}"
|
||||||
<div style="margin: 1rem 0; padding: 1rem; background: rgba(255,255,255,0.7); border-radius: 5px;">
|
data-subcategories='{{ act.subcategories|default([])|tojson }}'
|
||||||
<h4>Active Tasks:</h4>
|
onclick="startActivityImmediate(this)">
|
||||||
{% for task in tasks %}
|
<span>{{ act.name }}</span>
|
||||||
<div style="margin-bottom: 5px; display: flex; align-items: center;">
|
</div>
|
||||||
<input type="checkbox" id="task_{{ task._id }}"
|
<!-- Small Edit Link -->
|
||||||
{% if task.status == 'completed' %}checked{% endif %}
|
<a href="{{ url_for('edit_activity', activity_id=act._id) }}"
|
||||||
onchange="toggleTask('{{ task._id }}', this)">
|
style="position: absolute; top: 5px; right: 5px; color: rgba(255,255,255,0.8); text-decoration: none; font-size: 0.8rem; padding: 5px;"
|
||||||
<label for="task_{{ task._id }}" style="margin-left: 10px; flex-grow: 1; {% if task.status == 'completed' %}text-decoration: line-through;{% endif %}">
|
title="Edit Activity">
|
||||||
<a href="{{ url_for('task_detail', task_id=task._id) }}">{{ task.name }}</a>
|
Edit
|
||||||
</label>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form action="{{ url_for('stop_timer') }}" method="POST" style="text-align: center;">
|
<!-- Add New Button -->
|
||||||
<button type="submit" class="btn btn-danger" style="font-size: 1.2rem; width: 100%;">Stop Activity</button>
|
<div class="activity-card" style="background-color: #95a5a6; color: white;" onclick="document.getElementById('newActivityForm').style.display='block'">
|
||||||
</form>
|
<span>+ New Activity</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Script for Timer -->
|
|
||||||
<script>
|
|
||||||
let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %};
|
|
||||||
let timerInterval;
|
|
||||||
|
|
||||||
function updateTimer() {
|
|
||||||
if (!startTime) return;
|
|
||||||
const now = new Date().getTime();
|
|
||||||
const diff = now - startTime;
|
|
||||||
|
|
||||||
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
||||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
||||||
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
|
||||||
|
|
||||||
const timerEl = document.getElementById("timer");
|
|
||||||
if (timerEl) {
|
|
||||||
timerEl.innerHTML =
|
|
||||||
(hours < 10 ? "0" + hours : hours) + ":" +
|
|
||||||
(minutes < 10 ? "0" + minutes : minutes) + ":" +
|
|
||||||
(seconds < 10 ? "0" + seconds : seconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startTime) {
|
|
||||||
timerInterval = setInterval(updateTimer, 1000);
|
|
||||||
updateTimer();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<!-- End Script -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Start Activity Section -->
|
|
||||||
<div class="activity-grid" style="margin-top: 2rem;">
|
|
||||||
{% for act in activities %}
|
|
||||||
<div style="position: relative;">
|
|
||||||
<div class="activity-card"
|
|
||||||
style="background-color: {{ act.color }}"
|
|
||||||
data-name="{{ act.name }}"
|
|
||||||
data-id="{{ act._id }}"
|
|
||||||
data-color="{{ act.color }}"
|
|
||||||
data-subcategories='{{ act.subcategories|default([])|tojson }}'
|
|
||||||
onclick="startActivityImmediate(this)">
|
|
||||||
<span>{{ act.name }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- Small Edit Link -->
|
|
||||||
<a href="{{ url_for('edit_activity', activity_id=act._id) }}"
|
|
||||||
style="position: absolute; top: 5px; right: 5px; color: rgba(255,255,255,0.8); text-decoration: none; font-size: 0.8rem; padding: 5px;"
|
|
||||||
title="Edit Activity">
|
|
||||||
Edit
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<!-- Add New Button -->
|
|
||||||
<div class="activity-card" style="background-color: #95a5a6; color: white;" onclick="document.getElementById('newActivityForm').style.display='block'">
|
|
||||||
<span>+ New Activity</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Hidden Form for Starting Activity with Note -->
|
<!-- Create Activity Form -->
|
||||||
|
<div id="newActivityForm" class="card" style="display:none; margin-top: 2rem;">
|
||||||
|
<h3>Create New Activity Category</h3>
|
||||||
|
<form action="{{ url_for('add_activity') }}" method="POST" onsubmit="prepareTasksList()">
|
||||||
|
<label>Name</label>
|
||||||
|
<input type="text" name="name" required placeholder="e.g. Household">
|
||||||
|
|
||||||
<!-- Custom Start Modal -->
|
<label>Color</label>
|
||||||
<div id="startModal" style="display:none; position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.5); z-index: 1000; justify-content: center; align-items: center;">
|
<input type="color" name="color" value="#3498db" style="width:100%; height:40px; border:none;">
|
||||||
<div class="card" style="width: 90%; max-width: 400px;">
|
|
||||||
<h3 id="modalTitle">Activity Started</h3>
|
|
||||||
|
|
||||||
<form id="startForm" action="{{ url_for('update_active_entry') }}" method="POST">
|
<label>Default Tasks (Template)</label>
|
||||||
|
<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>
|
||||||
|
|
||||||
<div id="subcategoryContainer" style="display: none; margin-bottom: 1rem;">
|
<div style="display: flex; gap: 5px; margin-bottom: 10px;">
|
||||||
<label>Subcategory / Context</label>
|
<input type="text" id="newTaskInput" placeholder="Add task name..." style="margin-bottom: 0;">
|
||||||
<select name="subcategory" id="modalSubcatSelect" style="width: 100%; padding: 0.5rem;">
|
<button type="button" class="btn" style="background: #27ae60;" onclick="addNewTask()">Add</button>
|
||||||
<option value="">-- None --</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Note (Optional)</label>
|
<ul id="newTasksListDisplay" style="list-style: none; padding: 0; margin-bottom: 1rem;"></ul>
|
||||||
<input type="text" name="note" placeholder="What are you working on?" autocomplete="off">
|
<input type="hidden" name="tasks_list_data" id="tasksListData">
|
||||||
|
|
||||||
<div style="margin-top: 1rem; display: flex; justify-content: space-between;">
|
<div style="margin-top: 1rem;">
|
||||||
<!-- Cancel just closes modal, timer keeps running -->
|
<button type="submit" class="btn">Create</button>
|
||||||
<button type="button" class="btn" style="background: #95a5a6;" onclick="document.getElementById('startModal').style.display='none'">Skip</button>
|
<button type="button" class="btn" style="background: var(--text-secondary)" onclick="document.getElementById('newActivityForm').style.display='none'">Cancel</button>
|
||||||
<button type="submit" class="btn" style="background: #27ae60;">Update Details</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<!-- Right Column: Sidebar (Active Timer) -->
|
||||||
function startActivityImmediate(element) {
|
<!-- Only visible if tracking, or hidden via JS initially if not tracking but we use 'display:none' on the card itself -->
|
||||||
const id = element.getAttribute('data-id');
|
<div style="flex: 0 0 380px; position: sticky; top: 100px; max-width: 100%;">
|
||||||
const name = element.getAttribute('data-name');
|
|
||||||
const color = element.getAttribute('data-color');
|
|
||||||
const subcats = JSON.parse(element.getAttribute('data-subcategories') || '[]');
|
|
||||||
|
|
||||||
// 1. Start Background Timer
|
<div class="card active-section" id="activeSection" style="{% if not current_entry %}display:none;{% endif %} padding: 1.5rem;">
|
||||||
fetch('/start_timer_bg/' + id, { method: 'POST' })
|
<div style="text-align: center; margin-bottom: 1rem;">
|
||||||
.then(res => res.json())
|
<small style="text-transform: uppercase; font-weight: bold; color: var(--primary-color);">Currently Tracking</small>
|
||||||
.then(data => {
|
<h3 id="activeName" style="margin: 0.5rem 0; color: {{ current_entry.activity_color if current_entry else '#333' }}">
|
||||||
if(data.status === 'success') {
|
{{ current_entry.activity_name if current_entry else '' }}
|
||||||
// 2. Start Local Timer UI
|
</h3>
|
||||||
startTime = new Date(data.start_time).getTime();
|
|
||||||
clearInterval(timerInterval);
|
|
||||||
timerInterval = setInterval(updateTimer, 1000);
|
|
||||||
updateTimer();
|
|
||||||
|
|
||||||
// Update UI State
|
|
||||||
document.getElementById('activeSection').style.display = 'block';
|
|
||||||
document.getElementById('activeName').innerText = "Currently Tracking: " + name;
|
|
||||||
document.getElementById('activeName').style.color = color;
|
|
||||||
|
|
||||||
// Reset Note/Subcat display
|
|
||||||
document.getElementById('activeNote').style.display = 'none';
|
|
||||||
document.getElementById('activeSubcatContainer').style.display = 'none';
|
|
||||||
|
|
||||||
// 3. Open Modal for Details
|
|
||||||
openDetailsModal(name, subcats);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openDetailsModal(name, subcats) {
|
|
||||||
document.getElementById('modalTitle').innerText = name + " Started";
|
|
||||||
|
|
||||||
// Setup Subcategories
|
|
||||||
const subcatContainer = document.getElementById('subcategoryContainer');
|
|
||||||
const select = document.getElementById('modalSubcatSelect');
|
|
||||||
select.innerHTML = '<option value="">-- None --</option>';
|
|
||||||
|
|
||||||
if (subcats && subcats.length > 0) {
|
|
||||||
subcatContainer.style.display = 'block';
|
|
||||||
subcats.forEach(sc => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = sc;
|
|
||||||
opt.innerText = sc;
|
|
||||||
select.appendChild(opt);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
subcatContainer.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show Modal
|
|
||||||
document.getElementById('startModal').style.display = 'flex';
|
|
||||||
document.querySelector('#startForm input[name="note"]').focus();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Create Activity Form -->
|
|
||||||
<div id="newActivityForm" class="card" style="display:none; margin-top: 2rem;">
|
|
||||||
<h3>Create New Activity Category</h3>
|
|
||||||
<form action="{{ url_for('add_activity') }}" method="POST" onsubmit="prepareTasksList()">
|
|
||||||
<label>Name</label>
|
|
||||||
<input type="text" name="name" required placeholder="e.g. Household">
|
|
||||||
|
|
||||||
<label>Color</label>
|
|
||||||
<input type="color" name="color" value="#3498db" style="width:100%; height:40px; border:none;">
|
|
||||||
|
|
||||||
<label>Default Tasks (Template)</label>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<div style="display: flex; gap: 5px; margin-bottom: 10px;">
|
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<ul id="newTasksListDisplay" style="list-style: none; padding: 0; margin-bottom: 1rem;">
|
<!-- Context Info -->
|
||||||
<!-- Items will be injected here -->
|
<div id="activeSubcatContainer" style="text-align: center; {% if not current_entry or not current_entry.subcategory %}display:none;{% endif %} margin-bottom: 10px;">
|
||||||
</ul>
|
<span id="activeSubcatDisplay" style="background: var(--bg-input); padding: 4px 12px; border-radius: 12px; font-size: 0.85rem; color: var(--text-secondary);">
|
||||||
|
{{ current_entry.subcategory if current_entry else '' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="hidden" name="tasks_list_data" id="tasksListData">
|
<p id="activeNote" style="text-align: center; color: var(--text-secondary); font-style: italic; font-size: 0.9rem; margin-bottom: 1.5rem; {% if not current_entry or not current_entry.note %}display:none;{% endif %}">
|
||||||
|
"{{ current_entry.note if current_entry else '' }}"
|
||||||
|
</p>
|
||||||
|
|
||||||
<div style="margin-top: 1rem;">
|
<div class="timer-display" id="timer" style="margin: 1rem 0 2rem 0;">00:00:00</div>
|
||||||
<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>
|
<!-- Tasks List (Redesigned) -->
|
||||||
|
{% if tasks %}
|
||||||
|
<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 class="checklist-container">
|
||||||
|
{% for task in tasks %}
|
||||||
|
<div class="checklist-item {% if task.status == 'completed' %}completed{% endif %}">
|
||||||
|
<input type="checkbox" id="task_{{ task._id }}"
|
||||||
|
{% if task.status == 'completed' %}checked{% endif %}
|
||||||
|
onchange="toggleTask('{{ task._id }}', this)">
|
||||||
|
<label for="task_{{ task._id }}">
|
||||||
|
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: inherit;">
|
||||||
|
{{ task.name }}
|
||||||
|
</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts (Timer, Tasks, Modal) -->
|
||||||
|
<script>
|
||||||
|
// Timer Logic
|
||||||
|
let startTime = {% if current_entry %}{{ current_entry.start_time.timestamp() * 1000 }}{% else %}null{% endif %};
|
||||||
|
let timerInterval;
|
||||||
|
|
||||||
|
function updateTimer() {
|
||||||
|
if (!startTime) return;
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const diff = now - startTime;
|
||||||
|
|
||||||
|
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||||
|
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
|
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
||||||
|
|
||||||
|
const timerEl = document.getElementById("timer");
|
||||||
|
if (timerEl) {
|
||||||
|
timerEl.innerHTML =
|
||||||
|
(hours < 10 ? "0" + hours : hours) + ":" +
|
||||||
|
(minutes < 10 ? "0" + minutes : minutes) + ":" +
|
||||||
|
(seconds < 10 ? "0" + seconds : seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTime) {
|
||||||
|
timerInterval = setInterval(updateTimer, 1000);
|
||||||
|
updateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// AJAX for Tasks
|
||||||
|
function toggleTask(taskId, checkbox) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('task_id', taskId);
|
||||||
|
formData.append('is_checked', checkbox.checked);
|
||||||
|
|
||||||
|
fetch('/complete_task', { method: 'POST', body: formData })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
const item = checkbox.closest('.checklist-item');
|
||||||
|
if(checkbox.checked) {
|
||||||
|
item.classList.add('completed');
|
||||||
|
} else {
|
||||||
|
item.classList.remove('completed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal & Start Logic
|
||||||
|
function closeStartModal() {
|
||||||
|
document.getElementById('startModal').classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close on backdrop click
|
||||||
|
document.getElementById('startModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) closeStartModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
function startActivityImmediate(element) {
|
||||||
|
const id = element.getAttribute('data-id');
|
||||||
|
const name = element.getAttribute('data-name');
|
||||||
|
const color = element.getAttribute('data-color');
|
||||||
|
const subcats = JSON.parse(element.getAttribute('data-subcategories') || '[]');
|
||||||
|
|
||||||
|
// 1. Start Background Timer
|
||||||
|
fetch('/start_timer_bg/' + id, { method: 'POST' })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if(data.status === 'success') {
|
||||||
|
// 2. Start Local Timer UI
|
||||||
|
startTime = new Date(data.start_time).getTime();
|
||||||
|
clearInterval(timerInterval);
|
||||||
|
timerInterval = setInterval(updateTimer, 1000);
|
||||||
|
updateTimer();
|
||||||
|
|
||||||
|
// Update UI State
|
||||||
|
document.getElementById('activeSection').style.display = 'block';
|
||||||
|
document.getElementById('activeName').innerText = name;
|
||||||
|
document.getElementById('activeName').style.color = color;
|
||||||
|
|
||||||
|
// Reset Note/Subcat display
|
||||||
|
document.getElementById('activeNote').style.display = 'none';
|
||||||
|
document.getElementById('activeSubcatContainer').style.display = 'none';
|
||||||
|
|
||||||
|
// 3. Open Modal for Details
|
||||||
|
openDetailsModal(name, subcats);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDetailsModal(name, subcats) {
|
||||||
|
document.getElementById('modalTitle').innerText = name + " Started";
|
||||||
|
|
||||||
|
// Setup Subcategories
|
||||||
|
const subcatContainer = document.getElementById('subcategoryContainer');
|
||||||
|
const select = document.getElementById('modalSubcatSelect');
|
||||||
|
select.innerHTML = '<option value="">-- None --</option>';
|
||||||
|
|
||||||
|
if (subcats && subcats.length > 0) {
|
||||||
|
subcatContainer.style.display = 'block';
|
||||||
|
subcats.forEach(sc => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = sc;
|
||||||
|
opt.innerText = sc;
|
||||||
|
select.appendChild(opt);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subcatContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show Modal
|
||||||
|
document.getElementById('startModal').classList.add('show');
|
||||||
|
document.querySelector('#startForm input[name="note"]').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Task List Logic for Activity Creation form (Reusing existing script)
|
||||||
|
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>
|
||||||
|
|
||||||
|
<!-- Custom Start Modal (Hidden by default) -->
|
||||||
|
<div id="startModal" class="modal-backdrop">
|
||||||
|
<div class="modal-card">
|
||||||
|
<h3 id="modalTitle" style="margin-bottom: 1.5rem;">Activity Started</h3>
|
||||||
|
<form id="startForm" action="{{ url_for('update_active_entry') }}" method="POST">
|
||||||
|
<div id="subcategoryContainer" style="display: none; margin-bottom: 1rem;">
|
||||||
|
<label>Subcategory / Context</label>
|
||||||
|
<select name="subcategory" id="modalSubcatSelect" style="width: 100%; padding: 0.5rem;">
|
||||||
|
<option value="">-- None --</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label>Note (Optional)</label>
|
||||||
|
<input type="text" name="note" placeholder="What are you working on?" autocomplete="off">
|
||||||
|
<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="submit" class="btn" style="background: #27ae60;">Update Details</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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 %}
|
||||||
|
|||||||
@@ -34,7 +34,11 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label>Target Hours</label>
|
<label>Target Hours</label>
|
||||||
<input type="number" name="target_hours" step="0.1" value="{{ goal.target_hours }}" required>
|
<div class="stepper-input">
|
||||||
|
<button type="button" onclick="stepValue(this, -0.5)">-</button>
|
||||||
|
<input type="number" name="target_hours" step="0.1" value="{{ goal.target_hours }}" required>
|
||||||
|
<button type="button" onclick="stepValue(this, 0.5)">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 1rem; display: flex; gap: 10px;">
|
<div style="margin-top: 1rem; display: flex; gap: 10px;">
|
||||||
<button type="submit" class="btn">Save Changes</button>
|
<button type="submit" class="btn">Save Changes</button>
|
||||||
@@ -44,6 +48,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function stepValue(btn, step) {
|
||||||
|
const input = btn.parentElement.querySelector('input');
|
||||||
|
let val = parseFloat(input.value) || 0;
|
||||||
|
val += step;
|
||||||
|
if (val < 0) val = 0;
|
||||||
|
input.value = val.toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
// Logic to populate subcategories and set current value
|
// Logic to populate subcategories and set current value
|
||||||
const currentSubcat = "{{ goal.subcategory }}";
|
const currentSubcat = "{{ goal.subcategory }}";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||||
|
<h2 style="margin: 0;">My Goals</h2>
|
||||||
|
<button class="btn" onclick="openGoalModal()">+ New Goal</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card" style="margin-bottom: 2rem;">
|
<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;">
|
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem;">
|
||||||
{% for goal in goals %}
|
{% for goal in goals %}
|
||||||
<div style="border: 1px solid var(--border-dim); border-radius: 8px; padding: 1.5rem; position: relative;">
|
<div style="border: 1px solid var(--border-dim); border-radius: 8px; padding: 1.5rem; position: relative;">
|
||||||
@@ -41,43 +45,76 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" style="max-width: 500px;">
|
<!-- Goal Modal -->
|
||||||
<h3>Set New Goal</h3>
|
<div id="goalModal" class="modal-backdrop">
|
||||||
<form method="POST">
|
<div class="modal-card">
|
||||||
<label>Goal Name</label>
|
<h3 style="margin-bottom: 1.5rem;">Set New Goal</h3>
|
||||||
<input type="text" name="name" placeholder="e.g. Learn Python" required>
|
<form method="POST">
|
||||||
|
<label>Goal Name</label>
|
||||||
|
<input type="text" name="name" placeholder="e.g. Learn Python" required>
|
||||||
|
|
||||||
<label>Activity (Required)</label>
|
<label>Activity (Required)</label>
|
||||||
<select name="activity_id" id="activitySelect" required onchange="updateSubcategories()">
|
<select name="activity_id" id="activitySelect" required onchange="updateSubcategories()">
|
||||||
<option value="">-- Select Activity --</option>
|
<option value="">-- Select Activity --</option>
|
||||||
{% for act in activities %}
|
{% for act in activities %}
|
||||||
<option value="{{ act._id }}" data-subcats='{{ act.subcategories|default([])|tojson }}'>{{ act.name }}</option>
|
<option value="{{ act._id }}" data-subcats='{{ act.subcategories|default([])|tojson }}'>{{ act.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
|
||||||
|
|
||||||
<div id="subcatWrapper" style="display:none;">
|
|
||||||
<label>Subcategory (Optional)</label>
|
|
||||||
<select name="subcategory" id="subcategorySelect">
|
|
||||||
<!-- Javascript will populate this -->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
|
||||||
|
|
||||||
<label>Frequency</label>
|
<div id="subcatWrapper" style="display:none;">
|
||||||
<select name="frequency">
|
<label>Subcategory (Optional)</label>
|
||||||
<option value="daily">Daily</option>
|
<select name="subcategory" id="subcategorySelect">
|
||||||
<option value="weekly">Weekly</option>
|
<!-- Javascript will populate this -->
|
||||||
<option value="monthly">Monthly</option>
|
</select>
|
||||||
<option value="yearly">Yearly</option>
|
</div>
|
||||||
</select>
|
|
||||||
|
|
||||||
<label>Target Hours</label>
|
<label>Frequency</label>
|
||||||
<input type="number" name="target_hours" step="0.5" placeholder="e.g. 1.0" required>
|
<select name="frequency">
|
||||||
|
<option value="daily">Daily</option>
|
||||||
|
<option value="weekly">Weekly</option>
|
||||||
|
<option value="monthly">Monthly</option>
|
||||||
|
<option value="yearly">Yearly</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<button type="submit" class="btn" style="margin-top: 1rem;">Create Goal</button>
|
<label>Target Hours</label>
|
||||||
</form>
|
<div class="stepper-input">
|
||||||
|
<button type="button" onclick="stepValue(this, -0.5)">-</button>
|
||||||
|
<input type="number" name="target_hours" step="0.5" value="1.0" required>
|
||||||
|
<button type="button" onclick="stepValue(this, 0.5)">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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="closeGoalModal()">Cancel</button>
|
||||||
|
<button type="submit" class="btn">Create Goal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function stepValue(btn, step) {
|
||||||
|
const input = btn.parentElement.querySelector('input');
|
||||||
|
let val = parseFloat(input.value) || 0;
|
||||||
|
val += step;
|
||||||
|
if (val < 0) val = 0;
|
||||||
|
input.value = val.toFixed(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openGoalModal() {
|
||||||
|
const modal = document.getElementById('goalModal');
|
||||||
|
modal.classList.add('show'); // Triggers flex display and opacity transition
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeGoalModal() {
|
||||||
|
const modal = document.getElementById('goalModal');
|
||||||
|
modal.classList.remove('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close on backdrop click
|
||||||
|
document.getElementById('goalModal').addEventListener('click', function(e) {
|
||||||
|
if (e.target === this) closeGoalModal();
|
||||||
|
});
|
||||||
|
|
||||||
function updateSubcategories() {
|
function updateSubcategories() {
|
||||||
const actSelect = document.getElementById('activitySelect');
|
const actSelect = document.getElementById('activitySelect');
|
||||||
const subWrapper = document.getElementById('subcatWrapper');
|
const subWrapper = document.getElementById('subcatWrapper');
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
nav a:hover { color: var(--text-primary); }
|
nav a:hover { color: var(--text-primary); }
|
||||||
|
|
||||||
.container { max-width: 900px; margin: 3rem auto; padding: 0 1.5rem; }
|
.container { max-width: 1100px; margin: 3rem auto; padding: 0 1.5rem; }
|
||||||
|
|
||||||
/* Clean Notion-style Cards */
|
/* Clean Notion-style Cards */
|
||||||
.card {
|
.card {
|
||||||
@@ -110,6 +110,98 @@
|
|||||||
box-shadow: 0 0 0 3px rgba(35, 131, 226, 0.1);
|
box-shadow: 0 0 0 3px rgba(35, 131, 226, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Select Style */
|
||||||
|
select {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2337352f%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 12px center;
|
||||||
|
background-size: 10px;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notion-like Number Stepper */
|
||||||
|
.stepper-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--bg-input);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.stepper-input:focus-within {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 0 3px rgba(35, 131, 226, 0.1);
|
||||||
|
}
|
||||||
|
.stepper-input button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.stepper-input button:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
.stepper-input input {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.stepper-input input:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Checklist Styles */
|
||||||
|
.checklist-container {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.checklist-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid var(--border-dim);
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.checklist-item:last-child { border-bottom: none; }
|
||||||
|
.checklist-item input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
accent-color: var(--primary-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.checklist-item label {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-transform: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.checklist-item.completed label {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.checklist-item.completed {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
/* Minimalist Buttons */
|
/* Minimalist Buttons */
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -147,6 +239,57 @@
|
|||||||
.active-section { border: 1px solid var(--primary-color); background: white; box-shadow: 0 4px 12px rgba(35, 131, 226, 0.1); }
|
.active-section { border: 1px solid var(--primary-color); background: white; box-shadow: 0 4px 12px rgba(35, 131, 226, 0.1); }
|
||||||
|
|
||||||
small { color: var(--text-secondary); }
|
small { color: var(--text-secondary); }
|
||||||
|
|
||||||
|
/* Modern Modal System */
|
||||||
|
.modal-backdrop {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
z-index: 2000;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.modal-backdrop.show {
|
||||||
|
display: flex;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.modal-card {
|
||||||
|
background: white;
|
||||||
|
width: 100%; max-width: 480px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.2);
|
||||||
|
transform: scale(0.95);
|
||||||
|
transition: transform 0.2s ease-out;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.modal-backdrop.show .modal-card {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Improved Date/Time Picker styling */
|
||||||
|
input[type="datetime-local"], input[type="date"], input[type="time"] {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
font-family: inherit;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-input);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* Customizing the icon */
|
||||||
|
::-webkit-calendar-picker-indicator {
|
||||||
|
filter: invert(0.4); /* Make icon grey */
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
::-webkit-calendar-picker-indicator:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -39,6 +39,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Subcategory Edit -->
|
||||||
|
<label>Subcategory</label>
|
||||||
|
<select name="subcategory">
|
||||||
|
<option value="">-- None --</option>
|
||||||
|
{% if entry.activity and entry.activity.subcategories %}
|
||||||
|
{% for sub in entry.activity.subcategories %}
|
||||||
|
<option value="{{ sub }}" {% if sub == entry.subcategory %}selected{% endif %}>{{ sub }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
|
||||||
<label>Note</label>
|
<label>Note</label>
|
||||||
<textarea name="note" rows="3" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-family: inherit;">{{ entry.note }}</textarea>
|
<textarea name="note" rows="3" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-family: inherit;">{{ entry.note }}</textarea>
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,15 @@
|
|||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div style="border-bottom: 1px solid var(--border-dim); margin-bottom: 2rem; padding-bottom: 0.5rem; display: flex; gap: 20px;">
|
<div style="border-bottom: 1px solid var(--border-dim); margin-bottom: 2rem; padding-bottom: 0.5rem; display: flex; gap: 20px;">
|
||||||
<div class="filter-tab active" id="filter-all" onclick="setFilter('all')" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-primary);">
|
<div class="filter-tab active" id="filter-open" onclick="setFilter('open')" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-primary);">
|
||||||
All Tasks
|
Open
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-tab" id="filter-today" onclick="setFilter('today')" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary);">
|
<div class="filter-tab" id="filter-today" onclick="setFilter('today')" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary);">
|
||||||
Due Today
|
Due Today
|
||||||
</div>
|
</div>
|
||||||
|
<div class="filter-tab" id="filter-completed" onclick="setFilter('completed')" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary);">
|
||||||
|
Completed
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Grouped Lists -->
|
<!-- Grouped Lists -->
|
||||||
@@ -41,11 +44,34 @@
|
|||||||
{% if act_tasks %}
|
{% if act_tasks %}
|
||||||
<ul style="list-style: none; padding: 0; margin-top: 0;">
|
<ul style="list-style: none; padding: 0; margin-top: 0;">
|
||||||
{% for task in act_tasks %}
|
{% for task in act_tasks %}
|
||||||
{% include 'task_row_partial' %}
|
<!-- Inline Task Row -->
|
||||||
|
<li class="task-item-row"
|
||||||
|
data-due-date="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}"
|
||||||
|
data-status="{{ task.status }}"
|
||||||
|
style="padding: 8px 0; border-bottom: 1px solid var(--border-dim); display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; overflow: hidden;">
|
||||||
|
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: var(--text-primary); font-weight: 500; {% if task.status == 'completed' %}text-decoration: line-through; color: var(--text-secondary);{% endif %}">
|
||||||
|
{{ task.name }}
|
||||||
|
</a>
|
||||||
|
{% if task.due_date %}
|
||||||
|
<span style="font-size: 0.75rem; color: var(--text-secondary); background: var(--bg-input); padding: 2px 6px; border-radius: 4px;">
|
||||||
|
{{ task.due_date.strftime('%d. %b %H:%M') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 10px;">
|
||||||
|
<form action="{{ url_for('toggle_timer', activity_id=task.activity_id) }}" method="POST" style="margin: 0;">
|
||||||
|
<input type="hidden" name="note" value="{{ task.name }}">
|
||||||
|
<button type="submit" title="Start Timer" style="background: none; border: 1px solid var(--border-dim); cursor: pointer; border-radius: 4px; padding: 2px 6px; color: var(--text-secondary); font-size: 0.8rem;">
|
||||||
|
▶
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
|
||||||
<div style="padding-left: 20px; color: var(--text-secondary); font-size: 0.85rem; margin-bottom: 1rem;">No open tasks</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -67,7 +93,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul style="list-style: none; padding: 0; margin-top: 0;">
|
<ul style="list-style: none; padding: 0; margin-top: 0;">
|
||||||
{% for task in no_act_tasks %}
|
{% for task in no_act_tasks %}
|
||||||
{% include 'task_row_partial' %}
|
<!-- Inline Task Row -->
|
||||||
|
<li class="task-item-row"
|
||||||
|
data-due-date="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}"
|
||||||
|
data-status="{{ task.status }}"
|
||||||
|
style="padding: 8px 0; border-bottom: 1px solid var(--border-dim); display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; overflow: hidden;">
|
||||||
|
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: var(--text-primary); font-weight: 500; {% if task.status == 'completed' %}text-decoration: line-through; color: var(--text-secondary);{% endif %}">
|
||||||
|
{{ task.name }}
|
||||||
|
</a>
|
||||||
|
{% if task.due_date %}
|
||||||
|
<span style="font-size: 0.75rem; color: var(--text-secondary); background: var(--bg-input); padding: 2px 6px; border-radius: 4px;">
|
||||||
|
{{ task.due_date.strftime('%d. %b %H:%M') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 10px;">
|
||||||
|
<!-- No start button usually here if no activity linked -->
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,8 +143,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal for New Task -->
|
<!-- Modal for New Task -->
|
||||||
<div id="taskModal" style="display:none; position: fixed; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.4); z-index: 2000; justify-content: center; align-items: center;">
|
<div id="taskModal" class="modal-backdrop">
|
||||||
<div class="card" style="width: 90%; max-width: 450px; box-shadow: 0 10px 25px rgba(0,0,0,0.1);">
|
<div class="modal-card">
|
||||||
<h3 style="margin-bottom: 1.5rem;">Create New Task</h3>
|
<h3 style="margin-bottom: 1.5rem;">Create New Task</h3>
|
||||||
<form action="{{ url_for('create_task') }}" method="POST">
|
<form action="{{ url_for('create_task') }}" method="POST">
|
||||||
<label>Task Name</label>
|
<label>Task Name</label>
|
||||||
@@ -112,14 +158,10 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div style="display: flex; gap: 1rem;">
|
<label>Due Date</label>
|
||||||
<div style="flex: 1;">
|
<input type="datetime-local" name="due_date">
|
||||||
<label>Due Date</label>
|
|
||||||
<input type="datetime-local" name="due_date">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom: 1.5rem; display: flex; align-items: start; gap: 10px;">
|
<div style="margin-bottom: 1.5rem; display: flex; align-items: start; gap: 10px; margin-top: 1.5rem;">
|
||||||
<input type="checkbox" id="is_template" name="is_template" value="1" style="width: auto; margin-top: 4px;">
|
<input type="checkbox" id="is_template" name="is_template" value="1" style="width: auto; margin-top: 4px;">
|
||||||
<label for="is_template" style="margin-top: 0; text-transform: none; font-weight: 400; line-height: 1.4;">
|
<label for="is_template" style="margin-top: 0; text-transform: none; font-weight: 400; line-height: 1.4;">
|
||||||
<strong>Save as Template?</strong><br>
|
<strong>Save as Template?</strong><br>
|
||||||
@@ -127,23 +169,15 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: flex-end; gap: 10px;">
|
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 2rem;">
|
||||||
<button type="button" class="btn" style="background: white; color: var(--text-primary); border: 1px solid var(--border-dim);" onclick="closeTaskModal()">Cancel</button>
|
<button type="button" class="btn" style="background: transparent; color: var(--text-secondary); border: 1px solid var(--border-dim);" onclick="closeTaskModal()">Cancel</button>
|
||||||
<button type="submit" class="btn">Create Task</button>
|
<button type="submit" class="btn">Create Task</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inline macro for task rows to avoid duplication -->
|
<!-- Scripts removed dead macros -->
|
||||||
{% set today_str = now_date_str %} <!-- Assuming passed or calculated in JS. Let's use JS for 'Today' logic -->
|
|
||||||
|
|
||||||
<!-- We define the snippet inline via standard HTML inside loops above, but effectively we want this structure: -->
|
|
||||||
<!--
|
|
||||||
Used a macro or include approach? Jinja 'include' with local context is tricky inside loops for simple snippets.
|
|
||||||
I'll create the row logic directly in the loops above, but here is the block for reference.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function openTaskModal(activityId) {
|
function openTaskModal(activityId) {
|
||||||
const modal = document.getElementById('taskModal');
|
const modal = document.getElementById('taskModal');
|
||||||
@@ -156,12 +190,12 @@
|
|||||||
select.value = "";
|
select.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.style.display = 'flex';
|
modal.classList.add('show');
|
||||||
nameInput.focus();
|
nameInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeTaskModal() {
|
function closeTaskModal() {
|
||||||
document.getElementById('taskModal').style.display = 'none';
|
document.getElementById('taskModal').classList.remove('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close on backdrop click
|
// Close on backdrop click
|
||||||
@@ -171,74 +205,39 @@
|
|||||||
|
|
||||||
// Filtering Logic
|
// Filtering Logic
|
||||||
function setFilter(type) {
|
function setFilter(type) {
|
||||||
document.querySelectorAll('.filter-tab').forEach(el => el.style.color = 'var(--text-secondary)');
|
document.querySelectorAll('.filter-tab').forEach(el => {
|
||||||
|
el.style.color = 'var(--text-secondary)';
|
||||||
|
el.classList.remove('active');
|
||||||
|
});
|
||||||
document.getElementById('filter-' + type).style.color = 'var(--text-primary)';
|
document.getElementById('filter-' + type).style.color = 'var(--text-primary)';
|
||||||
|
document.getElementById('filter-' + type).classList.add('active');
|
||||||
|
|
||||||
const taskItems = document.querySelectorAll('.task-item-row');
|
const taskItems = document.querySelectorAll('.task-item-row');
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
taskItems.forEach(item => {
|
taskItems.forEach(item => {
|
||||||
if (type === 'all') {
|
const status = item.getAttribute('data-status');
|
||||||
item.style.display = 'flex';
|
const dateStr = item.getAttribute('data-due-date');
|
||||||
|
|
||||||
|
let show = false;
|
||||||
|
|
||||||
|
if (type === 'open') {
|
||||||
|
if (status !== 'completed') show = true;
|
||||||
} else if (type === 'today') {
|
} else if (type === 'today') {
|
||||||
const dateStr = item.getAttribute('data-due-date');
|
// Open tasks due today
|
||||||
// Simple check if date string starts with today's YYYY-MM-DD
|
if (status !== 'completed' && dateStr && dateStr.startsWith(today)) show = true;
|
||||||
if (dateStr && dateStr.startsWith(today)) {
|
} else if (type === 'completed') {
|
||||||
item.style.display = 'flex';
|
if (status === 'completed') show = true;
|
||||||
} else {
|
|
||||||
item.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.style.display = show ? 'flex' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide groups that become empty? Optional.
|
// Removed the group hiding logic block here to ensure Activity headers always stay visible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize filter
|
||||||
|
setFilter('open');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Inline Task Row Template Helper -->
|
|
||||||
{% macro task_row_partial() %}
|
|
||||||
<!-- This technically needs to be a separate file or defined as a macro at top of file.
|
|
||||||
Since I can't easily create a partial file in this response format without explicit request,
|
|
||||||
Reference: this is the HTML used inside the loops above. -->
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% macro task_row_partial() %}
|
|
||||||
<!-- NOTE: This is a hack to define the row content for the loops above.
|
|
||||||
Normally you'd copy-paste this inside the loops or use a real macro.
|
|
||||||
Since I can't put macros inside blocks in standard Jinja without issues,
|
|
||||||
I will copy paste this HTML into the loops in the final file version. -->
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
<!--
|
|
||||||
REAL TEMPLATE CONTENT FOR INCLUSION IN LOOPS:
|
|
||||||
-->
|
|
||||||
{% block task_row_content %}
|
|
||||||
<!-- ... THIS IS USED INSIDE THE LOOPS ... -->
|
|
||||||
<li class="task-item-row" data-due-date="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}"
|
|
||||||
style="padding: 8px 0; border-bottom: 1px solid var(--border-dim); display: flex; justify-content: space-between; align-items: center;">
|
|
||||||
|
|
||||||
<div style="display: flex; alignItems: center; gap: 10px; overflow: hidden;">
|
|
||||||
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: var(--text-primary); font-weight: 500;">
|
|
||||||
{{ task.name }}
|
|
||||||
</a>
|
|
||||||
{% if task.due_date %}
|
|
||||||
<span style="font-size: 0.75rem; color: {% if task.due_date < now %}var(--danger-color){% else %}var(--text-secondary){% endif %}; background: var(--bg-input); padding: 2px 6px; border-radius: 4px;">
|
|
||||||
{{ task.due_date.strftime('%d. %b %H:%M') }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px;">
|
|
||||||
{% if task.activity_id %}
|
|
||||||
<form action="{{ url_for('toggle_timer', activity_id=task.activity_id) }}" method="POST" style="margin: 0;">
|
|
||||||
<input type="hidden" name="note" value="{{ task.name }}">
|
|
||||||
<button type="submit" title="Start Timer" style="background: none; border: 1px solid var(--border-dim); cursor: pointer; border-radius: 4px; padding: 2px 6px; color: var(--text-secondary); font-size: 0.8rem;">
|
|
||||||
▶
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user