diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc index e960bd634688fe21b9c521f53e0f352cc4d3ddb8..3e6038a00a1f6a1ac6472eb94b2cba19f6f3c152 100644 Binary files a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc and b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc differ diff --git a/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc index a1664ddfcf212cf1b6857548f07d35419a15896b..3269e3e4f24d50ce020e28131d18a731824bf4cb 100644 Binary files a/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc and b/MisplaceAI/MisplaceAI/__pycache__/urls.cpython-310.pyc differ diff --git a/MisplaceAI/camera_integration/__init__.py b/MisplaceAI/camera_integration/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/MisplaceAI/camera_integration/admin.py b/MisplaceAI/camera_integration/admin.py deleted file mode 100644 index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..0000000000000000000000000000000000000000 --- a/MisplaceAI/camera_integration/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/MisplaceAI/camera_integration/apps.py b/MisplaceAI/camera_integration/apps.py deleted file mode 100644 index 991a054e1c4a8c9224838af49dc3d4ba5d936389..0000000000000000000000000000000000000000 --- a/MisplaceAI/camera_integration/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class CameraIntegrationConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'camera_integration' diff --git a/MisplaceAI/camera_integration/models.py b/MisplaceAI/camera_integration/models.py deleted file mode 100644 index 71a836239075aa6e6e4ecb700e9c42c95c022d91..0000000000000000000000000000000000000000 --- a/MisplaceAI/camera_integration/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/MisplaceAI/camera_integration/tests.py b/MisplaceAI/camera_integration/tests.py deleted file mode 100644 index 7ce503c2dd97ba78597f6ff6e4393132753573f6..0000000000000000000000000000000000000000 --- a/MisplaceAI/camera_integration/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/MisplaceAI/camera_integration/views.py b/MisplaceAI/camera_integration/views.py deleted file mode 100644 index 91ea44a218fbd2f408430959283f0419c921093e..0000000000000000000000000000000000000000 --- a/MisplaceAI/camera_integration/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/MisplaceAI/process_misplaced_manager/models.py b/MisplaceAI/process_misplaced_manager/models.py index eff9fe303826f704c79cf0c237cccfb6db7c294d..5ad5b8f214c0fb02275e937fb5820fafb6a8dcdc 100644 --- a/MisplaceAI/process_misplaced_manager/models.py +++ b/MisplaceAI/process_misplaced_manager/models.py @@ -1,14 +1,19 @@ # MisplaceAI/process_misplaced_manager/models.py from django.db import models +from django.contrib.auth.models import User class UploadedImage(models.Model): image = models.ImageField(upload_to='images/') uploaded_at = models.DateTimeField(auto_now_add=True) + +class UserVideoFramePreference(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + frame_interval = models.IntegerField(default=1) # interval in seconds + class UploadedVideo(models.Model): video = models.FileField(upload_to='videos/') uploaded_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return str(self.video) \ No newline at end of file + user = models.ForeignKey(User, on_delete=models.CASCADE) + user_video_frame_preference = models.ForeignKey(UserVideoFramePreference, on_delete=models.CASCADE, null=True, blank=True) \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/serializers.py b/MisplaceAI/process_misplaced_manager/serializers.py index a071586d25537956e0aa34fe899d40fff132ada1..56877789c63cfe3043b24ac3ff194d9dda880f84 100644 --- a/MisplaceAI/process_misplaced_manager/serializers.py +++ b/MisplaceAI/process_misplaced_manager/serializers.py @@ -11,4 +11,4 @@ class UploadedImageSerializer(serializers.ModelSerializer): class UploadedVideoSerializer(serializers.ModelSerializer): class Meta: model = UploadedVideo - fields = ['id', 'video', 'uploaded_at'] + fields = ['id', 'video', 'uploaded_at', 'user', 'user_video_frame_preference'] \ No newline at end of file diff --git a/MisplaceAI/process_misplaced_manager/urls.py b/MisplaceAI/process_misplaced_manager/urls.py index 9d410686acde72b829122104533e0c2b3951a687..7c43f0498adba3e8f0ccd791d19d4f6e3e71bf4d 100644 --- a/MisplaceAI/process_misplaced_manager/urls.py +++ b/MisplaceAI/process_misplaced_manager/urls.py @@ -4,7 +4,7 @@ from rest_framework.routers import DefaultRouter from .views import ( UploadedImageViewSet, UploadedVideoViewSet, normal_detection, - display_results, display_video_results + display_results, display_video_results,upload_video ) router = DefaultRouter() @@ -17,7 +17,7 @@ urlpatterns = [ path('', include(router.urls)), path('normal-detection/', normal_detection, name='normal_detection'), # path('segmentation-detection/', segmentation_detection, name='segmentation_detection'), - # path('upload-video/', upload_video, name='upload_video'), + path('upload-video/', upload_video, name='upload_video'), path('video-results/<int:video_id>/', display_video_results, name='display_video_results'), path('display-results/<int:image_id>/', display_results, name='display_results'), ] diff --git a/MisplaceAI/process_misplaced_manager/views.py b/MisplaceAI/process_misplaced_manager/views.py index 8fb50e25e8032132ffcd5339bbf2b2a61007ab1e..7a3c88ec22b9c466254cb30ef43d3ef9d75f875d 100644 --- a/MisplaceAI/process_misplaced_manager/views.py +++ b/MisplaceAI/process_misplaced_manager/views.py @@ -5,15 +5,23 @@ from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from django.shortcuts import get_object_or_404 -from .models import UploadedImage, UploadedVideo +from .models import UploadedImage, UploadedVideo, UserVideoFramePreference from .serializers import UploadedImageSerializer, UploadedVideoSerializer from item_detector.utils import run_inference, load_model, create_category_index_from_labelmap from placement_rules.utils import PlacementRules -from results_viewer.utils import visualize_misplaced_objects, visualize_video_misplaced_objects +from results_viewer.utils import visualize_misplaced_objects, visualize_video_misplaced_objects, visualize_pil_misplaced_objects from django.core.files.base import ContentFile import base64 import os from PIL import Image, ExifTags +import logging +import cv2 +from django.conf import settings +from django.http import JsonResponse +import numpy as np +from moviepy.editor import VideoFileClip, ImageSequenceClip + +logger = logging.getLogger(__name__) MODEL_PATH = "models/research/object_detection/faster_rcnn_resnet50_v1_1024x1024_coco17_tpu-8/saved_model" LABEL_MAP_PATH = "models/research/object_detection/data/mscoco_label_map.pbtxt" @@ -26,10 +34,7 @@ class UploadedImageViewSet(viewsets.ModelViewSet): serializer_class = UploadedImageSerializer permission_classes = [IsAuthenticated] -class UploadedVideoViewSet(viewsets.ModelViewSet): - queryset = UploadedVideo.objects.all() - serializer_class = UploadedVideoSerializer - permission_classes = [IsAuthenticated] + @api_view(['POST']) @permission_classes([IsAuthenticated]) @@ -101,17 +106,297 @@ def display_results(request, image_id): } return Response(response_data, status=status.HTTP_200_OK) + + +################################################################################################# +################################## Video Upload and Processing ################################# + + + + + + + + +class UploadedVideoViewSet(viewsets.ModelViewSet): + queryset = UploadedVideo.objects.all() + serializer_class = UploadedVideoSerializer + permission_classes = [IsAuthenticated] + +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +def upload_video(request): + print("Received request to upload video") + print("Request data:", request.data) + print("Request FILES:", request.FILES) + + if 'video' not in request.FILES: + print("No video file provided in request") + return Response({'error': 'No video file provided'}, status=status.HTTP_400_BAD_REQUEST) + + frame_interval = int(request.data.get('frame_interval', 1)) + user_video_frame_preference, created = UserVideoFramePreference.objects.get_or_create(user=request.user) + user_video_frame_preference.frame_interval = frame_interval + user_video_frame_preference.save() + + serializer = UploadedVideoSerializer(data={'video': request.FILES['video'], 'user': request.user.id, 'user_video_frame_preference': user_video_frame_preference.id}) + if serializer.is_valid(): + print("Serializer data is valid") + serializer.save() + print("Video successfully saved:", serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED) + else: + print("Serializer errors:", serializer.errors) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + @api_view(['GET']) @permission_classes([IsAuthenticated]) def display_video_results(request, video_id): - video = get_object_or_404(UploadedVideo, id=video_id) - video_path = video.video.path + print("Received request to display video results for video ID", video_id) + try: + video = get_object_or_404(UploadedVideo, id=video_id) + video_path = video.video.path - detected_objects, misplaced_objects = visualize_video_misplaced_objects(video_path, detection_model, category_index) + print("Processing video at path:", video_path) + frame_interval = video.user_video_frame_preference.frame_interval if video.user_video_frame_preference else 1 + detected_objects, misplaced_objects, output_video_path = process_video_for_misplaced_objects(video_path, frame_interval) + print - response_data = { - 'video_url': video.video.url, - 'detected_objects': detected_objects, - 'misplaced_objects': misplaced_objects - } - return Response(response_data, status=status.HTTP_200_OK) + response_data = { + 'video_url': request.build_absolute_uri(video.video.url), + 'output_video_url': request.build_absolute_uri(settings.MEDIA_URL + 'videos/' + os.path.basename(output_video_path)), + 'detected_objects': detected_objects, + 'misplaced_objects': misplaced_objects # Ensure misplaced_objects include 'allowed_locations' + } + + print("\nDEBUG FOR misplaced_objects: ", misplaced_objects) + + return Response(response_data, status=status.HTTP_200_OK) + except Exception as e: + print(f"Error processing video results: {e}") + return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + +def process_video_for_misplaced_objects(video_path, frame_interval): + print("Starting object detection for video:", video_path) + cap = cv2.VideoCapture(video_path) + print("Video capture object created") + frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + print("Frame width:", frame_width) + frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + print("Frame height:", frame_height) + fps = int(cap.get(cv2.CAP_PROP_FPS)) + print("FPS:", fps) + + misplaced_objects_all_frames = [] + print("Misplaced objects list created") + detected_objects_all_frames = [] + print("Detected objects list created") + + frame_count = 0 + frame_interval_frames = frame_interval * fps + annotated_frames = [] + + while cap.isOpened(): + print(f"Processing frame {frame_count}") + ret, frame = cap.read() + print("Frame read") + if not ret: + break + + if frame_count % frame_interval_frames == 0: + print("Frame read successfully") + image_np = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + print("Frame converted to RGB") + + # Convert the frame to PIL image + image_pil = Image.fromarray(image_np) + + detected_objects = run_inference(detection_model, category_index, image_pil) + print(f"Detected objects in frame {frame_count}: {detected_objects}") + + placement_rules = PlacementRules() + misplaced_objects = placement_rules.check_placement(detected_objects) + print(f"Misplaced objects in frame {frame_count}: {misplaced_objects}") + + detected_objects_all_frames.append(detected_objects) + misplaced_objects_all_frames.append(misplaced_objects) + + # Annotate the frame with bounding boxes and labels + annotated_image_pil = visualize_pil_misplaced_objects(image_pil, detected_objects, misplaced_objects) + annotated_image_np = np.array(annotated_image_pil) + annotated_frames.append(annotated_image_np) + + frame_count += 1 + + cap.release() + + # Create a video with a 1-second delay between each frame + output_video_path = os.path.join(settings.MEDIA_ROOT, 'videos', os.path.basename(video_path).replace('.mp4', '_annotated.mp4')) + annotated_clip = ImageSequenceClip([np.array(frame) for frame in annotated_frames], fps=1) + annotated_clip.write_videofile(output_video_path, codec='libx264', audio_codec='aac') + + print("Finished processing video:", output_video_path) + return detected_objects_all_frames, misplaced_objects_all_frames, output_video_path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# class UploadedVideoViewSet(viewsets.ModelViewSet): +# queryset = UploadedVideo.objects.all() +# serializer_class = UploadedVideoSerializer +# permission_classes = [IsAuthenticated] + +# @api_view(['POST']) +# @permission_classes([IsAuthenticated]) +# def upload_video(request): +# print("Received request to upload video") +# print("Request data:", request.data) +# print("Request FILES:", request.FILES) + +# if 'video' not in request.FILES: +# print("No video file provided in request") +# return Response({'error': 'No video file provided'}, status=status.HTTP_400_BAD_REQUEST) + +# serializer = UploadedVideoSerializer(data={'video': request.FILES['video']}) +# if serializer.is_valid(): +# print("Serializer data is valid") +# serializer.save() +# print("Video successfully saved:", serializer.data) +# return Response(serializer.data, status=status.HTTP_201_CREATED) +# else: +# print("Serializer errors:", serializer.errors) +# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + + + + +# @api_view(['GET']) +# @permission_classes([IsAuthenticated]) +# def display_video_results(request, video_id): +# print("Received request to display video results for video ID", video_id) +# try: +# video = get_object_or_404(UploadedVideo, id=video_id) +# video_path = video.video.path + +# print("Processing video at path:", video_path) +# detected_objects, misplaced_objects, output_video_path = process_video_for_misplaced_objects(video_path) + +# response_data = { +# 'video_url': request.build_absolute_uri(video.video.url), +# 'output_video_url': request.build_absolute_uri(settings.MEDIA_URL + 'videos/' + os.path.basename(output_video_path)), +# 'detected_objects': detected_objects, +# 'misplaced_objects': misplaced_objects +# } +# print("Successfully processed video and generated results") +# return Response(response_data, status=status.HTTP_200_OK) +# except Exception as e: +# print(f"Error processing video results: {e}") +# return JsonResponse({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + + + + + + + + + + + + + + + + + + +# def process_video_for_misplaced_objects(video_path): +# print("Starting object detection for video:", video_path) +# cap = cv2.VideoCapture(video_path) +# print("Video capture object created") +# frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) +# print("Frame width:", frame_width) +# frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) +# print("Frame height:", frame_height) +# fps = int(cap.get(cv2.CAP_PROP_FPS)) +# print("FPS:", fps) + +# output_video_path = os.path.join(settings.MEDIA_ROOT, 'videos', os.path.basename(video_path).replace('.mp4', '_annotated.mp4')) +# print("Output video path:", output_video_path) +# out = cv2.VideoWriter(output_video_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (frame_width, frame_height)) +# print("Video writer object created") +# misplaced_objects_all_frames = [] +# print("Misplaced objects list created") +# detected_objects_all_frames = [] +# print("Detected objects list created") + +# frame_count = 0 + +# while cap.isOpened(): +# print(f"Processing frame {frame_count}") +# ret, frame = cap.read() +# print("Frame read") +# if not ret: +# break +# print("Frame read successfully") +# image_np = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) +# print("Frame converted to RGB") + +# # Convert the frame to PIL image +# image_pil = Image.fromarray(image_np) + +# detected_objects = run_inference(detection_model, category_index, image_pil) +# print(f"Detected objects in frame {frame_count}: {detected_objects}") + +# placement_rules = PlacementRules() +# misplaced_objects = placement_rules.check_placement(detected_objects) +# print(f"Misplaced objects in frame {frame_count}: {misplaced_objects}") + +# detected_objects_all_frames.append(detected_objects) +# misplaced_objects_all_frames.append(misplaced_objects) + +# # Annotate the frame with bounding boxes and labels +# annotated_image_pil = visualize_pil_misplaced_objects(image_pil, detected_objects, misplaced_objects) +# annotated_image_np = np.array(annotated_image_pil) +# out.write(cv2.cvtColor(annotated_image_np, cv2.COLOR_RGB2BGR)) + +# frame_count += 1 + +# cap.release() +# out.release() + +# print("Finished processing video:", output_video_path) +# return detected_objects_all_frames, misplaced_objects_all_frames, output_video_path \ No newline at end of file diff --git a/MisplaceAI/requirements.txt b/MisplaceAI/requirements.txt index b87cc85ba12c7f7490f61a7162881c783570cded..288deadd1040d17007e53ed2dfccf9c16363bb05 100644 --- a/MisplaceAI/requirements.txt +++ b/MisplaceAI/requirements.txt @@ -6,3 +6,5 @@ opencv-python==4.5.5.64 djangorestframework==3.12.4 django-cors-headers==3.10.0 djangorestframework-simplejwt==4.6.0 +channels==3.0.3 +moviepy==1.0.3 diff --git a/MisplaceAI/results_viewer/utils.py b/MisplaceAI/results_viewer/utils.py index e0b08f59a8f23096efa80623b3b8213628069cb3..9a59bd42c70978ea07d5204fc3dfef1c9f26e0bd 100644 --- a/MisplaceAI/results_viewer/utils.py +++ b/MisplaceAI/results_viewer/utils.py @@ -5,6 +5,7 @@ import matplotlib.patches as patches import os from item_detector.utils import run_inference from placement_rules.utils import PlacementRules +from PIL import Image, ImageDraw, ImageFont import cv2 @@ -59,6 +60,30 @@ def visualize_misplaced_objects(image_path, detected_objects, misplaced_objects) + + + +def visualize_pil_misplaced_objects(image_pil, detected_objects, misplaced_objects): + """Visualize misplaced objects with annotations on a PIL image.""" + draw = ImageDraw.Draw(image_pil) + width, height = image_pil.size + + misplaced_names = [obj["class_name"] for obj in misplaced_objects] + + for obj in detected_objects: + ymin, xmin, ymax, xmax = [ + obj["ymin"] * height, + obj["xmin"] * width, + obj["ymax"] * height, + obj["xmax"] * width, + ] + + color = "green" if obj["class_name"] not in misplaced_names else "red" + draw.rectangle([xmin, ymin, xmax, ymax], outline=color, width=2) + draw.text((xmin, ymin), f"{'Misplaced: ' if obj['class_name'] in misplaced_names else ''}{obj['class_name']}", fill=color) + + return image_pil + def visualize_video_misplaced_objects(video_path, detection_model, category_index): cap = cv2.VideoCapture(video_path) misplaced_objects_list = [] diff --git a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc index da56f3ae2e58547857662327640353194ee94114..51706342ccfe8763354ef1f34e42cb72f0f8e3ac 100644 Binary files a/MisplaceAI/rules/__pycache__/views.cpython-310.pyc and b/MisplaceAI/rules/__pycache__/views.cpython-310.pyc differ diff --git a/MisplaceAI/rules/migrations/0001_initial.py b/MisplaceAI/rules/migrations/0001_initial.py index c88616d13dc5931656d8064a1534232f6d91f4a8..b569a06ea10df6e6f1f2301622ef44364a8e75c3 100644 --- a/MisplaceAI/rules/migrations/0001_initial.py +++ b/MisplaceAI/rules/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2 on 2024-05-15 20:43 +# Generated by Django 3.2 on 2024-06-25 20:00 from django.conf import settings from django.db import migrations, models diff --git a/MisplaceAI/rules/migrations/__pycache__/0001_initial.cpython-310.pyc b/MisplaceAI/rules/migrations/__pycache__/0001_initial.cpython-310.pyc index 41f9305df4cc8cb517a5b241eaef71e4d84b52e9..e717ee00d97b36956a3e9d972c6d89a8a5d84a4f 100644 Binary files a/MisplaceAI/rules/migrations/__pycache__/0001_initial.cpython-310.pyc and b/MisplaceAI/rules/migrations/__pycache__/0001_initial.cpython-310.pyc differ diff --git a/MisplaceAI/rules/views.py b/MisplaceAI/rules/views.py index 421e5d5edbc3829c0240abc39672d7db992c9af6..8ab33c19bd8020ce1892dedff15ba3f07cbe9736 100644 --- a/MisplaceAI/rules/views.py +++ b/MisplaceAI/rules/views.py @@ -1,3 +1,4 @@ +# misplaceAI/rules/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status diff --git a/frontend/graph.png b/frontend/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..27111b9c1a8b4f85142e36c0721d82335d2ffa87 Binary files /dev/null and b/frontend/graph.png differ diff --git a/frontend/src/App.js b/frontend/src/App.js index b2783fb88a4da604cd4933f78856a9a8485c29c7..1a19e4f7d61d556efceda0615a8e3ffdd0adad06 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -15,6 +15,8 @@ import DetectionOptionsPage from './pages/DetectionOptions/DetectionOptionsPage' import NormalDetectionPage from './pages/NormalDetection/NormalDetectionPage'; import UserDashboard from './pages/UserDashboard/UserDashboard'; import UserProfile from './pages/UserProfile/UserProfile'; +import VideoDetectionPage from './pages/Video/VideoDetectionPage'; // Import the new page + import ProtectedRoute from './firewall/ProtectedRoute'; import RouteProtection from './firewall/RouteProtection'; @@ -83,6 +85,16 @@ function App() { <UserProfile /> </ProtectedRoute> } /> + <Route path="/user/manage-rules" element={ + <ProtectedRoute> + <ManageRulesPage /> + </ProtectedRoute> + } /> + <Route path="/upload-video" element={ // Add the new route + <ProtectedRoute> + <VideoDetectionPage /> + </ProtectedRoute> + } /> <Route path="*" element={<HomePage />} /> {/* Catch-all route */} </Routes> diff --git a/frontend/src/ObjectDetection.js b/frontend/src/ObjectDetection.js deleted file mode 100644 index 6fd42983d8839e938c0b6c68470ec2c8796147c0..0000000000000000000000000000000000000000 --- a/frontend/src/ObjectDetection.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, { useRef, useEffect, useState } from 'react'; -import * as cocoSsd from '@tensorflow-models/coco-ssd'; -import '@tensorflow/tfjs'; - -const ObjectDetection = () => { - const videoRef = useRef(null); - const canvasRef = useRef(null); - const [model, setModel] = useState(null); - - useEffect(() => { - console.log('Loading model...'); - const loadModel = async () => { - try { - const loadedModel = await cocoSsd.load(); - setModel(loadedModel); - console.log('Model loaded'); - } catch (error) { - console.error('Error loading model:', error); - } - }; - loadModel(); - - const setupCamera = async () => { - console.log('Setting up camera...'); - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - try { - const stream = await navigator.mediaDevices.getUserMedia({ - video: { width: 640, height: 480 } - }); - videoRef.current.srcObject = stream; - console.log('Camera setup complete'); - } catch (error) { - console.error('Error setting up camera:', error); - } - } else { - console.error('getUserMedia not supported in this browser'); - alert('Your browser does not support getUserMedia API'); - } - }; - setupCamera(); - }, []); - - useEffect(() => { - if (model) { - console.log('Starting detection...'); - const detectObjects = async () => { - if (videoRef.current && videoRef.current.readyState === 4) { - try { - const predictions = await model.detect(videoRef.current); - console.log('Predictions:', predictions); - drawPredictions(predictions); - } catch (error) { - console.error('Error detecting objects:', error); - } - } - requestAnimationFrame(detectObjects); - }; - detectObjects(); - } - }, [model]); - - const drawPredictions = predictions => { - const ctx = canvasRef.current.getContext('2d'); - ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); - predictions.forEach(prediction => { - const [x, y, width, height] = prediction.bbox; - ctx.strokeStyle = 'green'; - ctx.lineWidth = 4; - ctx.strokeRect(x, y, width, height); - ctx.fillStyle = 'green'; - ctx.font = '18px Arial'; - ctx.fillText( - `${prediction.class} (${Math.round(prediction.score * 100)}%)`, - x, - y > 10 ? y - 5 : 10 - ); - }); - }; - - return ( - <div style={{ position: 'relative', width: '640px', height: '480px' }}> - <video - ref={videoRef} - autoPlay - playsInline - muted - width="640" - height="480" - style={{ position: 'absolute', top: 0, left: 0, zIndex: 1 }} - /> - <canvas - ref={canvasRef} - width="640" - height="480" - style={{ position: 'absolute', top: 0, left: 0, zIndex: 2 }} - /> - </div> - ); -}; - -export default ObjectDetection; diff --git a/frontend/src/components/Common/Form/InputField.js b/frontend/src/components/Common/Form/InputField.js deleted file mode 100644 index 61dbb3aeefff43e41a18909b92f4133cd5be0baf..0000000000000000000000000000000000000000 --- a/frontend/src/components/Common/Form/InputField.js +++ /dev/null @@ -1,30 +0,0 @@ -// src/components/Common/Form/InputField.js -import React from 'react'; -import PropTypes from 'prop-types'; - -const InputField = ({ label, type, value, onChange, required }) => ( - <div className="form-group"> - <label>{label}</label> - <input - type={type} - className="form-control" - value={value} - onChange={onChange} - required={required} - /> - </div> -); - -InputField.propTypes = { - label: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - required: PropTypes.bool -}; - -InputField.defaultProps = { - required: false -}; - -export default InputField; diff --git a/frontend/src/components/Rules/AddRule.js b/frontend/src/components/Rules/AddRule.js index 390f35831946476159ffe522025f4b836961ffe7..16e792aff78e1d2ffb06cbd1e51c89ab354a0af1 100644 --- a/frontend/src/components/Rules/AddRule.js +++ b/frontend/src/components/Rules/AddRule.js @@ -4,6 +4,9 @@ import { addRule } from '../../services/ruleApi'; import { getItems } from '../../services/itemApi'; import { getLocations } from '../../services/locationApi'; import '../../styles/main.css'; +import '../../styles/_forms.css'; +import '../../styles/checkbox/checkbox.css'; +import '../../styles/_responsive.css'; const AddRule = ({ onRuleAdded }) => { const [items, setItems] = useState([]); @@ -27,6 +30,20 @@ const AddRule = ({ onRuleAdded }) => { fetchData(); }, []); + const handleItemChange = (itemId) => { + setSelectedItem(itemId); + }; + + const handleLocationChange = (locationId) => { + setSelectedLocations((prev) => { + if (prev.includes(locationId)) { + return prev.filter(id => id !== locationId); + } else { + return [...prev, locationId]; + } + }); + }; + const handleSubmit = async (e) => { e.preventDefault(); try { @@ -46,35 +63,45 @@ const AddRule = ({ onRuleAdded }) => { <form onSubmit={handleSubmit}> <div className="form-group"> <label>Item</label> - <select - className="form-control" - value={selectedItem} - onChange={(e) => setSelectedItem(e.target.value)} - required - > - <option value="" disabled>Select an item</option> + <div className="checkbox-container"> {items.map((item) => ( - <option key={item.id} value={item.id}> - {item.name} - </option> + <label key={item.id} className="checkbox-wrapper-12"> + <div className="cbx"> + <input + type="checkbox" + checked={selectedItem === item.id} + onChange={() => handleItemChange(item.id)} + /> + <label></label> + <svg width="15" height="14" viewBox="0 0 15 14" fill="none"> + <path d="M2 8.36364L6.23077 12L13 2"></path> + </svg> + </div> + <span>{item.name}</span> + </label> ))} - </select> + </div> </div> <div className="form-group"> <label>Locations</label> - <select - className="form-control" - multiple - value={selectedLocations} - onChange={(e) => setSelectedLocations(Array.from(e.target.selectedOptions, option => option.value))} - required - > + <div className="checkbox-container"> {locations.map((location) => ( - <option key={location.id} value={location.id}> - {location.name} - </option> + <label key={location.id} className="checkbox-wrapper-12"> + <div className="cbx"> + <input + type="checkbox" + checked={selectedLocations.includes(location.id)} + onChange={() => handleLocationChange(location.id)} + /> + <label></label> + <svg width="15" height="14" viewBox="0 0 15 14" fill="none"> + <path d="M2 8.36364L6.23077 12L13 2"></path> + </svg> + </div> + <span>{location.name}</span> + </label> ))} - </select> + </div> </div> <button type="submit" className="btn btn-primary">Add Rule</button> </form> diff --git a/frontend/src/components/Rules/GetRules.js b/frontend/src/components/Rules/GetRules.js index 28ff36d62a5b2381119382ff71109448ac6c1faf..971b826776a7946d16c52b09597eafed31e00fc1 100644 --- a/frontend/src/components/Rules/GetRules.js +++ b/frontend/src/components/Rules/GetRules.js @@ -1,7 +1,9 @@ // src/components/Rules/GetRules.js + import React, { useEffect, useState } from 'react'; import { getRules, deleteRule } from '../../services/ruleApi'; -import '../../styles/main.css'; // Ensure this is the correct path to main.css +import '../../styles/main.css'; +import '../../styles/dashboard.css'; const GetRules = ({ onEditRule, onDeleteRule, refresh }) => { const [rules, setRules] = useState([]); @@ -21,19 +23,23 @@ const GetRules = ({ onEditRule, onDeleteRule, refresh }) => { }; return ( - <div className="rules-list"> - <h2>Rules List</h2> - <ul className="list-group"> - {rules.map(rule => ( - <li key={rule.id} className="list-group-item"> - <span>{rule.item.name} - {rule.locations.map(location => location.name).join(', ')}</span> - <div> - <button className="btn btn-secondary" onClick={() => onEditRule(rule)}>Edit</button> - <button className="btn btn-danger" onClick={() => handleDelete(rule.id)}>Delete</button> - </div> - </li> - ))} - </ul> + <div className="dashboard-card rules-list"> + <div className="card card-wide"> + <div className="card-body"> + <h2 className="card-title">Rules List</h2> + <ul className="list-group"> + {rules.map(rule => ( + <li key={rule.id} className="list-group-item"> + <span>{rule.item.name} - {rule.locations.map(location => location.name).join(', ')}</span> + <div> + <button className="btn btn-secondary" onClick={() => onEditRule(rule)}>Edit</button> + <button className="btn btn-danger" onClick={() => handleDelete(rule.id)}>Delete</button> + </div> + </li> + ))} + </ul> + </div> + </div> </div> ); }; diff --git a/frontend/src/components/detection/DetectionContainer.js b/frontend/src/components/detection/DetectionContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..1bbd552db068b7d6ac0378b3055a43e5ba45524e --- /dev/null +++ b/frontend/src/components/detection/DetectionContainer.js @@ -0,0 +1,25 @@ +// src/components/detection/DetectionContainer.js + +import React from 'react'; +import '../../styles/main.css'; // Ensure this path is correct based on your project structure + +const DetectionContainer = ({ title, children }) => { + return ( + <div className="pages-container-center"> + <div className="row justify-content-center"> + <div className="col-12 col-md-10 col-lg-8"> + <div className="card shadow-lg border-0"> + <div className="card-header text-center bg-primary text-white py-4"> + <h1 className="mb-0">{title}</h1> + </div> + <div className="card-body p-5"> + {children} + </div> + </div> + </div> + </div> + </div> + ); +}; + +export default DetectionContainer; diff --git a/frontend/src/components/detection/DetectionResults.js b/frontend/src/components/detection/DetectionResults.js new file mode 100644 index 0000000000000000000000000000000000000000..6d767ea2d7a395e971f0110f4be22681be02eb05 --- /dev/null +++ b/frontend/src/components/detection/DetectionResults.js @@ -0,0 +1,35 @@ +// src/components/detection/DetectionResults.js +import React from 'react'; +import '../../styles/main.css'; + +const DetectionResults = ({ result }) => { + return ( + result && ( + <div className="mt-5"> + <h2>Detection Results</h2> + {result.error ? ( + <p className="text-danger">{result.error}</p> + ) : ( + <> + {result.output_video_url && ( + <video controls src={result.output_video_url} className="img-fluid"></video> + )} + {result.output_image_url && ( + <img src={result.output_image_url} alt="Detected Objects" className="img-fluid" /> + )} + <h3 className="mt-4">Misplaced Objects</h3> + <ul className="list-group"> + {result.misplaced_objects.flat().map((obj, index) => ( + <li key={index} className="list-group-item"> + {obj.class_name} is misplaced. Allowed locations: {(obj.allowed_locations || []).join(", ")} + </li> + ))} + </ul> + </> + )} + </div> + ) + ); +}; + +export default DetectionResults; diff --git a/frontend/src/components/detection/FileUploadForm.js b/frontend/src/components/detection/FileUploadForm.js new file mode 100644 index 0000000000000000000000000000000000000000..cf9a25086a17257a929c1d5e0ed00cb3f71e1899 --- /dev/null +++ b/frontend/src/components/detection/FileUploadForm.js @@ -0,0 +1,27 @@ +// src/components/detection/FileUploadForm.js +import React from 'react'; +import '../../styles/main.css'; + +const FileUploadForm = ({ handleFileChange, handleSubmit, isLoading, children }) => { + return ( + <form id="uploadForm" className="text-center" onSubmit={handleSubmit} encType="multipart/form-data"> + <div className="form-group"> + {children} + <label className="h5 d-block mt-3"> + <input + type="file" + accept="video/*,image/*" + className="form-control-file" + onChange={handleFileChange} + disabled={isLoading} + /> + </label> + </div> + <button type="submit" className="btn btn-success btn-lg mt-4" disabled={isLoading}> + <i className="fas fa-upload"></i> Upload + </button> + </form> + ); +}; + +export default FileUploadForm; diff --git a/frontend/src/components/detection/LoadingIndicator.js b/frontend/src/components/detection/LoadingIndicator.js new file mode 100644 index 0000000000000000000000000000000000000000..b40ed79f5200f65dddfe0f0f3068f5af88a822ee --- /dev/null +++ b/frontend/src/components/detection/LoadingIndicator.js @@ -0,0 +1,17 @@ +// src/components/detection/LoadingIndicator.js +import React from 'react'; +import loadingGif from '../../assets/loading.gif'; +import '../../styles/main.css'; + +const LoadingIndicator = ({ isLoading, message }) => { + return ( + isLoading && ( + <div className="text-center"> + <img src={loadingGif} alt="Loading..." className="img-fluid" /> + <p>{message}</p> + </div> + ) + ); +}; + +export default LoadingIndicator; diff --git a/frontend/src/components/detection/UploadForm.js b/frontend/src/components/detection/UploadForm.js new file mode 100644 index 0000000000000000000000000000000000000000..d8c00f4734d3fa340038bcae6c26222cdb11e5d8 --- /dev/null +++ b/frontend/src/components/detection/UploadForm.js @@ -0,0 +1,33 @@ +// src/components/detection/UploadForm.js + +import React from 'react'; + +const UploadForm = ({ handleFileChange, handleSubmit, handleFrameIntervalChange, handleCameraClick, handleGalleryClick, isLoading }) => { + return ( + <form id="uploadForm" className="text-center" onSubmit={handleSubmit} encType="multipart/form-data"> + <div className="form-group"> + <label className="h5 d-block"> + <input + type="file" + accept="video/*" + className="form-control-file" + onChange={handleFileChange} + /> + </label> + <label className="h5 d-block mt-3"> + Frame Interval (seconds): + <input + type="number" + className="form-control" + onChange={handleFrameIntervalChange} + /> + </label> + </div> + <button type="submit" className="btn btn-success btn-lg mt-4" disabled={isLoading}> + <i className="fas fa-upload"></i> Upload + </button> + </form> + ); +}; + +export default UploadForm; diff --git a/frontend/src/pages/NormalDetection/NormalDetectionPage.js b/frontend/src/pages/NormalDetection/NormalDetectionPage.js index 3a2044c3db8cc1b4efac341039a8c3931c0b528f..2b4f37733d7aade36d5cf28692f30abd9f148bfe 100644 --- a/frontend/src/pages/NormalDetection/NormalDetectionPage.js +++ b/frontend/src/pages/NormalDetection/NormalDetectionPage.js @@ -4,14 +4,17 @@ import React, { useState } from 'react'; import { Modal, Button } from 'react-bootstrap'; import { normalDetection } from '../../services/processMisplacedManagerApi'; import '../../styles/main.css'; -import loadingGif from '../../assets/loading.gif'; // Import your downloaded GIF +import LoadingIndicator from '../../components/detection/LoadingIndicator'; +import DetectionResults from '../../components/detection/DetectionResults'; +import UploadForm from '../../components/detection/UploadForm'; +import DetectionContainer from '../../components/detection/DetectionContainer'; const NormalDetectionPage = () => { const [imageFile, setImageFile] = useState(null); const [resultImageUrl, setResultImageUrl] = useState(null); const [misplacedObjects, setMisplacedObjects] = useState([]); - const [isLoading, setIsLoading] = useState(false); // Add loading state - const [showModal, setShowModal] = useState(false); // State to control modal visibility + const [isLoading, setIsLoading] = useState(false); + const [showModal, setShowModal] = useState(false); const handleCameraClick = () => { document.getElementById('cameraInput').click(); @@ -28,7 +31,7 @@ const NormalDetectionPage = () => { const handleSubmit = async (event) => { event.preventDefault(); if (imageFile) { - setIsLoading(true); // Set loading to true when processing starts + setIsLoading(true); const formData = new FormData(); formData.append('image', imageFile); @@ -39,7 +42,7 @@ const NormalDetectionPage = () => { } catch (error) { console.error('Upload failed', error); } finally { - setIsLoading(false); // Set loading to false when processing is complete + setIsLoading(false); } } else { alert('Please select an image to upload.'); @@ -55,76 +58,23 @@ const NormalDetectionPage = () => { }; return ( - <div className="pages-container-center"> - <div className="row justify-content-center"> - <div className="col-12 col-md-10 col-lg-8"> - <div className="card shadow-lg border-0"> - <div className="card-header text-center bg-primary text-white py-4"> - <h1 className="mb-0">Upload Image for Normal Detection</h1> - </div> - <div className="card-body p-5"> - {isLoading ? ( // Show loading GIF or message while loading - <div className="text-center"> - <img src={loadingGif} alt="Loading..." className="img-fluid" /> - <p>Your photo is being processed, please wait...</p> - </div> - ) : ( - <form id="uploadForm" className="text-center" onSubmit={handleSubmit}> - <div className="form-group"> - <label className="h5 d-block"> - <button type="button" id="openCameraBtn" className="btn btn-primary btn-lg" onClick={handleCameraClick}> - <i className="fas fa-camera-retro fa-3x mb-3"></i> Take Photo - </button> - </label> - <input type="file" id="cameraInput" name="image" accept="image/*" capture="environment" - className="form-control-file d-none" onChange={handleFileChange} /> - </div> - - <div className="form-group"> - <label className="h5 d-block"> - <button type="button" id="openGalleryBtn" className="btn btn-secondary btn-lg" onClick={handleGalleryClick}> - <i className="fas fa-folder-open fa-3x mb-3"></i> Choose from Gallery - </button> - </label> - <input type="file" id="galleryInput" name="image" accept="image/*" - className="form-control-file d-none" onChange={handleFileChange} /> - </div> - - <button type="submit" className="btn btn-success btn-lg mt-4"> - <i className="fas fa-upload"></i> Upload - </button> - </form> - )} - {resultImageUrl && !isLoading && ( // Show results if not loading - <div className="mt-5"> - <h2>Detection Results</h2> - <img - src={resultImageUrl} - alt="Detected Objects" - className="img-fluid" - onClick={handleImageClick} - style={{ cursor: 'pointer' }} - /> - <h3 className="mt-4">Misplaced Objects</h3> - <ul className="list-group"> - {misplacedObjects.map((obj, index) => ( - <li key={index} className="list-group-item"> - {obj.class_name} is misplaced. Allowed locations: {obj.allowed_locations.join(", ")} - </li> - ))} - </ul> - </div> - )} - <div className="text-center mt-4"> - <a href="/detection-options" className="btn btn-link"> - <i className="fas fa-arrow-left"></i> Back to Detection Options - </a> - </div> - </div> - </div> - </div> + <DetectionContainer title="Upload Image for Normal Detection"> + <LoadingIndicator isLoading={isLoading} message="Your photo is being processed, please wait..." /> + {!isLoading && ( + <UploadForm + handleFileChange={handleFileChange} + handleSubmit={handleSubmit} + handleCameraClick={handleCameraClick} + handleGalleryClick={handleGalleryClick} + isLoading={isLoading} + /> + )} + <DetectionResults result={{ output_image_url: resultImageUrl, misplaced_objects: misplacedObjects }} /> + <div className="text-center mt-4"> + <a href="/detection-options" className="btn btn-link"> + <i className="fas fa-arrow-left"></i> Back to Detection Options + </a> </div> - {/* Modal for full-size image */} <Modal show={showModal} onHide={handleCloseModal} size="xl" centered> <Modal.Header closeButton> <Modal.Title>Full-Size Image</Modal.Title> @@ -138,7 +88,7 @@ const NormalDetectionPage = () => { </Button> </Modal.Footer> </Modal> - </div> + </DetectionContainer> ); }; diff --git a/frontend/src/pages/Rules/ManageRulesPage.js b/frontend/src/pages/Rules/ManageRulesPage.js index 7ef344dc5943d6959de4cac36864de941c25b6ee..fd22d68ad0722d22f528e242b7138f8d68771287 100644 --- a/frontend/src/pages/Rules/ManageRulesPage.js +++ b/frontend/src/pages/Rules/ManageRulesPage.js @@ -1,8 +1,11 @@ +// src/pages/Rules/ManageRulesPage.js + import React, { useState } from 'react'; import AddRule from '../../components/Rules/AddRule'; import GetRules from '../../components/Rules/GetRules'; import UpdateRule from '../../components/Rules/UpdateRule'; import '../../styles/main.css'; +import '../../styles/dashboard.css'; const ManageRulesPage = () => { const [editingRule, setEditingRule] = useState(null); @@ -27,12 +30,14 @@ const ManageRulesPage = () => { return ( <div className="pages-container-center"> - <h1>Manage Rules</h1> - <AddRule onRuleAdded={handleRuleAdded} /> - {editingRule && ( - <UpdateRule rule={editingRule} onUpdateCompleted={handleUpdateCompleted} /> - )} - <GetRules onEditRule={handleEditRule} onDeleteRule={handleDeleteRule} refresh={refreshRules} /> + <h1 className="dashboard-title">Manage Rules</h1> + <div className="dashboard-card"> + <AddRule onRuleAdded={handleRuleAdded} /> + {editingRule && ( + <UpdateRule rule={editingRule} onUpdateCompleted={handleUpdateCompleted} /> + )} + <GetRules onEditRule={handleEditRule} onDeleteRule={handleDeleteRule} refresh={refreshRules} /> + </div> </div> ); }; diff --git a/frontend/src/pages/UserDashboard/UserDashboard.js b/frontend/src/pages/UserDashboard/UserDashboard.js index 2bd3f31775aedf7b2817b1547464bdcbdef6df1a..68bf07570e2827544d8518856fc7f5b58169556f 100644 --- a/frontend/src/pages/UserDashboard/UserDashboard.js +++ b/frontend/src/pages/UserDashboard/UserDashboard.js @@ -23,6 +23,7 @@ const UserDashboard = () => { <h2 className="dashboard-greeting">Hello, {username}</h2> <div className="dashboard-buttons-container"> <ButtonLink to="/user/profile" label="User Profile" /> + <ButtonLink to="/user/manage-rules" label="Manage Rules" /> {/* Add more ButtonLink components here as needed */} </div> </div> diff --git a/frontend/src/pages/Video/VideoDetectionPage.js b/frontend/src/pages/Video/VideoDetectionPage.js new file mode 100644 index 0000000000000000000000000000000000000000..26af3d81b5c15aaf9fb01fd12db0e43aca5f3a1a --- /dev/null +++ b/frontend/src/pages/Video/VideoDetectionPage.js @@ -0,0 +1,80 @@ +// src/pages/VideoDetection/VideoDetectionPage.js + +import React, { useState } from 'react'; +import { uploadVideo, getVideoResults } from '../../services/processMisplacedManagerApi'; +import '../../styles/main.css'; +import LoadingIndicator from '../../components/detection/LoadingIndicator'; +import DetectionResults from '../../components/detection/DetectionResults'; +import UploadForm from '../../components/detection/UploadForm'; +import DetectionContainer from '../../components/detection/DetectionContainer'; + +const VideoDetectionPage = () => { + const [videoFile, setVideoFile] = useState(null); + const [frameInterval, setFrameInterval] = useState(1); // Added state for frame interval + const [result, setResult] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const handleFileChange = (event) => { + setVideoFile(event.target.files[0]); + }; + + const handleFrameIntervalChange = (event) => { + setFrameInterval(event.target.value); + }; + + const handleSubmit = async (event) => { + event.preventDefault(); + if (videoFile) { + setIsLoading(true); + const formData = new FormData(); + formData.append('video', videoFile); + formData.append('frame_interval', frameInterval); // Added frame interval to form data + + console.log('Uploading video file:', videoFile); + + try { + const uploadResponse = await uploadVideo(formData); + console.log('Response from server:', uploadResponse); + + if (uploadResponse.id) { + const videoResults = await getVideoResults(uploadResponse.id); + console.log('Video results:', videoResults); // Added log for video results + setResult(videoResults); + } else { + throw new Error('Upload response did not contain video ID.'); + } + } catch (error) { + console.error('Upload failed', error); + setResult({ error: 'Failed to process the video. Please try again.' }); + } finally { + setIsLoading(false); + } + } else { + alert('Please select a video to upload.'); + } + }; + + return ( + <DetectionContainer title="Upload Video for Misplaced Items Detection"> + <LoadingIndicator isLoading={isLoading} message="Your video is being processed, please wait..." /> + {!isLoading && ( + <UploadForm + handleFileChange={handleFileChange} + handleSubmit={handleSubmit} + handleFrameIntervalChange={handleFrameIntervalChange} + handleCameraClick={() => { }} + handleGalleryClick={() => { }} + isLoading={isLoading} + /> + )} + <DetectionResults result={result} /> + <div className="text-center mt-4"> + <a href="/detection-options" className="btn btn-link"> + <i className="fas fa-arrow-left"></i> Back to Detection Options + </a> + </div> + </DetectionContainer> + ); +}; + +export default VideoDetectionPage; diff --git a/frontend/src/services/processMisplacedManagerApi.js b/frontend/src/services/processMisplacedManagerApi.js index 526b31cd41e5f296830a241217580b392cb293a2..83879d022889e919b4c3b29053a7011fe4dba68b 100644 --- a/frontend/src/services/processMisplacedManagerApi.js +++ b/frontend/src/services/processMisplacedManagerApi.js @@ -96,10 +96,14 @@ export const segmentationDetection = async (data) => { // Function to upload a video export const uploadVideo = async (data) => { try { - const response = await api.post('/api/process_misplaced_manager/upload-video/', data); + const response = await api.post('/api/process_misplaced_manager/upload-video/', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); return response.data; } catch (error) { - console.error("Error uploading video:", error); + console.error('Error uploading video:', error); throw error; } }; @@ -114,3 +118,15 @@ export const checkPlacement = async (data) => { throw error; } }; + + +// Function to get video results +export const getVideoResults = async (id) => { + try { + const response = await api.get(`/api/process_misplaced_manager/video-results/${id}/`); + return response.data; + } catch (error) { + console.error(`Error fetching video results with ID ${id}:`, error); + throw error; + } +}; \ No newline at end of file diff --git a/frontend/src/setupTests.js b/frontend/src/setupTests.js deleted file mode 100644 index 8f2609b7b3e0e3897ab3bcaad13caf6876e48699..0000000000000000000000000000000000000000 --- a/frontend/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/frontend/src/styles/_forms.css b/frontend/src/styles/_forms.css index e42ec2d57783daf49446195fd59568e9689bb4a5..811ff061783a15da363539bf21cc5650a0079f1d 100644 --- a/frontend/src/styles/_forms.css +++ b/frontend/src/styles/_forms.css @@ -24,4 +24,27 @@ background: transparent; border-left: none; cursor: pointer; +} + +.checkbox-container { + display: flex; + flex-wrap: wrap; + gap: 10px; + /* Adds space between checkboxes */ +} + +.checkbox-wrapper-12 { + display: flex; + align-items: center; + gap: 5px; + /* Adds space between checkbox and text */ + border: 1px solid #ddd; + /* Adds a small border around each checkbox */ + padding: 5px; + border-radius: 4px; +} + +.form-group label { + font-weight: bold; + font-size: 1.1em; } \ No newline at end of file diff --git a/frontend/src/styles/_responsive.css b/frontend/src/styles/_responsive.css index 4bc337a0d6e1bf1670358117c1674bb1712ba930..35f20d32a0a912017f0afd41784d8d0712a0c934 100644 --- a/frontend/src/styles/_responsive.css +++ b/frontend/src/styles/_responsive.css @@ -1,6 +1,6 @@ /* src/styles/_responsive.css */ -/* Responsive utilities */ +/* General responsive styles */ @media (max-width: 768px) { .auth-container { padding: 10px; @@ -9,6 +9,42 @@ .auth-form { padding: 20px; } + + .pages-container-center { + padding: 10px; + } + + .dashboard-title { + font-size: 2rem; + } + + .card-wide { + width: 90%; + } + + .checkbox-container { + max-height: 200px; + overflow-y: auto; + } + + .checkbox-wrapper-12 { + flex: 1 1 100%; + } +} + +@media (min-width: 769px) and (max-width: 1199px) { + .card-wide { + width: 80%; + } + + .checkbox-container { + max-height: 300px; + overflow-y: auto; + } + + .checkbox-wrapper-12 { + flex: 1 1 48%; + } } @media (min-width: 1200px) { @@ -20,4 +56,37 @@ font-size: 1.25rem; padding: 0.75rem 1.5rem; } + + .card-wide { + width: 60%; + max-width: 900px; + } + + .checkbox-container { + max-height: 400px; + overflow-y: auto; + } + + .checkbox-wrapper-12 { + flex: 1 1 30%; + } +} + +/* Specific adjustments for the form container */ +.form-container { + width: 100%; + max-width: 600px; + margin: 0 auto; +} + +@media (max-width: 768px) { + .form-container { + max-width: 100%; + } +} + +@media (min-width: 1200px) { + .form-container { + max-width: 800px; + } } \ No newline at end of file diff --git a/frontend/src/styles/checkbox/checkbox.css b/frontend/src/styles/checkbox/checkbox.css new file mode 100644 index 0000000000000000000000000000000000000000..68945c1db14687cc6605ea83d54091d80dbe98cb --- /dev/null +++ b/frontend/src/styles/checkbox/checkbox.css @@ -0,0 +1,140 @@ +/* + https://getcssscan.com/css-checkboxes-examples +CHECKBOX NUMBER 12 BY Andreas Storm + */ +/* Src/styles/checkbox/checkbox.css */ + +.checkbox-wrapper-12 { + position: relative; +} + +.checkbox-wrapper-12>svg { + position: absolute; + top: -130%; + left: -170%; + width: 110px; + pointer-events: none; +} + +.checkbox-wrapper-12 * { + box-sizing: border-box; +} + +.checkbox-wrapper-12 input[type="checkbox"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + -webkit-tap-highlight-color: transparent; + cursor: pointer; + margin: 0; +} + +.checkbox-wrapper-12 input[type="checkbox"]:focus { + outline: 0; +} + +.checkbox-wrapper-12 .cbx { + width: 24px; + height: 24px; + top: calc(50vh - 12px); + left: calc(50vw - 12px); +} + +.checkbox-wrapper-12 .cbx input { + position: absolute; + top: 0; + left: 0; + width: 24px; + height: 24px; + border: 2px solid #bfbfc0; + border-radius: 50%; +} + +.checkbox-wrapper-12 .cbx label { + width: 24px; + height: 24px; + background: none; + border-radius: 50%; + position: absolute; + top: 0; + left: 0; + -webkit-filter: url("#goo-12"); + filter: url("#goo-12"); + transform: trasnlate3d(0, 0, 0); + pointer-events: none; +} + +.checkbox-wrapper-12 .cbx svg { + position: absolute; + top: 5px; + left: 4px; + z-index: 1; + pointer-events: none; +} + +.checkbox-wrapper-12 .cbx svg path { + stroke: #fff; + stroke-width: 3; + stroke-linecap: round; + stroke-linejoin: round; + stroke-dasharray: 19; + stroke-dashoffset: 19; + transition: stroke-dashoffset 0.3s ease; + transition-delay: 0.2s; +} + +.checkbox-wrapper-12 .cbx input:checked+label { + animation: splash-12 0.6s ease forwards; +} + +.checkbox-wrapper-12 .cbx input:checked+label+svg path { + stroke-dashoffset: 0; +} + +@-moz-keyframes splash-12 { + 40% { + background: #866efb; + box-shadow: 0 -18px 0 -8px #866efb, 16px -8px 0 -8px #866efb, 16px 8px 0 -8px #866efb, 0 18px 0 -8px #866efb, -16px 8px 0 -8px #866efb, -16px -8px 0 -8px #866efb; + } + + 100% { + background: #866efb; + box-shadow: 0 -36px 0 -10px transparent, 32px -16px 0 -10px transparent, 32px 16px 0 -10px transparent, 0 36px 0 -10px transparent, -32px 16px 0 -10px transparent, -32px -16px 0 -10px transparent; + } +} + +@-webkit-keyframes splash-12 { + 40% { + background: #866efb; + box-shadow: 0 -18px 0 -8px #866efb, 16px -8px 0 -8px #866efb, 16px 8px 0 -8px #866efb, 0 18px 0 -8px #866efb, -16px 8px 0 -8px #866efb, -16px -8px 0 -8px #866efb; + } + + 100% { + background: #866efb; + box-shadow: 0 -36px 0 -10px transparent, 32px -16px 0 -10px transparent, 32px 16px 0 -10px transparent, 0 36px 0 -10px transparent, -32px 16px 0 -10px transparent, -32px -16px 0 -10px transparent; + } +} + +@-o-keyframes splash-12 { + 40% { + background: #866efb; + box-shadow: 0 -18px 0 -8px #866efb, 16px -8px 0 -8px #866efb, 16px 8px 0 -8px #866efb, 0 18px 0 -8px #866efb, -16px 8px 0 -8px #866efb, -16px -8px 0 -8px #866efb; + } + + 100% { + background: #866efb; + box-shadow: 0 -36px 0 -10px transparent, 32px -16px 0 -10px transparent, 32px 16px 0 -10px transparent, 0 36px 0 -10px transparent, -32px 16px 0 -10px transparent, -32px -16px 0 -10px transparent; + } +} + +@keyframes splash-12 { + 40% { + background: #866efb; + box-shadow: 0 -18px 0 -8px #866efb, 16px -8px 0 -8px #866efb, 16px 8px 0 -8px #866efb, 0 18px 0 -8px #866efb, -16px 8px 0 -8px #866efb, -16px -8px 0 -8px #866efb; + } + + 100% { + background: #866efb; + box-shadow: 0 -36px 0 -10px transparent, 32px -16px 0 -10px transparent, 32px 16px 0 -10px transparent, 0 36px 0 -10px transparent, -32px 16px 0 -10px transparent, -32px -16px 0 -10px transparent; + } +} \ No newline at end of file diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..35db410baedd376bfe477d7b723c5578797db971 --- /dev/null +++ b/schema.sql @@ -0,0 +1,326 @@ +-- MySQL dump 10.13 Distrib 5.7.44, for Linux (x86_64) +-- +-- Host: localhost Database: misplaceai +-- ------------------------------------------------------ +-- Server version 5.7.44 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `auth_group` +-- + +DROP TABLE IF EXISTS `auth_group`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_group` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(150) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `auth_group_permissions` +-- + +DROP TABLE IF EXISTS `auth_group_permissions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_group_permissions` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `group_id` int(11) NOT NULL, + `permission_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_group_permissions_group_id_permission_id_0cd325b0_uniq` (`group_id`,`permission_id`), + KEY `auth_group_permissio_permission_id_84c5c92e_fk_auth_perm` (`permission_id`), + CONSTRAINT `auth_group_permissio_permission_id_84c5c92e_fk_auth_perm` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`), + CONSTRAINT `auth_group_permissions_group_id_b120cbf9_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `auth_permission` +-- + +DROP TABLE IF EXISTS `auth_permission`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_permission` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `content_type_id` int(11) NOT NULL, + `codename` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_permission_content_type_id_codename_01ab375a_uniq` (`content_type_id`,`codename`), + CONSTRAINT `auth_permission_content_type_id_2f476e4b_fk_django_co` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `auth_user` +-- + +DROP TABLE IF EXISTS `auth_user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `password` varchar(128) NOT NULL, + `last_login` datetime(6) DEFAULT NULL, + `is_superuser` tinyint(1) NOT NULL, + `username` varchar(150) NOT NULL, + `first_name` varchar(150) NOT NULL, + `last_name` varchar(150) NOT NULL, + `email` varchar(254) NOT NULL, + `is_staff` tinyint(1) NOT NULL, + `is_active` tinyint(1) NOT NULL, + `date_joined` datetime(6) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `auth_user_groups` +-- + +DROP TABLE IF EXISTS `auth_user_groups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_user_groups` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `group_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_user_groups_user_id_group_id_94350c0c_uniq` (`user_id`,`group_id`), + KEY `auth_user_groups_group_id_97559544_fk_auth_group_id` (`group_id`), + CONSTRAINT `auth_user_groups_group_id_97559544_fk_auth_group_id` FOREIGN KEY (`group_id`) REFERENCES `auth_group` (`id`), + CONSTRAINT `auth_user_groups_user_id_6a12ed8b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `auth_user_user_permissions` +-- + +DROP TABLE IF EXISTS `auth_user_user_permissions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `auth_user_user_permissions` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `permission_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `auth_user_user_permissions_user_id_permission_id_14a6b632_uniq` (`user_id`,`permission_id`), + KEY `auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm` (`permission_id`), + CONSTRAINT `auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm` FOREIGN KEY (`permission_id`) REFERENCES `auth_permission` (`id`), + CONSTRAINT `auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `django_admin_log` +-- + +DROP TABLE IF EXISTS `django_admin_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_admin_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `action_time` datetime(6) NOT NULL, + `object_id` longtext, + `object_repr` varchar(200) NOT NULL, + `action_flag` smallint(5) unsigned NOT NULL, + `change_message` longtext NOT NULL, + `content_type_id` int(11) DEFAULT NULL, + `user_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `django_admin_log_content_type_id_c4bce8eb_fk_django_co` (`content_type_id`), + KEY `django_admin_log_user_id_c564eba6_fk_auth_user_id` (`user_id`), + CONSTRAINT `django_admin_log_content_type_id_c4bce8eb_fk_django_co` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`), + CONSTRAINT `django_admin_log_user_id_c564eba6_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `django_content_type` +-- + +DROP TABLE IF EXISTS `django_content_type`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_content_type` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `app_label` varchar(100) NOT NULL, + `model` varchar(100) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `django_content_type_app_label_model_76bd3d3b_uniq` (`app_label`,`model`) +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `django_migrations` +-- + +DROP TABLE IF EXISTS `django_migrations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_migrations` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `app` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `applied` datetime(6) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `django_session` +-- + +DROP TABLE IF EXISTS `django_session`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `django_session` ( + `session_key` varchar(40) NOT NULL, + `session_data` longtext NOT NULL, + `expire_date` datetime(6) NOT NULL, + PRIMARY KEY (`session_key`), + KEY `django_session_expire_date_a5c62663` (`expire_date`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `placement_rules_placementrule` +-- + +DROP TABLE IF EXISTS `placement_rules_placementrule`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `placement_rules_placementrule` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `object_name` varchar(100) NOT NULL, + `allowed_locations` json NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `process_misplaced_manager_uploadedimage` +-- + +DROP TABLE IF EXISTS `process_misplaced_manager_uploadedimage`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `process_misplaced_manager_uploadedimage` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `image` varchar(100) NOT NULL, + `uploaded_at` datetime(6) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `process_misplaced_manager_uploadedvideo` +-- + +DROP TABLE IF EXISTS `process_misplaced_manager_uploadedvideo`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `process_misplaced_manager_uploadedvideo` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `video` varchar(100) NOT NULL, + `uploaded_at` datetime(6) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `rules_item` +-- + +DROP TABLE IF EXISTS `rules_item`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `rules_item` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `rules_location` +-- + +DROP TABLE IF EXISTS `rules_location`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `rules_location` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `rules_rule` +-- + +DROP TABLE IF EXISTS `rules_rule`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `rules_rule` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `item_id` bigint(20) NOT NULL, + `user_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + KEY `rules_rule_item_id_e7fc9070_fk_rules_item_id` (`item_id`), + KEY `rules_rule_user_id_a3576cd9_fk_auth_user_id` (`user_id`), + CONSTRAINT `rules_rule_item_id_e7fc9070_fk_rules_item_id` FOREIGN KEY (`item_id`) REFERENCES `rules_item` (`id`), + CONSTRAINT `rules_rule_user_id_a3576cd9_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `rules_rule_locations` +-- + +DROP TABLE IF EXISTS `rules_rule_locations`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `rules_rule_locations` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `rule_id` bigint(20) NOT NULL, + `location_id` bigint(20) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `rules_rule_locations_rule_id_location_id_aeeed3a6_uniq` (`rule_id`,`location_id`), + KEY `rules_rule_locations_location_id_71e247a5_fk_rules_location_id` (`location_id`), + CONSTRAINT `rules_rule_locations_location_id_71e247a5_fk_rules_location_id` FOREIGN KEY (`location_id`) REFERENCES `rules_location` (`id`), + CONSTRAINT `rules_rule_locations_rule_id_2a81ba57_fk_rules_rule_id` FOREIGN KEY (`rule_id`) REFERENCES `rules_rule` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2024-06-25 15:35:20