diff --git a/routers/focus.py b/routers/focus.py
index 9779b44..9420f64 100644
--- a/routers/focus.py
+++ b/routers/focus.py
@@ -222,6 +222,20 @@ async def reorder_focus(
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
+@router.post("/reorder-all")
+async def reorder_all_focus(
+ request: Request,
+ item_ids: str = Form(...),
+ focus_date: str = Form(...),
+ db: AsyncSession = Depends(get_db),
+):
+ repo = BaseRepository("daily_focus", db)
+ ids = [i.strip() for i in item_ids.split(",") if i.strip()]
+ if ids:
+ await repo.reorder(ids)
+ return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
+
+
@router.post("/{focus_id}/toggle")
async def toggle_focus(focus_id: str, request: Request, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("daily_focus", db)
diff --git a/static/style.css b/static/style.css
index 1395034..01fc77c 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1107,6 +1107,11 @@ a:hover { color: var(--accent-hover); }
transition: width 0.3s;
}
+/* ---- Focus Drag-and-Drop ---- */
+.focus-drag-row { cursor: grab; }
+.focus-drag-row.dragging { opacity: 0.4; background: var(--accent-soft); }
+.focus-drag-row.drag-over { box-shadow: 0 -2px 0 0 var(--accent) inset; }
+
/* ---- Reorder Grip Handle ---- */
.reorder-grip {
display: inline-flex;
diff --git a/templates/focus.html b/templates/focus.html
index 97a3edd..8728beb 100644
--- a/templates/focus.html
+++ b/templates/focus.html
@@ -15,20 +15,23 @@
{% if items %}
-
+
{% for domain in hierarchy %}
+
|
{{ domain.label }}
|
+
+
{% for item in domain.rows %}
-
+
| {% with reorder_url="/focus/reorder", item_id=item.id, extra_fields={"focus_date": focus_date|string} %}{% include 'partials/reorder_arrows.html' %}{% endwith %} |
|
{% endfor %}
+
{% endfor %}
+
{% else %}
No focus items for this day
@@ -182,6 +190,71 @@
document.getElementById('focus-show-more-wrap').style.display = 'none';
});
}
+
+ // Drag-and-drop reorder within domain groups
+ var dragRow = null;
+ var dragGroup = null;
+
+ document.querySelectorAll('.focus-drag-row').forEach(function(row) {
+ row.addEventListener('dragstart', function(e) {
+ dragRow = row;
+ dragGroup = row.closest('.focus-drag-group');
+ row.classList.add('dragging');
+ e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.setData('text/plain', row.dataset.id);
+ });
+
+ row.addEventListener('dragend', function() {
+ row.classList.remove('dragging');
+ document.querySelectorAll('.focus-drag-row.drag-over').forEach(function(el) {
+ el.classList.remove('drag-over');
+ });
+ // Collect all IDs across all groups in DOM order and submit
+ if (dragRow) {
+ var allIds = [];
+ document.querySelectorAll('#focus-table .focus-drag-row').forEach(function(r) {
+ allIds.push(r.dataset.id);
+ });
+ document.getElementById('focus-reorder-ids').value = allIds.join(',');
+ document.getElementById('focus-reorder-form').submit();
+ }
+ dragRow = null;
+ dragGroup = null;
+ });
+
+ row.addEventListener('dragover', function(e) {
+ e.preventDefault();
+ if (!dragRow || row === dragRow) return;
+ // Constrain to same domain group
+ if (row.closest('.focus-drag-group') !== dragGroup) return;
+ e.dataTransfer.dropEffect = 'move';
+ document.querySelectorAll('.focus-drag-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;
+ if (row.closest('.focus-drag-group') !== dragGroup) return;
+ 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 dragIdx = rows.indexOf(dragRow);
+ var targetIdx = rows.indexOf(row);
+ if (dragIdx < targetIdx) {
+ tbody.insertBefore(dragRow, row.nextSibling);
+ } else {
+ tbody.insertBefore(dragRow, row);
+ }
+ });
+ });
})();
{% endblock %}