From 380cb4d999711b04074285a8eed163733f787c94 Mon Sep 17 00:00:00 2001 From: ARJUN S THAMPI <61703062+arjun-thampi@users.noreply.github.com> Date: Wed, 25 Mar 2026 12:48:01 +0530 Subject: [PATCH] feat : add page academics & research --- .../academicsResearch.controller.js | 35 +++- frontend/src/App.tsx | 2 + frontend/src/api/academics.ts | 11 ++ frontend/src/components/layout/Sidebar.tsx | 4 + frontend/src/pages/Academics.tsx | 172 ++++++++++++++++++ frontend/src/pages/email.tsx | 26 ++- 6 files changed, 232 insertions(+), 18 deletions(-) create mode 100644 frontend/src/api/academics.ts create mode 100644 frontend/src/pages/Academics.tsx diff --git a/backend/src/controllers/academicsResearch.controller.js b/backend/src/controllers/academicsResearch.controller.js index cf72e49..e89edb3 100644 --- a/backend/src/controllers/academicsResearch.controller.js +++ b/backend/src/controllers/academicsResearch.controller.js @@ -1,10 +1,13 @@ import prisma from "../prisma/client.js"; +import { sendEmail } from "../utils/sendEmail.js"; +import { getEmailsByType } from "../utils/getEmailByTypes.js"; // CREATE ACADEMICS & RESEARCH export const createAcademicsResearch = async (req, res) => { try { - const {fullName, number, emailId, subject, courseName, message} = req.body; + const { fullName, number, emailId, subject, courseName, message } = + req.body; if (!fullName || !number) { return res.status(400).json({ @@ -24,6 +27,32 @@ export const createAcademicsResearch = async (req, res) => { }, }); + try { + const emailList = await getEmailsByType("ACADEMICS"); + + if (emailList && emailList.length > 0) { + await sendEmail({ + to: emailList, + subject: "New Academics & Research Inquiry", + html: ` +

New Academics & Research Inquiry

+ +

Name: ${fullName}

+

Phone: ${number}

+

Email: ${emailId || "-"}

+ +

Course: ${courseName || "-"}

+

Subject: ${subject || "-"}

+ +

Message:

+

${message || "-"}

+ `, + }); + } + } catch (err) { + console.error("Academics email failed:", err); + } + res.status(200).json({ success: true, status: 200, @@ -65,7 +94,7 @@ export const getAcademicsResearch = async (req, res) => { export const getSingleAcademicsResearch = async (req, res) => { try { - const {id} = req.params; + const { id } = req.params; const data = await prisma.academicsResearch.findUnique({ where: { @@ -96,7 +125,7 @@ export const getSingleAcademicsResearch = async (req, res) => { export const deleteAcademicsResearch = async (req, res) => { try { - const {id} = req.params; + const { id } = req.params; await prisma.academicsResearch.delete({ where: { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fb6e48f..6584407 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ import EmailPage from "./pages/email"; import CareerPage from "./pages/Career"; import CandidatePage from "./pages/candidates"; import InquiryPage from "./pages/inquiry"; +import AcademicsPage from "./pages/Academics"; export default function App() { return ( @@ -40,6 +41,7 @@ export default function App() { } /> } /> } /> + } /> diff --git a/frontend/src/api/academics.ts b/frontend/src/api/academics.ts new file mode 100644 index 0000000..57c6903 --- /dev/null +++ b/frontend/src/api/academics.ts @@ -0,0 +1,11 @@ +import apiClient from "@/api/client"; + +export const getAcademicsApi = async () => { + const res = await apiClient.get("/academics/getAll"); + return res.data; +}; + +export const deleteAcademicsApi = async (id: number) => { + const res = await apiClient.delete(`/academics/${id}`); + return res.data; +}; diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index a7413e5..95e2762 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -31,6 +31,10 @@ export default function Sidebar() { name: "Inquiry", path: "/inquiry", }, + { + name: "Academics & Research", + path: "/academics", + }, { name: "Email", path: "/email", diff --git a/frontend/src/pages/Academics.tsx b/frontend/src/pages/Academics.tsx new file mode 100644 index 0000000..dd8aa09 --- /dev/null +++ b/frontend/src/pages/Academics.tsx @@ -0,0 +1,172 @@ +import { useState, useEffect, useCallback } from "react"; + +import { getAcademicsApi, deleteAcademicsApi } from "@/api/academics"; +import { exportToExcel } from "@/utils/exportToExcel"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +import { Loader2, Trash, RefreshCw, Download } from "lucide-react"; + +export default function AcademicsPage() { + const [records, setRecords] = useState([]); + const [loading, setLoading] = useState(true); + + const [searchText, setSearchText] = useState(""); + + const fetchAll = useCallback(async () => { + setLoading(true); + try { + const res = await getAcademicsApi(); + setRecords(res?.data || []); + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchAll(); + }, [fetchAll]); + + const filteredRecords = records.filter((item) => { + return ( + item.fullName?.toLowerCase().includes(searchText.toLowerCase()) || + item.number?.includes(searchText) || + item.emailId?.toLowerCase().includes(searchText.toLowerCase()) || + item.subject?.toLowerCase().includes(searchText.toLowerCase()) || + item.courseName?.toLowerCase().includes(searchText.toLowerCase()) + ); + }); + + async function handleDelete(id: number) { + if (!confirm("Delete record?")) return; + await deleteAcademicsApi(id); + fetchAll(); + } + + const handleExport = () => { + const exportData = filteredRecords.map((item) => ({ + ID: item.id, + Name: item.fullName, + Phone: item.number, + Email: item.emailId, + Course: item.courseName, + Subject: item.subject, + Message: item.message, + Date: new Date(item.createdAt).toLocaleDateString(), + })); + + exportToExcel(exportData, "academics"); + }; + + return ( +
+
+

Academics & Research

+ +
+ setSearchText(e.target.value)} + className="w-[260px]" + /> + + + + +
+
+ + + + Academics Records + + + +
+ + + + ID + Name + Phone + Email + Course + Subject + Message + Date + Actions + + + + + {loading ? ( + + + + + + ) : filteredRecords.length === 0 ? ( + + + No records found + + + ) : ( + filteredRecords.map((item) => ( + + {item.id} + {item.fullName} + {item.number} + {item.emailId} + + {item.courseName} + {item.subject} + + + {item.message} + + + + {new Date(item.createdAt).toLocaleDateString()} + + + + + + + )) + )} + +
+
+
+
+
+ ); +} diff --git a/frontend/src/pages/email.tsx b/frontend/src/pages/email.tsx index 220c4c5..449a3ee 100644 --- a/frontend/src/pages/email.tsx +++ b/frontend/src/pages/email.tsx @@ -1,4 +1,4 @@ -import {useState, useEffect, useCallback} from "react"; +import { useState, useEffect, useCallback } from "react"; import { getEmailConfigsApi, @@ -16,9 +16,9 @@ import { TableRow, } from "@/components/ui/table"; -import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; -import {Button} from "@/components/ui/button"; -import {Input} from "@/components/ui/input"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Dialog, @@ -28,7 +28,7 @@ import { DialogFooter, } from "@/components/ui/dialog"; -import {Loader2, Plus, Pencil, Trash, RefreshCw} from "lucide-react"; +import { Loader2, Plus, Pencil, Trash, RefreshCw } from "lucide-react"; export default function EmailPage() { const [emails, setEmails] = useState([]); @@ -69,7 +69,7 @@ export default function EmailPage() { ); function handleChange(e: any) { - setForm({...form, [e.target.name]: e.target.value}); + setForm({ ...form, [e.target.name]: e.target.value }); } function openAdd() { @@ -181,16 +181,14 @@ export default function EmailPage() { @@ -227,11 +225,10 @@ export default function EmailPage() { name="type" value={form.type} onChange={handleChange} - className="border rounded px-2 py-2 w-full" - > + className="border rounded px-2 py-2 w-full"> - +