show already completed tasks

This commit is contained in:
2026-02-10 20:02:16 +01:00
parent 9ef70e0436
commit 9a0adbf05c
7 changed files with 604 additions and 375 deletions

View File

@@ -1,271 +1,293 @@
{% extends "layout.html" %}
{% block content %}
<!-- Active Timer Section -->
<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 style="display: flex; flex-wrap: wrap; gap: 2rem; align-items: flex-start;">
<!-- Left Column: Activity Grid (Main Content) -->
<div style="flex: 1; min-width: 300px;">
<h2 style="margin-bottom: 1.5rem;">Start Tracking</h2>
<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;">
<div id="activeSubcatDisplay" style="background: #eee; padding: 2px 10px; border-radius: 10px;">
{{ current_entry.subcategory if current_entry else '' }}
</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 %}">
"{{ current_entry.note if current_entry else '' }}"
</p>
<div class="timer-display" id="timer">00:00:00</div>
<!-- Tasks for current activity -->
{% if tasks %}
<div style="margin: 1rem 0; padding: 1rem; background: rgba(255,255,255,0.7); border-radius: 5px;">
<h4>Active Tasks:</h4>
{% for task in tasks %}
<div style="margin-bottom: 5px; display: flex; align-items: center;">
<input type="checkbox" id="task_{{ task._id }}"
{% if task.status == 'completed' %}checked{% endif %}
onchange="toggleTask('{{ task._id }}', this)">
<label for="task_{{ task._id }}" style="margin-left: 10px; flex-grow: 1; {% if task.status == 'completed' %}text-decoration: line-through;{% endif %}">
<a href="{{ url_for('task_detail', task_id=task._id) }}">{{ task.name }}</a>
</label>
<div class="activity-grid">
{% 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>
{% endfor %}
</div>
{% endif %}
<form action="{{ url_for('stop_timer') }}" method="POST" style="text-align: center;">
<button type="submit" class="btn btn-danger" style="font-size: 1.2rem; width: 100%;">Stop Activity</button>
</form>
</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>
<!-- 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>
{% 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>
<!-- Hidden Form for Starting Activity with Note -->
<!-- Custom Start Modal -->
<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;">
<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">
<!-- 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">
<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>
<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>
<label>Note (Optional)</label>
<input type="text" name="note" placeholder="What are you working on?" autocomplete="off">
<ul id="newTasksListDisplay" style="list-style: none; padding: 0; margin-bottom: 1rem;"></ul>
<input type="hidden" name="tasks_list_data" id="tasksListData">
<div style="margin-top: 1rem; display: flex; justify-content: space-between;">
<!-- Cancel just closes modal, timer keeps running -->
<button type="button" class="btn" style="background: #95a5a6;" onclick="document.getElementById('startModal').style.display='none'">Skip</button>
<button type="submit" class="btn" style="background: #27ae60;">Update Details</button>
<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>
</div>
</div>
<script>
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 = "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>
<!-- Right Column: Sidebar (Active Timer) -->
<!-- Only visible if tracking, or hidden via JS initially if not tracking but we use 'display:none' on the card itself -->
<div style="flex: 0 0 380px; position: sticky; top: 100px; max-width: 100%;">
<div class="card active-section" id="activeSection" style="{% if not current_entry %}display:none;{% endif %} padding: 1.5rem;">
<div style="text-align: center; margin-bottom: 1rem;">
<small style="text-transform: uppercase; font-weight: bold; color: var(--primary-color);">Currently Tracking</small>
<h3 id="activeName" style="margin: 0.5rem 0; color: {{ current_entry.activity_color if current_entry else '#333' }}">
{{ current_entry.activity_name if current_entry else '' }}
</h3>
</div>
<!-- Context Info -->
<div id="activeSubcatContainer" style="text-align: center; {% if not current_entry or not current_entry.subcategory %}display:none;{% endif %} margin-bottom: 10px;">
<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>
<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>
<ul id="newTasksListDisplay" style="list-style: none; padding: 0; margin-bottom: 1rem;">
<!-- Items will be injected here -->
</ul>
<div class="timer-display" id="timer" style="margin: 1rem 0 2rem 0;">00:00:00</div>
<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>
<!-- 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;">&times;</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>
</form>
</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;">&times;</span>
`;
list.appendChild(li);
});
}
function prepareTasksList() {
document.getElementById('tasksListData').value = newActivityTasks.join(',');
}
</script>
</div>
{% endblock %}

View File

@@ -34,7 +34,11 @@
</select>
<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;">
<button type="submit" class="btn">Save Changes</button>
@@ -44,6 +48,14 @@
</div>
<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
const currentSubcat = "{{ goal.subcategory }}";

View File

@@ -1,7 +1,11 @@
{% extends "layout.html" %}
{% 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;">
<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;">
@@ -41,43 +45,76 @@
</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 -->
<!-- Goal Modal -->
<div id="goalModal" class="modal-backdrop">
<div class="modal-card">
<h3 style="margin-bottom: 1.5rem;">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>
<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 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>
<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>
<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() {
const actSelect = document.getElementById('activitySelect');
const subWrapper = document.getElementById('subcatWrapper');

View File

@@ -58,7 +58,7 @@
}
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 */
.card {
@@ -110,6 +110,98 @@
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 */
.btn {
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); }
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>
</head>
<body>

View File

@@ -39,6 +39,17 @@
</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>
<textarea name="note" rows="3" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-family: inherit;">{{ entry.note }}</textarea>

View File

@@ -10,12 +10,15 @@
<!-- Filters -->
<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);">
All Tasks
<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);">
Open
</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);">
Due Today
</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>
<!-- Grouped Lists -->
@@ -41,11 +44,34 @@
{% if act_tasks %}
<ul style="list-style: none; padding: 0; margin-top: 0;">
{% 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 %}
</ul>
{% else %}
<div style="padding-left: 20px; color: var(--text-secondary); font-size: 0.85rem; margin-bottom: 1rem;">No open tasks</div>
{% endif %}
</div>
{% endfor %}
@@ -67,7 +93,27 @@
</div>
<ul style="list-style: none; padding: 0; margin-top: 0;">
{% 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 %}
</ul>
</div>
@@ -97,8 +143,8 @@
</div>
<!-- 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 class="card" style="width: 90%; max-width: 450px; box-shadow: 0 10px 25px rgba(0,0,0,0.1);">
<div id="taskModal" class="modal-backdrop">
<div class="modal-card">
<h3 style="margin-bottom: 1.5rem;">Create New Task</h3>
<form action="{{ url_for('create_task') }}" method="POST">
<label>Task Name</label>
@@ -112,14 +158,10 @@
{% endfor %}
</select>
<div style="display: flex; gap: 1rem;">
<div style="flex: 1;">
<label>Due Date</label>
<input type="datetime-local" name="due_date">
</div>
</div>
<label>Due Date</label>
<input type="datetime-local" name="due_date">
<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;">
<label for="is_template" style="margin-top: 0; text-transform: none; font-weight: 400; line-height: 1.4;">
<strong>Save as Template?</strong><br>
@@ -127,23 +169,15 @@
</label>
</div>
<div style="display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="btn" style="background: white; color: var(--text-primary); border: 1px solid var(--border-dim);" onclick="closeTaskModal()">Cancel</button>
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 2rem;">
<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>
</div>
</form>
</div>
</div>
<!-- Inline macro for task rows to avoid duplication -->
{% 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.
-->
<!-- Scripts removed dead macros -->
<script>
function openTaskModal(activityId) {
const modal = document.getElementById('taskModal');
@@ -156,12 +190,12 @@
select.value = "";
}
modal.style.display = 'flex';
modal.classList.add('show');
nameInput.focus();
}
function closeTaskModal() {
document.getElementById('taskModal').style.display = 'none';
document.getElementById('taskModal').classList.remove('show');
}
// Close on backdrop click
@@ -171,74 +205,39 @@
// Filtering Logic
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).classList.add('active');
const taskItems = document.querySelectorAll('.task-item-row');
const today = new Date().toISOString().split('T')[0];
taskItems.forEach(item => {
if (type === 'all') {
item.style.display = 'flex';
const status = item.getAttribute('data-status');
const dateStr = item.getAttribute('data-due-date');
let show = false;
if (type === 'open') {
if (status !== 'completed') show = true;
} else if (type === 'today') {
const dateStr = item.getAttribute('data-due-date');
// Simple check if date string starts with today's YYYY-MM-DD
if (dateStr && dateStr.startsWith(today)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
// Open tasks due today
if (status !== 'completed' && dateStr && dateStr.startsWith(today)) show = true;
} else if (type === 'completed') {
if (status === 'completed') show = true;
}
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>
<!-- 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 %}