from textual.app import ComposeResult from textual.coordinate import Coordinate from textual.binding import Binding from textual.screen import Screen from textual.widgets import DataTable, TabbedContent, TabPane, Header, Footer from peewee import fn, JOIN from ..utils import truncate from ..sync import sync from ..db import ( KimaiProject, KimaiCustomer, KimaiActivity, ) from ..kimaiapi import Timesheet as KimaiAPITimesheet from .list import ListPane class KimaiCustomerList(ListPane): pass class KimaiProjectList(ListPane): BINDINGS = [ ("s", "sort", "Sort"), ("r", "refresh", "Refresh"), ("g", "get", "Get data"), ("/", "filter", "Search"), Binding(key="escape", action="cancelfilter", show=False), ] def _refresh(self): self.table.clear() filter_search = self.query_one("#filter #search").value projects = ( KimaiProject.select( KimaiProject, KimaiCustomer.id, fn.COALESCE(KimaiCustomer.name, "").alias("customer_name"), fn.Count(KimaiActivity.id).alias("activities_count"), ) .join(KimaiCustomer, JOIN.LEFT_OUTER) .switch(KimaiProject) .join(KimaiActivity, JOIN.LEFT_OUTER) .where(KimaiActivity.project.is_null(False)) .group_by(KimaiProject) ) if filter_search: projects = projects.where( KimaiProject.name.contains(filter_search) | KimaiCustomer.name.contains(filter_search) ) self.table.add_rows( [ [ str(project.customer_id) if project.customer_id is not None else "", project.customer_name, project.id, project.name, project.activities_count, project.visible, ] for project in projects ] ) self.table.sort(*self.sort) def action_get(self) -> None: sync() self._refresh() def on_mount(self) -> None: self.table = self.query_one(DataTable) self.table.cursor_type = "row" self.columns = self.table.add_columns( "customer id", "customer", "project id", "project", "activities", "visible" ) self.sort = (self.columns[1], self.columns[3]) self._refresh() class KimaiActivityList(ListPane): BINDINGS = [ ("s", "sort", "Sort"), ("r", "refresh", "Refresh"), ("g", "get", "Get data"), ("#", "count", "Count"), ("/", "filter", "Search"), Binding(key="escape", action="cancelfilter", show=False), ] def _refresh(self): self.table.clear() filter_search = self.query_one("#filter #search").value activities = ( KimaiActivity.select( KimaiActivity, fn.COALESCE(KimaiProject.name, "").alias("project_name"), fn.COALESCE(KimaiCustomer.name, "").alias("customer_name"), ) .join(KimaiProject, JOIN.LEFT_OUTER) .join(KimaiCustomer, JOIN.LEFT_OUTER) .group_by(KimaiActivity) ) if filter_search: activities = activities.where(KimaiActivity.name.contains(filter_search)) self.table.add_rows( [ [ # activity.project.customer_id if activity.project is not None else '', # activity.customer_name, str(activity.project_id) if activity.project_id is not None else "", truncate(activity.project_name, 40), activity.id, truncate(activity.name, 40), activity.visible, "?", ] for activity in activities ] ) self.table.sort(*self.sort) def action_get(self) -> None: sync() self._refresh() def on_mount(self) -> None: self.table = self.query_one(DataTable) self.table.cursor_type = "row" self.columns = self.table.add_columns( # "customer id", # "customer", "project id", "project", "id", "name", "visible", "times", ) self.sort = (self.columns[1], self.columns[3]) self._refresh() def action_count(self) -> None: row_idx: int = self.table.cursor_row row_cells = self.table.get_row_at(row_idx) activity_id = row_cells[2] count = len(KimaiAPITimesheet.list_by(self.app.api, activity=activity_id)) self.table.update_cell_at(Coordinate(row_idx, 5), count) class KimaiScreen(Screen): BINDINGS = [ ("c", "show_tab('customers')", "Customers"), ("p", "show_tab('projects')", "Projects"), ("a", "show_tab('activities')", "Activities"), ] SUB_TITLE = "Kimai" def compose(self) -> ComposeResult: yield Header() with TabbedContent(initial="projects"): with TabPane("Customers", id="customers"): yield KimaiCustomerList() with TabPane("Projects", id="projects"): yield KimaiProjectList() with TabPane("Activities", id="activities"): yield KimaiActivityList() yield Footer() def on_mount(self) -> None: self.query_one("TabbedContent Tabs").can_focus = False self.query_one("#projects DataTable").focus() def action_show_tab(self, tab: str) -> None: """Switch to a new tab.""" self.get_child_by_type(TabbedContent).active = tab self.query_one(f"#{tab} DataTable").focus()