[AI] Tracking
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
53
src/time_tracker/ui/report_window.py
Normal file
53
src/time_tracker/ui/report_window.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user