show already completed tasks
This commit is contained in:
@@ -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;">×</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;">×</span>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareTasksList() {
|
||||
document.getElementById('tasksListData').value = newActivityTasks.join(',');
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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 }}";
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user