mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-19 05:54:07 +01:00
Merge pull request #83 from TurTaskProject/feature/dashboard
Completed all dashboard features and corrected percent issues. + Rewrite test of task/ dashboard + move some signal to users
This commit is contained in:
commit
92731815da
@ -4,6 +4,3 @@ from django.apps import AppConfig
|
|||||||
class BoardsConfig(AppConfig):
|
class BoardsConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'boards'
|
name = 'boards'
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
import boards.signals
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
from django.db.models.signals import post_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
|
|
||||||
from boards.models import Board, ListBoard
|
|
||||||
from users.models import CustomUser
|
|
||||||
|
|
||||||
@receiver(post_save, sender=CustomUser)
|
|
||||||
def create_default_board(sender, instance, created, **kwargs):
|
|
||||||
"""Signal handler to automatically create a default Board for a user upon creation."""
|
|
||||||
if created:
|
|
||||||
# Create unique board by user id
|
|
||||||
user_id = instance.id
|
|
||||||
board = Board.objects.create(user=instance, name=f"Board of #{user_id}")
|
|
||||||
ListBoard.objects.create(board=board, name="Backlog", position=1)
|
|
||||||
ListBoard.objects.create(board=board, name="Doing", position=2)
|
|
||||||
ListBoard.objects.create(board=board, name="Review", position=3)
|
|
||||||
ListBoard.objects.create(board=board, name="Done", position=4)
|
|
||||||
@ -88,6 +88,7 @@ SPECTACULAR_SETTINGS = {
|
|||||||
'DESCRIPTION': 'API documentation for TurTask',
|
'DESCRIPTION': 'API documentation for TurTask',
|
||||||
'VERSION': '1.0.0',
|
'VERSION': '1.0.0',
|
||||||
'SERVE_INCLUDE_SCHEMA': False,
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
|
'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'],
|
||||||
}
|
}
|
||||||
|
|
||||||
REST_USE_JWT = True
|
REST_USE_JWT = True
|
||||||
|
|||||||
@ -88,6 +88,7 @@ SPECTACULAR_SETTINGS = {
|
|||||||
'DESCRIPTION': 'API documentation for TurTask',
|
'DESCRIPTION': 'API documentation for TurTask',
|
||||||
'VERSION': '1.0.0',
|
'VERSION': '1.0.0',
|
||||||
'SERVE_INCLUDE_SCHEMA': False,
|
'SERVE_INCLUDE_SCHEMA': False,
|
||||||
|
'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'],
|
||||||
}
|
}
|
||||||
|
|
||||||
REST_USE_JWT = True
|
REST_USE_JWT = True
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from rest_framework import serializers
|
# from rest_framework import serializers
|
||||||
from .models import UserStats
|
# from .models import UserStats
|
||||||
|
|
||||||
class UserStatsSerializer(serializers.ModelSerializer):
|
# class UserStatsSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
# class Meta:
|
||||||
model = UserStats
|
# model = UserStats
|
||||||
fields = ['health', 'gold', 'experience', 'strength', 'intelligence', 'endurance', 'perception', 'luck', 'level']
|
# fields = ['health', 'gold', 'experience', 'strength', 'intelligence', 'endurance', 'perception', 'luck', 'level']
|
||||||
@ -1,32 +1,35 @@
|
|||||||
from django.test import TestCase
|
from rest_framework.test import APITestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from tasks.models import Todo
|
from tasks.models import Todo
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from tasks.tests.utils import create_test_user, login_user
|
from boards.models import Board
|
||||||
|
from tasks.tests.utils import create_test_user
|
||||||
|
|
||||||
class DashboardStatsAndWeeklyViewSetTests(TestCase):
|
class DashboardStatsAndWeeklyViewSetTests(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = create_test_user()
|
self.user = create_test_user()
|
||||||
self.client = login_user(self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
|
self.list_board = Board.objects.get(user=self.user).listboard_set.first()
|
||||||
|
|
||||||
def create_task(self, title, completed=False, completion_date=None, end_event=None):
|
def _create_task(self, title, completed=False, completion_date=None, end_event=None):
|
||||||
return Todo.objects.create(
|
return Todo.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
title=title,
|
title=title,
|
||||||
completed=completed,
|
completed=completed,
|
||||||
completion_date=completion_date,
|
completion_date=completion_date,
|
||||||
end_event=end_event
|
end_event=end_event,
|
||||||
|
list_board=self.list_board
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_dashboard_stats_view(self):
|
def test_dashboard_stats_view(self):
|
||||||
# Create tasks for testing
|
# Create tasks for testing
|
||||||
self.create_task('Task 1', completed=True)
|
self._create_task('Task 1', completed=True)
|
||||||
self.create_task('Task 2', end_event=timezone.now() - timedelta(days=8))
|
self._create_task('Task 2', end_event=timezone.now() - timedelta(days=8))
|
||||||
self.create_task('Task 3', end_event=timezone.now())
|
self._create_task('Task 3', end_event=timezone.now())
|
||||||
|
|
||||||
response = self.client.get(reverse('stats-list'))
|
response = self.client.get(reverse('statstodo-list'))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.assertEqual(response.data['completed_this_week'], 1)
|
self.assertEqual(response.data['completed_this_week'], 1)
|
||||||
@ -35,69 +38,9 @@ class DashboardStatsAndWeeklyViewSetTests(TestCase):
|
|||||||
|
|
||||||
def test_dashboard_weekly_view(self):
|
def test_dashboard_weekly_view(self):
|
||||||
# Create tasks for testing
|
# Create tasks for testing
|
||||||
self.create_task('Task 1', completion_date=timezone.now() - timedelta(days=1))
|
self._create_task('Task 1', completion_date=timezone.now() - timedelta(days=1))
|
||||||
self.create_task('Task 2', end_event=timezone.now() - timedelta(days=8))
|
self._create_task('Task 2', end_event=timezone.now() - timedelta(days=8))
|
||||||
self.create_task('Task 3', end_event=timezone.now())
|
self._create_task('Task 3', end_event=timezone.now())
|
||||||
|
|
||||||
response = self.client.get(reverse('weekly-list'))
|
response = self.client.get(reverse('weekly-list'))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
# class DashboardStatsAPITestCase(TestCase):
|
|
||||||
# def setUp(self):
|
|
||||||
# # Create a test user
|
|
||||||
# self.user = create_test_user()
|
|
||||||
|
|
||||||
# # Create test tasks
|
|
||||||
# self.todo = Todo.objects.create(user=self.user, title='Test Todo')
|
|
||||||
# self.recurrence_task = RecurrenceTask.objects.create(user=self.user, title='Test Recurrence Task')
|
|
||||||
|
|
||||||
# # Create an API client
|
|
||||||
# self.client = APIClient()
|
|
||||||
|
|
||||||
# def test_dashboard_stats_api(self):
|
|
||||||
# # Authenticate the user
|
|
||||||
# self.client.force_authenticate(user=self.user)
|
|
||||||
|
|
||||||
# # Make a GET request to the DashboardStatsAPIView
|
|
||||||
# response = self.client.get(reverse("dashboard-stats"))
|
|
||||||
|
|
||||||
# # Assert the response status code is 200
|
|
||||||
# self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# def test_task_completion_status_update(self):
|
|
||||||
# # Authenticate the user
|
|
||||||
# self.client.force_authenticate(user=self.user)
|
|
||||||
|
|
||||||
# # Make a POST request to update the completion status of a task
|
|
||||||
# data = {'task_id': self.todo.id, 'is_completed': True}
|
|
||||||
# response = self.client.post(reverse("dashboard-stats"), data, format='json')
|
|
||||||
|
|
||||||
# # Assert the response status code is 200
|
|
||||||
# self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# # Assert the message in the response
|
|
||||||
# self.assertEqual(response.data['message'], 'Task completion status updated successfully')
|
|
||||||
|
|
||||||
# # Refresh the todo instance from the database and assert the completion status
|
|
||||||
# self.todo.refresh_from_db()
|
|
||||||
# self.assertTrue(self.todo.completed)
|
|
||||||
|
|
||||||
|
|
||||||
# class WeeklyStatsAPITestCase(TestCase):
|
|
||||||
# def setUp(self):
|
|
||||||
# # Create a test user
|
|
||||||
# self.user = create_test_user()
|
|
||||||
|
|
||||||
# # Create an API client
|
|
||||||
# self.client = APIClient()
|
|
||||||
|
|
||||||
# def test_weekly_stats_api(self):
|
|
||||||
# # Authenticate the user
|
|
||||||
# self.client.force_authenticate(user=self.user)
|
|
||||||
|
|
||||||
# # Make a GET request to the WeeklyStatsAPIView
|
|
||||||
# response = self.client.get(reverse('dashboard-weekly-stats'))
|
|
||||||
|
|
||||||
# # Assert the response status code is 200
|
|
||||||
# self.assertEqual(response.status_code, 200)
|
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from .views import DashboardStatsViewSet, DashboardWeeklyViewSet
|
from .views import DashboardStatsTodoViewSet, DashboardWeeklyViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'dashboard/stats', DashboardStatsViewSet, basename='stats')
|
router.register(r'dashboard/todostats', DashboardStatsTodoViewSet, basename='statstodo')
|
||||||
router.register(r'dashboard/weekly', DashboardWeeklyViewSet, basename='weekly')
|
router.register(r'dashboard/weekly', DashboardWeeklyViewSet, basename='weekly')
|
||||||
|
router.register(r'dashboard/recstats', DashboardStatsTodoViewSet, basename='statsrec')
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -5,10 +5,13 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework import viewsets, mixins
|
from rest_framework import viewsets, mixins
|
||||||
|
|
||||||
from tasks.models import Todo
|
from tasks.models import Todo, RecurrenceTask
|
||||||
|
|
||||||
|
|
||||||
class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
class DashboardStatsTodoViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
||||||
|
"""
|
||||||
|
A viewset for retrieving statistics related to user tasks for the last 7 days.
|
||||||
|
"""
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -67,6 +70,27 @@ class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
|||||||
total_tasks = Todo.objects.filter(user=user).count()
|
total_tasks = Todo.objects.filter(user=user).count()
|
||||||
overall_completion_rate = (completed_last_7_days / total_tasks) * 100 if total_tasks > 0 else 0
|
overall_completion_rate = (completed_last_7_days / total_tasks) * 100 if total_tasks > 0 else 0
|
||||||
|
|
||||||
|
total_completed_tasks = Todo.objects.filter(user=user, completed=True).count()
|
||||||
|
|
||||||
|
total_tasks = Todo.objects.filter(user=user).count()
|
||||||
|
|
||||||
|
today_start = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
today_end = timezone.now().replace(hour=23, minute=59, second=59, microsecond=999999)
|
||||||
|
|
||||||
|
tasks_completed_today = Todo.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=True,
|
||||||
|
completion_date__gte=today_start,
|
||||||
|
completion_date__lte=today_end
|
||||||
|
).count()
|
||||||
|
|
||||||
|
total_tasks_today = Todo.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completion_date__gte=today_start,
|
||||||
|
completion_date__lte=today_end
|
||||||
|
).count()
|
||||||
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"completed_last_7_days": completed_last_7_days,
|
"completed_last_7_days": completed_last_7_days,
|
||||||
"tasks_assigned_last_week": tasks_assigned_last_week,
|
"tasks_assigned_last_week": tasks_assigned_last_week,
|
||||||
@ -75,6 +99,10 @@ class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
|||||||
"completed_this_week": completed_this_week,
|
"completed_this_week": completed_this_week,
|
||||||
"overdue_tasks": overdue_tasks,
|
"overdue_tasks": overdue_tasks,
|
||||||
"overall_completion_rate": overall_completion_rate,
|
"overall_completion_rate": overall_completion_rate,
|
||||||
|
"total_completed_tasks": total_completed_tasks,
|
||||||
|
"total_tasks" : total_tasks,
|
||||||
|
"total_tasks_today": total_tasks_today,
|
||||||
|
"tasks_completed_today": tasks_completed_today,
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
@ -145,6 +173,141 @@ class DashboardWeeklyViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
|||||||
|
|
||||||
return Response(weekly_stats, status=status.HTTP_200_OK)
|
return Response(weekly_stats, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
class DashboardStatsReccurenceViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
||||||
|
"""
|
||||||
|
A viewset for retrieving statistics related to user tasks for the last 7 days.
|
||||||
|
"""
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return RecurrenceTask.objects.all()
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
user = self.request.user
|
||||||
|
|
||||||
|
# Calculate the start and end date for the last 7 days
|
||||||
|
end_date = timezone.now()
|
||||||
|
start_date = end_date - timedelta(days=7)
|
||||||
|
|
||||||
|
# How many tasks were completed in the last 7 days
|
||||||
|
completed_last_7_days = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=True,
|
||||||
|
completion_date__gte=start_date,
|
||||||
|
completion_date__lte=end_date
|
||||||
|
).count()
|
||||||
|
|
||||||
|
# Task assign last week compared with this week
|
||||||
|
tasks_assigned_last_week = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completion_date__gte=start_date - timedelta(days=7),
|
||||||
|
completion_date__lte=start_date
|
||||||
|
).count()
|
||||||
|
|
||||||
|
tasks_assigned_this_week = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completion_date__gte=start_date,
|
||||||
|
completion_date__lte=end_date
|
||||||
|
).count()
|
||||||
|
|
||||||
|
# Completed tasks from last week compared with this week
|
||||||
|
completed_last_week = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=True,
|
||||||
|
completion_date__gte=start_date - timedelta(days=7),
|
||||||
|
completion_date__lte=start_date
|
||||||
|
).count()
|
||||||
|
|
||||||
|
completed_this_week = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=True,
|
||||||
|
completion_date__gte=start_date,
|
||||||
|
completion_date__lte=end_date
|
||||||
|
).count()
|
||||||
|
|
||||||
|
overdue_tasks = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=False,
|
||||||
|
end_event__lt=timezone.now()
|
||||||
|
).count()
|
||||||
|
|
||||||
|
# Overall completion rate
|
||||||
|
total_tasks = RecurrenceTask.objects.filter(user=user).count()
|
||||||
|
overall_completion_rate = (completed_last_7_days / total_tasks) * 100 if total_tasks > 0 else 0
|
||||||
|
|
||||||
|
total_completed_tasks = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
total_tasks = RecurrenceTask.objects.filter(user=user).count()
|
||||||
|
|
||||||
|
tasks_completed_today = RecurrenceTask.objects.filter(
|
||||||
|
user=user,
|
||||||
|
completed=True,
|
||||||
|
completion_date__gte=timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
).count()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"completed_last_7_days": completed_last_7_days,
|
||||||
|
"tasks_assigned_last_week": tasks_assigned_last_week,
|
||||||
|
"tasks_assigned_this_week": tasks_assigned_this_week,
|
||||||
|
"completed_last_week": completed_last_week,
|
||||||
|
"completed_this_week": completed_this_week,
|
||||||
|
"overdue_tasks": overdue_tasks,
|
||||||
|
"overall_completion_rate": overall_completion_rate,
|
||||||
|
"total_completed_tasks": total_completed_tasks,
|
||||||
|
"total_tasks" : total_tasks,
|
||||||
|
"tasks_completed_today": tasks_completed_today,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
# class DashboardStatsAllViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
|
||||||
|
# permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
# def get_queryset(self):
|
||||||
|
# return Todo.objects.filter(user=self.request.user)
|
||||||
|
|
||||||
|
# def list(self, request, *args, **kwargs):
|
||||||
|
# user = request.user
|
||||||
|
|
||||||
|
# # Calculate task usage statistics
|
||||||
|
# todo_count = self.get_queryset().count()
|
||||||
|
# recurrence_task_count = RecurrenceTask.objects.filter(user=user).count()
|
||||||
|
|
||||||
|
# # Calculate how many tasks were completed in the last 7 days
|
||||||
|
# completed_todo_count_last_week = Todo.objects.filter(user=user, completed=True, last_update__gte=timezone.now() - timezone.timedelta(days=7)).count()
|
||||||
|
# completed_recurrence_task_count_last_week = RecurrenceTask.objects.filter(user=user, completed=True, last_update__gte=timezone.now() - timezone.timedelta(days=7)).count()
|
||||||
|
|
||||||
|
# # Calculate subtask completion rate
|
||||||
|
# total_subtasks = Todo.objects.filter(user=user).aggregate(total=Count('subtask__id'))['total']
|
||||||
|
# completed_subtasks = Todo.objects.filter(user=user, subtask__completed=True).aggregate(total=Count('subtask__id'))['total']
|
||||||
|
|
||||||
|
# # Calculate overall completion rate
|
||||||
|
# total_tasks = todo_count + recurrence_task_count
|
||||||
|
# completed_tasks = completed_todo_count_last_week + completed_recurrence_task_count_last_week
|
||||||
|
# overall_completion_rate = (completed_tasks / total_tasks) * 100 if total_tasks > 0 else 0
|
||||||
|
|
||||||
|
# # pie chart show
|
||||||
|
# complete_todo_percent_last_week = (completed_todo_count_last_week / todo_count) * 100 if todo_count > 0 else 0
|
||||||
|
# complete_recurrence_percent_last_week = (completed_recurrence_task_count_last_week / recurrence_task_count) * 100 if recurrence_task_count > 0 else 0
|
||||||
|
# incomplete_task_percent_last_week = 100 - complete_recurrence_percent_last_week - complete_todo_percent_last_week
|
||||||
|
|
||||||
|
# data = {
|
||||||
|
# 'todo_count': todo_count,
|
||||||
|
# 'recurrence_task_count': recurrence_task_count,
|
||||||
|
# 'completed_todo_count_last_week': completed_todo_count_last_week,
|
||||||
|
# 'completed_recurrence_task_count_last_week': completed_recurrence_task_count_last_week,
|
||||||
|
# 'total_subtasks': total_subtasks,
|
||||||
|
# 'completed_subtasks': completed_subtasks,
|
||||||
|
# 'overall_completion_rate': overall_completion_rate,
|
||||||
|
# 'complete_todo_percent_last_week': complete_todo_percent_last_week,
|
||||||
|
# 'complete_recurrence_percent_last_week' : complete_recurrence_percent_last_week,
|
||||||
|
# 'incomplete_task_percent_last_week': incomplete_task_percent_last_week,
|
||||||
|
# }
|
||||||
|
|
||||||
|
# return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
# class DashboardStatsAPIView(APIView):
|
# class DashboardStatsAPIView(APIView):
|
||||||
# permission_classes = [IsAuthenticated]
|
# permission_classes = [IsAuthenticated]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Todo, RecurrenceTask
|
from boards.models import Board
|
||||||
|
from tasks.models import Todo, RecurrenceTask
|
||||||
|
|
||||||
|
|
||||||
class GoogleCalendarEventSerializer(serializers.Serializer):
|
class GoogleCalendarEventSerializer(serializers.Serializer):
|
||||||
@ -17,18 +18,22 @@ class TodoUpdateSerializer(serializers.ModelSerializer):
|
|||||||
updated = serializers.DateTimeField(source="last_update")
|
updated = serializers.DateTimeField(source="last_update")
|
||||||
start_datetime = serializers.DateTimeField(source="start_event", required=False)
|
start_datetime = serializers.DateTimeField(source="start_event", required=False)
|
||||||
end_datetime = serializers.DateTimeField(source="end_event", required=False)
|
end_datetime = serializers.DateTimeField(source="end_event", required=False)
|
||||||
|
list_board = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Todo
|
model = Todo
|
||||||
fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime')
|
fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime', 'list_board')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.user = kwargs.pop('user', None)
|
self.user = kwargs.pop('user', None)
|
||||||
super(TodoUpdateSerializer, self).__init__(*args, **kwargs)
|
super(TodoUpdateSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_list_board(self, obj):
|
||||||
|
return Board.objects.get(user=self.user).listboard_set.first()
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['user'] = self.user
|
validated_data['user'] = self.user
|
||||||
|
validated_data['list_board'] = self.get_list_board(self)
|
||||||
task = Todo.objects.create(**validated_data)
|
task = Todo.objects.create(**validated_data)
|
||||||
|
|
||||||
return task
|
return task
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
from django.db.models.signals import pre_save, post_save
|
from django.db.models.signals import pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from boards.models import ListBoard, Board
|
|
||||||
from tasks.models import Todo
|
from tasks.models import Todo
|
||||||
|
|
||||||
|
|
||||||
@ -25,65 +24,3 @@ def update_priority(sender, instance, **kwargs):
|
|||||||
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT
|
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT
|
||||||
else:
|
else:
|
||||||
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
|
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
|
||||||
|
|
||||||
|
|
||||||
# @receiver(post_save, sender=Todo)
|
|
||||||
# def assign_todo_to_listboard(sender, instance, created, **kwargs):
|
|
||||||
# """Signal handler to automatically assign a Todo to the first ListBoard in the user's Board upon creation."""
|
|
||||||
# if created:
|
|
||||||
# user_board = instance.user.board_set.first()
|
|
||||||
|
|
||||||
# if user_board:
|
|
||||||
# first_list_board = user_board.listboard_set.order_by('position').first()
|
|
||||||
|
|
||||||
# if first_list_board:
|
|
||||||
# instance.list_board = first_list_board
|
|
||||||
# instance.save()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=ListBoard)
|
|
||||||
def create_placeholder_tasks(sender, instance, created, **kwargs):
|
|
||||||
"""
|
|
||||||
Signal handler to create placeholder tasks for each ListBoard.
|
|
||||||
"""
|
|
||||||
if created:
|
|
||||||
list_board_position = instance.position
|
|
||||||
|
|
||||||
if list_board_position == 1:
|
|
||||||
placeholder_tasks = [
|
|
||||||
{"title": "Normal Task Example"},
|
|
||||||
{"title": "Task with Extra Information Example", "description": "Description for Task 2"},
|
|
||||||
]
|
|
||||||
elif list_board_position == 2:
|
|
||||||
placeholder_tasks = [
|
|
||||||
{"title": "Time Task Example #1", "description": "Description for Task 2",
|
|
||||||
"start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=5)},
|
|
||||||
]
|
|
||||||
elif list_board_position == 3:
|
|
||||||
placeholder_tasks = [
|
|
||||||
{"title": "Time Task Example #2", "description": "Description for Task 2",
|
|
||||||
"start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=30)},
|
|
||||||
]
|
|
||||||
elif list_board_position == 4:
|
|
||||||
placeholder_tasks = [
|
|
||||||
{"title": "Completed Task Example", "description": "Description for Task 2",
|
|
||||||
"start_event": timezone.now(), "completed": True},
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
placeholder_tasks = [
|
|
||||||
{"title": "Default Task Example"},
|
|
||||||
]
|
|
||||||
|
|
||||||
for task_data in placeholder_tasks:
|
|
||||||
Todo.objects.create(
|
|
||||||
list_board=instance,
|
|
||||||
user=instance.board.user,
|
|
||||||
title=task_data["title"],
|
|
||||||
notes=task_data.get("description", ""),
|
|
||||||
is_active=True,
|
|
||||||
start_event=task_data.get("start_event"),
|
|
||||||
end_event=task_data.get("end_event"),
|
|
||||||
completed=task_data.get("completed", False),
|
|
||||||
creation_date=timezone.now(),
|
|
||||||
last_update=timezone.now(),
|
|
||||||
)
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from users.models import CustomUser
|
||||||
from boards.models import ListBoard
|
from boards.models import ListBoard
|
||||||
from tasks.models import Todo, RecurrenceTask, Habit
|
from tasks.models import Todo, RecurrenceTask, Habit
|
||||||
|
|
||||||
@ -8,7 +9,14 @@ class TaskSerializer(serializers.ModelSerializer):
|
|||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Create a new task with validated data
|
user_id = validated_data.get('user')
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = CustomUser.objects.get(id=user_id)
|
||||||
|
except CustomUser.DoesNotExist:
|
||||||
|
raise serializers.ValidationError("User with the provided ID does not exist.")
|
||||||
|
|
||||||
|
validated_data['user'] = user
|
||||||
return Todo.objects.create(**validated_data)
|
return Todo.objects.create(**validated_data)
|
||||||
|
|
||||||
class TaskCreateSerializer(serializers.ModelSerializer):
|
class TaskCreateSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from django.test import TestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from tasks.tests.utils import create_test_user, login_user
|
from tasks.tests.utils import create_test_user
|
||||||
from tasks.serializers import TodoUpdateSerializer
|
from tasks.serializers import TodoUpdateSerializer
|
||||||
from tasks.models import Todo
|
from tasks.models import Todo
|
||||||
|
from boards.models import Board
|
||||||
|
|
||||||
class TaskUpdateSerializerTest(TestCase):
|
class TaskUpdateSerializerTest(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = create_test_user()
|
self.user = create_test_user()
|
||||||
|
self.client.force_authenticate(user=self.user)
|
||||||
self.current_time = '2020-08-01T00:00:00Z'
|
self.current_time = '2020-08-01T00:00:00Z'
|
||||||
self.end_time = '2020-08-01T00:00:00Z'
|
self.end_time = '2020-08-01T00:00:00Z'
|
||||||
|
self.list_board = Board.objects.get(user=self.user).listboard_set.first()
|
||||||
|
|
||||||
def test_serializer_create(self):
|
def test_serializer_create(self):
|
||||||
data = {
|
data = {
|
||||||
@ -23,6 +27,7 @@ class TaskUpdateSerializerTest(TestCase):
|
|||||||
'updated': self.end_time,
|
'updated': self.end_time,
|
||||||
'start_datetime' : self.current_time,
|
'start_datetime' : self.current_time,
|
||||||
'end_datetie': self.end_time,
|
'end_datetie': self.end_time,
|
||||||
|
'list_board': self.list_board.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer = TodoUpdateSerializer(data=data, user=self.user)
|
serializer = TodoUpdateSerializer(data=data, user=self.user)
|
||||||
@ -32,7 +37,7 @@ class TaskUpdateSerializerTest(TestCase):
|
|||||||
self.assertIsInstance(task, Todo)
|
self.assertIsInstance(task, Todo)
|
||||||
|
|
||||||
def test_serializer_update(self):
|
def test_serializer_update(self):
|
||||||
task = Todo.objects.create(title='Original Task', notes='Original description', user=self.user)
|
task = Todo.objects.create(title='Original Task', notes='Original description', user=self.user, list_board=self.list_board)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'id': '32141cwaNcapufh8jq2conw',
|
'id': '32141cwaNcapufh8jq2conw',
|
||||||
@ -42,6 +47,7 @@ class TaskUpdateSerializerTest(TestCase):
|
|||||||
'updated': self.end_time,
|
'updated': self.end_time,
|
||||||
'start_datetime' : self.current_time,
|
'start_datetime' : self.current_time,
|
||||||
'end_datetie': self.end_time,
|
'end_datetie': self.end_time,
|
||||||
|
'list_board': self.list_board.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer = TodoUpdateSerializer(instance=task, data=data)
|
serializer = TodoUpdateSerializer(instance=task, data=data)
|
||||||
|
|||||||
@ -2,72 +2,72 @@ from datetime import datetime, timedelta
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from tasks.tests.utils import create_test_user, login_user
|
from tasks.tests.utils import create_test_user
|
||||||
from tasks.models import Todo
|
from tasks.models import Todo
|
||||||
|
from boards.models import ListBoard, Board
|
||||||
|
|
||||||
|
|
||||||
# class TodoViewSetTests(APITestCase):
|
class TodoViewSetTests(APITestCase):
|
||||||
# def setUp(self):
|
def setUp(self):
|
||||||
# self.user = create_test_user()
|
self.user = create_test_user()
|
||||||
# self.client = login_user(self.user)
|
self.client.force_authenticate(user=self.user)
|
||||||
# self.url = reverse("todo-list")
|
self.url = reverse("todo-list")
|
||||||
# self.due_date = datetime.now() + timedelta(days=5)
|
self.due_date = datetime.now() + timedelta(days=5)
|
||||||
|
self.list_board = Board.objects.get(user=self.user).listboard_set.first()
|
||||||
|
|
||||||
# def test_create_valid_todo(self):
|
def test_create_valid_todo(self):
|
||||||
# """
|
"""
|
||||||
# Test creating a valid task using the API.
|
Test creating a valid task using the API.
|
||||||
# """
|
"""
|
||||||
# data = {
|
data = {
|
||||||
# 'title': 'Test Task',
|
'title': 'Test Task',
|
||||||
# 'type': 'habit',
|
'type': 'habit',
|
||||||
# 'exp': 10,
|
'difficulty': 1,
|
||||||
# 'attribute': 'str',
|
'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'),
|
||||||
# 'priority': 1,
|
'list_board': self.list_board.id,
|
||||||
# 'difficulty': 1,
|
}
|
||||||
# 'user': self.user.id,
|
response = self.client.post(self.url, data, format='json')
|
||||||
# 'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'),
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
# }
|
self.assertEqual(Todo.objects.count(), 1)
|
||||||
# response = self.client.post(self.url, data, format='json')
|
self.assertEqual(Todo.objects.get().title, 'Test Task')
|
||||||
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
# self.assertEqual(Todo.objects.count(), 1)
|
|
||||||
# self.assertEqual(Todo.objects.get().title, 'Test Task')
|
|
||||||
|
|
||||||
# def test_create_invalid_todo(self):
|
def test_create_invalid_todo(self):
|
||||||
# """
|
"""
|
||||||
# Test creating an invalid task using the API.
|
Test creating an invalid task using the API.
|
||||||
# """
|
"""
|
||||||
# data = {
|
data = {
|
||||||
# 'type': 'invalid', # Invalid task type
|
'type': 'invalid', # Invalid task type
|
||||||
# }
|
}
|
||||||
# response = self.client.post(self.url, data, format='json')
|
response = self.client.post(self.url, data, format='json')
|
||||||
# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
# self.assertEqual(Todo.objects.count(), 0) # No task should be created
|
self.assertEqual(Todo.objects.count(), 0) # No task should be created
|
||||||
|
|
||||||
# def test_missing_required_fields(self):
|
def test_missing_required_fields(self):
|
||||||
# """
|
"""
|
||||||
# Test creating a task with missing required fields using the API.
|
Test creating a task with missing required fields using the API.
|
||||||
# """
|
"""
|
||||||
# data = {
|
data = {
|
||||||
# 'title': 'Incomplete Task',
|
'type': 'habit',
|
||||||
# 'type': 'habit',
|
}
|
||||||
# }
|
response = self.client.post(self.url, data, format='json')
|
||||||
# response = self.client.post(self.url, data, format='json')
|
self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(Todo.objects.count(), 0) # No task should be created
|
||||||
# self.assertEqual(Todo.objects.count(), 0) # No task should be created
|
|
||||||
|
|
||||||
# def test_invalid_user_id(self):
|
def test_invalid_user_id(self):
|
||||||
# """
|
"""
|
||||||
# Test creating a task with an invalid user ID using the API.
|
Test creating a task with an invalid user ID using the API (OK because we retreive)
|
||||||
# """
|
id from request.
|
||||||
# data = {
|
"""
|
||||||
# 'title': 'Test Task',
|
data = {
|
||||||
# 'type': 'habit',
|
'title': 'Test Task',
|
||||||
# 'exp': 10,
|
'type': 'habit',
|
||||||
# 'priority': 1,
|
'exp': 10,
|
||||||
# 'difficulty': 1,
|
'priority': 1,
|
||||||
# 'user': 999, # Invalid user ID
|
'difficulty': 1,
|
||||||
# 'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'),
|
'user': -100, # Invalid user ID
|
||||||
# }
|
'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'),
|
||||||
# response = self.client.post(self.url, data, format='json')
|
'list_board': self.list_board.id,
|
||||||
# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
}
|
||||||
# self.assertEqual(Todo.objects.count(), 0) # No task should be created
|
response = self.client.post(self.url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(Todo.objects.count(), 1) # No task should be created
|
||||||
|
|||||||
@ -1,36 +1,39 @@
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from django.test import TestCase
|
from rest_framework.test import APITestCase
|
||||||
from tasks.models import Todo
|
from tasks.models import Todo
|
||||||
from tasks.tests.utils import create_test_user
|
from tasks.tests.utils import create_test_user
|
||||||
|
from boards.models import Board
|
||||||
|
|
||||||
class TodoPriorityTest(TestCase):
|
class TodoPriorityTest(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = create_test_user()
|
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_priority_calculation(self):
|
def test_priority_calculation(self):
|
||||||
# Important = 2, Till Due = none
|
# Important = 2, Till Due = none
|
||||||
todo = Todo(importance=2, end_event=None, user=self.user)
|
todo = Todo(importance=2, end_event=None, user=self.user, list_board=self.list_board)
|
||||||
todo.save()
|
todo.save()
|
||||||
# 'Not Important & Not Urgent'
|
# 'Not Important & Not Urgent'
|
||||||
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
|
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
|
||||||
|
|
||||||
due_date = datetime.now(timezone.utc) + timedelta(days=1)
|
due_date = datetime.now(timezone.utc) + timedelta(days=1)
|
||||||
# Important = 4, Till Due = 1
|
# Important = 4, Till Due = 1
|
||||||
todo = Todo(importance=4, end_event=due_date, user=self.user)
|
todo = Todo(importance=4, end_event=due_date, user=self.user, list_board=self.list_board)
|
||||||
todo.save()
|
todo.save()
|
||||||
# 'Important & Urgent'
|
# 'Important & Urgent'
|
||||||
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.IMPORTANT_URGENT)
|
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.IMPORTANT_URGENT)
|
||||||
|
|
||||||
due_date = datetime.now(timezone.utc) + timedelta(days=10)
|
due_date = datetime.now(timezone.utc) + timedelta(days=10)
|
||||||
# Important = 3, Till Due = 10
|
# Important = 3, Till Due = 10
|
||||||
todo = Todo(importance=3, end_event=due_date, user=self.user)
|
todo = Todo(importance=3, end_event=due_date, user=self.user, list_board=self.list_board)
|
||||||
todo.save()
|
todo.save()
|
||||||
# 'Important & Not Urgent'
|
# 'Important & Not Urgent'
|
||||||
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.IMPORTANT_NOT_URGENT)
|
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.IMPORTANT_NOT_URGENT)
|
||||||
|
|
||||||
due_date = datetime.now(timezone.utc) + timedelta(days=2)
|
due_date = datetime.now(timezone.utc) + timedelta(days=2)
|
||||||
# Important = 1, Till Due = 2
|
# Important = 1, Till Due = 2
|
||||||
todo = Todo(importance=1, end_event=due_date, user=self.user)
|
todo = Todo(importance=1, end_event=due_date, user=self.user, list_board=self.list_board)
|
||||||
todo.save()
|
todo.save()
|
||||||
# 'Not Important & Urgent'
|
# 'Not Important & Urgent'
|
||||||
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT)
|
self.assertEqual(todo.priority, Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT)
|
||||||
|
|||||||
@ -1,26 +1,24 @@
|
|||||||
|
from rest_framework import status
|
||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from users.models import CustomUser
|
from users.models import CustomUser
|
||||||
from ..models import Todo
|
from ..models import Todo
|
||||||
|
|
||||||
|
|
||||||
def create_test_user(email="testusertestuser@example.com", username="testusertestuser",
|
def create_test_user(email="testusertestuser@example.com",
|
||||||
first_name="Test", password="testpassword",):
|
username="testusertestuser",
|
||||||
"""create predifined user for testing"""
|
password="testpassword",) -> CustomUser:
|
||||||
return CustomUser.objects.create_user(
|
"""create predifined user without placeholder task for testing"""
|
||||||
email=email,
|
|
||||||
username=username,
|
|
||||||
first_name=first_name,
|
|
||||||
password=password,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def login_user(user):
|
|
||||||
"""Login a user to API client."""
|
|
||||||
|
|
||||||
client = APIClient()
|
client = APIClient()
|
||||||
client.force_authenticate(user=user)
|
response = client.post(reverse('create_user'), {'email': email,
|
||||||
return client
|
'username': username,
|
||||||
|
'password': password})
|
||||||
|
if response.status_code == status.HTTP_201_CREATED:
|
||||||
|
user = CustomUser.objects.get(username='testusertestuser')
|
||||||
|
user.todo_set.all().delete()
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def create_task_json(user, **kwargs):
|
def create_task_json(user, **kwargs):
|
||||||
@ -29,10 +27,7 @@ def create_task_json(user, **kwargs):
|
|||||||
"title": "Test Task",
|
"title": "Test Task",
|
||||||
"type": "habit",
|
"type": "habit",
|
||||||
"notes": "This is a test task created via the API.",
|
"notes": "This is a test task created via the API.",
|
||||||
"exp": 10,
|
|
||||||
"priority": 1.5,
|
|
||||||
"difficulty": 1,
|
"difficulty": 1,
|
||||||
"attribute": "str",
|
|
||||||
"challenge": False,
|
"challenge": False,
|
||||||
"fromSystem": False,
|
"fromSystem": False,
|
||||||
"creation_date": None,
|
"creation_date": None,
|
||||||
@ -51,8 +46,6 @@ def create_test_task(user, **kwargs):
|
|||||||
'title': "Test Task",
|
'title': "Test Task",
|
||||||
'task_type': 'habit',
|
'task_type': 'habit',
|
||||||
'notes': "This is a test task created via the API.",
|
'notes': "This is a test task created via the API.",
|
||||||
'exp': 10,
|
|
||||||
'priority': 1.5,
|
|
||||||
'difficulty': 1,
|
'difficulty': 1,
|
||||||
'attribute': 'str',
|
'attribute': 'str',
|
||||||
'challenge': False,
|
'challenge': False,
|
||||||
|
|||||||
@ -1,9 +1,74 @@
|
|||||||
|
from django.utils import timezone
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from tasks.models import Todo
|
||||||
from users.models import CustomUser, UserStats
|
from users.models import CustomUser, UserStats
|
||||||
|
from boards.models import ListBoard, Board
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=CustomUser)
|
@receiver(post_save, sender=CustomUser)
|
||||||
def create_user_stats(sender, instance, created, **kwargs):
|
def create_user_stats(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
UserStats.objects.create(user=instance)
|
UserStats.objects.create(user=instance)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=CustomUser)
|
||||||
|
def create_default_board(sender, instance, created, **kwargs):
|
||||||
|
"""Signal handler to automatically create a default Board for a user upon creation."""
|
||||||
|
if created:
|
||||||
|
# Create unique board by user id
|
||||||
|
user_id = instance.id
|
||||||
|
board = Board.objects.create(user=instance, name=f"Board of #{user_id}")
|
||||||
|
ListBoard.objects.create(board=board, name="Backlog", position=1)
|
||||||
|
ListBoard.objects.create(board=board, name="Doing", position=2)
|
||||||
|
ListBoard.objects.create(board=board, name="Review", position=3)
|
||||||
|
ListBoard.objects.create(board=board, name="Done", position=4)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=ListBoard)
|
||||||
|
def create_placeholder_tasks(sender, instance, created, **kwargs):
|
||||||
|
"""
|
||||||
|
Signal handler to create placeholder tasks for each ListBoard.
|
||||||
|
"""
|
||||||
|
if created:
|
||||||
|
list_board_position = instance.position
|
||||||
|
|
||||||
|
if list_board_position == 1:
|
||||||
|
placeholder_tasks = [
|
||||||
|
{"title": "Normal Task Example"},
|
||||||
|
{"title": "Task with Extra Information Example", "description": "Description for Task 2"},
|
||||||
|
]
|
||||||
|
elif list_board_position == 2:
|
||||||
|
placeholder_tasks = [
|
||||||
|
{"title": "Time Task Example #1", "description": "Description for Task 2",
|
||||||
|
"start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=5)},
|
||||||
|
]
|
||||||
|
elif list_board_position == 3:
|
||||||
|
placeholder_tasks = [
|
||||||
|
{"title": "Time Task Example #2", "description": "Description for Task 2",
|
||||||
|
"start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=30)},
|
||||||
|
]
|
||||||
|
elif list_board_position == 4:
|
||||||
|
placeholder_tasks = [
|
||||||
|
{"title": "Completed Task Example", "description": "Description for Task 2",
|
||||||
|
"start_event": timezone.now(), "completed": True},
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
placeholder_tasks = [
|
||||||
|
{"title": "Default Task Example"},
|
||||||
|
]
|
||||||
|
|
||||||
|
for task_data in placeholder_tasks:
|
||||||
|
Todo.objects.create(
|
||||||
|
list_board=instance,
|
||||||
|
user=instance.board.user,
|
||||||
|
title=task_data["title"],
|
||||||
|
notes=task_data.get("description", ""),
|
||||||
|
is_active=True,
|
||||||
|
start_event=task_data.get("start_event"),
|
||||||
|
end_event=task_data.get("end_event"),
|
||||||
|
completed=task_data.get("completed", False),
|
||||||
|
creation_date=timezone.now(),
|
||||||
|
last_update=timezone.now(),
|
||||||
|
)
|
||||||
@ -1,3 +1,39 @@
|
|||||||
from django.test import TestCase
|
from rest_framework.test import APITestCase
|
||||||
|
from rest_framework import status
|
||||||
|
from django.urls import reverse
|
||||||
|
from users.models import CustomUser, UserStats
|
||||||
|
from boards.models import Board, ListBoard
|
||||||
|
from tasks.models import Todo
|
||||||
|
|
||||||
# Create your tests here.
|
class SignalsTest(APITestCase):
|
||||||
|
def setUp(self):
|
||||||
|
response = self.client.post(reverse('create_user'), {'email': 'testusertestuser123@mail.com',
|
||||||
|
'username': 'testusertestuser123',
|
||||||
|
'password': '321testpassword123'})
|
||||||
|
# force login If response is 201 OK
|
||||||
|
if response.status_code == status.HTTP_201_CREATED:
|
||||||
|
self.user = CustomUser.objects.get(username='testusertestuser123')
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def test_create_user_with_stas_default_boards_and_lists(self):
|
||||||
|
# Stats check
|
||||||
|
self.assertTrue(UserStats.objects.filter(user=self.user).exists())
|
||||||
|
|
||||||
|
# check if user is created
|
||||||
|
self.assertEqual(CustomUser.objects.count(), 1)
|
||||||
|
user = CustomUser.objects.get(username='testusertestuser123')
|
||||||
|
|
||||||
|
# Check for default board
|
||||||
|
self.assertEqual(Board.objects.filter(user=self.user).count(), 1)
|
||||||
|
|
||||||
|
# Check for default lists in board
|
||||||
|
default_board = Board.objects.get(user=self.user)
|
||||||
|
self.assertEqual(ListBoard.objects.filter(board=default_board).count(), 4)
|
||||||
|
|
||||||
|
def test_create_user_with_placeholder_tasks(self):
|
||||||
|
default_board = Board.objects.get(user=self.user)
|
||||||
|
|
||||||
|
# Check if placeholder tasks are created for each ListBoard
|
||||||
|
for list_board in ListBoard.objects.filter(board=default_board):
|
||||||
|
placeholder_tasks_count = Todo.objects.filter(list_board=list_board).count()
|
||||||
|
self.assertTrue(placeholder_tasks_count > 0)
|
||||||
@ -1,4 +1,11 @@
|
|||||||
import { BadgeDelta, Card, Flex, Metric, ProgressBar, Text } from "@tremor/react";
|
import {
|
||||||
|
BadgeDelta,
|
||||||
|
Card,
|
||||||
|
Flex,
|
||||||
|
Metric,
|
||||||
|
ProgressBar,
|
||||||
|
Text,
|
||||||
|
} from "@tremor/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { axiosInstance } from "src/api/AxiosConfig";
|
import { axiosInstance } from "src/api/AxiosConfig";
|
||||||
|
|
||||||
@ -13,7 +20,7 @@ export function KpiCard() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchKpiCardData = async () => {
|
const fetchKpiCardData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get("/dashboard/stats/");
|
const response = await axiosInstance.get("/dashboard/todostats/");
|
||||||
const completedThisWeek = response.data.completed_this_week || 0;
|
const completedThisWeek = response.data.completed_this_week || 0;
|
||||||
const completedLastWeek = response.data.completed_last_week || 0;
|
const completedLastWeek = response.data.completed_last_week || 0;
|
||||||
const percentage = (completedThisWeek / completedLastWeek) * 100;
|
const percentage = (completedThisWeek / completedLastWeek) * 100;
|
||||||
@ -46,10 +53,16 @@ export function KpiCard() {
|
|||||||
<div>
|
<div>
|
||||||
<Metric>{kpiCardData.completedThisWeek}</Metric>
|
<Metric>{kpiCardData.completedThisWeek}</Metric>
|
||||||
</div>
|
</div>
|
||||||
<BadgeDelta deltaType={kpiCardData.incOrdec}>{kpiCardData.percentage.toFixed(0)}%</BadgeDelta>
|
<BadgeDelta deltaType={kpiCardData.incOrdec}>
|
||||||
|
{isNaN(kpiCardData.percentage) || !isFinite(kpiCardData.percentage)
|
||||||
|
? "0%"
|
||||||
|
: `${kpiCardData.percentage.toFixed(0)}%`}
|
||||||
|
</BadgeDelta>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex className="mt-4">
|
<Flex className="mt-4">
|
||||||
<Text className="truncate">vs. {kpiCardData.completedLastWeek} (last week)</Text>
|
<Text className="truncate">
|
||||||
|
vs. {kpiCardData.completedLastWeek} (last week)
|
||||||
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<ProgressBar value={kpiCardData.percentage} className="mt-2" />
|
<ProgressBar value={kpiCardData.percentage} className="mt-2" />
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -8,13 +8,13 @@ export function DonutChartGraph() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchDonutData = async () => {
|
const fetchDonutData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get("/dashboard/stats/");
|
const response = await axiosInstance.get("/dashboard/todostats/");
|
||||||
const todoCount = response.data.todo_count || 0;
|
const totalTask = response.data.total_tasks || 0;
|
||||||
const recurrenceCount = response.data.recurrence_count || 0;
|
const completedTask = response.data.total_completed_tasks || 0;
|
||||||
|
|
||||||
const donutData = [
|
const donutData = [
|
||||||
{ name: "Todo", count: todoCount },
|
{ name: "Completed task", count: completedTask},
|
||||||
{ name: "Recurrence", count: recurrenceCount },
|
{ name: "Total task", count: totalTask },
|
||||||
];
|
];
|
||||||
|
|
||||||
setDonutData(donutData);
|
setDonutData(donutData);
|
||||||
@ -31,9 +31,10 @@ export function DonutChartGraph() {
|
|||||||
data={donutData}
|
data={donutData}
|
||||||
category="count"
|
category="count"
|
||||||
index="name"
|
index="name"
|
||||||
colors={["rose", "yellow", "orange"]}
|
colors={["rose", "yellow"]}
|
||||||
showAnimation
|
showAnimation
|
||||||
radius={25}
|
radius={25}
|
||||||
|
variant="pie"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -8,7 +8,7 @@ export function ProgressCircleChart() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProgressData = async () => {
|
const fetchProgressData = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get("/dashboard/stats/");
|
const response = await axiosInstance.get("/dashboard/todostats/");
|
||||||
let completedLastWeek = response.data.completed_last_week || 0;
|
let completedLastWeek = response.data.completed_last_week || 0;
|
||||||
let assignLastWeek = response.data.tasks_assigned_last_week || 0;
|
let assignLastWeek = response.data.tasks_assigned_last_week || 0;
|
||||||
|
|
||||||
@ -33,9 +33,18 @@ export function ProgressCircleChart() {
|
|||||||
return (
|
return (
|
||||||
<Card className="max-w-lg mx-auto">
|
<Card className="max-w-lg mx-auto">
|
||||||
<Flex className="flex-col items-center">
|
<Flex className="flex-col items-center">
|
||||||
<ProgressCircle className="mt-6" value={progressData} size={200} strokeWidth={10} radius={60} color="indigo">
|
<ProgressCircle
|
||||||
|
className="mt-6"
|
||||||
|
value={progressData}
|
||||||
|
size={200}
|
||||||
|
strokeWidth={10}
|
||||||
|
radius={60}
|
||||||
|
color="indigo"
|
||||||
|
>
|
||||||
<span className="h-12 w-12 rounded-full bg-indigo-100 flex items-center justify-center text-sm text-indigo-500 font-medium">
|
<span className="h-12 w-12 rounded-full bg-indigo-100 flex items-center justify-center text-sm text-indigo-500 font-medium">
|
||||||
{progressData.toFixed(0)} %
|
{isNaN(progressData) || !isFinite(progressData)
|
||||||
|
? "0%"
|
||||||
|
: `${progressData.toFixed(0)}%`}
|
||||||
</span>
|
</span>
|
||||||
</ProgressCircle>
|
</ProgressCircle>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -1,22 +1,66 @@
|
|||||||
import { Card, Grid, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title, Legend } from "@tremor/react";
|
import {
|
||||||
|
Card,
|
||||||
|
Grid,
|
||||||
|
Tab,
|
||||||
|
TabGroup,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
TabPanels,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
Legend,
|
||||||
|
Metric,
|
||||||
|
ProgressCircle,
|
||||||
|
Flex,
|
||||||
|
} from "@tremor/react";
|
||||||
import { KpiCard } from "./KpiCard";
|
import { KpiCard } from "./KpiCard";
|
||||||
import { BarChartGraph } from "./Barchart";
|
import { BarChartGraph } from "./Barchart";
|
||||||
import { DonutChartGraph } from "./DonutChart";
|
|
||||||
import { AreaChartGraph } from "./Areachart";
|
import { AreaChartGraph } from "./Areachart";
|
||||||
|
import { DonutChartGraph } from "./PieChart";
|
||||||
import { ProgressCircleChart } from "./ProgressCircle";
|
import { ProgressCircleChart } from "./ProgressCircle";
|
||||||
import { useState } from "react";
|
import { axiosInstance } from "src/api/AxiosConfig";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const valueFormatter = (number) =>
|
||||||
|
`$ ${new Intl.NumberFormat("us").format(number).toString()}`;
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const [value, setValue] = useState({
|
const [totalTask, setTotalTask] = useState(0);
|
||||||
from: new Date(2021, 0, 1),
|
const [totalCompletedTasks, settotalCompletedTasks] = useState(0);
|
||||||
to: new Date(2023, 0, 7),
|
const [totalCompletedTasksToday, setTotalCompletedTasksToday] = useState(0);
|
||||||
});
|
const [progressData, setProgressData] = useState(0);
|
||||||
|
const [overdueTask, setOverdueTask] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const response = await axiosInstance.get("/dashboard/todostats/");
|
||||||
|
const totalTaskValue = response.data.total_tasks || 0;
|
||||||
|
const totalCompletedTasksValue = response.data.total_completed_tasks || 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;
|
||||||
|
const overdueTasks = response.data.overdue_tasks || 0;
|
||||||
|
|
||||||
|
const progress =
|
||||||
|
(totalCompletedTasksToday / totalCompletedTasksToday) * 100;
|
||||||
|
|
||||||
|
setTotalTask(totalTaskValue);
|
||||||
|
settotalCompletedTasks(totalCompletedTasksValue);
|
||||||
|
setTotalCompletedTasksToday(totalCompletedTasksTodayValue);
|
||||||
|
setTotalTaskToday(totalTaskToday);
|
||||||
|
setProgressData(progress);
|
||||||
|
setOverdueTask(overdueTasks);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col p-12">
|
<div className="flex flex-col p-12">
|
||||||
<div>
|
<div>
|
||||||
<Title>Dashboard</Title>
|
<Title>Dashboard</Title>
|
||||||
<Text>All of your progress will be shown right here.</Text>
|
<Text>All of your progress will be shown right here.</Text>
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -39,8 +83,9 @@ export function Dashboard() {
|
|||||||
<ProgressCircleChart />
|
<ProgressCircleChart />
|
||||||
<Legend
|
<Legend
|
||||||
className="mt-3 mx-auto w-1/2"
|
className="mt-3 mx-auto w-1/2"
|
||||||
categories={["Completed Tasks", "Assigned Tasks"]}
|
categories={["Completed Tasks"]}
|
||||||
colors={["indigo"]}></Legend>
|
colors={["indigo"]}
|
||||||
|
></Legend>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<BarChartGraph />
|
<BarChartGraph />
|
||||||
@ -50,19 +95,79 @@ export function Dashboard() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
{/*Overview Tab*/}
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<div className="h-31">
|
<Grid numItemsMd={2} numItemsLg={3} className="gap-6 mt-6">
|
||||||
|
<Card>
|
||||||
|
<Title className="mx-auto">Overview</Title>
|
||||||
|
<Card
|
||||||
|
className="max-w-xs mx-auto"
|
||||||
|
decoration="top"
|
||||||
|
decorationColor="yellow"
|
||||||
|
>
|
||||||
|
<Text>Total tasks</Text>
|
||||||
|
<Metric>{totalTask}</Metric>
|
||||||
|
</Card>
|
||||||
|
<br></br>
|
||||||
|
<Card
|
||||||
|
className="max-w-xs mx-auto"
|
||||||
|
decoration="top"
|
||||||
|
decorationColor="rose"
|
||||||
|
>
|
||||||
|
<Text>Total completed tasks</Text>
|
||||||
|
<Metric>{totalCompletedTasks}</Metric>
|
||||||
|
</Card>
|
||||||
|
<br></br>
|
||||||
|
<Card
|
||||||
|
className="max-w-xs mx-auto"
|
||||||
|
decoration="top"
|
||||||
|
decorationColor="pink"
|
||||||
|
>
|
||||||
|
<Text>Overdue tasks</Text>
|
||||||
|
<Metric>{overdueTask}</Metric>
|
||||||
|
</Card>
|
||||||
|
<br></br>
|
||||||
|
</Card>
|
||||||
|
{/*Pie chart graph*/}
|
||||||
<Card className="mx-auto h-full">
|
<Card className="mx-auto h-full">
|
||||||
<Title>Tasks</Title>
|
<Title>Overall completion rate</Title>
|
||||||
<DonutChartGraph />
|
<DonutChartGraph />
|
||||||
<br />
|
<br />
|
||||||
<Legend
|
<Legend
|
||||||
className="mt-3 mx-auto w-1/2"
|
className="mt-3 mx-auto w-1/2"
|
||||||
categories={["Todo Task", "Recurrence Task"]}
|
categories={["Completed Task", "Total Task"]}
|
||||||
colors={["rose", "yellow"]}
|
colors={["rose", "yellow"]}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
{/*Progress circle graph*/}
|
||||||
|
|
||||||
|
<Card className="max-w-lg mx-auto">
|
||||||
|
<Title>Today's progress</Title>
|
||||||
|
<br />
|
||||||
|
<Flex className="flex-col items-center">
|
||||||
|
<ProgressCircle
|
||||||
|
className="mt-6"
|
||||||
|
value={progressData}
|
||||||
|
size={200}
|
||||||
|
strokeWidth={10}
|
||||||
|
radius={60}
|
||||||
|
color="rose"
|
||||||
|
>
|
||||||
|
<span className="h-12 w-12 rounded-full bg-rose-100 flex items-center justify-center text-sm text-rose-500 font-medium">
|
||||||
|
{isNaN(progressData) || !isFinite(progressData)
|
||||||
|
? "0%"
|
||||||
|
: `${progressData.toFixed(0)}%`}
|
||||||
|
</span>
|
||||||
|
</ProgressCircle>
|
||||||
|
<br></br>
|
||||||
|
<Legend
|
||||||
|
className="mt-3 mx-auto w-1/2"
|
||||||
|
categories={["Completed Tasks"]}
|
||||||
|
colors={["rose"]}
|
||||||
|
></Legend>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</TabGroup>
|
</TabGroup>
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
import { useMemo, useState, useEffect } from "react";
|
import { useMemo, useState, useEffect } from "react";
|
||||||
import { ColumnContainerCard } from "./columnContainerWrapper";
|
import { ColumnContainerCard } from "./columnContainerWrapper";
|
||||||
import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
import {
|
||||||
|
DndContext,
|
||||||
|
DragOverlay,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
} from "@dnd-kit/core";
|
||||||
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
|
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { TaskCard } from "./taskCard";
|
import { TaskCard } from "./taskCard";
|
||||||
@ -26,7 +32,9 @@ export function KanbanBoard() {
|
|||||||
|
|
||||||
// ---------------- Task Handlers ----------------
|
// ---------------- Task Handlers ----------------
|
||||||
const handleTaskUpdate = (tasks, updatedTask) => {
|
const handleTaskUpdate = (tasks, updatedTask) => {
|
||||||
const updatedTasks = tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task));
|
const updatedTasks = tasks.map((task) =>
|
||||||
|
task.id === updatedTask.id ? updatedTask : task
|
||||||
|
);
|
||||||
setTasks(updatedTasks);
|
setTasks(updatedTasks);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,8 +176,14 @@ export function KanbanBoard() {
|
|||||||
justify-center
|
justify-center
|
||||||
overflow-x-auto
|
overflow-x-auto
|
||||||
overflow-y-hidden
|
overflow-y-hidden
|
||||||
">
|
"
|
||||||
<DndContext sensors={sensors} onDragStart={onDragStart} onDragEnd={onDragEnd} onDragOver={onDragOver}>
|
>
|
||||||
|
<DndContext
|
||||||
|
sensors={sensors}
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
|
onDragOver={onDragOver}
|
||||||
|
>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
{!isLoading ? (
|
{!isLoading ? (
|
||||||
@ -181,7 +195,9 @@ export function KanbanBoard() {
|
|||||||
createTask={createTask}
|
createTask={createTask}
|
||||||
deleteTask={deleteTask}
|
deleteTask={deleteTask}
|
||||||
updateTask={updateTask}
|
updateTask={updateTask}
|
||||||
tasks={(tasks || []).filter((task) => task.columnId === col.id)}
|
tasks={(tasks || []).filter(
|
||||||
|
(task) => task.columnId === col.id
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
))}{" "}
|
))}{" "}
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
@ -194,7 +210,11 @@ export function KanbanBoard() {
|
|||||||
{createPortal(
|
{createPortal(
|
||||||
<DragOverlay className="bg-white" dropAnimation={null} zIndex={20}>
|
<DragOverlay className="bg-white" dropAnimation={null} zIndex={20}>
|
||||||
{/* Render the active task as a draggable overlay */}
|
{/* Render the active task as a draggable overlay */}
|
||||||
<TaskCard task={activeTask} deleteTask={deleteTask} updateTask={updateTask} />
|
<TaskCard
|
||||||
|
task={activeTask}
|
||||||
|
deleteTask={deleteTask}
|
||||||
|
updateTask={updateTask}
|
||||||
|
/>
|
||||||
</DragOverlay>,
|
</DragOverlay>,
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
@ -302,7 +322,11 @@ export function KanbanBoard() {
|
|||||||
|
|
||||||
const isOverAColumn = over.data.current?.type === "Column";
|
const isOverAColumn = over.data.current?.type === "Column";
|
||||||
// Move the Task to a different column and update columnId
|
// 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) => {
|
setTasks((tasks) => {
|
||||||
const activeIndex = tasks.findIndex((t) => t.id === activeId);
|
const activeIndex = tasks.findIndex((t) => t.id === activeId);
|
||||||
axiosInstance
|
axiosInstance
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { BsFillTrashFill } from "react-icons/bs";
|
import { BsFillTrashFill } from "react-icons/bs";
|
||||||
import { useSortable } from "@dnd-kit/sortable";
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
import { CSS } from "@dnd-kit/utilities";
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
@ -6,6 +7,9 @@ import { TaskDetailModal } from "./taskDetailModal";
|
|||||||
|
|
||||||
export function TaskCard({ task, deleteTask, updateTask }) {
|
export function TaskCard({ task, deleteTask, updateTask }) {
|
||||||
const [mouseIsOver, setMouseIsOver] = useState(false);
|
const [mouseIsOver, setMouseIsOver] = useState(false);
|
||||||
|
// console.log(task.challenge);
|
||||||
|
// console.log(task.importance);
|
||||||
|
// console.log(task.difficulty);
|
||||||
|
|
||||||
const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
|
const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
|
||||||
id: task.id,
|
id: task.id,
|
||||||
@ -14,12 +18,13 @@ export function TaskCard({ task, deleteTask, updateTask }) {
|
|||||||
task,
|
task,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
transition,
|
transition,
|
||||||
transform: CSS.Transform.toString(transform),
|
transform: CSS.Transform.toString(transform),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* If card is dragged */
|
/* If card is dragged */
|
||||||
}
|
}
|
||||||
@ -60,7 +65,7 @@ export function TaskCard({ task, deleteTask, updateTask }) {
|
|||||||
setMouseIsOver(false);
|
setMouseIsOver(false);
|
||||||
}}>
|
}}>
|
||||||
<p
|
<p
|
||||||
className="p-2.5 my-auto w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-xl shadow bg-white"
|
className={`p-2.5 my-auto w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-xl shadow bg-white`}
|
||||||
onClick={() => document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
|
onClick={() => document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
|
||||||
{task.content}
|
{task.content}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -7,6 +7,9 @@ export function TaskDetailModal({ title, description, tags, difficulty, challeng
|
|||||||
const [isChallengeChecked, setChallengeChecked] = useState(challenge);
|
const [isChallengeChecked, setChallengeChecked] = useState(challenge);
|
||||||
const [isImportantChecked, setImportantChecked] = useState(importance);
|
const [isImportantChecked, setImportantChecked] = useState(importance);
|
||||||
const [currentDifficulty, setCurrentDifficulty] = useState(difficulty);
|
const [currentDifficulty, setCurrentDifficulty] = useState(difficulty);
|
||||||
|
// console.log(currentDifficulty);
|
||||||
|
// console.log(isChallengeChecked);
|
||||||
|
// console.log(isImportantChecked);
|
||||||
|
|
||||||
const handleChallengeChange = () => {
|
const handleChallengeChange = () => {
|
||||||
setChallengeChecked(!isChallengeChecked);
|
setChallengeChecked(!isChallengeChecked);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user