feat: add status bar to UI for improved user feedback and information display
This commit is contained in:
+49
-2
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user