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