From 9ab7b5b591ec63226bc960ddb3231b7206063c9a Mon Sep 17 00:00:00 2001
From: a2-imeri <Alfret2.imeri@live.uwe.ac.uk>
Date: Thu, 20 Jun 2024 18:16:09 +0100
Subject: [PATCH] Add user Dashboard

---
 .../__pycache__/settings.cpython-310.pyc      | Bin 3629 -> 3680 bytes
 .../__pycache__/settings.cpython-39.pyc       | Bin 3537 -> 3708 bytes
 .../__pycache__/urls.cpython-310.pyc          | Bin 885 -> 941 bytes
 MisplaceAI/MisplaceAI/settings.py             |   3 +-
 MisplaceAI/MisplaceAI/urls.py                 |   2 +
 MisplaceAI/placement_rules/utils.py           |  71 ++++++++++++++----
 MisplaceAI/user_dashboard/__init__.py         |   0
 MisplaceAI/user_dashboard/admin.py            |   3 +
 MisplaceAI/user_dashboard/apps.py             |   6 ++
 MisplaceAI/user_dashboard/models.py           |   3 +
 MisplaceAI/user_dashboard/serializers.py      |   8 ++
 MisplaceAI/user_dashboard/tests.py            |   3 +
 MisplaceAI/user_dashboard/urls.py             |   7 ++
 MisplaceAI/user_dashboard/views.py            |  15 ++++
 frontend/src/App.js                           |   6 ++
 frontend/src/layouts/Navbar/Navbar.js         |  11 ++-
 .../src/pages/UserDashboard/UserDashboard.css |  17 +++++
 .../src/pages/UserDashboard/UserDashboard.js  |  44 +++++++++++
 frontend/src/services/api.js                  |   2 +-
 frontend/src/services/auth.js                 |   4 +-
 20 files changed, 186 insertions(+), 19 deletions(-)
 create mode 100644 MisplaceAI/user_dashboard/__init__.py
 create mode 100644 MisplaceAI/user_dashboard/admin.py
 create mode 100644 MisplaceAI/user_dashboard/apps.py
 create mode 100644 MisplaceAI/user_dashboard/models.py
 create mode 100644 MisplaceAI/user_dashboard/serializers.py
 create mode 100644 MisplaceAI/user_dashboard/tests.py
 create mode 100644 MisplaceAI/user_dashboard/urls.py
 create mode 100644 MisplaceAI/user_dashboard/views.py
 create mode 100644 frontend/src/pages/UserDashboard/UserDashboard.css
 create mode 100644 frontend/src/pages/UserDashboard/UserDashboard.js

diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc
index 8217cae40ee3843e77dc74817b9b954ad653e373..e960bd634688fe21b9c521f53e0f352cc4d3ddb8 100644
GIT binary patch
delta 107
zcmZ20^FW3-pO=@5fq{WxL3Bx4$41@`M)e>@1_mw#-^}8IoW$f*$4q@V(^J1VwWK67
zFTGf=pmOs}#%YX<LYobkpD+sYl@_NK#it||XC&n(7NtZ<Oz!27*sRN1&B4e!c_nWP
E0Mbz*zyJUM

delta 56
zcmaDLvsQ*TpO=@5fq{WxdbfXCz((E<MizZW28PXh7^g8Z3T_T&e!|EoG5G+8#O4In
MY7R!8$#-~L0G&k-LjV8(

diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-39.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-39.pyc
index 14ca3d9fd2ec57dde6a87341fbb00d4500a3edf3..31df9d414c9434dccb4753594eba6ed710b7b5d4 100644
GIT binary patch
delta 653
zcmca8{YQo`k(ZZ?fq{X+xx6qfKx87H3}ebf?U;J*bjB#b6c8zt!V@K&!W$)$!k5mF
z!k)sP&X6Loh%u8fMQ{#FI%AY*icpkTIzx)^BE}SvbjB3XIZRRFDPk$&DH17?DN-rY
zDKaUtbJ#K&qa;$~QsgrkQxsAZQ<O3pQ<Ud0rO2hIWHP46rKn~yrn5vzrl>_prKm?q
z*Qc;Y$)vDH$);#T$)&QVXi77rvZrWCGo&*`$){*XDWvjFU@S^VVN7ApQH)Zoa!6HN
zpp>Gska2<XLWUGwNrn_gNrn_XNrotuRFws)Df$Z;Qw$^-7O18eE@TABMX9CoEl^KY
zTgb@B5YCV%!NQQLn5B`WIf1b<j*)?ppMjAfk|B@DfFXrBm_gHMa|h#WMn=KSD$I`=
zrJ^K?Qj1IC(~1&vQ_J&<vg3<0a|?1(v&u^*v$JMQ&S6uZJd0Iiax?2oanX#Dk^(Dz
z{ha*d#GH)$;u0%k0|NuM&6C&y8Cf)$tBfa0amY<(=J3?9#AT*lVrfZ6YF<fZa$-ql
zex9CJc*tg3j&)3oT9e;!Ez%Zo_74h<cl7b`4|jEm_Yd-P_w)<CCE^?$<Q5+i6dD|Y
z5Z&C%ZNbQ9!N9;!WHI?PPklWj0|P^G69WSS3l9$?2eSYh3lj??A2SOJ8w-aR2M^PK
z7FG~uVPaunVq{`uV&eGE^oQj?ivSBV8ynMqreAFQU~!Nf2>$}h{byofVq*Ev^pAy|
q<v$Y(%YUZNETSw-V93J4#Ky?>kA(w7^KdXSF-kE?F!C`9F#-UVoS=LF

delta 491
zcmew(b5WWvk(ZZ?fq{WRXQxlvV&RE=GK>)uwPWhpqXbj9ql8j;(iu|NQ+U%EQur1z
zW-_Mm&tXYtj1o=}h!RN=Oc6?FN)b+HOc9yG6eXG>nj)4Wo+6PVnIe@Uogy=bEt4@y
zEJZd&E|W1uK1Cr#F_SSxX%170Y>ILwV~T8wN+x4EOO$wuYLrBZT9jl8dz4fPdz5sF
zdX!9kDtn5CG(#$Til#I}I#ZNvidK|dD(?iwqL38E6y_ZHDETUfRQUx8DcTDe7bq@d
zNYRmGNMV#@NYRyKh*C;bTA-Yww~#SKUy@;ga*Dx1Mvz>TN-Ez1)l`**jEoH740#eP
z45{*2YFX+N7%PJq89*?SA&<#`A%!`ZnL*QV^AyI}jEn-C4VWJ@PS$13n7oJe@8qeh
z>YFdJ1u!yI8BdPlkelqx;ko%Z$9yJ6jmhcUi#D6`m@~4OGcYg|nNKd_t*>WdU|=Y&
zV_;xl;o)KAU>0C!VP;|EV_{)qW91Ox;AUZBVPRrqVq{|C_|No*<v)u6nE#K7mFYjz
zZ#G`A97rC7f3f^$5oGz##KOeH@}KD+3p<F-@}KE5ix>+N7_zc3u`#m!W8nhP9E?ni
N98!!DjC_nji~vWnYBc}=

diff --git a/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc
index 8483a1b21baf23ab9ef05a13e9bf9e1254ecb39f..a1664ddfcf212cf1b6857548f07d35419a15896b 100644
GIT binary patch
delta 142
zcmey$ww9eYpO=@5fq{XcJGvxo`9xk>##<A$SDU1=r%0r7q)0Y1HZ!GjMRBJxX7Lnd
zr*fw<r!r>orZQ&nrAYNMnKMLjr$`4gfUu^_#(!mus#U^?1)2J##i>Q{DT&1yN%@II
gDf(5yr~;GonI15zOb%d{<>Fyv`Nqa4!N~F-01+N3O#lD@

delta 115
zcmZ3>{*{e4pO=@5fq{WxcDH|8!bDzK#&Z+3S8JqmMscSyX7LoIrgEn;r!r>orZQ&n
zrAYKLnKMLjr$`1ffUu_2W-i7u#>uOgZmTIVFfi~if}j8+4>O2{U;#z}Mjl3%?`%90
Hj4b~F6Ji#?

diff --git a/MisplaceAI/MisplaceAI/settings.py b/MisplaceAI/MisplaceAI/settings.py
index 001bfb4..a1a77a2 100644
--- a/MisplaceAI/MisplaceAI/settings.py
+++ b/MisplaceAI/MisplaceAI/settings.py
@@ -1,4 +1,5 @@
 """
+ MisplaceAi/MisplaceAI/settings.py
 Django settings for MisplaceAI project.
 
 Generated by 'django-admin startproject' using Django 4.0.2.
@@ -51,7 +52,7 @@ INSTALLED_APPS = [
     'process_misplaced_manager.apps.ProcessMisplacedManagerConfig',
     'rest_framework',
     'corsheaders',
- 
+    'user_dashboard',
     'rest_framework_simplejwt',
  
 ]
diff --git a/MisplaceAI/MisplaceAI/urls.py b/MisplaceAI/MisplaceAI/urls.py
index c1e4986..a747898 100644
--- a/MisplaceAI/MisplaceAI/urls.py
+++ b/MisplaceAI/MisplaceAI/urls.py
@@ -14,6 +14,8 @@ urlpatterns = [
     path('api/process_misplaced_manager/', include('process_misplaced_manager.urls', namespace='process_misplaced_manager')),
     path('api/results_viewer/', include('results_viewer.urls', namespace='results_viewer')),
     path('api/placement_rules/', include('placement_rules.urls')),
+    path('api/user_dashboard/', include('user_dashboard.urls')),   
+
 ]
 
 if settings.DEBUG:
diff --git a/MisplaceAI/placement_rules/utils.py b/MisplaceAI/placement_rules/utils.py
index e04857c..9bc52a1 100644
--- a/MisplaceAI/placement_rules/utils.py
+++ b/MisplaceAI/placement_rules/utils.py
@@ -4,64 +4,109 @@ from rules.models import Rule
 
 class PlacementRules:
     def __init__(self):
+        # Initialize the PlacementRules object and load rules from the database
         self.rules = self.load_rules_from_db()
 
     def load_rules_from_db(self):
+        """Load rules from the database and store them in a dictionary."""
         rules_dict = {}
+        # Retrieve all Rule objects from the database
         rules = Rule.objects.all()
         for rule in rules:
+            # Extract the name of the item associated with the rule
             item_name = rule.item.name
+            # Extract the list of allowed locations for this item
             allowed_locations = list(rule.locations.values_list('name', flat=True))
+            # Store the item name and its allowed locations in the dictionary
             rules_dict[item_name] = allowed_locations
         return rules_dict
 
     def check_placement(self, dataLocation):
-        """Check the placement of each object based on the rules."""
+        """Check the placement of each object based on the rules.
+        
+        Args:
+            dataLocation (list): A list of dictionaries, each representing an object's data.
+        
+        Returns:
+            list: A list of misplaced objects with their allowed locations.
+        """
         misplaced_objects = []
         print("-----------------------------------")
         print("--CHECK-PLACEMENT-FOR-EACH-OBJECT--")
+        
+        # Iterate over each object in the dataLocation list
         for obj in dataLocation:
             object_name = obj["class_name"]
+            # Retrieve the allowed locations for the current object from the rules dictionary
             allowed_locations = self.rules.get(object_name, [])
             is_placed_correctly = False
             print(f"Checking {object_name}, allowed on: {allowed_locations}")
 
+            # Check if the object is correctly placed in any of the allowed locations
             for location in allowed_locations:
                 is_on_top, _ = self.is_on_top_of(dataLocation, object_name, location)
                 if is_on_top:
                     is_placed_correctly = True
                     break
 
+            # If the object is not placed correctly and there are allowed locations, it is misplaced
             if not is_placed_correctly and allowed_locations:
-                print(
-                    f"{object_name} is misplaced. It should be on {', '.join(allowed_locations)}."
-                )
-                obj["allowed_locations"] = allowed_locations  # Add allowed locations to obj
+                print(f"{object_name} is misplaced. It should be on {', '.join(allowed_locations)}.")
+                # Add the allowed locations to the object for reference
+                obj["allowed_locations"] = allowed_locations
+                # Add the misplaced object to the list
                 misplaced_objects.append(obj)
         print("-----------------------------------")
         return misplaced_objects
 
     def is_on_top_of(self, dataLocation, top_object_name, bottom_object_name):
-        # Filter objects by class_name
-        top_objects = [
-            obj for obj in dataLocation if obj["class_name"] == top_object_name
-        ]
-        bottom_objects = [
-            obj for obj in dataLocation if obj["class_name"] == bottom_object_name
-        ]
+        """Check if any object of top_object_name is on top of any object of bottom_object_name.
+        
+        Args:
+            dataLocation (list): A list of dictionaries, each representing an object's data.
+            top_object_name (str): The class name of the object to be checked if on top.
+            bottom_object_name (str): The class name of the object to be checked if below.
+        
+        Returns:
+            tuple: A boolean indicating if the top object is on top of the bottom object, and a message.
+        """
+        # Filter objects by their class name
+        top_objects = [obj for obj in dataLocation if obj["class_name"] == top_object_name]
+        bottom_objects = [obj for obj in dataLocation if obj["class_name"] == bottom_object_name]
 
         # Define a function to check if two objects have horizontal overlap
         def has_horizontal_overlap(a, b):
+            """
+            Check if two objects have horizontal overlap based on their bounding box coordinates.
+
+            Args:
+                a (dict): Dictionary containing bounding box coordinates of the first object.
+                b (dict): Dictionary containing bounding box coordinates of the second object.
+
+            Returns:
+                bool: True if there is horizontal overlap, False otherwise.
+            """
+            # 'xmax' is the right edge and 'xmin' is the left edge of the bounding box.
+            # Check if there is no horizontal overlap:
+            # 1. a["xmax"] <= b["xmin"]: True if the right edge of 'a' is to the left of or exactly at the left edge of 'b'
+            # 2. a["xmin"] >= b["xmax"]: True if the left edge of 'a' is to the right of or exactly at the right edge of 'b'
+            # If either of the above conditions is true, there is no overlap.
+            # 'not' inverts this to check for overlap instead.
             return not (a["xmax"] <= b["xmin"] or a["xmin"] >= b["xmax"])
 
-        # Check if any of the top objects is on top of any of the bottom objects
+
+        # Check if any top object is on top of any bottom object
         for top in top_objects:
             for bottom in bottom_objects:
+                # Check if the top and bottom objects have horizontal overlap
                 if has_horizontal_overlap(top, bottom):
+                    # Check if the top object's ymax is within the y-range of the bottom object
                     if top["ymax"] <= bottom["ymax"] and top["ymax"] >= bottom["ymin"]:
+                        # Return True if the top object is found on top of the bottom object
                         return (
                             True,
                             f"Object {top['Object']} ({top['class_name']}) is on top of Object {bottom['Object']} ({bottom['class_name']})",
                         )
 
+        # Return False if no top object is found on top of a bottom object
         return False, f"No {top_object_name} is on top of any {bottom_object_name}."
diff --git a/MisplaceAI/user_dashboard/__init__.py b/MisplaceAI/user_dashboard/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/MisplaceAI/user_dashboard/admin.py b/MisplaceAI/user_dashboard/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/MisplaceAI/user_dashboard/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/MisplaceAI/user_dashboard/apps.py b/MisplaceAI/user_dashboard/apps.py
new file mode 100644
index 0000000..4b36bdd
--- /dev/null
+++ b/MisplaceAI/user_dashboard/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class UserDashboardConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'user_dashboard'
diff --git a/MisplaceAI/user_dashboard/models.py b/MisplaceAI/user_dashboard/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/MisplaceAI/user_dashboard/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/MisplaceAI/user_dashboard/serializers.py b/MisplaceAI/user_dashboard/serializers.py
new file mode 100644
index 0000000..46b3e49
--- /dev/null
+++ b/MisplaceAI/user_dashboard/serializers.py
@@ -0,0 +1,8 @@
+# MisplaceAI/user_dashboard/serializers.py
+from django.contrib.auth.models import User
+from rest_framework import serializers
+
+class UserSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = User
+        fields = ['username', 'email', 'first_name', 'last_name']
diff --git a/MisplaceAI/user_dashboard/tests.py b/MisplaceAI/user_dashboard/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/MisplaceAI/user_dashboard/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/MisplaceAI/user_dashboard/urls.py b/MisplaceAI/user_dashboard/urls.py
new file mode 100644
index 0000000..93f9170
--- /dev/null
+++ b/MisplaceAI/user_dashboard/urls.py
@@ -0,0 +1,7 @@
+# MisplaceAI/user_dashboard/urls.py
+from django.urls import path
+from .views import UserDashboardView
+
+urlpatterns = [
+    path('dashboard/', UserDashboardView.as_view(), name='user-dashboard'),
+]
diff --git a/MisplaceAI/user_dashboard/views.py b/MisplaceAI/user_dashboard/views.py
new file mode 100644
index 0000000..b428d17
--- /dev/null
+++ b/MisplaceAI/user_dashboard/views.py
@@ -0,0 +1,15 @@
+# MisplaceAI/user_dashboard/views.py
+from django.contrib.auth.models import User
+from rest_framework import generics
+from rest_framework.permissions import IsAuthenticated
+from .serializers import UserSerializer
+
+class UserDashboardView(generics.RetrieveAPIView):
+    queryset = User.objects.all()
+    serializer_class = UserSerializer
+    permission_classes = [IsAuthenticated]
+
+    def get_object(self):
+        user = self.request.user
+        print(f'User: {user}')  # Debugging statement
+        return user
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 1f8039e..c472e5b 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -13,6 +13,7 @@ import ManageItems from './pages/Items/ManageItemsPage';
 import ManageRulesPage from './pages/Rules/ManageRulesPage';
 import DetectionOptionsPage from './pages/DetectionOptions/DetectionOptionsPage';
 import NormalDetectionPage from './pages/NormalDetection/NormalDetectionPage';
+import UserDashboard from './pages/UserDashboard/UserDashboard';
 
 import ProtectedRoute from './firewall/ProtectedRoute';
 import RouteProtection from './firewall/RouteProtection';
@@ -71,6 +72,11 @@ function App() {
                 <NormalDetectionPage />
               </ProtectedRoute>
             } />
+            <Route path="/user/dashboard" element={
+              <ProtectedRoute>
+                <UserDashboard />
+              </ProtectedRoute>
+            } />
             <Route path="*" element={<HomePage />} /> {/* Catch-all route */}
           </Routes>
         </div>
diff --git a/frontend/src/layouts/Navbar/Navbar.js b/frontend/src/layouts/Navbar/Navbar.js
index 12293a3..749ee39 100644
--- a/frontend/src/layouts/Navbar/Navbar.js
+++ b/frontend/src/layouts/Navbar/Navbar.js
@@ -31,9 +31,14 @@ const Navbar = () => {
                             <Link className="nav-link" to="/">Home</Link>
                         </li>
                         {isAuthenticated && (
-                            <li className="nav-item">
-                                <Link className="nav-link" to="/detection-options">Detect Misplaced Items</Link>
-                            </li>
+                            <>
+                                <li className="nav-item">
+                                    <Link className="nav-link" to="/detection-options">Detect Misplaced Items</Link>
+                                </li>
+                                <li className="nav-item">
+                                    <Link className="nav-link" to="/user/dashboard">User Dashboard</Link>
+                                </li>
+                            </>
                         )}
                         {!isAuthenticated && (
                             <>
diff --git a/frontend/src/pages/UserDashboard/UserDashboard.css b/frontend/src/pages/UserDashboard/UserDashboard.css
new file mode 100644
index 0000000..e3fcffc
--- /dev/null
+++ b/frontend/src/pages/UserDashboard/UserDashboard.css
@@ -0,0 +1,17 @@
+/* src/pages/UserDashboard/UserDashboard.css */
+
+.user-dashboard {
+    padding: 20px;
+    background-color: #f9f9f9;
+    border-radius: 8px;
+    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+}
+
+.user-dashboard h1 {
+    margin-bottom: 20px;
+}
+
+.user-dashboard .user-info p {
+    margin: 10px 0;
+    font-size: 16px;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/UserDashboard/UserDashboard.js b/frontend/src/pages/UserDashboard/UserDashboard.js
new file mode 100644
index 0000000..94af599
--- /dev/null
+++ b/frontend/src/pages/UserDashboard/UserDashboard.js
@@ -0,0 +1,44 @@
+// src/pages/UserDashboard/UserDashboard.js
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import api from '../../services/api';
+import './UserDashboard.css';
+
+const UserDashboard = () => {
+    const [userData, setUserData] = useState(null);
+    const navigate = useNavigate();
+
+    useEffect(() => {
+        const fetchUserData = async () => {
+            try {
+                const response = await api.get('/api/user_dashboard/dashboard/');
+                setUserData(response.data);
+            } catch (error) {
+                console.error('Failed to fetch user data', error);
+                if (error.response && error.response.status === 401) {
+                    navigate('/login');
+                }
+            }
+        };
+
+        fetchUserData();
+    }, [navigate]);
+
+    if (!userData) {
+        return <div>Loading...</div>;
+    }
+
+    return (
+        <div className="user-dashboard">
+            <h1>User Dashboard</h1>
+            <div className="user-info">
+                <p><strong>Username:</strong> {userData.username}</p>
+                <p><strong>Email:</strong> {userData.email}</p>
+                <p><strong>First Name:</strong> {userData.first_name}</p>
+                <p><strong>Last Name:</strong> {userData.last_name}</p>
+            </div>
+        </div>
+    );
+};
+
+export default UserDashboard;
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index 6ca18ff..5e6e9d1 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -21,7 +21,7 @@ const api = axios.create({
 
 api.interceptors.request.use(
     config => {
-        const token = localStorage.getItem('adminToken');
+        const token = localStorage.getItem('token') || localStorage.getItem('adminToken');
         const csrfToken = getCsrfToken();
         if (token) {
             config.headers.Authorization = `Bearer ${token}`;
diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js
index 8f3f0ea..73b599b 100644
--- a/frontend/src/services/auth.js
+++ b/frontend/src/services/auth.js
@@ -1,10 +1,11 @@
-// frontend/src/services/auth.js
+// src/services/auth.js
 import api from './api';
 
 export const login = async (credentials) => {
     const response = await api.post('/api/auth/login/', credentials);
     if (response.data.access) {
         localStorage.setItem('token', response.data.access);
+        localStorage.setItem('isAuthenticated', true);
     }
     return response.data;
 };
@@ -16,6 +17,7 @@ export const register = async (userData) => {
 
 export const logout = () => {
     localStorage.removeItem('token');
+    localStorage.removeItem('isAuthenticated');
 };
 
 export const getCurrentUser = async () => {
-- 
GitLab