Skip to content

Mrx64920 - Frontend components for new student dashboard. #77

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server/.env.local
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
HUGGINGFACE_TOKEN=
4 changes: 3 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
67 changes: 67 additions & 0 deletions server/src/app/api/generateAiResponse/route.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}
20 changes: 20 additions & 0 deletions server/src/app/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
}



110 changes: 6 additions & 104 deletions server/src/app/student/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <StudentDashboard />;
}

const StudentPage: React.FC = () => {
const [session, setSession] = useState<SessionData | null>(null);
const [mentorName, setMentorName] = useState<string | null>(null);
const [mentorId, setMentorId] = useState<string | null>(null);
const [selectedUser, setSelectedUser] = useState<string | null>(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 (
<div className="gap-5 flex flex-col bg-[#f1f1f9] min-h-screen">
{/* Top Bar with Logo, Avatar, and Logout */}
<div className="flex gap-1 justify-between items-center p-4 bg-gradient-to-t from-blue-50 via-blue-75 to-blue-100 shadow-md h-[10vh] w-full max-w-[95vw] mx-auto mt-[15px] rounded-lg">
<Image
src="/logo.png"
alt="Logo"
width={200}
height={40}
className="mt-[-0px]"
/>

<div className="flex items-center gap-4 mt-[0px] relative mr-[15px]">
{/* Avatar */}
<div onClick={togglePopup} className="cursor-pointer">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>

{/* Popup Screen */}
{isPopupOpen && (
<div className="absolute top-[100%] right-0 mt-2 bg-gradient-to-t from-blue-100 via-blue-200 to-blue-300 shadow-md shadow-lg p-6 rounded-lg z-50 w-[250px]">
{/* Large Avatar */}
<div className="flex justify-center mb-4">
<Avatar className="w-24 h-24">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</div>

{/* Student Name and Email */}
<div className="text-center">
<h3 className="text-lg font-semibold">
{session ? `${session.fname} ${session.lname}` : 'Loading...'}
</h3>
<p className="text-xs text-gray-500">
{session ? session.email : 'Loading...'}
</p>
</div>

{/* Logout Button */}
<form action="/auth/logout" method="post" className="mt-4">
<Button variant="blue" className="w-full border-black">Logout</Button>
</form>
</div>
)}
</div>
</div>

<div className="flex-grow flex flex-col items-center justify-center mt-[-15px] w-full max-w-[95vw] mx-auto">
{/* Center the calendar with rounded corners */}
<div className="bg-white p-4 rounded-xl shadow-lg w-full max-w-[95vw] min-h-[60vh]">
<div className="flex justify-center items-center w-full">
<TaskCalendar selectedUser={selectedUser || ""}/>
</div>
</div>
</div>
</div>
);
};

export default StudentPage;
87 changes: 87 additions & 0 deletions server/src/components/STCalendar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex bg-gray-100 rounded-lg shadow-lg overflow-hidden max-w-lg mx-auto">
{/* Left Sidebar */}
<div className="bg-gray-700 text-white p-6 flex flex-col items-center justify-center w-1/3">
<div className="text-3xl font-bold">{today.getFullYear()}</div>
<div className="text-xl font-semibold mt-2">{today.toLocaleString("default", { month: "long" }).toUpperCase()}</div>
<div className="text-5xl font-bold mt-2">{today.getDate()}</div>
<div className="text-lg font-medium mt-2">{daysOfWeek[today.getDay()]}</div>
</div>

{/* Calendar */}
<div className="bg-white p-4 w-2/3">
{/* Header */}
<div className="flex justify-between items-center mb-3">
<button onClick={prevMonth}>
<ChevronLeft className="w-5 h-5 text-gray-600" />
</button>
<h2 className="text-lg font-semibold">
{currentDate.toLocaleString("default", { month: "long", year: "numeric" })}
</h2>
<button onClick={nextMonth}>
<ChevronRight className="w-5 h-5 text-gray-600" />
</button>
</div>

{/* Days of the week */}
<div className="grid grid-cols-7 text-center text-sm font-semibold text-gray-600 mb-2">
{daysOfWeek.map((day) => (
<div key={day} className="py-1">{day}</div>
))}
</div>

{/* Calendar Grid */}
<div className="grid grid-cols-7 gap-1">
{generateDays().map((day, index) => {
const isToday =
day === today.getDate() &&
currentDate.getMonth() === today.getMonth() &&
currentDate.getFullYear() === today.getFullYear();

return (
<div
key={index}
className={`h-10 flex items-center justify-center rounded-md text-sm cursor-pointer transition duration-200
${day ? "bg-gray-100 hover:bg-blue-500 hover:text-white" : ""}
${isToday ? "bg-purple-500 text-white font-bold" : ""}`}
>
{day || ""}
</div>
);
})}
</div>
</div>
</div>
);
};

export default Calendar;
42 changes: 42 additions & 0 deletions server/src/components/STdashboardOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";

interface DashboardOverviewProps {
userType: "mentor" | "student"; // Determines dashboard type
}

const DashboardOverview: React.FC<DashboardOverviewProps> = ({ 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 (
<div className="bg-white p-4 rounded-md shadow-md w-full max-w-[800px] h-auto">
<h2 className="text-xl font-semibold mb-4">Dashboard Overview</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3">
{dashboardData.map((item, index) => (
<div key={index} className="bg-[#E3E3E3] p-4 w-[180px] h-[120px] rounded-md shadow-md text-left flex flex-col justify-between">
<div className="flex items-center justify-between">
<div className={`text-2xl ${item.colorClass}`}>{item.icon}</div>
<div className="text-xl font-bold">{item.number}</div>
</div>
<p className="text-md font-semibold">{item.title}</p>
<p className="text-xs text-gray-500">{item.status}</p>
</div>
))}
</div>
</div>
);
};

export default DashboardOverview;
Loading