Add kimai "visible" field, moar kimai screens

This commit is contained in:
3wc 2023-11-03 23:42:11 +00:00
parent d3c2da74e6
commit fd28955da0
6 changed files with 152 additions and 33 deletions

View File

@ -25,7 +25,7 @@ from .db import (
from .sync import sync from .sync import sync
HAMSTER_DIR = Path.home() / ".local/share/hamster" HAMSTER_DIR = Path.home() / ".local/share/hamster"
HAMSTER_FILE = HAMSTER_DIR / 'hamster.db' HAMSTER_FILE = HAMSTER_DIR / "hamster.db"
db.init(HAMSTER_FILE) db.init(HAMSTER_FILE)

View File

@ -43,6 +43,7 @@ class HamsterFact(Model):
class KimaiCustomer(Model): class KimaiCustomer(Model):
visible = BooleanField(default=True)
name = CharField() name = CharField()
class Meta: class Meta:
@ -53,6 +54,7 @@ class KimaiCustomer(Model):
class KimaiProject(Model): class KimaiProject(Model):
name = CharField() name = CharField()
customer = ForeignKeyField(KimaiCustomer, backref="projects") customer = ForeignKeyField(KimaiCustomer, backref="projects")
visible = BooleanField(default=True)
allow_global_activities = BooleanField(default=True) allow_global_activities = BooleanField(default=True)
class Meta: class Meta:
@ -63,6 +65,7 @@ class KimaiProject(Model):
class KimaiActivity(Model): class KimaiActivity(Model):
name = CharField() name = CharField()
project = ForeignKeyField(KimaiProject, backref="activities", null=True) project = ForeignKeyField(KimaiProject, backref="activities", null=True)
visible = BooleanField(default=True)
class Meta: class Meta:
database = db database = db

View File

@ -44,24 +44,26 @@ class BaseAPI(object):
setattr(self, key, value) setattr(self, key, value)
@dataclass
class Customer(BaseAPI): class Customer(BaseAPI):
def __init__(self, api, id, name): api: KimaiAPI = field(repr=False)
super().__init__(api, id=id, name=name) id: int
name: str
visible: bool = field(default=True)
@staticmethod @staticmethod
def list(api): def list(api):
return [Customer(api, c["id"], c["name"]) for c in api.customers_json] return [
Customer(api, c["id"], c["name"], c["visible"]) for c in api.customers_json
]
@staticmethod @staticmethod
def get_by_id(api, id): def get_by_id(api, id):
for value in api.customers_json: for c in api.customers_json:
if value["id"] == id: if c["id"] == id:
return Customer(api, value["id"], value["name"]) return Customer(api, c["id"], c["name"], c["visible"])
raise NotFound() raise NotFound()
def __repr__(self):
return f"Customer (id={self.id}, name={self.name})"
@dataclass @dataclass
class Project(BaseAPI): class Project(BaseAPI):
@ -70,6 +72,7 @@ class Project(BaseAPI):
name: str name: str
customer: Customer customer: Customer
allow_global_activities: bool = field(default=True) allow_global_activities: bool = field(default=True)
visible: bool = field(default=True)
@staticmethod @staticmethod
def list(api): def list(api):
@ -80,20 +83,22 @@ class Project(BaseAPI):
p["name"], p["name"],
Customer.get_by_id(api, p["customer"]), Customer.get_by_id(api, p["customer"]),
p["globalActivities"], p["globalActivities"],
p["visible"],
) )
for p in api.projects_json for p in api.projects_json
] ]
@staticmethod @staticmethod
def get_by_id(api, id, none=False): def get_by_id(api, id, none=False):
for value in api.projects_json: for p in api.projects_json:
if value["id"] == id: if p["id"] == id:
return Project( return Project(
api, api,
value["id"], p["id"],
value["name"], p["name"],
Customer.get_by_id(api, value["customer"]), Customer.get_by_id(api, p["customer"]),
value["globalActivities"], p["globalActivities"],
p["visible"],
) )
if not none: if not none:
raise NotFound() raise NotFound()
@ -105,25 +110,31 @@ class Activity(BaseAPI):
id: int id: int
name: str name: str
project: Project project: Project
visible: bool = field(default=True)
@staticmethod @staticmethod
def list(api): def list(api):
return [ return [
Activity( Activity(
api, a["id"], a["name"], Project.get_by_id(api, a["project"], none=True) api,
a["id"],
a["name"],
Project.get_by_id(api, a["project"], none=True),
a["visible"],
) )
for a in api.activities_json for a in api.activities_json
] ]
@staticmethod @staticmethod
def get_by_id(api, id, none=False): def get_by_id(api, id, none=False):
for value in api.activities_json: for a in api.activities_json:
if value["id"] == id: if a["id"] == id:
return Activity( return Activity(
api, api,
value["id"], a["id"],
value["name"], a["name"],
Project.get_by_id(api, value["project"]), Project.get_by_id(api, a["project"]),
a["visible"],
) )
if not none: if not none:
raise NotFound() raise NotFound()

View File

@ -7,7 +7,16 @@ from textual.coordinate import Coordinate
from textual.containers import Horizontal, Vertical from textual.containers import Horizontal, Vertical
from textual.events import DescendantBlur from textual.events import DescendantBlur
from textual.screen import Screen, ModalScreen from textual.screen import Screen, ModalScreen
from textual.widgets import Header, Footer, DataTable, Input, Label, Checkbox, TabbedContent, TabPane from textual.widgets import (
Header,
Footer,
DataTable,
Input,
Label,
Checkbox,
TabbedContent,
TabPane,
)
from peewee import fn, JOIN from peewee import fn, JOIN
@ -558,6 +567,11 @@ class HamsterScreen(Screen):
yield ActivityList() yield ActivityList()
yield Footer() yield Footer()
def on_mount(self) -> None:
self.query_one("TabbedContent Tabs").can_focus = False
self.query_one("#activities DataTable").focus()
def action_show_tab(self, tab: str) -> None: def action_show_tab(self, tab: str) -> None:
"""Switch to a new tab.""" """Switch to a new tab."""
self.get_child_by_type(TabbedContent).active = tab self.get_child_by_type(TabbedContent).active = tab
self.query_one(f"#{tab} DataTable").focus()

View File

@ -15,6 +15,10 @@ from ..db import (
from .list import ListPane from .list import ListPane
class KimaiCustomerList(ListPane):
pass
class KimaiProjectList(ListPane): class KimaiProjectList(ListPane):
BINDINGS = [ BINDINGS = [
("s", "sort", "Sort"), ("s", "sort", "Sort"),
@ -24,9 +28,11 @@ class KimaiProjectList(ListPane):
Binding(key="escape", action="cancelfilter", show=False), Binding(key="escape", action="cancelfilter", show=False),
] ]
def _refresh(self, filter_query=None): def _refresh(self):
self.table.clear() self.table.clear()
filter_search = self.query_one("#filter #search").value
projects = ( projects = (
KimaiProject.select( KimaiProject.select(
KimaiProject, KimaiProject,
@ -40,10 +46,10 @@ class KimaiProjectList(ListPane):
.group_by(KimaiProject) .group_by(KimaiProject)
) )
if filter_query: if filter_search:
projects = projects.where( projects = projects.where(
KimaiProject.name.contains(filter_query) KimaiProject.name.contains(filter_search)
| KimaiCustomer.name.contains(filter_query) | KimaiCustomer.name.contains(filter_search)
) )
self.table.add_rows( self.table.add_rows(
@ -54,6 +60,7 @@ class KimaiProjectList(ListPane):
project.id, project.id,
project.name, project.name,
project.activities_count, project.activities_count,
project.visible,
] ]
for project in projects for project in projects
] ]
@ -69,29 +76,104 @@ class KimaiProjectList(ListPane):
self.table = self.query_one(DataTable) self.table = self.query_one(DataTable)
self.table.cursor_type = "row" self.table.cursor_type = "row"
self.columns = self.table.add_columns( self.columns = self.table.add_columns(
"customer id", "customer", "project id", "project", "activities" "customer id", "customer", "project id", "project", "activities", "visible"
) )
# self.sort = (self.columns[1], self.columns[3]) # self.sort = (self.columns[1], self.columns[3])
self.sort = self.columns[1] self.sort = self.columns[1]
self._refresh() self._refresh()
class KimaiActivityList(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
activities = (
KimaiActivity.select(
KimaiActivity,
fn.COALESCE(KimaiProject.name, "None").alias("project_name"),
fn.COALESCE(KimaiCustomer.name, "None").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 "",
activity.project_name,
activity.id,
activity.name,
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",
)
# self.sort = (self.columns[1], self.columns[3])
self.sort = self.columns[3]
self._refresh()
class KimaiScreen(Screen): class KimaiScreen(Screen):
BINDINGS = [ BINDINGS = [
("c", "show_tab('customers')", "Customers"),
("p", "show_tab('projects')", "Projects"), ("p", "show_tab('projects')", "Projects"),
("a", "show_tab('activities')", "Activities"),
] ]
SUB_TITLE = "Kimai" SUB_TITLE = "Kimai"
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
with TabbedContent(initial="activities"): with TabbedContent(initial="projects"):
with TabPane("Categories", id="categories"): with TabPane("Customers", id="customers"):
yield KimaiCustomerList()
with TabPane("Projects", id="projects"):
yield KimaiProjectList() yield KimaiProjectList()
with TabPane("Activities", id="activities"): with TabPane("Activities", id="activities"):
yield KimaiProjectList() yield KimaiActivityList()
yield Footer() 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: def action_show_tab(self, tab: str) -> None:
"""Switch to a new tab.""" """Switch to a new tab."""
self.get_child_by_type(TabbedContent).active = tab self.get_child_by_type(TabbedContent).active = tab
self.query_one(f"#{tab} DataTable").focus()

View File

@ -22,7 +22,14 @@ def sync() -> None:
customers = KimaiAPICustomer.list(api) customers = KimaiAPICustomer.list(api)
with db.atomic(): with db.atomic():
KimaiCustomer.insert_many( KimaiCustomer.insert_many(
[{"id": customer.id, "name": customer.name} for customer in customers] [
{
"id": customer.id,
"name": customer.name,
"visible": customer.visible,
}
for customer in customers
]
).execute() ).execute()
projects = KimaiAPIProject.list(api) projects = KimaiAPIProject.list(api)
@ -34,6 +41,7 @@ def sync() -> None:
"name": project.name, "name": project.name,
"customer_id": project.customer.id, "customer_id": project.customer.id,
"allow_global_activities": project.allow_global_activities, "allow_global_activities": project.allow_global_activities,
"visible": project.visible,
} }
for project in projects for project in projects
] ]
@ -46,7 +54,8 @@ def sync() -> None:
{ {
"id": activity.id, "id": activity.id,
"name": activity.name, "name": activity.name,
"project_id": (activity.project and activity.project.id or None), "project_id": (activity.project.id if activity.project else None),
"visible": activity.visible,
} }
for activity in activities for activity in activities
] ]