use toml for more
This commit is contained in:
parent
855d9ee0f8
commit
74ed6516b4
@ -3,7 +3,6 @@ import httpx
|
||||
import lxml.html
|
||||
from typing_extensions import Annotated
|
||||
from .controller.tasks import add_task
|
||||
from .import_csv import import_tasks_from_csv
|
||||
from .db import initialize_db
|
||||
from .tui.tasks import run as tasks_tui
|
||||
from .tui.overview import run as overview_tui
|
||||
@ -62,11 +61,5 @@ def overview():
|
||||
overview_tui()
|
||||
|
||||
|
||||
@app.command()
|
||||
def import_csv(filename: str):
|
||||
import_tasks_from_csv(filename)
|
||||
print("Import complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
|
@ -17,10 +17,10 @@ def get_enum(name):
|
||||
|
||||
raise ValueError(f"no such enum! {name}")
|
||||
|
||||
def get_view_columns(name):
|
||||
def get_view(name):
|
||||
for view in _load_config().get("views", []):
|
||||
if view["name"] == name:
|
||||
return view["columns"]
|
||||
return view
|
||||
|
||||
raise ValueError(f"no such view! {name}")
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from datetime import datetime, timedelta
|
||||
from peewee import fn, JOIN
|
||||
from ..db import Task, Category
|
||||
from ..db import Task
|
||||
|
||||
|
||||
def get_category_summary(num: int = 5) -> list[dict]:
|
||||
@ -13,81 +13,7 @@ def get_category_summary(num: int = 5) -> list[dict]:
|
||||
Returns:
|
||||
List of dicts containing category name and task statistics
|
||||
"""
|
||||
now = datetime.now()
|
||||
week_from_now = now + timedelta(days=7)
|
||||
|
||||
# Subquery for overdue tasks
|
||||
overdue_count = (
|
||||
Task.select(Task.category, fn.COUNT(Task.id).alias("overdue"))
|
||||
.where(
|
||||
(~Task.deleted) & (Task.due < now) & (Task.status != "done")
|
||||
)
|
||||
.group_by(Task.category)
|
||||
)
|
||||
|
||||
# Subquery for tasks due in next 7 days
|
||||
due_soon_count = (
|
||||
Task.select(Task.category, fn.COUNT(Task.id).alias("due_soon"))
|
||||
.where(
|
||||
(~Task.deleted)
|
||||
& (Task.due >= now)
|
||||
& (Task.due <= week_from_now)
|
||||
& (Task.status != "done")
|
||||
)
|
||||
.group_by(Task.category)
|
||||
)
|
||||
|
||||
# Main query joining all the information
|
||||
query = (
|
||||
Category.select(
|
||||
Category.name,
|
||||
fn.COALESCE(fn.SUM(Task.status == "zero"), 0).alias(
|
||||
"zero_count"
|
||||
),
|
||||
fn.COALESCE(fn.SUM(Task.status == "wip"), 0).alias(
|
||||
"wip_count"
|
||||
),
|
||||
fn.COALESCE(fn.SUM(Task.status == "blocked"), 0).alias(
|
||||
"blocked_count"
|
||||
),
|
||||
fn.COALESCE(fn.SUM(Task.status == "done"), 0).alias(
|
||||
"done_count"
|
||||
),
|
||||
fn.COALESCE(overdue_count.c.overdue, 0).alias("overdue"),
|
||||
fn.COALESCE(due_soon_count.c.due_soon, 0).alias("due_soon"),
|
||||
)
|
||||
.join(Task, JOIN.LEFT_OUTER)
|
||||
.join(
|
||||
overdue_count,
|
||||
JOIN.LEFT_OUTER,
|
||||
on=(Category.id == overdue_count.c.category_id),
|
||||
)
|
||||
.join(
|
||||
due_soon_count,
|
||||
JOIN.LEFT_OUTER,
|
||||
on=(Category.id == due_soon_count.c.category_id),
|
||||
)
|
||||
.where(~Task.deleted)
|
||||
.group_by(Category.id)
|
||||
.order_by(fn.COUNT(Task.id).desc())
|
||||
.limit(num)
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"category": cat.name,
|
||||
"tasks": {
|
||||
"zero": cat.zero_count,
|
||||
"wip": cat.wip_count,
|
||||
"blocked": cat.blocked_count,
|
||||
"done": cat.done_count,
|
||||
"overdue": cat.overdue,
|
||||
"due_soon": cat.due_soon,
|
||||
},
|
||||
}
|
||||
for cat in query
|
||||
]
|
||||
|
||||
return []
|
||||
|
||||
def get_recently_active(num: int = 5, category: str | None = None) -> list[dict]:
|
||||
"""
|
||||
|
@ -2,7 +2,7 @@ import json
|
||||
from datetime import datetime
|
||||
from peewee import fn
|
||||
from peewee import Case, Value
|
||||
from ..db import db, Task, Category, SavedSearch
|
||||
from ..db import db, Task, SavedSearch
|
||||
from .. import config
|
||||
|
||||
|
||||
@ -84,8 +84,8 @@ def get_tasks(
|
||||
query = query.where(fn.Lower(Task.text).contains(search_text.lower()))
|
||||
if statuses:
|
||||
query = query.where(Task.status.in_(statuses))
|
||||
if projects:
|
||||
query = query.where(Task.project.in_(projects))
|
||||
#if projects:
|
||||
# query = query.where(Task.project.in_(projects))
|
||||
|
||||
sort_expressions = _parse_sort_string(sort, statuses)
|
||||
query = query.order_by(*sort_expressions)
|
||||
@ -93,10 +93,6 @@ def get_tasks(
|
||||
return list(query)
|
||||
|
||||
|
||||
def get_categories() -> list[Category]:
|
||||
return list(Category.select().order_by(Category.name))
|
||||
|
||||
|
||||
def save_view(name: str, *, filters: dict, sort_string: str) -> SavedSearch:
|
||||
filters_json = json.dumps(filters)
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import json
|
||||
from datetime import date, timedelta, datetime
|
||||
from enum import Enum
|
||||
from peewee import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
DateTimeField,
|
||||
ForeignKeyField,
|
||||
Model,
|
||||
SqliteDatabase,
|
||||
TextField,
|
||||
|
@ -24,6 +24,8 @@ class NotifyValidationError(Exception):
|
||||
"""will notify and continue if raised"""
|
||||
|
||||
|
||||
ELLIPSIS = "…"
|
||||
|
||||
class TableColumnConfig:
|
||||
def __init__(
|
||||
self,
|
||||
@ -73,7 +75,6 @@ class EnumColumnConfig(TableColumnConfig):
|
||||
def format_for_display(self, val):
|
||||
return get_color_enum(val, self.enum)
|
||||
|
||||
|
||||
def start_change(self, app, current_value):
|
||||
# a weird hack? pass app here and correct modal gets pushed
|
||||
app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
|
||||
@ -93,7 +94,6 @@ class DateColumnConfig(TableColumnConfig):
|
||||
app.push_screen(DateModal(current_value), app.apply_change)
|
||||
|
||||
|
||||
ELLIPSIS = "…"
|
||||
|
||||
|
||||
class TableEditor(App):
|
||||
@ -167,18 +167,25 @@ class TableEditor(App):
|
||||
("?", "show_keys", "show keybindings"),
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, editor_config: str):
|
||||
super().__init__()
|
||||
self.filters = {}
|
||||
self.sort_string = "" # TODO: default sort
|
||||
self._load_config(editor_config)
|
||||
self.search_query = ""
|
||||
self.saved_cursor_pos = (1, 0)
|
||||
self.save_on_move = None
|
||||
|
||||
def _load_config(self, name):
|
||||
# column config
|
||||
columns = []
|
||||
for col in config.get_view_columns(name):
|
||||
"""
|
||||
Reads configuration from TOML and sets up:
|
||||
- table_config
|
||||
- filters
|
||||
- sort_string
|
||||
"""
|
||||
self.table_config = []
|
||||
view = config.get_view(name)
|
||||
|
||||
# set up columns
|
||||
for col in view["columns"]:
|
||||
field_type = col.get("field_type", "text")
|
||||
field_cls = {
|
||||
"text": TableColumnConfig,
|
||||
@ -196,8 +203,24 @@ class TableEditor(App):
|
||||
extras["enable_editor"] = True
|
||||
|
||||
cc = field_cls(field_name, display_name, default=default, **extras)
|
||||
columns.append(cc)
|
||||
return columns
|
||||
self.table_config.append(cc)
|
||||
|
||||
# load default filters
|
||||
self.filters = view["filters"]
|
||||
self.sort_string = view["sort"]
|
||||
|
||||
def _db_item_to_row(self, item):
|
||||
"""
|
||||
Convert db item to a row for display.
|
||||
"""
|
||||
row = []
|
||||
|
||||
for col in self.table_config:
|
||||
val = getattr(item, col.field)
|
||||
display_val = col.format_for_display(val)
|
||||
row.append(display_val)
|
||||
|
||||
return row
|
||||
|
||||
def compose(self):
|
||||
self.header = Header()
|
||||
|
@ -45,10 +45,9 @@ class ChoiceModal(ModalScreen):
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
("j", "cursor_down", "Down"),
|
||||
("j,tab", "cursor_down", "Down"),
|
||||
("k", "cursor_up", "Up"),
|
||||
Binding("enter", "select", "Select", priority=True),
|
||||
("c", "select", "Select"),
|
||||
Binding("enter", "select", "Select", priority=False),
|
||||
("escape", "cancel", "cancel"),
|
||||
]
|
||||
|
||||
@ -75,7 +74,8 @@ class ChoiceModal(ModalScreen):
|
||||
|
||||
def action_select(self):
|
||||
rs = self.query_one(RadioSet)
|
||||
rs.action_toggle_button()
|
||||
# TODO: this doesn't work
|
||||
#rs.action_toggle_button()
|
||||
pressed = rs.pressed_button
|
||||
self.dismiss(str(pressed.label))
|
||||
|
||||
@ -101,9 +101,8 @@ class DateModal(ModalScreen):
|
||||
BINDINGS = [
|
||||
("j", "cursor_down", "Down"),
|
||||
("k", "cursor_up", "Up"),
|
||||
("h", "cursor_left", "Left"),
|
||||
("h,tab", "cursor_left", "Left"),
|
||||
("l", "cursor_right", "Right"),
|
||||
# ("0,1,2,3,4,5,6,7,8,9", "num_entry", "#"),
|
||||
Binding("enter", "select", "Select", priority=True),
|
||||
("escape", "cancel", "cancel"),
|
||||
]
|
||||
|
@ -21,14 +21,12 @@ class TT(TableEditor):
|
||||
]
|
||||
|
||||
def __init__(self, default_view="default"):
|
||||
super().__init__()
|
||||
self._load_view(default_view)
|
||||
super().__init__("tasks")
|
||||
self.update_item_callback = update_task
|
||||
self.add_item_callback = add_task
|
||||
self.get_item_callback = get_task
|
||||
self.table_config = self._load_config("tasks")
|
||||
|
||||
def _load_view(self, name):
|
||||
def _load_db_view(self, name):
|
||||
try:
|
||||
saved = get_saved_view(name)
|
||||
self.filters = json.loads(saved.filters)
|
||||
@ -55,19 +53,6 @@ class TT(TableEditor):
|
||||
# if event isn't handled here it will bubble to parent
|
||||
event.prevent_default()
|
||||
|
||||
def _db_item_to_row(self, item):
|
||||
"""
|
||||
convert db item to an item
|
||||
"""
|
||||
row = []
|
||||
|
||||
for col in self.table_config:
|
||||
val = getattr(item, col.field)
|
||||
display_val = col.format_for_display(val)
|
||||
row.append(display_val)
|
||||
|
||||
return row
|
||||
|
||||
def refresh_items(self):
|
||||
items = get_tasks(
|
||||
self.search_query,
|
||||
|
8
tt.toml
8
tt.toml
@ -2,8 +2,8 @@
|
||||
name = "status"
|
||||
values = [
|
||||
{ value = "zero", color = "#666666" },
|
||||
{ value = "blocked", color = "#33a99" },
|
||||
{ value = "wip", color = "#cc9900" },
|
||||
{ value = "blocked", color = "#cc9900" },
|
||||
{ value = "wip", color = "#33aa99" },
|
||||
{ value = "done", color = "#009900" },
|
||||
]
|
||||
|
||||
@ -28,6 +28,10 @@ values = [
|
||||
|
||||
[[views]]
|
||||
name = "tasks"
|
||||
sort = "due"
|
||||
|
||||
[views.filters]
|
||||
status = "wip,blocked,zero"
|
||||
|
||||
[[views.columns]]
|
||||
field_name = "id"
|
||||
|
Loading…
Reference in New Issue
Block a user