Add kimai "visible" field, moar kimai screens
This commit is contained in:
parent
d3c2da74e6
commit
fd28955da0
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user