diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py index 1fa30920c006c0e224df4c7bec44c9ddc3912a90..b2dd5473d2a861bb04d53cc8c44c7602dad5bef6 100644 --- a/app/frontend/components/dashboard.py +++ b/app/frontend/components/dashboard.py @@ -115,6 +115,20 @@ def dashboard_frame(parent, switch_func, API_URL, token): ) shop_icon.pack(pady=10) + # Category Icon - Add Browse Categories button + category_icon = ctk.CTkButton( + sidebar, + text="📑", # Changed from 🏷️ to a more consistently sized emoji + font=("Helvetica", 20), + fg_color="transparent", + text_color="white", + hover_color="#3b3b3b", + width=40, + height=40, + command=lambda: view_all_categories(), + ) + category_icon.pack(pady=10) + # Add a spacer spacer = ctk.CTkFrame(sidebar, fg_color="transparent", height=40) spacer.pack(pady=10) @@ -748,6 +762,211 @@ def dashboard_frame(parent, switch_func, API_URL, token): except Exception as e: CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") + # Function to fetch and display all categories + def view_all_categories(): + """Fetch and display all product categories""" + headers = {"Authorization": f"Bearer {token}"} + try: + resp = requests.get(f"{API_URL}/category", headers=headers) + if resp.status_code == 200: + all_categories = resp.json() + + # Change the product section title + products_title.configure(text="Browse By Category") + + # Clear product section + for widget in products_frame.winfo_children(): + widget.destroy() + + if not all_categories: + no_categories_label = ctk.CTkLabel( + products_frame, + text="No categories available.", + font=("Helvetica", 14), + text_color="gray", + ) + no_categories_label.pack(pady=20) + return + + # Create a grid layout for categories + grid_frame = ctk.CTkFrame(products_frame, fg_color="transparent") + grid_frame.pack(fill="both", expand=True) + + max_cols = 4 # We can fit more categories per row + row = 0 + col = 0 + + for category in all_categories: + # Create category card + category_card = create_category_card(grid_frame, category) + category_card.grid( + row=row, column=col, padx=10, pady=10, sticky="nsew" + ) + + col += 1 + if col >= max_cols: + col = 0 + row += 1 + + for i in range(max_cols): + grid_frame.grid_columnconfigure(i, weight=1) + else: + CTkMessagebox( + title="Error", + message=f"Failed to fetch categories. Status code: {resp.status_code}", + icon="cancel", + ) + except Exception as e: + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") + + # Function to create a category card + def create_category_card(parent, category_data): + """Create a card displaying category details""" + card = ctk.CTkFrame( + parent, fg_color=CARD_BG, corner_radius=10, width=220, height=180 + ) + card.pack_propagate(False) + + # Category icon/image (using emoji for simplicity) + icon_frame = ctk.CTkFrame(card, fg_color="transparent", height=80) + icon_frame.pack(fill="x", padx=10, pady=10) + icon_frame.pack_propagate(False) + + icon_label = ctk.CTkLabel( + icon_frame, text="📑", font=("Helvetica", 48), text_color=SHOPPING + ) + icon_label.pack(expand=True) + + # Category Details + details_frame = ctk.CTkFrame(card, fg_color="transparent") + details_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10)) + + # Category Name + name_label = ctk.CTkLabel( + details_frame, + text=category_data.get("name", "Unknown Category"), + font=("Helvetica", 16, "bold"), + text_color="white", + wraplength=200, + justify="center", + ) + name_label.pack(pady=(5, 10)) + + # View Products Button + view_button = ctk.CTkButton( + details_frame, + text="View Products", + fg_color=SHOPPING, + text_color="white", + corner_radius=8, + height=30, + command=lambda: view_products_by_category(category_data), + ) + view_button.pack(pady=(0, 5)) + + return card + + # Function to fetch and display products by category + def view_products_by_category(category_data): + """Display products filtered by category""" + category_id = category_data.get("id") + category_name = category_data.get("name", "Unknown Category") + + if not category_id: + CTkMessagebox(title="Error", message="Invalid category ID", icon="cancel") + return + + headers = {"Authorization": f"Bearer {token}"} + try: + # Use the search endpoint with category parameter + resp = requests.get( + f"{API_URL}/search?category={category_name}&search_type=products", + headers=headers, + ) + + if resp.status_code == 200: + products = resp.json() + print( + f"[DEBUG] Found {len(products)} products in category '{category_name}'" + ) + + # Get complete product data for each product + complete_products = [] + for product in products: + try: + product_id = product.get("id") + product_resp = requests.get( + f"{API_URL}/product/get/{product_id}", headers=headers + ) + if product_resp.status_code == 200: + complete_product = product_resp.json() + complete_products.append(complete_product) + else: + # Use the basic product data if we can't get complete data + complete_products.append(product) + except Exception as e: + print(f"[DEBUG] Error fetching product details: {e}") + complete_products.append(product) + + # Update the title to show we're displaying category products + products_title.configure(text=f"Products in '{category_name}' Category") + + # Clear product section + for widget in products_frame.winfo_children(): + widget.destroy() + + if not complete_products: + no_products_label = ctk.CTkLabel( + products_frame, + text=f"No products found in category '{category_name}'.", + font=("Helvetica", 14), + text_color="gray", + ) + no_products_label.pack(pady=20) + + # Add a back button + back_button = ctk.CTkButton( + products_frame, + text="Back to Categories", + fg_color=SHOPPING, + text_color="white", + width=150, + height=35, + command=view_all_categories, + ) + back_button.pack(pady=10) + return + + # Create a grid layout for products + grid_frame = ctk.CTkFrame(products_frame, fg_color="transparent") + grid_frame.pack(fill="both", expand=True, pady=(0, 10)) + + # Display products + display_products(complete_products, grid_frame) + + # Add a back button after the products + back_frame = ctk.CTkFrame(products_frame, fg_color="transparent") + back_frame.pack(fill="x", pady=(5, 10)) + + back_button = ctk.CTkButton( + back_frame, + text="Back to Categories", + fg_color=SHOPPING, + text_color="white", + width=150, + height=35, + command=view_all_categories, + ) + back_button.pack(side="left", padx=10) + else: + CTkMessagebox( + title="Error", + message=f"Failed to fetch products. Status code: {resp.status_code}", + icon="cancel", + ) + except Exception as e: + CTkMessagebox(title="Error", message=f"Request error: {e}", icon="cancel") + # Function to create a shop card def create_shop_card(parent, shop_data): """Create a card displaying shop details""" @@ -846,4 +1065,7 @@ def dashboard_frame(parent, switch_func, API_URL, token): frame.refresh_data = refresh_data + # Make the view_products_by_category function accessible from outside + frame.view_products_by_category = view_products_by_category + return frame diff --git a/app/frontend/components/product/view_product.py b/app/frontend/components/product/view_product.py index 47cfd52af13adb50b9b7c29d7128c02df25dfd07..1991babe1bd8bc905fa2d62265997afa216aa52c 100644 --- a/app/frontend/components/product/view_product.py +++ b/app/frontend/components/product/view_product.py @@ -599,7 +599,47 @@ def view_product_frame( text_color=ACCENT_COLOR, anchor="w", ) - price_label.pack(fill="x", pady=(0, 20)) + price_label.pack(fill="x", pady=(0, 10)) + + # Add Category display + category_name = "Uncategorized" + try: + if (product_data.get("category") is not None and + product_data["category"].get("name")): + category_name = product_data["category"]["name"] + print(f"[DEBUG] Found category: {category_name}") + elif product_data.get("category_id"): + print(f"[DEBUG] Category ID found but no category object: {product_data.get('category_id')}") + except Exception as e: + print(f"[DEBUG] Error processing category: {e}") + + category_frame = ctk.CTkFrame(details_inner, fg_color="transparent") + category_frame.pack(fill="x", pady=(0, 10)) + + category_label = ctk.CTkLabel( + category_frame, + text=f"Category: {category_name}", + font=("Helvetica", 14), + text_color="#A0A0A0", # Light gray color for category + anchor="w", + ) + category_label.pack(side="left") + + # Add a button to view all products in this category + if product_data.get("category") is not None and product_data["category"].get("id"): + category_id = product_data["category"].get("id") + browse_category_btn = ctk.CTkButton( + category_frame, + text="Browse Category", + font=("Helvetica", 12), + fg_color=SHOPPING, + text_color="white", + corner_radius=8, + height=25, + width=120, + command=lambda: switch_func("browse_category", product_data["category"]), + ) + browse_category_btn.pack(side="right") # Stock information stock_status = "In Stock" if product_stock > 0 else "Out of Stock" diff --git a/app/frontend/main.py b/app/frontend/main.py index 709fbd1ea42109022fd7ec98f4d76895ad86e183..0b5a2c9dd4cb68cdbd1258e44452d20f74eeb1f6 100644 --- a/app/frontend/main.py +++ b/app/frontend/main.py @@ -147,12 +147,12 @@ def switch_frame(frame_name, *args): # Always allow shop owners to view products print("Shop owner viewing product - allowing access") pass # Just proceed with view_product frame - # Explicitly allow all users to access view_shop and view_product - elif frame_name in ["view_shop", "view_product"]: + # Explicitly allow all users to access view_shop, view_product, and browse_category + elif frame_name in ["view_shop", "view_product", "browse_category"]: print( f"User with role {user_role} accessing {frame_name} - allowing access" ) - pass # Allow access to view_shop and view_product for all roles + pass # Allow access to view_shop, view_product, and browse_category for all roles elif user_role == "admin" and frame_name == "dashboard": # Redirect admins to the admin dashboard print("Redirecting admin from dashboard to admin_dashboard") @@ -223,6 +223,16 @@ def switch_frame(frame_name, *args): f"*** DEBUG: Refreshing edit_product with product_id: {product_id}" ) frame.refresh_data(product_id) + elif frame_name == "browse_category": + print(f"*** DEBUG: Switching to browse_category with args: {args}") + # If we have category data, handle it in the dashboard frame + if len(args) > 0 and args[0] is not None: + category_data = args[0] + print( + f"*** DEBUG: Browsing category: {category_data.get('name')} (ID: {category_data.get('id')})" + ) + frames["dashboard"].view_products_by_category(category_data) + frame_name = "dashboard" # Redirect to dashboard which will show category products # Make sure we have a valid token for authenticated pages if frame_name not in ["login", "register"] and (