refactor: enhance TimerPage layout and functionality; update theme and tokens for improved design consistency
- Refactored TimerPage component to improve layout and user experience. - Updated theme configuration to utilize design tokens for better maintainability. - Introduced new tokens for colors and typography to align with design principles. - Simplified state management and improved timer functionality. - Enhanced accessibility and responsiveness of UI elements.
This commit is contained in:
84 files changed
+4501
-2682
No files matched your search
@@ -1,9 +1,14 @@
|
||||
"""The task list: refreshing it from every source, and acting on it.
|
||||
"""The task list: refreshing it from every source, and reading it.
|
||||
|
||||
External tasks are mirrored into the `tasks` table on each refresh. The mirror
|
||||
buys three things a live call cannot: the UI stays instant, the daily report
|
||||
still works when a source is down, and "I finished this" is recorded locally
|
||||
even if the write-back to Jira fails.
|
||||
buys two things a live call cannot: the UI stays instant, and the daily report
|
||||
still works when a source is down.
|
||||
|
||||
Nothing here closes a task. Jira, Todoist and Google Tasks each own completion
|
||||
for their own items, and a second place to tick things off is a second place to
|
||||
be wrong — so the refresh *detects* what you closed in those tools instead:
|
||||
an item that comes back done, or that a healthy source stops returning at all,
|
||||
is recorded as closed today.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -19,7 +24,7 @@ from sqlalchemy.orm import Session
|
||||
from app.models import TaskRecord, utcnow
|
||||
from app.settings_schema import AppSettings
|
||||
from app.sources.base import SourceResult, TodoItem
|
||||
from app.sources.registry import SOURCE_LABELS, complete_remote, fetch_all
|
||||
from app.sources.registry import SOURCE_LABELS, fetch_all
|
||||
from app.timeutil import local_date
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -30,7 +35,12 @@ LOCAL_SOURCE = "local"
|
||||
# --------------------------------------------------------------------------
|
||||
# Refresh
|
||||
# --------------------------------------------------------------------------
|
||||
def _upsert(session: Session, item: TodoItem) -> TaskRecord:
|
||||
# Laundry slots vanish from the grid when they finish; that is the washing
|
||||
# machine's business, not a task you completed.
|
||||
_TRANSIENT_SOURCES = frozenset({"laundry"})
|
||||
|
||||
|
||||
def _upsert(session: Session, item: TodoItem, today: date) -> TaskRecord:
|
||||
record = session.scalar(
|
||||
select(TaskRecord).where(
|
||||
TaskRecord.source == item.source,
|
||||
@@ -54,18 +64,24 @@ def _upsert(session: Session, item: TodoItem) -> TaskRecord:
|
||||
record.last_seen_at = utcnow()
|
||||
record.stale = False
|
||||
if item.is_done and not record.is_done:
|
||||
# Closed in the source, not here: it counts as done but not as "mine".
|
||||
record.is_done = True
|
||||
record.completed_at = record.completed_at or utcnow()
|
||||
_mark_closed(record, today)
|
||||
return record
|
||||
|
||||
|
||||
def _mark_closed(record: TaskRecord, today: date) -> None:
|
||||
"""Record that a task is finished, as of the first refresh that saw it so."""
|
||||
record.is_done = True
|
||||
record.completed_at = record.completed_at or utcnow()
|
||||
record.completed_date = record.completed_date or today
|
||||
|
||||
|
||||
def refresh_tasks(session: Session, settings: AppSettings) -> dict[str, Any]:
|
||||
"""Pull every enabled source and reconcile the mirror.
|
||||
|
||||
Tasks a source stops returning are flagged `stale` rather than deleted: the
|
||||
history behind "you finished 6 things today" has to survive a source that
|
||||
quietly drops an item.
|
||||
A task a *healthy* source stops returning has left your plate — Jira drops
|
||||
it from "open issues" the moment you move it to Done — so it is recorded as
|
||||
closed today rather than deleted. Nothing is ever dropped on a source that
|
||||
failed: an outage must not look like a productive afternoon.
|
||||
"""
|
||||
results: list[SourceResult] = fetch_all(settings)
|
||||
today = local_date(settings.timezone)
|
||||
@@ -86,7 +102,7 @@ def refresh_tasks(session: Session, settings: AppSettings) -> dict[str, Any]:
|
||||
|
||||
seen_ids: set[str] = set()
|
||||
for item in result.items:
|
||||
_upsert(session, item)
|
||||
_upsert(session, item, today)
|
||||
seen_ids.add(item.external_id)
|
||||
|
||||
existing = session.scalars(
|
||||
@@ -94,9 +110,15 @@ def refresh_tasks(session: Session, settings: AppSettings) -> dict[str, Any]:
|
||||
TaskRecord.source == result.source, TaskRecord.is_done.is_(False)
|
||||
)
|
||||
).all()
|
||||
closed = 0
|
||||
for record in existing:
|
||||
if record.external_id not in seen_ids:
|
||||
if record.external_id in seen_ids:
|
||||
continue
|
||||
if result.source in _TRANSIENT_SOURCES:
|
||||
record.stale = True
|
||||
continue
|
||||
_mark_closed(record, today)
|
||||
closed += 1
|
||||
|
||||
summary.append(
|
||||
{
|
||||
@@ -105,6 +127,7 @@ def refresh_tasks(session: Session, settings: AppSettings) -> dict[str, Any]:
|
||||
"ok": True,
|
||||
"error": None,
|
||||
"count": len(result.items),
|
||||
"closed": closed,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,6 +137,7 @@ def refresh_tasks(session: Session, settings: AppSettings) -> dict[str, Any]:
|
||||
"date": today.isoformat(),
|
||||
"sources": summary,
|
||||
"total": sum(entry["count"] for entry in summary),
|
||||
"closed": sum(entry.get("closed", 0) for entry in summary),
|
||||
}
|
||||
|
||||
|
||||
@@ -155,26 +179,28 @@ def bucket_tasks(records: Iterable[TaskRecord], today: date) -> dict[str, list[T
|
||||
buckets: dict[str, list[TaskRecord]] = {
|
||||
"overdue": [],
|
||||
"today": [],
|
||||
"active": [],
|
||||
"upcoming": [],
|
||||
"someday": [],
|
||||
}
|
||||
for record in records:
|
||||
if record.due_date is None:
|
||||
buckets["someday"].append(record)
|
||||
elif record.due_date < today:
|
||||
if record.due_date is not None and record.due_date < today:
|
||||
buckets["overdue"].append(record)
|
||||
elif record.due_date == today:
|
||||
buckets["today"].append(record)
|
||||
elif record.start_date is not None and record.start_date <= today:
|
||||
# Started, but not due today — in progress rather than "upcoming".
|
||||
buckets["active"].append(record)
|
||||
elif record.due_date is None:
|
||||
buckets["someday"].append(record)
|
||||
else:
|
||||
buckets["upcoming"].append(record)
|
||||
return buckets
|
||||
|
||||
|
||||
def completed_on(session: Session, day: date, *, only_here: bool = True) -> list[TaskRecord]:
|
||||
query = select(TaskRecord).where(TaskRecord.completed_date == day)
|
||||
if only_here:
|
||||
query = query.where(TaskRecord.completed_here.is_(True))
|
||||
return list(session.scalars(query))
|
||||
def completed_on(session: Session, day: date) -> list[TaskRecord]:
|
||||
"""Tasks seen to close on `day`, whichever tool they were closed in."""
|
||||
return list(session.scalars(select(TaskRecord).where(TaskRecord.completed_date == day)))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -209,53 +235,6 @@ def create_local_task(
|
||||
return record
|
||||
|
||||
|
||||
def complete_task(
|
||||
session: Session, settings: AppSettings, task_id: int, *, done: bool = True
|
||||
) -> tuple[TaskRecord, str | None]:
|
||||
"""Mark a task done (or undone) here, and try to mirror it to its source.
|
||||
|
||||
Returns the record plus a warning when the write-back failed — the local
|
||||
state still changes, because the alternative is losing the fact that you
|
||||
finished something because Jira hiccupped.
|
||||
"""
|
||||
record = session.get(TaskRecord, task_id)
|
||||
if record is None:
|
||||
raise LookupError(f"no task {task_id}")
|
||||
|
||||
warning: str | None = None
|
||||
if done:
|
||||
record.is_done = True
|
||||
record.completed_here = True
|
||||
record.completed_at = utcnow()
|
||||
record.completed_date = local_date(settings.timezone)
|
||||
if record.source != LOCAL_SOURCE:
|
||||
item = TodoItem(
|
||||
source=record.source,
|
||||
external_id=record.external_id,
|
||||
title=record.title,
|
||||
raw=record.raw or {},
|
||||
)
|
||||
wrote, error = complete_remote(settings, item)
|
||||
if error:
|
||||
warning = (
|
||||
f"Marked done here, but {SOURCE_LABELS.get(record.source, record.source)}"
|
||||
f" did not accept it: {error}"
|
||||
)
|
||||
elif not wrote:
|
||||
warning = (
|
||||
f"Marked done here only — write-back is off for "
|
||||
f"{SOURCE_LABELS.get(record.source, record.source)}."
|
||||
)
|
||||
else:
|
||||
record.is_done = False
|
||||
record.completed_here = False
|
||||
record.completed_at = None
|
||||
record.completed_date = None
|
||||
|
||||
session.commit()
|
||||
return record, warning
|
||||
|
||||
|
||||
def update_task(session: Session, task_id: int, **changes: Any) -> TaskRecord:
|
||||
record = session.get(TaskRecord, task_id)
|
||||
if record is None:
|
||||
|
||||
Reference in new issue
Block a user