From b39cb67b0fadda662cada43632b613dc8e991fc2 Mon Sep 17 00:00:00 2001 From: amNobodyyy <131776812+amNobodyyy@users.noreply.github.com> Date: Thu, 23 Jan 2025 22:42:41 +0530 Subject: [PATCH 01/12] mailing-part 50% --- client/src/Pages/ConsolidatedTable.jsx | 259 +++++++------------------ client/src/api.js | 28 +++ server/routes/emailRoutes.js | 58 ++++++ server/server.js | 2 + 4 files changed, 154 insertions(+), 193 deletions(-) create mode 100644 server/routes/emailRoutes.js diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index d38dea5..d6e1f95 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -1,197 +1,7 @@ -// import React, { useState, useEffect } from "react"; -// import axios from "axios"; - -// const ConsolidatedTable = () => { -// const [data, setData] = useState([]); -// const [loading, setLoading] = useState(true); - -// useEffect(() => { -// const fetchData = async () => { -// try { -// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); -// setData(response.data); -// setLoading(false); -// } catch (error) { -// console.error("Error fetching table data:", error); -// setLoading(false); -// } -// }; - -// fetchData(); -// }, []); - -// if (loading) { -// return
Loading...
; -// } - -// return ( -//
-//

Consolidated Table

-// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// {data.map((row, index) => ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// ))} -// -//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
-//
-// ); -// }; - -// export default ConsolidatedTable; - - - -// import React, { useState, useEffect } from "react"; -// import axios from "axios"; -// import { CSVLink } from "react-csv"; - -// const ConsolidatedTable = () => { -// const [data, setData] = useState([]); -// const [loading, setLoading] = useState(true); - -// useEffect(() => { -// const fetchData = async () => { -// try { -// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); -// setData(response.data); -// setLoading(false); -// } catch (error) { -// console.error("Error fetching table data:", error); -// setLoading(false); -// } -// }; - -// fetchData(); -// }, []); - -// if (loading) { -// return
Loading...
; -// } - -// // Extract unique faculty names -// const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; - -// return ( -//
-//

Faculty Tables with Download Option

-// {uniqueTeachers.map((teacher, index) => { -// // Filter rows for the current teacher -// const teacherData = data.filter((row) => row.Name === teacher); - -// return ( -//
-//

{teacher}'s Table

-// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// {teacherData.map((row, idx) => ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// ))} -// -//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
-// {/* CSV Download Button */} -// -// Download CSV -// -//
-// ); -// })} -//
-// ); -// }; - -// export default ConsolidatedTable; - - - import React, { useState, useEffect, useRef } from "react"; import axios from "axios"; import { CSVLink } from "react-csv"; +import { sendEmail } from "../api"; const ConsolidatedTable = () => { const [data, setData] = useState([]); @@ -201,7 +11,9 @@ const ConsolidatedTable = () => { useEffect(() => { const fetchData = async () => { try { - const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); + const response = await axios.get( + "http://localhost:8080/api/table/consolidated-table" + ); setData(response.data); setLoading(false); } catch (error) { @@ -229,6 +41,46 @@ const ConsolidatedTable = () => { }); }; + const handleSendEmail = async (teacher, teacherData) => { + const csvHeaders = Object.keys(teacherData[0]); + const csvRows = teacherData.map((row) => + csvHeaders + .map( + (header) => `"${row[header]?.toString().replace(/"/g, '""') || ""}"` + ) + .join(",") + ); + const csvContent = [csvHeaders.join(","), ...csvRows].join("\n"); + const fileName = `${teacher.replace(/\s+/g, "_")}_table.csv`; + + try { + const recipientEmail = prompt(`Enter recipient email for ${teacher}:`); + if (!recipientEmail) { + alert("Email is required!"); + return; + } + + // Create email data object + const emailData = { + teacher, + csvData: csvContent, + fileName, + recipientEmail, + }; + + // Call sendEmail from api.js + const response = await sendEmail(emailData); + + // Handle success + alert(`Email sent successfully to ${recipientEmail}`); + console.log("Response from server:", response); + alert(`Email sent successfully to ${recipientEmail}`); + } catch (error) { + console.error("Error sending email:", error); + alert("Failed to send email."); + } + }; + return (

Faculty Tables with Download Options

@@ -285,7 +137,14 @@ const ConsolidatedTable = () => { return (

{teacher}'s Table

- +
@@ -345,6 +204,20 @@ const ConsolidatedTable = () => { > Download {teacher}'s CSV + {/* Send Email Button */} + ); })} diff --git a/client/src/api.js b/client/src/api.js index 3e58baf..5ab5e71 100644 --- a/client/src/api.js +++ b/client/src/api.js @@ -137,3 +137,31 @@ export const updateCourseStatus = async (courseId) => { }); }; +// Send email +export const sendEmail = async (emailData) => { + console.log("Sending email with data:", emailData); + + // Validate input + // if (!emailData.to || !emailData.subject || !emailData.message) { + // const errorMessage = "Missing required fields: to, subject, message"; + // console.error(errorMessage); + // throw new Error(errorMessage); + // } + + try { + const url = `${BASE_URL}/send-email`; + const response = await fetchData(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(emailData), // Pass the email data to the server + }); + + console.log("Email sent successfully:", response); + return response; + } catch (error) { + console.error("Error sending email:", error.message); + throw error; + } +}; \ No newline at end of file diff --git a/server/routes/emailRoutes.js b/server/routes/emailRoutes.js new file mode 100644 index 0000000..17cb07b --- /dev/null +++ b/server/routes/emailRoutes.js @@ -0,0 +1,58 @@ +// /routes/email.js +const express = require("express"); +const nodemailer = require("nodemailer"); +const fs = require("fs"); +const router = express.Router(); + +router.post("/", async (req, res) => { + const { teacher, csvData, fileName, recipientEmail } = req.body; + + if (!teacher || !csvData || !fileName || !recipientEmail) { + return res.status(400).json({ error: "Missing required fields" }); + } + + // Save the CSV data to a temporary file + const filePath = `./${fileName}`; + fs.writeFileSync(filePath, csvData); + + // Configure Nodemailer transporter + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: "swdc.ate@gmail.com", // Replace with your email + pass: "umlc hbkr dpga iywd", // Replace with your app-specific password or token + }, + // tls: { + // rejectUnauthorized: false, // Disable SSL verification + // } + }); + + // Email options + const mailOptions = { + from: "swdc.ate@gmail.com", // Replace with your email + to: recipientEmail, + subject: `CSV File for ${teacher}`, + text: `Attached is the CSV file for ${teacher}.`, + attachments: [ + { + filename: fileName, + path: filePath, + }, + ], + }; + + try { + // Send email + await transporter.sendMail(mailOptions); + + // Delete the temporary file after sending the email + fs.unlinkSync(filePath); + + res.status(200).json({ message: "Email sent successfully" }); + } catch (error) { + console.error("Error sending email:", error); + res.status(500).json({ error: "Failed to send email" }); + } +}); + +module.exports = router; diff --git a/server/server.js b/server/server.js index 5558ae2..2b1baa9 100644 --- a/server/server.js +++ b/server/server.js @@ -15,6 +15,7 @@ const facultyRoutes = require("./routes/facultyRoutes"); const appointmentRoutes = require("./routes/appointmentRoutes"); const optionsRoutes = require("./routes/optionsRoutes"); const consolidatedRoutes = require("./routes/consolidatedRoutes"); +const emailRoutes = require("./routes/emailRoutes"); const Course = require("./models/Course"); // MongoDB Connection @@ -56,6 +57,7 @@ app.use("/api/faculty", facultyRoutes); app.use("/api/appointments", appointmentRoutes); app.use("/api/options", optionsRoutes); app.use("/api/data", consolidatedRoutes); // Moved after `app` initialization +app.use("/api/send-email", emailRoutes); // Google OAuth Routes app.get( From e109559d7ac9c32b65281f0075697b4b2280970d Mon Sep 17 00:00:00 2001 From: Harshitha Shetty <141444342+HarshithaShetty27@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:26:39 +0530 Subject: [PATCH 02/12] XLSX- CSS and mail-xlsx --- client/package-lock.json | 297 +++++++++++++++++- client/package.json | 5 +- client/src/Pages/ConsolidatedTable.jsx | 400 +++++++++++++++++++++---- 3 files changed, 639 insertions(+), 63 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 63b5ea0..2c28366 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -23,7 +23,10 @@ "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", "react-toastify": "^10.0.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "xlsx": "^0.18.5", + "xlsx-js-style": "^1.2.0", + "xlsx-style": "^0.8.13" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -5096,6 +5099,14 @@ "node": ">=8.9" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -6156,6 +6167,18 @@ "node": ">=4" } }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -6315,6 +6338,14 @@ "node": ">= 4.0" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", @@ -6343,6 +6374,14 @@ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, + "node_modules/colors": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "integrity": "sha512-OsSVtHK8Ir8r3+Fxw/b4jS1ZLPXkV6ZxDRJQzeD7qo0SqMXWrHDM71DgYzPMHY8SFJ0Ao+nNU2p1MmwdzKqPrw==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -6431,6 +6470,20 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/confusing-browser-globals": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", @@ -6533,6 +6586,17 @@ "node": ">=10" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -8419,6 +8483,14 @@ "node": ">= 0.8.0" } }, + "node_modules/exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/expect": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", @@ -8561,6 +8633,11 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz", + "integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8977,6 +9054,14 @@ "node": ">= 0.6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -12521,6 +12606,14 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.4.0.tgz", + "integrity": "sha512-m+yvNmYfRCaf1gr5YFT5e3fnSqLnE9McbNyRd0fNycsT0HltS19NKc18fh3Lvl/AIW/ovL6/MQ1JnfFg4G3o4A==", + "dependencies": { + "pako": "~0.2.5" + } + }, "node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -13534,6 +13627,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -15080,6 +15178,17 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "bin": { + "printj": "bin/printj.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -16630,6 +16739,17 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -17669,6 +17789,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -17919,6 +18044,17 @@ "node": ">= 0.8" } }, + "node_modules/voc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/voc/-/voc-1.2.0.tgz", + "integrity": "sha512-BOuDjFFYvJdZO6e/N65AlaDItXo2TgyLjeyRYcqgAPkXpp5yTJcvkL2n+syO1r9Qc5g96tfBD2tuiMhYDmaGcA==", + "bin": { + "voc": "voc.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -18407,6 +18543,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -18804,6 +18956,149 @@ } } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-js-style": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz", + "integrity": "sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==", + "dependencies": { + "adler-32": "~1.2.0", + "cfb": "^1.1.4", + "codepage": "~1.14.0", + "commander": "~2.17.1", + "crc-32": "~1.2.0", + "exit-on-epipe": "~1.0.1", + "fflate": "^0.3.8", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-js-style/node_modules/adler-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", + "integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==", + "dependencies": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + }, + "bin": { + "adler32": "bin/adler32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-js-style/node_modules/codepage": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz", + "integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==", + "dependencies": { + "commander": "~2.14.1", + "exit-on-epipe": "~1.0.1" + }, + "bin": { + "codepage": "bin/codepage.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-js-style/node_modules/codepage/node_modules/commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" + }, + "node_modules/xlsx-js-style/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==" + }, + "node_modules/xlsx-style": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/xlsx-style/-/xlsx-style-0.8.13.tgz", + "integrity": "sha512-Cj3pGUvzrP2q9oowpLP8GyujovTaBGjBRRUlCKPitNvHWj9JDD5+FDPZIM5QQggGb995ZhkuBSsMZOSd5TzIWg==", + "dependencies": { + "adler-32": "", + "cfb": ">=0.10.0", + "codepage": "~1.3.6", + "commander": "", + "crc-32": "", + "jszip": "2.4.0", + "ssf": "~0.8.1" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-style/node_modules/codepage": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.3.8.tgz", + "integrity": "sha512-cjAoQW5L/TCKWRbzt/xGBvhwJKQFhcIVO0jWQtpKQx4gr9qvXNkpRfq6gSmjjA8dB2Is/DPOb7gNwqQXP7UgTQ==", + "dependencies": { + "commander": "", + "concat-stream": "", + "voc": "" + }, + "bin": { + "codepage": "bin/codepage.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-style/node_modules/frac": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/frac/-/frac-0.3.1.tgz", + "integrity": "sha512-1Lzf2jOjhIkRaa013KlxNOn2D9FemmQNeYUDpEIyPeFXmpLvbZXJOlaayMBT6JKXx+afQFgQ1QJ4kaF7Z07QFQ==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx-style/node_modules/ssf": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.8.2.tgz", + "integrity": "sha512-+ZkFDAG+ImJ48DcZvabx6YTrZ67DKkM0kbyOOtH73mbUEvNhQWWgRZrHC8+k7GuGKWQnACYLi7bj0eCt1jmosQ==", + "dependencies": { + "colors": "0.6.2", + "frac": "0.3.1", + "voc": "" + }, + "bin": { + "ssf": "bin/ssf.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", diff --git a/client/package.json b/client/package.json index f466e56..b67e3ac 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,10 @@ "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", "react-toastify": "^10.0.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "xlsx": "^0.18.5", + "xlsx-js-style": "^1.2.0", + "xlsx-style": "^0.8.13" }, "scripts": { "start": "react-scripts start", diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index d6e1f95..f8d2d7b 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -1,12 +1,201 @@ +// import React, { useState, useEffect } from "react"; +// import axios from "axios"; + +// const ConsolidatedTable = () => { +// const [data, setData] = useState([]); +// const [loading, setLoading] = useState(true); + +// useEffect(() => { +// const fetchData = async () => { +// try { +// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); +// setData(response.data); +// setLoading(false); +// } catch (error) { +// console.error("Error fetching table data:", error); +// setLoading(false); +// } +// }; + +// fetchData(); +// }, []); + +// if (loading) { +// return
Loading...
; +// } + +// return ( +//
+//

Consolidated Table

+//
Semester
+// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// {data.map((row, index) => ( +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ))} +// +//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+//
+// ); +// }; + +// export default ConsolidatedTable; + + + +// import React, { useState, useEffect } from "react"; +// import axios from "axios"; +// import { CSVLink } from "react-csv"; + +// const ConsolidatedTable = () => { +// const [data, setData] = useState([]); +// const [loading, setLoading] = useState(true); + +// useEffect(() => { +// const fetchData = async () => { +// try { +// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); +// setData(response.data); +// setLoading(false); +// } catch (error) { +// console.error("Error fetching table data:", error); +// setLoading(false); +// } +// }; + +// fetchData(); +// }, []); + +// if (loading) { +// return
Loading...
; +// } + +// // Extract unique faculty names +// const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; + +// return ( +//
+//

Faculty Tables with Download Option

+// {uniqueTeachers.map((teacher, index) => { +// // Filter rows for the current teacher +// const teacherData = data.filter((row) => row.Name === teacher); + +// return ( +//
+//

{teacher}'s Table

+// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// {teacherData.map((row, idx) => ( +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// ))} +// +//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+// {/* CSV Download Button */} +// +// Download CSV +// +//
+// ); +// })} +//
+// ); +// }; + +// export default ConsolidatedTable; + + + import React, { useState, useEffect, useRef } from "react"; import axios from "axios"; import { CSVLink } from "react-csv"; -import { sendEmail } from "../api"; const ConsolidatedTable = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); - const csvLinksRef = useRef([]); // Ref to store CSV links for bulk download useEffect(() => { const fetchData = async () => { @@ -32,13 +221,131 @@ const ConsolidatedTable = () => { // Extract unique faculty names const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; - const handleBulkDownload = () => { - // Trigger all individual downloads programmatically - csvLinksRef.current.forEach((csvLink) => { - if (csvLink) { - csvLink.link.click(); + const createExcelFile = (teacherData, teacherName) => { + const workbook = XLSX.utils.book_new(); + + // Define header information + const headerInfo = [ + ["Somaiya Vidyavihar University"], + ["K. J. SOMAIYA COLLEGE OF ENGINEERING"], + [ + "Appointment of Internal Examiners for Paper Setting / OR/PR/Assessment/Reassessment", + ], + ["Class: B Tech/M Tech/Honour/Minor"], + ["Department - Computer Engineering"], + [], + ]; + + // Add table headers + const tableHeaders = [ + [ + "Sr No", + "Semester", + "Course Code", + "Course Name", + "Exam Type", + "Year", + "Marks", + "Surname", + "First Name", + "Middle Name", + "Affiliation/College", + "Highest Qualification", + "Career Experience", + "Oral/Practical", + "Assessment", + "Reassessment", + "Paper Setting", + "Moderation", + "PwD Paper Setting", + ], + ]; + + // Add table data + const dataRows = teacherData.map((row, index) => [ + index + 1, + row.semester, + row.courseCode, + row.courseName, + row.examType, + row.year, + row.marks, + row.surname, + row.firstName, + row.middleName, + row.affiliation, + row.qualification, + row.experience, + row.oralPractical, + row.assessment, + row.reassessment, + row.paperSetting, + row.moderation, + row.pwdPaperSetting, + ]); + + // Combine all rows + const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; + const worksheet = XLSX.utils.aoa_to_sheet(sheetData); + + // Add merged cells + worksheet["!merges"] = [ + { s: { r: 0, c: 0 }, e: { r: 0, c: 18 } }, + { s: { r: 1, c: 0 }, e: { r: 1, c: 18 } }, + { s: { r: 2, c: 0 }, e: { r: 2, c: 18 } }, + { s: { r: 3, c: 0 }, e: { r: 3, c: 18 } }, + { s: { r: 4, c: 0 }, e: { r: 4, c: 18 } }, + ]; + + // Define styles + const boldStyle = { + font: { bold: true, name: "Times New Roman", sz: 12 }, + alignment: { horizontal: "center", vertical: "center" }, + }; + + const redStyle = { + font: { color: { rgb: "FF0000" }, name: "Times New Roman", sz: 12 }, + alignment: { horizontal: "center", vertical: "center" }, + }; + + const normalStyle = { + font: { name: "Times New Roman", sz: 12 }, + }; + + // Apply styles to header + const headerRanges = [ + { row: 0, style: boldStyle }, + { row: 1, style: boldStyle }, + { row: 2, style: boldStyle }, + { row: 3, style: boldStyle }, + { row: 4, style: redStyle }, + ]; + + headerRanges.forEach(({ row, style }) => { + for (let col = 0; col <= 18; col++) { + const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); + if (!worksheet[cellAddress]) continue; // Skip empty cells + worksheet[cellAddress].s = style; } }); + + // Apply normal font style to all cells + Object.keys(worksheet).forEach((key) => { + if (worksheet[key] && key[0] !== "!") { + worksheet[key].s = worksheet[key].s || normalStyle; + } + }); + + // Add worksheet to workbook and save file + XLSX.utils.book_append_sheet(workbook, worksheet, teacherName); + XLSX.writeFile(workbook, `${teacherName.replace(/\s+/g, "_")}_Table.xlsx`); + }; + + const bulkDownload = () => { + uniqueTeachers.forEach((teacher) => { + const teacherData = data.filter((row) => row.Name === teacher); + createExcelFile(teacherData, teacher); + }); }; const handleSendEmail = async (teacher, teacherData) => { @@ -83,13 +390,25 @@ const ConsolidatedTable = () => { return (
-

Faculty Tables with Download Options

+

Faculty Tables with Download Options

- {/* Bulk Download Button for Consolidated Data */} -
- + +
- {/* Scrollable Content */}
{ }} > {uniqueTeachers.map((teacher, index) => { - // Filter rows for the current teacher const teacherData = data.filter((row) => row.Name === teacher); return (

{teacher}'s Table

- +
@@ -188,10 +482,9 @@ const ConsolidatedTable = () => { ))}
Semester
- {/* Individual CSV Download Button */} - createExcelFile(teacherData, teacher)} className="btn btn-primary" style={{ padding: "10px 15px", @@ -200,24 +493,9 @@ const ConsolidatedTable = () => { textDecoration: "none", borderRadius: "5px", }} - ref={(el) => (csvLinksRef.current[index] = el)} // Store ref for bulk download > Download {teacher}'s CSV - {/* Send Email Button */} -
); })} From 2841638ba576bd959fc22690cede3e066d5ee1cd Mon Sep 17 00:00:00 2001 From: Harshitha Shetty <141444342+HarshithaShetty27@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:27:39 +0530 Subject: [PATCH 03/12] XLSX- css and xlsx-mail --- client/src/Pages/ConsolidatedTable.jsx | 369 +++++++++---------------- client/src/api.js | 126 +++++++-- server/package-lock.json | 145 ++++++++++ server/package.json | 1 + server/routes/emailRoutes.js | 99 +++++-- 5 files changed, 475 insertions(+), 265 deletions(-) diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index f8d2d7b..9febcfd 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -1,197 +1,7 @@ -// import React, { useState, useEffect } from "react"; -// import axios from "axios"; - -// const ConsolidatedTable = () => { -// const [data, setData] = useState([]); -// const [loading, setLoading] = useState(true); - -// useEffect(() => { -// const fetchData = async () => { -// try { -// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); -// setData(response.data); -// setLoading(false); -// } catch (error) { -// console.error("Error fetching table data:", error); -// setLoading(false); -// } -// }; - -// fetchData(); -// }, []); - -// if (loading) { -// return
Loading...
; -// } - -// return ( -//
-//

Consolidated Table

-// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// {data.map((row, index) => ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// ))} -// -//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
-//
-// ); -// }; - -// export default ConsolidatedTable; - - - -// import React, { useState, useEffect } from "react"; -// import axios from "axios"; -// import { CSVLink } from "react-csv"; - -// const ConsolidatedTable = () => { -// const [data, setData] = useState([]); -// const [loading, setLoading] = useState(true); - -// useEffect(() => { -// const fetchData = async () => { -// try { -// const response = await axios.get("http://localhost:8080/api/table/consolidated-table"); -// setData(response.data); -// setLoading(false); -// } catch (error) { -// console.error("Error fetching table data:", error); -// setLoading(false); -// } -// }; - -// fetchData(); -// }, []); - -// if (loading) { -// return
Loading...
; -// } - -// // Extract unique faculty names -// const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; - -// return ( -//
-//

Faculty Tables with Download Option

-// {uniqueTeachers.map((teacher, index) => { -// // Filter rows for the current teacher -// const teacherData = data.filter((row) => row.Name === teacher); - -// return ( -//
-//

{teacher}'s Table

-// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// {teacherData.map((row, idx) => ( -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// ))} -// -//
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
-// {/* CSV Download Button */} -// -// Download CSV -// -//
-// ); -// })} -//
-// ); -// }; - -// export default ConsolidatedTable; - - - -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect } from "react"; import axios from "axios"; -import { CSVLink } from "react-csv"; +import * as XLSX from "xlsx-js-style"; +import { sendEmail } from "../api"; const ConsolidatedTable = () => { const [data, setData] = useState([]); @@ -236,7 +46,6 @@ const ConsolidatedTable = () => { [], ]; - // Add table headers const tableHeaders = [ [ "Sr No", @@ -261,7 +70,6 @@ const ConsolidatedTable = () => { ], ]; - // Add table data const dataRows = teacherData.map((row, index) => [ index + 1, row.semester, @@ -284,7 +92,6 @@ const ConsolidatedTable = () => { row.pwdPaperSetting, ]); - // Combine all rows const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; const worksheet = XLSX.utils.aoa_to_sheet(sheetData); @@ -299,12 +106,12 @@ const ConsolidatedTable = () => { // Define styles const boldStyle = { - font: { bold: true, name: "Times New Roman", sz: 12 }, + font: { bold: true, name: "Times New Roman", sz: 14 }, alignment: { horizontal: "center", vertical: "center" }, }; const redStyle = { - font: { color: { rgb: "FF0000" }, name: "Times New Roman", sz: 12 }, + font: { bold: true, color: { rgb: "FF0000" }, name: "Times New Roman", sz: 14 }, alignment: { horizontal: "center", vertical: "center" }, }; @@ -312,7 +119,7 @@ const ConsolidatedTable = () => { font: { name: "Times New Roman", sz: 12 }, }; - // Apply styles to header + // Apply styles to headers const headerRanges = [ { row: 0, style: boldStyle }, { row: 1, style: boldStyle }, @@ -329,6 +136,29 @@ const ConsolidatedTable = () => { } }); + // Set column widths for better readability + worksheet["!cols"] = [ + { wch: 10 }, // Sr No + { wch: 12 }, // Semester + { wch: 15 }, // Course Code + { wch: 25 }, // Course Name + { wch: 15 }, // Exam Type + { wch: 10 }, // Year + { wch: 10 }, // Marks + { wch: 15 }, // Surname + { wch: 15 }, // First Name + { wch: 15 }, // Middle Name + { wch: 20 }, // Affiliation + { wch: 20 }, // Qualification + { wch: 15 }, // Career Experience + { wch: 15 }, // Oral/Practical + { wch: 15 }, // Assessment + { wch: 15 }, // Reassessment + { wch: 15 }, // Paper Setting + { wch: 15 }, // Moderation + { wch: 15 }, // PwD Paper Setting + ]; + // Apply normal font style to all cells Object.keys(worksheet).forEach((key) => { if (worksheet[key] && key[0] !== "!") { @@ -349,45 +179,97 @@ const ConsolidatedTable = () => { }; const handleSendEmail = async (teacher, teacherData) => { - const csvHeaders = Object.keys(teacherData[0]); - const csvRows = teacherData.map((row) => - csvHeaders - .map( - (header) => `"${row[header]?.toString().replace(/"/g, '""') || ""}"` - ) - .join(",") - ); - const csvContent = [csvHeaders.join(","), ...csvRows].join("\n"); - const fileName = `${teacher.replace(/\s+/g, "_")}_table.csv`; - + const workbook = XLSX.utils.book_new(); + + const headerInfo = [ + ["Somaiya Vidyavihar University"], + ["K. J. SOMAIYA COLLEGE OF ENGINEERING"], + [ + "Appointment of Internal Examiners for Paper Setting / OR/PR/Assessment/Reassessment", + ], + ["Class: B Tech/M Tech/Honour/Minor"], + ["Department - Computer Engineering"], + [], + ]; + + const tableHeaders = [ + [ + "Sr No", + "Semester", + "Course Code", + "Course Name", + "Exam Type", + "Year", + "Marks", + "Surname", + "First Name", + "Middle Name", + "Affiliation/College", + "Highest Qualification", + "Career Experience", + "Oral/Practical", + "Assessment", + "Reassessment", + "Paper Setting", + "Moderation", + "PwD Paper Setting", + ], + ]; + + const dataRows = teacherData.map((row, index) => [ + index + 1, + row.semester, + row.courseCode, + row.courseName, + row.examType, + row.year, + row.marks, + row.surname, + row.firstName, + row.middleName, + row.affiliation, + row.qualification, + row.experience, + row.oralPractical, + row.assessment, + row.reassessment, + row.paperSetting, + row.moderation, + row.pwdPaperSetting, + ]); + + const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; + const worksheet = XLSX.utils.aoa_to_sheet(sheetData); + XLSX.utils.book_append_sheet(workbook, worksheet, teacher); + + const fileName = `${teacher.replace(/\s+/g, "_")}_table.xlsx`; + const excelBlob = XLSX.write(workbook, { + bookType: "xlsx", + type: "array", + }); + + const file = new File([excelBlob], fileName, { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + + const formData = new FormData(); + formData.append("teacher", teacher); + formData.append("fileName", fileName); + const recipientEmail = prompt(`Enter recipient email for ${teacher}:`); + formData.append("recipientEmail", recipientEmail); + formData.append("file", file); + try { - const recipientEmail = prompt(`Enter recipient email for ${teacher}:`); - if (!recipientEmail) { - alert("Email is required!"); - return; - } - - // Create email data object - const emailData = { - teacher, - csvData: csvContent, - fileName, - recipientEmail, - }; - - // Call sendEmail from api.js - const response = await sendEmail(emailData); - - // Handle success + const response = await sendEmail(formData); alert(`Email sent successfully to ${recipientEmail}`); console.log("Response from server:", response); - alert(`Email sent successfully to ${recipientEmail}`); } catch (error) { console.error("Error sending email:", error); alert("Failed to send email."); } }; + return (

Faculty Tables with Download Options

@@ -437,8 +319,15 @@ const ConsolidatedTable = () => { return (
-

{teacher}'s Table

- +

{teacher}'s Table

+
@@ -494,8 +383,22 @@ const ConsolidatedTable = () => { borderRadius: "5px", }} > - Download {teacher}'s CSV - + Download {teacher}'s Table + + {/* Send Email Button */} + ); })} diff --git a/client/src/api.js b/client/src/api.js index 5ab5e71..5e942f3 100644 --- a/client/src/api.js +++ b/client/src/api.js @@ -1,4 +1,5 @@ const BASE_URL = "http://localhost:8080/api"; +const XLSX = require("xlsx-js-style"); // Helper function for handling fetch requests const fetchData = async (url, options) => { @@ -138,30 +139,123 @@ export const updateCourseStatus = async (courseId) => { }; // Send email -export const sendEmail = async (emailData) => { - console.log("Sending email with data:", emailData); +const handleSendEmail = async (teacher, teacherData) => { + const workbook = XLSX.utils.book_new(); - // Validate input - // if (!emailData.to || !emailData.subject || !emailData.message) { - // const errorMessage = "Missing required fields: to, subject, message"; - // console.error(errorMessage); - // throw new Error(errorMessage); - // } + const headerInfo = [ + ["Somaiya Vidyavihar University"], + ["K. J. SOMAIYA COLLEGE OF ENGINEERING"], + [ + "Appointment of Internal Examiners for Paper Setting / OR/PR/Assessment/Reassessment", + ], + ["Class: B Tech/M Tech/Honour/Minor"], + ["Department - Computer Engineering"], + [], + ]; + + const tableHeaders = [ + [ + "Sr No", + "Semester", + "Course Code", + "Course Name", + "Exam Type", + "Year", + "Marks", + "Surname", + "First Name", + "Middle Name", + "Affiliation/College", + "Highest Qualification", + "Career Experience", + "Oral/Practical", + "Assessment", + "Reassessment", + "Paper Setting", + "Moderation", + "PwD Paper Setting", + ], + ]; + + const dataRows = teacherData.map((row, index) => [ + index + 1, + row.semester, + row.courseCode, + row.courseName, + row.examType, + row.year, + row.marks, + row.surname, + row.firstName, + row.middleName, + row.affiliation, + row.qualification, + row.experience, + row.oralPractical, + row.assessment, + row.reassessment, + row.paperSetting, + row.moderation, + row.pwdPaperSetting, + ]); + + const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; + const worksheet = XLSX.utils.aoa_to_sheet(sheetData); + XLSX.utils.book_append_sheet(workbook, worksheet, teacher); + + const fileName = `${teacher.replace(/\s+/g, "_")}_table.xlsx`; + const excelBlob = XLSX.write(workbook, { + bookType: "xlsx", + type: "array", + }); + + const file = new File([excelBlob], fileName, { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + + const formData = new FormData(); + formData.append("teacher", teacher); + formData.append("fileName", fileName); + const recipientEmail = prompt(`Enter recipient email for ${teacher}:`); + formData.append("recipientEmail", recipientEmail); + formData.append("file", file); + try { + const response = await sendEmail(formData); + alert(`Email sent successfully to ${recipientEmail}`); + console.log("Response from server:", response); + } catch (error) { + console.error("Error sending email:", error); + alert("Failed to send email."); + } +}; + + +export const sendEmail = async (formData) => { try { const url = `${BASE_URL}/send-email`; - const response = await fetchData(url, { + const response = await fetch(url, { method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(emailData), // Pass the email data to the server + body: formData, // Directly pass FormData }); - console.log("Email sent successfully:", response); - return response; + if (!response.ok) { + let errorDetails = {}; + try { + errorDetails = await response.json(); + } catch (err) { + console.warn("Failed to parse error details:", err); + } + throw new Error( + `Error: ${response.statusText} (${response.status}) - ${ + errorDetails.message || "No details available" + }` + ); + } + + return await response.json(); } catch (error) { - console.error("Error sending email:", error.message); + console.error( error.message); throw error; } }; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index d2be342..8fb5c17 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -22,6 +22,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.9.5", "mongoose-findorcreate": "^4.0.0", + "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.13", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", @@ -119,6 +120,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -270,6 +276,22 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -330,6 +352,20 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/connect-mongo": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.1.0.tgz", @@ -406,6 +442,11 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1163,6 +1204,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -1390,6 +1436,25 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mongodb": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", @@ -1530,6 +1595,23 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1818,6 +1900,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1895,6 +1982,25 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -2084,6 +2190,27 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2154,6 +2281,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -2194,6 +2326,11 @@ "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", "license": "BSD" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -2245,6 +2382,14 @@ "engines": { "node": ">=16" } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } } } } diff --git a/server/package.json b/server/package.json index c0df411..9cc037b 100644 --- a/server/package.json +++ b/server/package.json @@ -31,6 +31,7 @@ "jsonwebtoken": "^9.0.2", "mongoose": "^8.9.5", "mongoose-findorcreate": "^4.0.0", + "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.13", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", diff --git a/server/routes/emailRoutes.js b/server/routes/emailRoutes.js index 17cb07b..e8af839 100644 --- a/server/routes/emailRoutes.js +++ b/server/routes/emailRoutes.js @@ -1,20 +1,90 @@ -// /routes/email.js +// // /routes/email.js +// const express = require("express"); +// const nodemailer = require("nodemailer"); +// const fs = require("fs"); +// const router = express.Router(); + +// router.post("/", async (req, res) => { +// const { teacher, csvData, fileName, recipientEmail } = req.body; + +// if (!teacher || !csvData || !fileName || !recipientEmail) { +// return res.status(400).json({ error: "Missing required fields" }); +// } + +// // Save the CSV data to a temporary file +// const filePath = `./${fileName}`; +// fs.writeFileSync(filePath, csvData); + +// // Configure Nodemailer transporter +// const transporter = nodemailer.createTransport({ +// service: "gmail", +// auth: { +// user: "swdc.ate@gmail.com", // Replace with your email +// pass: "umlc hbkr dpga iywd", // Replace with your app-specific password or token +// }, +// // tls: { +// // rejectUnauthorized: false, // Disable SSL verification +// // } +// }); + +// // Email options +// const mailOptions = { +// from: "swdc.ate@gmail.com", // Replace with your email +// to: recipientEmail, +// subject: `Excel File for ${teacher}`, +// text: `Attached is the Excel file for ${teacher}.`, +// attachments: [ +// { +// filename: fileName, +// path: filePath, +// contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // MIME type for xlsx +// }, +// ], +// }; + +// try { +// // Send email +// await transporter.sendMail(mailOptions); + +// // Delete the temporary file after sending the email +// fs.unlinkSync(filePath); + +// res.status(200).json({ message: "Email sent successfully" }); +// } catch (error) { +// console.error("Error sending email:", error); +// res.status(500).json({ error: "Failed to send email" }); +// } +// }); + +// module.exports = router; + + const express = require("express"); const nodemailer = require("nodemailer"); const fs = require("fs"); +const multer = require("multer"); const router = express.Router(); -router.post("/", async (req, res) => { - const { teacher, csvData, fileName, recipientEmail } = req.body; +// Setup multer for handling file uploads +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, "./"); // Save to the current directory (you can customize this) + }, + filename: function (req, file, cb) { + cb(null, file.originalname); // Use the original file name + }, +}); - if (!teacher || !csvData || !fileName || !recipientEmail) { - return res.status(400).json({ error: "Missing required fields" }); +const upload = multer({ storage }); + +// Route to handle email sending with file attachment +router.post("/", upload.single("file"), async (req, res) => { + const { teacher, fileName, recipientEmail } = req.body; + + if (!teacher || !fileName || !recipientEmail || !req.file) { + return res.status(400).json({ error: "Missing required fields or file" }); } - // Save the CSV data to a temporary file - const filePath = `./${fileName}`; - fs.writeFileSync(filePath, csvData); - // Configure Nodemailer transporter const transporter = nodemailer.createTransport({ service: "gmail", @@ -22,21 +92,18 @@ router.post("/", async (req, res) => { user: "swdc.ate@gmail.com", // Replace with your email pass: "umlc hbkr dpga iywd", // Replace with your app-specific password or token }, - // tls: { - // rejectUnauthorized: false, // Disable SSL verification - // } }); // Email options const mailOptions = { from: "swdc.ate@gmail.com", // Replace with your email to: recipientEmail, - subject: `CSV File for ${teacher}`, - text: `Attached is the CSV file for ${teacher}.`, + subject: `Excel File for ${teacher}`, + text: `Attached is the Excel file for ${teacher}.`, attachments: [ { filename: fileName, - path: filePath, + path: req.file.path, // Use the uploaded file's path }, ], }; @@ -46,7 +113,7 @@ router.post("/", async (req, res) => { await transporter.sendMail(mailOptions); // Delete the temporary file after sending the email - fs.unlinkSync(filePath); + fs.unlinkSync(req.file.path); res.status(200).json({ message: "Email sent successfully" }); } catch (error) { From add7192ccf3e28e84370d3490d642416eec9dca2 Mon Sep 17 00:00:00 2001 From: amNobodyyy <131776812+amNobodyyy@users.noreply.github.com> Date: Fri, 24 Jan 2025 01:11:21 +0530 Subject: [PATCH 04/12] auto email --- client/src/Pages/ConsolidatedTable.jsx | 219 +++++++++++++------------ server/routes/consolidatedRoutes.js | 1 + 2 files changed, 115 insertions(+), 105 deletions(-) diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index 9febcfd..9d7e9c1 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -33,7 +33,7 @@ const ConsolidatedTable = () => { const createExcelFile = (teacherData, teacherName) => { const workbook = XLSX.utils.book_new(); - + // Define header information const headerInfo = [ ["Somaiya Vidyavihar University"], @@ -45,7 +45,7 @@ const ConsolidatedTable = () => { ["Department - Computer Engineering"], [], ]; - + const tableHeaders = [ [ "Sr No", @@ -55,9 +55,7 @@ const ConsolidatedTable = () => { "Exam Type", "Year", "Marks", - "Surname", - "First Name", - "Middle Name", + "Name", "Affiliation/College", "Highest Qualification", "Career Experience", @@ -69,7 +67,7 @@ const ConsolidatedTable = () => { "PwD Paper Setting", ], ]; - + const dataRows = teacherData.map((row, index) => [ index + 1, row.semester, @@ -78,9 +76,7 @@ const ConsolidatedTable = () => { row.examType, row.year, row.marks, - row.surname, - row.firstName, - row.middleName, + row.Name, row.affiliation, row.qualification, row.experience, @@ -91,10 +87,10 @@ const ConsolidatedTable = () => { row.moderation, row.pwdPaperSetting, ]); - + const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; const worksheet = XLSX.utils.aoa_to_sheet(sheetData); - + // Add merged cells worksheet["!merges"] = [ { s: { r: 0, c: 0 }, e: { r: 0, c: 18 } }, @@ -103,22 +99,27 @@ const ConsolidatedTable = () => { { s: { r: 3, c: 0 }, e: { r: 3, c: 18 } }, { s: { r: 4, c: 0 }, e: { r: 4, c: 18 } }, ]; - + // Define styles const boldStyle = { font: { bold: true, name: "Times New Roman", sz: 14 }, alignment: { horizontal: "center", vertical: "center" }, }; - + const redStyle = { - font: { bold: true, color: { rgb: "FF0000" }, name: "Times New Roman", sz: 14 }, + font: { + bold: true, + color: { rgb: "FF0000" }, + name: "Times New Roman", + sz: 14, + }, alignment: { horizontal: "center", vertical: "center" }, }; - + const normalStyle = { font: { name: "Times New Roman", sz: 12 }, }; - + // Apply styles to headers const headerRanges = [ { row: 0, style: boldStyle }, @@ -127,7 +128,7 @@ const ConsolidatedTable = () => { { row: 3, style: boldStyle }, { row: 4, style: redStyle }, ]; - + headerRanges.forEach(({ row, style }) => { for (let col = 0; col <= 18; col++) { const cellAddress = XLSX.utils.encode_cell({ r: row, c: col }); @@ -135,7 +136,7 @@ const ConsolidatedTable = () => { worksheet[cellAddress].s = style; } }); - + // Set column widths for better readability worksheet["!cols"] = [ { wch: 10 }, // Sr No @@ -158,14 +159,14 @@ const ConsolidatedTable = () => { { wch: 15 }, // Moderation { wch: 15 }, // PwD Paper Setting ]; - + // Apply normal font style to all cells Object.keys(worksheet).forEach((key) => { if (worksheet[key] && key[0] !== "!") { worksheet[key].s = worksheet[key].s || normalStyle; } }); - + // Add worksheet to workbook and save file XLSX.utils.book_append_sheet(workbook, worksheet, teacherName); XLSX.writeFile(workbook, `${teacherName.replace(/\s+/g, "_")}_Table.xlsx`); @@ -179,100 +180,108 @@ const ConsolidatedTable = () => { }; const handleSendEmail = async (teacher, teacherData) => { - const workbook = XLSX.utils.book_new(); - - const headerInfo = [ - ["Somaiya Vidyavihar University"], - ["K. J. SOMAIYA COLLEGE OF ENGINEERING"], - [ - "Appointment of Internal Examiners for Paper Setting / OR/PR/Assessment/Reassessment", - ], - ["Class: B Tech/M Tech/Honour/Minor"], - ["Department - Computer Engineering"], - [], - ]; - - const tableHeaders = [ - [ - "Sr No", - "Semester", - "Course Code", - "Course Name", - "Exam Type", - "Year", - "Marks", - "Surname", - "First Name", - "Middle Name", - "Affiliation/College", - "Highest Qualification", - "Career Experience", - "Oral/Practical", - "Assessment", - "Reassessment", - "Paper Setting", - "Moderation", - "PwD Paper Setting", - ], - ]; - - const dataRows = teacherData.map((row, index) => [ - index + 1, - row.semester, - row.courseCode, - row.courseName, - row.examType, - row.year, - row.marks, - row.surname, - row.firstName, - row.middleName, - row.affiliation, - row.qualification, - row.experience, - row.oralPractical, - row.assessment, - row.reassessment, - row.paperSetting, - row.moderation, - row.pwdPaperSetting, - ]); - - const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; - const worksheet = XLSX.utils.aoa_to_sheet(sheetData); - XLSX.utils.book_append_sheet(workbook, worksheet, teacher); - - const fileName = `${teacher.replace(/\s+/g, "_")}_table.xlsx`; - const excelBlob = XLSX.write(workbook, { - bookType: "xlsx", - type: "array", - }); - - const file = new File([excelBlob], fileName, { - type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }); - - const formData = new FormData(); - formData.append("teacher", teacher); - formData.append("fileName", fileName); - const recipientEmail = prompt(`Enter recipient email for ${teacher}:`); - formData.append("recipientEmail", recipientEmail); - formData.append("file", file); - + const facultyId = teacherData[0].facultyId; // This assumes all rows for a teacher have the same facultyId + try { - const response = await sendEmail(formData); - alert(`Email sent successfully to ${recipientEmail}`); - console.log("Response from server:", response); + // Fetch email from the backend + const response = await axios.get( + `http://localhost:8080/api/faculty/email/${facultyId}` + ); + const facultyEmail = response.data.email; + const workbook = XLSX.utils.book_new(); + + const headerInfo = [ + ["Somaiya Vidyavihar University"], + ["K. J. SOMAIYA COLLEGE OF ENGINEERING"], + [ + "Appointment of Internal Examiners for Paper Setting / OR/PR/Assessment/Reassessment", + ], + ["Class: B Tech/M Tech/Honour/Minor"], + ["Department - Computer Engineering"], + [], + ]; + + const tableHeaders = [ + [ + "Sr No", + "Semester", + "Course Code", + "Course Name", + "Exam Type", + "Year", + "Marks", + "Name", + "Affiliation/College", + "Highest Qualification", + "Career Experience", + "Oral/Practical", + "Assessment", + "Reassessment", + "Paper Setting", + "Moderation", + "PwD Paper Setting", + ], + ]; + + const dataRows = teacherData.map((row, index) => [ + index + 1, + row.semester, + row.courseCode, + row.courseName, + row.examType, + row.year, + row.marks, + row.Name, + row.affiliation, + row.qualification, + row.experience, + row.oralPractical, + row.assessment, + row.reassessment, + row.paperSetting, + row.moderation, + row.pwdPaperSetting, + ]); + + const sheetData = [...headerInfo, ...tableHeaders, ...dataRows]; + const worksheet = XLSX.utils.aoa_to_sheet(sheetData); + XLSX.utils.book_append_sheet(workbook, worksheet, teacher); + + const fileName = `${teacher.replace(/\s+/g, "_")}_table.xlsx`; + const excelBlob = XLSX.write(workbook, { + bookType: "xlsx", + type: "array", + }); + + const file = new File([excelBlob], fileName, { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + + const formData = new FormData(); + formData.append("teacher", teacher); + formData.append("fileName", fileName); + formData.append("recipientEmail", facultyEmail); + formData.append("file", file); + + try { + const response = await sendEmail(formData); + alert(`Email sent successfully to ${facultyEmail}`); + console.log("Response from server:", response); + } catch (error) { + console.error("Error sending email:", error); + alert("Failed to send email."); + } } catch (error) { console.error("Error sending email:", error); alert("Failed to send email."); } }; - return (
-

Faculty Tables with Download Options

+

+ Faculty Tables with Download Options +

))} @@ -256,6 +224,75 @@ const CourseForm = () => { )}
))} +
+ + + + + {errors.examPeriod && ( + {errors.examPeriod} + )} +
diff --git a/client/src/api.js b/client/src/api.js index 826784c..5349583 100644 --- a/client/src/api.js +++ b/client/src/api.js @@ -78,6 +78,7 @@ export const fetchOptions = async () => { } }; + // Save multiple appointments to MongoDB export const saveAppointment = async (appointmentsData) => { console.log("Saving appointments with payload:", appointmentsData); diff --git a/server/models/Appointment.js b/server/models/Appointment.js index 4b19fe9..e3b9efc 100644 --- a/server/models/Appointment.js +++ b/server/models/Appointment.js @@ -2,19 +2,16 @@ const mongoose = require("mongoose"); const { v4: uuidv4 } = require("uuid"); const AppointmentSchema = new mongoose.Schema({ - appointmentId: { - type: String, - required: true, - unique: true, - default: uuidv4 - }, + appointmentId: { type: String, required: true, unique: true, default: uuidv4 }, facultyId: { type: String, required: true }, facultyName: { type: String, required: true }, courseId: { type: String, required: true }, courseName: { type: String, required: true }, task: { type: String, required: true }, + examPeriod: { type: String, required: true }, // New field for exam period }); module.exports = mongoose.model("Appointment", AppointmentSchema); + diff --git a/server/routes/appointmentRoutes.js b/server/routes/appointmentRoutes.js index 8b50f46..683a656 100644 --- a/server/routes/appointmentRoutes.js +++ b/server/routes/appointmentRoutes.js @@ -7,17 +7,22 @@ const Course = require("../models/Course"); // Save multiple appointments router.post("/", async (req, res) => { try { - const { appointments } = req.body; // Expecting an array of appointments + const { appointments } = req.body; if (!appointments || !Array.isArray(appointments)) { return res.status(400).json({ error: "Invalid or missing data" }); } const savedAppointments = []; for (const appointment of appointments) { - const { facultyId, courseId, tasks } = appointment; + const { facultyId, courseId, tasks, examPeriod } = appointment; - // Validate input data - if (!facultyId || !courseId || !Array.isArray(tasks) || tasks.length === 0) { + if ( + !facultyId || + !courseId || + !Array.isArray(tasks) || + tasks.length === 0 || + !examPeriod + ) { return res.status(400).json({ error: "Invalid appointment data" }); } @@ -30,7 +35,6 @@ router.post("/", async (req, res) => { }); } - // Save each task as a separate appointment for (const task of tasks) { const newAppointment = new Appointment({ facultyId, @@ -38,6 +42,7 @@ router.post("/", async (req, res) => { courseId, courseName: course.name, task, + examPeriod, }); const savedAppointment = await newAppointment.save(); savedAppointments.push(savedAppointment); From 3b189d42704ac9b43b6488c7b9824192a8f931c3 Mon Sep 17 00:00:00 2001 From: Harshitha Shetty <141444342+HarshithaShetty27@users.noreply.github.com> Date: Sat, 25 Jan 2025 00:22:06 +0530 Subject: [PATCH 10/12] Pagination --- client/src/Pages/ConsolidatedTable.jsx | 55 +++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index a87acaf..bdea1b0 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -7,6 +7,8 @@ import { createExcelBook } from "../api"; const ConsolidatedTable = () => { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); + const [currentPage, setCurrentPage] = useState(1); + const tablesPerPage = 5; useEffect(() => { const fetchData = async () => { @@ -32,6 +34,21 @@ const ConsolidatedTable = () => { // Extract unique faculty names const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; + //pagination + const indexOfLastTable = currentPage * tablesPerPage; + const indexOfFirstTable = indexOfLastTable - tablesPerPage; + const currentTeachers = uniqueTeachers.slice(indexOfFirstTable,indexOfLastTable); + + const totalPages = Math.ceil(uniqueTeachers.length/tablesPerPage); + + const handleNextPage = ()=>{ + if(currentPage < totalPages) setCurrentPage((prevPage)=>prevPage+1); + }; + + const handlePrevPage = ()=>{ + if(currentPage > 1) setCurrentPage((prevPage)=>prevPage-1); + }; + const createExcelFile = (teacherData, teacherName) => { const workbook = createExcelBook(teacherData, teacherName); XLSX.writeFile(workbook, `${teacherName.replace(/\s+/g, "_")}_Table.xlsx`); @@ -129,7 +146,7 @@ const ConsolidatedTable = () => { backgroundColor: "#f9f9f9", }} > - {uniqueTeachers.map((teacher, index) => { + {currentTeachers.map((teacher, index) => { const teacherData = data.filter((row) => row.Name === teacher); return (
@@ -217,6 +234,42 @@ const ConsolidatedTable = () => { ); })}
+ + {/* pagination controls */} +
+ + + Page {currentPage} of {totalPages} + + +
); }; From 585615cdf09bb8a2cb052044a77febe0c81c3e16 Mon Sep 17 00:00:00 2001 From: Harshitha Shetty <141444342+HarshithaShetty27@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:12:07 +0530 Subject: [PATCH 11/12] UI for consolidated --- client/src/Pages/ConsolidatedTable.jsx | 232 +++++++++++++------------ 1 file changed, 125 insertions(+), 107 deletions(-) diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index bdea1b0..4d47e6e 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -9,6 +9,7 @@ const ConsolidatedTable = () => { const [loading, setLoading] = useState(true); const [currentPage, setCurrentPage] = useState(1); const tablesPerPage = 5; + const [expandedTeacher, setExpandedTeacher] = useState(null); useEffect(() => { const fetchData = async () => { @@ -34,19 +35,19 @@ const ConsolidatedTable = () => { // Extract unique faculty names const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; - //pagination + // Pagination const indexOfLastTable = currentPage * tablesPerPage; const indexOfFirstTable = indexOfLastTable - tablesPerPage; - const currentTeachers = uniqueTeachers.slice(indexOfFirstTable,indexOfLastTable); + const currentTeachers = uniqueTeachers.slice(indexOfFirstTable, indexOfLastTable); - const totalPages = Math.ceil(uniqueTeachers.length/tablesPerPage); + const totalPages = Math.ceil(uniqueTeachers.length / tablesPerPage); - const handleNextPage = ()=>{ - if(currentPage < totalPages) setCurrentPage((prevPage)=>prevPage+1); + const handleNextPage = () => { + if (currentPage < totalPages) setCurrentPage((prevPage) => prevPage + 1); }; - const handlePrevPage = ()=>{ - if(currentPage > 1) setCurrentPage((prevPage)=>prevPage-1); + const handlePrevPage = () => { + if (currentPage > 1) setCurrentPage((prevPage) => prevPage - 1); }; const createExcelFile = (teacherData, teacherName) => { @@ -82,7 +83,7 @@ const ConsolidatedTable = () => { const formData = new FormData(); formData.append("teacher", teacher); - formData.append("fileName", fileName); + formData.append("fileName", fileName); formData.append("recipientEmail", facultyEmail); formData.append("file", file); @@ -150,104 +151,122 @@ const ConsolidatedTable = () => { const teacherData = data.filter((row) => row.Name === teacher); return (
-

{teacher}'s Table

-
Semester
setExpandedTeacher(expandedTeacher === teacher ? null : teacher)} > - - - - - - - - - - - - - - - - - - - - - - {teacherData.map((row, idx) => ( - - - - - - - - - - - - - - - - - - - ))} - -
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+

{teacher}'s Table

+
+ + +
+
- - {/* Send Email Button */} - + {expandedTeacher === teacher && ( + + + + + + + + + + + + + + + + + + + + + + + {teacherData.map((row, idx) => ( + + + + + + + + + + + + + + + + + + + ))} + +
SemesterCourse CodeCourse NameExam TypeYearMarksNameAffiliation/CollegeHighest QualificationCareer ExperienceOral/PracticalAssessmentReassessmentPaper SettingModerationPwD Paper Setting
{row.semester}{row.courseCode}{row.courseName}{row.examType}{row.year}{row.marks}{row.Name}{row.affiliation}{row.qualification}{row.experience}{row.oralPractical}{row.assessment}{row.reassessment}{row.paperSetting}{row.moderation}{row.pwdPaperSetting}
+ )}
); })}
- {/* pagination controls */} -
+ {/* Pagination controls */} +
@@ -255,17 +274,16 @@ const ConsolidatedTable = () => { Page {currentPage} of {totalPages} From a4b3babde5cd33eeda557e9e892a49660df1b944 Mon Sep 17 00:00:00 2001 From: Harshitha Shetty <141444342+HarshithaShetty27@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:28:04 +0530 Subject: [PATCH 12/12] Send email to all faculties --- client/src/Pages/ConsolidatedTable.jsx | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/client/src/Pages/ConsolidatedTable.jsx b/client/src/Pages/ConsolidatedTable.jsx index 4d47e6e..7f70b7f 100644 --- a/client/src/Pages/ConsolidatedTable.jsx +++ b/client/src/Pages/ConsolidatedTable.jsx @@ -35,7 +35,7 @@ const ConsolidatedTable = () => { // Extract unique faculty names const uniqueTeachers = [...new Set(data.map((row) => row.Name))]; - // Pagination + // Pagination const indexOfLastTable = currentPage * tablesPerPage; const indexOfFirstTable = indexOfLastTable - tablesPerPage; const currentTeachers = uniqueTeachers.slice(indexOfFirstTable, indexOfLastTable); @@ -65,7 +65,6 @@ const ConsolidatedTable = () => { const handleSendEmail = async (teacher, teacherData) => { const facultyId = teacherData[0].facultyId; // This assumes all rows for a teacher have the same facultyId try { - // Fetch email from the backend const response = await axios.get( `http://localhost:8080/api/faculty/${facultyId}` ); @@ -101,6 +100,15 @@ const ConsolidatedTable = () => { } }; + // Send emails to all teachers + const sendEmailsToAllTeachers = async () => { + for (let teacher of uniqueTeachers) { + const teacherData = data.filter((row) => row.Name === teacher); + await handleSendEmail(teacher, teacherData); // Wait for each email to be sent before proceeding to the next + } + alert("Emails sent to all teachers."); + }; + return (

@@ -135,6 +143,21 @@ const ConsolidatedTable = () => { > Download Consolidated Table + +