time_logix/time_logix.py
Blake Ridgway cc53fe9c41 (feat): Implemented CSV loading/export, and PDF export using reportlab.
Added work description entry, total hours calculation/display.
Implemented CSV loading/export, and PDF export using reportlab.
2025-03-20 19:37:42 -05:00

231 lines
9.3 KiB
Python

import tkinter as tk
from tkinter import messagebox
from datetime import datetime, timedelta
import csv
import os
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
from reportlab.lib import colors
from reportlab.lib.units import inch
class TimeLogix(tk.Tk):
def __init__(self):
super().__init__()
self.title("TimeLogix")
self.geometry("500x500")
self.start_time = None
self.tracking = False
self.sessions = []
self.total_hours = 0
self.create_widgets()
self.load_sessions_from_csv()
self.update_total_hours()
def create_widgets(self):
self.status_label = tk.Label(self, text="Status: Not Tracking", font=("Helvetica", 12))
self.status_label.pack(pady=5)
self.start_button = tk.Button(
self, text="Start Tracking", width=20, command=self.start_tracking
)
self.start_button.pack(pady=2)
self.stop_button = tk.Button(
self, text="Stop Tracking", width=20, command=self.stop_tracking, state=tk.DISABLED
)
self.stop_button.pack(pady=2)
self.description_label = tk.Label(self, text="Work Description:")
self.description_label.pack(pady=1)
self.description_entry = tk.Text(self, height=3, width=40)
self.description_entry.pack(pady=1)
self.export_csv_button = tk.Button(
self, text="Export to CSV", width=20, command=self.export_sessions_csv
)
self.export_csv_button.pack(pady=2)
self.export_pdf_button = tk.Button(
self, text="Export to PDF", width=20, command=self.export_sessions_pdf
)
self.export_pdf_button.pack(pady=2)
self.total_hours_label = tk.Label(self, text="Total Hours Worked: 0.00", font=("Helvetica", 12))
self.total_hours_label.pack(pady=5)
self.log_text = tk.Text(self, height=10, state=tk.DISABLED)
self.log_text.pack(pady=5, padx=10, fill='both', expand=True)
def log_message(self, message):
if hasattr(self, 'log_text'):
self.log_text.configure(state=tk.NORMAL)
self.log_text.insert(tk.END, f"{message}\n")
self.log_text.configure(state=tk.DISABLED)
self.log_text.see(tk.END)
def start_tracking(self):
if self.tracking:
messagebox.showwarning("Warning", "Already tracking!")
return
self.start_time = datetime.now()
self.tracking = True
self.status_label.config(
text=f"Status: Tracking started at {self.start_time.strftime('%H:%M:%S')}"
)
self.log_message(f"Started at: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
def stop_tracking(self):
if not self.tracking:
messagebox.showwarning("Warning", "Not currently tracking!")
return
end_time = datetime.now()
duration = end_time - self.start_time
description = self.description_entry.get("1.0", tk.END).strip()
self.sessions.append((self.start_time, end_time, duration, description))
self.tracking = False
self.status_label.config(text="Status: Not Tracking")
decimal_hours = self.get_decimal_hours(duration)
self.log_message(
f"Stopped at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}, Duration: {self.format_duration(duration)} "
f"({decimal_hours:.2f} hours), Description: {description}"
)
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.description_entry.delete("1.0", tk.END)
self.update_total_hours()
def format_duration(self, duration):
total_seconds = int(duration.total_seconds())
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
return f"{hours}:{minutes:02d}:{seconds:02d}"
def get_decimal_hours(self, duration):
total_hours = duration.total_seconds() / 3600
return total_hours
def export_sessions_csv(self):
if not self.sessions:
messagebox.showinfo("Info", "No sessions to export.")
return
filename = "working_sessions.csv"
try:
with open(filename, mode="w", newline="") as csvfile:
writer = csv.writer(csvfile)
writer.writerow([
"Start Time", "End Time",
"Duration (H:MM:SS)", "Decimal Hours", "Description"
])
for start, end, duration, description in self.sessions:
writer.writerow([
start.strftime("%Y-%m-%d %H:%M:%S"),
end.strftime("%Y-%m-%d %H:%M:%S"),
self.format_duration(duration),
f"{self.get_decimal_hours(duration):.2f}",
description
])
writer.writerow(["", "", "", "Total Hours", f"{self.total_hours:.2f}"])
messagebox.showinfo("Export Successful", f"Sessions exported to {filename}")
except Exception as e:
messagebox.showerror("Error", f"Failed to export: {e}")
def export_sessions_pdf(self):
if not self.sessions:
messagebox.showinfo("Info", "No sessions to export.")
return
filename = "working_sessions.pdf"
try:
doc = SimpleDocTemplate(filename, pagesize=letter)
elements = []
data = [
["Start Time", "End Time", "Duration (H:MM:SS)", "Decimal Hours", "Description"]
]
for start, end, duration, description in self.sessions:
data.append([
start.strftime("%Y-%m-%d %H:%M:%S"),
end.strftime("%Y-%m-%d %H:%M:%S"),
self.format_duration(duration),
f"{self.get_decimal_hours(duration):.2f}",
description
])
data.append(["", "", "", "Total Hours", f"{self.total_hours:.2f}"])
table = Table(data)
style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black)
])
table.setStyle(style)
elements.append(table)
doc.build(elements)
messagebox.showinfo("Export Successful", f"Sessions exported to {filename}")
except Exception as e:
messagebox.showerror("Error", f"Failed to export: {e}")
def load_sessions_from_csv(self):
filename = "working_sessions.csv"
if os.path.exists(filename):
try:
with open(filename, mode="r") as csvfile:
reader = csv.reader(csvfile)
header = next(reader, None)
for row in reader:
if len(row) == 5:
start_time_str, end_time_str, duration_str, decimal_hours_str, description = row
elif len(row) == 4:
start_time_str, end_time_str, duration_str, decimal_hours_str = row
description = ""
else:
print(f"Skipping row with unexpected number of columns: {len(row)}")
continue
try:
start_time = datetime.strptime(start_time_str, "%Y-%m-%d %H:%M:%S")
end_time = datetime.strptime(end_time_str, "%Y-%m-%d %H:%M:%S")
duration = self.parse_duration(duration_str)
self.sessions.append((start_time, end_time, duration, description))
self.log_message(
f"Loaded: {start_time.strftime('%Y-%m-%d %H:%M:%S')} - {end_time.strftime('%Y-%m-%d %H:%M:%S')}, "
f"Duration: {duration_str}, Description: {description}"
)
except ValueError as ve:
print(f"Skipping row due to parsing error: {ve}")
except FileNotFoundError:
messagebox.showinfo("Info", "No CSV file found. Starting with a new session.")
except Exception as e:
messagebox.showerror("Error", f"Failed to load sessions from CSV: {e}")
def parse_duration(self, duration_str):
hours, minutes, seconds = map(int, duration_str.split(':'))
return timedelta(hours=hours, minutes=minutes, seconds=seconds)
def update_total_hours(self):
total_duration = timedelta()
for start, end, duration, description in self.sessions:
total_duration += duration
self.total_hours = total_duration.total_seconds() / 3600
self.total_hours_label.config(text=f"Total Hours Worked: {self.total_hours:.2f}")
if __name__ == "__main__":
app = TimeLogix()
app.mainloop()