diff --git a/time_logix.py b/time_logix.py index a47d77c..9017a93 100644 --- a/time_logix.py +++ b/time_logix.py @@ -11,6 +11,7 @@ from reportlab.platypus import Table, TableStyle, Paragraph from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch import sys +import os class TimeLogix: @@ -28,112 +29,201 @@ class TimeLogix: # --- App Data --- self.project_file = "projects.txt" + self.invoice_file = "invoice_number.txt" # File to store invoice number self.projects = self.load_projects() self.is_running = False self.start_time = None self.elapsed_time = 0 self.timer_id = None self.log_entries = [] - self.invoice_number = 1 + self.invoice_number = self.load_invoice_number() # Load invoice number self.company_name = "Your Company Name" self.company_address = "123 Main St, Anytown, USA" self.client_name = "Client Name" self.client_address = "Client Address" self.hourly_rate = 60.00 + self.csv_file = "working_sessions.csv" # --- Scrollable Frame --- - self.scrollable_frame = ctk.CTkScrollableFrame(self.root, width=450, height=600) # Consider making height adaptable + self.scrollable_frame = ctk.CTkScrollableFrame( + self.root, width=450, height=600 + ) # Consider making height adaptable self.scrollable_frame.pack(fill="both", expand=True, padx=10, pady=10) # --- UI Elements --- - self.task_label = ctk.CTkLabel(self.scrollable_frame, text="Task Description:", font=(self.font_family, self.font_size)) + self.task_label = ctk.CTkLabel( + self.scrollable_frame, + text="Task Description:", + font=(self.font_family, self.font_size), + ) self.task_label.pack(pady=(10, 2)) self.task_entry = ctk.CTkEntry(self.scrollable_frame, width=250) self.task_entry.pack(pady=(2, 10)) - self.project_label = ctk.CTkLabel(self.scrollable_frame, text="Project:", font=(self.font_family, self.font_size)) + self.project_label = ctk.CTkLabel( + self.scrollable_frame, + text="Project:", + font=(self.font_family, self.font_size), + ) self.project_label.pack(pady=(10, 2)) - self.project_combo = ctk.CTkComboBox(self.scrollable_frame, values=self.projects, width=250) + self.project_combo = ctk.CTkComboBox( + self.scrollable_frame, values=self.projects, width=250 + ) self.project_combo.pack(pady=(2, 10)) if self.projects: self.project_combo.set(self.projects[0]) - self.time_label = ctk.CTkLabel(self.scrollable_frame, text="00:00:00", font=(self.font_family, 36)) + self.time_label = ctk.CTkLabel( + self.scrollable_frame, text="00:00:00", font=(self.font_family, 36) + ) self.time_label.pack(pady=(15, 20)) - self.start_stop_button = ctk.CTkButton(self.scrollable_frame, text="Start", command=self.toggle_timer, corner_radius=8) + self.start_stop_button = ctk.CTkButton( + self.scrollable_frame, + text="Start", + command=self.toggle_timer, + corner_radius=8, + ) self.start_stop_button.pack(pady=(5, 20)) - self.log_label = ctk.CTkLabel(self.scrollable_frame, text="Log Entries:", font=(self.font_family, self.font_size)) + self.log_label = ctk.CTkLabel( + self.scrollable_frame, + text="Log Entries:", + font=(self.font_family, self.font_size), + ) self.log_label.pack(pady=(10, 2)) - self.log_text = ctk.CTkTextbox(self.scrollable_frame, width=400, height=100, font=(self.font_family, self.font_size)) - self.log_text.pack(pady=5, padx=10, fill='both', expand=True) + self.log_text = ctk.CTkTextbox( + self.scrollable_frame, + width=400, + height=100, + font=(self.font_family, self.font_size), + ) + self.log_text.pack(pady=5, padx=10, fill="both", expand=True) - self.new_project_label = ctk.CTkLabel(self.scrollable_frame, text="New Project:", font=(self.font_family, self.font_size)) + self.new_project_label = ctk.CTkLabel( + self.scrollable_frame, + text="New Project:", + font=(self.font_family, self.font_size), + ) self.new_project_label.pack(pady=(10, 2)) self.new_project_entry = ctk.CTkEntry(self.scrollable_frame, width=250) self.new_project_entry.pack(pady=(2, 10)) - self.add_project_button = ctk.CTkButton(self.scrollable_frame, text="Add Project", command=self.add_project, corner_radius=8) + self.add_project_button = ctk.CTkButton( + self.scrollable_frame, + text="Add Project", + command=self.add_project, + corner_radius=8, + ) self.add_project_button.pack(pady=(5, 15)) # --- Button Frame --- button_frame = ctk.CTkFrame(self.scrollable_frame) button_frame.pack(pady=(10, 15)) - self.export_csv_button = ctk.CTkButton(button_frame, text="Export to CSV", command=self.export_to_csv, corner_radius=8) - self.export_csv_button.pack(side="left", padx=5, pady=5) # Using side="left" for horizontal layout + self.export_csv_button = ctk.CTkButton( + button_frame, + text="Export to CSV", + command=self.export_to_csv, + corner_radius=8, + ) + self.export_csv_button.pack( + side="left", padx=5, pady=5 + ) # Using side="left" for horizontal layout - self.export_pdf_button = ctk.CTkButton(button_frame, text="Export to PDF", command=self.export_to_pdf, corner_radius=8) + self.export_pdf_button = ctk.CTkButton( + button_frame, + text="Export to PDF", + command=self.export_to_pdf, + corner_radius=8, + ) self.export_pdf_button.pack(side="left", padx=5, pady=5) - self.exit_button = ctk.CTkButton(button_frame, text="Exit", command=self.exit_app, corner_radius=8) + self.exit_button = ctk.CTkButton( + button_frame, text="Exit", command=self.exit_app, corner_radius=8 + ) self.exit_button.pack(side="left", padx=5, pady=5) - self.total_time_button = ctk.CTkButton(self.scrollable_frame, text="Calculate Total Time", command=self.calculate_total_time, corner_radius=8) + self.total_time_button = ctk.CTkButton( + self.scrollable_frame, + text="Calculate Total Time", + command=self.calculate_total_time, + corner_radius=8, + ) self.total_time_button.pack(pady=(5, 15)) - self.total_time_label = ctk.CTkLabel(self.scrollable_frame, text="Total Time: 00:00:00", font=(self.font_family, self.font_size)) + self.total_time_label = ctk.CTkLabel( + self.scrollable_frame, + text="Total Time: 00:00:00", + font=(self.font_family, self.font_size), + ) self.total_time_label.pack(pady=(5, 15)) # --- Settings UI --- - self.company_name_label = ctk.CTkLabel(self.scrollable_frame, text="Company Name:", font=(self.font_family, self.font_size)) + self.company_name_label = ctk.CTkLabel( + self.scrollable_frame, + text="Company Name:", + font=(self.font_family, self.font_size), + ) self.company_name_label.pack(pady=(10, 2)) self.company_name_entry = ctk.CTkEntry(self.scrollable_frame, width=250) self.company_name_entry.pack(pady=(2, 10)) - self.company_address_label = ctk.CTkLabel(self.scrollable_frame, text="Company Address:", font=(self.font_family, self.font_size)) + self.company_address_label = ctk.CTkLabel( + self.scrollable_frame, + text="Company Address:", + font=(self.font_family, self.font_size), + ) self.company_address_label.pack(pady=(10, 2)) self.company_address_entry = ctk.CTkEntry(self.scrollable_frame, width=250) self.company_address_entry.pack(pady=(2, 10)) - self.client_name_label = ctk.CTkLabel(self.scrollable_frame, text="Client Name:", font=(self.font_family, self.font_size)) + self.client_name_label = ctk.CTkLabel( + self.scrollable_frame, + text="Client Name:", + font=(self.font_family, self.font_size), + ) self.client_name_label.pack(pady=(10, 2)) self.client_name_entry = ctk.CTkEntry(self.scrollable_frame, width=250) self.client_name_entry.pack(pady=(2, 10)) - self.client_address_label = ctk.CTkLabel(self.scrollable_frame, text="Client Address:", font=(self.font_family, self.font_size)) + self.client_address_label = ctk.CTkLabel( + self.scrollable_frame, + text="Client Address:", + font=(self.font_family, self.font_size), + ) self.client_address_label.pack(pady=(10, 2)) self.client_address_entry = ctk.CTkEntry(self.scrollable_frame, width=250) self.client_address_entry.pack(pady=(2, 10)) - self.hourly_rate_label = ctk.CTkLabel(self.scrollable_frame, text="Hourly Rate:", font=(self.font_family, self.font_size)) + self.hourly_rate_label = ctk.CTkLabel( + self.scrollable_frame, + text="Hourly Rate:", + font=(self.font_family, self.font_size), + ) self.hourly_rate_label.pack(pady=(10, 2)) self.hourly_rate_entry = ctk.CTkEntry(self.scrollable_frame, width=100) self.hourly_rate_entry.pack(pady=(2, 10)) - self.update_settings_button = ctk.CTkButton(self.scrollable_frame, text="Update Settings", command=self.update_settings, corner_radius=8) + self.update_settings_button = ctk.CTkButton( + self.scrollable_frame, + text="Update Settings", + command=self.update_settings, + corner_radius=8, + ) self.update_settings_button.pack(pady=(15, 20)) + self.load_log_entries() + def load_projects(self): try: with open(self.project_file, "r") as f: @@ -150,6 +240,23 @@ class TimeLogix: except Exception as e: print(f"Error saving projects: {e}") + def load_invoice_number(self): + try: + with open(self.invoice_file, "r") as f: + return int(f.read().strip()) + except FileNotFoundError: + return 1 + except ValueError: + print("Invalid invoice number in file, resetting to 1.") + return 1 + + def save_invoice_number(self): + try: + with open(self.invoice_file, "w") as f: + f.write(str(self.invoice_number)) + except Exception as e: + print(f"Error saving invoice number: {e}") + def add_project(self): new_project = self.new_project_entry.get().strip() if new_project and new_project not in self.projects: @@ -229,12 +336,40 @@ class TimeLogix: self.log_text.insert(tk.END, f"Project: {entry['project']}\n") self.log_text.insert(tk.END, f"Start: {entry['start_time']}\n") self.log_text.insert(tk.END, f"End: {entry['end_time']}\n") - self.log_text.insert(tk.END, f"Duration: {entry['duration']} seconds\n") + self.log_text.insert( + tk.END, f"Duration: {entry['duration']} seconds\n" + ) self.log_text.insert(tk.END, "-" * 20 + "\n") + def load_log_entries(self): + if os.path.exists(self.csv_file): + try: + with open(self.csv_file, "r") as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + try: + row["duration"] = float(row["duration"]) + except ValueError: + # Try converting duration from hh:mm:ss format to seconds + try: + h, m, s = map(int, row["duration"].split(":")) + row["duration"] = h * 3600 + m * 60 + s + except ValueError: + print( + f"Invalid duration format: {row['duration']}. " + "Skipping this entry." + ) + continue # Skip this entry if the format is invalid + + self.log_entries.append(row) + self.update_log_display() + print("Loaded log entries from CSV successfully!") + except Exception as e: + print(f"Error loading log entries from CSV: {e}") + def export_to_csv(self): try: - with open("working_sessions.csv", "w", newline="") as csvfile: + with open(self.csv_file, "w", newline="") as csvfile: fieldnames = ["task", "project", "start_time", "end_time", "duration"] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() @@ -266,11 +401,19 @@ class TimeLogix: c.drawString(4.5 * inch, 7.1 * inch, f"Date: {current_date}") # --- Client Info --- + bill_to_y = 6.5 * inch # Starting y position for "Bill To:" + line_height = 0.2 * inch # Height for each line of text + c.setFont("Helvetica-Bold", 12) - c.drawString(inch, 6.5 * inch, "Bill To:") + c.drawString(inch, bill_to_y, "Bill To:") # "Bill To:" label + c.setFont("Helvetica", 10) - c.drawString(inch, 6.3 * inch, self.client_name) - c.drawString(inch, 6.1 * inch, self.client_address) + c.drawString( + inch, bill_to_y - line_height, self.client_name + ) # Client Name + c.drawString( + inch, bill_to_y - 2 * line_height, self.client_address + ) # Client Address # --- Table --- data = [["Task", "Project", "Hours", "Rate", "Total"]] @@ -330,6 +473,7 @@ class TimeLogix: c.save() print(f"Exported to PDF successfully as {filename}!") self.invoice_number += 1 + self.save_invoice_number() # Save the updated invoice number except Exception as e: print(f"Error exporting to PDF: {e}")