2023-11-03 21:52:08 +00:00
|
|
|
from textual.app import ComposeResult
|
2023-11-17 22:54:25 +00:00
|
|
|
from textual.coordinate import Coordinate
|
2023-11-03 21:52:08 +00:00
|
|
|
from textual.binding import Binding
|
|
|
|
from textual.screen import Screen
|
|
|
|
from textual.widgets import DataTable, TabbedContent, TabPane, Header, Footer
|
|
|
|
|
|
|
|
from peewee import fn, JOIN
|
|
|
|
|
2024-12-07 15:08:20 +00:00
|
|
|
from ...utils import truncate
|
|
|
|
from ...sync import sync
|
|
|
|
from ...db import (
|
2023-11-03 21:52:08 +00:00
|
|
|
KimaiProject,
|
|
|
|
KimaiCustomer,
|
|
|
|
KimaiActivity,
|
|
|
|
)
|
2024-12-07 15:08:20 +00:00
|
|
|
from ...kimaiapi import Timesheet as KimaiAPITimesheet
|
2023-11-03 21:52:08 +00:00
|
|
|
|
2024-12-07 15:08:20 +00:00
|
|
|
from ..list import ListPane
|
2023-11-03 21:52:08 +00:00
|
|
|
|
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
class KimaiCustomerList(ListPane):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2023-11-03 21:52:08 +00:00
|
|
|
class KimaiProjectList(ListPane):
|
|
|
|
BINDINGS = [
|
|
|
|
("s", "sort", "Sort"),
|
|
|
|
("r", "refresh", "Refresh"),
|
|
|
|
("g", "get", "Get data"),
|
|
|
|
("/", "filter", "Search"),
|
|
|
|
Binding(key="escape", action="cancelfilter", show=False),
|
|
|
|
]
|
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
def _refresh(self):
|
2023-11-03 21:52:08 +00:00
|
|
|
self.table.clear()
|
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
filter_search = self.query_one("#filter #search").value
|
|
|
|
|
2023-11-03 21:52:08 +00:00
|
|
|
projects = (
|
|
|
|
KimaiProject.select(
|
|
|
|
KimaiProject,
|
2023-11-04 00:01:53 +00:00
|
|
|
KimaiCustomer.id,
|
|
|
|
fn.COALESCE(KimaiCustomer.name, "").alias("customer_name"),
|
2023-11-03 21:52:08 +00:00
|
|
|
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)
|
|
|
|
)
|
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
if filter_search:
|
2023-11-03 21:52:08 +00:00
|
|
|
projects = projects.where(
|
2023-11-03 23:42:11 +00:00
|
|
|
KimaiProject.name.contains(filter_search)
|
|
|
|
| KimaiCustomer.name.contains(filter_search)
|
2023-11-03 21:52:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
self.table.add_rows(
|
|
|
|
[
|
|
|
|
[
|
2023-11-04 00:01:53 +00:00
|
|
|
str(project.customer_id) if project.customer_id is not None else "",
|
|
|
|
project.customer_name,
|
2023-11-03 21:52:08 +00:00
|
|
|
project.id,
|
|
|
|
project.name,
|
|
|
|
project.activities_count,
|
2023-11-03 23:42:11 +00:00
|
|
|
project.visible,
|
2023-11-03 21:52:08 +00:00
|
|
|
]
|
|
|
|
for project in projects
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2023-11-04 00:01:53 +00:00
|
|
|
self.table.sort(*self.sort)
|
2023-11-03 21:52:08 +00:00
|
|
|
|
|
|
|
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(
|
2023-11-03 23:42:11 +00:00
|
|
|
"customer id", "customer", "project id", "project", "activities", "visible"
|
2023-11-03 21:52:08 +00:00
|
|
|
)
|
2023-11-04 00:01:53 +00:00
|
|
|
self.sort = (self.columns[1], self.columns[3])
|
2023-11-03 21:52:08 +00:00
|
|
|
self._refresh()
|
|
|
|
|
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
class KimaiActivityList(ListPane):
|
|
|
|
BINDINGS = [
|
|
|
|
("s", "sort", "Sort"),
|
|
|
|
("r", "refresh", "Refresh"),
|
|
|
|
("g", "get", "Get data"),
|
2023-11-17 22:54:25 +00:00
|
|
|
("#", "count", "Count"),
|
2023-11-03 23:42:11 +00:00
|
|
|
("/", "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,
|
2023-11-04 00:01:53 +00:00
|
|
|
fn.COALESCE(KimaiProject.name, "").alias("project_name"),
|
|
|
|
fn.COALESCE(KimaiCustomer.name, "").alias("customer_name"),
|
2023-11-03 23:42:11 +00:00
|
|
|
)
|
|
|
|
.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 "",
|
2023-11-04 00:01:53 +00:00
|
|
|
truncate(activity.project_name, 40),
|
2023-11-03 23:42:11 +00:00
|
|
|
activity.id,
|
2023-11-04 00:01:53 +00:00
|
|
|
truncate(activity.name, 40),
|
2023-11-03 23:42:11 +00:00
|
|
|
activity.visible,
|
2023-11-18 11:23:54 +00:00
|
|
|
"?",
|
2023-11-03 23:42:11 +00:00
|
|
|
]
|
|
|
|
for activity in activities
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2023-11-04 00:01:53 +00:00
|
|
|
self.table.sort(*self.sort)
|
2023-11-03 23:42:11 +00:00
|
|
|
|
|
|
|
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",
|
2023-11-17 22:54:25 +00:00
|
|
|
"times",
|
2023-11-03 23:42:11 +00:00
|
|
|
)
|
2023-11-04 00:01:53 +00:00
|
|
|
self.sort = (self.columns[1], self.columns[3])
|
2023-11-03 23:42:11 +00:00
|
|
|
self._refresh()
|
|
|
|
|
2023-11-17 22:54:25 +00:00
|
|
|
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))
|
|
|
|
|
2023-11-18 11:23:54 +00:00
|
|
|
self.table.update_cell_at(Coordinate(row_idx, 5), count)
|
2023-11-17 22:54:25 +00:00
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
|
2023-11-03 21:52:08 +00:00
|
|
|
class KimaiScreen(Screen):
|
|
|
|
BINDINGS = [
|
2023-11-03 23:42:11 +00:00
|
|
|
("c", "show_tab('customers')", "Customers"),
|
2023-11-03 21:52:08 +00:00
|
|
|
("p", "show_tab('projects')", "Projects"),
|
2023-11-03 23:42:11 +00:00
|
|
|
("a", "show_tab('activities')", "Activities"),
|
2023-11-03 21:52:08 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
SUB_TITLE = "Kimai"
|
|
|
|
|
|
|
|
def compose(self) -> ComposeResult:
|
|
|
|
yield Header()
|
2023-11-03 23:42:11 +00:00
|
|
|
with TabbedContent(initial="projects"):
|
|
|
|
with TabPane("Customers", id="customers"):
|
|
|
|
yield KimaiCustomerList()
|
|
|
|
with TabPane("Projects", id="projects"):
|
2023-11-03 21:52:08 +00:00
|
|
|
yield KimaiProjectList()
|
|
|
|
with TabPane("Activities", id="activities"):
|
2023-11-03 23:42:11 +00:00
|
|
|
yield KimaiActivityList()
|
2023-11-03 21:52:08 +00:00
|
|
|
yield Footer()
|
|
|
|
|
2023-11-03 23:42:11 +00:00
|
|
|
def on_mount(self) -> None:
|
|
|
|
self.query_one("TabbedContent Tabs").can_focus = False
|
|
|
|
self.query_one("#projects DataTable").focus()
|
|
|
|
|
2023-11-03 21:52:08 +00:00
|
|
|
def action_show_tab(self, tab: str) -> None:
|
|
|
|
"""Switch to a new tab."""
|
|
|
|
self.get_child_by_type(TabbedContent).active = tab
|
2023-11-03 23:42:11 +00:00
|
|
|
self.query_one(f"#{tab} DataTable").focus()
|