diff --git a/CLAUDE.md b/CLAUDE.md index 866adbd..5238cd6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -97,4 +97,10 @@ The application requires: - The system enforces task constraints within work desktops - switching to a non-work task automatically reverts to the designated work task - Task files are monitored for changes to enable automatic context switching - Web interface runs on port 10000 by default -- Core daemon sleeps for 2 seconds between workspace checks \ No newline at end of file +- Core daemon sleeps for 2 seconds between workspace checks + +## Tool Development Guidelines + +- This is a personal tool, not professionally developed +- reuse as it is as much as possible unless refactor is required explicitly, suggest improvements +- Only modify existing code if it absolutely doesn't make sense to add a new flow \ No newline at end of file diff --git a/dmapp/dmweb/dm.py b/dmapp/dmweb/dm.py index a041307..313f249 100644 --- a/dmapp/dmweb/dm.py +++ b/dmapp/dmweb/dm.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from flask import Blueprint, render_template -from .get_period_times import get_period_totals, task_or_none, timezone +from .get_period_times import get_period_totals, task_or_none, timezone, get_work_project_tasks, get_work_period_totals dmbp = Blueprint("deskmeter", __name__, url_prefix="/", template_folder="templates") @@ -66,6 +66,20 @@ def period(start, end): return render_template("pages.html", rows=rows) +@dmbp.route("/work") +def work(): + """ + Show total time used per work project for today + """ + start = datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone) + end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) + + rows = get_work_period_totals(start, end) + print(rows) + + return render_template("main.html", rows=rows) + + @dmbp.route("/totals") @dmbp.route("/totals/") def totals(task=None): diff --git a/dmapp/dmweb/dmcal.py b/dmapp/dmweb/dmcal.py index 45f1b27..24d41fd 100644 --- a/dmapp/dmweb/dmcal.py +++ b/dmapp/dmweb/dmcal.py @@ -12,13 +12,17 @@ from .get_period_times import ( task_file, task_or_none, timezone, + get_work_project_tasks, + get_work_period_totals, ) + class DMHTMLCalendar(calendar.HTMLCalendar): - # def formatmonth(self, theyear, themonth, withyear=True): - # self.dmmonth = themonth - # super().formatmonth(self, theyear, themonth) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.use_work_projects = False + self.task = None def setcalmonth(self, month): self.dmmonth = month @@ -29,6 +33,9 @@ class DMHTMLCalendar(calendar.HTMLCalendar): def settask(self, task): self.task = task + def set_work_projects_mode(self, enabled=True): + self.use_work_projects = enabled + def oneday(self, month, day): current_year = datetime.today().year start = datetime(self.dmyear, month, day).replace( @@ -38,7 +45,10 @@ class DMHTMLCalendar(calendar.HTMLCalendar): hour=23, minute=59, second=59, tzinfo=timezone ) - rows = get_period_totals(start, end, self.task) + if self.use_work_projects: + rows = get_work_period_totals(start, end) + else: + rows = get_period_totals(start, end, self.task) returnstr = "" for row in rows: @@ -75,7 +85,10 @@ class DMHTMLCalendar(calendar.HTMLCalendar): hour=23, minute=59, second=59, tzinfo=timezone ) - rows = get_period_totals(start, end, self.task) + if self.use_work_projects: + rows = get_work_period_totals(start, end) + else: + rows = get_period_totals(start, end, self.task) print(rows) @@ -117,6 +130,28 @@ class DMHTMLCalendar(calendar.HTMLCalendar): ) +@dmbp.route("/workmonth") +@dmbp.route("/workmonth/") +@dmbp.route("/workmonth//") +def workmonth(month=None, year=None): + usemonth = datetime.today().month + useyear = datetime.today().year + + if month: + usemonth = month + + if year: + useyear = year + + cal = DMHTMLCalendar(calendar.SATURDAY) + + cal.set_work_projects_mode(True) + cal.setcalmonth(usemonth) + cal.setcalyear(useyear) + + return render_template("calendar.html", content=cal.formatmonth(useyear, usemonth)) + + @dmbp.route("/month") @dmbp.route("/month/") @dmbp.route("/month//") diff --git a/dmapp/dmweb/get_period_times.py b/dmapp/dmweb/get_period_times.py index c64b51f..61d27a3 100644 --- a/dmapp/dmweb/get_period_times.py +++ b/dmapp/dmweb/get_period_times.py @@ -11,6 +11,7 @@ utctz = ZoneInfo("UTC") client = MongoClient() db = client.deskmeter switches = db.switch +tasks = db.task task_file = "/home/mariano/LETRAS/adm/task/main" @@ -61,6 +62,70 @@ def read_and_extract(file_path): return None +def get_work_projects(): + """Get dict of work projects with their task IDs.""" + work_tasks = list( + tasks.find( + {"path": {"$regex": "^work/"}, "task_id": {"$exists": True}}, + {"path": 1, "task_id": 1, "_id": 0}, + ) + ) + + projects = {} + for task in work_tasks: + # Extract project name from path like "work/cal" -> "cal" + path_parts = task["path"].split("/") + if len(path_parts) >= 2: + project_name = path_parts[1] + if project_name not in projects: + projects[project_name] = [] + projects[project_name].append(task["task_id"]) + + return projects + + +def get_work_project_tasks(): + """Get comma-separated string of all task IDs under work/* paths.""" + projects = get_work_projects() + all_task_ids = [] + for task_ids in projects.values(): + all_task_ids.extend(task_ids) + return ",".join(all_task_ids) if all_task_ids else None + + +def get_work_period_totals(start, end): + """Get period totals grouped by work project.""" + projects = get_work_projects() + combined_rows = [] + + for project_name, task_ids in projects.items(): + if not task_ids: + continue + + task_string = ",".join(task_ids) + rows = get_period_totals(start, end, task_string) + + # Sum up all time for this project (looking for "Work" workspace) + total_seconds = 0 + for row in rows: + if row["ws"] == "Work": + # Convert time string back to seconds to sum properly + time_parts = row["total"].split(":") + if len(time_parts) == 3: + hours, minutes, seconds = map(int, time_parts) + total_seconds += hours * 3600 + minutes * 60 + seconds + + if total_seconds > 0: + combined_rows.append({ + "ws": project_name, + "total": convert_seconds(total_seconds) + }) + + # Sort by project name for consistency + combined_rows.sort(key=lambda x: x["ws"]) + return combined_rows + + def get_period_totals(start, end, task=None): task_query = {"$in": task.split(",")} if task else {}