diff --git a/src/time_tracker/app.py b/src/time_tracker/app.py index 793ddb7..98ca1da 100644 --- a/src/time_tracker/app.py +++ b/src/time_tracker/app.py @@ -26,6 +26,7 @@ class Application: self.is_idle: bool = False self._tracking_thread: Optional[threading.Thread] = None self._running = True + self.session_start_time: Optional[float] = None # Загружаем задачу if self.config.current_task_id: @@ -73,6 +74,7 @@ class Application: def _check_and_update_tracking(self): with self._lock: if not self.tracking_enabled or not self.current_task: + self.session_start_time = None return current_time = time.time() @@ -85,12 +87,17 @@ class Application: if current_time - self.last_activity > 60 or is_blocked: self.is_idle = True self.last_window_group = None + self.session_start_time = None else: self.is_idle = False if self.last_window_group != group_name: self.last_window_group = group_name self._add_work_detail(group_name, 5.0) + # Начинаем/продолжаем сессию + if self.session_start_time is None: + self.session_start_time = current_time + # Сохраняем при переходе в бездействие if not was_idle and self.is_idle: self.maybe_save_task() @@ -222,7 +229,11 @@ class Application: "detail": f"{window_info.title} | {window_info.process_name}" } - # === Остальные методы (без изменений, но с локом где нужно) === + def get_current_session_duration(self) -> float: + with self._lock: + if self.session_start_time is None: + return 0.0 + return time.time() - self.session_start_time def _load_or_create_config(self) -> TrackerConfig: if self.config_path.exists(): diff --git a/src/time_tracker/ui/overlay.py b/src/time_tracker/ui/overlay.py index f83ebbe..f544f07 100644 --- a/src/time_tracker/ui/overlay.py +++ b/src/time_tracker/ui/overlay.py @@ -12,10 +12,11 @@ if TYPE_CHECKING: class OverlayWindow: def __init__(self, app: "Application"): self.app = app + self.session_timer_label = None self.root = tk.Tk() self.root.protocol("WM_DELETE_WINDOW", self.on_close) self.root.title("Citrus Time Tracker") - self.root.geometry("320x180") + self.root.geometry("380x190") self.root.overrideredirect(True) # убираем заголовок self.root.attributes("-topmost", True) # всегда поверх self.root.attributes("-alpha", 0.5) # полупрозрачность по умолчанию @@ -48,9 +49,9 @@ class OverlayWindow: self.root.update_idletasks() screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() - x = (screen_width // 2) - (320 // 2) - y = (screen_height // 2) - (180 // 2) - self.root.geometry(f"320x180+{x}+{y}") + x = (screen_width // 2) - (380 // 2) + y = (screen_height // 2) - (190 // 2) + self.root.geometry(f"380x190+{x}+{y}") def _build_ui(self): # Главный фрейм с отступами @@ -69,6 +70,9 @@ class OverlayWindow: self.time_info_label = Label(main_frame, text="Времени нет", anchor="w", font=("TkDefaultFont", 9)) self.time_info_label.pack(anchor="w") + self.session_timer_label = Label(main_frame, text="0:00", font=("Arial", 16, "bold"), fg="green") + self.session_timer_label.pack(pady=(5, 0)) + # Текущее окно self.window_group_label = Label(main_frame, text="Неизвестное приложение", anchor="w", fg="gray") self.window_group_label.pack(anchor="w") @@ -89,6 +93,10 @@ class OverlayWindow: self.adjust_time_btn = Button(btn_frame, text="✏️ Время", command=self.on_adjust_time) self.adjust_time_btn.pack(side="left", fill="x", expand=True, padx=(2, 0)) + Button(btn_frame, text="📊 Отчёт", command=self.on_show_report).pack(side="left", fill="x", expand=True, padx=2) + + + self._update_display() def _update_display(self): @@ -127,7 +135,13 @@ class OverlayWindow: def _auto_update(self): self._update_display() - self.root.after(2000, self._auto_update) # обновляем каждые 2 сек + + # Обновляем таймер сессии каждую секунду + session_sec = self.app.get_current_session_duration() + session_str = format_seconds(session_sec) + self.session_timer_label.config(text=session_str) + + self.root.after(500, self._auto_update) # теперь каждую секунду # === Обработчики событий (пока без логики, только открытие окон) === @@ -146,6 +160,10 @@ class OverlayWindow: from .adjust_time_window import AdjustTimeWindow AdjustTimeWindow(self.root, self.app, self._update_display) + def on_show_report(self): + from .report_window import ReportWindow + ReportWindow(self.root, self.app) + # === UI-управление === def on_mouse_enter(self, event): diff --git a/src/time_tracker/ui/report_window.py b/src/time_tracker/ui/report_window.py new file mode 100644 index 0000000..10c6bb0 --- /dev/null +++ b/src/time_tracker/ui/report_window.py @@ -0,0 +1,53 @@ +import tkinter as tk +from tkinter import Toplevel, Text, Scrollbar, Button +from ..utils import format_seconds + + +class ReportWindow: + def __init__(self, parent, app: "Application"): + self.window = Toplevel(parent) + self.window.title("Отчёт по задаче") + self.window.geometry("500x400") + self.window.transient(parent) + self.window.grab_set() + + if not app.current_task: + tk.Label(self.window, text="Нет активной задачи", font=("Arial", 12)).pack(pady=20) + Button(self.window, text="Закрыть", command=self.window.destroy).pack() + return + + task = app.current_task + text_widget = Text(self.window, wrap="word", padx=10, pady=10) + scrollbar = Scrollbar(self.window, command=text_widget.yview) + text_widget.config(yscrollcommand=scrollbar.set) + + # Собираем отчёт + report = f"Задача: {task.task_id}\n" + if task.project_id: + report += f"Проект: {task.project_id}\n" + report += f"Всего затрачено: {format_seconds(task.total_worked_seconds)}\n\n" + + # Группировка по группам + group_totals = {} + for session in task.sessions: + for detail in session.details: + group = detail.group_name + group_totals[group] = group_totals.get(group, 0) + detail.duration_seconds + + report += "Распределение по группам:\n" + for group, total in sorted(group_totals.items(), key=lambda x: -x[1]): + report += f" • {group}: {format_seconds(total)}\n" + + report += "\nПодробные сессии:\n" + for i, session in enumerate(task.sessions, 1): + report += f"\nСессия {i} ({session.start_time.strftime('%H:%M')}–{session.end_time.strftime('%H:%M')}):\n" + for detail in session.details: + report += f" - {detail.group_name}: {format_seconds(detail.duration_seconds)}\n" + + text_widget.insert("1.0", report) + text_widget.config(state="disabled") + + text_widget.pack(side="left", fill="both", expand=True) + scrollbar.pack(side="right", fill="y") + + Button(self.window, text="Закрыть", command=self.window.destroy).pack(pady=10) \ No newline at end of file