working datatable

This commit is contained in:
James Turk 2025-01-03 19:00:17 -06:00
parent ccc7eb22b2
commit 31bd51aa28
3 changed files with 197 additions and 14 deletions

View File

@ -27,24 +27,12 @@ def add_task(
def update_task(
task_id: int,
text: str | None,
type: str | None,
status: str | None = None,
due: datetime | None = None,
**kwargs,
) -> Task:
with db.atomic():
task = Task.get_by_id(task_id)
update_dict = {
"text": text,
"type": type,
"due": due,
"status": status,
}
if status not in [s.value for s in TaskStatus]:
raise ValueError(f"Invalid status: {status}")
query = Task.update(update_dict).where(Task.id == task_id)
query = Task.update(kwargs).where(Task.id == task_id)
query.execute()
task = Task.get_by_id(task_id)
return task

View File

@ -51,6 +51,8 @@ class Task(BaseModel):
def initialize_db():
db.connect()
db.create_tables([Category, Task])
if not Category.select().exists():
Category.create(name="main")
db.close()

193
src/tt/tui.py Normal file
View File

@ -0,0 +1,193 @@
from textual.app import App
from textual.widgets import DataTable, Header, Input
from textual.containers import Container
from datetime import datetime
from .controller import get_tasks, add_task, update_task, TaskStatus
from .db import initialize_db
class TaskManagerApp(App):
CSS = """
Input {
dock: bottom;
margin: 0;
padding: 0;
border: none;
background: $boost;
}
"""
BINDINGS = [
# movement
("h", "cursor_left", "Left"),
("j", "cursor_down", "Down"),
("k", "cursor_up", "Up"),
("l", "cursor_right", "Right"),
("g", "cursor_top", "Top"),
("G", "cursor_bottom", "Bottom"),
("a", "add_task", "Add Task"),
("c", "start_edit", "Edit Cell"),
("escape", "cancel_edit", "Cancel Edit"),
("q", "quit", "Quit"),
]
def __init__(self):
super().__init__()
self.edit_row = None
self.edit_column = None
def compose(self):
yield Header()
yield Container(DataTable())
# Input is hidden by default
yield Input(id="cell_editor")
def action_cursor_left(self):
table = self.query_one(DataTable)
table.move_cursor(column=table.cursor_column - 1)
def action_cursor_right(self):
table = self.query_one(DataTable)
table.move_cursor(column=table.cursor_column + 1)
def action_cursor_up(self):
table = self.query_one(DataTable)
table.move_cursor(row=table.cursor_row - 1)
def action_cursor_down(self):
table = self.query_one(DataTable)
y, x = table.cursor_coordinate
table.move_cursor(row=table.cursor_row + 1)
def action_cursor_top(self):
table = self.query_one(DataTable)
table.move_cursor(row=0)
def action_cursor_bottom(self):
table = self.query_one(DataTable)
table.move_cursor(row=table.row_count - 1)
def on_mount(self):
table = self.query_one(DataTable)
table.add_columns("ID", "Task", "Status", "Type", "Category", "Due Date")
self.refresh_tasks()
def refresh_tasks(self):
"""Refresh the tasks table with current data."""
table = self.query_one(DataTable)
table.clear()
tasks = get_tasks()
for task in tasks:
# Format the due date nicely
due_str = task.due.strftime("%Y-%m-%d %H:%M") if task.due else "No due date"
# Get category name or empty string if None
category = task.category.name if task.category else ""
table.add_row(
str(task.id),
task.text,
task.status,
task.type,
category,
due_str,
key=str(task.id),
)
def action_add_task(self):
"""Add a new task with default values."""
add_task(
text="New Task",
type="task",
status=TaskStatus.ZERO.value,
category="main",
)
self.refresh_tasks()
def action_start_edit(self):
"""Show the input widget for editing the current cell."""
table = self.query_one(DataTable)
if table.cursor_row is None or table.cursor_column == 0: # Don't edit ID column
return
# Store the current cell position
self.edit_row = table.cursor_row
self.edit_column = table.cursor_column
# Get current value and show input
current_value = table.get_cell_at((table.cursor_row, table.cursor_column))
input_widget = self.query_one("#cell_editor")
input_widget.value = current_value
# input_widget.visible = True
self.set_focus(input_widget)
def action_cancel_edit(self):
"""Hide the input widget without saving."""
if self.edit_row is not None:
input_widget = self.query_one("#cell_editor")
# input_widget.visible = False
self.edit_row = None
self.edit_column = None
self.set_focus(self.query_one(DataTable))
def on_input_submitted(self, event: Input.Submitted):
"""Handle the submission of the edit input."""
if self.edit_row is None:
return
table = self.query_one(DataTable)
task_id = int(table.get_cell_at((self.edit_row, 0)))
new_value = event.value
# Map column index to field name
column_to_field = {
1: "text",
2: "status",
3: "type",
4: "category_name",
5: "due",
}
if self.edit_column in column_to_field:
field = column_to_field[self.edit_column]
update_data = {}
# Special handling for different field types
if field == "due":
try:
update_data["due"] = datetime.strptime(new_value, "%Y-%m-%d")
except ValueError:
self.notify("Invalid date format. Use YYYY-MM-DD")
self.refresh_tasks()
return
elif field == "status":
try:
TaskStatus(new_value) # Validate status
update_data["status"] = new_value
except ValueError:
self.notify(f"Invalid status. Use: {[s.value for s in TaskStatus]}")
self.refresh_tasks()
return
else:
update_data[field] = new_value
try:
update_task(task_id, **update_data)
self.refresh_tasks()
except Exception as e:
self.notify(f"Error updating task: {str(e)}")
finally:
# Hide the input widget and restore focus
self.action_cancel_edit()
def run():
initialize_db()
app = TaskManagerApp()
app.run()
if __name__ == "__main__":
run()