feat: standalone focus items with edit, convert, project tab, domain ordering

- Add standalone text line items to focus (quick-add with optional domain)
- Edit page for standalone items (title, domain, project)
- Convert standalone items to task, note, link, or list item
- Focus tab on project detail page showing assigned focus items
- Sort domain groups: General first, then by domain sort_order
- Add domain_id and title to nullable_fields in BaseRepository

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 02:14:31 +00:00
parent 6aa36c570e
commit a61248b67d
6 changed files with 319 additions and 11 deletions

View File

@@ -7,6 +7,18 @@
</div>
</div>
<!-- Quick add -->
<form action="/focus/quick-add" method="post" class="flex items-center gap-2 mb-3">
<input type="text" name="title" class="form-input" placeholder="Add item to focus..." required style="flex:1;padding:6px 10px;font-size:0.85rem;">
<select name="quick_domain_id" class="filter-select" style="min-width:120px;padding:6px 8px;font-size:0.82rem;">
<option value="">No Domain</option>
{% for d in domains %}
<option value="{{ d.id }}">{{ d.name }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary btn-sm">+ Add</button>
</form>
<!-- Focus items grouped by domain -->
{% if items %}
<div class="card">
@@ -39,10 +51,10 @@
</form>
</td>
<td style="padding:1px 3px;vertical-align:middle;color:var(--muted);font-size:0.78rem;white-space:nowrap;">{{ item.due_date or '' }}</td>
<td style="padding:1px 1px;vertical-align:middle;">{% if item.task_id %}<span class="priority-dot priority-{{ item.priority }}"></span>{% elif item.list_item_id %}<span style="color:var(--muted);font-size:0.85rem;">&#9776;</span>{% endif %}</td>
<td style="padding:1px 3px;vertical-align:middle;{{ 'text-decoration:line-through;' if item.completed }}">{% if item.task_id %}{% if item.title %}<a href="/tasks/{{ item.task_id }}" class="focus-title">{{ item.title }}</a>{% else %}<span style="color:var(--muted)">[Deleted]</span>{% endif %}{% elif item.list_item_id %}{% if item.list_item_content %}<a href="/lists/{{ item.list_item_list_id }}" class="focus-title">{{ item.list_item_content }}</a>{% else %}<span style="color:var(--muted)">[Deleted]</span>{% endif %}{% endif %}</td>
<td style="padding:1px 1px;vertical-align:middle;">{% if item.task_id %}<span class="priority-dot priority-{{ item.priority }}"></span>{% elif item.list_item_id %}<span style="color:var(--muted);font-size:0.85rem;">&#9776;</span>{% else %}<span style="color:var(--muted);font-size:0.85rem;">&#9679;</span>{% endif %}</td>
<td style="padding:1px 3px;vertical-align:middle;{{ 'text-decoration:line-through;' if item.completed }}">{% if item.task_id %}{% if item.task_title %}<a href="/tasks/{{ item.task_id }}" class="focus-title">{{ item.task_title }}</a>{% else %}<span style="color:var(--muted)">[Deleted]</span>{% endif %}{% elif item.list_item_id %}{% if item.list_item_content %}<a href="/lists/{{ item.list_item_list_id }}" class="focus-title">{{ item.list_item_content }}</a>{% else %}<span style="color:var(--muted)">[Deleted]</span>{% endif %}{% else %}<a href="/focus/{{ item.id }}/edit" class="focus-title">{{ item.title }}</a>{% endif %}</td>
<td style="padding:1px 3px;vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{% if item.area_name %}<span class="row-tag">{{ item.area_name }}</span>{% endif %}</td>
<td style="padding:1px 3px;vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{% if item.task_id and item.project_name %}<span class="row-tag" style="background:var(--accent-soft);color:var(--accent)">{{ item.project_name }}</span>{% elif item.list_item_id and item.list_name %}<span class="row-tag" style="background:var(--purple);color:#fff">{{ item.list_name }}</span>{% endif %}</td>
<td style="padding:1px 3px;vertical-align:middle;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{% if item.project_name %}<span class="row-tag" style="background:var(--accent-soft);color:var(--accent)">{{ item.project_name }}</span>{% elif item.list_item_id and item.list_name %}<span class="row-tag" style="background:var(--purple);color:#fff">{{ item.list_name }}</span>{% endif %}</td>
<td style="padding:1px 3px;vertical-align:middle;text-align:right;color:var(--muted);font-size:0.78rem;">{{ '~%smin'|format(item.estimated_minutes) if item.estimated_minutes else '' }}</td>
<td style="padding:1px 1px;vertical-align:middle;">
<form action="/focus/{{ item.id }}/remove" method="post" style="display:inline">

85
templates/focus_edit.html Normal file
View File

@@ -0,0 +1,85 @@
{% extends "base.html" %}
{% block content %}
<div class="breadcrumb">
<a href="/focus">Focus</a>
<span class="sep">/</span>
<span>Edit Item</span>
</div>
<div class="page-header">
<h1 class="page-title">Edit Focus Item</h1>
</div>
<div class="card">
<form method="post" action="/focus/{{ item.id }}/edit">
<div class="form-grid">
<div class="form-group full-width">
<label class="form-label">Title *</label>
<input type="text" name="title" class="form-input" required
value="{{ item.title or '' }}">
</div>
<div class="form-group">
<label class="form-label">Domain</label>
<select name="domain_id" class="form-select">
<option value="">-- No Domain --</option>
{% for d in domains %}
<option value="{{ d.id }}"
{{ 'selected' if item.domain_id and item.domain_id|string == d.id|string }}>
{{ d.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label class="form-label">Project</label>
<select name="project_id" class="form-select">
<option value="">-- No Project --</option>
{% for p in projects %}
<option value="{{ p.id }}"
{{ 'selected' if item.project_id and item.project_id|string == p.id|string }}>
{{ p.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="/focus" class="btn btn-secondary">Cancel</a>
</div>
</div>
</form>
</div>
<div class="card" style="margin-top:16px;">
<div style="padding:16px;">
<h3 style="margin:0 0 12px;font-size:14px;color:var(--muted);">Convert to...</h3>
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:end;">
<form action="/focus/{{ item.id }}/convert-to-task" method="post" data-confirm="Convert to task? Opens the task editor." style="display:inline">
<button type="submit" class="btn btn-secondary btn-sm">Task</button>
</form>
<form action="/focus/{{ item.id }}/convert-to-note" method="post" data-confirm="Convert to note? Opens the note editor." style="display:inline">
<button type="submit" class="btn btn-secondary btn-sm">Note</button>
</form>
<form action="/focus/{{ item.id }}/convert-to-link" method="post" data-confirm="Convert to link? Opens the link editor." style="display:inline">
<button type="submit" class="btn btn-secondary btn-sm">Link</button>
</form>
<form action="/focus/{{ item.id }}/convert-to-list-item" method="post" data-confirm="Add to selected list?" style="display:inline-flex;gap:6px;align-items:end;">
<select name="list_id" class="form-select" style="min-width:160px;height:32px;font-size:13px;" required>
<option value="">Select list...</option>
{% for l in lists %}
<option value="{{ l.id }}">{{ l.name }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-secondary btn-sm">List Item</button>
</form>
</div>
</div>
</div>
<div style="margin-top:12px;display:flex;justify-content:flex-end;">
<form action="/focus/{{ item.id }}/remove" method="post" data-confirm="Delete this focus item?" style="display:inline">
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
</div>
{% endblock %}

View File

@@ -40,6 +40,7 @@
<a href="/projects/{{ item.id }}?tab=lists" class="tab-item {{ 'active' if tab == 'lists' }}">Lists{% if counts.lists %} ({{ counts.lists }}){% endif %}</a>
<a href="/projects/{{ item.id }}?tab=decisions" class="tab-item {{ 'active' if tab == 'decisions' }}">Decisions{% if counts.decisions %} ({{ counts.decisions }}){% endif %}</a>
<a href="/projects/{{ item.id }}?tab=meetings" class="tab-item {{ 'active' if tab == 'meetings' }}">Meetings{% if counts.meetings %} ({{ counts.meetings }}){% endif %}</a>
<a href="/projects/{{ item.id }}?tab=focus" class="tab-item {{ 'active' if tab == 'focus' }}">Focus{% if counts.focus %} ({{ counts.focus }}){% endif %}</a>
<a href="/projects/{{ item.id }}?tab=processes" class="tab-item {{ 'active' if tab == 'processes' }}">Processes</a>
<a href="/projects/{{ item.id }}?tab=contacts" class="tab-item {{ 'active' if tab == 'contacts' }}">Contacts{% if counts.contacts %} ({{ counts.contacts }}){% endif %}</a>
</div>
@@ -162,6 +163,20 @@
<div class="empty-state"><div class="empty-state-text">No meetings linked to this project</div></div>
{% endfor %}
{% elif tab == 'focus' %}
{% for f in tab_data %}
<div class="list-row">
<span class="row-title"><a href="/focus/{{ f.id }}/edit">{{ f.title }}</a></span>
{% if f.domain_name %}<span class="row-tag" style="{% if f.domain_color %}border-color:{{ f.domain_color }};color:{{ f.domain_color }}{% endif %}">{{ f.domain_name }}</span>{% endif %}
<span class="row-meta">{{ f.created_at.strftime('%Y-%m-%d') if f.created_at else '' }}</span>
<div class="row-actions">
<a href="/focus/{{ f.id }}/edit" class="btn btn-ghost btn-xs">Edit</a>
</div>
</div>
{% else %}
<div class="empty-state"><div class="empty-state-text">No focus items assigned to this project</div></div>
{% endfor %}
{% elif tab == 'processes' %}
<div class="empty-state"><div class="empty-state-text">Process management coming soon</div></div>