Add kimai "visible" field, moar kimai screens
This commit is contained in:
		@ -25,7 +25,7 @@ from .db import (
 | 
			
		||||
from .sync import sync
 | 
			
		||||
 | 
			
		||||
HAMSTER_DIR = Path.home() / ".local/share/hamster"
 | 
			
		||||
HAMSTER_FILE = HAMSTER_DIR / 'hamster.db'
 | 
			
		||||
HAMSTER_FILE = HAMSTER_DIR / "hamster.db"
 | 
			
		||||
 | 
			
		||||
db.init(HAMSTER_FILE)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -43,6 +43,7 @@ class HamsterFact(Model):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KimaiCustomer(Model):
 | 
			
		||||
    visible = BooleanField(default=True)
 | 
			
		||||
    name = CharField()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
@ -53,6 +54,7 @@ class KimaiCustomer(Model):
 | 
			
		||||
class KimaiProject(Model):
 | 
			
		||||
    name = CharField()
 | 
			
		||||
    customer = ForeignKeyField(KimaiCustomer, backref="projects")
 | 
			
		||||
    visible = BooleanField(default=True)
 | 
			
		||||
    allow_global_activities = BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
@ -63,6 +65,7 @@ class KimaiProject(Model):
 | 
			
		||||
class KimaiActivity(Model):
 | 
			
		||||
    name = CharField()
 | 
			
		||||
    project = ForeignKeyField(KimaiProject, backref="activities", null=True)
 | 
			
		||||
    visible = BooleanField(default=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        database = db
 | 
			
		||||
 | 
			
		||||
@ -44,24 +44,26 @@ class BaseAPI(object):
 | 
			
		||||
            setattr(self, key, value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class Customer(BaseAPI):
 | 
			
		||||
    def __init__(self, api, id, name):
 | 
			
		||||
        super().__init__(api, id=id, name=name)
 | 
			
		||||
    api: KimaiAPI = field(repr=False)
 | 
			
		||||
    id: int
 | 
			
		||||
    name: str
 | 
			
		||||
    visible: bool = field(default=True)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    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
 | 
			
		||||
    def get_by_id(api, id):
 | 
			
		||||
        for value in api.customers_json:
 | 
			
		||||
            if value["id"] == id:
 | 
			
		||||
                return Customer(api, value["id"], value["name"])
 | 
			
		||||
        for c in api.customers_json:
 | 
			
		||||
            if c["id"] == id:
 | 
			
		||||
                return Customer(api, c["id"], c["name"], c["visible"])
 | 
			
		||||
        raise NotFound()
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return f"Customer (id={self.id}, name={self.name})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass
 | 
			
		||||
class Project(BaseAPI):
 | 
			
		||||
@ -70,6 +72,7 @@ class Project(BaseAPI):
 | 
			
		||||
    name: str
 | 
			
		||||
    customer: Customer
 | 
			
		||||
    allow_global_activities: bool = field(default=True)
 | 
			
		||||
    visible: bool = field(default=True)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def list(api):
 | 
			
		||||
@ -80,20 +83,22 @@ class Project(BaseAPI):
 | 
			
		||||
                p["name"],
 | 
			
		||||
                Customer.get_by_id(api, p["customer"]),
 | 
			
		||||
                p["globalActivities"],
 | 
			
		||||
                p["visible"],
 | 
			
		||||
            )
 | 
			
		||||
            for p in api.projects_json
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_by_id(api, id, none=False):
 | 
			
		||||
        for value in api.projects_json:
 | 
			
		||||
            if value["id"] == id:
 | 
			
		||||
        for p in api.projects_json:
 | 
			
		||||
            if p["id"] == id:
 | 
			
		||||
                return Project(
 | 
			
		||||
                    api,
 | 
			
		||||
                    value["id"],
 | 
			
		||||
                    value["name"],
 | 
			
		||||
                    Customer.get_by_id(api, value["customer"]),
 | 
			
		||||
                    value["globalActivities"],
 | 
			
		||||
                    p["id"],
 | 
			
		||||
                    p["name"],
 | 
			
		||||
                    Customer.get_by_id(api, p["customer"]),
 | 
			
		||||
                    p["globalActivities"],
 | 
			
		||||
                    p["visible"],
 | 
			
		||||
                )
 | 
			
		||||
        if not none:
 | 
			
		||||
            raise NotFound()
 | 
			
		||||
@ -105,25 +110,31 @@ class Activity(BaseAPI):
 | 
			
		||||
    id: int
 | 
			
		||||
    name: str
 | 
			
		||||
    project: Project
 | 
			
		||||
    visible: bool = field(default=True)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def list(api):
 | 
			
		||||
        return [
 | 
			
		||||
            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
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_by_id(api, id, none=False):
 | 
			
		||||
        for value in api.activities_json:
 | 
			
		||||
            if value["id"] == id:
 | 
			
		||||
        for a in api.activities_json:
 | 
			
		||||
            if a["id"] == id:
 | 
			
		||||
                return Activity(
 | 
			
		||||
                    api,
 | 
			
		||||
                    value["id"],
 | 
			
		||||
                    value["name"],
 | 
			
		||||
                    Project.get_by_id(api, value["project"]),
 | 
			
		||||
                    a["id"],
 | 
			
		||||
                    a["name"],
 | 
			
		||||
                    Project.get_by_id(api, a["project"]),
 | 
			
		||||
                    a["visible"],
 | 
			
		||||
                )
 | 
			
		||||
        if not none:
 | 
			
		||||
            raise NotFound()
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,16 @@ from textual.coordinate import Coordinate
 | 
			
		||||
from textual.containers import Horizontal, Vertical
 | 
			
		||||
from textual.events import DescendantBlur
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -558,6 +567,11 @@ class HamsterScreen(Screen):
 | 
			
		||||
                yield ActivityList()
 | 
			
		||||
        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:
 | 
			
		||||
        """Switch to a new tab."""
 | 
			
		||||
        self.get_child_by_type(TabbedContent).active = tab
 | 
			
		||||
        self.query_one(f"#{tab} DataTable").focus()
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,10 @@ from ..db import (
 | 
			
		||||
from .list import ListPane
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KimaiCustomerList(ListPane):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KimaiProjectList(ListPane):
 | 
			
		||||
    BINDINGS = [
 | 
			
		||||
        ("s", "sort", "Sort"),
 | 
			
		||||
@ -24,9 +28,11 @@ class KimaiProjectList(ListPane):
 | 
			
		||||
        Binding(key="escape", action="cancelfilter", show=False),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def _refresh(self, filter_query=None):
 | 
			
		||||
    def _refresh(self):
 | 
			
		||||
        self.table.clear()
 | 
			
		||||
 | 
			
		||||
        filter_search = self.query_one("#filter #search").value
 | 
			
		||||
 | 
			
		||||
        projects = (
 | 
			
		||||
            KimaiProject.select(
 | 
			
		||||
                KimaiProject,
 | 
			
		||||
@ -40,10 +46,10 @@ class KimaiProjectList(ListPane):
 | 
			
		||||
            .group_by(KimaiProject)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if filter_query:
 | 
			
		||||
        if filter_search:
 | 
			
		||||
            projects = projects.where(
 | 
			
		||||
                KimaiProject.name.contains(filter_query)
 | 
			
		||||
                | KimaiCustomer.name.contains(filter_query)
 | 
			
		||||
                KimaiProject.name.contains(filter_search)
 | 
			
		||||
                | KimaiCustomer.name.contains(filter_search)
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        self.table.add_rows(
 | 
			
		||||
@ -54,6 +60,7 @@ class KimaiProjectList(ListPane):
 | 
			
		||||
                    project.id,
 | 
			
		||||
                    project.name,
 | 
			
		||||
                    project.activities_count,
 | 
			
		||||
                    project.visible,
 | 
			
		||||
                ]
 | 
			
		||||
                for project in projects
 | 
			
		||||
            ]
 | 
			
		||||
@ -69,29 +76,104 @@ class KimaiProjectList(ListPane):
 | 
			
		||||
        self.table = self.query_one(DataTable)
 | 
			
		||||
        self.table.cursor_type = "row"
 | 
			
		||||
        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._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):
 | 
			
		||||
    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="activities"):
 | 
			
		||||
            with TabPane("Categories", id="categories"):
 | 
			
		||||
        with TabbedContent(initial="projects"):
 | 
			
		||||
            with TabPane("Customers", id="customers"):
 | 
			
		||||
                yield KimaiCustomerList()
 | 
			
		||||
            with TabPane("Projects", id="projects"):
 | 
			
		||||
                yield KimaiProjectList()
 | 
			
		||||
            with TabPane("Activities", id="activities"):
 | 
			
		||||
                yield KimaiProjectList()
 | 
			
		||||
                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()
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,14 @@ def sync() -> None:
 | 
			
		||||
    customers = KimaiAPICustomer.list(api)
 | 
			
		||||
    with db.atomic():
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
    projects = KimaiAPIProject.list(api)
 | 
			
		||||
@ -34,6 +41,7 @@ def sync() -> None:
 | 
			
		||||
                    "name": project.name,
 | 
			
		||||
                    "customer_id": project.customer.id,
 | 
			
		||||
                    "allow_global_activities": project.allow_global_activities,
 | 
			
		||||
                    "visible": project.visible,
 | 
			
		||||
                }
 | 
			
		||||
                for project in projects
 | 
			
		||||
            ]
 | 
			
		||||
@ -46,7 +54,8 @@ def sync() -> None:
 | 
			
		||||
                {
 | 
			
		||||
                    "id": activity.id,
 | 
			
		||||
                    "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
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user