From d0a56099c1188604899ac17e108ac444bc80147f Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:10:29 +0530 Subject: [PATCH 1/8] Update index.css --- server/src/app/index.css | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/src/app/index.css b/server/src/app/index.css index cefb791..5570c6e 100644 --- a/server/src/app/index.css +++ b/server/src/app/index.css @@ -67,3 +67,23 @@ } } +.ql-container { + height: 120px !important; /* Fixed editor height */ + display: flex; + flex-direction: column; + width: 100% !important; /* Keeps width consistent */ + max-width: 600px !important; /* Adjust to your preferred width */ +} + +.ql-editor { + flex: 1; + max-height: 90px !important; /* Prevents resizing */ + overflow-y: auto !important; /* Enables scrolling for text */ + padding-bottom: 10px; /* Avoids content cutting off */ + word-wrap: break-word; /* Prevents text from expanding width */ + overflow-wrap: break-word; + white-space: pre-wrap; /* Keeps text inside the box */ +} + + + From 40d26f8cbab86b0a704223316fcec40c497e84e1 Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:15:27 +0530 Subject: [PATCH 2/8] Update student page.tsx change the student page according to a new student dashboard. --- server/src/app/student/page.tsx | 110 ++------------------------------ 1 file changed, 6 insertions(+), 104 deletions(-) diff --git a/server/src/app/student/page.tsx b/server/src/app/student/page.tsx index a3d2dd3..874f9e8 100644 --- a/server/src/app/student/page.tsx +++ b/server/src/app/student/page.tsx @@ -1,106 +1,8 @@ -'use client'; +"use client"; // This file needs to use some client-side logic for pop-up control -import { Button } from '@/components/ui/button'; -import React, { useState, useEffect } from 'react'; -import { getSessionOnClient } from "@/server_actions/getSession"; -import TaskCalendar from "@/components/calendar"; -import Image from "next/image"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; - -interface SessionData { - id: string; - fname: string; - lname: string; - email: string; +import { useState } from "react"; +import MentorRegStudentForm from "@/components/mentorregstudentform"; +import StudentDashboard from "@/components/studentDashboard"; +export default function StudentPage() { + return ; } - -const StudentPage: React.FC = () => { - const [session, setSession] = useState(null); - const [mentorName, setMentorName] = useState(null); - const [mentorId, setMentorId] = useState(null); - const [selectedUser, setSelectedUser] = useState(null); - - useEffect(() => { - getSessionOnClient() - .then((data: SessionData | null) => { - if (data) { - setSession(data); - setMentorName(`${data.fname} ${data.lname}`); - setMentorId(data.id); - setSelectedUser(data.id); - } - }) - .catch((error) => { - console.error('Error fetching session:', error); - }); - }, []); - - const [isPopupOpen, setIsPopupOpen] = useState(false); - const togglePopup = () => { - setIsPopupOpen(!isPopupOpen); - }; - - return ( -
- {/* Top Bar with Logo, Avatar, and Logout */} -
- Logo - -
- {/* Avatar */} -
- - - CN - -
- - {/* Popup Screen */} - {isPopupOpen && ( -
- {/* Large Avatar */} -
- - - CN - -
- - {/* Student Name and Email */} -
-

- {session ? `${session.fname} ${session.lname}` : 'Loading...'} -

-

- {session ? session.email : 'Loading...'} -

-
- - {/* Logout Button */} -
- -
-
- )} -
-
- -
- {/* Center the calendar with rounded corners */} -
-
- -
-
-
-
- ); -}; - -export default StudentPage; From 7425f7aed3e763f71dade3be785ec5fff6fbfbb2 Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:19:14 +0530 Subject: [PATCH 3/8] Create route.ts for generate ai responses backend for AI description enhancer --- .../src/app/api/generateAiResponse/route.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 server/src/app/api/generateAiResponse/route.ts diff --git a/server/src/app/api/generateAiResponse/route.ts b/server/src/app/api/generateAiResponse/route.ts new file mode 100644 index 0000000..e3953ca --- /dev/null +++ b/server/src/app/api/generateAiResponse/route.ts @@ -0,0 +1,67 @@ +import { NextRequest, NextResponse } from "next/server"; + +const API_URL = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.3"; +const HF_TOKEN = process.env.HUGGINGFACE_TOKEN; + +export async function POST(req: NextRequest) { + try { + const { userInput } = await req.json(); + + if (!HF_TOKEN) { + throw new Error("Missing Hugging Face API token"); + } + + // Check if the user is online + // if (!navigator.onLine) { + // return NextResponse.json({ aiResponse: "You are currently offline. Please check your internet connection." }, { status: 200 }); + // } + + // Adjust the prompt to limit the response + const prompt = `Refine the following activity description to make it more detailed and clearer, but keep the answer concise (maximum 500 characters): "${userInput}"`; + + // Fetch the response from Hugging Face API + const response = await fetch(API_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${HF_TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + inputs: prompt, + }), + }); + + // Check if the response is OK + if (!response.ok) { + throw new Error("Failed to fetch response from AI service."); + } + + const data = await response.json(); + + // Check if the response is valid and return the refined description + if (!data || !data[0]?.generated_text) { + throw new Error("Invalid AI response"); + } + + // Extract the refined description from the model's response + let refinedDescription = data[0].generated_text.trim(); + + // Ensure that the generated text doesn't include the prompt or input part + const promptIndex = refinedDescription.indexOf(userInput); + if (promptIndex !== -1) { + refinedDescription = refinedDescription.slice(promptIndex + userInput.length).trim(); + } + + // Return only the refined activity description + return NextResponse.json({ aiResponse: refinedDescription }); + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); // Log error + return NextResponse.json({ aiResponse: "Unable to process your request at the moment. Please try again later." }, { status: 200 }); + } else { + console.error("Unknown error:", error); + return NextResponse.json({ aiResponse: "An unexpected error occurred. Please try again later." }, { status: 200 }); + } + //return NextResponse.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 }); + } +} From 3cdd3e21fcc41d593114d34bf31aa5eee1c1deab Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:20:33 +0530 Subject: [PATCH 4/8] Update package.json --- server/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index ace44fa..b76415f 100644 --- a/server/package.json +++ b/server/package.json @@ -28,6 +28,7 @@ "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", + "@radix-ui/react-tooltip": "^1.1.8", "@shadcn/ui": "^0.0.4", "bcrypt": "^5.1.1", "bcrypt-ts": "^5.0.2", @@ -41,11 +42,12 @@ "jose": "^5.8.0", "json2csv": "^6.0.0-alpha.2", "lucide-react": "^0.436.0", - "next": "14.2.5", + "next": "^14.2.24", "nodemailer": "^6.9.15", "react": "^18", "react-big-calendar": "^1.13.4", "react-dom": "^18", + "react-quill": "^2.0.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "tsx": "^4.19.0", From 69fe32cfb6424b6f66058cbb6d4829ac1b7c1597 Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 12:23:26 +0530 Subject: [PATCH 5/8] Create .env.local --- server/.env.local | 1 + 1 file changed, 1 insertion(+) create mode 100644 server/.env.local diff --git a/server/.env.local b/server/.env.local new file mode 100644 index 0000000..2aa7400 --- /dev/null +++ b/server/.env.local @@ -0,0 +1 @@ +HUGGINGFACE_TOKEN= From c4bab214303df3020d1e51ba8f5f712bdf0a3177 Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 19:30:21 +0530 Subject: [PATCH 6/8] Add new student dashboard frontend components --- server/src/components/STCalendar.tsx | 87 +++++ server/src/components/STdashboardOverview.tsx | 42 +++ server/src/components/activity.tsx | 65 ++++ server/src/components/addNewActivity.tsx | 336 +++++++++++++++++ server/src/components/recentReviews.tsx | 178 +++++++++ server/src/components/studentDashboard.tsx | 50 +++ server/src/components/updateActivity.tsx | 349 ++++++++++++++++++ 7 files changed, 1107 insertions(+) create mode 100644 server/src/components/STCalendar.tsx create mode 100644 server/src/components/STdashboardOverview.tsx create mode 100644 server/src/components/activity.tsx create mode 100644 server/src/components/addNewActivity.tsx create mode 100644 server/src/components/recentReviews.tsx create mode 100644 server/src/components/studentDashboard.tsx create mode 100644 server/src/components/updateActivity.tsx diff --git a/server/src/components/STCalendar.tsx b/server/src/components/STCalendar.tsx new file mode 100644 index 0000000..71b4dbb --- /dev/null +++ b/server/src/components/STCalendar.tsx @@ -0,0 +1,87 @@ +import React, { useState } from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; + +const Calendar = () => { + const [currentDate, setCurrentDate] = useState(new Date()); + const today = new Date(); + + const daysOfWeek = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; + + const prevMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1)); + }; + + const nextMonth = () => { + setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1)); + }; + + const generateDays = () => { + const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); + const lastDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); + const firstDayIndex = firstDayOfMonth.getDay(); + const totalDays = lastDayOfMonth.getDate(); + + const emptyDays = Array.from({ length: firstDayIndex }, () => null); + const days = Array.from({ length: totalDays }, (_, i) => i + 1); + + return [...emptyDays, ...days]; + }; + + return ( +
+ {/* Left Sidebar */} +
+
{today.getFullYear()}
+
{today.toLocaleString("default", { month: "long" }).toUpperCase()}
+
{today.getDate()}
+
{daysOfWeek[today.getDay()]}
+
+ + {/* Calendar */} +
+ {/* Header */} +
+ +

+ {currentDate.toLocaleString("default", { month: "long", year: "numeric" })} +

+ +
+ + {/* Days of the week */} +
+ {daysOfWeek.map((day) => ( +
{day}
+ ))} +
+ + {/* Calendar Grid */} +
+ {generateDays().map((day, index) => { + const isToday = + day === today.getDate() && + currentDate.getMonth() === today.getMonth() && + currentDate.getFullYear() === today.getFullYear(); + + return ( +
+ {day || ""} +
+ ); + })} +
+
+
+ ); +}; + +export default Calendar; \ No newline at end of file diff --git a/server/src/components/STdashboardOverview.tsx b/server/src/components/STdashboardOverview.tsx new file mode 100644 index 0000000..8c22940 --- /dev/null +++ b/server/src/components/STdashboardOverview.tsx @@ -0,0 +1,42 @@ +import React from "react"; + +interface DashboardOverviewProps { + userType: "mentor" | "student"; // Determines dashboard type +} + +const DashboardOverview: React.FC = ({ userType }) => { + // Data for mentor and student dashboards + const dashboardData = userType === "mentor" + ? [ + { title: "Total Projects", icon: "📁", colorClass: "text-blue-500", status: "Current assigned", number: 2 }, + { title: "Pending Approval", icon: "⏳", colorClass: "text-yellow-500", status: "Awaiting review", number: 4 }, + { title: "Approved Activities", icon: "✔️", colorClass: "text-green-500", status: "Successfully complete", number: 6 }, + { title: "Rejected Activities", icon: "❌", colorClass: "text-red-500", status: "Need revisions", number: 5 } + ] + : [ + { title: "Total Activities", icon: "📑", colorClass: "text-blue-500", status: "Current total activities", number: 10 }, + { title: "Pending Approval", icon: "⏳", colorClass: "text-yellow-500", status: "Awaiting review", number: 3 }, + { title: "Approved Activities", icon: "✔️", colorClass: "text-green-500", status: "Successfully complete", number: 8 }, + { title: "Rejected Activities", icon: "❌", colorClass: "text-red-500", status: "Need revisions", number: 2 } + ]; + + return ( +
+

Dashboard Overview

+
+ {dashboardData.map((item, index) => ( +
+
+
{item.icon}
+
{item.number}
+
+

{item.title}

+

{item.status}

+
+ ))} +
+
+ ); +}; + +export default DashboardOverview; \ No newline at end of file diff --git a/server/src/components/activity.tsx b/server/src/components/activity.tsx new file mode 100644 index 0000000..cea37d6 --- /dev/null +++ b/server/src/components/activity.tsx @@ -0,0 +1,65 @@ +import React from "react"; + +type Activity = { + id: number; + status: "approved" | "pending" | "rejected"; + message: string; +}; + +// Mock data (to be received from parent component) +const mockActivities: Activity[] = [ + { id: 1, status: "approved", message: "Made minor updates and improvements." }, + { id: 2, status: "pending", message: "Fixed the bug and pushed the update to the Git branch" }, + { id: 3, status: "rejected", message: "Something went wrong during deployment." }, +]; + +type Props = { + selectedDate: string; // Date is received from another component + activities: Activity[]; // Activities are received dynamically +}; + +const Activity: React.FC = ({ selectedDate, activities }) => { + return ( +
+ {/* Header Section */} +
+

📄 Today Activities

+ {selectedDate} +
+ + {/* Activity List (Fixed Height) */} +
+ {activities.length > 0 ? ( + activities.map((activity) => ( +
+ {/* Status Icon */} + {activity.status === "approved" && } + {activity.status === "pending" && } + {activity.status === "rejected" && } + {/* Message */} +

{activity.message}

+
+ )) + ) : ( +
+

No activities found.

+
+ )} +
+
+ ); +}; + +// Example usage with mock data +const ParentComponent = () => { + const selectedDate = "03/13/2025"; // Date from another component + return ; +}; + +export default ParentComponent; \ No newline at end of file diff --git a/server/src/components/addNewActivity.tsx b/server/src/components/addNewActivity.tsx new file mode 100644 index 0000000..d6431a2 --- /dev/null +++ b/server/src/components/addNewActivity.tsx @@ -0,0 +1,336 @@ +import { useState, useRef, useEffect } from "react"; +import { AlertDialog, AlertDialogContent } from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent } from "@/components/ui/card"; +import { Toaster } from "@/components/ui/toaster"; +import ReactQuill from "react-quill"; +import "react-quill/dist/quill.snow.css"; +import { User, Bot, Activity, Play, SquareX, Send, Wand2, Pencil, ArrowBigRightDash } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; + +interface AddNewActivityProps { + isOpen: boolean; + onClose: () => void; +} + +const AddNewActivity: React.FC = ({ isOpen, onClose }) => { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [hours, setHours] = useState(""); + const [aiInput, setAiInput] = useState(""); + const [messages, setMessages] = useState([ + "Hello! I can help you generate detailed activity descriptions. What would you like to describe?" + ]); + const [showAISection, setShowAISection] = useState(false); + + const chatEndRef = useRef(null); + const flyoutRef = useRef(null); + const buttonRef = useRef(null); + + const [editingIndex, setEditingIndex] = useState(null); + + // Handle click outside flyout + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + flyoutRef.current && + !flyoutRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + setShowAISection(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + // Scroll to bottom of chat + useEffect(() => { + chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const handleHoursChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (/^([1-9]|1[0-2])?$/.test(value)) { + setHours(value); + } + }; + + const handleAISuggestion = async () => { + if (!aiInput.trim()) return; + + try { + const res = await fetch("/api/generateAiResponse", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userInput: aiInput }), + }); + + const data = await res.json(); + if (data.aiResponse) { + setMessages((prev) => [...prev, data.aiResponse]); + } + } catch (error) { + console.error("Error fetching AI response:", error); + } + setAiInput(""); + }; + + const handleSubmit = () => { + console.log("Activity Submitted!"); + onClose(); + }; + + const handleDescriptionChange = (value: string) => { + setDescription(value || "


"); + }; + + const handleSendMessage = async () => { + if (aiInput.trim() === "") return; + + if (editingIndex !== null) { + const newMessages = [...messages]; + newMessages[editingIndex] = aiInput; + if (newMessages[editingIndex + 1]) { + newMessages.splice(editingIndex + 1, 1); + } + setMessages(newMessages); + setEditingIndex(null); + await handleAISuggestion(); + } else { + setMessages((prev) => [...prev, aiInput]); + await handleAISuggestion(); + } + setAiInput(""); + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + handleSendMessage(); + } + }; + + const handleUseThis = (message: string) => { + setDescription(message); + }; + + const handleRegenerate = async (index: number) => { + try { + const userMessage = messages[index - 1]; + const res = await fetch("/api/generateAiResponse", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userInput: userMessage }), + }); + + const data = await res.json(); + if (data.aiResponse) { + const newMessages = [...messages]; + newMessages[index] = data.aiResponse; + setMessages(newMessages); + } + } catch (error) { + console.error("Error regenerating response:", error); + } + }; + + const handleEditMessage = (index: number) => { + setAiInput(messages[index - 1]); + setEditingIndex(index - 1); + }; + + return ( + + + {/* Main Form Section */} +
+
+ {/* Close Button */} + +
+
+ +
+

Add New Activity

+
+
+ {/* Added gray background container */} +
+ +
+
+ + setTitle(e.target.value)} + placeholder="Enter activity title" + /> +
+ +
+ + +
+ +
+
+ +
+ +
+
+ + + {/* Submit, cancel Buttons */} +
+ + +
+
+ {/* AI Trigger Button */} + + + {/* AI Flyout Menu */} + {showAISection && ( +
+
+ +
+

+ 🤖 AI Description Assistant +

+
+
+ {messages.map((msg, index) => ( +
+ {index % 2 === 0 && } + + + {msg} + {index % 2 === 0 && index !== 0 && ( // Added index !== 0 condition + +
+ {/* Use this */} + + + + + Use this message + + + {/* Regenerate */} + + + + + Regenerate + + + {/* Edit */} + + + + + Edit message + +
+
+ )} +
+
+ {index % 2 !== 0 && } +
+ ))} +
+
+ +
+ setAiInput(e.target.value)} + onKeyDown={handleKeyPress} + placeholder="Describe your activity..." + className="pr-10" // Add right padding to prevent text underlap + /> + +
+
+
+
+ )} +
+ + + + ); +}; + +export default AddNewActivity; \ No newline at end of file diff --git a/server/src/components/recentReviews.tsx b/server/src/components/recentReviews.tsx new file mode 100644 index 0000000..14d3186 --- /dev/null +++ b/server/src/components/recentReviews.tsx @@ -0,0 +1,178 @@ +import React, { useMemo, useState, useRef, useEffect } from "react"; +import { ExternalLink, StickyNoteIcon, Search, CirclePlus, FilterIcon, ArrowDownUpIcon } from "lucide-react"; +import { Input } from "@/components/ui/input"; + +type Review = { + id: number; + status: "approved" | "rejected" | "pending"; + message: string; + date: string; +}; + +const mockReviews: Review[] = [ + { id: 1, status: "approved", message: "Made minor updates and improvements.", date: "03/13/2025" }, + { id: 2, status: "rejected", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 3, status: "approved", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 4, status: "pending", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 5, status: "rejected", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 6, status: "pending", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 7, status: "approved", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 8, status: "rejected", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 9, status: "rejected", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, + { id: 10, status: "pending", message: "Designed wireframe to enhance the system’s UI/UX.", date: "03/12/2025" }, +]; + +interface RecentReviewsProps { + onAddNewActivity: () => void; + onUpdateActivity: () => void; +} + +const RecentReviews: React.FC = ({ onAddNewActivity, onUpdateActivity }) => { + const [filter, setFilter] = useState<"all" | "approved" | "rejected" | "pending">("all"); + const [searchQuery, setSearchQuery] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const menuRef = useRef(null); + + + // Close flyout when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + // Memoize filtered submissions to prevent re-renders + const filteredSubmissions = useMemo( + () => + mockReviews.filter( + (sub) => + (filter === "all" || sub.status === filter) && + (sub.message.toLowerCase().includes(searchQuery.toLowerCase()) || + sub.date.toLowerCase().includes(searchQuery.toLowerCase())) + ), + [filter, searchQuery] + ); + + const filteredReviews = filter === "all" ? mockReviews : mockReviews.filter((r) => r.status === filter); + + return ( +
+
+
+ +

All Recent Reviews

+
+ +
+
+ {/* Search Input */} +
+
+ setSearchQuery(e.target.value)} + /> + +
+ +
+ + {/* Filter Buttons */} +
+ + + + +
+ + + {isOpen && ( +
+ +
+ +
+ )} +
+ + +
+
+ +
+ {filteredReviews.length > 0 ? ( + filteredReviews.map((review) => ( +
+
+ {review.status === "approved" ? ( + + ) : review.status === "rejected" ? ( + + ) : } +

{review.message}

+
+ {review.date} + +
+ )) + ) : ( +

No records found

+ )} +
+
+ ); +}; + +export default RecentReviews; diff --git a/server/src/components/studentDashboard.tsx b/server/src/components/studentDashboard.tsx new file mode 100644 index 0000000..180800c --- /dev/null +++ b/server/src/components/studentDashboard.tsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import Navbar from "@/components/navBar"; +import DashboardOverview from "@/components/STdashboardOverview"; +import Calendar from "@/components/STCalendar"; +import Footer from "@/components/footer"; +import RecentReviews from "./recentReviews"; +import Activity from "./activity"; +import AddNewActivity from "./addNewActivity"; +import UpdateActivity from "./updateActivity"; + +const StudentDashboard = () => { + const [isAddNewActivityOpen, setIsAddNewActivityOpen] = useState(false); + const openAddNewActivity = () => setIsAddNewActivityOpen(true); + const closeAddNewActivity = () => setIsAddNewActivityOpen(false); + + const [isUpdateActivityOpen, setIsUpdateActivityOpen] = useState(false); + const openUpdateActivity = () => setIsUpdateActivityOpen(true); + const closeUpdateActivity = () => setIsUpdateActivityOpen(false); + + return ( +
+ {/* Navbar */} + + + {/* Main Content */} +
+ {/* Left Section */} +
+ + +
+ + {/* Right Section */} +
+ + +
+
+ + {/* Footer */} +
+ + {/* Modals */} + + +
+ ); +}; + +export default StudentDashboard; diff --git a/server/src/components/updateActivity.tsx b/server/src/components/updateActivity.tsx new file mode 100644 index 0000000..fd98ce2 --- /dev/null +++ b/server/src/components/updateActivity.tsx @@ -0,0 +1,349 @@ +import { useState, useRef, useEffect } from "react"; +import { AlertDialog, AlertDialogContent } from "@/components/ui/alert-dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent } from "@/components/ui/card"; +import { Toaster } from "@/components/ui/toaster"; +import ReactQuill from "react-quill"; +import "react-quill/dist/quill.snow.css"; +import { User, Bot, Activity, Play, SquareX, Send, Wand2, Pencil, ArrowBigRightDash } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; + +interface UpdateActivityProps { + isOpen: boolean; + onClose: () => void; + activity?: { // Add activity prop to receive existing data + title: string; + description: string; + hours: string; + }; +} + +const UpdateActivity: React.FC = ({ isOpen, onClose, activity }) => { + const [title, setTitle] = useState("Made minor updates and improvements"); + const [description, setDescription] = useState("Resolved a critical login bug that was preventing users from accessing their accounts. Conducted multiple tests to ensure functionality before pushing the fix to GitHub."); + const [hours, setHours] = useState("8"); + const [aiInput, setAiInput] = useState(""); + const [messages, setMessages] = useState([ + "Hello! I can help you generate detailed activity descriptions. What would you like to describe?" + ]); + const [showAISection, setShowAISection] = useState(false); + + const chatEndRef = useRef(null); + const flyoutRef = useRef(null); + const buttonRef = useRef(null); + + const [editingIndex, setEditingIndex] = useState(null); + + // Handle click outside flyout + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + flyoutRef.current && + !flyoutRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + setShowAISection(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + useEffect(() => { + if (isOpen && activity) { + setTitle(activity.title || ""); + setDescription(activity.description || ""); + setHours(activity.hours || ""); + } + }, [isOpen, activity]); // Trigger when modal opens or activity changes + + // Scroll to bottom of chat + useEffect(() => { + chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const handleHoursChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (/^([1-9]|1[0-2])?$/.test(value)) { + setHours(value); + } + }; + + const handleAISuggestion = async () => { + if (!aiInput.trim()) return; + + try { + const res = await fetch("/api/generateAiResponse", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userInput: aiInput }), + }); + + const data = await res.json(); + if (data.aiResponse) { + setMessages((prev) => [...prev, data.aiResponse]); + } + } catch (error) { + console.error("Error fetching AI response:", error); + } + setAiInput(""); + }; + + const handleSubmit = () => { + console.log("Activity Submitted!"); + onClose(); + }; + + const handleDescriptionChange = (value: string) => { + setDescription(value || "


"); + }; + + const handleSendMessage = async () => { + if (aiInput.trim() === "") return; + + if (editingIndex !== null) { + const newMessages = [...messages]; + newMessages[editingIndex] = aiInput; + if (newMessages[editingIndex + 1]) { + newMessages.splice(editingIndex + 1, 1); + } + setMessages(newMessages); + setEditingIndex(null); + await handleAISuggestion(); + } else { + setMessages((prev) => [...prev, aiInput]); + await handleAISuggestion(); + } + setAiInput(""); + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + handleSendMessage(); + } + }; + + const handleUseThis = (message: string) => { + setDescription(message); + }; + + const handleRegenerate = async (index: number) => { + try { + const userMessage = messages[index - 1]; + const res = await fetch("/api/generateAiResponse", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userInput: userMessage }), + }); + + const data = await res.json(); + if (data.aiResponse) { + const newMessages = [...messages]; + newMessages[index] = data.aiResponse; + setMessages(newMessages); + } + } catch (error) { + console.error("Error regenerating response:", error); + } + }; + + const handleEditMessage = (index: number) => { + setAiInput(messages[index - 1]); + setEditingIndex(index - 1); + }; + + return ( + + + {/* Main Form Section */} +
+
+ {/* Close Button */} + +
+
+ +
+

Update Activity

+
+
+ {/* Added gray background container */} +
+ +
+
+ + setTitle(e.target.value)} + placeholder="Enter activity title" + /> +
+ +
+ + +
+ +
+
+ +
+ +
+
+ + + {/* Submit, cancel Buttons */} +
+ + +
+
+ {/* AI Trigger Button */} + + + {/* AI Flyout Menu */} + {showAISection && ( +
+
+ +
+

+ 🤖 AI Description Assistant +

+
+
+ {messages.map((msg, index) => ( +
+ {index % 2 === 0 && } + + + {msg} + {index % 2 === 0 && index !== 0 && ( // Added index !== 0 condition + +
+ {/* Use this */} + + + + + Use this message + + + {/* Regenerate */} + + + + + Regenerate + + + {/* Edit */} + + + + + Edit message + +
+
+ )} +
+
+ {index % 2 !== 0 && } +
+ ))} +
+
+ +
+ setAiInput(e.target.value)} + onKeyDown={handleKeyPress} + placeholder="Describe your activity..." + className="pr-10" // Add right padding to prevent text underlap + /> + +
+
+
+
+ )} +
+ + + + ); +}; + +export default UpdateActivity; \ No newline at end of file From d26d016772f0d6f997e1955b05d25866fa9addd7 Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Thu, 10 Apr 2025 19:41:08 +0530 Subject: [PATCH 7/8] Update navBar.tsx --- server/src/components/navBar.tsx | 163 ++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 44 deletions(-) diff --git a/server/src/components/navBar.tsx b/server/src/components/navBar.tsx index 3937d99..5155bbf 100644 --- a/server/src/components/navBar.tsx +++ b/server/src/components/navBar.tsx @@ -1,51 +1,126 @@ // components/Navbar.tsx -import React from 'react'; -import Image from 'next/image'; -import { Bell } from 'lucide-react'; +import React, { useState, useEffect, useRef } from "react"; +import Image from "next/image"; +import { Bell } from "lucide-react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import StudentDetailsPopup from "./StudentDetailsPopup"; -const Navbar = () => { - return ( -
- {/* Logo & Navigation */} -
- Logo - - -
- - {/* User Profile & Notifications */} -
- -
-
- Jhon -

Senior Mentor

-
- - - JH - +interface NavbarProps { + userType: "mentor" | "student"; // Determines dashboard type + userName: string; // User's name +} + +const Navbar: React.FC = ({ userType, userName }) => { + const [isStudentPopupOpen, setIsStudentPopupOpen] = useState(false); + const [isLogoutPopupOpen, setIsLogoutPopupOpen] = useState(false); + const logoutPopupRef = useRef(null); + + const openStudentPopup = () => setIsStudentPopupOpen(true); + const closeStudentPopup = () => setIsStudentPopupOpen(false); + const toggleLogoutPopup = () => setIsLogoutPopupOpen((prev) => !prev); + + const handleLogout = () => { + // Clear authentication data + localStorage.removeItem("authToken"); + sessionStorage.removeItem("authToken"); + document.cookie = "authToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + + // Redirect to login page + window.location.href = "/login"; + }; + + // Close logout popup when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + logoutPopupRef.current && + event.target instanceof Node && + !logoutPopupRef.current.contains(event.target) + ) { + setIsLogoutPopupOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+ {/* Logo & Navigation */} +
+ Logo + {/* Show navigation only for mentors */} + {userType === "mentor" && ( + + )} +
+ + {/* User Profile & Notifications */} +
+ +
+
+ {userName} +

+ {userType === "mentor" ? "Senior Mentor" : "Student"} +

+
+ + + {userName.charAt(0)} + + {/* Logout Popup */} + {isLogoutPopupOpen && ( +
+ +
+ + + +
+

Are you sure you want to logout?

+ +
+ + + + )} +
+
+ {/* Conditionally render the StudentDetailsPopup */} + {isStudentPopupOpen && }
-
-
- ); + ); }; export default Navbar; From 47995994f366318ce3fc65933f6185a694219544 Mon Sep 17 00:00:00 2001 From: Yasindu Induwara <148317183+YEdirisingha@users.noreply.github.com> Date: Fri, 11 Apr 2025 08:54:34 +0530 Subject: [PATCH 8/8] Add files via upload --- server/src/components/ui/tooltip.tsx | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 server/src/components/ui/tooltip.tsx diff --git a/server/src/components/ui/tooltip.tsx b/server/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..28e1918 --- /dev/null +++ b/server/src/components/ui/tooltip.tsx @@ -0,0 +1,32 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }