diff --git a/backend/tasks/misc/views.py b/backend/tasks/misc/views.py
index 3fbc837..8f00572 100644
--- a/backend/tasks/misc/views.py
+++ b/backend/tasks/misc/views.py
@@ -1,8 +1,10 @@
from rest_framework import viewsets
+from rest_framework.permissions import IsAuthenticated
from ..models import Tag
from .serializers import TagSerializer
class TagViewSet(viewsets.ModelViewSet):
queryset = Tag.objects.all()
- serializer_class = TagSerializer
\ No newline at end of file
+ serializer_class = TagSerializer
+ permission_classes = (IsAuthenticated,)
\ No newline at end of file
diff --git a/backend/tasks/models.py b/backend/tasks/models.py
index c848e00..97a2c4b 100644
--- a/backend/tasks/models.py
+++ b/backend/tasks/models.py
@@ -81,6 +81,11 @@ class Todo(Task):
priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
def save(self, *args, **kwargs):
+ done_list_name = "Done"
+ if self.list_board.name == done_list_name:
+ self.completed = True
+ Todo.objects.filter(list_board=self.list_board).update(completed=True)
+
if self.completed and not self.completion_date:
self.completion_date = timezone.now()
elif not self.completed:
diff --git a/backend/tasks/tasks/serializers.py b/backend/tasks/tasks/serializers.py
index 12bea72..9a56632 100644
--- a/backend/tasks/tasks/serializers.py
+++ b/backend/tasks/tasks/serializers.py
@@ -1,9 +1,12 @@
from rest_framework import serializers
from users.models import CustomUser
from boards.models import ListBoard
-from tasks.models import Todo, RecurrenceTask, Habit
+from tasks.models import Todo, RecurrenceTask, Habit, Subtask
class TaskSerializer(serializers.ModelSerializer):
+ tags = serializers.SerializerMethodField()
+ sub_task_count = serializers.SerializerMethodField()
+
class Meta:
model = Todo
fields = '__all__'
@@ -19,6 +22,12 @@ class TaskSerializer(serializers.ModelSerializer):
validated_data['user'] = user
return Todo.objects.create(**validated_data)
+ def get_tags(self, instance):
+ return [tag.name for tag in instance.tags.all()]
+
+ def get_sub_task_count(self, instance):
+ return instance.subtask_set.count()
+
class TaskCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
@@ -97,4 +106,14 @@ class HabitTaskSerializer(serializers.ModelSerializer):
class HabitTaskCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Habit
- exclude = ('tags',)
\ No newline at end of file
+ exclude = ('tags',)
+
+
+class SubTaskSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Subtask
+ fields = '__all__'
+
+ def create(self, validated_data):
+ # Create a new task with validated data
+ return Subtask.objects.create(**validated_data)
\ No newline at end of file
diff --git a/backend/tasks/tasks/views.py b/backend/tasks/tasks/views.py
index fe1c82d..bd5d568 100644
--- a/backend/tasks/tasks/views.py
+++ b/backend/tasks/tasks/views.py
@@ -4,10 +4,13 @@ from rest_framework import viewsets, status, serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
+from rest_framework import mixins
-from .serializers import ChangeTaskListBoardSerializer, ChangeTaskOrderSerializer
+from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
+
+from tasks.tasks.serializers import ChangeTaskListBoardSerializer, ChangeTaskOrderSerializer, SubTaskSerializer
from boards.models import ListBoard, KanbanTaskOrder
-from tasks.models import Todo, RecurrenceTask, Habit
+from tasks.models import Todo, RecurrenceTask, Habit, Subtask
from tasks.tasks.serializers import (TaskCreateSerializer,
TaskSerializer,
RecurrenceTaskSerializer,
@@ -32,6 +35,18 @@ class TodoViewSet(viewsets.ModelViewSet):
return TaskCreateSerializer
return TaskSerializer
+ def list(self, request, *args, **kwargs):
+ """
+ list all tasks of the authenticated
+ user and send tags if those Todo too.
+ """
+ try:
+ queryset = self.get_queryset()
+ serializer = TaskSerializer(queryset, many=True)
+ return Response(serializer.data)
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
def create(self, request, *args, **kwargs):
try:
new_task_data = request.data
@@ -117,6 +132,73 @@ class TodoViewSet(viewsets.ModelViewSet):
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+@extend_schema_view(
+list=extend_schema(
+ parameters=[
+ OpenApiParameter(name='parent_task', description='Parent Task ID', type=int),
+ ]
+ )
+)
+class SubTaskViewset(viewsets.GenericViewSet,
+ mixins.CreateModelMixin,
+ mixins.DestroyModelMixin,
+ mixins.ListModelMixin,
+ mixins.UpdateModelMixin):
+ queryset = Subtask.objects.all()
+ permission_classes = (IsAuthenticated,)
+
+ def get_serializer_class(self):
+ return SubTaskSerializer
+
+ def list(self, request, *args, **kwargs):
+ """List only subtask of parent task."""
+ try:
+ parent_task = request.query_params.get('parent_task')
+ if not parent_task:
+ raise serializers.ValidationError('parent_task is required.')
+ queryset = self.get_queryset().filter(parent_task_id=parent_task)
+ serializer = self.get_serializer(queryset, many=True)
+ return Response(serializer.data)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ def create(self, request, *args, **kwargs):
+ """Create a new subtask, point to some parent tasks."""
+ try:
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ self.perform_create(serializer)
+ return Response(serializer.data)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+ def destroy(self, request, *args, **kwargs):
+ """Delete a subtask."""
+ try:
+ instance = self.get_object()
+ self.perform_destroy(instance)
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+ def partial_update(self, request, *args, **kwargs):
+ """Update a subtask."""
+ try:
+ instance = self.get_object()
+ serializer = self.get_serializer(instance, data=request.data, partial=True)
+ serializer.is_valid(raise_exception=True)
+ self.perform_update(serializer)
+ return Response(serializer.data)
+
+ except Exception as e:
+ return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
class RecurrenceTaskViewSet(viewsets.ModelViewSet):
queryset = RecurrenceTask.objects.all()
serializer_class = RecurrenceTaskSerializer
diff --git a/backend/tasks/tests/test_todo_signal.py b/backend/tasks/tests/test_todo_signal.py
new file mode 100644
index 0000000..fa7fba5
--- /dev/null
+++ b/backend/tasks/tests/test_todo_signal.py
@@ -0,0 +1,33 @@
+from datetime import datetime, timedelta
+from django.urls import reverse
+from rest_framework import status
+from rest_framework.test import APITestCase
+from tasks.tests.utils import create_test_user
+from tasks.models import Todo
+from boards.models import ListBoard, Board
+
+
+class TodoSignalHandlersTests(APITestCase):
+ def setUp(self):
+ self.user = create_test_user()
+ self.client.force_authenticate(user=self.user)
+ self.list_board = Board.objects.get(user=self.user).listboard_set.first()
+
+ def test_update_priority_signal_handler(self):
+ """
+ Test the behavior of the update_priority signal handler.
+ """
+ due_date = datetime.now() + timedelta(days=5)
+ data = {
+ 'title': 'Test Task',
+ 'type': 'habit',
+ 'difficulty': 1,
+ 'end_event': due_date.strftime('%Y-%m-%dT%H:%M:%S'),
+ 'list_board': self.list_board.id,
+ }
+ response = self.client.post(reverse("todo-list"), data, format='json')
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+ # Retrieve the created task and check if priority is updated
+ task = Todo.objects.get(title='Test Task')
+ self.assertIsNotNone(task.priority) # Check if priority is not None
\ No newline at end of file
diff --git a/backend/tasks/urls.py b/backend/tasks/urls.py
index d830a65..cf37e23 100644
--- a/backend/tasks/urls.py
+++ b/backend/tasks/urls.py
@@ -3,7 +3,7 @@ from django.urls import path, include
from rest_framework.routers import DefaultRouter
from tasks.api import GoogleCalendarEventViewset
-from tasks.tasks.views import TodoViewSet, RecurrenceTaskViewSet, HabitTaskViewSet
+from tasks.tasks.views import TodoViewSet, RecurrenceTaskViewSet, HabitTaskViewSet, SubTaskViewset
from tasks.misc.views import TagViewSet
@@ -13,6 +13,7 @@ router.register(r'daily', RecurrenceTaskViewSet)
router.register(r'habit', HabitTaskViewSet)
router.register(r'tags', TagViewSet)
router.register(r'calendar-events', GoogleCalendarEventViewset, basename='calendar-events')
+router.register(r'subtasks', SubTaskViewset, basename='subtasks')
urlpatterns = [
path('', include(router.urls)),
diff --git a/backend/users/serializers.py b/backend/users/serializers.py
index 962d789..f7ed6fa 100644
--- a/backend/users/serializers.py
+++ b/backend/users/serializers.py
@@ -32,12 +32,32 @@ class UpdateProfileSerializer(serializers.ModelSerializer):
Serializer for updating user profile.
"""
profile_pic = serializers.ImageField(required=False)
- first_name = serializers.CharField(max_length=255, required=False)
+ username = serializers.CharField(max_length=255, required=False)
about = serializers.CharField(required=False)
class Meta:
model = CustomUser
- fields = ('profile_pic', 'first_name', 'about')
+ fields = ('profile_pic', 'username', 'about')
+
+ def update(self, instance, validated_data):
+ """
+ Update an existing user's profile.
+ """
+ for attr, value in validated_data.items():
+ setattr(instance, attr, value)
+ instance.save()
+ return instance
+
+class UpdateProfileNopicSerializer(serializers.ModelSerializer):
+ """
+ Serializer for updating user profile.
+ """
+ username = serializers.CharField(max_length=255, required=False)
+ about = serializers.CharField(required=False)
+
+ class Meta:
+ model = CustomUser
+ fields = ('username', 'about')
def update(self, instance, validated_data):
"""
diff --git a/backend/users/views.py b/backend/users/views.py
index 7446d7b..0157c0c 100644
--- a/backend/users/views.py
+++ b/backend/users/views.py
@@ -9,7 +9,7 @@ from rest_framework.parsers import MultiPartParser
from rest_framework_simplejwt.tokens import RefreshToken
-from users.serializers import CustomUserSerializer, UpdateProfileSerializer
+from users.serializers import CustomUserSerializer, UpdateProfileSerializer, UpdateProfileNopicSerializer
from users.models import CustomUser
class CustomUserCreate(APIView):
@@ -57,13 +57,17 @@ class CustomUserProfileUpdate(APIView):
return Response ({
'error': 'User does not exist'
}, status=status.HTTP_404_NOT_FOUND)
+
serializer = UpdateProfileSerializer(request.user, data=request.data)
+ if request.data.get('profile_pic') == "null":
+ serializer = UpdateProfileNopicSerializer(request.user, data=request.data)
+
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
-
+
class UserDataRetriveViewset(viewsets.GenericViewSet, mixins.RetrieveModelMixin):
queryset = CustomUser.objects.all()
permission_classes = (IsAuthenticated,)
@@ -72,4 +76,4 @@ class UserDataRetriveViewset(viewsets.GenericViewSet, mixins.RetrieveModelMixin)
def retrieve(self, request, *args, **kwargs):
serializer = self.get_serializer(request.user)
return Response(serializer.data)
-
\ No newline at end of file
+
diff --git a/frontend/package.json b/frontend/package.json
index f057dce..618e30c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -34,6 +34,7 @@
"@wojtekmaj/react-daterange-picker": "^5.4.4",
"axios": "^1.6.1",
"bootstrap": "^5.3.2",
+ "date-fns": "^2.30.0",
"dotenv": "^16.3.1",
"framer-motion": "^10.16.4",
"gapi-script": "^1.2.0",
@@ -42,9 +43,11 @@
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-bootstrap": "^2.9.1",
+ "react-datepicker": "^4.23.0",
"react-datetime-picker": "^5.5.3",
"react-dom": "^18.2.0",
"react-icons": "^4.11.0",
+ "react-ios-time-picker": "^0.2.2",
"react-router-dom": "^6.18.0",
"react-tsparticles": "^2.12.2",
"tsparticles": "^2.12.0"
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 22fdadd..b514b56 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -77,6 +77,9 @@ dependencies:
bootstrap:
specifier: ^5.3.2
version: 5.3.2(@popperjs/core@2.11.8)
+ date-fns:
+ specifier: ^2.30.0
+ version: 2.30.0
dotenv:
specifier: ^16.3.1
version: 16.3.1
@@ -101,6 +104,9 @@ dependencies:
react-bootstrap:
specifier: ^2.9.1
version: 2.9.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
+ react-datepicker:
+ specifier: ^4.23.0
+ version: 4.23.0(react-dom@18.2.0)(react@18.2.0)
react-datetime-picker:
specifier: ^5.5.3
version: 5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
@@ -110,6 +116,9 @@ dependencies:
react-icons:
specifier: ^4.11.0
version: 4.12.0(react@18.2.0)
+ react-ios-time-picker:
+ specifier: ^0.2.2
+ version: 0.2.2(react-dom@18.2.0)(react@18.2.0)
react-router-dom:
specifier: ^6.18.0
version: 6.19.0(react-dom@18.2.0)(react@18.2.0)
@@ -3473,6 +3482,22 @@ packages:
- '@types/react-dom'
dev: false
+ /react-datepicker@4.23.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-w+msqlOZ14v6H1UknTKtZw/dw9naFMgAOspf59eY130gWpvy5dvKj/bgsFICDdvxB7PtKWxDcbGlAqCloY1d2A==}
+ peerDependencies:
+ react: ^16.9.0 || ^17 || ^18
+ react-dom: ^16.9.0 || ^17 || ^18
+ dependencies:
+ '@popperjs/core': 2.11.8
+ classnames: 2.3.2
+ date-fns: 2.30.0
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-onclickoutside: 6.13.0(react-dom@18.2.0)(react@18.2.0)
+ react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0)
+ dev: false
+
/react-datetime-picker@5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-bWGEPwGrZjaXTB8P4pbTSDygctLaqTWp0nNibaz8po+l4eTh9gv3yiJ+n4NIcpIJDqZaQJO57Bnij2rAFVQyLw==}
peerDependencies:
@@ -3520,6 +3545,10 @@ packages:
scheduler: 0.23.0
dev: false
+ /react-fast-compare@3.2.2:
+ resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
+ dev: false
+
/react-fit@1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==}
peerDependencies:
@@ -3550,6 +3579,17 @@ packages:
react: 18.2.0
dev: false
+ /react-ios-time-picker@0.2.2(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-bi+K23lK6Pf2xDXmhAlz+RJuy9/onWYi7Ye+ODVhIkis9AVFECOza2ckkZl/4vUypj2+TdTsHn+VZrTNdGIwDQ==}
+ peerDependencies:
+ react: ^18.2.0
+ react-dom: ^18.2.0
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-portal: 4.2.2(react-dom@18.2.0)(react@18.2.0)
+ dev: false
+
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -3565,6 +3605,41 @@ packages:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
dev: false
+ /react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==}
+ peerDependencies:
+ react: ^15.5.x || ^16.x || ^17.x || ^18.x
+ react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==}
+ peerDependencies:
+ '@popperjs/core': ^2.0.0
+ react: ^16.8.0 || ^17 || ^18
+ react-dom: ^16.8.0 || ^17 || ^18
+ dependencies:
+ '@popperjs/core': 2.11.8
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-fast-compare: 3.2.2
+ warning: 4.0.3
+ dev: false
+
+ /react-portal@4.2.2(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-vS18idTmevQxyQpnde0Td6ZcUlv+pD8GTyR42n3CHUQq9OHi1C4jDE4ZWEbEsrbrLRhSECYiao58cvocwMtP7Q==}
+ peerDependencies:
+ react: ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0
+ react-dom: ^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0
+ dependencies:
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
/react-redux@7.2.9(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==}
peerDependencies:
diff --git a/frontend/src/api/SubTaskApi.jsx b/frontend/src/api/SubTaskApi.jsx
new file mode 100644
index 0000000..1458153
--- /dev/null
+++ b/frontend/src/api/SubTaskApi.jsx
@@ -0,0 +1,44 @@
+import { axiosInstance } from "./AxiosConfig";
+
+export const getSubtask = async (parentTaskId) => {
+ try {
+ const response = await axiosInstance.get(`subtasks?parent_task=${parentTaskId}`);
+ return response.data;
+ } catch (error) {
+ console.error("Error fetching subtasks:", error);
+ throw error;
+ }
+};
+
+export const addSubtasks = async (parentTaskId, text) => {
+ try {
+ const response = await axiosInstance.post("subtasks/", {
+ description: text,
+ completed: false,
+ parent_task: parentTaskId,
+ });
+ return response.data;
+ } catch (error) {
+ console.error("Error adding subtask:", error);
+ throw error;
+ }
+};
+
+export const deleteSubtasks = async (subtaskId) => {
+ try {
+ await axiosInstance.delete(`subtasks/${subtaskId}/`);
+ } catch (error) {
+ console.error("Error deleting subtask:", error);
+ throw error;
+ }
+};
+
+export const updateSubtask = async (subtaskId, data) => {
+ try {
+ const response = await axiosInstance.patch(`subtasks/${subtaskId}/`, data);
+ return response.data;
+ } catch (error) {
+ console.error("Error updating subtask:", error);
+ throw error;
+ }
+};
diff --git a/frontend/src/api/TaskApi.jsx b/frontend/src/api/TaskApi.jsx
index 098d934..0a56767 100644
--- a/frontend/src/api/TaskApi.jsx
+++ b/frontend/src/api/TaskApi.jsx
@@ -38,6 +38,15 @@ export const updateTask = (endpoint, id, data) => {
});
};
+export const updateTaskPartial = (endpoint, id, data) => {
+ return axiosInstance
+ .patch(`${baseURL}${endpoint}/${id}/`, data)
+ .then((response) => response.data)
+ .catch((error) => {
+ throw error;
+ });
+};
+
export const deleteTask = (endpoint, id) => {
return axiosInstance
.delete(`${baseURL}${endpoint}/${id}/`)
@@ -64,6 +73,7 @@ export const readHabitTaskByID = (id) => readTaskByID("habit", id);
// Update
export const updateTodoTask = (id, data) => updateTask("todo", id, data);
+export const updateTodoTaskPartial = (id, data) => updateTaskPartial("todo", id, data);
export const updateRecurrenceTask = (id, data) => updateTask("daily", id, data);
export const updateHabitTask = (id, data) => updateTask("habit", id, data);
diff --git a/frontend/src/api/UserProfileApi.jsx b/frontend/src/api/UserProfileApi.jsx
index 6dfc050..1386e33 100644
--- a/frontend/src/api/UserProfileApi.jsx
+++ b/frontend/src/api/UserProfileApi.jsx
@@ -11,8 +11,6 @@ const ApiUpdateUserProfile = async (formData) => {
},
});
- console.log(response.data);
-
return response.data;
} catch (error) {
console.error("Error updating user profile:", error);
diff --git a/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx b/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx
index cc19aaa..9954950 100644
--- a/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx
+++ b/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx
@@ -1,5 +1,10 @@
import { useState, useEffect } from "react";
-import { FiAlertCircle, FiClock, FiXCircle, FiCheckCircle } from "react-icons/fi";
+import {
+ FiAlertCircle,
+ FiClock,
+ FiXCircle,
+ FiCheckCircle,
+} from "react-icons/fi";
import { readTodoTasks } from "../../api/TaskApi";
import { axiosInstance } from "src/api/AxiosConfig";
@@ -26,7 +31,9 @@ function EachBlog({ name, colorCode, contentList, icon }) {
};
return (
-
+
{icon}
{name}
@@ -39,10 +46,14 @@ function EachBlog({ name, colorCode, contentList, icon }) {
handleCheckboxChange(index)}
/>
-
diff --git a/frontend/src/components/calendar/calendar.jsx b/frontend/src/components/calendar/calendar.jsx
index 79dae97..c03637a 100644
--- a/frontend/src/components/calendar/calendar.jsx
+++ b/frontend/src/components/calendar/calendar.jsx
@@ -5,6 +5,7 @@ import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { getEvents, createEventId } from "./TaskDataHandler";
+import { axiosInstance } from "src/api/AxiosConfig";
export class Calendar extends React.Component {
state = {
@@ -25,13 +26,13 @@ export class Calendar extends React.Component {
right: "dayGridMonth,timeGridWeek,timeGridDay",
}}
initialView="dayGridMonth"
- editable={true}
- selectable={true}
+ editable={false}
+ selectable={false}
selectMirror={true}
dayMaxEvents={true}
weekends={this.state.weekendsVisible}
initialEvents={getEvents}
- select={this.handleDateSelect}
+ // select={this.handleDateSelect}
eventContent={renderEventContent}
eventClick={this.handleEventClick}
eventsSet={this.handleEvents}
@@ -85,22 +86,22 @@ export class Calendar extends React.Component {
});
};
- handleDateSelect = (selectInfo) => {
- let title = prompt("Please enter a new title for your event");
- let calendarApi = selectInfo.view.calendar;
+ // handleDateSelect = (selectInfo) => {
+ // let title = prompt("Please enter a new title for your event");
+ // let calendarApi = selectInfo.view.calendar;
- calendarApi.unselect(); // clear date selection
+ // calendarApi.unselect(); // clear date selection
- if (title) {
- calendarApi.addEvent({
- id: createEventId(),
- title,
- start: selectInfo.startStr,
- end: selectInfo.endStr,
- allDay: selectInfo.allDay,
- });
- }
- };
+ // if (title) {
+ // calendarApi.addEvent({
+ // id: createEventId(),
+ // title,
+ // start: selectInfo.startStr,
+ // end: selectInfo.endStr,
+ // allDay: selectInfo.allDay,
+ // });
+ // }
+ // };
handleEventClick = (clickInfo) => {
if (confirm(`Are you sure you want to delete the event '${clickInfo.event.title}'`)) {
diff --git a/frontend/src/components/dashboard/dashboard.jsx b/frontend/src/components/dashboard/dashboard.jsx
index 005f98a..74b4ac8 100644
--- a/frontend/src/components/dashboard/dashboard.jsx
+++ b/frontend/src/components/dashboard/dashboard.jsx
@@ -28,6 +28,7 @@ export function Dashboard() {
const [totalTask, setTotalTask] = useState(0);
const [totalCompletedTasks, settotalCompletedTasks] = useState(0);
const [totalCompletedTasksToday, setTotalCompletedTasksToday] = useState(0);
+ const [totalTaskToday, setTotalTaskToday] = useState(0);
const [progressData, setProgressData] = useState(0);
const [overdueTask, setOverdueTask] = useState(0);
@@ -36,19 +37,16 @@ export function Dashboard() {
const response = await axiosInstance.get("/dashboard/todostats/");
const totalTaskValue = response.data.total_tasks || 0;
const totalCompletedTasksValue = response.data.total_completed_tasks || 0;
+ const totalTaskTodayValue = response.data.total_task_today || 0;
const totalCompletedTasksTodayValue =
- response.data.total_completed_tasks_today || 0;
- const totalTaskToday = response.data.total_task_today || 0;
- const totalCompletedTasksToday = response.data.tasks_completed_today || 0;
+ response.data.tasks_completed_today || 0;
const overdueTasks = response.data.overdue_tasks || 0;
-
- const progress =
- (totalCompletedTasksToday / totalCompletedTasksToday) * 100;
+ const progress = (totalCompletedTasksToday / totalTaskToday) * 100;
setTotalTask(totalTaskValue);
settotalCompletedTasks(totalCompletedTasksValue);
setTotalCompletedTasksToday(totalCompletedTasksTodayValue);
- setTotalTaskToday(totalTaskToday);
+ setTotalTaskToday(totalTaskTodayValue);
setProgressData(progress);
setOverdueTask(overdueTasks);
};
@@ -147,7 +145,11 @@ export function Dashboard() {
task.id);
}, [tasks]);
+ const { setNodeRef, attributes, listeners } = useSortable({
+ id: column.id,
+ data: {
+ type: "Column",
+ column,
+ },
+ });
+
return (
{/* Column title */}
{
- const updatedTasks = tasks.map((task) =>
- task.id === updatedTask.id ? updatedTask : task
- );
+ const updatedTasks = tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task));
setTasks(updatedTasks);
};
@@ -129,6 +121,7 @@ export function KanbanBoard() {
user: task.user,
list_board: task.list_board,
tags: task.tags,
+ subtaskCount: task.sub_task_count,
}));
setTasks(transformedTasks);
@@ -176,14 +169,8 @@ export function KanbanBoard() {
justify-center
overflow-x-auto
overflow-y-hidden
- "
- >
-
+ ">
+
{!isLoading ? (
@@ -195,9 +182,7 @@ export function KanbanBoard() {
createTask={createTask}
deleteTask={deleteTask}
updateTask={updateTask}
- tasks={(tasks || []).filter(
- (task) => task.columnId === col.id
- )}
+ tasks={(tasks || []).filter((task) => task.columnId === col.id)}
/>
))}{" "}
@@ -210,11 +195,7 @@ export function KanbanBoard() {
{createPortal(
{/* Render the active task as a draggable overlay */}
-
+
,
document.body
)}
@@ -240,26 +221,43 @@ export function KanbanBoard() {
if (!over) return; // If not dropped over anything, exit
const activeId = active.id;
- const overId = over.id;
-
const isActiveATask = active.data.current?.type === "Task";
+ const isOverATask = over.data.current?.type === "Task";
const isOverAColumn = over.data.current?.type === "Column";
+ if (isActiveATask && isOverATask) {
+ setTasks((tasks) => {
+ const activeIndex = tasks.findIndex((t) => t.id === activeId);
+ const columnId = over.data.current.task.columnId;
+ tasks[activeIndex].columnId = columnId;
+ // API call to update task's columnId
+ axiosInstance
+ .put(`todo/change_task_list_board/`, {
+ todo_id: activeId,
+ new_list_board_id: columnId,
+ new_index: 0,
+ })
+ .then((response) => {})
+ .catch((error) => {
+ console.error("Error updating task columnId:", error);
+ });
+
+ return arrayMove(tasks, activeIndex, activeIndex);
+ });
+ }
+
// Move tasks between columns and update columnId
if (isActiveATask && isOverAColumn) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
-
- // Extract the column ID from overId
- const columnId = extractColumnId(overId);
+ const columnId = over.data.current.column.id;
tasks[activeIndex].columnId = columnId;
-
// API call to update task's columnId
axiosInstance
.put(`todo/change_task_list_board/`, {
todo_id: activeId,
- new_list_board_id: over.data.current.task.columnId,
+ new_list_board_id: columnId,
new_index: 0,
})
.then((response) => {})
@@ -271,15 +269,6 @@ export function KanbanBoard() {
});
}
}
-
- // Helper function to extract the column ID from the element ID
- function extractColumnId(elementId) {
- // Implement logic to extract the column ID from elementId
- // For example, if elementId is in the format "column-123", you can do:
- const parts = elementId.split("-");
- return parts.length === 2 ? parseInt(parts[1], 10) : null;
- }
-
// Handle the drag-over event
function onDragOver(event) {
const { active, over } = event;
@@ -306,39 +295,15 @@ export function KanbanBoard() {
tasks[activeIndex].columnId = tasks[overIndex].columnId;
return arrayMove(tasks, activeIndex, overIndex - 1);
}
- axiosInstance
- .put(`todo/change_task_list_board/`, {
- todo_id: activeId,
- new_list_board_id: over.data.current.task.columnId,
- new_index: 0,
- })
- .then((response) => {})
- .catch((error) => {
- console.error("Error updating task columnId:", error);
- });
return arrayMove(tasks, activeIndex, overIndex);
});
}
const isOverAColumn = over.data.current?.type === "Column";
// Move the Task to a different column and update columnId
- if (
- isActiveATask &&
- isOverAColumn &&
- tasks.some((task) => task.columnId !== overId)
- ) {
+ if (isActiveATask && isOverAColumn && tasks.some((task) => task.columnId !== overId)) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
- axiosInstance
- .put(`todo/change_task_list_board/`, {
- todo_id: activeId,
- new_list_board_id: over.data.current.task.columnId,
- new_index: 0,
- })
- .then((response) => {})
- .catch((error) => {
- console.error("Error updating task columnId:", error);
- });
tasks[activeIndex].columnId = overId;
return arrayMove(tasks, activeIndex, activeIndex);
});
diff --git a/frontend/src/components/kanbanBoard/taskCard.jsx b/frontend/src/components/kanbanBoard/taskCard.jsx
index e631a26..4c40a9e 100644
--- a/frontend/src/components/kanbanBoard/taskCard.jsx
+++ b/frontend/src/components/kanbanBoard/taskCard.jsx
@@ -1,16 +1,14 @@
import { useState } from "react";
-import { useEffect } from "react";
-import { BsFillTrashFill } from "react-icons/bs";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { TaskDetailModal } from "./taskDetailModal";
+import { GoChecklist, GoArchive } from "react-icons/go";
export function TaskCard({ task, deleteTask, updateTask }) {
+ // State to track if the mouse is over the task card
const [mouseIsOver, setMouseIsOver] = useState(false);
- // console.log(task.challenge);
- // console.log(task.importance);
- // console.log(task.difficulty);
+ // DnD Kit hook for sortable items
const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
id: task.id,
data: {
@@ -18,68 +16,163 @@ export function TaskCard({ task, deleteTask, updateTask }) {
task,
},
});
+
+ // Style for the task card, adjusting for dragging animation
const style = {
transition,
transform: CSS.Transform.toString(transform),
};
+ // ---- DESC AND TAG ---- */
-
- {
- /* If card is dragged */
+ if (task.tags === undefined) {
+ task.tags = [];
}
+
+ // Tags
+ const tags =
+ task.tags.length > 0 ? (
+
+ {task.tags.map((tag, index) => (
+
+ ))}
+
+ ) : null;
+
+ // difficulty?
+ const difficultyTag = task.difficulty ? (
+
+ difficulty
+
+ ) : null;
+
+ // Due Date
+ const dueDateTag =
+ task.end_event && new Date(task.end_event) > new Date()
+ ? (() => {
+ const daysUntilDue = Math.ceil((new Date(task.end_event) - new Date()) / (1000 * 60 * 60 * 24));
+
+ let colorClass =
+ daysUntilDue >= 365
+ ? "gray-200"
+ : daysUntilDue >= 30
+ ? "blue-200"
+ : daysUntilDue >= 7
+ ? "green-200"
+ : daysUntilDue > 0
+ ? "yellow-200"
+ : "red-200";
+
+ const formattedDueDate =
+ daysUntilDue >= 365
+ ? new Date(task.end_event).toLocaleDateString("en-US", {
+ day: "numeric",
+ month: "short",
+ year: "numeric",
+ })
+ : new Date(task.end_event).toLocaleDateString("en-US", { day: "numeric", month: "short" });
+
+ return (
+
+ Due: {formattedDueDate}
+
+ );
+ })()
+ : null;
+
+ // Subtask count
+ const subtaskCountTag = task.subtaskCount ? (
+
+ {task.subtaskCount}
+
+ ) : null;
+
+ // ---- DRAG STATE ---- */
+
+ // If the card is being dragged
if (isDragging) {
return (
);
}
+ // If the card is not being dragged
return (
+ {/* Task Detail Modal */}
+
+ {/* -------- Task Card -------- */}
{
setMouseIsOver(true);
}}
onMouseLeave={() => {
setMouseIsOver(false);
- }}>
-
document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
- {task.content}
-
-
- {mouseIsOver && (
-
- )}
+ }}
+ onClick={() => document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
+ {/* -------- Task Content -------- */}
+ {/* Tags */}
+ {tags}
+
+ {/* Title */}
+
document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
+ {task.content}
+
+ {/* -------- Archive Task Button -------- */}
+ {mouseIsOver && (
+
+ )}
+
+ {/* Description */}
+
+ {difficultyTag}
+ {dueDateTag}
+ {subtaskCountTag}
+
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/components/kanbanBoard/taskDetailModal.jsx b/frontend/src/components/kanbanBoard/taskDetailModal.jsx
index d349c3d..3941ba1 100644
--- a/frontend/src/components/kanbanBoard/taskDetailModal.jsx
+++ b/frontend/src/components/kanbanBoard/taskDetailModal.jsx
@@ -1,44 +1,283 @@
-import { useState } from "react";
+import { useState, useEffect } from "react";
import { FaTasks, FaRegListAlt } from "react-icons/fa";
-import { FaPlus } from "react-icons/fa6";
+import { FaPlus, FaRegTrashCan, FaPencil } from "react-icons/fa6";
import { TbChecklist } from "react-icons/tb";
+import DatePicker from "react-datepicker";
+import "react-datepicker/dist/react-datepicker.css";
+import { addSubtasks, deleteSubtasks, getSubtask, updateSubtask } from "src/api/SubTaskApi";
+import { updateTodoTaskPartial } from "src/api/TaskApi";
+import format from "date-fns/format";
-export function TaskDetailModal({ title, description, tags, difficulty, challenge, importance, taskId }) {
+export function TaskDetailModal({
+ title,
+ description,
+ tags,
+ difficulty,
+ challenge,
+ importance,
+ taskId,
+ updateTask,
+ completed,
+}) {
const [isChallengeChecked, setChallengeChecked] = useState(challenge);
const [isImportantChecked, setImportantChecked] = useState(importance);
- const [currentDifficulty, setCurrentDifficulty] = useState(difficulty);
- // console.log(currentDifficulty);
- // console.log(isChallengeChecked);
- // console.log(isImportantChecked);
+ const [currentDifficulty, setCurrentDifficulty] = useState((difficulty - 1) * 25);
+ const [selectedTags, setSelectedTags] = useState([]);
+ const [dateStart, setDateStart] = useState(new Date());
+ const [dateEnd, setDateEnd] = useState(new Date());
+ const [startDateEnabled, setStartDateEnabled] = useState(false);
+ const [endDateEnabled, setEndDateEnabled] = useState(false);
+ const [isTaskComplete, setTaskComplete] = useState(completed);
+ const [starteventValue, setStartEventValue] = useState("10:00 PM");
+ const [endeventValue, setEndEventValue] = useState("11:00 AM");
+ const [subtaskText, setSubtaskText] = useState("");
+ const [subtasks, setSubtasks] = useState([]);
+ const [currentTitle, setTitle] = useState(title);
+ const [isTitleEditing, setTitleEditing] = useState(false);
- const handleChallengeChange = () => {
+ const handleTitleChange = async () => {
+ const data = {
+ title: currentTitle,
+ };
+ await updateTodoTaskPartial(taskId, data);
+ setTitleEditing(false);
+ };
+
+ const handleStartEventTimeChange = async (timeValue) => {
+ const formattedTime = convertToFormattedTime(timeValue);
+ setStartEventValue(formattedTime);
+ console.log(formattedTime);
+ const data = {
+ startTime: formattedTime,
+ };
+ await updateTodoTaskPartial(taskId, data);
+ };
+
+ const handleEndEventTimeChange = async (timeValue) => {
+ const inputTime = event.target.value;
+ // Validate the input time format
+ if (!validateTimeFormat(inputTime)) {
+ // Display an error message or handle invalid format
+ console.error("Invalid time format. Please use HH:mm AM/PM");
+ return;
+ }
+
+ const formattedTime = convertToFormattedTime(timeValue);
+ setEndEventValue(formattedTime);
+ const data = {
+ endTime: formattedTime,
+ };
+ await updateTodoTaskPartial(taskId, data);
+ };
+
+ const convertToFormattedTime = (timeValue) => {
+ const formattedTime = format(timeValue, "HH:mm:ss.SSSX", { timeZone: "UTC" });
+ return formattedTime;
+ };
+
+ const validateTimeFormat = (time) => {
+ const timeFormatRegex = /^(0[1-9]|1[0-2]):[0-5][0-9] (AM|PM)$/i;
+ return timeFormatRegex.test(time);
+ };
+
+ const handleChallengeChange = async () => {
setChallengeChecked(!isChallengeChecked);
+ const data = {
+ challenge: !isChallengeChecked,
+ };
+ await updateTodoTaskPartial(taskId, data);
};
- const handleImportantChange = () => {
+ const handleImportantChange = async () => {
setImportantChecked(!isImportantChecked);
+ const data = {
+ important: !isImportantChecked,
+ };
+ await updateTodoTaskPartial(taskId, data);
};
- const handleDifficultyChange = (event) => {
+ const handleDifficultyChange = async (event) => {
setCurrentDifficulty(parseInt(event.target.value, 10));
+ let diff = event.target.value / 25 + 1;
+ const data = {
+ difficulty: diff,
+ };
+ await updateTodoTaskPartial(taskId, data);
};
+ const handleTagChange = (tag) => {
+ const isSelected = selectedTags.includes(tag);
+ setSelectedTags(isSelected ? selectedTags.filter((selectedTag) => selectedTag !== tag) : [...selectedTags, tag]);
+ ``;
+ };
+
+ const handleStartDateValueChange = (date) => {
+ if (!isTaskComplete) {
+ setDateStart(date);
+ const formattedStartDate = convertToFormattedDate(date);
+ const data = {
+ startTime: formattedStartDate,
+ };
+ updateTodoTaskPartial(taskId, data);
+ }
+ };
+
+ const handleEndDateValueChange = (date) => {
+ if (!isTaskComplete) {
+ setDateEnd(date);
+ const formattedEndDate = convertToFormattedDate(date);
+ const data = {
+ endTime: formattedEndDate,
+ };
+ updateTodoTaskPartial(taskId, data);
+ }
+ };
+
+ const convertToFormattedDate = (dateValue) => {
+ const formattedDate = format(dateValue, "yyyy-MM-dd'T'", { timeZone: "UTC" });
+ return formattedDate;
+ };
+
+ const handleStartDateChange = () => {
+ if (!isTaskComplete) {
+ setStartDateEnabled(!startDateEnabled);
+ }
+ };
+
+ const handleEndDateChange = () => {
+ if (!isTaskComplete) {
+ setEndDateEnabled(!endDateEnabled);
+ }
+ };
+
+ const handleTaskCompleteChange = async () => {
+ let completed = false;
+ if (isTaskComplete) {
+ setTaskComplete(false);
+ completed = false;
+ } else {
+ setTaskComplete(true);
+ completed = true;
+ setStartDateEnabled(false);
+ setEndDateEnabled(false);
+ }
+ const data = {
+ completed: completed,
+ };
+ await updateTodoTaskPartial(taskId, data);
+ };
+
+ const addSubtask = async () => {
+ try {
+ if (subtaskText.trim() !== "") {
+ const newSubtask = await addSubtasks(taskId, subtaskText.trim());
+ setSubtasks([...subtasks, newSubtask]);
+ setSubtaskText("");
+ }
+ } catch (error) {
+ console.error("Error adding subtask:", error);
+ }
+ };
+
+ const toggleSubtaskCompletion = async (index) => {
+ try {
+ const updatedSubtasks = [...subtasks];
+ updatedSubtasks[index].completed = !updatedSubtasks[index].completed;
+ await updateSubtask(updatedSubtasks[index].id, { completed: updatedSubtasks[index].completed });
+ setSubtasks(updatedSubtasks);
+ } catch (error) {
+ console.error("Error updating subtask:", error);
+ }
+ };
+
+ const deleteSubtask = async (index) => {
+ try {
+ await deleteSubtasks(subtasks[index].id);
+ const updatedSubtasks = [...subtasks];
+ updatedSubtasks.splice(index, 1);
+ setSubtasks(updatedSubtasks);
+ } catch (error) {
+ console.error("Error deleting subtask:", error);
+ }
+ };
+
+ const subtaskElements = subtasks.map((subtask, index) => (
+
+
toggleSubtaskCompletion(index)}
+ />
+
+ {subtask.description}
+ deleteSubtask(index)} />
+
+
+ ));
+
+ useEffect(() => {
+ const fetchSubtasks = async () => {
+ try {
+ const fetchedSubtasks = await getSubtask(taskId);
+ setSubtasks(fetchedSubtasks);
+ } catch (error) {
+ console.error("Error fetching subtasks:", error);
+ }
+ };
+
+ fetchSubtasks();
+ }, [taskId]);
+
+ // Existing tags
+ const existingTags = tags.map((tag, index) => (
+
+ {tag.name}
+
+ ));
+
+ // Selected tags
+ const selectedTagElements = selectedTags.map((tag, index) => (
+
+ {tag.name}
+
+ ));
+
return (
-
{/* Subtask */}
-
diff --git a/frontend/src/components/navigations/Navbar.jsx b/frontend/src/components/navigations/Navbar.jsx
index 22120b3..dadb503 100644
--- a/frontend/src/components/navigations/Navbar.jsx
+++ b/frontend/src/components/navigations/Navbar.jsx
@@ -1,6 +1,8 @@
import { useNavigate } from "react-router-dom";
import { apiUserLogout } from "src/api/AuthenticationApi";
import { useAuth } from "src/hooks/AuthHooks";
+import { axiosInstance } from "src/api/AxiosConfig";
+import { useEffect, useState } from "react";
const settings = {
Profile: "/profile",
@@ -10,6 +12,7 @@ const settings = {
export function NavBar() {
const Navigate = useNavigate();
const { isAuthenticated, setIsAuthenticated } = useAuth();
+ const [profile_pic, setProfilePic] = useState(undefined);
const logout = () => {
apiUserLogout();
@@ -17,6 +20,25 @@ export function NavBar() {
Navigate("/");
};
+ useEffect(() => {
+ const fetchUser = async () => {
+ if (isAuthenticated) {
+ try {
+ const response = await axiosInstance.get("/user/data/");
+ const fetchedProfilePic = response.data.profile_pic;
+ setProfilePic(fetchedProfilePic);
+ } catch (error) {
+ console.error("Error fetching user:", error);
+ }
+ } else {
+ setProfilePic(
+ "https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png"
+ );
+ }
+ };
+ fetchUser();
+ }, []);
+
return (
@@ -32,19 +54,23 @@ export function NavBar() {
-

+
) : (
-
diff --git a/frontend/src/components/profile/ProfileUpdateComponent.jsx b/frontend/src/components/profile/ProfileUpdateComponent.jsx
index 12f5e98..6a918af 100644
--- a/frontend/src/components/profile/ProfileUpdateComponent.jsx
+++ b/frontend/src/components/profile/ProfileUpdateComponent.jsx
@@ -1,13 +1,30 @@
import { useState, useRef } from "react";
import { ApiUpdateUserProfile } from "src/api/UserProfileApi";
+import { axiosInstance } from "src/api/AxiosConfig";
+import { useEffect } from "react";
export function ProfileUpdateComponent() {
const [file, setFile] = useState(null);
- const [username, setUsername] = useState("");
- const [fullName, setFullName] = useState("");
- const [about, setAbout] = useState("");
- const defaultImage = "https://i1.sndcdn.com/artworks-cTz48e4f1lxn5Ozp-L3hopw-t500x500.jpg";
+ const [username, setUserName] = useState("");
+ const [about, setAbout] = useState();
const fileInputRef = useRef(null);
+ const [profile_pic, setProfilePic] = useState(undefined);
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const response = await axiosInstance.get("/user/data/");
+ const fetchedProfilePic = response.data.profile_pic;
+ const fetchedName = response.data.username;
+ const fetchedAbout = response.data.about;
+ setProfilePic(fetchedProfilePic);
+ setAbout(fetchedAbout);
+ setUserName(fetchedName);
+ } catch (error) {
+ console.error("Error fetching user:", error);
+ }
+ };
+ fetchUser();
+ }, []);
const handleImageUpload = () => {
if (fileInputRef.current) {
@@ -25,7 +42,7 @@ export function ProfileUpdateComponent() {
const handleSave = () => {
const formData = new FormData();
formData.append("profile_pic", file);
- formData.append("first_name", username);
+ formData.append("username", username);
formData.append("about", about);
ApiUpdateUserProfile(formData);
@@ -50,7 +67,7 @@ export function ProfileUpdateComponent() {
})
) : (
<>
-

+
>
@@ -58,7 +75,7 @@ export function ProfileUpdateComponent() {
- {/* Username Field */}
+ {/* Username Field
Username
setUsername(e.target.value)}
/>
-
+
*/}
{/* Full Name Field */}
- Full Name
+ username
setFullName(e.target.value)}
+ value={username}
+ onChange={(e) => setUserName(e.target.value)}
/>
diff --git a/frontend/src/components/profile/profilePage.jsx b/frontend/src/components/profile/profilePage.jsx
index d61368a..b544323 100644
--- a/frontend/src/components/profile/profilePage.jsx
+++ b/frontend/src/components/profile/profilePage.jsx
@@ -1,36 +1,66 @@
import { ProfileUpdateComponent } from "./ProfileUpdateComponent";
+import { axiosInstance } from "src/api/AxiosConfig";
+import { useEffect, useState } from "react";
export function ProfileUpdatePage() {
+ const [profile_pic, setProfilePic] = useState(undefined);
+ const [about, setAbout] = useState();
+ const [username, setUsernames] = useState();
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const response = await axiosInstance.get("/user/data/");
+ const fetchedProfilePic = response.data.profile_pic;
+ const fetchedAbout = response.data.about;
+ const fetchedUsernames = response.data.username;
+ setProfilePic(fetchedProfilePic);
+ setAbout(fetchedAbout);
+ setUsernames(fetchedUsernames);
+ } catch (error) {
+ console.error("Error fetching user:", error);
+ }
+ };
+ fetchUser();
+ }, []);
return (
Username
-
Sirin
-
User ID
+
{username}
+ {/*
User ID
*/}
-

+
-
*/}
+ {/*
Level
@@ -40,13 +70,18 @@ export function ProfileUpdatePage() {
xmlns="http://www.w3.org/2000/svg"
fill="#3abff8"
viewBox="0 0 24 24"
- className="inline-block w-8 h-8">
+ className="inline-block w-8 h-8"
+ >
3213/321312321 points
-
+
@@ -58,34 +93,40 @@ export function ProfileUpdatePage() {
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
- className="inline-block w-8 h-8 stroke-current">
+ className="inline-block w-8 h-8 stroke-current"
+ >
+ d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
+ >
Top 12% of Global Ranking
-
-
+
+
*/}
-
-
+ {/*
@@ -110,18 +151,21 @@ export function ProfileUpdatePage() {
-
+
*/}