feat: daily focus search, show all tasks, and list item support
- Add list_item_id column to daily_focus (task_id now nullable, CHECK constraint ensures exactly one) - Remove LIMIT 50 + [:15] slice — show up to 200 items with "show more" at 25 - Add text search (ILIKE) for filtering available items - Add tab strip to switch between Tasks and List Items sources - Toggle syncs list_item completed status alongside task status - Graceful [Deleted] fallback for removed source items Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,11 +26,25 @@
|
||||
<label for="f-{{ item.id }}"></label>
|
||||
</div>
|
||||
</form>
|
||||
<span class="priority-dot priority-{{ item.priority }}"></span>
|
||||
<a href="/tasks/{{ item.task_id }}" class="focus-title">{{ item.title }}</a>
|
||||
{% if item.project_name %}<span class="row-tag">{{ item.project_name }}</span>{% endif %}
|
||||
{% if item.estimated_minutes %}<span class="focus-meta">~{{ item.estimated_minutes }}min</span>{% endif %}
|
||||
{% if item.due_date %}<span class="focus-meta">{{ item.due_date }}</span>{% endif %}
|
||||
{% if item.task_id %}
|
||||
<span class="priority-dot priority-{{ item.priority }}"></span>
|
||||
{% if item.title %}
|
||||
<a href="/tasks/{{ item.task_id }}" class="focus-title">{{ item.title }}</a>
|
||||
{% else %}
|
||||
<span class="focus-title" style="color:var(--muted)">[Deleted]</span>
|
||||
{% endif %}
|
||||
{% if item.project_name %}<span class="row-tag">{{ item.project_name }}</span>{% endif %}
|
||||
{% if item.estimated_minutes %}<span class="focus-meta">~{{ item.estimated_minutes }}min</span>{% endif %}
|
||||
{% if item.due_date %}<span class="focus-meta">{{ item.due_date }}</span>{% endif %}
|
||||
{% elif item.list_item_id %}
|
||||
<span style="color:var(--muted);font-size:0.85rem;margin-right:4px">☰</span>
|
||||
{% if item.list_item_content %}
|
||||
<a href="/lists/{{ item.list_item_list_id }}" class="focus-title">{{ item.list_item_content }}</a>
|
||||
{% else %}
|
||||
<span class="focus-title" style="color:var(--muted)">[Deleted]</span>
|
||||
{% endif %}
|
||||
{% if item.list_name %}<span class="row-tag" style="background:var(--purple);color:#fff">{{ item.list_name }}</span>{% endif %}
|
||||
{% endif %}
|
||||
<form action="/focus/{{ item.id }}/remove" method="post" style="display:inline">
|
||||
<button class="btn btn-ghost btn-xs" style="color:var(--red)" title="Remove from focus">×</button>
|
||||
</form>
|
||||
@@ -41,47 +55,94 @@
|
||||
<div class="empty-state mb-4"><div class="empty-state-text">No focus items for this day</div></div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Add task to focus -->
|
||||
<!-- Add to Focus -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header"><h2 class="card-title">Add to Focus</h2></div>
|
||||
|
||||
<!-- Tab strip -->
|
||||
<div class="tab-strip" style="padding: 0 12px;">
|
||||
<a href="/focus?focus_date={{ focus_date }}&source_type=tasks{% if current_search %}&search={{ current_search }}{% endif %}{% if current_domain_id %}&domain_id={{ current_domain_id }}{% endif %}{% if current_project_id %}&project_id={{ current_project_id }}{% endif %}"
|
||||
class="tab-item {{ 'active' if current_source_type == 'tasks' }}">Tasks</a>
|
||||
<a href="/focus?focus_date={{ focus_date }}&source_type=list_items{% if current_search %}&search={{ current_search }}{% endif %}{% if current_domain_id %}&domain_id={{ current_domain_id }}{% endif %}{% if current_project_id %}&project_id={{ current_project_id }}{% endif %}"
|
||||
class="tab-item {{ 'active' if current_source_type == 'list_items' }}">List Items</a>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<form class="filters-bar" method="get" action="/focus" id="focus-filters" style="padding: 8px 12px; border-bottom: 1px solid var(--border);">
|
||||
<input type="hidden" name="focus_date" value="{{ focus_date }}">
|
||||
<input type="hidden" name="source_type" value="{{ current_source_type }}">
|
||||
<input type="text" name="search" value="{{ current_search }}" placeholder="Search..." class="filter-select" style="min-width:150px">
|
||||
<select name="domain_id" class="filter-select" id="focus-domain" onchange="this.form.submit()">
|
||||
<option value="">All Domains</option>
|
||||
{% for d in domains %}
|
||||
<option value="{{ d.id }}" {{ 'selected' if current_domain_id == d.id|string }}>{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if current_source_type == 'tasks' %}
|
||||
<select name="area_id" class="filter-select" onchange="this.form.submit()">
|
||||
<option value="">All Areas</option>
|
||||
{% for a in areas %}
|
||||
<option value="{{ a.id }}" {{ 'selected' if current_area_id == a.id|string }}>{{ a.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
<select name="project_id" class="filter-select" id="focus-project" onchange="this.form.submit()">
|
||||
<option value="">All Projects</option>
|
||||
{% for p in projects %}
|
||||
<option value="{{ p.id }}" {{ 'selected' if current_project_id == p.id|string }}>{{ p.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="btn btn-ghost btn-xs">Search</button>
|
||||
{% if current_search or current_domain_id or current_area_id or current_project_id %}
|
||||
<a href="/focus?focus_date={{ focus_date }}&source_type={{ current_source_type }}" class="btn btn-ghost btn-xs" style="color:var(--red)">Clear</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% for t in available_tasks[:15] %}
|
||||
<div class="list-row">
|
||||
<span class="priority-dot priority-{{ t.priority }}"></span>
|
||||
<span class="row-title">{{ t.title }}</span>
|
||||
{% if t.project_name %}<span class="row-tag">{{ t.project_name }}</span>{% endif %}
|
||||
{% if t.due_date %}<span class="row-meta">{{ t.due_date }}</span>{% endif %}
|
||||
<form action="/focus/add" method="post" style="display:inline">
|
||||
<input type="hidden" name="task_id" value="{{ t.id }}">
|
||||
<input type="hidden" name="focus_date" value="{{ focus_date }}">
|
||||
<button class="btn btn-ghost btn-xs" style="color:var(--green)">+ Focus</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="padding: 16px; color: var(--muted); font-size: 0.85rem;">No available tasks matching filters</div>
|
||||
{% endfor %}
|
||||
<!-- Available tasks -->
|
||||
{% if current_source_type == 'tasks' %}
|
||||
{% for t in available_tasks %}
|
||||
<div class="list-row{% if loop.index > 25 %} focus-hidden-item hidden{% endif %}">
|
||||
<span class="priority-dot priority-{{ t.priority }}"></span>
|
||||
<span class="row-title">{{ t.title }}</span>
|
||||
{% if t.project_name %}<span class="row-tag">{{ t.project_name }}</span>{% endif %}
|
||||
{% if t.due_date %}<span class="row-meta">{{ t.due_date }}</span>{% endif %}
|
||||
<form action="/focus/add" method="post" style="display:inline">
|
||||
<input type="hidden" name="task_id" value="{{ t.id }}">
|
||||
<input type="hidden" name="focus_date" value="{{ focus_date }}">
|
||||
<button class="btn btn-ghost btn-xs" style="color:var(--green)">+ Focus</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="padding: 16px; color: var(--muted); font-size: 0.85rem;">No available tasks matching filters</div>
|
||||
{% endfor %}
|
||||
{% if available_tasks|length > 25 %}
|
||||
<div style="padding: 8px 12px; text-align:center;" id="focus-show-more-wrap">
|
||||
<button class="btn btn-ghost btn-sm" id="focus-show-more">Show all {{ available_tasks|length }} tasks</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Available list items -->
|
||||
{% elif current_source_type == 'list_items' %}
|
||||
{% for li in available_list_items %}
|
||||
<div class="list-row{% if loop.index > 25 %} focus-hidden-item hidden{% endif %}">
|
||||
<span style="color:var(--muted);font-size:0.85rem;margin-right:4px">☰</span>
|
||||
<span class="row-title">{{ li.content }}</span>
|
||||
{% if li.list_name %}<span class="row-tag" style="background:var(--purple);color:#fff">{{ li.list_name }}</span>{% endif %}
|
||||
<form action="/focus/add" method="post" style="display:inline">
|
||||
<input type="hidden" name="list_item_id" value="{{ li.id }}">
|
||||
<input type="hidden" name="focus_date" value="{{ focus_date }}">
|
||||
<button class="btn btn-ghost btn-xs" style="color:var(--green)">+ Focus</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="padding: 16px; color: var(--muted); font-size: 0.85rem;">No available list items matching filters</div>
|
||||
{% endfor %}
|
||||
{% if available_list_items|length > 25 %}
|
||||
<div style="padding: 8px 12px; text-align:center;" id="focus-show-more-wrap">
|
||||
<button class="btn btn-ghost btn-sm" id="focus-show-more">Show all {{ available_list_items|length }} items</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -91,24 +152,36 @@
|
||||
var currentProjectId = '{{ current_project_id }}';
|
||||
var form = document.getElementById('focus-filters');
|
||||
|
||||
domainSel.addEventListener('change', function() {
|
||||
var did = domainSel.value;
|
||||
if (!did) { form.submit(); return; }
|
||||
fetch('/projects/api/by-domain?domain_id=' + encodeURIComponent(did))
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(projects) {
|
||||
projectSel.innerHTML = '<option value="">All Projects</option>';
|
||||
projects.forEach(function(p) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = p.name;
|
||||
if (p.id === currentProjectId) opt.selected = true;
|
||||
projectSel.appendChild(opt);
|
||||
});
|
||||
form.submit();
|
||||
})
|
||||
.catch(function() { form.submit(); });
|
||||
});
|
||||
if (domainSel) {
|
||||
domainSel.addEventListener('change', function() {
|
||||
var did = domainSel.value;
|
||||
if (!did || !projectSel) { form.submit(); return; }
|
||||
fetch('/projects/api/by-domain?domain_id=' + encodeURIComponent(did))
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(projects) {
|
||||
projectSel.innerHTML = '<option value="">All Projects</option>';
|
||||
projects.forEach(function(p) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = p.name;
|
||||
if (p.id === currentProjectId) opt.selected = true;
|
||||
projectSel.appendChild(opt);
|
||||
});
|
||||
form.submit();
|
||||
})
|
||||
.catch(function() { form.submit(); });
|
||||
});
|
||||
}
|
||||
|
||||
// Show more button
|
||||
var showMoreBtn = document.getElementById('focus-show-more');
|
||||
if (showMoreBtn) {
|
||||
showMoreBtn.addEventListener('click', function() {
|
||||
var hidden = document.querySelectorAll('.focus-hidden-item.hidden');
|
||||
hidden.forEach(function(el) { el.classList.remove('hidden'); });
|
||||
document.getElementById('focus-show-more-wrap').style.display = 'none';
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user