From 937b711f4685515b27d5e1c1613d442cdc473d35 Mon Sep 17 00:00:00 2001
From: nn2-minh <Nguyen12.Minh@live.uwe.ac.uk>
Date: Sun, 16 Mar 2025 00:45:10 +0700
Subject: [PATCH] Add dashboard

---
 app/backend/routes/product.py               |   6 +
 app/backend/routes/shop.py                  |  20 +-
 app/frontend/components/auth/login.py       |   3 +-
 app/frontend/components/dashboard.py        | 205 ++++++++++++++++++++
 app/frontend/components/shop/create_shop.py |  15 +-
 app/frontend/components/shop/view_shop.py   |   4 +-
 app/frontend/main.py                        |  28 ++-
 app/static/default_shop_image.png           | Bin 16783 -> 0 bytes
 8 files changed, 251 insertions(+), 30 deletions(-)
 create mode 100644 app/frontend/components/dashboard.py
 delete mode 100644 app/static/default_shop_image.png

diff --git a/app/backend/routes/product.py b/app/backend/routes/product.py
index 4ccdeb6..daaaf9e 100644
--- a/app/backend/routes/product.py
+++ b/app/backend/routes/product.py
@@ -55,6 +55,12 @@ def create_product(
     return product
 
 
+@router.get("/list", response_model=list[ProductRead])
+def read_all_products(session: Session = Depends(get_session)):
+    products = session.query(Product).all()
+    return products
+
+
 @router.get("/{product_id}", response_model=ProductRead)
 def read_product(product_id: int, session: Session = Depends(get_session)):
     product = session.get(Product, product_id)
diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py
index ffea6d9..6641eb3 100644
--- a/app/backend/routes/shop.py
+++ b/app/backend/routes/shop.py
@@ -19,12 +19,12 @@ def create_shop(
     description: str = Form(None),
     file: UploadFile = File(None),
     session: Session = Depends(get_session),
-    # current_user: User = Depends(get_current_user),
-    owner_id = int,
+    current_user: User = Depends(get_current_user),
+    #owner_id = int,
 
 ):
-    # shop = ShopCreate(name=name, description=description, owner_id=current_user.id)
-    shop = ShopCreate(name=name, description=description, owner_id=owner_id)
+    shop = ShopCreate(name=name, description=description, owner_id=current_user.id)
+    #shop = ShopCreate(name=name, description=description, owner_id=owner_id)
     db_shop = Shop.from_orm(shop)
 
     if file and file.filename:
@@ -42,12 +42,18 @@ def create_shop(
     session.commit()
     session.refresh(db_shop)
 
-    if file:
-        # Delete the image file after session commit
-        os.remove(file_location)
+    # if file:
+    #     # Delete the image file after session commit
+    #     os.remove(file_location)
     return db_shop
 
 
+@router.get("/list", response_model=list[ShopRead])
+def get_all_shops(session: Session = Depends(get_session)):
+    shops = session.query(Shop).all()
+    return shops
+
+
 @router.get("/{shop_id}", response_model=ShopRead)
 def read_shop(shop_id: int, session: Session = Depends(get_session)):
     shop = session.get(Shop, shop_id)
diff --git a/app/frontend/components/auth/login.py b/app/frontend/components/auth/login.py
index 64a9f81..1c7baeb 100644
--- a/app/frontend/components/auth/login.py
+++ b/app/frontend/components/auth/login.py
@@ -23,7 +23,8 @@ def login_frame(parent, switch_func, API_URL):
             if response.status_code == 200:
                 access_token = response_data["access_token"]
                 messagebox.showinfo("Login Successful", f"Welcome back, {email}!")
-                switch_func("create_shop", access_token)
+                switch_func("dashboard", access_token)
+                print(f"Access Token in login: {access_token}")  # Debugging line
             else:
                 messagebox.showerror(
                     "Login Failed", response_data.get("detail", "Invalid credentials")
diff --git a/app/frontend/components/dashboard.py b/app/frontend/components/dashboard.py
new file mode 100644
index 0000000..32a128a
--- /dev/null
+++ b/app/frontend/components/dashboard.py
@@ -0,0 +1,205 @@
+import customtkinter as ctk
+import requests
+from PIL import Image, ImageTk
+from tkinter import messagebox
+import io
+
+SHOPPING = "#00c1ff"
+
+def dashboard_frame(parent, switch_func, API_URL, token):
+    
+    # Main container frame
+    frame = ctk.CTkFrame(parent, fg_color="transparent")
+
+    # ------------- HEADER (Top Bar) -------------
+    header_frame = ctk.CTkFrame(frame, fg_color=SHOPPING)
+    header_frame.place(relx=0, rely=0, relwidth=1, relheight=0.1)
+
+    # App Logo (Left)
+    logo_label = ctk.CTkLabel(
+        header_frame,
+        text="Shopping App",
+        text_color="white",
+        font=("Helvetica", 20, "bold")
+    )
+    logo_label.place(relx=0.01, rely=0.1, relwidth=0.15, relheight=0.8)
+
+    # Search bar (Center)
+    search_entry = ctk.CTkEntry(
+        header_frame,
+        placeholder_text="Search in Shop...",
+        height=30
+    )
+    search_entry.place(relx=0.25, rely=0.25, relwidth=0.45, relheight=0.5)
+
+    def perform_search():
+        """Call an endpoint to search products by keyword."""
+        keyword = search_entry.get().strip()
+        if not keyword:
+            messagebox.showinfo("Info", "Please enter a search keyword.")
+            return
+        try:
+            headers = {"Authorization": f"Bearer {token}"}
+            resp = requests.get(f"{API_URL}/product?search={keyword}", headers=headers)
+            if resp.status_code == 200:
+                products = resp.json()
+                display_products(products, bottom_products_frame)
+            else:
+                messagebox.showerror("Error", "Failed to fetch search results.")
+        except Exception as e:
+            messagebox.showerror("Error", f"Request error: {e}")
+
+    # Search button
+    search_button = ctk.CTkButton(
+        header_frame,
+        text="Search",
+        fg_color="white",
+        text_color="black",
+        command=perform_search
+    )
+    search_button.place(relx=0.71, rely=0.25, relwidth=0.08, relheight=0.5)
+
+    # ------------- MIDDLE (Featured/Top Shops) -------------
+    middle_frame = ctk.CTkFrame(frame, fg_color="transparent")
+    middle_frame.place(relx=0, rely=0.1, relwidth=1, relheight=0.25)
+
+    # Section Title
+    featured_label = ctk.CTkLabel(
+        middle_frame,
+        text="TOP SHOP",
+        font=("Helvetica", 16, "bold")
+    )
+    featured_label.pack(pady=5)
+
+    # A frame to hold the featured shops
+    top_shops_frame = ctk.CTkFrame(middle_frame, fg_color="transparent")
+    top_shops_frame.pack(fill="both", expand=True, padx=10, pady=5)
+    
+    def display_shops(shops, container):
+        """Given a list of shop dicts, display them in the given container."""
+        # Clear old widgets
+        for widget in container.winfo_children():
+            widget.destroy()
+        
+        if not shops:
+            ctk.CTkLabel(container, text="No shops found.").pack(pady=10)
+            return
+        
+        # Display shops in a vertical list
+        for shop in shops:
+            sframe = ctk.CTkFrame(container, corner_radius=5, fg_color="#2b2b2b")
+            sframe.pack(fill="x", padx=5, pady=5)
+
+            # Shop image/logo on left
+            image_label = ctk.CTkLabel(sframe, text="")
+            image_label.pack(side="left", padx=5, pady=5)
+
+            # Load shop logo if available (assumes key "logo_url")
+            logo_url = shop.get("logo_url")
+            if logo_url:
+                try:
+                    resp = requests.get(logo_url)
+                    if resp.status_code == 200:
+                        pil_img = Image.open(io.BytesIO(resp.content)).resize((50, 50))
+                        tk_img = ImageTk.PhotoImage(pil_img)
+                        image_label.configure(image=tk_img, text="")
+                        image_label.image = tk_img
+                except Exception as e:
+                    print(f"Shop image error: {e}")
+
+            # Shop info on right
+            info_frame = ctk.CTkFrame(sframe, fg_color="transparent")
+            info_frame.pack(side="left", fill="both", expand=True, padx=10)
+
+            ctk.CTkLabel(info_frame, text=shop.get("name", "No Name"), font=("Helvetica", 13, "bold")).pack(anchor="w")
+            # Optionally add more shop details, for example a rating if available:
+            if shop.get("rating"):
+                ctk.CTkLabel(info_frame, text=f"Rating: {shop['rating']}").pack(anchor="w")
+
+    # ------------- BOTTOM (Recommendations - Products) -------------
+    bottom_frame = ctk.CTkFrame(frame, fg_color="transparent")
+    bottom_frame.place(relx=0, rely=0.35, relwidth=1, relheight=0.65)
+
+    # Section Title
+    recommend_label = ctk.CTkLabel(
+        bottom_frame,
+        text="TODAY'S RECOMMENDATIONS",
+        font=("Helvetica", 16, "bold")
+    )
+    recommend_label.pack(pady=5)
+
+    bottom_products_frame = ctk.CTkFrame(bottom_frame, fg_color="transparent")
+    bottom_products_frame.pack(fill="both", expand=True, padx=10, pady=5)
+
+    def display_products(products, container):
+        """Given a list of product dicts, display them in the given container."""
+        # Clear old widgets
+        for widget in container.winfo_children():
+            widget.destroy()
+
+        if not products:
+            ctk.CTkLabel(container, text="No products found.").pack(pady=10)
+            return
+
+        # Display products in a vertical list
+        for product in products:
+            pframe = ctk.CTkFrame(container, corner_radius=5, fg_color="#2b2b2b")
+            pframe.pack(fill="x", padx=5, pady=5)
+
+            # Product image on left
+            image_label = ctk.CTkLabel(pframe, text="")
+            image_label.pack(side="left", padx=5, pady=5)
+
+            # Load first image if available
+            if product.get("images"):
+                try:
+                    img_url = product["images"][0]["image_url"]
+                    resp = requests.get(img_url)
+                    if resp.status_code == 200:
+                        pil_img = Image.open(io.BytesIO(resp.content)).resize((50, 50))
+                        tk_img = ImageTk.PhotoImage(pil_img)
+                        image_label.configure(image=tk_img, text="")
+                        image_label.image = tk_img
+                except Exception as e:
+                    print(f"Product image error: {e}")
+
+            # Product info on right
+            info_frame = ctk.CTkFrame(pframe, fg_color="transparent")
+            info_frame.pack(side="left", fill="both", expand=True, padx=10)
+
+            ctk.CTkLabel(info_frame, text=product.get("name", "No Name"), font=("Helvetica", 13, "bold")).pack(anchor="w")
+            price = product.get("price", 0.0)
+            ctk.CTkLabel(info_frame, text=f"Price: {price:.2f}").pack(anchor="w")
+
+    def fetch_featured_shops():
+        """Fetch some 'featured' or 'top' shops for the middle section."""
+        headers = {"Authorization": f"Bearer {token}"}
+        try:
+            resp = requests.get(f"{API_URL}/shops/list", headers=headers)
+            if resp.status_code == 200:
+                shops = resp.json()
+                # Slice the list to display only a handful of top shops
+                display_shops(shops[:5], top_shops_frame)
+            else:
+                messagebox.showerror("Error", "Failed to fetch featured shops.")
+        except Exception as e:
+            messagebox.showerror("Error", f"Request error: {e}")
+
+    def fetch_recommendations():
+        """Fetch recommended products for the bottom section."""
+        headers = {"Authorization": f"Bearer {token}"}
+        try:
+            resp = requests.get(f"{API_URL}/product/list", headers=headers)
+            if resp.status_code == 200:
+                products = resp.json()
+                display_products(products, bottom_products_frame)
+            else:
+                messagebox.showerror("Error", "Failed to fetch recommended products.")
+        except Exception as e:
+            messagebox.showerror("Error", f"Request error: {e}")
+
+    # Load data on start
+    fetch_featured_shops()
+    fetch_recommendations()
+
+    return frame
diff --git a/app/frontend/components/shop/create_shop.py b/app/frontend/components/shop/create_shop.py
index 5319ebe..44d4d79 100644
--- a/app/frontend/components/shop/create_shop.py
+++ b/app/frontend/components/shop/create_shop.py
@@ -3,9 +3,14 @@ from tkinter import messagebox, filedialog
 import requests
 import os
 
-
 def create_shop_frame(parent, switch_func, API_URL, token):
     frame = ctk.CTkFrame(parent)
+    frame.access_token = token
+
+    def update_token(new_token):
+        frame.access_token = new_token
+
+    frame.update_token = update_token
 
     selected_file_path = [None]
 
@@ -37,14 +42,14 @@ def create_shop_frame(parent, switch_func, API_URL, token):
                 messagebox.showerror("File Error", f"Unable to open file: {str(e)}")
                 return
 
-        if not token:
+        if not frame.access_token:
             messagebox.showerror(
                 "Token Error", "Access token not found. Please log in."
             )
             return
 
-        headers = {"Authorization": f"Bearer {token}"}
-        print(f"Access Token in create_shop: {token}")  # Debugging line
+        headers = {"Authorization": f"Bearer {frame.access_token}"}
+        print(f"Access Token in create_shop: {frame.access_token}")  # Debugging line
 
         try:
             response = requests.post(url, data=data, files=files, headers=headers)
@@ -90,4 +95,4 @@ def create_shop_frame(parent, switch_func, API_URL, token):
         command=lambda: switch_func("login"),
     ).pack(pady=5)
 
-    return frame
+    return frame
\ No newline at end of file
diff --git a/app/frontend/components/shop/view_shop.py b/app/frontend/components/shop/view_shop.py
index a5b9141..e7aa158 100644
--- a/app/frontend/components/shop/view_shop.py
+++ b/app/frontend/components/shop/view_shop.py
@@ -35,7 +35,7 @@ def view_shop_frame(parent, switch_func, API_URL, token):
         headers = {"Authorization": f"Bearer {token}"}
         try:
             response = requests.get(
-                f"{API_URL}/shops/my-shop", headers=headers
+                f"{API_URL}/shops/1", headers=headers
             )  # Adjust the endpoint as needed
             if response.status_code == 200:
                 shop_data = response.json()
@@ -137,4 +137,4 @@ def view_shop_frame(parent, switch_func, API_URL, token):
     # Fetch shop data on load
     fetch_shop_data()
 
-    return frame
+    return frame
\ No newline at end of file
diff --git a/app/frontend/main.py b/app/frontend/main.py
index d81ac74..eb16a15 100644
--- a/app/frontend/main.py
+++ b/app/frontend/main.py
@@ -1,3 +1,4 @@
+# main.py
 import customtkinter as ctk
 from components.auth.login import login_frame
 from components.auth.register import register_frame
@@ -5,40 +6,37 @@ from components.shop.create_shop import create_shop_frame
 from components.shop.view_shop import view_shop_frame
 from components.product.create_product import product_frame
 from components.admin.category import category_frame
+from components.dashboard import dashboard_frame  # import the dashboard
 
-# Backend API URL
 API_URL = "http://127.0.0.1:8000"
-
-# Global variable to store the access token
 access_token = None
 
-
-# Function to switch between frames
 def switch_frame(frame_name, token=None):
     global access_token
     if token:
         access_token = token
 
-    frames.get(frame_name, login).tkraise()  # Default to login if frame_name is invalid
-
+    frame = frames.get(frame_name, login)
+    if hasattr(frame, 'update_token'):
+        frame.update_token(access_token)
+    frame.tkraise()
 
-# Create main window
-ctk.set_appearance_mode("dark")  # Light, Dark, or System
+ctk.set_appearance_mode("dark")
 ctk.set_default_color_theme("blue")
 
 root = ctk.CTk()
 root.title("Shopping App")
 root.geometry("900x800")
 
-# Create Frames inside the main window
+# Create Frames
 login = login_frame(root, switch_frame, API_URL)
 register = register_frame(root, switch_frame, API_URL)
 create_shop = create_shop_frame(root, switch_frame, API_URL, access_token)
 view_shop = view_shop_frame(root, switch_frame, API_URL, access_token)
 product = product_frame(root, switch_frame, API_URL, access_token)
 category = category_frame(root, switch_frame, API_URL, access_token)
+dashboard = dashboard_frame(root, switch_frame, API_URL, access_token)  # new dashboard frame
 
-# Store frames in a dictionary for easier management
 frames = {
     "login": login,
     "register": register,
@@ -46,13 +44,13 @@ frames = {
     "create_product": product,
     "category": category,
     "view_shop": view_shop,
+    "dashboard": dashboard,  # add dashboard here
 }
 
-# Place all frames responsively
 for frame in frames.values():
-    frame.place(relx=0, rely=0.2, relwidth=1, relheight=0.8)
+    frame.place(relx=0, rely=0, relwidth=1, relheight=0.8)
 
-# Show the login frame first
-switch_frame("login")
+# Show the login frame first (or switch to dashboard as needed)
+switch_frame("login")  # switch to dashboard
 
 root.mainloop()
diff --git a/app/static/default_shop_image.png b/app/static/default_shop_image.png
deleted file mode 100644
index cbe892f8fcae34a8022b471f94c9ef8b7140d7d6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 16783
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

-- 
GitLab