logbook search
This commit is contained in:
@@ -1,19 +1,29 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Logbook</h2>
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||||
|
<h2 style="margin: 0;">Logbook</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Bar -->
|
||||||
|
<div style="margin-bottom: 2rem;">
|
||||||
|
<input type="text" id="logSearchInput" placeholder="Search logbook (Activity, Subcategory, Notes)..." onkeyup="filterLog()"
|
||||||
|
style="background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23999%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2211%22%20cy%3D%2211%22%20r%3D%228%22%2F%3E%3Cline%20x1%3D%2221%22%20y1%3D%2221%22%20x2%3D%2216.65%22%20y2%3D%2216.65%22%2F%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: 10px center; background-size: 16px; padding-left: 36px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if not log %}
|
{% if not log %}
|
||||||
<p>No activities recorded yet.</p>
|
<p>No activities recorded yet.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<div id="logbook-container">
|
||||||
{% for entry in log %}
|
{% for entry in log %}
|
||||||
<!-- Wrapped whole card in a link to the detail view -->
|
<!-- Wrapped whole card in a link to the detail view -->
|
||||||
<a href="{{ url_for('log_entry_detail', entry_id=entry._id) }}" style="text-decoration: none; color: inherit; display: block;">
|
<a href="{{ url_for('log_entry_detail', entry_id=entry._id) }}" class="log-entry-item" style="text-decoration: none; color: inherit; display: block;">
|
||||||
<div class="card" style="border-left: 5px solid {{ entry.activity.color }}; transition: transform 0.1s;">
|
<div class="card" style="border-left: 5px solid {{ entry.activity.color }}; transition: transform 0.1s;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
<div>
|
<div>
|
||||||
<h3 style="margin: 0;">
|
<h3 style="margin: 0;" class="entry-title">
|
||||||
{{ entry.activity.name }}
|
{{ entry.activity.name }}
|
||||||
{% if entry.subcategory %}
|
{% if entry.subcategory %}
|
||||||
<span style="font-size: 0.8rem; background: #eee; padding: 2px 8px; border-radius: 10px; color: #555; vertical-align: middle;">
|
<span class="entry-subcat" style="font-size: 0.8rem; background: #eee; padding: 2px 8px; border-radius: 10px; color: #555; vertical-align: middle;">
|
||||||
{{ entry.subcategory }}
|
{{ entry.subcategory }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -22,7 +32,10 @@
|
|||||||
{{ entry.start_time.strftime('%Y-%m-%d %H:%M') }} - {{ entry.end_time.strftime('%H:%M') }}
|
{{ entry.start_time.strftime('%Y-%m-%d %H:%M') }} - {{ entry.end_time.strftime('%H:%M') }}
|
||||||
</small>
|
</small>
|
||||||
{% if entry.note %}
|
{% if entry.note %}
|
||||||
<p style="margin: 5px 0; font-style: italic; color: #555;">"{{ entry.note }}"</p>
|
<p class="entry-note" style="margin: 5px 0; font-style: italic; color: #555;">"{{ entry.note }}"</p>
|
||||||
|
{% else %}
|
||||||
|
<!-- Hidden span for consistent JS selection if note empty -->
|
||||||
|
<span class="entry-note" style="display:none;"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight: bold; font-size: 1.2rem;">
|
<div style="font-weight: bold; font-size: 1.2rem;">
|
||||||
@@ -43,8 +56,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function filterLog() {
|
||||||
|
const input = document.getElementById('logSearchInput');
|
||||||
|
const filter = input.value.toLowerCase();
|
||||||
|
const items = document.getElementsByClassName('log-entry-item');
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const title = items[i].querySelector('.entry-title').textContent || "";
|
||||||
|
const note = items[i].querySelector('.entry-note').textContent || "";
|
||||||
|
// Combine text content for searching
|
||||||
|
const textValue = (title + " " + note).toLowerCase();
|
||||||
|
|
||||||
|
if (textValue.indexOf(filter) > -1) {
|
||||||
|
items[i].style.display = "";
|
||||||
|
} else {
|
||||||
|
items[i].style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Small hover effect to indicate clickability */
|
/* Small hover effect to indicate clickability */
|
||||||
a .card:hover {
|
a .card:hover {
|
||||||
|
|||||||
@@ -69,26 +69,40 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Tasks in this Session</h3>
|
<h3>Tasks in this Session</h3>
|
||||||
{% if not tasks %}
|
{% if not tasks %}
|
||||||
<p style="color: #999;">No tasks were tracked for this session.</p>
|
<p style="color: var(--text-secondary);">No tasks were tracked for this session.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<ul style="list-style: none; padding: 0;">
|
<div class="checklist-container">
|
||||||
{% for task in tasks %}
|
{% for task in tasks %}
|
||||||
<li style="border-bottom: 1px solid #eee; padding: 15px 0; display: flex; align-items: center;">
|
<div class="checklist-item {% if task.status == 'completed' %}completed{% endif %}" style="padding: 12px 0;">
|
||||||
<span style="margin-right: 15px; font-size: 1.2rem;">
|
<!-- Visual Checkbox (Read Only) -->
|
||||||
{% if task.status == 'completed' %}Done{% else %}Open{% endif %}
|
<div style="
|
||||||
</span>
|
width: 18px; height: 18px;
|
||||||
<div style="flex-grow: 1;">
|
border: 2px solid {% if task.status == 'completed' %}var(--primary-color){% else %}var(--text-secondary){% endif %};
|
||||||
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="font-weight: bold; display: block; font-size: 1.1rem;">{{ task.name }}</a>
|
border-radius: 4px;
|
||||||
{% if task.completed_at %}
|
background: {% if task.status == 'completed' %}var(--primary-color){% else %}transparent{% endif %};
|
||||||
<small style="color: #27ae60;">Completed at {{ task.completed_at.strftime('%H:%M') }}</small>
|
margin-right: 12px; margin-top: 3px;
|
||||||
{% else %}
|
display: flex; align-items: center; justify-content: center;
|
||||||
<small style="color: #e67e22;">Not completed</small>
|
">
|
||||||
|
{% if task.status == 'completed' %}
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ url_for('task_detail', task_id=task._id) }}" class="btn" style="font-size: 0.8rem; padding: 5px 10px;">View Task ></a>
|
|
||||||
</li>
|
<div style="flex-grow: 1;">
|
||||||
|
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="text-decoration: none; color: inherit; font-weight: 500; display: block;">
|
||||||
|
{{ task.name }}
|
||||||
|
</a>
|
||||||
|
{% if task.completed_at %}
|
||||||
|
<small style="color: var(--text-secondary); font-size: 0.8rem;">Completed at {{ task.completed_at.strftime('%H:%M') }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ url_for('task_detail', task_id=task._id) }}" style="color: var(--text-secondary); text-decoration: none; font-size: 1.2rem; line-height: 1;">
|
||||||
|
›
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -8,15 +8,21 @@
|
|||||||
<button class="btn" onclick="openTaskModal()">+ New Task</button>
|
<button class="btn" onclick="openTaskModal()">+ New Task</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Bar -->
|
||||||
|
<div style="margin-bottom: 1.5rem;">
|
||||||
|
<input type="text" id="taskSearchInput" placeholder="Search tasks..." onkeyup="applyFilters()"
|
||||||
|
style="background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23999%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Ccircle%20cx%3D%2211%22%20cy%3D%2211%22%20r%3D%228%22%2F%3E%3Cline%20x1%3D%2221%22%20y1%3D%2221%22%20x2%3D%2216.65%22%20y2%3D%2216.65%22%2F%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: 10px center; background-size: 16px; padding-left: 36px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 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-open" onclick="setFilter('open')" 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')" data-filter="open" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-primary);">
|
||||||
Open
|
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')" data-filter="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);">
|
<div class="filter-tab" id="filter-completed" onclick="setFilter('completed')" data-filter="completed" style="cursor: pointer; font-weight: 600; font-size: 0.9rem; color: var(--text-secondary);">
|
||||||
Completed
|
Completed
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,38 +47,37 @@
|
|||||||
title="Add task to {{ act.name }}">+</button>
|
title="Add task to {{ act.name }}">+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if act_tasks %}
|
<!-- Always render the UL, just empty if no tasks initially -->
|
||||||
<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 %}
|
||||||
<!-- Inline Task Row -->
|
<!-- Inline Task Row -->
|
||||||
<li class="task-item-row"
|
<li class="task-item-row"
|
||||||
data-due-date="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}"
|
data-due-date="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}"
|
||||||
data-status="{{ task.status }}"
|
data-status="{{ task.status }}"
|
||||||
style="padding: 8px 0; border-bottom: 1px solid var(--border-dim); display: flex; justify-content: space-between; align-items: center;">
|
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;">
|
<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 %}">
|
<a href="{{ url_for('task_detail', task_id=task._id) }}" class="task-name-link" 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 }}
|
{{ task.name }}
|
||||||
</a>
|
</a>
|
||||||
{% if task.due_date %}
|
{% if task.due_date %}
|
||||||
<span style="font-size: 0.75rem; color: var(--text-secondary); background: var(--bg-input); padding: 2px 6px; border-radius: 4px;">
|
<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') }}
|
{{ task.due_date.strftime('%d. %b %H:%M') }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px;">
|
<div style="display: flex; gap: 10px;">
|
||||||
<form action="{{ url_for('toggle_timer', activity_id=task.activity_id) }}" method="POST" style="margin: 0;">
|
<form action="{{ url_for('toggle_timer', activity_id=task.activity_id) }}" method="POST" style="margin: 0;">
|
||||||
<input type="hidden" name="note" value="{{ task.name }}">
|
<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 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>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
@@ -204,7 +209,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Filtering Logic
|
// Filtering Logic
|
||||||
|
let currentFilterType = 'open';
|
||||||
|
|
||||||
function setFilter(type) {
|
function setFilter(type) {
|
||||||
|
currentFilterType = type;
|
||||||
|
|
||||||
document.querySelectorAll('.filter-tab').forEach(el => {
|
document.querySelectorAll('.filter-tab').forEach(el => {
|
||||||
el.style.color = 'var(--text-secondary)';
|
el.style.color = 'var(--text-secondary)';
|
||||||
el.classList.remove('active');
|
el.classList.remove('active');
|
||||||
@@ -212,28 +221,37 @@
|
|||||||
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');
|
document.getElementById('filter-' + type).classList.add('active');
|
||||||
|
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters() {
|
||||||
|
const searchVal = document.getElementById('taskSearchInput').value.toLowerCase();
|
||||||
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 => {
|
||||||
const status = item.getAttribute('data-status');
|
const status = item.getAttribute('data-status');
|
||||||
const dateStr = item.getAttribute('data-due-date');
|
const dateStr = item.getAttribute('data-due-date');
|
||||||
|
const name = item.querySelector('.task-name-link').innerText.toLowerCase();
|
||||||
|
|
||||||
let show = false;
|
let show = false;
|
||||||
|
|
||||||
if (type === 'open') {
|
// 1. Check Tabs
|
||||||
|
if (currentFilterType === 'open') {
|
||||||
if (status !== 'completed') show = true;
|
if (status !== 'completed') show = true;
|
||||||
} else if (type === 'today') {
|
} else if (currentFilterType === 'today') {
|
||||||
// Open tasks due today
|
|
||||||
if (status !== 'completed' && dateStr && dateStr.startsWith(today)) show = true;
|
if (status !== 'completed' && dateStr && dateStr.startsWith(today)) show = true;
|
||||||
} else if (type === 'completed') {
|
} else if (currentFilterType === 'completed') {
|
||||||
if (status === 'completed') show = true;
|
if (status === 'completed') show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Check Search
|
||||||
|
if (show && searchVal) {
|
||||||
|
if (!name.includes(searchVal)) show = false;
|
||||||
|
}
|
||||||
|
|
||||||
item.style.display = show ? 'flex' : 'none';
|
item.style.display = show ? 'flex' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Removed the group hiding logic block here to ensure Activity headers always stay visible
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize filter
|
// Initialize filter
|
||||||
|
|||||||
Reference in New Issue
Block a user