Skip to content

Commit

Permalink
Merge pull request #44 from trebidav/feat/interactive-tasks
Browse files Browse the repository at this point in the history
Feat/interactive tasks
  • Loading branch information
trebidav authored Nov 30, 2024
2 parents 5496cfa + d9ebe0e commit 3c817a7
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 27 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,8 @@ cython_debug/
.idea/

# macOs
**/.DS_Store
**/.DS_Store


# Redis
**/dump.rdb
2 changes: 2 additions & 0 deletions comrade/comrade_core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ def __str__(self) -> str:
def start(self, user: User):
if user == self.owner:
raise ValidationError("Owner cannot start the task")
if self.state != Task.State.OPEN:
raise ValidationError("Task is not open")

has_required_skills = user.skills.filter(
id__in=self.skill_execute.all()
Expand Down
8 changes: 7 additions & 1 deletion comrade/comrade_core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ class UserDetailSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = ["id", "username", "skills"]
fields = ["id", "username", "skills"]


class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = "__all__"
141 changes: 122 additions & 19 deletions comrade/comrade_core/templates/map.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
<script>
let map;
let userMarker;
let infoWindow;

const socket = new WebSocket("ws://localhost:8000/ws/location/?token=a922af5f0cb0cd30abe67208e7f6a9ccc8795d0a");


socket.onopen = function() {
console.log("WebSocket connection established.");
startSendingLocation(); // Start sending location updates
Expand Down Expand Up @@ -65,13 +67,18 @@
}

}
function initMap() {
// Initialize the map centered at a default location
map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 50, lng: 14 }, // Default center
zoom: 2 // Default zoom level

});

async function initMap() {
const { Map } = await google.maps.importLibrary("maps");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");

map = new Map(document.getElementById("map"), {
center: { lat: 50, lng: 14 },
zoom: 8,
mapId: "DEMO_MAP_ID",
});

infoWindow = new google.maps.InfoWindow();

// Get the user's location
if (navigator.geolocation) {
Expand All @@ -81,14 +88,38 @@
lat: position.coords.latitude,
lng: position.coords.longitude
};
// Center the map on the user's location

// Center the map on the user's location
map.setCenter(userLocation);
map.setZoom(20); // Zoom in closer
// Add a marker for the user's location
userMarker = new google.maps.Marker({

// Add a marker for the user's location
const userMarker = new google.maps.marker.AdvancedMarkerElement({
position: userLocation,
map: map,
title: "You are here!"
gmpClickable: true
});

// Fetch user details and update the tooltip
fetch('/user/', {
headers: { 'Authorization': 'Token a922af5f0cb0cd30abe67208e7f6a9ccc8795d0a' }
})
.then(response => response.json())
.then(data => {
const username = data.username;
const skills = data.skills.join(', ');
userMarker.title = `Username: ${username}\nSkills: ${skills}`;
})
.catch(error => {
console.error("Error fetching user details:", error);
});

userMarker.addListener("click", ({ domEvent, latLng }) => {
const { target } = domEvent;

infoWindow.close();
infoWindow.setContent(userMarker.title);
infoWindow.open(userMarker.map, userMarker);
});
},
() => {
Expand All @@ -99,6 +130,8 @@
// Browser doesn't support Geolocation
handleLocationError(false, map.getCenter());
}

displayTasks(); // Display tasks on the map
}

function handleLocationError(browserHasGeolocation, pos) {
Expand All @@ -119,19 +152,89 @@

// Update the marker position
if (userMarker) {
userMarker.setPosition(newLocation);
userMarker.position = newLocation;
} else {
// If the marker doesn't exist, create it
userMarker = new google.maps.Marker({
position: newLocation,
map: map,
title: "You are here!"
});
// If the marker doesn't exist, create it
userMarker = new google.maps.marker.AdvancedMarkerElement({
position: newLocation,
map: map,
gmpClickable: true
});

fetch('/user/', {
headers: { 'Authorization': 'Token a922af5f0cb0cd30abe67208e7f6a9ccc8795d0a' }
})
.then(response => response.json())
.then(data => {
const username = data.username;
const skills = data.skills.join(', ');
userMarker.title = `Username: ${username}\nSkills: ${skills}`;
})
.catch(error => {
console.error("Error fetching user details:", error);
});

userMarker.addListener("click", ({ domEvent, latLng }) => {
const { target } = domEvent;

infoWindow.close();
infoWindow.setContent(userMarker.title);
infoWindow.open(userMarker.map, userMarker);
});



}

// Optionally, center the map on the new location
map.setCenter(newLocation);
}
}

function displayTasks() {
// Get the tasks from the server
fetch('/tasks/', {
headers: { 'Authorization': 'Token a922af5f0cb0cd30abe67208e7f6a9ccc8795d0a' }
})
.then(response => response.json())
.then(data => {
console.log("Tasks:", data);
// Check if data is an array
if (Array.isArray(data.tasks)) {
// Display the tasks on the map
data.tasks.forEach(task => {
const taskLocation = {
lat: task.lat,
lng: task.lon
};
const taskMarker = new google.maps.marker.AdvancedMarkerElement({
position: taskLocation,
map: map,
title: task.name,
gmpClickable: true
});

// Add a click listener for each marker, and set up the info window.
taskMarker.addListener("click", ({ domEvent, latLng }) => {
const { target } = domEvent;

fetch("http://localhost:8000/task/"+task.id+"/start", {
method: 'POST',
headers: { 'Authorization': 'Token a922af5f0cb0cd30abe67208e7f6a9ccc8795d0a' }
});
infoWindow.close();
infoWindow.setContent("<a id='button' href=http://localhost:8000/task/"+task.id+"/start target='_blank'>"+task.name+"</a>");
infoWindow.open(taskMarker.map, taskMarker);
});
});
} else {
console.error("Error: Expected an array of tasks");
}
})
.catch(error => {
console.error("Error fetching tasks:", error);
});
}


</script>
<style>
Expand Down
4 changes: 4 additions & 0 deletions comrade/comrade_core/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.urls import include, path
from django.urls import re_path

from . import consumers

from . import views
Expand All @@ -10,6 +11,9 @@
path('user/', views.UserDetailView.as_view(), name='user_detail'),
path('user/token/', views.login_view, name='login'),
path('map/', views.map, name='map'),
path('task/<int:taskId>/start', views.TaskStartView.as_view(), name='start_task'),
path('task/<int:taskId>/finish', views.TaskFinishView.as_view(), name='finish_task'),
path('tasks/', views.TaskListView.as_view(), name='task_list'),
]

websocket_urlpatterns = [
Expand Down
49 changes: 43 additions & 6 deletions comrade/comrade_core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from .serializers import UserDetailSerializer
from .serializers import UserDetailSerializer, TaskSerializer

from django.core.exceptions import ValidationError


def index(request):
Expand Down Expand Up @@ -60,20 +62,55 @@ def login_view(request):


# POST /task/{taskId}/start
class MyPostView(APIView):
class TaskStartView(APIView):
permission_classes = [IsAuthenticated]

def post(self, request: Request):
taskId = request.query_params["taskId"]
def post(self, request: Request, taskId: int):
task = None
try:
task = Task.objects.get(pk=taskId)
except Task.DoesNotExist as e:
raise e
return Response({"error": "Task not found"}, status=status.HTTP_404_NOT_FOUND)

task.start(request.user)
try:
task.start(request.user)
except ValidationError as e:
return Response({"error": str(e)}, status=status.HTTP_412_PRECONDITION_FAILED)

return Response(
{"message": "Task started!"},
status=status.HTTP_200_OK,
)

class TaskFinishView(APIView):
permission_classes = [IsAuthenticated]

def post(self, request: Request, taskId: int):
task = None
try:
task = Task.objects.get(pk=taskId)
except Task.DoesNotExist as e:
return Response({"error": "Task not found"}, status=status.HTTP_404_NOT_FOUND)

print(request.user)
try:
task.finish(request.user)
except ValidationError as e:
return Response({"error": str(e)}, status=status.HTTP_412_PRECONDITION_FAILED)

return Response(
{"message": "Task finished!"},
status=status.HTTP_200_OK,
)

class TaskListView(APIView):
permission_classes = [IsAuthenticated]

def get(self, request):
user = request.user
tasks = Task.objects.filter(skill_read__in=user.skills.all())
serializer = TaskSerializer(tasks, many=True)
return Response(
{"tasks": serializer.data},
status=status.HTTP_200_OK,
)
1 change: 1 addition & 0 deletions comrade/data.json

Large diffs are not rendered by default.

Binary file added dump.rdb
Binary file not shown.

0 comments on commit 3c817a7

Please sign in to comment.