From b7b31d0ab1c4ee028c7bc273f7f3a812e86f8c9b Mon Sep 17 00:00:00 2001
From: nn2-minh <Nguyen12.Minh@live.uwe.ac.uk>
Date: Sun, 27 Apr 2025 14:35:10 +0700
Subject: [PATCH] add dashboard category function

---
 app/frontend/components/dashboard.py          | 222 ++++++++++++++++++
 .../components/product/view_product.py        |  42 +++-
 app/frontend/main.py                          |  16 +-
 3 files changed, 276 insertions(+), 4 deletions(-)

diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py
index 1fa3092..b2dd547 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 47cfd52..1991bab 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 709fbd1..0b5a2c9 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 (
-- 
GitLab