diff --git a/server/src/app/api/blogs/route.ts b/server/src/app/api/blogs/route.ts new file mode 100644 index 0000000..14890cc --- /dev/null +++ b/server/src/app/api/blogs/route.ts @@ -0,0 +1,163 @@ +import { NextRequest, NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; +import getSession from "@/server_actions/getSession"; + +export const GET = async (req: NextRequest) => { + try { + const session = await getSession(); + if (!session) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); + } + + const userId = session.getId(); + if (!userId) { + return NextResponse.json({ message: "User ID not found" }, { status: 401 }); + } + + const url = new URL(req.url); + const date = url.searchParams.get('date'); + + const activities = await prisma.activity.findMany({ + where: { + studentId: userId, + ...(date && { date: new Date(date) }), + }, + include: { + feedback: { + select: { + status: true, // Fetch the feedback status + feedbackNotes: true, // Optionally fetch feedback notes if needed + }, + }, + }, + }); + + console.log(activities); + + return NextResponse.json(activities); + } catch (error) { + console.error("Error fetching activities:", error); + return NextResponse.json({ message: "Error fetching activities" }, { status: 500 }); + } +}; + +export const POST = async (req: NextRequest) => { + try { + const session = await getSession(); + + if (!session) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); + } + const userId = session.getId(); + console.log(userId) + if (!userId) { + return NextResponse.json({ message: "User ID not found" }, { status: 401 }); + } + + const { date, timeSpent, notes } = await req.json(); + + if (!date || typeof timeSpent !== 'number' || !notes) { + return NextResponse.json({ message: "Invalid input data" }, { status: 400 }); + } + + const newActivity = await prisma.activity.create({ + data: { + studentId: userId, + date: new Date(date), + timeSpent, + notes, + }, + }); + + return NextResponse.json(newActivity, { status: 201 }); + } catch (error) { + console.error("Error creating activity:", error); + return NextResponse.json({ message: "Error creating activity" }, { status: 500 }); + } +}; + +export const PATCH = async (req: NextRequest) => { + try { + const session = await getSession(); + if (!session) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); + } + const userId = session.getId(); + + if (!userId) { + return NextResponse.json({ message: "User ID not found" }, { status: 401 }); + } + + const { id, timeSpent, notes } = await req.json(); + + if (!id || (timeSpent === undefined && notes === undefined)) { + return NextResponse.json({ message: "Invalid input data" }, { status: 400 }); + } + + const updatedActivity = await prisma.activity.update({ + where: { + id, + studentId: userId, + }, + data: { + timeSpent: timeSpent !== undefined ? timeSpent : undefined, + notes: notes !== undefined ? notes : undefined + } + }); + + return NextResponse.json(updatedActivity, { status: 200 }); + } catch (error) { + console.error("Error updating activity:", error); + return NextResponse.json({ message: "Error updating activity" }, { status: 500 }); + } +}; + +export const DELETE = async (req: NextRequest) => { + try { + const session = await getSession(); + if (!session) { + return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); + } + const userId = session.getId(); + if (!userId) { + return NextResponse.json({ message: "User ID not found" }, { status: 401 }); + } + + const url = new URL(req.url); + const id = url.searchParams.get('id'); + + if (!id) { + return NextResponse.json({ message: "Missing activity ID" }, { status: 400 }); + } + + const activity = await prisma.activity.findUnique({ + where: { id }, + select: { createdAt: true, studentId: true } + }); + + if (!activity) { + return NextResponse.json({ message: "Activity not found" }, { status: 404 }); + } + + if (activity.studentId !== userId) { + return NextResponse.json({ message: "Not authorized to delete this activity" }, { status: 403 }); + } + + const now = new Date(); + const creationDate = new Date(activity.createdAt); + const twoDays = 2 * 24 * 60 * 60 * 1000; + + if (now.getTime() - creationDate.getTime() > twoDays) { + return NextResponse.json({ message: "Cannot delete activity after 2 days" }, { status: 403 }); + } + + await prisma.activity.delete({ + where: { id } + }); + + return NextResponse.json({ message: "Activity deleted successfully" }, { status: 200 }); + } catch (error) { + console.error("Error deleting activity:", error); + return NextResponse.json({ message: "Error deleting activity" }, { status: 500 }); + } +}; diff --git a/server/src/components/calendar.tsx b/server/src/components/calendar.tsx index b8b09d8..474b68b 100644 --- a/server/src/components/calendar.tsx +++ b/server/src/components/calendar.tsx @@ -1,9 +1,9 @@ -'use client'; +"use client"; -import React, { useState } from 'react'; -import { Calendar as BigCalendar, momentLocalizer, Views, Event as BigCalendarEvent } from 'react-big-calendar'; -import moment from 'moment'; -import 'react-big-calendar/lib/css/react-big-calendar.css'; +import React, { useState, useEffect } from "react"; +import { Calendar as BigCalendar, momentLocalizer, Views } from "react-big-calendar"; +import moment from "moment"; +import "react-big-calendar/lib/css/react-big-calendar.css"; import { AlertDialog, AlertDialogAction, @@ -13,130 +13,144 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, - AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; -import MentorPopUp from './MentorPopUp'; +import { getSessionOnClient } from "@/server_actions/getSession"; -moment.locale('en-GB'); +moment.locale("en-GB"); const localizer = momentLocalizer(moment); +interface FormData { + studentId: string; + date: string; + timeSpent: number; + notes: string; +} + interface CalendarEvent { - id: number; + id: string; title: string; start: Date; end: Date; allDay?: boolean; + color?: string; + createdAt: Date; + studentId: string; + timeSpent?: number; + notes?: string; + status: "pending" | "approved" | "rejected"; // New field for status } -const events: CalendarEvent[] = [ - { - id: 0, - title: 'Board meeting', - start: new Date(2024, 7, 29, 9, 0, 0), // August 29, 2024, 9:00 AM - end: new Date(2024, 7, 29, 13, 0, 0), // August 29, 2024, 1:00 PM - }, - { - id: 1, - title: 'MS training', - allDay: true, - start: new Date(2024, 7, 29, 14, 0, 0), // August 29, 2024, 2:00 PM - end: new Date(2024, 7, 29, 16, 30, 0), // August 29, 2024, 4:30 PM - }, - { - id: 2, - title: 'Team lead meeting', - start: new Date(2024, 7, 29, 8, 30, 0), // August 29, 2024, 8:30 AM - end: new Date(2024, 7, 29, 12, 30, 0), // August 29, 2024, 12:30 PM - }, - { - id: 11, - title: 'Birthday Party', - start: new Date(2024, 7, 30, 7, 0, 0), // August 30, 2024, 7:00 AM - end: new Date(2024, 7, 30, 10, 30, 0), // August 30, 2024, 10:30 AM - } -]; +const convertToCalendarEvents = (data: any[]): CalendarEvent[] => { + return data.map((item, index) => { + const startDate = new Date(item.date); + const endDate = new Date(item.date); // Calculate end time + console.log(item.feedback[0]?.status); -const styles = { - container: { - width: '80vw', - height: '60vh', - margin: '2em' - } + return { + id: index, + title: item.notes || "No Title", + start: startDate, + end: endDate, + status: item.feedback[0]?.status, // Add the status field + }; + }); }; const TaskCalendar: React.FC = () => { const [taskModalOpen, setTaskModalOpen] = useState(false); - const [mentorModalOpen, setMentorModalOpen] = useState(false); - const [selectedMentorEvent, setSelectedMentorEvent] = useState(null); const [selectedDate, setSelectedDate] = useState(null); const [workingHours, setWorkingHours] = useState(0); - const [notes, setNotes] = useState(''); - const [studentId, setStudentId] = useState(''); + const [session, setSession] = useState(null); + const [notes, setNotes] = useState(""); + const [studentId, setStudentId] = useState(""); const [currentDate, setCurrentDate] = useState(new Date()); const [formData, setFormData] = useState({ - studentId: '', - date: '', + studentId: "", + date: "", timeSpent: 0, - notes: '', + notes: "", }); const [events, setEvents] = useState([]); const [editingEvent, setEditingEvent] = useState(null); const [isEditable, setIsEditable] = useState(true); useEffect(() => { - // Fetch the logged-in user's student ID - const fetchStudentId = async () => { - try { - const response = await fetch('/api/auth/user'); // user API route - const data = await response.json(); - setStudentId(data.studentId); // Assuming the API response has a `studentId` field - } catch (error) { - console.error('Failed to fetch student ID:', error); - } - }; - - fetchStudentId(); + getSessionOnClient() + .then((data) => { + setSession(data); + setStudentId(data.id); + }) + .catch((error) => { + console.error("Error fetching session:", error); + }); }, []); - useEffect(() => { - const fetchEvents = async () => { - try { - const response = await fetch(`http://localhost:3000/api/blogs?studentId=${studentId}`); - const data = await response.json(); - setEvents(data); - } catch (error) { - console.error('Failed to fetch events:', error); - } - }; + const fetchEvents = async () => { + try { + const response = await fetch(`http://localhost:3000/api/blogs?studentId=${studentId}`); + const data = await response.json(); + const parsedEvents = convertToCalendarEvents(data); + setEvents(parsedEvents); + } catch (error) { + console.error("Failed to fetch events:", error); + } + }; + useEffect(() => { if (studentId) { fetchEvents(); } }, [studentId]); - const handleSelectEvent = (event: BigCalendarEvent) => { - const calendarEvent = event as CalendarEvent; // Type assertion - setTaskDetail({ selectedDate: calendarEvent.title }); - setTaskModalOpen(true); + const fetchEventForDate = async (formattedDate: string) => { + try { + const response = await fetch(`http://localhost:3000/api/blogs?date=${formattedDate}&studentId=${studentId}`); + const data = await response.json(); + if (data.length > 0) { + const existingEvent = data[0]; + setFormData({ + studentId: existingEvent.studentId || "", + date: formattedDate, + timeSpent: existingEvent.timeSpent || 0, + notes: existingEvent.notes || "", + }); + setWorkingHours(existingEvent.timeSpent || 0); + setNotes(existingEvent.notes || ""); + setEditingEvent(existingEvent); + } else { + setFormData({ + studentId: "", + date: formattedDate, + timeSpent: 0, + notes: "", + }); + setWorkingHours(0); + setNotes(""); + setEditingEvent(null); + } + } catch (error) { + console.error("Failed to fetch event for date:", error); + } }; - const handleReviewChange = (e: React.ChangeEvent) => { - setReview(e.target.value); - }; + const handleDateClick = (date: Date) => { + setSelectedDate(date); + const formattedDate = moment(date).format("YYYY-MM-DD"); - const handleAccept = () => { - console.log("Task Accepted"); - // Implement further logic here, e.g., update task status, send feedback - setTaskModalOpen(false); - }; + const today = moment().startOf("day"); + const dayBeforeYesterday = moment().subtract(2, "days").startOf("day"); - const handleReject = () => { - console.log("Task Rejected"); - // Implement further logic here, e.g., update task status, send feedback - setTaskModalOpen(false); + if (moment(date).isBefore(dayBeforeYesterday) || moment(date).isAfter(today)) { + setIsEditable(false); + } else { + setIsEditable(true); + } + + fetchEventForDate(formattedDate); + setTaskModalOpen(true); }; const handleClose = () => { @@ -153,140 +167,98 @@ const TaskCalendar: React.FC = () => { notes, }; - const response = await fetch('http://localhost:3000/api/blogs', { - method: editingEvent ? 'PATCH' : 'POST', + const response = await fetch("http://localhost:3000/api/blogs", { + method: editingEvent ? "PATCH" : "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify({ ...newFormData, - id: editingEvent?.id + id: editingEvent?.id, }), }); if (response.ok) { - const updatedEvent: CalendarEvent = await response.json(); - // Refetch events to update the calendar - const fetchEvents = async () => { - try { - const response = await fetch(`http://localhost:3000/api/blogs?studentId=${studentId}`); - const data = await response.json(); - setEvents(data); - } catch (error) { - console.error('Failed to fetch events:', error); - } - }; - + // Update events without refreshing fetchEvents(); handleClose(); } else { - console.error('Failed to save event:', await response.json()); + console.error("Failed to save event:", await response.json()); } } catch (error) { - console.error('Error submitting form:', error); + console.error("Error submitting form:", error); } }; - const eventPropGetter = (event: CalendarEvent) => { - return { - style: { - backgroundColor: event.color || '#3174ad', - color: '#fff', - }, + // Custom Toolbar + const CustomToolbar = (toolbar: any) => { + const goToBack = () => { + toolbar.onNavigate("PREV"); }; - }; - const handleNextMonth = () => { - setCurrentDate(moment(currentDate).add(1, 'months').toDate()); - }; - - const handlePrevMonth = () => { - setCurrentDate(moment(currentDate).subtract(1, 'months').toDate()); - }; + const goToNext = () => { + toolbar.onNavigate("NEXT"); + }; - const handleMentorSelectEvent = (event: CalendarEvent) => { - setSelectedMentorEvent(event); - setMentorModalOpen(true); + return ( +
+ + {moment(toolbar.date).format("MMMM YYYY")} + +
+ ); }; - - const components = { - month: { - dateHeader: ({ date, label }: { date: Date; label: string }) => { - const dayEvents = events.filter((event) => - moment(event.start).isSame(date, 'day') - ); - - return ( -
- {label} -
- {dayEvents.map((event) => ( -
handleMentorSelectEvent(event)} - > - {event.title} -
- ))} -
-
- ); + // Event Prop Getter to set custom styles + const eventPropGetter = (event: CalendarEvent) => { + let backgroundColor = "#FFD93D"; // Default background color + let textColor = "#000000"; // Default text color (white) + console.log(event); + + // Customize based on event status + if (event.status === "pending") { + backgroundColor = "#FFD93D"; // Yellow background for pending + textColor = "#000000"; // Black text for better contrast + } else if (event.status === "approved") { + backgroundColor = "#6BCB77"; // Green background for accepted + textColor = "#000000"; // White text for contrast + } else if (event.status === "rejected") { + backgroundColor = "#F25C54"; // Red background for rejected + textColor = "#000000"; // White text for contrast + } + + return { + style: { + backgroundColor, + color: textColor, // Apply text color based on status }, - }, + }; }; - // Filter events based on search query - const filteredEvents = events.filter(event => - event.title.toLowerCase().includes(searchQuery.toLowerCase()) - ); - return ( <> - setMentorModalOpen(false)} - mentorDetails={{ - selectedDate: selectedMentorEvent?.start.toDateString() || '', - workingHours: selectedMentorEvent?.timeSpent?.toString() || '', - studentActivity: selectedMentorEvent?.notes || '', - review: selectedMentorEvent?.review || '', - }} - /> - {/* Task Detail Modal */} - -
- - Task Detail + Task Details
- +
- +
{
-