hamster-tools/hamstertools/app.py

325 lines
9.6 KiB
Python
Raw Normal View History

2023-10-27 00:13:08 +00:00
from textual.app import App, ComposeResult
2023-10-27 03:04:30 +00:00
from textual.binding import Binding
from textual.widgets import Header, Footer, DataTable, Input
from textual.containers import Horizontal, Vertical
from textual.coordinate import Coordinate
2023-10-27 01:26:15 +00:00
from textual.screen import Screen
2023-10-27 00:13:08 +00:00
2023-10-27 23:42:30 +00:00
from peewee import fn, JOIN
from .db import (
db,
HamsterCategory,
HamsterActivity,
HamsterFact,
KimaiProject,
KimaiCustomer,
KimaiActivity,
)
from .kimai import (
KimaiAPI,
Customer as KimaiAPICustomer,
Project as KimaiAPIProject,
Activity as KimaiAPIActivity,
)
2023-10-27 00:13:08 +00:00
2023-10-27 15:40:25 +00:00
2023-10-27 15:39:58 +00:00
class ListScreen(Screen):
def compose(self) -> ComposeResult:
2023-10-27 01:26:15 +00:00
"""create child widgets for the app."""
yield Header()
2023-10-27 03:04:30 +00:00
with Vertical():
yield DataTable()
with Horizontal():
yield Input(id="filter")
yield Footer()
2023-10-27 00:13:08 +00:00
def action_refresh(self) -> None:
self._refresh()
def action_sort(self) -> None:
self.table.cursor_type = "column"
2023-10-27 15:28:46 +00:00
def on_data_table_column_selected(self, event):
self.sort = (event.column_key,)
event.data_table.sort(*self.sort)
event.data_table.cursor_type = "row"
2023-10-27 03:04:30 +00:00
def action_filter(self) -> None:
filter_input = self.query_one("#filter")
filter_input.display = True
2023-10-27 15:28:46 +00:00
self._refresh(filter_input.value)
2023-10-27 03:04:30 +00:00
filter_input.focus()
2023-10-27 15:28:46 +00:00
def on_input_submitted(self, event):
self.table.focus()
2023-10-27 03:04:30 +00:00
def action_cancelfilter(self) -> None:
filter_input = self.query_one("#filter")
filter_input.display = False
2023-10-27 15:28:46 +00:00
filter_input.clear()
self.table.focus()
2023-10-27 03:04:30 +00:00
self._refresh()
2023-10-27 15:39:58 +00:00
def on_input_changed(self, event):
self._refresh(event.value)
class ActivitiesScreen(ListScreen):
BINDINGS = [
("s", "sort", "Sort"),
("r", "refresh", "Refresh"),
("/", "filter", "Search"),
("d", "delete", "Delete activity"),
("f", "move_facts", "Move facts"),
Binding(key="escape", action="cancelfilter", show=False),
]
def _refresh(self, filter_query=None):
self.table.clear()
2023-10-27 23:42:30 +00:00
activities = (
HamsterActivity.select(
HamsterActivity,
HamsterCategory,
fn.Count(HamsterFact.id).alias("facts_count")
)
.join(HamsterCategory, JOIN.LEFT_OUTER)
.switch(HamsterActivity)
.join(HamsterFact, JOIN.LEFT_OUTER)
.group_by(HamsterActivity)
)
if filter_query:
activities = activities.where(
HamsterActivity.name.contains(filter_query)
| HamsterCategory.name.contains(filter_query)
)
2023-10-27 15:39:58 +00:00
2023-10-27 15:40:25 +00:00
self.table.add_rows(
[
[
2023-10-27 23:42:30 +00:00
activity.category.id,
activity.category.name,
2023-10-27 15:40:25 +00:00
activity.id,
activity.name,
activity.facts_count,
]
for activity in activities
]
)
2023-10-27 15:39:58 +00:00
self.table.sort(*self.sort)
def on_mount(self) -> None:
self.table = self.query_one(DataTable)
self.table.cursor_type = "row"
2023-10-27 15:40:25 +00:00
self.columns = self.table.add_columns(
"category id", "category", "activity id", "activity", "entries"
)
2023-10-27 15:39:58 +00:00
self.sort = (self.columns[1], self.columns[3])
self._refresh()
2023-10-27 01:26:15 +00:00
def action_delete(self) -> None:
2023-10-27 15:28:46 +00:00
# get the keys for the row and column under the cursor.
row_key, _ = self.table.coordinate_to_cell_key(self.table.cursor_coordinate)
activity_id = self.table.get_cell_at(
Coordinate(self.table.cursor_coordinate.row, 2),
)
2023-10-27 23:42:30 +00:00
activity = HamsterActivity.get(id=activity_id)
activity.delete_instance()
2023-10-27 01:26:15 +00:00
# supply the row key to `remove_row` to delete the row.
self.table.remove_row(row_key)
2023-10-27 15:28:46 +00:00
def action_move_facts(self) -> None:
row_idx: int = self.table.cursor_row
row_cells = self.table.get_row_at(row_idx)
2023-10-27 23:42:30 +00:00
self.move_from_activity = HamsterActivity.get(id=row_cells[2])
2023-10-27 15:28:46 +00:00
for col_idx, cell_value in enumerate(row_cells):
cell_coordinate = Coordinate(row_idx, col_idx)
self.table.update_cell_at(
cell_coordinate,
f"[red]{cell_value}[/red]",
)
2023-10-27 15:40:25 +00:00
self.table.move_cursor(row=self.table.cursor_coordinate[0] + 1)
2023-10-27 15:28:46 +00:00
def on_data_table_row_selected(self, event):
if getattr(self, "move_from_activity", None) is not None:
2023-10-27 23:42:30 +00:00
move_to_activity = HamsterActivity.get(
self.table.get_cell_at(Coordinate(event.cursor_row, 2))
2023-10-27 15:40:25 +00:00
)
2023-10-27 23:42:30 +00:00
HamsterFact.update({HamsterFact.activity:
move_to_activity}).where(HamsterFact.activity ==
self.move_from_activity).execute()
2023-10-27 15:48:03 +00:00
filter_input = self.query_one("#filter")
self._refresh(filter_input.value)
2023-10-27 15:28:46 +00:00
del self.move_from_activity
2023-10-27 01:26:15 +00:00
2023-10-27 15:40:25 +00:00
2023-10-27 15:39:58 +00:00
class CategoriesScreen(ListScreen):
2023-10-27 01:26:15 +00:00
BINDINGS = [
("s", "sort", "Sort"),
("r", "refresh", "Refresh"),
2023-10-27 03:04:30 +00:00
("/", "filter", "Search"),
2023-10-27 15:39:58 +00:00
("d", "delete", "Delete category"),
2023-10-27 03:04:30 +00:00
Binding(key="escape", action="cancelfilter", show=False),
2023-10-27 01:26:15 +00:00
]
2023-10-27 03:04:30 +00:00
def _refresh(self, filter_query=None):
2023-10-27 01:26:15 +00:00
self.table.clear()
2023-10-27 23:42:30 +00:00
categories = (
HamsterCategory.select(
HamsterCategory,
fn.Count(HamsterActivity.id).alias("activities_count")
)
.join(HamsterActivity, JOIN.LEFT_OUTER)
.group_by(HamsterCategory)
2023-10-27 15:40:25 +00:00
)
2023-10-27 03:04:30 +00:00
2023-10-27 23:42:30 +00:00
if filter_query:
categories = categories.where(
HamsterCategory.name.contains(filter_query)
)
2023-10-27 15:40:25 +00:00
self.table.add_rows(
[
[
category.id,
category.name,
2023-10-27 23:42:30 +00:00
category.activities_count,
2023-10-27 15:40:25 +00:00
]
for category in categories
]
)
2023-10-27 03:04:30 +00:00
self.table.sort(self.sort)
2023-10-27 01:26:15 +00:00
def on_mount(self) -> None:
self.table = self.query_one(DataTable)
self.table.cursor_type = "row"
2023-10-27 15:40:25 +00:00
self.columns = self.table.add_columns("category id", "category", "activities")
2023-10-27 03:04:30 +00:00
self.sort = self.columns[1]
2023-10-27 01:26:15 +00:00
self._refresh()
def action_delete(self) -> None:
# get the keys for the row and column under the cursor.
row_key, _ = self.table.coordinate_to_cell_key(self.table.cursor_coordinate)
category_id = self.table.get_cell_at(
Coordinate(self.table.cursor_coordinate.row, 0),
)
2023-10-27 23:42:30 +00:00
category = HamsterCategory.get(id=category_id)
category.delete_instance()
2023-10-27 01:26:15 +00:00
# supply the row key to `remove_row` to delete the row.
self.table.remove_row(row_key)
2023-10-27 15:40:25 +00:00
class KimaiScreen(ListScreen):
BINDINGS = [
("s", "sort", "Sort"),
("r", "refresh", "Refresh"),
("g", "get", "Get data"),
("/", "filter", "Search"),
Binding(key="escape", action="cancelfilter", show=False),
]
def _refresh(self, filter_query=None):
self.table.clear()
2023-10-27 23:42:30 +00:00
projects = (
KimaiProject.select(
KimaiProject,
KimaiCustomer,
fn.Count(KimaiActivity.id).alias("activities_count")
)
.join(KimaiCustomer, JOIN.LEFT_OUTER)
.switch(KimaiProject)
.join(KimaiActivity, JOIN.LEFT_OUTER)
.group_by(KimaiProject)
)
if filter_query:
projects = projects.where(
KimaiProject.name.contains(filter_query)
| KimaiCustomer.name.contains(filter_query)
)
self.table.add_rows(
[
[
2023-10-27 23:42:30 +00:00
project.customer.id,
project.customer.name,
project.id,
project.name,
2023-10-27 23:42:30 +00:00
project.activities_count
]
for project in projects
]
)
self.table.sort(self.sort)
def action_get(self) -> None:
api = KimaiAPI()
customers = KimaiAPICustomer.list(api)
2023-10-27 23:42:30 +00:00
with db.atomic():
KimaiCustomer.insert_many([{
'id': customer.id,
'name': customer.name
} for customer in customers]).execute()
projects = KimaiAPIProject.list(api)
2023-10-27 23:42:30 +00:00
with db.atomic():
KimaiProject.insert_many([{
'id': project.id,
'name': project.name,
'customer_id': project.customer.id
} for project in projects]).execute()
self._refresh()
def on_mount(self) -> None:
self.table = self.query_one(DataTable)
self.table.cursor_type = "row"
2023-10-27 23:42:30 +00:00
self.columns = self.table.add_columns(
"customer id", "customer", "project id", "project", "activities"
)
# self.sort = (self.columns[1], self.columns[3])
self.sort = self.columns[1]
self._refresh()
2023-10-27 01:26:15 +00:00
class HamsterToolsApp(App):
2023-10-27 15:40:25 +00:00
CSS_PATH = "app.tcss"
2023-10-27 01:26:15 +00:00
BINDINGS = [
("a", "switch_mode('activities')", "Activities"),
("c", "switch_mode('categories')", "Categories"),
("k", "switch_mode('kimai')", "Kimai"),
2023-10-27 01:26:15 +00:00
("q", "quit", "Quit"),
]
2023-10-27 03:04:30 +00:00
def __init__(self):
2023-10-27 23:42:30 +00:00
db.init("hamster-testing.db")
2023-10-27 01:26:15 +00:00
self.MODES = {
2023-10-27 23:42:30 +00:00
"categories": CategoriesScreen(),
"activities": ActivitiesScreen(),
"kimai": KimaiScreen(),
2023-10-27 01:26:15 +00:00
}
super().__init__()
def on_mount(self) -> None:
self.switch_mode("activities")
def action_quit(self) -> None:
self.exit()
2023-10-27 23:42:30 +00:00
db.close()