From c9318ee35bb4692eede5bca6441e567fa6951fc5 Mon Sep 17 00:00:00 2001 From: Nanvithaa Date: Mon, 7 Apr 2025 18:11:16 -0500 Subject: [PATCH 01/10] Done with sprint 1 --- client/.env | 2 +- client/README.md | 119 ---------- client/package.json | 9 +- client/public/index.html | 1 - client/src/App.js | 1 - client/src/index.js | 1 - client/src/pages/A3JobEvaluationForm.jsx | 250 ---------------------- client/src/pages/CoordinatorDashboard.js | 12 ++ client/src/pages/Home.js | 110 ++-------- client/src/pages/SignIn.js | 67 ++++++ client/src/pages/SupervisorDashboard.js | 68 ++++++ client/src/router.js | 22 +- client/src/styles/A3JobEvaluationForm.css | 58 ----- server/.env | 12 +- server/controllers/approvalController.js | 61 ++++++ server/controllers/authController.js | 27 +++ server/controllers/emailController.js | 60 ------ server/index.js | 118 ---------- server/middleware/authMiddleware.js | 11 + server/models/Evaluation.js | 40 ---- server/models/Submission.js | 10 + server/models/User.js | 28 +-- server/package.json | 20 +- server/routes/approvalRoutes.js | 10 + server/routes/authRoutes.js | 7 + server/routes/emailRoutes.js | 8 - server/server.js | 20 ++ server/services/emailService.js | 79 ------- 28 files changed, 337 insertions(+), 894 deletions(-) delete mode 100644 client/README.md delete mode 100644 client/src/pages/A3JobEvaluationForm.jsx create mode 100644 client/src/pages/CoordinatorDashboard.js create mode 100644 client/src/pages/SignIn.js create mode 100644 client/src/pages/SupervisorDashboard.js delete mode 100644 client/src/styles/A3JobEvaluationForm.css create mode 100644 server/controllers/approvalController.js create mode 100644 server/controllers/authController.js delete mode 100644 server/controllers/emailController.js delete mode 100644 server/index.js create mode 100644 server/middleware/authMiddleware.js delete mode 100644 server/models/Evaluation.js create mode 100644 server/models/Submission.js create mode 100644 server/routes/approvalRoutes.js create mode 100644 server/routes/authRoutes.js delete mode 100644 server/routes/emailRoutes.js create mode 100644 server/server.js delete mode 100644 server/services/emailService.js diff --git a/client/.env b/client/.env index be3740476..cfa1dac9b 100644 --- a/client/.env +++ b/client/.env @@ -1,2 +1,2 @@ -REACT_APP_API_URL=http://localhost:5001 +REACT_APP_API_URL=http://localhost:5000 REACT_APP_ENV=development \ No newline at end of file diff --git a/client/README.md b/client/README.md deleted file mode 100644 index 58332a95f..000000000 --- a/client/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Internship Program Management System (IPMS) - -## πŸ“‹ A.3 Job Performance Evaluation Form - -This module allows internship advisors and coordinators to fill out an evaluation form with: - -- Performance ratings (Satisfactory / Unsatisfactory) -- Comments per category -- Digital signature support (drawn or typed) - ---- - -## How to Run This App (Frontend Only) - -1. Open terminal and navigate to the root of the project. - -2. Move into the React frontend folder: - -```bash -cd client -``` - -3. Install all required frontend dependencies: - -```bash -npm install -``` - -This installs: -- `react` -- `react-bootstrap` -- `bootstrap` -- `react-signature-canvas` -*(These are declared in `client/package.json`)* - -4. Start the React development server: - -```bash -npm start -``` - ---- - -## 🌐 Open in Your Browser - -After `npm start`, your browser will open to: - -``` -http://localhost:3000/ -``` - - **Change the URL to:** - -``` -http://localhost:3000/evaluation -``` - -This will load the **A.3 Job Performance Evaluation Form** page. - ---- - -## Dependencies Used - -These libraries are already included in the project: - -| Library | Purpose | -|------------------------|----------------------------| -| `react-bootstrap` | UI components | -| `bootstrap` | Styling and layout | -| `react-signature-canvas` | Signature input support | -| `react-router-dom` | Routing between pages | - ---- - -## πŸ’‘ Notes for New Developers - -- You **must run `npm install` inside the `client/` folder**. -- You **do not need to install anything globally or separately**. -- If you run from the root folder, be sure to `cd client` before installing or starting. - ---- - -πŸ“ Viewing Signatures from the Database - -If you're checking stored evaluation data in a database tool (like Studio 3T), you might see a Base64 string under the signature field. It looks something like this: -"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAA..." - -πŸ‘‰ How to View the Drawn Signature: - -Right-click the value field in the database view. -Choose Copy β†’ Copy Value. -Paste the copied string into your web browser’s address bar. -Important: Remove the surrounding double quotes (") before hitting Enter. -The browser will render and display the signature image. - - -## File Structure (Frontend) - -``` -client/ -β”œβ”€β”€ public/ -β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ pages/ -β”‚ β”‚ └── A3JobEvaluationForm.jsx -β”‚ β”œβ”€β”€ styles/ -β”‚ β”‚ └── A3JobEvaluationForm.css -β”‚ β”œβ”€β”€ index.js -β”‚ └── App.js / router.js -β”œβ”€β”€ package.json -└── README.md -``` - ---- - -## You're all set! - -If you follow the steps above, the evaluation form should be up and running β€” no additional setup needed. - -For help, contact the module owner or contributor who integrated the evaluation form. diff --git a/client/package.json b/client/package.json index 1e86de512..0d1b9b22f 100644 --- a/client/package.json +++ b/client/package.json @@ -8,13 +8,10 @@ "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.8.2", - "bootstrap": "^5.3.5", "react": "^19.0.0", - "react-bootstrap": "^2.10.9", - "react-dom": "^19.0.0", - "react-router-dom": "^7.4.1", - "react-scripts": "5.0.1", - "react-signature-canvas": "^1.1.0-alpha.2", +"react-dom": "^19.0.0", +"react-router-dom": "^7.3.0", +"react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/client/public/index.html b/client/public/index.html index 873d55812..503f8bbae 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -7,7 +7,6 @@ IPMS - Internship Program Management System - diff --git a/client/src/App.js b/client/src/App.js index aec9d81c4..7d7a8c498 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,4 +1,3 @@ -import React from 'react'; import { RouterProvider } from "react-router-dom"; import router from "./router"; import "./styles/App.css"; diff --git a/client/src/index.js b/client/src/index.js index 37d1eeaf0..d136d245c 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -1,7 +1,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; -import 'bootstrap/dist/css/bootstrap.min.css'; import "./styles/index.css"; const root = ReactDOM.createRoot(document.getElementById("root")); diff --git a/client/src/pages/A3JobEvaluationForm.jsx b/client/src/pages/A3JobEvaluationForm.jsx deleted file mode 100644 index 72f3c26d0..000000000 --- a/client/src/pages/A3JobEvaluationForm.jsx +++ /dev/null @@ -1,250 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Form, Button, Container, Row, Col, Table, Modal, Tab, Nav } from 'react-bootstrap'; -import SignatureCanvas from 'react-signature-canvas'; -import '../styles/A3JobEvaluationForm.css'; - -// Fonts used for styled signature typing -const fonts = ['Pacifico', 'Indie Flower', 'Dancing Script', 'Great Vibes', 'Satisfy']; - -// Evaluation criteria items -const evaluationItems = [ - 'Task Execution and Quality', - 'Initiative and Proactiveness', - 'Communication and Collaboration', - 'Time Management and Dependability', - 'Problem Solving and Critical Thinking', - 'Creativity and Innovation', - 'Technical and Industry Specific Skills', - 'Work Ethic and Cultural Fit', - 'Feedback Reception and Implementation' -]; - - -const A3JobEvaluationForm = () => { - // Form state management - const [formData, setFormData] = useState({ - advisorSignature: '', - advisorAgreement: false, - coordinatorSignature: '', - coordinatorAgreement: false, - }); - - // Ratings and comments - const [ratings, setRatings] = useState({}); - const [comments, setComments] = useState({}); - - // Modal state - const [showModal, setShowModal] = useState(false); - const [activeSignatureTarget, setActiveSignatureTarget] = useState('advisor'); - const [typedSignatures, setTypedSignatures] = useState({ advisor: '', coordinator: '' }); - const [selectedFont, setSelectedFont] = useState(fonts[0]); - const [activeTab, setActiveTab] = useState('type'); - - // Signature canvas ref - const sigCanvasRef = useRef(null); - - - // Clear typed signature if tab switches to "type" - useEffect(() => { - if (activeTab === 'type') { - setTypedSignatures(prev => ({ ...prev, [activeSignatureTarget]: '' })); - } - }, [activeSignatureTarget, showModal, activeTab]); - - // Handle form input changes - const handleChange = (field, value) => { - setFormData(prev => ({ ...prev, [field]: value })); - }; - - // Rating selection - const handleRatingChange = (item, value) => { - setRatings(prev => ({ ...prev, [item]: value })); - }; - - // Comment box - const handleCommentChange = (item, value) => { - setComments(prev => ({ ...prev, [item]: value })); - }; - - // Handle inserting signature from modal - const handleSignatureInsert = () => { - const targetField = activeSignatureTarget === 'advisor' ? 'advisorSignature' : 'coordinatorSignature'; - if (activeTab === 'type' && typedSignatures[activeSignatureTarget].trim()) { - //handleChange(targetField, JSON.stringify({ type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont })); - handleChange(targetField, { type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont }); - setShowModal(false); - } else if (activeTab === 'draw') { - const canvas = sigCanvasRef.current; - if (canvas && !canvas.isEmpty()) { - let trimmedCanvas; - try { - trimmedCanvas = canvas.getTrimmedCanvas(); - } catch (err) { - console.warn("getTrimmedCanvas() failed, using full canvas instead."); - trimmedCanvas = canvas.getCanvas(); - } - const signatureData = trimmedCanvas.toDataURL('image/png'); - //handleChange(targetField, JSON.stringify({ type: 'draw', value: signatureData })); - handleChange(targetField, { type: 'draw', value: signatureData }) - setShowModal(false); - } else { - alert("Please draw your signature before inserting."); - } - } - }; - - // Submit the form to the backend - const handleSubmit = async (e) => { - e.preventDefault(); - if (!formData.advisorAgreement || !formData.coordinatorAgreement) { - alert('Please confirm both signature agreements before submitting.'); - return; - } - try { - const response = await fetch('http://localhost:5001/api/evaluation', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ formData, ratings, comments }), - }); - if (response.ok) { - alert('Evaluation submitted successfully!'); - setFormData({ advisorSignature: '', advisorAgreement: false, coordinatorSignature: '', coordinatorAgreement: false }); - setRatings({}); - setComments({}); - setTypedSignatures({ advisor: '', coordinator: '' }); - sigCanvasRef.current?.clear(); - } else { - const err = await response.json(); - console.error("Backend returned error:", err); - alert(`Submission failed: ${err.error}`); - } - } catch (err) { - alert('Server error. Please try again.'); - console.error(err); - } - }; - -// Show preview of signature (text or image) -const renderSignaturePreview = (field) => { - if (!formData[field]) { - return Click to sign; - } - - let sig = formData[field]; - if (typeof sig === 'string') { - try { - sig = JSON.parse(sig); - } catch (err) { - return Invalid signature format; - } - } - - if (sig.type === 'draw') { - return Signature; - } - if (sig.type === 'text') { - return {sig.value}; - } - - return Unknown signature type; - }; - - return ( -
-

A.3 – Job Performance Evaluation

- -
- - - - - - {evaluationItems.map((item, index) => ( - - - - - - - ))} - -
ItemSatisfactoryUnsatisfactoryComments
{item} handleRatingChange(item, 'Satisfactory')} required /> handleRatingChange(item, 'Unsatisfactory')} /> handleCommentChange(item, e.target.value)} placeholder="Enter comments" style={{ minWidth: '250px' }} />
- - {/* Signature section */} - - - - Internship Advisor Signature -
{ setActiveSignatureTarget('advisor'); setShowModal(true); }}> - {renderSignaturePreview('advisorSignature')} -
- handleChange('advisorAgreement', e.target.checked)} required /> -
- - - - Internship Coordinator Signature -
{ setActiveSignatureTarget('coordinator'); setShowModal(true); }}> - {renderSignaturePreview('coordinatorSignature')} -
- handleChange('coordinatorAgreement', e.target.checked)} required /> -
- -
- - {/* Submit button */} -
- -
-
-
- - {/* Signature Modal */} - setShowModal(false)} centered dialogClassName="custom-signature-modal"> - -
-
Sign Here
- -
- - - - - setTypedSignatures(prev => ({ ...prev, [activeSignatureTarget]: e.target.value }))} className="mb-3" /> -
- {fonts.map(font => ( -
setSelectedFont(font)} style={{ cursor: 'pointer', fontFamily: font, padding: '10px 15px', border: font === selectedFont ? '2px solid #9d2235' : '1px solid #ccc', borderRadius: '10px', fontSize: '24px', backgroundColor: '#fff' }}> - {typedSignatures[activeSignatureTarget] || 'Your name'} -
- ))} -
-
- -
-
- - {/* Draw tab */} - -
-
Draw here
- -
- - -
-
-
-
-
-
-
-
- ); -}; - -export default A3JobEvaluationForm; - - diff --git a/client/src/pages/CoordinatorDashboard.js b/client/src/pages/CoordinatorDashboard.js new file mode 100644 index 000000000..082ff2478 --- /dev/null +++ b/client/src/pages/CoordinatorDashboard.js @@ -0,0 +1,12 @@ +import React from "react"; + +const CoordinatorDashboard = () => { + return ( +
+

Coordinator Dashboard

+

Welcome, Coordinator!

+
+ ); +}; + +export default CoordinatorDashboard; \ No newline at end of file diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index 6068e1027..7dd2e8b6f 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -1,102 +1,18 @@ -import React from 'react'; -import { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; -import "../styles/App.css"; - -function Home() { - const navigate = useNavigate(); - const [formData, setFormData] = useState({ - email: "", - password: "", - role: "Student", - }); - - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData({ - ...formData, - [name]: value, - }); - }; - - const handleSubmit = (e) => { - e.preventDefault(); - console.log(`${formData.role} sign in attempted`, formData); - }; +import React from "react"; +import { Link } from "react-router-dom"; +const Home = () => { return ( -
-
-
- Student at computer -
- -
-

Sign in to continue

- -
-
- - -
- -
- - -
- -
- - -
- - -
- -
- Don't have an account? - { - e.preventDefault(); - navigate("/signup"); - }} - > - {" "} - Sign Up - -
-
-
+
+

Welcome to the Internship Portal

+

This is the homepage.

+ + +
); -} +}; -export default Home; +export default Home; \ No newline at end of file diff --git a/client/src/pages/SignIn.js b/client/src/pages/SignIn.js new file mode 100644 index 000000000..7e8d48217 --- /dev/null +++ b/client/src/pages/SignIn.js @@ -0,0 +1,67 @@ +import React, { useState } from "react"; +import axios from "axios"; +import { useNavigate } from "react-router-dom"; + +const SignIn = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [role, setRole] = useState("Supervisor"); + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + console.log("Login button clicked"); + + try { + const response = await axios.post("http://localhost:5000/api/login", { + email, + password, + role, + }); + + console.log("Login Success:", response.data); + alert("Login successful"); + + if (role === "Supervisor") navigate("/supervisor-dashboard"); + else if (role === "Coordinator") navigate("/coordinator-dashboard"); + else navigate("/"); + + } catch (error) { + console.error("Login failed:", error); + alert("Login failed"); + } + }; + + return ( +
+

Sign In

+
+ +

+ setEmail(e.target.value)} + required + />

+ setPassword(e.target.value)} + required + />

+ +
+
+ ); +}; + +export default SignIn; \ No newline at end of file diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js new file mode 100644 index 000000000..9e5ff05db --- /dev/null +++ b/client/src/pages/SupervisorDashboard.js @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from "react"; +import axios from "axios"; + +const SupervisorDashboard = () => { + const [submissions, setSubmissions] = useState([]); + + useEffect(() => { + fetchPendingSubmissions(); + }, []); + + const fetchPendingSubmissions = async () => { + try { + const response = await axios.get("http://localhost:5000/api/submissions/pending"); + setSubmissions(response.data); + } catch (err) { + console.error("Error fetching submissions:", err); + } + }; + + const handleDecision = async (id, action) => { + try { + const endpoint = `http://localhost:5000/api/submissions/${id}/${action}`; + await axios.post(endpoint); + alert(`Submission ${action}d successfully!`); + fetchPendingSubmissions(); // refresh list + } catch (err) { + console.error("Error updating submission:", err); + } + }; + + return ( +
+

Supervisor Dashboard

+

Welcome, Supervisor!

+ +

Pending Submissions

+ {submissions.length === 0 ? ( +

No pending submissions.

+ ) : ( + + + + + + + + + + + {submissions.map((submission) => ( + + + + + + + ))} + +
IDTitleSubmitted ByAction
{submission._id}{submission.title || "N/A"}{submission.studentName || "N/A"} + {" "} + +
+ )} +
+ ); +}; + +export default SupervisorDashboard; \ No newline at end of file diff --git a/client/src/router.js b/client/src/router.js index 3c0852d50..70e8c082e 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -1,6 +1,3 @@ -import React from 'react'; - - import { createBrowserRouter } from "react-router-dom"; // Layout @@ -10,7 +7,9 @@ import Layout from "./components/Layout"; import Home from "./pages/Home"; import SignUp from "./pages/SignUp"; import NotFound from "./pages/NotFound"; -import A3JobEvaluationForm from "./pages/A3JobEvaluationForm"; +import SupervisorDashboard from "./pages/SupervisorDashboard"; +import CoordinatorDashboard from "./pages/CoordinatorDashboard"; +import SignIn from "./pages/SignIn"; // Create and export the router configuration const router = createBrowserRouter([ @@ -27,11 +26,20 @@ const router = createBrowserRouter([ path: "signup", element: , }, - // Add more routes as needed { - path: "evaluation", - element: , + path: "signin", + element: , + }, + { + path: "supervisor-dashboard", + element: , + }, + { + path: "coordinator-dashboard", + element: , + }, + // Add more routes as needed ], }, ]); diff --git a/client/src/styles/A3JobEvaluationForm.css b/client/src/styles/A3JobEvaluationForm.css deleted file mode 100644 index 21b709b19..000000000 --- a/client/src/styles/A3JobEvaluationForm.css +++ /dev/null @@ -1,58 +0,0 @@ -/* Custom color for checked Bootstrap radio button */ -/* .form-check-input:checked { - background-color: #9d2235; - border-color: #9d2235; - accent-color: #9d2235; -} */ -.custom-table input[type="radio"]:checked { - background-color: #9d2235; - border-color: #9d2235; - box-shadow: 0 0 0 1px #9d2235; -} - - - -.custom-table thead th { - color: white !important; - background-color: #9d2235 -} - - -.form-check-input:focus { - box-shadow: 0 0 0 0.2rem rgba(157, 34, 53, 0.25); -} - -.heading-maroon { - color: #9d2235; - font-size: 1.8rem; - font-weight: 700; - font-family: 'Poppins', 'Segoe UI', sans-serif; - text-align: center; -} - -.signature-preview { - font-size: 28px; - padding: 8px 12px; - border: 2px solid #ccc; - border-radius: 10px; - cursor: pointer; - margin: 8px 0; - transition: all 0.3s ease; -} -.signature-preview.selected { - border-color: #9d2235; - background-color: #f9f3f3; -} - -.signature-font-1 { font-family: 'Great Vibes', cursive; } -.signature-font-2 { font-family: 'Pacifico', cursive; } -.signature-font-3 { font-family: 'Satisfy', cursive; } - -.page-content { /* to adjust content display */ - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - min-height: 100vh; - padding: 10px; -} \ No newline at end of file diff --git a/server/.env b/server/.env index 950bffcc5..e2787b6c6 100644 --- a/server/.env +++ b/server/.env @@ -1,12 +1,2 @@ -# Server Configuration -PORT=5001 MONGO_URI=mongodb://localhost:27017/IPMS - -# Email Configuration -EMAIL_HOST=smtp.gmail.com -EMAIL_PORT=587 -EMAIL_SECURE=false -EMAIL_USER=your-email@gmail.com -EMAIL_PASSWORD=your-app-password -EMAIL_DEFAULT_SENDER=Internship Program Management System - +PORT=5000 diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js new file mode 100644 index 000000000..bc568b1d5 --- /dev/null +++ b/server/controllers/approvalController.js @@ -0,0 +1,61 @@ +const Submission = require("../models/Submission"); + +// βœ… Get pending submissions for supervisor +exports.getPendingSubmissions = async (req, res) => { + try { + const submissions = await Submission.find({ status: "pending" }); + res.json(submissions); + } catch (err) { + res.status(500).json({ message: "Failed to fetch pending submissions", error: err }); + } +}; + +// βœ… Supervisor Approves +exports.approveSubmission = async (req, res) => { + const { id } = req.params; + + try { + const submission = await Submission.findByIdAndUpdate( + id, + { status: "approved_by_supervisor" }, + { new: true } + ); + + if (!submission) { + return res.status(404).json({ message: "Submission not found" }); + } + + res.json({ + message: "Submission approved and forwarded to Coordinator", + updatedSubmission: submission + }); + + } catch (err) { + res.status(500).json({ message: "Approval failed", error: err }); + } +}; + +// ❌ Supervisor Rejects +exports.rejectSubmission = async (req, res) => { + const { id } = req.params; + + try { + const submission = await Submission.findByIdAndUpdate( + id, + { status: "rejected_by_supervisor" }, + { new: true } + ); + + if (!submission) { + return res.status(404).json({ message: "Submission not found" }); + } + + res.json({ + message: "Submission rejected", + updatedSubmission: submission + }); + + } catch (err) { + res.status(500).json({ message: "Rejection failed", error: err }); + } +}; \ No newline at end of file diff --git a/server/controllers/authController.js b/server/controllers/authController.js new file mode 100644 index 000000000..115af0238 --- /dev/null +++ b/server/controllers/authController.js @@ -0,0 +1,27 @@ +const User = require("../models/User"); + +exports.login = async (req, res) => { + const { email, password, role } = req.body; + + try { + console.log("Login attempt:", { email, password, role }); + + const user = await User.findOne({ + Email: { $regex: new RegExp("^" + email + "$", "i") }, // case-insensitive + Password: password, + role: role, + }); + + if (!user) { + console.log("Login failed: user not found"); + return res.status(401).json({ message: "Invalid credentials" }); + } + + console.log("Login success:", user.Email); + res.json({ message: "Login successful", user }); + + } catch (err) { + console.error("Login error:", err.message); + res.status(500).json({ message: "Login failed", error: err.message }); + } +}; \ No newline at end of file diff --git a/server/controllers/emailController.js b/server/controllers/emailController.js deleted file mode 100644 index 8f5b4e4cc..000000000 --- a/server/controllers/emailController.js +++ /dev/null @@ -1,60 +0,0 @@ -const emailService = require("../services/emailService"); - -/** - * Email Controller for handling email-related routes - */ -const emailController = { - /** - * Send an email with custom content - * Allows users to trigger emails on demand with their own content - */ - sendEmail: async (req, res) => { - try { - const { to, subject, html, text, from, attachments, cc, bcc } = req.body; - - // Validate required fields - if (!to || !subject || !html) { - return res.status(400).json({ - success: false, - message: - "Required fields missing: to, subject, and html content are required", - }); - } - - // Send the email with all provided options - const result = await emailService.sendEmail({ - to, - subject, - html, - text, - from, - attachments, - cc, - bcc, - }); - - if (result.success) { - return res.status(200).json({ - success: true, - message: "Email sent successfully", - messageId: result.messageId, - }); - } else { - return res.status(500).json({ - success: false, - message: "Failed to send email", - error: result.error, - }); - } - } catch (error) { - console.error("Error in sendEmail controller:", error); - return res.status(500).json({ - success: false, - message: "Internal server error", - error: error.message, - }); - } - }, -}; - -module.exports = emailController; diff --git a/server/index.js b/server/index.js deleted file mode 100644 index dd3c5643a..000000000 --- a/server/index.js +++ /dev/null @@ -1,118 +0,0 @@ -const express = require("express"); -const Evaluation = require("./models/Evaluation"); -const mongoose = require("mongoose"); -const cors = require("cors"); -const User = require("./models/User"); // Import User model -require("dotenv").config(); - -// Import routes -const emailRoutes = require("./routes/emailRoutes"); - -const app = express(); -app.use(express.json()); -app.use(cors()); - -// MongoDB Configuration -const mongoConfig = { - serverSelectionTimeoutMS: 5000, - autoIndex: true, - maxPoolSize: 10, - serverSelectionTimeoutMS: 5000, - socketTimeoutMS: 45000, - family: 4, -}; - -// MongoDB Local Connection -mongoose - .connect(process.env.MONGO_URI, mongoConfig) - .then(() => { - console.log("Connected to Local MongoDB"); - }) - .catch((err) => { - console.error("MongoDB Connection Error:", err); - process.exit(1); // Exit if cannot connect to database - }); - -// Handle MongoDB connection errors after initial connection -mongoose.connection.on("error", (err) => { - console.error("MongoDB error after initial connection:", err); -}); - -mongoose.connection.on("disconnected", () => { - console.log("Lost MongoDB connection..."); - // Attempt to reconnect - if (!mongoose.connection.readyState) { - mongoose - .connect(process.env.MONGO_URI, mongoConfig) - .then(() => console.log("Reconnected to MongoDB")) - .catch((err) => console.error("Error reconnecting to MongoDB:", err)); - } -}); - -// Basic Route -app.get("/", (req, res) => { - res.send("IPMS Backend Running"); -}); - -// Test Communication Route -app.get("/api/message", (req, res) => { - res.json({ message: "Hello from the backend!" }); -}); - -// Register routes -app.use("/api/email", emailRoutes); - -// Create User Endpoint -app.post("/api/createUser", async (req, res) => { - try { - const { userName, email, password, role } = req.body; - const user = new User({ userName, email, password, role }); - await user.save(); - console.log("New user created:", JSON.stringify(user)); - res.status(201).json({ message: "User created successfully", user }); - } catch (error) { - console.error("Error creating user:", error); - res.status(500).json({ - message: "Failed to create user", - error: error.message, - }); - } -}); - -// Submit Evaluation Form -app.post("/api/evaluation", async (req, res) => { - try { - const { formData, ratings, comments } = req.body; - - const evaluations = Object.keys(ratings).map(category => ({ - category, - rating: ratings[category], - comment: comments[category] || '' - })); - - const newEvaluation = new Evaluation({ - advisorSignature: formData.advisorSignature, - advisorAgreement: formData.advisorAgreement, - coordinatorSignature: formData.coordinatorSignature, - coordinatorAgreement: formData.coordinatorAgreement, - evaluations - }); - - await newEvaluation.save(); - res.status(201).json({ message: "Evaluation saved successfully!" }); - } catch (error) { - console.error("Error saving evaluation:", error); - res.status(500).json({ error: "Failed to save evaluation" }); - } -}); - -// Graceful shutdown -process.on("SIGINT", () => { - mongoose.connection.close(() => { - console.log("MongoDB connection closed through app termination"); - process.exit(0); - }); -}); - -const PORT = process.env.PORT || 5000; -app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); diff --git a/server/middleware/authMiddleware.js b/server/middleware/authMiddleware.js new file mode 100644 index 000000000..adef8663d --- /dev/null +++ b/server/middleware/authMiddleware.js @@ -0,0 +1,11 @@ +exports.isSupervisor = (req, res, next) => { + // const supervisor = Sup.find({$id: username}) + + + req.user = { role: 'supervisor' }; // Mocking user role for demo + if (req.user.role === "supervisor") { + next(); + } else { + res.status(403).json({ message: "Access denied. Not a supervisor." }); + } +}; \ No newline at end of file diff --git a/server/models/Evaluation.js b/server/models/Evaluation.js deleted file mode 100644 index d104353f5..000000000 --- a/server/models/Evaluation.js +++ /dev/null @@ -1,40 +0,0 @@ -const mongoose = require('mongoose'); - -const signatureSchema = new mongoose.Schema({ - type: { type: String, enum: ['text', 'draw'], required: true }, - value: { type: String, required: true }, - font: { type: String } -}, { _id: false }); - -const evaluationItemSchema = new mongoose.Schema({ - category: { - type: String, - required: true, - }, - rating: { - type: String, - enum: ['Satisfactory', 'Unsatisfactory'], - required: true - }, - comment: { type: String, maxlength: 500 } -}, { _id: false }); - -const evaluationSchema = new mongoose.Schema({ - interneeId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: false }, - internshipId: { type: mongoose.Schema.Types.ObjectId, ref: 'Internship', required: false }, - - evaluations: { - type: [evaluationItemSchema], - validate: [arr => arr.length > 0, 'At least one evaluation item is required'] - }, - - advisorSignature: { type: signatureSchema, required: true }, - advisorAgreement: { type: Boolean, required: true }, - coordinatorSignature: { type: signatureSchema, required: true }, - coordinatorAgreement: { type: Boolean, required: true } - -}, { timestamps: true }); - -evaluationSchema.index({ interneeId: 1, internshipId: 1 }); - -module.exports = mongoose.model('Evaluation', evaluationSchema); diff --git a/server/models/Submission.js b/server/models/Submission.js new file mode 100644 index 000000000..3ced42c19 --- /dev/null +++ b/server/models/Submission.js @@ -0,0 +1,10 @@ +const mongoose = require("mongoose"); + +const submissionSchema = new mongoose.Schema({ + studentName: { type: String, required: true }, + internshipDetails: { type: String, required: true }, + status: { type: String, default: "pending" }, + supervisorComment: { type: String } +}, { timestamps: true }); + +module.exports = mongoose.model("Submission", submissionSchema); \ No newline at end of file diff --git a/server/models/User.js b/server/models/User.js index 387fe614c..3c18f77c2 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -1,27 +1,9 @@ const mongoose = require("mongoose"); const userSchema = new mongoose.Schema({ - userName: { - type: String, - required: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, - role: { - type: String, - required: true, - }, - createdAt: { - type: Date, - default: Date.now, - }, -}); + Email: { type: String, required: true, unique: true }, + Password: { type: String, required: true }, + role: { type: String, required: true } +}, { timestamps: true }); -module.exports = mongoose.model("User", userSchema); +module.exports = mongoose.model("User", userSchema); \ No newline at end of file diff --git a/server/package.json b/server/package.json index e67a4b22b..50021c22d 100644 --- a/server/package.json +++ b/server/package.json @@ -1,22 +1,14 @@ { - "name": "server", + "name": "groupe-backend", "version": "1.0.0", - "description": "", - "main": "index.js", + "main": "server.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node index.js", - "debug": "node --inspect index.js" + "start": "node server.js" }, - "keywords": [], - "author": "", - "license": "ISC", "dependencies": { "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "mongodb": "^6.14.2", - "mongoose": "^8.12.1", - "nodemailer": "^6.10.0" + "dotenv": "^16.0.3", + "express": "^4.18.2", + "mongoose": "^7.0.3" } } diff --git a/server/routes/approvalRoutes.js b/server/routes/approvalRoutes.js new file mode 100644 index 000000000..0d8211b83 --- /dev/null +++ b/server/routes/approvalRoutes.js @@ -0,0 +1,10 @@ +const express = require("express"); +const router = express.Router(); +const { getPendingSubmissions, approveSubmission, rejectSubmission } = require("../controllers/approvalController"); +const { isSupervisor } = require("../middleware/authMiddleware"); + +router.get("/submissions/pending", isSupervisor, getPendingSubmissions); +router.post("/submissions/:id/approve", isSupervisor, approveSubmission); +router.post("/submissions/:id/reject", isSupervisor, rejectSubmission); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js new file mode 100644 index 000000000..ce620a1c5 --- /dev/null +++ b/server/routes/authRoutes.js @@ -0,0 +1,7 @@ +const express = require("express"); +const router = express.Router(); +const { login } = require("../controllers/authController"); + +router.post("/login", login); + +module.exports = router; \ No newline at end of file diff --git a/server/routes/emailRoutes.js b/server/routes/emailRoutes.js deleted file mode 100644 index 34aa969bd..000000000 --- a/server/routes/emailRoutes.js +++ /dev/null @@ -1,8 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const emailController = require("../controllers/emailController"); - -// Route to send custom emails -router.post("/send", emailController.sendEmail); - -module.exports = router; diff --git a/server/server.js b/server/server.js new file mode 100644 index 000000000..40ba4214d --- /dev/null +++ b/server/server.js @@ -0,0 +1,20 @@ +const express = require("express"); +const cors = require("cors"); +const mongoose = require("mongoose"); +const approvalRoutes = require("./routes/approvalRoutes"); +const authRoutes = require("./routes/authRoutes"); +require("dotenv").config(); + +const app = express(); +app.use(cors()); +app.use(express.json()); + +mongoose.connect(process.env.MONGO_URI) + .then(() => console.log("MongoDB Connected")) + .catch((err) => console.error(err)); + +app.use("/api", approvalRoutes); +app.use("/api", authRoutes); + +const PORT = process.env.PORT || 5000; +app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); \ No newline at end of file diff --git a/server/services/emailService.js b/server/services/emailService.js deleted file mode 100644 index 0e03ced01..000000000 --- a/server/services/emailService.js +++ /dev/null @@ -1,79 +0,0 @@ -const nodemailer = require("nodemailer"); -require("dotenv").config(); - -/** - * Simple Email Service for the Internship Management System - */ -class EmailService { - constructor() { - // Create transporter using SMTP transport - this.transporter = nodemailer.createTransport({ - host: process.env.EMAIL_HOST || "smtp.gmail.com", - port: process.env.EMAIL_PORT || 587, - secure: process.env.EMAIL_SECURE === "true", - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASSWORD, - }, - }); - - this.defaultSender = - process.env.EMAIL_DEFAULT_SENDER || - "Internship Program Management System "; - } - - /** - * Send an email with custom content - * @param {Object} options - Email options - * @param {string} options.to - Recipient email(s) - * @param {string} options.subject - Email subject - * @param {string} options.html - HTML content of the email - * @param {string} [options.text] - Plain text version (optional) - * @param {string} [options.from] - Sender email (defaults to system default) - * @param {Array} [options.attachments] - Array of attachment objects - * @param {Array} [options.cc] - Carbon copy recipients - * @param {Array} [options.bcc] - Blind carbon copy recipients - * @returns {Promise} - Result of the email sending operation - */ - async sendEmail(options) { - try { - if (!options.to || !options.subject || !options.html) { - return { - success: false, - error: - "Missing required fields: to, subject, and html content are required", - }; - } - - const mailOptions = { - from: options.from || this.defaultSender, - to: options.to, - subject: options.subject, - html: options.html, - text: options.text || options.html.replace(/<[^>]*>/g, ""), - attachments: options.attachments || [], - }; - - // Add optional fields if provided - if (options.cc) mailOptions.cc = options.cc; - if (options.bcc) mailOptions.bcc = options.bcc; - - const info = await this.transporter.sendMail(mailOptions); - console.log("Email sent successfully:", info.messageId); - return { - success: true, - messageId: info.messageId, - }; - } catch (error) { - console.error("Error sending email:", error); - return { - success: false, - error: error.message, - }; - } - } -} - -// Create and export a singleton instance -const emailService = new EmailService(); -module.exports = emailService; From 09b3eddf53c911b01d49bb06413a95c61a46ff97 Mon Sep 17 00:00:00 2001 From: Hozen Date: Tue, 8 Apr 2025 15:23:07 -0500 Subject: [PATCH 02/10] resolve compilation errors --- client/.env | 2 +- client/src/pages/Home.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/.env b/client/.env index cfa1dac9b..be3740476 100644 --- a/client/.env +++ b/client/.env @@ -1,2 +1,2 @@ -REACT_APP_API_URL=http://localhost:5000 +REACT_APP_API_URL=http://localhost:5001 REACT_APP_ENV=development \ No newline at end of file diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index 01d8996b9..2e8b87c46 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -31,7 +31,6 @@ function Home() { console.log(`${formData.role} sign in attempted`, formData); }; -const Home = () => { return (
From 946eb76c23d3c08174c3a8276a95bb9142413e97 Mon Sep 17 00:00:00 2001 From: Hozen Date: Tue, 8 Apr 2025 15:42:27 -0500 Subject: [PATCH 03/10] Resolve files deleted and modified for nothing --- client/README.md | 119 ++++++++++ client/public/index.html | 1 + client/src/App.js | 1 + client/src/index.js | 1 + client/src/pages/A3JobEvaluationForm.jsx | 250 ++++++++++++++++++++++ client/src/pages/Home.js | 137 +++--------- client/src/router.js | 5 + client/src/styles/A3JobEvaluationForm.css | 58 +++++ server/.env | 11 +- server/models/Evaluation.js | 40 ++++ server/package.json | 16 +- server/server.js | 20 -- 12 files changed, 519 insertions(+), 140 deletions(-) create mode 100644 client/README.md create mode 100644 client/src/pages/A3JobEvaluationForm.jsx create mode 100644 client/src/styles/A3JobEvaluationForm.css create mode 100644 server/models/Evaluation.js delete mode 100644 server/server.js diff --git a/client/README.md b/client/README.md new file mode 100644 index 000000000..58332a95f --- /dev/null +++ b/client/README.md @@ -0,0 +1,119 @@ +# Internship Program Management System (IPMS) + +## πŸ“‹ A.3 Job Performance Evaluation Form + +This module allows internship advisors and coordinators to fill out an evaluation form with: + +- Performance ratings (Satisfactory / Unsatisfactory) +- Comments per category +- Digital signature support (drawn or typed) + +--- + +## How to Run This App (Frontend Only) + +1. Open terminal and navigate to the root of the project. + +2. Move into the React frontend folder: + +```bash +cd client +``` + +3. Install all required frontend dependencies: + +```bash +npm install +``` + +This installs: +- `react` +- `react-bootstrap` +- `bootstrap` +- `react-signature-canvas` +*(These are declared in `client/package.json`)* + +4. Start the React development server: + +```bash +npm start +``` + +--- + +## 🌐 Open in Your Browser + +After `npm start`, your browser will open to: + +``` +http://localhost:3000/ +``` + + **Change the URL to:** + +``` +http://localhost:3000/evaluation +``` + +This will load the **A.3 Job Performance Evaluation Form** page. + +--- + +## Dependencies Used + +These libraries are already included in the project: + +| Library | Purpose | +|------------------------|----------------------------| +| `react-bootstrap` | UI components | +| `bootstrap` | Styling and layout | +| `react-signature-canvas` | Signature input support | +| `react-router-dom` | Routing between pages | + +--- + +## πŸ’‘ Notes for New Developers + +- You **must run `npm install` inside the `client/` folder**. +- You **do not need to install anything globally or separately**. +- If you run from the root folder, be sure to `cd client` before installing or starting. + +--- + +πŸ“ Viewing Signatures from the Database + +If you're checking stored evaluation data in a database tool (like Studio 3T), you might see a Base64 string under the signature field. It looks something like this: +"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAA..." + +πŸ‘‰ How to View the Drawn Signature: + +Right-click the value field in the database view. +Choose Copy β†’ Copy Value. +Paste the copied string into your web browser’s address bar. +Important: Remove the surrounding double quotes (") before hitting Enter. +The browser will render and display the signature image. + + +## File Structure (Frontend) + +``` +client/ +β”œβ”€β”€ public/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ pages/ +β”‚ β”‚ └── A3JobEvaluationForm.jsx +β”‚ β”œβ”€β”€ styles/ +β”‚ β”‚ └── A3JobEvaluationForm.css +β”‚ β”œβ”€β”€ index.js +β”‚ └── App.js / router.js +β”œβ”€β”€ package.json +└── README.md +``` + +--- + +## You're all set! + +If you follow the steps above, the evaluation form should be up and running β€” no additional setup needed. + +For help, contact the module owner or contributor who integrated the evaluation form. diff --git a/client/public/index.html b/client/public/index.html index 503f8bbae..873d55812 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -7,6 +7,7 @@ IPMS - Internship Program Management System + diff --git a/client/src/App.js b/client/src/App.js index 7d7a8c498..aec9d81c4 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,3 +1,4 @@ +import React from 'react'; import { RouterProvider } from "react-router-dom"; import router from "./router"; import "./styles/App.css"; diff --git a/client/src/index.js b/client/src/index.js index d136d245c..37d1eeaf0 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -1,6 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; +import 'bootstrap/dist/css/bootstrap.min.css'; import "./styles/index.css"; const root = ReactDOM.createRoot(document.getElementById("root")); diff --git a/client/src/pages/A3JobEvaluationForm.jsx b/client/src/pages/A3JobEvaluationForm.jsx new file mode 100644 index 000000000..72f3c26d0 --- /dev/null +++ b/client/src/pages/A3JobEvaluationForm.jsx @@ -0,0 +1,250 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Form, Button, Container, Row, Col, Table, Modal, Tab, Nav } from 'react-bootstrap'; +import SignatureCanvas from 'react-signature-canvas'; +import '../styles/A3JobEvaluationForm.css'; + +// Fonts used for styled signature typing +const fonts = ['Pacifico', 'Indie Flower', 'Dancing Script', 'Great Vibes', 'Satisfy']; + +// Evaluation criteria items +const evaluationItems = [ + 'Task Execution and Quality', + 'Initiative and Proactiveness', + 'Communication and Collaboration', + 'Time Management and Dependability', + 'Problem Solving and Critical Thinking', + 'Creativity and Innovation', + 'Technical and Industry Specific Skills', + 'Work Ethic and Cultural Fit', + 'Feedback Reception and Implementation' +]; + + +const A3JobEvaluationForm = () => { + // Form state management + const [formData, setFormData] = useState({ + advisorSignature: '', + advisorAgreement: false, + coordinatorSignature: '', + coordinatorAgreement: false, + }); + + // Ratings and comments + const [ratings, setRatings] = useState({}); + const [comments, setComments] = useState({}); + + // Modal state + const [showModal, setShowModal] = useState(false); + const [activeSignatureTarget, setActiveSignatureTarget] = useState('advisor'); + const [typedSignatures, setTypedSignatures] = useState({ advisor: '', coordinator: '' }); + const [selectedFont, setSelectedFont] = useState(fonts[0]); + const [activeTab, setActiveTab] = useState('type'); + + // Signature canvas ref + const sigCanvasRef = useRef(null); + + + // Clear typed signature if tab switches to "type" + useEffect(() => { + if (activeTab === 'type') { + setTypedSignatures(prev => ({ ...prev, [activeSignatureTarget]: '' })); + } + }, [activeSignatureTarget, showModal, activeTab]); + + // Handle form input changes + const handleChange = (field, value) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + // Rating selection + const handleRatingChange = (item, value) => { + setRatings(prev => ({ ...prev, [item]: value })); + }; + + // Comment box + const handleCommentChange = (item, value) => { + setComments(prev => ({ ...prev, [item]: value })); + }; + + // Handle inserting signature from modal + const handleSignatureInsert = () => { + const targetField = activeSignatureTarget === 'advisor' ? 'advisorSignature' : 'coordinatorSignature'; + if (activeTab === 'type' && typedSignatures[activeSignatureTarget].trim()) { + //handleChange(targetField, JSON.stringify({ type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont })); + handleChange(targetField, { type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont }); + setShowModal(false); + } else if (activeTab === 'draw') { + const canvas = sigCanvasRef.current; + if (canvas && !canvas.isEmpty()) { + let trimmedCanvas; + try { + trimmedCanvas = canvas.getTrimmedCanvas(); + } catch (err) { + console.warn("getTrimmedCanvas() failed, using full canvas instead."); + trimmedCanvas = canvas.getCanvas(); + } + const signatureData = trimmedCanvas.toDataURL('image/png'); + //handleChange(targetField, JSON.stringify({ type: 'draw', value: signatureData })); + handleChange(targetField, { type: 'draw', value: signatureData }) + setShowModal(false); + } else { + alert("Please draw your signature before inserting."); + } + } + }; + + // Submit the form to the backend + const handleSubmit = async (e) => { + e.preventDefault(); + if (!formData.advisorAgreement || !formData.coordinatorAgreement) { + alert('Please confirm both signature agreements before submitting.'); + return; + } + try { + const response = await fetch('http://localhost:5001/api/evaluation', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ formData, ratings, comments }), + }); + if (response.ok) { + alert('Evaluation submitted successfully!'); + setFormData({ advisorSignature: '', advisorAgreement: false, coordinatorSignature: '', coordinatorAgreement: false }); + setRatings({}); + setComments({}); + setTypedSignatures({ advisor: '', coordinator: '' }); + sigCanvasRef.current?.clear(); + } else { + const err = await response.json(); + console.error("Backend returned error:", err); + alert(`Submission failed: ${err.error}`); + } + } catch (err) { + alert('Server error. Please try again.'); + console.error(err); + } + }; + +// Show preview of signature (text or image) +const renderSignaturePreview = (field) => { + if (!formData[field]) { + return Click to sign; + } + + let sig = formData[field]; + if (typeof sig === 'string') { + try { + sig = JSON.parse(sig); + } catch (err) { + return Invalid signature format; + } + } + + if (sig.type === 'draw') { + return Signature; + } + if (sig.type === 'text') { + return {sig.value}; + } + + return Unknown signature type; + }; + + return ( +
+

A.3 – Job Performance Evaluation

+ +
+ + + + + + {evaluationItems.map((item, index) => ( + + + + + + + ))} + +
ItemSatisfactoryUnsatisfactoryComments
{item} handleRatingChange(item, 'Satisfactory')} required /> handleRatingChange(item, 'Unsatisfactory')} /> handleCommentChange(item, e.target.value)} placeholder="Enter comments" style={{ minWidth: '250px' }} />
+ + {/* Signature section */} + + + + Internship Advisor Signature +
{ setActiveSignatureTarget('advisor'); setShowModal(true); }}> + {renderSignaturePreview('advisorSignature')} +
+ handleChange('advisorAgreement', e.target.checked)} required /> +
+ + + + Internship Coordinator Signature +
{ setActiveSignatureTarget('coordinator'); setShowModal(true); }}> + {renderSignaturePreview('coordinatorSignature')} +
+ handleChange('coordinatorAgreement', e.target.checked)} required /> +
+ +
+ + {/* Submit button */} +
+ +
+
+
+ + {/* Signature Modal */} + setShowModal(false)} centered dialogClassName="custom-signature-modal"> + +
+
Sign Here
+ +
+ + + + + setTypedSignatures(prev => ({ ...prev, [activeSignatureTarget]: e.target.value }))} className="mb-3" /> +
+ {fonts.map(font => ( +
setSelectedFont(font)} style={{ cursor: 'pointer', fontFamily: font, padding: '10px 15px', border: font === selectedFont ? '2px solid #9d2235' : '1px solid #ccc', borderRadius: '10px', fontSize: '24px', backgroundColor: '#fff' }}> + {typedSignatures[activeSignatureTarget] || 'Your name'} +
+ ))} +
+
+ +
+
+ + {/* Draw tab */} + +
+
Draw here
+ +
+ + +
+
+
+
+
+
+
+
+ ); +}; + +export default A3JobEvaluationForm; + + diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index 2e8b87c46..6068e1027 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -2,11 +2,6 @@ import React from 'react'; import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import "../styles/App.css"; -import { FaEnvelope, FaLock, FaEye, FaEyeSlash } from "react-icons/fa"; -import "../styles/login.css"; -import StudentIcon from "../Icons/StudentIcon"; -import CoordinatorIcon from "../Icons/CoordinatorIcon"; -import SupervisorIcon from "../Icons/SupervisorIcon"; function Home() { const navigate = useNavigate(); @@ -15,8 +10,6 @@ function Home() { password: "", role: "Student", }); - const [showPassword, setShowPassword] = useState(false); - const [role, setRole] = useState("-"); const handleInputChange = (e) => { const { name, value } = e.target; @@ -39,109 +32,48 @@ function Home() {
-

- Welcome back -

+

Sign in to continue

- -
- {[ - { role: "student", Icon: StudentIcon }, - { role: "supervisor", Icon: SupervisorIcon }, - { role: "coordinator", Icon: CoordinatorIcon }, - ].map(({ role: r, Icon }) => ( -
setRole(r)} - > - -

- {r.charAt(0).toUpperCase() + r.slice(1)} -

- -
- ))} -
+ +
-
- +
+
-
- -
- - setShowPassword(!showPassword)} - > - {showPassword ? : } - -
-
- -
- - - Forgot password? - - +
+ +
-
+
Don't have an account? { e.preventDefault(); navigate("/signup"); }} - style={{ - color: "#7f1d1d", - fontWeight: "600", - marginLeft: "4px", - textDecoration: "underline", - }} > - Sign up for free + {" "} + Sign Up
); -}; +} export default Home; diff --git a/client/src/router.js b/client/src/router.js index 70e8c082e..c30e7eb90 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -7,6 +7,7 @@ import Layout from "./components/Layout"; import Home from "./pages/Home"; import SignUp from "./pages/SignUp"; import NotFound from "./pages/NotFound"; +import A3JobEvaluationForm from "./pages/A3JobEvaluationForm"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; import SignIn from "./pages/SignIn"; @@ -30,6 +31,10 @@ const router = createBrowserRouter([ path: "signin", element: , }, + { + path: "evaluation", + element: , + }, { path: "supervisor-dashboard", element: , diff --git a/client/src/styles/A3JobEvaluationForm.css b/client/src/styles/A3JobEvaluationForm.css new file mode 100644 index 000000000..21b709b19 --- /dev/null +++ b/client/src/styles/A3JobEvaluationForm.css @@ -0,0 +1,58 @@ +/* Custom color for checked Bootstrap radio button */ +/* .form-check-input:checked { + background-color: #9d2235; + border-color: #9d2235; + accent-color: #9d2235; +} */ +.custom-table input[type="radio"]:checked { + background-color: #9d2235; + border-color: #9d2235; + box-shadow: 0 0 0 1px #9d2235; +} + + + +.custom-table thead th { + color: white !important; + background-color: #9d2235 +} + + +.form-check-input:focus { + box-shadow: 0 0 0 0.2rem rgba(157, 34, 53, 0.25); +} + +.heading-maroon { + color: #9d2235; + font-size: 1.8rem; + font-weight: 700; + font-family: 'Poppins', 'Segoe UI', sans-serif; + text-align: center; +} + +.signature-preview { + font-size: 28px; + padding: 8px 12px; + border: 2px solid #ccc; + border-radius: 10px; + cursor: pointer; + margin: 8px 0; + transition: all 0.3s ease; +} +.signature-preview.selected { + border-color: #9d2235; + background-color: #f9f3f3; +} + +.signature-font-1 { font-family: 'Great Vibes', cursive; } +.signature-font-2 { font-family: 'Pacifico', cursive; } +.signature-font-3 { font-family: 'Satisfy', cursive; } + +.page-content { /* to adjust content display */ + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + min-height: 100vh; + padding: 10px; +} \ No newline at end of file diff --git a/server/.env b/server/.env index 97e994923..950bffcc5 100644 --- a/server/.env +++ b/server/.env @@ -1,13 +1,12 @@ +# Server Configuration +PORT=5001 MONGO_URI=mongodb://localhost:27017/IPMS -PORT=5000 # Email Configuration EMAIL_HOST=smtp.gmail.com EMAIL_PORT=587 EMAIL_SECURE=false -EMAIL_USER=sep.ipms.spring2025@gmail.com -EMAIL_PASSWORD=rmrl msnq kflk uimr -EMAIL_DEFAULT_SENDER=sep.ipms.spring2025@gmail.com +EMAIL_USER=your-email@gmail.com +EMAIL_PASSWORD=your-app-password +EMAIL_DEFAULT_SENDER=Internship Program Management System -# Secret -JWT_SECRET=supersecretkey123 diff --git a/server/models/Evaluation.js b/server/models/Evaluation.js new file mode 100644 index 000000000..d104353f5 --- /dev/null +++ b/server/models/Evaluation.js @@ -0,0 +1,40 @@ +const mongoose = require('mongoose'); + +const signatureSchema = new mongoose.Schema({ + type: { type: String, enum: ['text', 'draw'], required: true }, + value: { type: String, required: true }, + font: { type: String } +}, { _id: false }); + +const evaluationItemSchema = new mongoose.Schema({ + category: { + type: String, + required: true, + }, + rating: { + type: String, + enum: ['Satisfactory', 'Unsatisfactory'], + required: true + }, + comment: { type: String, maxlength: 500 } +}, { _id: false }); + +const evaluationSchema = new mongoose.Schema({ + interneeId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: false }, + internshipId: { type: mongoose.Schema.Types.ObjectId, ref: 'Internship', required: false }, + + evaluations: { + type: [evaluationItemSchema], + validate: [arr => arr.length > 0, 'At least one evaluation item is required'] + }, + + advisorSignature: { type: signatureSchema, required: true }, + advisorAgreement: { type: Boolean, required: true }, + coordinatorSignature: { type: signatureSchema, required: true }, + coordinatorAgreement: { type: Boolean, required: true } + +}, { timestamps: true }); + +evaluationSchema.index({ interneeId: 1, internshipId: 1 }); + +module.exports = mongoose.model('Evaluation', evaluationSchema); diff --git a/server/package.json b/server/package.json index 38bc1f035..7258c54ff 100644 --- a/server/package.json +++ b/server/package.json @@ -1,5 +1,5 @@ { - "name": "groupe-backend", + "name": "server", "version": "1.0.0", "description": "IPMS backend", "main": "index.js", @@ -9,12 +9,13 @@ "debug": "node --inspect index.js", "test": "jest --coverage" }, + "keywords": [], + "author": "", + "license": "ISC", "dependencies": { "cors": "^2.8.5", - "cron-parser": "^5.1.1", "dotenv": "^16.4.7", "express": "^4.21.2", - "jsonwebtoken": "^9.0.2", "mongodb": "^6.14.2", "mongoose": "^8.13.2", "node-cron": "^3.0.3", @@ -26,14 +27,11 @@ "jest": { "collectCoverage": true, "collectCoverageFrom": [ - "**/*.js", + "**/*.js", "!node_modules/**", - "!**/test/**" + "!**/test/**" ], "coverageDirectory": "coverage", - "coverageReporters": [ - "text", - "lcov" - ] + "coverageReporters": ["text", "lcov"] } } diff --git a/server/server.js b/server/server.js deleted file mode 100644 index 40ba4214d..000000000 --- a/server/server.js +++ /dev/null @@ -1,20 +0,0 @@ -const express = require("express"); -const cors = require("cors"); -const mongoose = require("mongoose"); -const approvalRoutes = require("./routes/approvalRoutes"); -const authRoutes = require("./routes/authRoutes"); -require("dotenv").config(); - -const app = express(); -app.use(cors()); -app.use(express.json()); - -mongoose.connect(process.env.MONGO_URI) - .then(() => console.log("MongoDB Connected")) - .catch((err) => console.error(err)); - -app.use("/api", approvalRoutes); -app.use("/api", authRoutes); - -const PORT = process.env.PORT || 5000; -app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); \ No newline at end of file From 71a01f65645935154b67e36ff7d0fbd1d630322c Mon Sep 17 00:00:00 2001 From: Hozen Date: Tue, 8 Apr 2025 15:54:50 -0500 Subject: [PATCH 04/10] Resolve files not updated --- client/src/pages/Home.js | 135 ++++++++++++++++++++++++++++++--------- client/src/router.js | 3 + server/.env | 8 ++- server/models/User.js | 27 ++++++-- server/package.json | 11 +++- 5 files changed, 143 insertions(+), 41 deletions(-) diff --git a/client/src/pages/Home.js b/client/src/pages/Home.js index 6068e1027..c9334218b 100644 --- a/client/src/pages/Home.js +++ b/client/src/pages/Home.js @@ -2,6 +2,11 @@ import React from 'react'; import { useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import "../styles/App.css"; +import { FaEnvelope, FaLock, FaEye, FaEyeSlash } from "react-icons/fa"; +import "../styles/login.css"; +import StudentIcon from "../Icons/StudentIcon"; +import CoordinatorIcon from "../Icons/CoordinatorIcon"; +import SupervisorIcon from "../Icons/SupervisorIcon"; function Home() { const navigate = useNavigate(); @@ -10,6 +15,8 @@ function Home() { password: "", role: "Student", }); + const [showPassword, setShowPassword] = useState(false); + const [role, setRole] = useState("-"); const handleInputChange = (e) => { const { name, value } = e.target; @@ -32,48 +39,109 @@ function Home() {
-

Sign in to continue

+

+ Welcome back +

- - + +
+ {[ + { role: "student", Icon: StudentIcon }, + { role: "supervisor", Icon: SupervisorIcon }, + { role: "coordinator", Icon: CoordinatorIcon }, + ].map(({ role: r, Icon }) => ( +
setRole(r)} + > + +

+ {r.charAt(0).toUpperCase() + r.slice(1)} +

+ +
+ ))} +
-
- +
+
-
- - +
+ +
+ + setShowPassword(!showPassword)} + > + {showPassword ? : } + +
+
+ +
+ + + Forgot password? + +
-
+
Don't have an account? { e.preventDefault(); navigate("/signup"); }} + style={{ + color: "#7f1d1d", + fontWeight: "600", + marginLeft: "4px", + textDecoration: "underline", + }} > - {" "} - Sign Up + Sign up for free
diff --git a/client/src/router.js b/client/src/router.js index c30e7eb90..8f82738af 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -1,3 +1,6 @@ +import React from 'react'; + + import { createBrowserRouter } from "react-router-dom"; // Layout diff --git a/server/.env b/server/.env index 950bffcc5..32d995d42 100644 --- a/server/.env +++ b/server/.env @@ -6,7 +6,9 @@ MONGO_URI=mongodb://localhost:27017/IPMS EMAIL_HOST=smtp.gmail.com EMAIL_PORT=587 EMAIL_SECURE=false -EMAIL_USER=your-email@gmail.com -EMAIL_PASSWORD=your-app-password -EMAIL_DEFAULT_SENDER=Internship Program Management System +EMAIL_USER=sep.ipms.spring2025@gmail.com +EMAIL_PASSWORD=rmrl msnq kflk uimr +EMAIL_DEFAULT_SENDER=sep.ipms.spring2025@gmail.com +# Secret +JWT_SECRET=supersecretkey123 \ No newline at end of file diff --git a/server/models/User.js b/server/models/User.js index fffc20f23..59aecfdd8 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -1,9 +1,28 @@ const mongoose = require("mongoose"); const userSchema = new mongoose.Schema({ - Email: { type: String, required: true, unique: true }, - Password: { type: String, required: true }, - role: { type: String, required: true } -}, { timestamps: true }); + userName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + role: { + type: String, + required: true, + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); module.exports = mongoose.model("User", userSchema); + diff --git a/server/package.json b/server/package.json index 7258c54ff..124ec89bb 100644 --- a/server/package.json +++ b/server/package.json @@ -14,8 +14,10 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", + "cron-parser": "^5.1.1", "dotenv": "^16.4.7", "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", "mongodb": "^6.14.2", "mongoose": "^8.13.2", "node-cron": "^3.0.3", @@ -27,11 +29,14 @@ "jest": { "collectCoverage": true, "collectCoverageFrom": [ - "**/*.js", + "**/*.js", "!node_modules/**", - "!**/test/**" + "!**/test/**" ], "coverageDirectory": "coverage", - "coverageReporters": ["text", "lcov"] + "coverageReporters": [ + "text", + "lcov" + ] } } From 5f9a083aeabdaac6d43a3d9e88db5609751e9bc4 Mon Sep 17 00:00:00 2001 From: Hozen Date: Tue, 8 Apr 2025 15:57:56 -0500 Subject: [PATCH 05/10] change bad User schema for our work --- server/controllers/authController.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/controllers/authController.js b/server/controllers/authController.js index 115af0238..2dd26e64a 100644 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -7,8 +7,8 @@ exports.login = async (req, res) => { console.log("Login attempt:", { email, password, role }); const user = await User.findOne({ - Email: { $regex: new RegExp("^" + email + "$", "i") }, // case-insensitive - Password: password, + email: { $regex: new RegExp("^" + email + "$", "i") }, // case-insensitive + password: password, role: role, }); @@ -17,11 +17,11 @@ exports.login = async (req, res) => { return res.status(401).json({ message: "Invalid credentials" }); } - console.log("Login success:", user.Email); + console.log("Login success:", user.email); res.json({ message: "Login successful", user }); } catch (err) { console.error("Login error:", err.message); res.status(500).json({ message: "Login failed", error: err.message }); } -}; \ No newline at end of file +}; From cca982ec3531f60c63a5e4e41b7132100bd6c32f Mon Sep 17 00:00:00 2001 From: Hozen Date: Tue, 8 Apr 2025 20:06:32 -0500 Subject: [PATCH 06/10] Use Home instead of SignIn page --- client/src/pages/SignIn.js | 67 ---------- client/src/pages/SupervisorDashboard.js | 156 +++++++++++++++--------- client/src/router.js | 5 - 3 files changed, 99 insertions(+), 129 deletions(-) delete mode 100644 client/src/pages/SignIn.js diff --git a/client/src/pages/SignIn.js b/client/src/pages/SignIn.js deleted file mode 100644 index 7e8d48217..000000000 --- a/client/src/pages/SignIn.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState } from "react"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; - -const SignIn = () => { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [role, setRole] = useState("Supervisor"); - const navigate = useNavigate(); - - const handleSubmit = async (e) => { - e.preventDefault(); - console.log("Login button clicked"); - - try { - const response = await axios.post("http://localhost:5000/api/login", { - email, - password, - role, - }); - - console.log("Login Success:", response.data); - alert("Login successful"); - - if (role === "Supervisor") navigate("/supervisor-dashboard"); - else if (role === "Coordinator") navigate("/coordinator-dashboard"); - else navigate("/"); - - } catch (error) { - console.error("Login failed:", error); - alert("Login failed"); - } - }; - - return ( -
-

Sign In

-
- -

- setEmail(e.target.value)} - required - />

- setPassword(e.target.value)} - required - />

- -
-
- ); -}; - -export default SignIn; \ No newline at end of file diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index 9e5ff05db..0af3319f5 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -2,67 +2,109 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; const SupervisorDashboard = () => { - const [submissions, setSubmissions] = useState([]); + const [submissions, setSubmissions] = useState([]); + const url = "http://localhost:5001" - useEffect(() => { - fetchPendingSubmissions(); - }, []); + useEffect(() => { + fetchPendingSubmissions(); + }, []); - const fetchPendingSubmissions = async () => { - try { - const response = await axios.get("http://localhost:5000/api/submissions/pending"); - setSubmissions(response.data); - } catch (err) { - console.error("Error fetching submissions:", err); - } - }; + const fetchPendingSubmissions = async () => { + try { + const response = await axios.get(url + "/api/submissions/pending"); + setSubmissions(response.data); + } catch (err) { + console.error("Error fetching submissions:", err); + } + }; - const handleDecision = async (id, action) => { - try { - const endpoint = `http://localhost:5000/api/submissions/${id}/${action}`; - await axios.post(endpoint); - alert(`Submission ${action}d successfully!`); - fetchPendingSubmissions(); // refresh list - } catch (err) { - console.error("Error updating submission:", err); - } - }; + const handleDecision = async (id, action) => { + try { + const endpoint = url + `/api/submissions/${id}/${action}`; + await axios.post(endpoint); + alert(`Submission ${action}d successfully!`); + fetchPendingSubmissions(); // refresh list + } catch (err) { + console.error("Error updating submission:", err); + } + }; - return ( -
-

Supervisor Dashboard

-

Welcome, Supervisor!

+ return ( +
+

Supervisor Dashboard

+

Welcome, Supervisor!

-

Pending Submissions

- {submissions.length === 0 ? ( -

No pending submissions.

- ) : ( - - - - - - - - - - - {submissions.map((submission) => ( - - - - - - - ))} - -
IDTitleSubmitted ByAction
{submission._id}{submission.title || "N/A"}{submission.studentName || "N/A"} - {" "} - -
- )} -
- ); +

Pending Submissions

+ {submissions.length === 0 ? ( +

No pending submissions.

+ ) : ( +
+
+
ID
+
Title
+
Submitted By
+
Action
+
+ + {submissions.map((submission) => ( +
+
{submission._id}
+
{submission.title || "N/A"}
+
{submission.studentName || "N/A"}
+
+ + +
+
+ ))} +
+ )} +
+ ); }; -export default SupervisorDashboard; \ No newline at end of file +export default SupervisorDashboard; diff --git a/client/src/router.js b/client/src/router.js index 8f82738af..d71ac8f5e 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -13,7 +13,6 @@ import NotFound from "./pages/NotFound"; import A3JobEvaluationForm from "./pages/A3JobEvaluationForm"; import SupervisorDashboard from "./pages/SupervisorDashboard"; import CoordinatorDashboard from "./pages/CoordinatorDashboard"; -import SignIn from "./pages/SignIn"; // Create and export the router configuration const router = createBrowserRouter([ @@ -30,10 +29,6 @@ const router = createBrowserRouter([ path: "signup", element: , }, - { - path: "signin", - element: , - }, { path: "evaluation", element: , From 4b952c5d70a4985990bfa0c06fe8aca1b1ff72a3 Mon Sep 17 00:00:00 2001 From: Hozen Date: Tue, 8 Apr 2025 23:21:40 -0500 Subject: [PATCH 07/10] Better Supervisor UI, Remove unused files --- client/package.json | 1 + client/src/pages/SupervisorDashboard.js | 96 ++++---------- client/src/styles/SupervisorDashboard.css | 106 +++++++++++++++ server/controllers/approvalController.js | 8 +- server/controllers/authController.js | 27 ---- server/index.js | 153 +++++++++++----------- server/models/Submission.js | 13 +- server/routes/authRoutes.js | 7 - 8 files changed, 216 insertions(+), 195 deletions(-) create mode 100644 client/src/styles/SupervisorDashboard.css delete mode 100644 server/controllers/authController.js delete mode 100644 server/routes/authRoutes.js diff --git a/client/package.json b/client/package.json index 57a691162..102348e2b 100644 --- a/client/package.json +++ b/client/package.json @@ -18,6 +18,7 @@ "react-signature-canvas": "^1.1.0-alpha.2", "web-vitals": "^2.1.4" }, + "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index 0af3319f5..dc7b24ff9 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import axios from "axios"; +import '../styles/SupervisorDashboard.css'; const SupervisorDashboard = () => { const [submissions, setSubmissions] = useState([]); @@ -30,80 +31,27 @@ const SupervisorDashboard = () => { }; return ( -
-

Supervisor Dashboard

-

Welcome, Supervisor!

- -

Pending Submissions

- {submissions.length === 0 ? ( -

No pending submissions.

- ) : ( -
-
-
ID
-
Title
-
Submitted By
-
Action
-
- - {submissions.map((submission) => ( -
-
{submission._id}
-
{submission.title || "N/A"}
-
{submission.studentName || "N/A"}
-
- - -
-
- ))} -
- )} -
+
+

Supervisor Dashboard

+

Pending Approvals

+
    + {submissions.length === 0 ? ( +
    +
    No pending approvals at this time.
    +
    + ) : ( + submissions.map(item => ( +
  • + {item.name} - Details: {item.details} - Status: {item.supervisor_status} +
    + + +
    +
  • + )) + )} +
+
); }; diff --git a/client/src/styles/SupervisorDashboard.css b/client/src/styles/SupervisorDashboard.css new file mode 100644 index 000000000..0e2acfa75 --- /dev/null +++ b/client/src/styles/SupervisorDashboard.css @@ -0,0 +1,106 @@ +.dashboard-container { + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.dashboard-container h2 { + font-size: 20px; + margin-bottom: 20px; + color: #333; +} + +.dashboard-title { + font-size: 24px; + margin-bottom: 20px; + color: #333; +} + +.pending-approvals { + list-style-type: none; + padding: 0; +} + +.pending-approvals li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + margin: 40px 0; + background-color: #fff; + border-radius: 5px; + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1); + font-size: 16px; +} + +.pending-approvals li button { + margin-left: 10px; + padding: 5px 10px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s; +} + +.approve{ + background-color: #28a745; /* Green background for approve button */ + color: white; +} + +.reject{ + background-color: #dc3545; /* Red background for reject button */ + color: white; +} + +.approve:hover { + background-color: #218838; +} + +.reject:hover{ + background-color: #c82333; +} + +.pending-approvals li button:focus { + outline: none; +} + +.empty-message-container{ + display: flex; /* Use flexbox */ + justify-content: center; /* Center horizontally */ + align-items: center; /* Center vertically */ + height: 20vh; +} +.empty-message { + font-size: 28px; /* Adjust font size */ + color: #000000; /* Change text color for better visibility */ + text-align: center; /*Center the message*/ + margin: 20px 0; /* Add some margin for spacing */ + font-weight: bold; /* Make the message bold */ +} + +form { + margin-bottom: 5px; +} + +form input, +form textarea { + width: 100%; + padding: 10px; + margin: 5px 0; + border: 1px solid #ccc; + border-radius: 5px; +} + +form button { + padding: 10px 15px; + background-color: #007bff; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +form button:hover { + background-color: #0056b3; +} diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js index bc568b1d5..724786cb0 100644 --- a/server/controllers/approvalController.js +++ b/server/controllers/approvalController.js @@ -3,7 +3,7 @@ const Submission = require("../models/Submission"); // βœ… Get pending submissions for supervisor exports.getPendingSubmissions = async (req, res) => { try { - const submissions = await Submission.find({ status: "pending" }); + const submissions = await Submission.find({ supervisor_status: "pending" }); res.json(submissions); } catch (err) { res.status(500).json({ message: "Failed to fetch pending submissions", error: err }); @@ -17,7 +17,7 @@ exports.approveSubmission = async (req, res) => { try { const submission = await Submission.findByIdAndUpdate( id, - { status: "approved_by_supervisor" }, + { supervisor_status: "approved_by_supervisor" }, { new: true } ); @@ -42,7 +42,7 @@ exports.rejectSubmission = async (req, res) => { try { const submission = await Submission.findByIdAndUpdate( id, - { status: "rejected_by_supervisor" }, + { supervisor_status: "rejected_by_supervisor" }, { new: true } ); @@ -58,4 +58,4 @@ exports.rejectSubmission = async (req, res) => { } catch (err) { res.status(500).json({ message: "Rejection failed", error: err }); } -}; \ No newline at end of file +}; diff --git a/server/controllers/authController.js b/server/controllers/authController.js deleted file mode 100644 index 2dd26e64a..000000000 --- a/server/controllers/authController.js +++ /dev/null @@ -1,27 +0,0 @@ -const User = require("../models/User"); - -exports.login = async (req, res) => { - const { email, password, role } = req.body; - - try { - console.log("Login attempt:", { email, password, role }); - - const user = await User.findOne({ - email: { $regex: new RegExp("^" + email + "$", "i") }, // case-insensitive - password: password, - role: role, - }); - - if (!user) { - console.log("Login failed: user not found"); - return res.status(401).json({ message: "Invalid credentials" }); - } - - console.log("Login success:", user.email); - res.json({ message: "Login successful", user }); - - } catch (err) { - console.error("Login error:", err.message); - res.status(500).json({ message: "Login failed", error: err.message }); - } -}; diff --git a/server/index.js b/server/index.js index a59d0f76f..27e3021a4 100644 --- a/server/index.js +++ b/server/index.js @@ -5,9 +5,8 @@ const User = require("./models/User"); require("dotenv").config(); const emailRoutes = require("./routes/emailRoutes"); -const approvalRoutes = require("./routes/approvalRoutes"); -const authRoutes = require("./routes/authRoutes"); const tokenRoutes = require("./routes/token"); +const approvalRoutes = require("./routes/approvalRoutes"); // Import cron job manager and register jobs const cronJobManager = require("./utils/cronUtils"); @@ -18,108 +17,106 @@ app.use(express.json()); app.use(cors()); const mongoConfig = { - serverSelectionTimeoutMS: 5000, - autoIndex: true, - maxPoolSize: 10, - socketTimeoutMS: 45000, - family: 4, + serverSelectionTimeoutMS: 5000, + autoIndex: true, + maxPoolSize: 10, + socketTimeoutMS: 45000, + family: 4, }; mongoose - .connect(process.env.MONGO_URI, mongoConfig) - .then(async () => { - console.log("Connected to Local MongoDB"); - // Initialize cron jobs after database connection is established - try { - await registerAllJobs(); - console.log("βœ… Cron jobs initialized successfully"); - } catch (error) { - console.error("❌ Failed to initialize cron jobs:", error); - } - }) - .catch((err) => { - console.error("MongoDB Connection Error:", err); - process.exit(1); - }); + .connect(process.env.MONGO_URI, mongoConfig) + .then(async () => { + console.log("Connected to Local MongoDB"); + // Initialize cron jobs after database connection is established + try { + await registerAllJobs(); + console.log("βœ… Cron jobs initialized successfully"); + } catch (error) { + console.error("❌ Failed to initialize cron jobs:", error); + } + }) + .catch((err) => { + console.error("MongoDB Connection Error:", err); + process.exit(1); + }); mongoose.connection.on("error", (err) => { - console.error("MongoDB error after initial connection:", err); + console.error("MongoDB error after initial connection:", err); }); mongoose.connection.on("disconnected", () => { - console.log("Lost MongoDB connection..."); - if (!mongoose.connection.readyState) { - mongoose - .connect(process.env.MONGO_URI, mongoConfig) - .then(() => console.log("Reconnected to MongoDB")) - .catch((err) => console.error("Error reconnecting to MongoDB:", err)); - } + console.log("Lost MongoDB connection..."); + if (!mongoose.connection.readyState) { + mongoose + .connect(process.env.MONGO_URI, mongoConfig) + .then(() => console.log("Reconnected to MongoDB")) + .catch((err) => console.error("Error reconnecting to MongoDB:", err)); + } }); app.get("/", (req, res) => { - res.send("IPMS Backend Running"); + res.send("IPMS Backend Running"); }); app.get("/api/message", (req, res) => { - res.json({ message: "Hello from the backend!" }); + res.json({ message: "Hello from the backend!" }); }); app.use("/api/email", emailRoutes); -app.use("/api", approvalRoutes); -app.use("/api", authRoutes); app.use("/api/token", tokenRoutes); +app.use("/api", approvalRoutes); app.post("/api/createUser", async (req, res) => { - try { - const { userName, email, password, role } = req.body; - const user = new User({ userName, email, password, role }); - await user.save(); - console.log("New user created:", JSON.stringify(user)); - res.status(201).json({ message: "User created successfully", user }); - } catch (error) { - console.error("Error creating user:", error); - res - .status(500) - .json({ message: "Failed to create user", error: error.message }); - } + try { + const { userName, email, password, role } = req.body; + const user = new User({ userName, email, password, role }); + await user.save(); + console.log("New user created:", JSON.stringify(user)); + res.status(201).json({ message: "User created successfully", user }); + } catch (error) { + console.error("Error creating user:", error); + res + .status(500) + .json({ message: "Failed to create user", error: error.message }); + } }); - app.post("/api/evaluation", async (req, res) => { - try { - const { formData, ratings, comments } = req.body; - const evaluations = Object.keys(ratings).map((category) => ({ - category, - rating: ratings[category], - comment: comments[category] || "", - })); - - const newEvaluation = new Evaluation({ - advisorSignature: formData.advisorSignature, - advisorAgreement: formData.advisorAgreement, - coordinatorSignature: formData.coordinatorSignature, - coordinatorAgreement: formData.coordinatorAgreement, - evaluations, - }); + try { + const { formData, ratings, comments } = req.body; + + const evaluations = Object.keys(ratings).map((category) => ({ + category, + rating: ratings[category], + comment: comments[category] || "", + })); + + const newEvaluation = new Evaluation({ + advisorSignature: formData.advisorSignature, + advisorAgreement: formData.advisorAgreement, + coordinatorSignature: formData.coordinatorSignature, + coordinatorAgreement: formData.coordinatorAgreement, + evaluations, + }); - await newEvaluation.save(); - res.status(201).json({ message: "Evaluation saved successfully!" }); - } catch (error) { - console.error("Error saving evaluation:", error); - res.status(500).json({ error: "Failed to save evaluation" }); - } + await newEvaluation.save(); + res.status(201).json({ message: "Evaluation saved successfully!" }); + } catch (error) { + console.error("Error saving evaluation:", error); + res.status(500).json({ error: "Failed to save evaluation" }); + } }); - // Graceful shutdown (async Mongoose support) process.on("SIGINT", async () => { - try { - cronJobManager.stopAllJobs(); - await mongoose.connection.close(); - console.log("βœ… MongoDB connection closed through app termination"); - process.exit(0); - } catch (err) { - console.error("❌ Error during shutdown:", err); - process.exit(1); - } + try { + cronJobManager.stopAllJobs(); + await mongoose.connection.close(); + console.log("βœ… MongoDB connection closed through app termination"); + process.exit(0); + } catch (err) { + console.error("❌ Error during shutdown:", err); + process.exit(1); + } }); const PORT = process.env.PORT || 5001; diff --git a/server/models/Submission.js b/server/models/Submission.js index 3ced42c19..c8bd10d76 100644 --- a/server/models/Submission.js +++ b/server/models/Submission.js @@ -1,10 +1,13 @@ const mongoose = require("mongoose"); const submissionSchema = new mongoose.Schema({ - studentName: { type: String, required: true }, - internshipDetails: { type: String, required: true }, - status: { type: String, default: "pending" }, - supervisorComment: { type: String } + name: { type: String, required: true }, + student_name: { type: String, required: true }, + details: { type: String, required: true }, + supervisor_status: { type: String, default: "pending" }, + supervisor_comment: { type: String }, + coordinator_status: { type: String, default: "pending" }, + coordinator_comment: { type: String }, }, { timestamps: true }); -module.exports = mongoose.model("Submission", submissionSchema); \ No newline at end of file +module.exports = mongoose.model("Submission", submissionSchema); diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js deleted file mode 100644 index ce620a1c5..000000000 --- a/server/routes/authRoutes.js +++ /dev/null @@ -1,7 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const { login } = require("../controllers/authController"); - -router.post("/login", login); - -module.exports = router; \ No newline at end of file From 9cd0d303969a62a8b2958b4532f5b59c01930f25 Mon Sep 17 00:00:00 2001 From: MRPHFitch <69053145+MRPHFitch@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:20:33 -0500 Subject: [PATCH 08/10] Changed made --- client/src/pages/SupervisorDashboard.js | 2 +- server/controllers/approvalController.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index dc7b24ff9..203d96d0f 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -4,7 +4,7 @@ import '../styles/SupervisorDashboard.css'; const SupervisorDashboard = () => { const [submissions, setSubmissions] = useState([]); - const url = "http://localhost:5001" + const url = process.env.REACT_APP_API_URL useEffect(() => { fetchPendingSubmissions(); diff --git a/server/controllers/approvalController.js b/server/controllers/approvalController.js index 724786cb0..8b3357a5e 100644 --- a/server/controllers/approvalController.js +++ b/server/controllers/approvalController.js @@ -17,7 +17,7 @@ exports.approveSubmission = async (req, res) => { try { const submission = await Submission.findByIdAndUpdate( id, - { supervisor_status: "approved_by_supervisor" }, + { supervisor_status: "Approved" }, { new: true } ); @@ -42,7 +42,7 @@ exports.rejectSubmission = async (req, res) => { try { const submission = await Submission.findByIdAndUpdate( id, - { supervisor_status: "rejected_by_supervisor" }, + { supervisor_status: "Rejected" }, { new: true } ); From 299fb71141997dc5133634747ec287bf729d1283 Mon Sep 17 00:00:00 2001 From: MRPHFitch <69053145+MRPHFitch@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:31:46 -0500 Subject: [PATCH 09/10] Changes made --- client/src/pages/SupervisorDashboard.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/client/src/pages/SupervisorDashboard.js b/client/src/pages/SupervisorDashboard.js index 203d96d0f..88accfab4 100644 --- a/client/src/pages/SupervisorDashboard.js +++ b/client/src/pages/SupervisorDashboard.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import axios from "axios"; import '../styles/SupervisorDashboard.css'; @@ -6,18 +6,19 @@ const SupervisorDashboard = () => { const [submissions, setSubmissions] = useState([]); const url = process.env.REACT_APP_API_URL - useEffect(() => { - fetchPendingSubmissions(); - }, []); + const fetchPendingSubmissions = useCallback(async () => { + try { + const response = await axios.get(url + "/api/submissions/pending"); + setSubmissions(response.data); + } catch (err) { + console.error("Error fetching submissions:", err); + } + }, [url]); - const fetchPendingSubmissions = async () => { - try { - const response = await axios.get(url + "/api/submissions/pending"); - setSubmissions(response.data); - } catch (err) { - console.error("Error fetching submissions:", err); - } - }; + useEffect(() => { + fetchPendingSubmissions(); + }, [fetchPendingSubmissions]); + const handleDecision = async (id, action) => { try { From 01d96cf7f0ef42beec3719112c1bd8340ee8a060 Mon Sep 17 00:00:00 2001 From: Phani Charan Date: Wed, 9 Apr 2025 16:19:48 -0500 Subject: [PATCH 10/10] A3 form change --- client/src/pages/A3JobEvaluationForm.jsx | 376 +++++++++++++++++------ client/src/router.js | 6 +- server/index.js | 1 + 3 files changed, 290 insertions(+), 93 deletions(-) diff --git a/client/src/pages/A3JobEvaluationForm.jsx b/client/src/pages/A3JobEvaluationForm.jsx index 72f3c26d0..582ac6db0 100644 --- a/client/src/pages/A3JobEvaluationForm.jsx +++ b/client/src/pages/A3JobEvaluationForm.jsx @@ -1,79 +1,103 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Form, Button, Container, Row, Col, Table, Modal, Tab, Nav } from 'react-bootstrap'; -import SignatureCanvas from 'react-signature-canvas'; -import '../styles/A3JobEvaluationForm.css'; +import React, { useState, useRef, useEffect } from "react"; +import { + Form, + Button, + Container, + Row, + Col, + Table, + Modal, + Tab, + Nav, +} from "react-bootstrap"; +import SignatureCanvas from "react-signature-canvas"; +import "../styles/A3JobEvaluationForm.css"; // Fonts used for styled signature typing -const fonts = ['Pacifico', 'Indie Flower', 'Dancing Script', 'Great Vibes', 'Satisfy']; +const fonts = [ + "Pacifico", + "Indie Flower", + "Dancing Script", + "Great Vibes", + "Satisfy", +]; // Evaluation criteria items const evaluationItems = [ - 'Task Execution and Quality', - 'Initiative and Proactiveness', - 'Communication and Collaboration', - 'Time Management and Dependability', - 'Problem Solving and Critical Thinking', - 'Creativity and Innovation', - 'Technical and Industry Specific Skills', - 'Work Ethic and Cultural Fit', - 'Feedback Reception and Implementation' + "Task Execution and Quality", + "Initiative and Proactiveness", + "Communication and Collaboration", + "Time Management and Dependability", + "Problem Solving and Critical Thinking", + "Creativity and Innovation", + "Technical and Industry Specific Skills", + "Work Ethic and Cultural Fit", + "Feedback Reception and Implementation", ]; - const A3JobEvaluationForm = () => { // Form state management const [formData, setFormData] = useState({ - advisorSignature: '', + advisorSignature: "", advisorAgreement: false, - coordinatorSignature: '', + coordinatorSignature: "", coordinatorAgreement: false, }); - // Ratings and comments + // Ratings and comments const [ratings, setRatings] = useState({}); const [comments, setComments] = useState({}); // Modal state const [showModal, setShowModal] = useState(false); - const [activeSignatureTarget, setActiveSignatureTarget] = useState('advisor'); - const [typedSignatures, setTypedSignatures] = useState({ advisor: '', coordinator: '' }); + const [activeSignatureTarget, setActiveSignatureTarget] = useState("advisor"); + const [typedSignatures, setTypedSignatures] = useState({ + advisor: "", + coordinator: "", + }); const [selectedFont, setSelectedFont] = useState(fonts[0]); - const [activeTab, setActiveTab] = useState('type'); + const [activeTab, setActiveTab] = useState("type"); // Signature canvas ref const sigCanvasRef = useRef(null); - // Clear typed signature if tab switches to "type" useEffect(() => { - if (activeTab === 'type') { - setTypedSignatures(prev => ({ ...prev, [activeSignatureTarget]: '' })); + if (activeTab === "type") { + setTypedSignatures((prev) => ({ ...prev, [activeSignatureTarget]: "" })); } }, [activeSignatureTarget, showModal, activeTab]); // Handle form input changes const handleChange = (field, value) => { - setFormData(prev => ({ ...prev, [field]: value })); + setFormData((prev) => ({ ...prev, [field]: value })); }; // Rating selection const handleRatingChange = (item, value) => { - setRatings(prev => ({ ...prev, [item]: value })); + setRatings((prev) => ({ ...prev, [item]: value })); }; // Comment box const handleCommentChange = (item, value) => { - setComments(prev => ({ ...prev, [item]: value })); + setComments((prev) => ({ ...prev, [item]: value })); }; // Handle inserting signature from modal const handleSignatureInsert = () => { - const targetField = activeSignatureTarget === 'advisor' ? 'advisorSignature' : 'coordinatorSignature'; - if (activeTab === 'type' && typedSignatures[activeSignatureTarget].trim()) { + const targetField = + activeSignatureTarget === "advisor" + ? "advisorSignature" + : "coordinatorSignature"; + if (activeTab === "type" && typedSignatures[activeSignatureTarget].trim()) { //handleChange(targetField, JSON.stringify({ type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont })); - handleChange(targetField, { type: 'text', value: typedSignatures[activeSignatureTarget], font: selectedFont }); + handleChange(targetField, { + type: "text", + value: typedSignatures[activeSignatureTarget], + font: selectedFont, + }); setShowModal(false); - } else if (activeTab === 'draw') { + } else if (activeTab === "draw") { const canvas = sigCanvasRef.current; if (canvas && !canvas.isEmpty()) { let trimmedCanvas; @@ -83,9 +107,9 @@ const A3JobEvaluationForm = () => { console.warn("getTrimmedCanvas() failed, using full canvas instead."); trimmedCanvas = canvas.getCanvas(); } - const signatureData = trimmedCanvas.toDataURL('image/png'); + const signatureData = trimmedCanvas.toDataURL("image/png"); //handleChange(targetField, JSON.stringify({ type: 'draw', value: signatureData })); - handleChange(targetField, { type: 'draw', value: signatureData }) + handleChange(targetField, { type: "draw", value: signatureData }); setShowModal(false); } else { alert("Please draw your signature before inserting."); @@ -97,143 +121,319 @@ const A3JobEvaluationForm = () => { const handleSubmit = async (e) => { e.preventDefault(); if (!formData.advisorAgreement || !formData.coordinatorAgreement) { - alert('Please confirm both signature agreements before submitting.'); + alert("Please confirm both signature agreements before submitting."); return; } try { - const response = await fetch('http://localhost:5001/api/evaluation', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ formData, ratings, comments }), - }); + const response = await fetch( + `${process.env.REACT_APP_API_URL}/api/evaluation`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ formData, ratings, comments }), + } + ); if (response.ok) { - alert('Evaluation submitted successfully!'); - setFormData({ advisorSignature: '', advisorAgreement: false, coordinatorSignature: '', coordinatorAgreement: false }); + alert("Evaluation submitted successfully!"); + setFormData({ + advisorSignature: "", + advisorAgreement: false, + coordinatorSignature: "", + coordinatorAgreement: false, + }); setRatings({}); setComments({}); - setTypedSignatures({ advisor: '', coordinator: '' }); + setTypedSignatures({ advisor: "", coordinator: "" }); sigCanvasRef.current?.clear(); } else { const err = await response.json(); console.error("Backend returned error:", err); alert(`Submission failed: ${err.error}`); - } + } } catch (err) { - alert('Server error. Please try again.'); + alert("Server error. Please try again."); console.error(err); } }; -// Show preview of signature (text or image) -const renderSignaturePreview = (field) => { + // Show preview of signature (text or image) + const renderSignaturePreview = (field) => { if (!formData[field]) { - return Click to sign; + return Click to sign; } - + let sig = formData[field]; - if (typeof sig === 'string') { + if (typeof sig === "string") { try { sig = JSON.parse(sig); } catch (err) { - return Invalid signature format; + return Invalid signature format; } } - - if (sig.type === 'draw') { - return Signature; + + if (sig.type === "draw") { + return ( + Signature + ); } - if (sig.type === 'text') { - return {sig.value}; + if (sig.type === "text") { + return ( + + {sig.value} + + ); } - - return Unknown signature type; + + return Unknown signature type; }; - + return (
-

A.3 – Job Performance Evaluation

- +

A.3 – Job Performance Evaluation

+
- + + + + + + {evaluationItems.map((item, index) => ( - - - + + + ))}
ItemSatisfactoryUnsatisfactoryComments
ItemSatisfactoryUnsatisfactoryComments
{item} handleRatingChange(item, 'Satisfactory')} required /> handleRatingChange(item, 'Unsatisfactory')} /> handleCommentChange(item, e.target.value)} placeholder="Enter comments" style={{ minWidth: '250px' }} /> + handleRatingChange(item, "Satisfactory")} + required + /> + + + handleRatingChange(item, "Unsatisfactory") + } + /> + + + handleCommentChange(item, e.target.value) + } + placeholder="Enter comments" + style={{ minWidth: "250px" }} + /> +
- + {/* Signature section */} Internship Advisor Signature -
{ setActiveSignatureTarget('advisor'); setShowModal(true); }}> - {renderSignaturePreview('advisorSignature')} +
{ + setActiveSignatureTarget("advisor"); + setShowModal(true); + }} + > + {renderSignaturePreview("advisorSignature")}
- handleChange('advisorAgreement', e.target.checked)} required /> + + handleChange("advisorAgreement", e.target.checked) + } + required + /> Internship Coordinator Signature -
{ setActiveSignatureTarget('coordinator'); setShowModal(true); }}> - {renderSignaturePreview('coordinatorSignature')} +
{ + setActiveSignatureTarget("coordinator"); + setShowModal(true); + }} + > + {renderSignaturePreview("coordinatorSignature")}
- handleChange('coordinatorAgreement', e.target.checked)} required /> + + handleChange("coordinatorAgreement", e.target.checked) + } + required + /> - + {/* Submit button */}
- +
{/* Signature Modal */} - setShowModal(false)} centered dialogClassName="custom-signature-modal"> - + setShowModal(false)} + centered + dialogClassName="custom-signature-modal" + > +
Sign Here
- +
- setTypedSignatures(prev => ({ ...prev, [activeSignatureTarget]: e.target.value }))} className="mb-3" /> + + setTypedSignatures((prev) => ({ + ...prev, + [activeSignatureTarget]: e.target.value, + })) + } + className="mb-3" + />
- {fonts.map(font => ( -
setSelectedFont(font)} style={{ cursor: 'pointer', fontFamily: font, padding: '10px 15px', border: font === selectedFont ? '2px solid #9d2235' : '1px solid #ccc', borderRadius: '10px', fontSize: '24px', backgroundColor: '#fff' }}> - {typedSignatures[activeSignatureTarget] || 'Your name'} + {fonts.map((font) => ( +
setSelectedFont(font)} + style={{ + cursor: "pointer", + fontFamily: font, + padding: "10px 15px", + border: + font === selectedFont + ? "2px solid #9d2235" + : "1px solid #ccc", + borderRadius: "10px", + fontSize: "24px", + backgroundColor: "#fff", + }} + > + {typedSignatures[activeSignatureTarget] || "Your name"}
))}
- +
{/* Draw tab */}
-
Draw here
- +
+ Draw here +
+
- - + +
@@ -246,5 +446,3 @@ const renderSignaturePreview = (field) => { }; export default A3JobEvaluationForm; - - diff --git a/client/src/router.js b/client/src/router.js index d71ac8f5e..69479cdfd 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -1,5 +1,4 @@ -import React from 'react'; - +import React from "react"; import { createBrowserRouter } from "react-router-dom"; @@ -30,7 +29,7 @@ const router = createBrowserRouter([ element: , }, { - path: "evaluation", + path: "evaluation", element: , }, { @@ -40,7 +39,6 @@ const router = createBrowserRouter([ { path: "coordinator-dashboard", element: , - }, // Add more routes as needed ], diff --git a/server/index.js b/server/index.js index 27e3021a4..cddf72aa6 100644 --- a/server/index.js +++ b/server/index.js @@ -11,6 +11,7 @@ const approvalRoutes = require("./routes/approvalRoutes"); // Import cron job manager and register jobs const cronJobManager = require("./utils/cronUtils"); const { registerAllJobs } = require("./jobs/registerCronJobs"); +const Evaluation = require("./models/Evaluation"); const app = express(); app.use(express.json());