Files
lifeos-dev/templates/focus_detail.html
Michael 6abef336c4 feat: focus item detail page with inline note + checklist
Each standalone focus item now auto-creates a linked note and checklist.
Clicking a focus item opens a detail page with side-by-side note editor
(left) and checklist (right) with drag-to-reorder. Save & Return writes
the note and goes back to the focus list. Added focus_id FK to notes and
lists tables, made domain optional when creating from focus context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 20:02:27 +00:00

174 lines
8.9 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="breadcrumb">
<a href="/focus">Focus</a>
<span class="sep">/</span>
<span>{{ item.title }}</span>
</div>
<div class="detail-header">
<div class="flex items-center justify-between">
<h1 class="detail-title" style="{{ 'text-decoration:line-through;opacity:0.6;' if item.completed }}">{{ item.title }}</h1>
<div class="flex gap-2">
<a href="/focus/{{ item.id }}/edit" class="btn btn-secondary btn-sm">Edit</a>
<form action="/focus/{{ item.id }}/toggle" method="post" style="display:inline">
<button class="btn {{ 'btn-secondary' if item.completed else 'btn-primary' }} btn-sm">
{{ 'Reopen' if item.completed else 'Complete' }}
</button>
</form>
<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>
</div>
<div class="detail-meta mt-2">
{% if item.completed %}<span class="status-badge status-done">completed</span>{% endif %}
{% if domain %}<span class="row-domain-tag" style="background: {{ domain.color or '#4F6EF7' }}22; color: {{ domain.color or '#4F6EF7' }}">{{ domain.name }}</span>{% endif %}
{% if project %}<span class="row-tag">{{ project.name }}</span>{% endif %}
</div>
</div>
<!-- Convert row -->
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:end;margin-bottom:16px;padding:8px 0;">
<span style="font-size:0.78rem;color:var(--muted);font-weight:600;">Convert to:</span>
<form action="/focus/{{ item.id }}/convert-to-task" method="post" data-confirm="Convert to task?" style="display:inline">
<button type="submit" class="btn btn-ghost btn-xs">Task</button>
</form>
<form action="/focus/{{ item.id }}/convert-to-note" method="post" data-confirm="Convert to note?" style="display:inline">
<button type="submit" class="btn btn-ghost btn-xs">Note</button>
</form>
<form action="/focus/{{ item.id }}/convert-to-link" method="post" data-confirm="Convert to link?" style="display:inline">
<button type="submit" class="btn btn-ghost btn-xs">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:4px;align-items:end;">
<select name="list_id" class="form-select" style="min-width:140px;height:28px;font-size:12px;" required>
<option value="">Select list...</option>
{% for l in all_lists %}
<option value="{{ l.id }}">{{ l.name }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-ghost btn-xs">List Item</button>
</form>
</div>
<!-- Side-by-side: Note (left) + List (right) -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;">
<!-- Note panel -->
<div class="card" style="padding:0;">
<div style="padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;">
<span style="font-weight:600;font-size:0.82rem;color:var(--text);">Notes</span>
</div>
<form action="/focus/{{ item.id }}/save-note" method="post">
<input type="hidden" name="note_id" value="{{ note.id }}">
<textarea name="body" style="width:100%;min-height:320px;padding:10px 12px;border:none;background:transparent;color:var(--text);font-family:var(--font-mono);font-size:0.85rem;resize:vertical;outline:none;box-sizing:border-box;" placeholder="Start typing notes...">{{ note.body if note.body else '' }}</textarea>
<div style="padding:8px 12px;border-top:1px solid var(--border);display:flex;gap:8px;">
<button type="submit" class="btn btn-primary btn-sm">Save &amp; Return</button>
<a href="/focus" class="btn btn-ghost btn-sm">Back</a>
</div>
</form>
</div>
<!-- List panel -->
<div class="card" style="padding:0;">
<div style="padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;">
<span style="font-weight:600;font-size:0.82rem;color:var(--text);">Checklist</span>
<span style="font-size:0.72rem;color:var(--muted);">{{ list_items|length }} item{{ 's' if list_items|length != 1 }}</span>
</div>
<!-- Add item -->
<form action="/focus/{{ item.id }}/list-item/add" method="post" style="display:flex;gap:6px;padding:8px 12px;border-bottom:1px solid var(--border);">
<input type="hidden" name="list_id" value="{{ list_id }}">
<input type="text" name="content" class="form-input" placeholder="Add item..." required style="flex:1;padding:5px 8px;font-size:0.82rem;">
<button type="submit" class="btn btn-primary btn-sm">+</button>
</form>
<!-- Items -->
<div id="focus-checklist" style="max-height:340px;overflow-y:auto;">
{% for li in list_items %}
<div class="list-row focus-cl-row" draggable="true" data-id="{{ li.id }}" style="padding:4px 8px;min-height:0;border-bottom:1px solid var(--border);{{ 'opacity:0.5;' if li.completed }}cursor:grab;">
<span class="reorder-grip" style="margin-right:4px;color:var(--muted);font-size:0.7rem;cursor:grab;">&#x2630;</span>
<form action="/focus/{{ item.id }}/list-item/{{ li.id }}/toggle" method="post" style="display:inline">
<div class="row-check" style="margin-right:6px;">
<input type="checkbox" id="li-{{ li.id }}" {{ 'checked' if li.completed }} onchange="this.form.submit()">
<label for="li-{{ li.id }}"></label>
</div>
</form>
<span style="flex:1;font-size:0.82rem;{{ 'text-decoration:line-through;' if li.completed }}">{{ li.content }}</span>
<form action="/focus/{{ item.id }}/list-item/{{ li.id }}/delete" method="post" style="display:inline">
<button class="btn btn-ghost btn-xs" style="color:var(--red);padding:0 4px;" title="Remove">&times;</button>
</form>
</div>
{% else %}
<div style="padding:16px 12px;color:var(--muted);font-size:0.82rem;text-align:center;">No items yet</div>
{% endfor %}
</div>
<form id="cl-reorder-form" action="/focus/{{ item.id }}/list-item/reorder-all" method="post" style="display:none;">
<input type="hidden" name="item_ids" id="cl-reorder-ids">
</form>
</div>
</div>
<script>
(function() {
var container = document.getElementById('focus-checklist');
if (!container) return;
var dragRow = null;
container.querySelectorAll('.focus-cl-row').forEach(function(row) {
row.addEventListener('dragstart', function(e) {
dragRow = row;
row.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/plain', row.dataset.id);
});
row.addEventListener('dragend', function() {
row.classList.remove('dragging');
container.querySelectorAll('.focus-cl-row.drag-over').forEach(function(el) {
el.classList.remove('drag-over');
});
if (dragRow) {
var allIds = [];
container.querySelectorAll('.focus-cl-row').forEach(function(r) {
allIds.push(r.dataset.id);
});
document.getElementById('cl-reorder-ids').value = allIds.join(',');
document.getElementById('cl-reorder-form').submit();
}
dragRow = null;
});
row.addEventListener('dragover', function(e) {
e.preventDefault();
if (!dragRow || row === dragRow) return;
e.dataTransfer.dropEffect = 'move';
container.querySelectorAll('.focus-cl-row.drag-over').forEach(function(el) {
el.classList.remove('drag-over');
});
row.classList.add('drag-over');
});
row.addEventListener('dragleave', function() {
row.classList.remove('drag-over');
});
row.addEventListener('drop', function(e) {
e.preventDefault();
if (!dragRow || row === dragRow) return;
row.classList.remove('drag-over');
var rows = Array.from(container.querySelectorAll('.focus-cl-row'));
var dragIdx = rows.indexOf(dragRow);
var targetIdx = rows.indexOf(row);
if (dragIdx < targetIdx) {
container.insertBefore(dragRow, row.nextSibling);
} else {
container.insertBefore(dragRow, row);
}
});
});
})();
</script>
{% endblock %}