mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-19 05:54:07 +01:00
Merge pull request #27 from TurTaskProject/feature/calendar
Add Calendar and Link with google api
This commit is contained in:
commit
dcfc050408
@ -31,6 +31,10 @@ class GoogleCalendarEventViewset(viewsets.ViewSet):
|
|||||||
for event in events.get('items', []):
|
for event in events.get('items', []):
|
||||||
if event.get('recurringEventId'):
|
if event.get('recurringEventId'):
|
||||||
continue
|
continue
|
||||||
|
event['start_datetime'] = event.get('start').get('dateTime')
|
||||||
|
event['end_datetime'] = event.get('end').get('dateTime')
|
||||||
|
event.pop('start')
|
||||||
|
event.pop('end')
|
||||||
try:
|
try:
|
||||||
task = Todo.objects.get(google_calendar_id=event['id'])
|
task = Todo.objects.get(google_calendar_id=event['id'])
|
||||||
serializer = TodoUpdateSerializer(instance=task, data=event)
|
serializer = TodoUpdateSerializer(instance=task, data=event)
|
||||||
|
|||||||
@ -1,37 +1,16 @@
|
|||||||
from rest_framework import status
|
from rest_framework import viewsets
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.generics import CreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, DestroyAPIView
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from ..models import Todo
|
from tasks.models import Todo
|
||||||
from .serializers import TaskCreateSerializer, TaskGeneralSerializer
|
from .serializers import TaskCreateSerializer, TaskGeneralSerializer
|
||||||
|
|
||||||
class TaskCreateView(CreateAPIView):
|
|
||||||
queryset = Todo.objects.all()
|
|
||||||
serializer_class = TaskCreateSerializer
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
class TodoViewSet(viewsets.ModelViewSet):
|
||||||
serializer = self.get_serializer(data=request.data)
|
|
||||||
|
|
||||||
if serializer.is_valid():
|
|
||||||
self.perform_create(serializer)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
||||||
|
|
||||||
class TaskRetrieveView(RetrieveAPIView):
|
|
||||||
queryset = Todo.objects.all()
|
queryset = Todo.objects.all()
|
||||||
serializer_class = TaskGeneralSerializer
|
serializer_class = TaskGeneralSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
class TaskUpdateView(RetrieveUpdateAPIView):
|
# Can't add ManytoMany at creation time (Tags)
|
||||||
queryset = Todo.objects.all()
|
if self.action == 'create':
|
||||||
serializer_class = TaskGeneralSerializer
|
return TaskCreateSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
return TaskGeneralSerializer
|
||||||
|
|
||||||
|
|
||||||
class TaskDeleteView(DestroyAPIView):
|
|
||||||
queryset = Todo.objects.all()
|
|
||||||
permission_classes = [IsAuthenticated]
|
|
||||||
@ -1,21 +1,18 @@
|
|||||||
from datetime import datetime, timedelta
|
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, login_user
|
||||||
from ..models import Todo
|
from tasks.models import Todo
|
||||||
|
|
||||||
class TodoCreateViewTests(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 = login_user(self.user)
|
||||||
self.url = reverse("add-task")
|
self.url = reverse("todo-list")
|
||||||
self.due_date = datetime.now() + timedelta(days=5)
|
self.due_date = datetime.now() + timedelta(days=5)
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
@ -28,7 +25,7 @@ class TodoCreateViewTests(APITestCase):
|
|||||||
'priority': 1,
|
'priority': 1,
|
||||||
'difficulty': 1,
|
'difficulty': 1,
|
||||||
'user': self.user.id,
|
'user': self.user.id,
|
||||||
'end_event': self.due_date,
|
'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'),
|
||||||
}
|
}
|
||||||
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_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
@ -42,7 +39,6 @@ class TodoCreateViewTests(APITestCase):
|
|||||||
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_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
|
||||||
@ -55,7 +51,6 @@ class TodoCreateViewTests(APITestCase):
|
|||||||
'title': 'Incomplete Task',
|
'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_400_BAD_REQUEST)
|
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
|
||||||
@ -71,9 +66,8 @@ class TodoCreateViewTests(APITestCase):
|
|||||||
'priority': 1,
|
'priority': 1,
|
||||||
'difficulty': 1,
|
'difficulty': 1,
|
||||||
'user': 999, # Invalid user ID
|
'user': 999, # Invalid user ID
|
||||||
'end_event': self.due_date,
|
'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'),
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
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 .api import GoogleCalendarEventViewset
|
|
||||||
from .tasks.views import TaskCreateView, TaskRetrieveView, TaskUpdateView, TaskDeleteView
|
from tasks.api import GoogleCalendarEventViewset
|
||||||
from .misc.views import TagViewSet
|
from tasks.tasks.views import TodoViewSet
|
||||||
|
from tasks.misc.views import TagViewSet
|
||||||
|
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
|
router.register(r'todo', TodoViewSet)
|
||||||
router.register(r'tags', TagViewSet)
|
router.register(r'tags', TagViewSet)
|
||||||
router.register(r'calendar-events', GoogleCalendarEventViewset, basename='calendar-events')
|
router.register(r'calendar-events', GoogleCalendarEventViewset, basename='calendar-events')
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
path('tasks/create/', TaskCreateView.as_view(), name="add-task"),
|
|
||||||
path('tasks/<int:pk>/', TaskRetrieveView.as_view(), name='retrieve-task'),
|
|
||||||
path('tasks/<int:pk>/update/', TaskUpdateView.as_view(), name='update-task'),
|
|
||||||
path('tasks/<int:pk>/delete/', TaskDeleteView.as_view(), name='delete-task'),
|
|
||||||
]
|
]
|
||||||
@ -12,6 +12,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@fullcalendar/core": "^6.1.9",
|
||||||
|
"@fullcalendar/daygrid": "^6.1.9",
|
||||||
|
"@fullcalendar/interaction": "^6.1.9",
|
||||||
|
"@fullcalendar/react": "^6.1.9",
|
||||||
|
"@fullcalendar/timegrid": "^6.1.9",
|
||||||
"@mui/icons-material": "^5.14.15",
|
"@mui/icons-material": "^5.14.15",
|
||||||
"@mui/material": "^5.14.15",
|
"@mui/material": "^5.14.15",
|
||||||
"@mui/system": "^5.14.15",
|
"@mui/system": "^5.14.15",
|
||||||
|
|||||||
@ -11,6 +11,21 @@ dependencies:
|
|||||||
'@emotion/styled':
|
'@emotion/styled':
|
||||||
specifier: ^11.11.0
|
specifier: ^11.11.0
|
||||||
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0)
|
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0)
|
||||||
|
'@fullcalendar/core':
|
||||||
|
specifier: ^6.1.9
|
||||||
|
version: 6.1.9
|
||||||
|
'@fullcalendar/daygrid':
|
||||||
|
specifier: ^6.1.9
|
||||||
|
version: 6.1.9(@fullcalendar/core@6.1.9)
|
||||||
|
'@fullcalendar/interaction':
|
||||||
|
specifier: ^6.1.9
|
||||||
|
version: 6.1.9(@fullcalendar/core@6.1.9)
|
||||||
|
'@fullcalendar/react':
|
||||||
|
specifier: ^6.1.9
|
||||||
|
version: 6.1.9(@fullcalendar/core@6.1.9)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@fullcalendar/timegrid':
|
||||||
|
specifier: ^6.1.9
|
||||||
|
version: 6.1.9(@fullcalendar/core@6.1.9)
|
||||||
'@mui/icons-material':
|
'@mui/icons-material':
|
||||||
specifier: ^5.14.15
|
specifier: ^5.14.15
|
||||||
version: 5.14.15(@mui/material@5.14.15)(@types/react@18.2.33)(react@18.2.0)
|
version: 5.14.15(@mui/material@5.14.15)(@types/react@18.2.33)(react@18.2.0)
|
||||||
@ -728,6 +743,49 @@ packages:
|
|||||||
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
|
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@fullcalendar/core@6.1.9:
|
||||||
|
resolution: {integrity: sha512-eeG+z9BWerdsU9Ac6j16rpYpPnE0wxtnEHiHrh/u/ADbGTR3hCOjCD9PxQOfhOTHbWOVs7JQunGcksSPu5WZBQ==}
|
||||||
|
dependencies:
|
||||||
|
preact: 10.12.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fullcalendar/daygrid@6.1.9(@fullcalendar/core@6.1.9):
|
||||||
|
resolution: {integrity: sha512-o/6joH/7lmVHXAkbaa/tUbzWYnGp/LgfdiFyYPkqQbjKEeivNZWF1WhHqFbhx0zbFONSHtrvkjY2bjr+Ef2quQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.9
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.9
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fullcalendar/interaction@6.1.9(@fullcalendar/core@6.1.9):
|
||||||
|
resolution: {integrity: sha512-I3FGnv0kKZpIwujg3HllbKrciNjTqeTYy3oJG226oAn7lV6wnrrDYMmuGmA0jPJAGN46HKrQqKN7ItxQRDec4Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.9
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.9
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fullcalendar/react@6.1.9(@fullcalendar/core@6.1.9)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-ioxu0V++pYz2u/N1LL1V8DkMyiKGRun0gMAll2tQz3Kzi3r9pTwncGKRb1zO8h0e+TrInU08ywk/l5lBwp7eog==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.9
|
||||||
|
react: ^16.7.0 || ^17 || ^18
|
||||||
|
react-dom: ^16.7.0 || ^17 || ^18
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.9
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fullcalendar/timegrid@6.1.9(@fullcalendar/core@6.1.9):
|
||||||
|
resolution: {integrity: sha512-le7UV05wVE1Trdr054kgJXTwa+A1pEI8nlCBnPWdcyrL+dTLoPvQ4AWEVCnV7So+4zRYaCqnqGXfCJsj0RQa0g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fullcalendar/core': ~6.1.9
|
||||||
|
dependencies:
|
||||||
|
'@fullcalendar/core': 6.1.9
|
||||||
|
'@fullcalendar/daygrid': 6.1.9(@fullcalendar/core@6.1.9)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@humanwhocodes/config-array@0.11.13:
|
/@humanwhocodes/config-array@0.11.13:
|
||||||
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
|
resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
|
||||||
engines: {node: '>=10.10.0'}
|
engines: {node: '>=10.10.0'}
|
||||||
@ -2779,6 +2837,10 @@ packages:
|
|||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/preact@10.12.1:
|
||||||
|
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/prelude-ls@1.2.1:
|
/prelude-ls@1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|||||||
@ -4,9 +4,10 @@ import { BrowserRouter, Route, Routes, Link } from 'react-router-dom';
|
|||||||
import TestAuth from './components/testAuth';
|
import TestAuth from './components/testAuth';
|
||||||
import LoginPage from './components/authentication/LoginPage';
|
import LoginPage from './components/authentication/LoginPage';
|
||||||
import SignUpPage from './components/authentication/SignUpPage';
|
import SignUpPage from './components/authentication/SignUpPage';
|
||||||
import NavBar from './components/Nav/Navbar';
|
import NavBar from './components/nav/Navbar';
|
||||||
import Home from './components/Home';
|
import Home from './components/Home';
|
||||||
import ProfileUpdate from './components/ProfileUpdatePage'
|
import ProfileUpdate from './components/ProfileUpdatePage';
|
||||||
|
import Calendar from './components/calendar/calendar';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
@ -19,6 +20,7 @@ const App = () => {
|
|||||||
<Route path="/signup" element={<SignUpPage/>}/>
|
<Route path="/signup" element={<SignUpPage/>}/>
|
||||||
<Route path="/testAuth" element={<TestAuth/>}/>
|
<Route path="/testAuth" element={<TestAuth/>}/>
|
||||||
<Route path="/update_profile" element={<ProfileUpdate/>}/>
|
<Route path="/update_profile" element={<ProfileUpdate/>}/>
|
||||||
|
<Route path="/calendar" element={<Calendar/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|||||||
23
frontend/src/api/TaskApi.jsx
Normal file
23
frontend/src/api/TaskApi.jsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// Create an Axios instance with common configurations
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
baseURL: 'http://127.0.0.1:8000/api/',
|
||||||
|
timeout: 5000,
|
||||||
|
headers: {
|
||||||
|
'Authorization': "Bearer " + localStorage.getItem('access_token'),
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'accept': 'application/json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchTodoTasks = () => {
|
||||||
|
return axiosInstance
|
||||||
|
.get('todo/')
|
||||||
|
.then((response) => {
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import IsAuthenticated from '../authentication/IsAuthenticated';
|
import IsAuthenticated from '../authentication/IsAuthenticated';
|
||||||
import axiosapi from '../../api/axiosapi';
|
import axiosapi from '../../api/AuthenticationApi';
|
||||||
import AppBar from '@mui/material/AppBar';
|
import AppBar from '@mui/material/AppBar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useGoogleLogin } from "@react-oauth/google"
|
import { useGoogleLogin } from "@react-oauth/google"
|
||||||
|
|
||||||
import refreshAccessToken from './refreshAcesstoken';
|
import refreshAccessToken from './refreshAcesstoken';
|
||||||
import axiosapi from '../../api/axiosapi';
|
import axiosapi from '../../api/AuthenticationApi';
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
const Navigate = useNavigate();
|
const Navigate = useNavigate();
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import axiosapi from '../../api/axiosapi';
|
import axiosapi from '../../api/AuthenticationApi';
|
||||||
|
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
|
|||||||
42
frontend/src/components/calendar/TaskDataHandler.jsx
Normal file
42
frontend/src/components/calendar/TaskDataHandler.jsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { fetchTodoTasks } from '../../api/TaskApi';
|
||||||
|
|
||||||
|
let eventGuid = 0
|
||||||
|
|
||||||
|
// function getDateAndTime(dateString) {
|
||||||
|
// const dateObject = new Date(dateString);
|
||||||
|
|
||||||
|
// const year = dateObject.getFullYear();
|
||||||
|
// const month = (dateObject.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
// const day = dateObject.getDate().toString().padStart(2, '0');
|
||||||
|
// const dateFormatted = `${year}-${month}-${day}`;
|
||||||
|
|
||||||
|
// const hours = dateObject.getUTCHours().toString().padStart(2, '0');
|
||||||
|
// const minutes = dateObject.getUTCMinutes().toString().padStart(2, '0');
|
||||||
|
// const seconds = dateObject.getUTCSeconds().toString().padStart(2, '0');
|
||||||
|
// const timeFormatted = `T${hours}:${minutes}:${seconds}`;
|
||||||
|
|
||||||
|
// return dateFormatted + timeFormatted;
|
||||||
|
// }
|
||||||
|
|
||||||
|
const mapResponseToEvents = (response) => {
|
||||||
|
return response.map(item => ({
|
||||||
|
id: createEventId(),
|
||||||
|
title: item.title,
|
||||||
|
start: item.start_event,
|
||||||
|
end: item.end_event,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEvents() {
|
||||||
|
try {
|
||||||
|
const response = await fetchTodoTasks();
|
||||||
|
return mapResponseToEvents(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEventId() {
|
||||||
|
return String(eventGuid++);
|
||||||
|
}
|
||||||
127
frontend/src/components/calendar/calendar.jsx
Normal file
127
frontend/src/components/calendar/calendar.jsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { formatDate } from "@fullcalendar/core";
|
||||||
|
import FullCalendar from "@fullcalendar/react";
|
||||||
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
|
import { getEvents, createEventId } from "./TaskDataHandler";
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
export default class Calendar extends React.Component {
|
||||||
|
state = {
|
||||||
|
weekendsVisible: true,
|
||||||
|
currentEvents: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="demo-app">
|
||||||
|
{this.renderSidebar()}
|
||||||
|
<div className="demo-app-main">
|
||||||
|
<FullCalendar
|
||||||
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||||
|
headerToolbar={{
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
||||||
|
}}
|
||||||
|
initialView="dayGridMonth"
|
||||||
|
editable={true}
|
||||||
|
selectable={true}
|
||||||
|
selectMirror={true}
|
||||||
|
dayMaxEvents={true}
|
||||||
|
weekends={this.state.weekendsVisible}
|
||||||
|
initialEvents={getEvents} // alternatively, use the `events` setting to fetch from a feed
|
||||||
|
select={this.handleDateSelect}
|
||||||
|
eventContent={renderEventContent} // custom render function
|
||||||
|
eventClick={this.handleEventClick}
|
||||||
|
eventsSet={this.handleEvents} // called after events are initialized/added/changed/removed
|
||||||
|
/* you can update a remote database when these fire:
|
||||||
|
eventAdd={function(){}}
|
||||||
|
eventChange={function(){}}
|
||||||
|
eventRemove={function(){}}
|
||||||
|
*/
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSidebar() {
|
||||||
|
return (
|
||||||
|
<div className="demo-app-sidebar">
|
||||||
|
<div className="demo-app-sidebar-section">
|
||||||
|
<h2>Instructions</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Select dates and you will be prompted to create a new event</li>
|
||||||
|
<li>Drag, drop, and resize events</li>
|
||||||
|
<li>Click an event to delete it</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="demo-app-sidebar-section">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" checked={this.state.weekendsVisible} onChange={this.handleWeekendsToggle}></input>
|
||||||
|
toggle weekends
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="demo-app-sidebar-section">
|
||||||
|
<h2>All Events ({this.state.currentEvents.length})</h2>
|
||||||
|
<ul>{this.state.currentEvents.map(renderSidebarEvent)}</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWeekendsToggle = () => {
|
||||||
|
this.setState({
|
||||||
|
weekendsVisible: !this.state.weekendsVisible,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDateSelect = selectInfo => {
|
||||||
|
let title = prompt("Please enter a new title for your event");
|
||||||
|
let calendarApi = selectInfo.view.calendar;
|
||||||
|
|
||||||
|
calendarApi.unselect(); // clear date selection
|
||||||
|
|
||||||
|
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}'`)) {
|
||||||
|
clickInfo.event.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEvents = events => {
|
||||||
|
this.setState({
|
||||||
|
currentEvents: events,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEventContent(eventInfo) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<b>{eventInfo.timeText}</b>
|
||||||
|
<i>{eventInfo.event.title}</i>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSidebarEvent(event) {
|
||||||
|
return (
|
||||||
|
<li key={event.id}>
|
||||||
|
<b>{formatDate(event.start, { year: "numeric", month: "short", day: "numeric" })}</b>
|
||||||
|
<i>{event.title}</i>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
55
frontend/src/components/calendar/index.css
Normal file
55
frontend/src/components/calendar/index.css
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
body > div { /* the react root */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0 0 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 1.5em 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
b { /* used for event dates/times */
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-app {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100%;
|
||||||
|
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-app-sidebar {
|
||||||
|
width: 300px;
|
||||||
|
line-height: 1.5;
|
||||||
|
background: #eaf9ff;
|
||||||
|
border-right: 1px solid #d3e2e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-app-sidebar-section {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-app-main {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc { /* the calendar root */
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axiosapi from '../api/axiosapi';
|
import axiosapi from '../api/AuthenticationApi';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user