From ec918660ea4b659275986ba552e4e2b20d4874ba Mon Sep 17 00:00:00 2001
From: nn2-minh <Nguyen12.Minh@live.uwe.ac.uk>
Date: Sun, 27 Apr 2025 15:11:15 +0700
Subject: [PATCH] add statistics view for admin

---
 app/backend/routes/admin.py                   | 170 +++-
 app/frontend/components/admin/__init__.py     |   3 +-
 app/frontend/components/admin/dashboard.py    |  10 +-
 .../components/admin/system_statistics.py     | 733 ++++++++++++++++++
 app/frontend/main.py                          |   4 +
 5 files changed, 910 insertions(+), 10 deletions(-)
 create mode 100644 app/frontend/components/admin/system_statistics.py

diff --git a/app/backend/routes/admin.py b/app/backend/routes/admin.py
index 698f454..54e5a3d 100644
--- a/app/backend/routes/admin.py
+++ b/app/backend/routes/admin.py
@@ -1,9 +1,10 @@
 from fastapi import APIRouter, Depends, HTTPException
-from sqlmodel import Session, select
-from app.backend.models.models import User, Shop, Category
+from sqlmodel import Session, select, func
+from app.backend.models.models import User, Shop, Category, Product, Order, OrderItem
 from app.backend.database import get_session
 from app.backend.routes.auth import get_current_user
 from typing import List
+from datetime import datetime, timedelta
 
 router = APIRouter()
 
@@ -177,3 +178,168 @@ def get_all_categories(
 
     categories = session.exec(select(Category)).all()
     return categories
+
+
+@router.get("/stats/users")
+def get_user_statistics(
+    session: Session = Depends(get_session),
+    current_user: User = Depends(get_current_user),
+):
+    """Get user statistics for the admin dashboard"""
+    verify_admin(current_user)
+
+    # Count total users
+    total_users = session.exec(
+        select(func.count()).where(User.role == "customer")
+    ).one()
+
+    # Count shop owners
+    total_shop_owners = session.exec(
+        select(func.count()).where(User.role == "shop_owner")
+    ).one()
+
+    # Get user growth over time (last 30 days)
+    end_date = datetime.utcnow()
+    start_date = end_date - timedelta(days=30)
+
+    # Get all users created within the time period
+    users_by_day = {}
+
+    # Initialize all days with 0 count
+    for i in range(31):
+        day = end_date - timedelta(days=i)
+        day_str = day.strftime("%Y-%m-%d")
+        users_by_day[day_str] = 0
+
+    # Count users by creation date
+    user_counts = session.exec(
+        select(func.date(User.created_at).label("day"), func.count().label("count"))
+        .where(User.created_at >= start_date)
+        .group_by(func.date(User.created_at))
+    ).all()
+
+    # Update counts
+    for day_data in user_counts:
+        day_str = day_data[0].strftime("%Y-%m-%d")
+        users_by_day[day_str] = day_data[1]
+
+    # Convert to list of dictionaries
+    user_growth = [
+        {"date": date, "count": count} for date, count in users_by_day.items()
+    ]
+
+    # Sort by date
+    user_growth.sort(key=lambda x: x["date"])
+
+    return {
+        "total_users": total_users,
+        "total_shop_owners": total_shop_owners,
+        "user_growth": user_growth,
+    }
+
+
+@router.get("/stats/revenue")
+def get_revenue_statistics(
+    session: Session = Depends(get_session),
+    current_user: User = Depends(get_current_user),
+):
+    """Get revenue statistics for the admin dashboard"""
+    verify_admin(current_user)
+
+    # Calculate total revenue (5% of product sales + shipping)
+    orders = session.exec(select(Order)).all()
+
+    total_product_revenue = 0
+    total_shipping_revenue = 0
+
+    for order in orders:
+        # 5% of the product price is the revenue
+        product_revenue = order.total_price * 0.05
+        total_product_revenue += product_revenue
+
+        # Shipping fee is also revenue
+        total_shipping_revenue += order.shipping_price
+
+    total_revenue = total_product_revenue + total_shipping_revenue
+
+    # Get revenue over time (last 30 days)
+    end_date = datetime.utcnow()
+    start_date = end_date - timedelta(days=30)
+
+    # Initialize all days with 0 revenue
+    revenue_by_day = {}
+    for i in range(31):
+        day = end_date - timedelta(days=i)
+        day_str = day.strftime("%Y-%m-%d")
+        revenue_by_day[day_str] = 0
+
+    # Calculate revenue by day
+    for order in orders:
+        if order.created_at >= start_date:
+            day_str = order.created_at.strftime("%Y-%m-%d")
+            if day_str in revenue_by_day:
+                revenue_by_day[day_str] += (
+                    order.total_price * 0.05
+                ) + order.shipping_price
+
+    # Convert to list of dictionaries
+    revenue_over_time = [
+        {"date": date, "amount": round(amount, 2)}
+        for date, amount in revenue_by_day.items()
+    ]
+
+    # Sort by date
+    revenue_over_time.sort(key=lambda x: x["date"])
+
+    return {
+        "total_revenue": round(total_revenue, 2),
+        "product_revenue": round(total_product_revenue, 2),
+        "shipping_revenue": round(total_shipping_revenue, 2),
+        "revenue_over_time": revenue_over_time,
+    }
+
+
+@router.get("/stats/products")
+def get_product_statistics(
+    session: Session = Depends(get_session),
+    current_user: User = Depends(get_current_user),
+):
+    """Get product statistics for the admin dashboard"""
+    verify_admin(current_user)
+
+    # Total products count
+    total_products = session.exec(select(func.count(Product.id))).one()
+
+    # Products by category
+    products_by_category = session.exec(
+        select(Category.name, func.count(Product.id).label("count"))
+        .join(Category, Product.category_id == Category.id)
+        .group_by(Category.name)
+    ).all()
+
+    category_stats = [
+        {"category_name": name, "product_count": count}
+        for name, count in products_by_category
+    ]
+
+    # Top selling products (using order items)
+    product_sales = session.exec(
+        select(
+            Product.id, Product.name, func.sum(OrderItem.quantity).label("total_sold")
+        )
+        .join(OrderItem, OrderItem.product_id == Product.id)
+        .group_by(Product.id, Product.name)
+        .order_by(func.sum(OrderItem.quantity).desc())
+        .limit(20)
+    ).all()
+
+    product_sales_stats = [
+        {"product_id": id, "product_name": name, "sales_count": total_sold}
+        for id, name, total_sold in product_sales
+    ]
+
+    return {
+        "total_products": total_products,
+        "products_by_category": category_stats,
+        "product_sales": product_sales_stats,
+    }
diff --git a/app/frontend/components/admin/__init__.py b/app/frontend/components/admin/__init__.py
index 1f6bf6c..6b3b6d1 100644
--- a/app/frontend/components/admin/__init__.py
+++ b/app/frontend/components/admin/__init__.py
@@ -2,4 +2,5 @@
 from .dashboard import admin_dashboard_frame
 from .user_management import admin_user_management_frame
 from .shop_owner_management import admin_shop_owner_management_frame
-from .category import category_frame 
\ No newline at end of file
+from .category import category_frame
+from .system_statistics import admin_system_statistics_frame
diff --git a/app/frontend/components/admin/dashboard.py b/app/frontend/components/admin/dashboard.py
index 3a25bcf..dfa637a 100644
--- a/app/frontend/components/admin/dashboard.py
+++ b/app/frontend/components/admin/dashboard.py
@@ -202,7 +202,7 @@ def admin_dashboard_frame(parent, switch_func, API_URL, access_token):
         0,
         "Category Management",
         "Manage product categories",
-        "🏷️",
+        "📑",
         lambda: switch_func("category"),
         "#9c27b0",
     )
@@ -212,12 +212,8 @@ def admin_dashboard_frame(parent, switch_func, API_URL, access_token):
         1,
         "System Statistics",
         "View system analytics and data",
-        "��",
-        lambda: CTkMessagebox(
-            title="Coming Soon",
-            message="System statistics will be available in a future update.",
-            icon="info",
-        ),
+        "📊",
+        lambda: switch_func("admin_system_statistics"),
         "#e91e63",
     )
 
diff --git a/app/frontend/components/admin/system_statistics.py b/app/frontend/components/admin/system_statistics.py
new file mode 100644
index 0000000..9ec931a
--- /dev/null
+++ b/app/frontend/components/admin/system_statistics.py
@@ -0,0 +1,733 @@
+import customtkinter as ctk
+from CTkMessagebox import CTkMessagebox
+import requests
+from tkinter import ttk
+import matplotlib.pyplot as plt
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+import numpy as np
+from datetime import datetime, timedelta
+
+
+def admin_system_statistics_frame(parent, switch_func, API_URL, access_token):
+    """
+    Admin dashboard component for system statistics and analytics
+    """
+    frame = ctk.CTkFrame(parent)
+
+    # Store the token for later use
+    def update_token(new_token):
+        nonlocal access_token
+        access_token = new_token
+
+    frame.update_token = update_token
+
+    # Create main container with padding
+    main_container = ctk.CTkFrame(frame, fg_color="transparent")
+    main_container.pack(padx=40, pady=30, fill="both", expand=True)
+
+    # Header section
+    header_frame = ctk.CTkFrame(main_container, corner_radius=15, fg_color="#e91e63")
+    header_frame.pack(fill="x", pady=(0, 20))
+
+    # Title and description
+    title_frame = ctk.CTkFrame(header_frame, fg_color="transparent")
+    title_frame.pack(padx=20, pady=15)
+
+    ctk.CTkLabel(
+        title_frame,
+        text="System Statistics",
+        font=("Helvetica", 24, "bold"),
+        text_color="#ffffff",
+    ).pack(anchor="w")
+
+    ctk.CTkLabel(
+        title_frame,
+        text="Key performance indicators and system analytics",
+        font=("Helvetica", 14),
+        text_color="#e0e0e0",
+    ).pack(anchor="w")
+
+    # Function to create statistic cards
+    def create_stat_card(parent, title, value, icon, color, row, column):
+        card = ctk.CTkFrame(
+            parent,
+            corner_radius=12,
+            fg_color=color,
+            border_width=1,
+            border_color="#2a5a8f",
+            height=100,
+        )
+        card.grid(row=row, column=column, padx=10, pady=10, sticky="nsew")
+
+        # Make sure card keeps its height
+        card.grid_propagate(False)
+
+        # Create icon circle
+        icon_frame = ctk.CTkFrame(
+            card, width=40, height=40, corner_radius=20, fg_color="#ffffff"
+        )
+        icon_frame.place(relx=0.1, rely=0.3, anchor="center")
+
+        # Icon text
+        ctk.CTkLabel(
+            icon_frame, text=icon, font=("Helvetica", 16, "bold"), text_color=color
+        ).place(relx=0.5, rely=0.5, anchor="center")
+
+        # Card title
+        ctk.CTkLabel(
+            card,
+            text=title,
+            font=("Helvetica", 14, "bold"),
+            text_color="#ffffff",
+        ).place(relx=0.5, rely=0.3, anchor="center")
+
+        # Card value
+        value_label = ctk.CTkLabel(
+            card,
+            text=value,
+            font=("Helvetica", 22, "bold"),
+            text_color="#ffffff",
+        )
+        value_label.place(relx=0.5, rely=0.7, anchor="center")
+
+        return value_label
+
+    # Statistics overview cards
+    stats_container = ctk.CTkFrame(main_container, fg_color="transparent")
+    stats_container.pack(fill="x", pady=(0, 20))
+
+    # Configure grid for stat cards
+    stats_container.grid_columnconfigure(0, weight=1)
+    stats_container.grid_columnconfigure(1, weight=1)
+    stats_container.grid_columnconfigure(2, weight=1)
+
+    # Placeholder values (will be filled with real data)
+    user_count = "Loading..."
+    owner_count = "Loading..."
+    revenue = "Loading..."
+
+    # User accounts count card
+    user_card = create_stat_card(
+        stats_container, "Total Users", user_count, "👤", "#3a7ebf", 0, 0
+    )
+
+    # Shop owner accounts count card
+    owner_card = create_stat_card(
+        stats_container, "Shop Owners", owner_count, "🏪", "#2e8b57", 0, 1
+    )
+
+    # Revenue card
+    revenue_card = create_stat_card(
+        stats_container, "Total Revenue", revenue, "💰", "#e91e63", 0, 2
+    )
+
+    # Create tabs for different charts
+    tab_view = ctk.CTkTabview(main_container, corner_radius=10)
+    tab_view.pack(fill="both", expand=True, pady=(10, 20))
+
+    # Add tabs
+    tab_view.add("User Growth")
+    tab_view.add("Revenue")
+    tab_view.add("Product Sales")
+
+    # Set default tab
+    tab_view.set("User Growth")
+
+    # Place for future graphs in each tab
+    user_graph_frame = ctk.CTkFrame(tab_view.tab("User Growth"), fg_color="transparent")
+    user_graph_frame.pack(fill="both", expand=True, padx=10, pady=10)
+
+    revenue_graph_frame = ctk.CTkFrame(tab_view.tab("Revenue"), fg_color="transparent")
+    revenue_graph_frame.pack(fill="both", expand=True, padx=10, pady=10)
+
+    sales_graph_frame = ctk.CTkFrame(
+        tab_view.tab("Product Sales"), fg_color="transparent"
+    )
+    sales_graph_frame.pack(fill="both", expand=True, padx=10, pady=10)
+
+    # Store current active tab
+    current_tab = "User Growth"
+
+    # Function to handle tab changes
+    def handle_tab_change():
+        nonlocal current_tab
+        selected_tab = tab_view.get()
+
+        # Skip if no change
+        if selected_tab == current_tab:
+            return
+
+        # Unbind mousewheel from previous tab
+        if current_tab == "User Growth" and hasattr(
+            user_graph_frame, "unbind_mousewheel"
+        ):
+            user_graph_frame.unbind_mousewheel()
+        elif current_tab == "Revenue" and hasattr(
+            revenue_graph_frame, "unbind_mousewheel"
+        ):
+            revenue_graph_frame.unbind_mousewheel()
+        elif current_tab == "Product Sales" and hasattr(
+            sales_graph_frame, "unbind_mousewheel"
+        ):
+            sales_graph_frame.unbind_mousewheel()
+
+        # Bind mousewheel to new tab
+        if selected_tab == "User Growth" and hasattr(
+            user_graph_frame, "bind_mousewheel"
+        ):
+            user_graph_frame.bind_mousewheel()
+        elif selected_tab == "Revenue" and hasattr(
+            revenue_graph_frame, "bind_mousewheel"
+        ):
+            revenue_graph_frame.bind_mousewheel()
+        elif selected_tab == "Product Sales" and hasattr(
+            sales_graph_frame, "bind_mousewheel"
+        ):
+            sales_graph_frame.bind_mousewheel()
+
+        # Update current tab
+        current_tab = selected_tab
+
+    # Configure tab change event
+    original_select = tab_view._segmented_button._command
+
+    def tab_changed(*args, **kwargs):
+        result = original_select(*args, **kwargs)
+        handle_tab_change()
+        return result
+
+    tab_view._segmented_button._command = tab_changed
+
+    # Control panel with refresh button
+    control_panel = ctk.CTkFrame(main_container, corner_radius=10, height=50)
+    control_panel.pack(fill="x", pady=(0, 15))
+
+    refresh_button = ctk.CTkButton(
+        control_panel,
+        text="Refresh Statistics",
+        font=("Helvetica", 12, "bold"),
+        height=35,
+        width=150,
+        corner_radius=8,
+        fg_color="#4caf50",
+        hover_color="#388e3c",
+        command=lambda: fetch_statistics(),
+    )
+    refresh_button.pack(side="right", padx=20, pady=10)
+
+    date_range = ctk.CTkOptionMenu(
+        control_panel,
+        values=["Last 7 Days", "Last 30 Days", "Last 90 Days", "All Time"],
+        font=("Helvetica", 12),
+        width=150,
+        height=35,
+        dropdown_font=("Helvetica", 12),
+    )
+    date_range.pack(side="left", padx=20, pady=10)
+    date_range.set("Last 30 Days")
+
+    # Footer with back button
+    footer_frame = ctk.CTkFrame(main_container, fg_color="transparent", height=50)
+    footer_frame.pack(fill="x", pady=(15, 0))
+
+    back_button = ctk.CTkButton(
+        footer_frame,
+        text="Back to Admin Dashboard",
+        command=lambda: switch_func("admin_dashboard"),
+        font=("Helvetica", 12, "bold"),
+        height=40,
+        corner_radius=8,
+        fg_color="#555555",
+        hover_color="#444444",
+    )
+    back_button.pack(side="left")
+
+    # Function to fetch statistics data
+    def fetch_statistics():
+        headers = {"Authorization": f"Bearer {access_token}"}
+
+        try:
+            # Fetch user count
+            response = requests.get(f"{API_URL}/admin/stats/users", headers=headers)
+            if response.status_code == 200:
+                stats = response.json()
+                user_count = stats.get("total_users", 0)
+                owner_count = stats.get("total_shop_owners", 0)
+
+                # Update the cards
+                user_card.configure(text=str(user_count))
+                owner_card.configure(text=str(owner_count))
+
+                # Create user growth chart
+                create_user_growth_chart(user_graph_frame, stats.get("user_growth", []))
+            else:
+                CTkMessagebox(
+                    title="Error",
+                    message="Failed to fetch user statistics",
+                    icon="cancel",
+                )
+
+            # Fetch revenue data
+            response = requests.get(f"{API_URL}/admin/stats/revenue", headers=headers)
+            if response.status_code == 200:
+                stats = response.json()
+                total_revenue = stats.get("total_revenue", 0)
+
+                # Format revenue with currency symbol and 2 decimal places
+                revenue_card.configure(text=f"${total_revenue:.2f}")
+
+                # Create revenue chart
+                create_revenue_chart(
+                    revenue_graph_frame, stats.get("revenue_over_time", [])
+                )
+            else:
+                CTkMessagebox(
+                    title="Error",
+                    message="Failed to fetch revenue statistics",
+                    icon="cancel",
+                )
+
+            # Fetch product sales data
+            response = requests.get(f"{API_URL}/admin/stats/products", headers=headers)
+            if response.status_code == 200:
+                stats = response.json()
+                create_product_sales_chart(
+                    sales_graph_frame, stats.get("product_sales", [])
+                )
+            else:
+                CTkMessagebox(
+                    title="Error",
+                    message="Failed to fetch product statistics",
+                    icon="cancel",
+                )
+
+        except Exception as e:
+            CTkMessagebox(
+                title="Error",
+                message=f"Failed to connect to server: {e}",
+                icon="cancel",
+            )
+
+    # Function to create user growth chart
+    def create_user_growth_chart(parent_frame, data):
+        # Clear existing widgets
+        for widget in parent_frame.winfo_children():
+            widget.destroy()
+
+        # If no data, show message
+        if not data:
+            ctk.CTkLabel(
+                parent_frame,
+                text="No user growth data available",
+                font=("Helvetica", 14),
+            ).pack(expand=True)
+            return
+
+        # Create a canvas with scrollbar for the chart
+        canvas_frame = ctk.CTkFrame(parent_frame, fg_color="transparent")
+        canvas_frame.pack(fill="both", expand=True)
+
+        # Add scrollbar to the frame
+        scrollbar = ttk.Scrollbar(canvas_frame, orient="vertical")
+        scrollbar.pack(side="right", fill="y")
+
+        # Create a canvas that will be scrollable
+        canvas = ctk.CTkCanvas(canvas_frame, bd=0, highlightthickness=0, bg="#333333")
+        canvas.pack(side="left", fill="both", expand=True)
+
+        # Configure the scrollbar to work with the canvas
+        scrollbar.config(command=canvas.yview)
+        canvas.configure(yscrollcommand=scrollbar.set)
+
+        # Create a frame inside the canvas for the chart
+        chart_frame = ctk.CTkFrame(canvas, fg_color="#333333")
+
+        # Create a window inside the canvas with the chart frame
+        canvas_window = canvas.create_window((0, 0), window=chart_frame, anchor="nw")
+
+        # Make sure we have sorted data by date
+        sorted_data = sorted(data, key=lambda x: x.get("date", ""))
+
+        # Extract dates and user counts
+        dates = [item.get("date") for item in sorted_data]
+        users = [item.get("count") for item in sorted_data]
+
+        # Set fixed chart dimensions with enough height for scrolling
+        chart_width = 700
+        chart_height = max(
+            400, len(dates) * 25
+        )  # Adjust height based on number of dates
+
+        # Create figure for the chart - rotate the chart to be vertical
+        fig, ax = plt.subplots(figsize=(10, max(8, len(dates) * 0.4)), dpi=100)
+        fig.patch.set_facecolor("#333333")
+        ax.set_facecolor("#333333")
+
+        # Create a vertical bar chart instead of a line chart
+        ax.bar(dates, users, color="#3a7ebf", alpha=0.8)
+        ax.set_title("User Growth Over Time", color="white", fontsize=16)
+        ax.set_xlabel("Date", color="white", fontsize=12)
+        ax.set_ylabel("Number of Users", color="white", fontsize=12)
+        ax.tick_params(
+            axis="x", colors="white", rotation=90
+        )  # Rotate x labels for better readability
+        ax.tick_params(axis="y", colors="white")
+        ax.grid(True, linestyle="--", alpha=0.7, axis="y")
+
+        # Add data labels on top of bars
+        for i, v in enumerate(users):
+            if v > 0:  # Only show labels for bars with values
+                ax.text(
+                    i,
+                    v + 0.1,
+                    str(v),
+                    ha="center",
+                    va="bottom",
+                    color="white",
+                    fontsize=10,
+                )
+
+        # Adjust subplot parameters for better spacing
+        plt.tight_layout()
+
+        # Embed the chart in the tkinter frame
+        chart_canvas = FigureCanvasTkAgg(fig, master=chart_frame)
+        chart_canvas.draw()
+        chart_widget = chart_canvas.get_tk_widget()
+        chart_widget.configure(width=chart_width, height=chart_height)
+        chart_widget.pack(fill="both", expand=True)
+
+        # Update the canvas's scroll region
+        chart_frame.update_idletasks()
+        canvas.config(scrollregion=canvas.bbox("all"))
+
+        # Add helpful instruction text
+        instruction_label = ctk.CTkLabel(
+            parent_frame,
+            text="Scroll down to see more data points",
+            font=("Helvetica", 12),
+            text_color="#e0e0e0",
+        )
+        instruction_label.pack(pady=(5, 0))
+
+        # Define mouse wheel scrolling function
+        def _on_mousewheel(event):
+            canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
+
+        # Bind the mousewheel to the canvas directly for better response
+        canvas.bind("<MouseWheel>", _on_mousewheel)
+
+        # Store event bindings to manage them
+        canvas.bind_all("<MouseWheel>", _on_mousewheel)
+
+        # Function to unbind mousewheel event when tab changes
+        def _unbind_mousewheel():
+            canvas.unbind_all("<MouseWheel>")
+
+        # Store the unbind function to be called when switching tabs
+        parent_frame.unbind_mousewheel = _unbind_mousewheel
+
+        # Function to bind mousewheel event when tab is selected
+        def _bind_mousewheel():
+            canvas.bind_all("<MouseWheel>", _on_mousewheel)
+
+        # Store the bind function to be called when this tab is selected
+        parent_frame.bind_mousewheel = _bind_mousewheel
+
+        # Bind frame configure event to adjust the canvas window
+        def _on_frame_configure(event):
+            canvas.configure(scrollregion=canvas.bbox("all"))
+
+        chart_frame.bind("<Configure>", _on_frame_configure)
+
+        # Adjust canvas width when the window resizes
+        def _on_canvas_configure(event):
+            canvas.itemconfig(canvas_window, width=event.width)
+
+        canvas.bind("<Configure>", _on_canvas_configure)
+
+    # Function to create revenue chart
+    def create_revenue_chart(parent_frame, data):
+        # Clear existing widgets
+        for widget in parent_frame.winfo_children():
+            widget.destroy()
+
+        # If no data, show message
+        if not data:
+            ctk.CTkLabel(
+                parent_frame,
+                text="No revenue data available",
+                font=("Helvetica", 14),
+            ).pack(expand=True)
+            return
+
+        # Create a canvas with scrollbar for the chart
+        canvas_frame = ctk.CTkFrame(parent_frame, fg_color="transparent")
+        canvas_frame.pack(fill="both", expand=True)
+
+        # Add scrollbar to the frame
+        scrollbar = ttk.Scrollbar(canvas_frame, orient="vertical")
+        scrollbar.pack(side="right", fill="y")
+
+        # Create a canvas that will be scrollable
+        canvas = ctk.CTkCanvas(canvas_frame, bd=0, highlightthickness=0, bg="#333333")
+        canvas.pack(side="left", fill="both", expand=True)
+
+        # Configure the scrollbar to work with the canvas
+        scrollbar.config(command=canvas.yview)
+        canvas.configure(yscrollcommand=scrollbar.set)
+
+        # Create a frame inside the canvas for the chart
+        chart_frame = ctk.CTkFrame(canvas, fg_color="#333333")
+
+        # Create a window inside the canvas with the chart frame
+        canvas_window = canvas.create_window((0, 0), window=chart_frame, anchor="nw")
+
+        # Sort data by date
+        sorted_data = sorted(data, key=lambda x: x.get("date", ""))
+
+        # Extract dates and amounts
+        dates = [item.get("date") for item in sorted_data]
+        amounts = [item.get("amount") for item in sorted_data]
+
+        # Set chart dimensions
+        chart_width = 700
+        chart_height = max(
+            400, len(dates) * 25
+        )  # Adjust height based on number of dates
+
+        # Create figure for the chart
+        fig, ax = plt.subplots(figsize=(10, max(8, len(dates) * 0.4)), dpi=100)
+        fig.patch.set_facecolor("#333333")
+        ax.set_facecolor("#333333")
+
+        # Create the plot - vertical bars
+        bars = ax.bar(dates, amounts, color="#e91e63", alpha=0.8)
+        ax.set_title("Revenue Over Time", color="white", fontsize=16)
+        ax.set_xlabel("Date", color="white", fontsize=12)
+        ax.set_ylabel("Revenue (USD)", color="white", fontsize=12)
+        ax.tick_params(
+            axis="x", colors="white", rotation=90
+        )  # Rotate x labels for better readability
+        ax.tick_params(axis="y", colors="white")
+        ax.grid(True, linestyle="--", alpha=0.7, axis="y")
+
+        # Add labels on top of bars for better readability
+        for bar in bars:
+            height = bar.get_height()
+            if height > 0:  # Only add labels for bars with values
+                ax.text(
+                    bar.get_x() + bar.get_width() / 2,
+                    height + 0.1,
+                    f"${height:.2f}",
+                    ha="center",
+                    va="bottom",
+                    color="white",
+                    fontsize=9,
+                )
+
+        # Adjust subplot parameters for better spacing
+        plt.tight_layout()
+
+        # Embed the chart in the tkinter frame
+        chart_canvas = FigureCanvasTkAgg(fig, master=chart_frame)
+        chart_canvas.draw()
+        chart_widget = chart_canvas.get_tk_widget()
+        chart_widget.configure(width=chart_width, height=chart_height)
+        chart_widget.pack(fill="both", expand=True)
+
+        # Update the canvas's scroll region
+        chart_frame.update_idletasks()
+        canvas.config(scrollregion=canvas.bbox("all"))
+
+        # Add helpful instruction text
+        instruction_label = ctk.CTkLabel(
+            parent_frame,
+            text="Scroll down to see more data points",
+            font=("Helvetica", 12),
+            text_color="#e0e0e0",
+        )
+        instruction_label.pack(pady=(5, 0))
+
+        # Define mouse wheel scrolling function
+        def _on_mousewheel(event):
+            canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
+
+        # Bind the mousewheel to the canvas directly for better response
+        canvas.bind("<MouseWheel>", _on_mousewheel)
+
+        # Store event bindings to manage them
+        canvas.bind_all("<MouseWheel>", _on_mousewheel)
+
+        # Function to unbind mousewheel event when tab changes
+        def _unbind_mousewheel():
+            canvas.unbind_all("<MouseWheel>")
+
+        # Store the unbind function to be called when switching tabs
+        parent_frame.unbind_mousewheel = _unbind_mousewheel
+
+        # Function to bind mousewheel event when tab is selected
+        def _bind_mousewheel():
+            canvas.bind_all("<MouseWheel>", _on_mousewheel)
+
+        # Store the bind function to be called when this tab is selected
+        parent_frame.bind_mousewheel = _bind_mousewheel
+
+        # Bind frame configure event to adjust the canvas window
+        def _on_frame_configure(event):
+            canvas.configure(scrollregion=canvas.bbox("all"))
+
+        chart_frame.bind("<Configure>", _on_frame_configure)
+
+        # Adjust canvas width when the window resizes
+        def _on_canvas_configure(event):
+            canvas.itemconfig(canvas_window, width=event.width)
+
+        canvas.bind("<Configure>", _on_canvas_configure)
+
+    # Function to create product sales chart
+    def create_product_sales_chart(parent_frame, data):
+        # Clear existing widgets
+        for widget in parent_frame.winfo_children():
+            widget.destroy()
+
+        # If no data, show message
+        if not data:
+            ctk.CTkLabel(
+                parent_frame,
+                text="No product sales data available",
+                font=("Helvetica", 14),
+            ).pack(expand=True)
+            return
+
+        # Create a canvas with scrollbar for the chart
+        canvas_frame = ctk.CTkFrame(parent_frame, fg_color="transparent")
+        canvas_frame.pack(fill="both", expand=True)
+
+        # Add scrollbar to the frame
+        scrollbar = ttk.Scrollbar(canvas_frame, orient="vertical")
+        scrollbar.pack(side="right", fill="y")
+
+        # Create a canvas that will be scrollable
+        canvas = ctk.CTkCanvas(canvas_frame, bd=0, highlightthickness=0, bg="#333333")
+        canvas.pack(side="left", fill="both", expand=True)
+
+        # Configure the scrollbar to work with the canvas
+        scrollbar.config(command=canvas.yview)
+        canvas.configure(yscrollcommand=scrollbar.set)
+
+        # Create a frame inside the canvas for the chart
+        chart_frame = ctk.CTkFrame(canvas, fg_color="#333333")
+
+        # Create a window inside the canvas with the chart frame
+        canvas_window = canvas.create_window((0, 0), window=chart_frame, anchor="nw")
+
+        # Make the chart wider for better visibility
+        chart_width = 700
+        chart_height = max(
+            400, len(data) * 30
+        )  # Adjust height based on number of products
+
+        # Sort data by sales count (descending)
+        sorted_data = sorted(data, key=lambda x: x.get("sales_count", 0), reverse=True)
+
+        # Extract product names and sales counts
+        products = [item.get("product_name", "Unknown") for item in sorted_data]
+        sales = [item.get("sales_count", 0) for item in sorted_data]
+
+        # Create figure for the chart
+        fig, ax = plt.subplots(figsize=(10, max(6, len(products) * 0.4)), dpi=100)
+        fig.patch.set_facecolor("#333333")
+        ax.set_facecolor("#333333")
+
+        # Create the horizontal bar plot
+        bars = ax.barh(products, sales, color="#2e8b57", alpha=0.7)
+        ax.set_title("Product Sales Ranking", color="white", fontsize=16)
+        ax.set_xlabel("Number of Sales", color="white", fontsize=12)
+        ax.set_ylabel("Product Name", color="white", fontsize=12)
+        ax.tick_params(axis="x", colors="white")
+        ax.tick_params(axis="y", colors="white")
+        ax.grid(True, linestyle="--", alpha=0.7, axis="x")
+
+        # Add labels to the bars
+        for bar in bars:
+            width = bar.get_width()
+            ax.text(
+                width + 0.3,
+                bar.get_y() + bar.get_height() / 2,
+                f"{width:.0f}",
+                ha="left",
+                va="center",
+                color="white",
+            )
+
+        # Adjust subplot parameters for better spacing
+        plt.tight_layout()
+
+        # Embed the chart in the tkinter frame
+        chart_canvas = FigureCanvasTkAgg(fig, master=chart_frame)
+        chart_canvas.draw()
+        chart_widget = chart_canvas.get_tk_widget()
+        chart_widget.configure(width=chart_width, height=chart_height)
+        chart_widget.pack(fill="both", expand=True)
+
+        # Update the canvas's scroll region
+        chart_frame.update_idletasks()
+        canvas.config(scrollregion=canvas.bbox("all"))
+
+        # Add helpful instruction text
+        instruction_label = ctk.CTkLabel(
+            parent_frame,
+            text="Scroll down to see more products",
+            font=("Helvetica", 12),
+            text_color="#e0e0e0",
+        )
+        instruction_label.pack(pady=(5, 0))
+
+        # Define mouse wheel scrolling function
+        def _on_mousewheel(event):
+            canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
+
+        # Bind the mousewheel to the canvas directly for better response
+        canvas.bind("<MouseWheel>", _on_mousewheel)
+
+        # Store event bindings to manage them
+        canvas.bind_all("<MouseWheel>", _on_mousewheel)
+
+        # Function to unbind mousewheel event when tab changes
+        def _unbind_mousewheel():
+            canvas.unbind_all("<MouseWheel>")
+
+        # Store the unbind function to be called when switching tabs
+        parent_frame.unbind_mousewheel = _unbind_mousewheel
+
+        # Function to bind mousewheel event when tab is selected
+        def _bind_mousewheel():
+            canvas.bind_all("<MouseWheel>", _on_mousewheel)
+
+        # Store the bind function to be called when this tab is selected
+        parent_frame.bind_mousewheel = _bind_mousewheel
+
+        # Bind frame configure event to adjust the canvas window
+        def _on_frame_configure(event):
+            canvas.configure(scrollregion=canvas.bbox("all"))
+
+        chart_frame.bind("<Configure>", _on_frame_configure)
+
+        # Adjust canvas width when the window resizes
+        def _on_canvas_configure(event):
+            canvas.itemconfig(canvas_window, width=event.width)
+
+        canvas.bind("<Configure>", _on_canvas_configure)
+
+    # Load statistics when the frame is shown
+    def on_frame_shown():
+        fetch_statistics()
+
+        # Enable mouse wheel scrolling for the default tab after statistics are loaded
+        if hasattr(user_graph_frame, "bind_mousewheel"):
+            user_graph_frame.bind_mousewheel()
+
+    frame.on_frame_shown = on_frame_shown
+
+    return frame
diff --git a/app/frontend/main.py b/app/frontend/main.py
index 0b5a2c9..4c2806e 100644
--- a/app/frontend/main.py
+++ b/app/frontend/main.py
@@ -16,6 +16,7 @@ from components.admin.category import category_frame
 from components.admin.dashboard import admin_dashboard_frame
 from components.admin.user_management import admin_user_management_frame
 from components.admin.shop_owner_management import admin_shop_owner_management_frame
+from components.admin.system_statistics import admin_system_statistics_frame
 from components.dashboard import dashboard_frame
 from components.user_details import user_details_frame
 from components.user_orders import user_orders_frame
@@ -302,6 +303,9 @@ def initialize_authenticated_frames(token):
     frames["admin_shop_owner_management"] = admin_shop_owner_management_frame(
         root, switch_frame, API_URL, token
     )
+    frames["admin_system_statistics"] = admin_system_statistics_frame(
+        root, switch_frame, API_URL, token
+    )
 
     # Place all authenticated frames
     for key, frame in frames.items():
-- 
GitLab