feat: add status bar to UI for improved user feedback and information display

This commit is contained in:
William Valentin
2025-08-02 10:31:17 -07:00
parent b7a22524d7
commit fdcc210fc4
2 changed files with 175 additions and 26 deletions
+49 -2
View File
@@ -108,7 +108,7 @@ class MedTrackerApp:
self.root.grid_columnconfigure(0, weight=1)
# Configure main frame grid for scaling
for i in range(2):
for i in range(3): # Changed from 2 to 3 to accommodate status bar
main_frame.grid_rowconfigure(i, weight=1 if i == 1 else 0)
main_frame.grid_columnconfigure(i, weight=3 if i == 1 else 1)
logger.debug("Main frame and root grid configured for scaling.")
@@ -155,9 +155,15 @@ class MedTrackerApp:
self.tree: ttk.Treeview = table_ui["tree"]
self.tree.bind("<Double-1>", self.handle_double_click)
# --- Create Status Bar ---
self.status_bar = self.ui_manager.create_status_bar(main_frame)
# Load data
self.refresh_data_display()
# Initialize status bar with ready message
self.ui_manager.update_status("Application ready", "info")
def _setup_menu(self) -> None:
"""Set up the menu bar."""
menubar = tk.Menu(self.root)
@@ -182,22 +188,29 @@ class MedTrackerApp:
def _open_export_window(self) -> None:
"""Open the export window."""
self.ui_manager.update_status("Opening export window", "info")
ExportWindow(self.root, self.export_manager)
def _open_pathology_manager(self) -> None:
"""Open the pathology management window."""
self.ui_manager.update_status("Opening pathology manager", "info")
PathologyManagementWindow(
self.root, self.pathology_manager, self._refresh_ui_after_config_change
)
def _open_medicine_manager(self) -> None:
"""Open the medicine management window."""
self.ui_manager.update_status("Opening medicine manager", "info")
MedicineManagementWindow(
self.root, self.medicine_manager, self._refresh_ui_after_config_change
)
def _refresh_ui_after_config_change(self) -> None:
"""Refresh UI components after pathology or medicine configuration changes."""
self.ui_manager.update_status(
"Refreshing UI after configuration change", "info"
)
# Clear caches in optimized data manager
if hasattr(self.data_manager, "_invalidate_cache"):
self.data_manager._invalidate_cache()
@@ -238,14 +251,22 @@ class MedTrackerApp:
# Refresh data display
self.refresh_data_display()
# Update status to show completion
self.ui_manager.update_status("UI refreshed successfully", "success")
def handle_double_click(self, event: tk.Event) -> None:
"""Handle double-click event to edit an entry."""
logger.debug("Double-click event triggered on treeview.")
if len(self.tree.get_children()) > 0:
item_id = self.tree.selection()[0]
item_values = self.tree.item(item_id, "values")
self.ui_manager.update_status(
f"Opening entry for {item_values[0]} for editing", "info"
)
logger.debug(f"Editing item_id={item_id}, values={item_values}")
self._create_edit_window(item_id, item_values)
else:
self.ui_manager.update_status("No entries to edit", "warning")
def _create_edit_window(self, item_id: str, values: tuple[str, ...]) -> None:
"""Create a new Toplevel window for editing an entry."""
@@ -347,8 +368,10 @@ class MedTrackerApp:
values.append(note)
self.ui_manager.update_status("Saving changes...", "info")
if self.data_manager.update_entry(original_date, values):
edit_win.destroy()
self.ui_manager.update_status("Entry updated successfully!", "success")
messagebox.showinfo(
"Success", "Entry updated successfully!", parent=self.root
)
@@ -358,6 +381,7 @@ class MedTrackerApp:
# Check if it's a duplicate date issue
df = self.data_manager.load_data()
if original_date != date and not df.empty and date in df["date"].values:
self.ui_manager.update_status("Duplicate date found", "error")
messagebox.showerror(
"Error",
f"An entry for date '{date}' already exists. "
@@ -365,6 +389,7 @@ class MedTrackerApp:
parent=edit_win,
)
else:
self.ui_manager.update_status("Failed to save changes", "error")
messagebox.showerror("Error", "Failed to save changes", parent=edit_win)
def handle_window_closing(self) -> None:
@@ -409,10 +434,13 @@ class MedTrackerApp:
# Check if date is empty
if not self.date_var.get().strip():
self.ui_manager.update_status("Please enter a date", "error")
messagebox.showerror("Error", "Please enter a date.", parent=self.root)
return
self.ui_manager.update_status("Adding new entry...", "info")
if self.data_manager.add_entry(entry):
self.ui_manager.update_status("Entry added successfully!", "success")
messagebox.showinfo(
"Success", "Entry added successfully!", parent=self.root
)
@@ -422,6 +450,7 @@ class MedTrackerApp:
# Check if it's a duplicate date by trying to load existing data
df = self.data_manager.load_data()
if not df.empty and self.date_var.get() in df["date"].values:
self.ui_manager.update_status("Duplicate entry found", "error")
messagebox.showerror(
"Error",
f"An entry for date '{self.date_var.get()}' already exists. "
@@ -429,6 +458,7 @@ class MedTrackerApp:
parent=self.root,
)
else:
self.ui_manager.update_status("Failed to add entry", "error")
messagebox.showerror("Error", "Failed to add entry", parent=self.root)
def _delete_entry(self, edit_win: tk.Toplevel, item_id: str) -> None:
@@ -443,13 +473,16 @@ class MedTrackerApp:
date: str = self.tree.item(item_id, "values")[0]
logger.debug(f"Deleting entry with date={date}")
self.ui_manager.update_status("Deleting entry...", "info")
if self.data_manager.delete_entry(date):
edit_win.destroy()
self.ui_manager.update_status("Entry deleted successfully!", "success")
messagebox.showinfo(
"Success", "Entry deleted successfully!", parent=self.root
)
self.refresh_data_display()
else:
self.ui_manager.update_status("Failed to delete entry", "error")
messagebox.showerror("Error", "Failed to delete entry", parent=edit_win)
def _clear_entries(self) -> None:
@@ -471,12 +504,14 @@ class MedTrackerApp:
if children:
self.tree.delete(*children)
try:
# Load data from the CSV file
df: pd.DataFrame = self.data_manager.load_data()
# Update the treeview with the data
if not df.empty:
# Build display columns dynamically (exclude dose columns for table view)
# Build display columns dynamically
# (exclude dose columns for table view)
display_columns = ["date"]
# Add pathology columns
@@ -504,6 +539,18 @@ class MedTrackerApp:
# Update the graph
self.graph_manager.update_graph(df)
# Update status bar with file info
entry_count = len(df) if not df.empty else 0
self.ui_manager.update_file_info(self.filename, entry_count)
if entry_count == 0:
self.ui_manager.update_status("No data to display", "warning")
else:
self.ui_manager.update_status("Data loaded successfully", "success")
except Exception as e:
logger.error(f"Error loading data: {e}")
self.ui_manager.update_status(f"Error loading data: {str(e)}", "error")
if __name__ == "__main__":
root: tk.Tk = tk.Tk()
+102
View File
@@ -28,6 +28,11 @@ class UIManager:
self.medicine_manager = medicine_manager
self.pathology_manager = pathology_manager
# Status bar attributes
self.status_bar: tk.Frame | None = None
self.status_label: tk.Label | None = None
self.file_info_label: tk.Label | None = None
def setup_application_icon(self, img_path: str) -> bool:
"""Set up the application icon."""
try:
@@ -297,6 +302,103 @@ class UIManager:
return button_frame
def create_status_bar(self, parent_frame: tk.Widget) -> tk.Frame:
"""Create and configure the status bar at the bottom of the application."""
# Create the status bar frame
self.status_bar = tk.Frame(parent_frame, relief=tk.SUNKEN, bd=1)
self.status_bar.grid(row=2, column=0, columnspan=2, sticky="ew", padx=5, pady=2)
# Configure the parent to make the status bar stretch
parent_frame.grid_columnconfigure(0, weight=1)
# Create status message label (left side)
self.status_label = tk.Label(
self.status_bar,
text="Ready",
anchor=tk.W,
font=("TkDefaultFont", 9),
padx=10,
pady=2,
)
self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Create file info label (right side)
self.file_info_label = tk.Label(
self.status_bar,
text="",
anchor=tk.E,
font=("TkDefaultFont", 9),
padx=10,
pady=2,
)
self.file_info_label.pack(side=tk.RIGHT)
return self.status_bar
def update_status(self, message: str, message_type: str = "info") -> None:
"""
Update the status bar with a message.
Args:
message: The message to display
message_type: Type of message ('info', 'success', 'warning', 'error')
"""
if not self.status_label:
return
# Color mapping for different message types
colors = {
"info": "#000000", # Black
"success": "#28A745", # Green
"warning": "#FFC107", # Yellow/Orange
"error": "#DC3545", # Red
}
color = colors.get(message_type, "#000000")
self.status_label.config(text=message, fg=color)
# Clear the message after 5 seconds for non-info messages
if message_type != "info":
self.root.after(5000, lambda: self.update_status("Ready", "info"))
def update_file_info(self, filename: str, entry_count: int = 0) -> None:
"""
Update the file information in the status bar.
Args:
filename: Name of the current data file
entry_count: Number of entries in the file
"""
if not self.file_info_label:
return
file_display = os.path.basename(filename) if filename else "No file"
info_text = f"{file_display}"
if entry_count > 0:
info_text += f" ({entry_count} entries)"
self.file_info_label.config(text=info_text)
def show_status_message(self, message: str, duration: int = 3000) -> None:
"""
Show a temporary status message for a specific duration.
Args:
message: The message to display
duration: How long to show the message in milliseconds
"""
if not self.status_label:
return
original_text = self.status_label.cget("text")
original_color = self.status_label.cget("fg")
self.status_label.config(text=message, fg="#2E86AB")
self.root.after(
duration,
lambda: self.status_label.config(text=original_text, fg=original_color),
)
def create_edit_window(
self, values: tuple[str, ...], callbacks: dict[str, Callable]
) -> tk.Toplevel: