From 4c072beec0aed4375ea892afcb0d028fe38cff23 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 3 Mar 2026 15:51:14 +0000 Subject: [PATCH] feat: focus page table layout with domain grouping, area/project columns, compact padding Co-Authored-By: Claude Opus 4.6 --- routers/focus.py | 44 ++++------------------ templates/focus.html | 88 ++++++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 90 deletions(-) diff --git a/routers/focus.py b/routers/focus.py index d731ce7..9779b44 100644 --- a/routers/focus.py +++ b/routers/focus.py @@ -42,8 +42,8 @@ async def focus_view( COALESCE(d.name, ld.name) as domain_name, COALESCE(d.color, ld.color) as domain_color, COALESCE(d.id, ld.id) as effective_domain_id, - COALESCE(a.name, la.name) as area_name, - COALESCE(a.id, la.id) as effective_area_id, + COALESCE(a.name, pa.name, la.name) as area_name, + COALESCE(a.id, pa.id, la.id) as effective_area_id, li.content as list_item_content, li.list_id as list_item_list_id, li.completed as list_item_completed, l.name as list_name @@ -52,6 +52,7 @@ async def focus_view( LEFT JOIN projects p ON t.project_id = p.id LEFT JOIN domains d ON t.domain_id = d.id LEFT JOIN areas a ON t.area_id = a.id + LEFT JOIN areas pa ON p.area_id = pa.id LEFT JOIN list_items li ON df.list_item_id = li.id LEFT JOIN lists l ON li.list_id = l.id LEFT JOIN projects lp ON l.project_id = lp.id @@ -62,7 +63,7 @@ async def focus_view( """), {"target_date": target_date}) items = [dict(r._mapping) for r in result] - # Build Domain > Area > Project hierarchy + # Group items by domain only — area/project shown as inline columns from collections import OrderedDict domain_map = OrderedDict() for item in items: @@ -70,41 +71,10 @@ async def focus_view( dl = item.get("domain_name") or "General" dc = item.get("domain_color") or "" if dk not in domain_map: - domain_map[dk] = {"label": dl, "color": dc, "areas": OrderedDict()} + domain_map[dk] = {"label": dl, "color": dc, "rows": []} + domain_map[dk]["rows"].append(item) - ak = item.get("effective_area_id") or "__none__" - al = item.get("area_name") or "General" - area_map = domain_map[dk]["areas"] - if ak not in area_map: - area_map[ak] = {"label": al, "projects": OrderedDict()} - - pk = item.get("effective_project_id") or "__none__" - pl = item.get("project_name") or "General" - proj_map = area_map[ak]["projects"] - if pk not in proj_map: - proj_map[pk] = {"label": pl, "rows": []} - proj_map[pk]["rows"].append(item) - - # Convert to nested lists for Jinja — "General" first, then alpha - def _sorted_projects(proj_dict): - general = [] - named = [] - for pk, pv in proj_dict.items(): - entry = {"label": pv["label"], "rows": pv["rows"]} - if pk == "__none__": - general.append(entry) - else: - named.append(entry) - named.sort(key=lambda p: p["label"].lower()) - return general + named - - hierarchy = [] - for dk, dv in domain_map.items(): - domain_group = {"label": dv["label"], "color": dv["color"], "areas": []} - for ak, av in dv["areas"].items(): - area_group = {"label": av["label"], "projects": _sorted_projects(av["projects"])} - domain_group["areas"].append(area_group) - hierarchy.append(domain_group) + hierarchy = list(domain_map.values()) # --- Available tasks --- available_tasks = [] diff --git a/templates/focus.html b/templates/focus.html index 7f4c016..97a3edd 100644 --- a/templates/focus.html +++ b/templates/focus.html @@ -12,63 +12,45 @@ {{ focus_date }} - + {% if items %} +
+ ++ + + {% for domain in hierarchy %} -
- -
- - {{ domain.label }} -
- {% for area in domain.areas %} - -
- {{ area.label }} -
- {% for project in area.projects %} - -
- {{ project.label }} -
- {% for item in project.rows %} -
- {% with reorder_url="/focus/reorder", item_id=item.id, extra_fields={"focus_date": focus_date|string} %} - {% include 'partials/reorder_arrows.html' %} - {% endwith %} -
-
- - -
- - {% if item.task_id %} - {{ item.due_date if item.due_date else ' '|safe }} - - {% if item.title %} - {{ item.title }} - {% else %} - [Deleted] - {% endif %} - {% if item.estimated_minutes %}~{{ item.estimated_minutes }}min{% endif %} - {% elif item.list_item_id %} - - {% if item.list_item_content %} - {{ item.list_item_content }} - {% else %} - [Deleted] - {% endif %} - {% if item.list_name %}{{ item.list_name }}{% endif %} - {% endif %} -
- - -
+
+ {% for item in domain.rows %} + + + + + + + + + + + {% endfor %} - {% endfor %} - {% endfor %} - {% endfor %} +
+ + + {{ domain.label }} + +
{% with reorder_url="/focus/reorder", item_id=item.id, extra_fields={"focus_date": focus_date|string} %}{% include 'partials/reorder_arrows.html' %}{% endwith %} +
+
+
+
{{ item.due_date or '' }}{% if item.task_id %}{% elif item.list_item_id %}{% endif %}{% if item.task_id %}{% if item.title %}{{ item.title }}{% else %}[Deleted]{% endif %}{% elif item.list_item_id %}{% if item.list_item_content %}{{ item.list_item_content }}{% else %}[Deleted]{% endif %}{% endif %}{% if item.area_name %}{{ item.area_name }}{% endif %}{% if item.task_id and item.project_name %}{{ item.project_name }}{% elif item.list_item_id and item.list_name %}{{ item.list_name }}{% endif %}{{ '~%smin'|format(item.estimated_minutes) if item.estimated_minutes else '' }} +
+ +
+
+
{% else %}
No focus items for this day
{% endif %}