feat: ungroup focus items and add domain column
Remove domain-based grouping on focus page. Show all items in a flat list with a domain tag column per row. Simplify drag-and-drop to work across all items without group constraints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,29 +19,16 @@
|
|||||||
<button type="submit" class="btn btn-primary btn-sm">+ Add</button>
|
<button type="submit" class="btn btn-primary btn-sm">+ Add</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Focus items grouped by domain -->
|
<!-- Focus items flat list -->
|
||||||
{% if items %}
|
{% if items %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<table id="focus-table" style="width:100%;border-collapse:collapse;font-size:0.8rem;">
|
<table id="focus-table" style="width:100%;border-collapse:collapse;font-size:0.8rem;">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width:18px"><col style="width:24px"><col style="width:20px"><col style="width:18px"><col style="width:74px"><col style="width:10px">
|
<col style="width:18px"><col style="width:24px"><col style="width:20px"><col style="width:18px"><col style="width:74px"><col style="width:10px">
|
||||||
<col><col style="width:110px"><col style="width:120px"><col style="width:50px"><col style="width:24px">
|
<col><col style="width:100px"><col style="width:110px"><col style="width:120px"><col style="width:50px"><col style="width:24px">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
{% for domain in hierarchy %}
|
<tbody class="focus-drag-group">
|
||||||
<tbody>
|
{% for item in items %}
|
||||||
<tr class="focus-domain-header" data-domain-key="{{ domain.key }}" style="cursor:pointer;">
|
|
||||||
<td colspan="11" style="padding:2px 8px;border-bottom:1px solid var(--border);background:var(--surface2);">
|
|
||||||
<span style="display:inline-flex;align-items:center;gap:6px;">
|
|
||||||
<svg class="focus-domain-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" style="transition:transform 0.15s;flex-shrink:0;color:var(--muted);"><polyline points="4 2 8 6 4 10"/></svg>
|
|
||||||
<span style="width:8px;height:8px;border-radius:50%;background:{{ domain.color or 'var(--accent)' }};flex-shrink:0;"></span>
|
|
||||||
<span style="font-weight:700;font-size:0.8rem;letter-spacing:0.03em;text-transform:uppercase;color:var(--text)">{{ domain.label }}</span>
|
|
||||||
<span style="font-size:0.72rem;color:var(--muted);font-weight:400;">{{ domain.rows|length }}</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tbody class="focus-drag-group" data-domain-key="{{ domain.key }}">
|
|
||||||
{% for item in domain.rows %}
|
|
||||||
<tr class="focus-drag-row" draggable="true" data-id="{{ item.id }}" style="border-bottom:1px solid var(--border);{{ 'opacity:0.6;' if item.completed }}">
|
<tr class="focus-drag-row" draggable="true" data-id="{{ item.id }}" style="border-bottom:1px solid var(--border);{{ 'opacity:0.6;' if item.completed }}">
|
||||||
<td class="focus-row-num" style="padding:1px 2px;vertical-align:middle;text-align:center;color:var(--muted);font-size:0.72rem;font-weight:600;">{{ loop.index }}</td>
|
<td class="focus-row-num" style="padding:1px 2px;vertical-align:middle;text-align:center;color:var(--muted);font-size:0.72rem;font-weight:600;">{{ loop.index }}</td>
|
||||||
<td style="padding:1px 1px;vertical-align:middle;">{% with reorder_url="/focus/reorder", item_id=item.id %}{% include 'partials/reorder_arrows.html' %}{% endwith %}</td>
|
<td style="padding:1px 1px;vertical-align:middle;">{% with reorder_url="/focus/reorder", item_id=item.id %}{% include 'partials/reorder_arrows.html' %}{% endwith %}</td>
|
||||||
@@ -58,6 +45,7 @@
|
|||||||
<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 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;">☰</span>{% else %}<span style="color:var(--muted);font-size:0.85rem;">●</span>{% 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;">☰</span>{% else %}<span style="color:var(--muted);font-size:0.85rem;">●</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 }}" class="focus-title">{{ item.title }}</a>{% 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 }}" 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.domain_name %}<span class="row-domain-tag" style="background:{{ (item.domain_color or 'var(--accent)') }}22;color:{{ item.domain_color or 'var(--accent)' }}">{{ item.domain_name }}</span>{% 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.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.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 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>
|
||||||
@@ -67,9 +55,8 @@
|
|||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<form id="focus-reorder-form" action="/focus/reorder-all" method="post" style="display:none;">
|
<form id="focus-reorder-form" action="/focus/reorder-all" method="post" style="display:none;">
|
||||||
<input type="hidden" name="item_ids" id="focus-reorder-ids">
|
<input type="hidden" name="item_ids" id="focus-reorder-ids">
|
||||||
@@ -204,45 +191,22 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domain group collapse/expand
|
// Renumber rows
|
||||||
document.querySelectorAll('.focus-domain-header').forEach(function(header) {
|
|
||||||
var key = 'focus-domain-' + header.dataset.domainKey;
|
|
||||||
var tbody = document.querySelector('tbody.focus-drag-group[data-domain-key="' + header.dataset.domainKey + '"]');
|
|
||||||
var chevron = header.querySelector('.focus-domain-chevron');
|
|
||||||
// Restore saved state
|
|
||||||
if (localStorage.getItem(key) === 'true') {
|
|
||||||
tbody.style.display = 'none';
|
|
||||||
chevron.style.transform = 'rotate(0deg)';
|
|
||||||
} else {
|
|
||||||
chevron.style.transform = 'rotate(90deg)';
|
|
||||||
}
|
|
||||||
header.addEventListener('click', function() {
|
|
||||||
var hidden = tbody.style.display === 'none';
|
|
||||||
tbody.style.display = hidden ? '' : 'none';
|
|
||||||
chevron.style.transform = hidden ? 'rotate(90deg)' : 'rotate(0deg)';
|
|
||||||
localStorage.setItem(key, !hidden);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Renumber rows within each domain group
|
|
||||||
function renumberFocusRows() {
|
function renumberFocusRows() {
|
||||||
document.querySelectorAll('.focus-drag-group').forEach(function(tbody) {
|
var rows = document.querySelectorAll('.focus-drag-row');
|
||||||
var rows = tbody.querySelectorAll('.focus-drag-row');
|
rows.forEach(function(row, i) {
|
||||||
rows.forEach(function(row, i) {
|
var numCell = row.querySelector('.focus-row-num');
|
||||||
var numCell = row.querySelector('.focus-row-num');
|
if (numCell) numCell.textContent = i + 1;
|
||||||
if (numCell) numCell.textContent = i + 1;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag-and-drop reorder within domain groups
|
// Drag-and-drop reorder
|
||||||
var dragRow = null;
|
var dragRow = null;
|
||||||
var dragGroup = null;
|
var tbody = document.querySelector('.focus-drag-group');
|
||||||
|
|
||||||
document.querySelectorAll('.focus-drag-row').forEach(function(row) {
|
document.querySelectorAll('.focus-drag-row').forEach(function(row) {
|
||||||
row.addEventListener('dragstart', function(e) {
|
row.addEventListener('dragstart', function(e) {
|
||||||
dragRow = row;
|
dragRow = row;
|
||||||
dragGroup = row.closest('.focus-drag-group');
|
|
||||||
row.classList.add('dragging');
|
row.classList.add('dragging');
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
e.dataTransfer.setData('text/plain', row.dataset.id);
|
e.dataTransfer.setData('text/plain', row.dataset.id);
|
||||||
@@ -253,7 +217,6 @@
|
|||||||
document.querySelectorAll('.focus-drag-row.drag-over').forEach(function(el) {
|
document.querySelectorAll('.focus-drag-row.drag-over').forEach(function(el) {
|
||||||
el.classList.remove('drag-over');
|
el.classList.remove('drag-over');
|
||||||
});
|
});
|
||||||
// Collect all IDs across all groups in DOM order and submit
|
|
||||||
if (dragRow) {
|
if (dragRow) {
|
||||||
var allIds = [];
|
var allIds = [];
|
||||||
document.querySelectorAll('#focus-table .focus-drag-row').forEach(function(r) {
|
document.querySelectorAll('#focus-table .focus-drag-row').forEach(function(r) {
|
||||||
@@ -263,14 +226,11 @@
|
|||||||
document.getElementById('focus-reorder-form').submit();
|
document.getElementById('focus-reorder-form').submit();
|
||||||
}
|
}
|
||||||
dragRow = null;
|
dragRow = null;
|
||||||
dragGroup = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
row.addEventListener('dragover', function(e) {
|
row.addEventListener('dragover', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!dragRow || row === dragRow) return;
|
if (!dragRow || row === dragRow) return;
|
||||||
// Constrain to same domain group
|
|
||||||
if (row.closest('.focus-drag-group') !== dragGroup) return;
|
|
||||||
e.dataTransfer.dropEffect = 'move';
|
e.dataTransfer.dropEffect = 'move';
|
||||||
document.querySelectorAll('.focus-drag-row.drag-over').forEach(function(el) {
|
document.querySelectorAll('.focus-drag-row.drag-over').forEach(function(el) {
|
||||||
el.classList.remove('drag-over');
|
el.classList.remove('drag-over');
|
||||||
@@ -285,10 +245,7 @@
|
|||||||
row.addEventListener('drop', function(e) {
|
row.addEventListener('drop', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!dragRow || row === dragRow) return;
|
if (!dragRow || row === dragRow) return;
|
||||||
if (row.closest('.focus-drag-group') !== dragGroup) return;
|
|
||||||
row.classList.remove('drag-over');
|
row.classList.remove('drag-over');
|
||||||
// Insert dragged row before or after target based on position
|
|
||||||
var tbody = dragGroup;
|
|
||||||
var rows = Array.from(tbody.querySelectorAll('.focus-drag-row'));
|
var rows = Array.from(tbody.querySelectorAll('.focus-drag-row'));
|
||||||
var dragIdx = rows.indexOf(dragRow);
|
var dragIdx = rows.indexOf(dragRow);
|
||||||
var targetIdx = rows.indexOf(row);
|
var targetIdx = rows.indexOf(row);
|
||||||
|
|||||||
Reference in New Issue
Block a user