diff --git a/backend/core/settings.py b/backend/core/settings.py
index 5c01d05..4936805 100644
--- a/backend/core/settings.py
+++ b/backend/core/settings.py
@@ -52,6 +52,7 @@ INSTALLED_APPS = [
'tasks',
'users',
'authentications',
+ 'dashboard',
'corsheaders',
'drf_spectacular',
diff --git a/backend/core/urls.py b/backend/core/urls.py
index a02869c..06a4fd2 100644
--- a/backend/core/urls.py
+++ b/backend/core/urls.py
@@ -27,4 +27,5 @@ urlpatterns = [
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
+ path('api/', include('dashboard.urls')),
]
\ No newline at end of file
diff --git a/frontend/src/components/navigators/Navbar.jsx b/backend/dashboard/__init__.py
similarity index 100%
rename from frontend/src/components/navigators/Navbar.jsx
rename to backend/dashboard/__init__.py
diff --git a/backend/dashboard/admin.py b/backend/dashboard/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/backend/dashboard/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/backend/dashboard/apps.py b/backend/dashboard/apps.py
new file mode 100644
index 0000000..7b1cc05
--- /dev/null
+++ b/backend/dashboard/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DashboardConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'dashboard'
diff --git a/backend/dashboard/migrations/__init__.py b/backend/dashboard/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/dashboard/serializers.py b/backend/dashboard/serializers.py
new file mode 100644
index 0000000..ddc207b
--- /dev/null
+++ b/backend/dashboard/serializers.py
@@ -0,0 +1,7 @@
+from rest_framework import serializers
+from .models import UserStats
+
+class UserStatsSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = UserStats
+ fields = ['health', 'gold', 'experience', 'strength', 'intelligence', 'endurance', 'perception', 'luck', 'level']
\ No newline at end of file
diff --git a/backend/dashboard/tests.py b/backend/dashboard/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/backend/dashboard/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/backend/dashboard/urls.py b/backend/dashboard/urls.py
new file mode 100644
index 0000000..beb8f1b
--- /dev/null
+++ b/backend/dashboard/urls.py
@@ -0,0 +1,6 @@
+from django.urls import path
+from .views import DashboardStatsAPIView
+
+urlpatterns = [
+ path('dashboard/stats/', DashboardStatsAPIView.as_view(), name='dashboard-stats'),
+]
diff --git a/backend/dashboard/views.py b/backend/dashboard/views.py
new file mode 100644
index 0000000..4475d77
--- /dev/null
+++ b/backend/dashboard/views.py
@@ -0,0 +1,58 @@
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.permissions import IsAuthenticated
+from django.db.models import Count
+from django.utils import timezone
+
+from tasks.models import Todo, RecurrenceTask
+
+class DashboardStatsAPIView(APIView):
+ permission_classes = [IsAuthenticated]
+
+ def get(self, request):
+ user = request.user
+
+ # Calculate task usage statistics
+ todo_count = Todo.objects.filter(user=user).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
+
+ 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,
+ }
+
+ return Response(data, status=status.HTTP_200_OK)
+
+ def post(self, request):
+ # Handle incoming data from the POST request
+ # Update the necessary information based on the data
+
+ task_id = request.data.get('task_id')
+ is_completed = request.data.get('is_completed')
+
+ try:
+ task = Todo.objects.get(id=task_id, user=request.user)
+ task.completed = is_completed
+ task.save()
+ return Response({'message': 'Task completion status updated successfully'}, status=status.HTTP_200_OK)
+ except Todo.DoesNotExist:
+ return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND)
\ No newline at end of file
diff --git a/backend/tasks/migrations/0014_recurrencetask_completed_todo_completed.py b/backend/tasks/migrations/0014_recurrencetask_completed_todo_completed.py
new file mode 100644
index 0000000..d89360d
--- /dev/null
+++ b/backend/tasks/migrations/0014_recurrencetask_completed_todo_completed.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.6 on 2023-11-17 16:40
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0013_alter_recurrencetask_recurrence_rule'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='recurrencetask',
+ name='completed',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='todo',
+ name='completed',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/backend/tasks/models.py b/backend/tasks/models.py
index a8fc4e5..9a914c0 100644
--- a/backend/tasks/models.py
+++ b/backend/tasks/models.py
@@ -18,7 +18,6 @@ class Task(models.Model):
:param title: Title of the task.
:param notes: Optional additional notes for the task.
:param tags: Associated tags for the task.
- :param completed: A boolean field indicating whether the task is completed.
:param importance: The importance of the task (range: 1 to 5)
:param difficulty: The difficulty of the task (range: 1 to 5).
:param challenge: Associated challenge (optional).
@@ -62,12 +61,14 @@ class Todo(Task):
NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent'
NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent'
+ completed = models.BooleanField(default=False)
priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
def __str__(self):
return self.title
class RecurrenceTask(Task):
+ completed = models.BooleanField(default=False)
recurrence_rule = models.CharField()
def __str__(self) -> str:
diff --git a/frontend/package.json b/frontend/package.json
index fed56d1..7b2a7ba 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -16,6 +16,9 @@
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
+ "@fortawesome/fontawesome-svg-core": "^6.4.2",
+ "@fortawesome/free-brands-svg-icons": "^6.4.2",
+ "@fortawesome/react-fontawesome": "^0.2.0",
"@fullcalendar/core": "^6.1.9",
"@fullcalendar/daygrid": "^6.1.9",
"@fullcalendar/interaction": "^6.1.9",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index afbf464..8898286 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -23,6 +23,15 @@ dependencies:
'@emotion/styled':
specifier: ^11.11.0
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.37)(react@18.2.0)
+ '@fortawesome/fontawesome-svg-core':
+ specifier: ^6.4.2
+ version: 6.4.2
+ '@fortawesome/free-brands-svg-icons':
+ specifier: ^6.4.2
+ version: 6.4.2
+ '@fortawesome/react-fontawesome':
+ specifier: ^0.2.0
+ version: 0.2.0(@fortawesome/fontawesome-svg-core@6.4.2)(react@18.2.0)
'@fullcalendar/core':
specifier: ^6.1.9
version: 6.1.9
@@ -855,6 +864,39 @@ packages:
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
dev: false
+ /@fortawesome/fontawesome-common-types@6.4.2:
+ resolution: {integrity: sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dev: false
+
+ /@fortawesome/fontawesome-svg-core@6.4.2:
+ resolution: {integrity: sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.4.2
+ dev: false
+
+ /@fortawesome/free-brands-svg-icons@6.4.2:
+ resolution: {integrity: sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.4.2
+ dev: false
+
+ /@fortawesome/react-fontawesome@0.2.0(@fortawesome/fontawesome-svg-core@6.4.2)(react@18.2.0):
+ resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==}
+ peerDependencies:
+ '@fortawesome/fontawesome-svg-core': ~1 || ~6
+ react: '>=16.3'
+ dependencies:
+ '@fortawesome/fontawesome-svg-core': 6.4.2
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
/@fullcalendar/core@6.1.9:
resolution: {integrity: sha512-eeG+z9BWerdsU9Ac6j16rpYpPnE0wxtnEHiHrh/u/ADbGTR3hCOjCD9PxQOfhOTHbWOVs7JQunGcksSPu5WZBQ==}
dependencies:
diff --git a/frontend/src/components/authentication/LoginPage.jsx b/frontend/src/components/authentication/LoginPage.jsx
index 12ad18e..a48c075 100644
--- a/frontend/src/components/authentication/LoginPage.jsx
+++ b/frontend/src/components/authentication/LoginPage.jsx
@@ -7,6 +7,8 @@ import { loadFull } from "tsparticles";
import refreshAccessToken from "./refreshAcesstoken";
import axiosapi from "../../api/AuthenticationApi";
import { useAuth } from "../../hooks/authentication/IsAuthenticated";
+import { FcGoogle } from "react-icons/fc";
+
function LoginPage() {
const Navigate = useNavigate();
@@ -208,7 +210,7 @@ function LoginPage() {
className="btn btn-outline btn-secondary w-full "
onClick={() => googleLoginImplicit()}
>
- Login with Google
+
+ Start spending more time on your own table. +
+