diff --git a/backend/src/controllers/candidate.controller.js b/backend/src/controllers/candidate.controller.js
index bdc6b6c..b87f053 100644
--- a/backend/src/controllers/candidate.controller.js
+++ b/backend/src/controllers/candidate.controller.js
@@ -1,10 +1,14 @@
import prisma from "../prisma/client.js";
+import { sendEmail } from "../utils/sendEmail.js";
+import { getEmailsByType } from "../utils/getEmailByTypes.js";
+
// CREATE CANDIDATE
export const createCandidate = async (req, res) => {
try {
- const {fullName, mobile, email, subject, coverLetter, careerId} = req.body;
+ const { fullName, mobile, email, subject, coverLetter, careerId } =
+ req.body;
if (!fullName || !mobile || !email || !careerId) {
return res.status(400).json({
@@ -22,8 +26,38 @@ export const createCandidate = async (req, res) => {
coverLetter,
careerId: Number(careerId),
},
+ include: {
+ career: true,
+ },
});
+ try {
+ const emailList = await getEmailsByType("CANDIDATE");
+
+ if (emailList && emailList.length > 0) {
+ await sendEmail({
+ to: emailList,
+ subject: "New Job Application Received",
+ html: `
+
New Candidate Application
+
+ Name: ${fullName}
+ Phone: ${mobile}
+ Email: ${email}
+
+ Applied For: ${candidate.career?.post || "-"}
+ Designation: ${candidate.career?.designation || "-"}
+
+ Subject: ${subject || "-"}
+ Cover Letter:
+ ${coverLetter || "-"}
+ `,
+ });
+ }
+ } catch (err) {
+ console.error("Candidate email failed:", err);
+ }
+
res.status(201).json({
success: true,
message: "Application submitted successfully",
@@ -68,7 +102,7 @@ export const getCandidates = async (req, res) => {
export const getCandidate = async (req, res) => {
try {
- const {id} = req.params;
+ const { id } = req.params;
const candidate = await prisma.candidate.findUnique({
where: {
@@ -103,7 +137,7 @@ export const getCandidate = async (req, res) => {
export const getCandidatesByCareer = async (req, res) => {
try {
- const {careerId} = req.params;
+ const { careerId } = req.params;
const candidates = await prisma.candidate.findMany({
where: {
@@ -134,7 +168,7 @@ export const getCandidatesByCareer = async (req, res) => {
export const updateCandidate = async (req, res) => {
try {
- const {id} = req.params;
+ const { id } = req.params;
const candidate = await prisma.candidate.update({
where: {
@@ -161,7 +195,7 @@ export const updateCandidate = async (req, res) => {
export const deleteCandidate = async (req, res) => {
try {
- const {id} = req.params;
+ const { id } = req.params;
await prisma.candidate.delete({
where: {
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index d8a1b4d..f2a027b 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -16,6 +16,7 @@ import BlogEditorPage from "./pages/BlogEditor";
import Appointment from "./pages/Appointment";
import EmailPage from "./pages/email";
import CareerPage from "./pages/Career";
+import CandidatePage from "./pages/candidates";
export default function App() {
return (
@@ -36,6 +37,7 @@ export default function App() {
} />
} />
} />
+ } />
diff --git a/frontend/src/api/candidates.ts b/frontend/src/api/candidates.ts
new file mode 100644
index 0000000..fa81dc6
--- /dev/null
+++ b/frontend/src/api/candidates.ts
@@ -0,0 +1,11 @@
+import apiClient from "@/api/client";
+
+export const getCandidatesApi = async () => {
+ const res = await apiClient.get("/candidates/getAll");
+ return res.data;
+};
+
+export const deleteCandidateApi = async (id: number) => {
+ const res = await apiClient.delete(`/candidates/${id}`);
+ return res.data;
+};
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx
index a3e1acd..d434faa 100644
--- a/frontend/src/components/layout/Sidebar.tsx
+++ b/frontend/src/components/layout/Sidebar.tsx
@@ -23,6 +23,10 @@ export default function Sidebar() {
name: "Career",
path: "/career",
},
+ {
+ name: "Candidates",
+ path: "/candidate",
+ },
{
name: "Email",
path: "/email",
@@ -49,8 +53,7 @@ export default function Sidebar() {
diff --git a/frontend/src/pages/candidates.tsx b/frontend/src/pages/candidates.tsx
new file mode 100644
index 0000000..45a8ea7
--- /dev/null
+++ b/frontend/src/pages/candidates.tsx
@@ -0,0 +1,187 @@
+import { useState, useEffect, useCallback } from "react";
+
+import { getCandidatesApi, deleteCandidateApi } from "@/api/candidates";
+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 CandidatePage() {
+ const [candidates, setCandidates] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ const [searchText, setSearchText] = useState("");
+ const [filterCareer, setFilterCareer] = useState("");
+
+ const fetchAll = useCallback(async () => {
+ setLoading(true);
+ try {
+ const res = await getCandidatesApi();
+ setCandidates(res?.data || []);
+ } catch (err) {
+ console.error(err);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ fetchAll();
+ }, [fetchAll]);
+
+ const filteredCandidates = candidates.filter((item) => {
+ const matchesSearch =
+ item.fullName?.toLowerCase().includes(searchText.toLowerCase()) ||
+ item.mobile?.includes(searchText) ||
+ item.email?.toLowerCase().includes(searchText.toLowerCase());
+
+ const matchesCareer = filterCareer
+ ? item.career?.post?.toLowerCase().includes(filterCareer.toLowerCase())
+ : true;
+
+ return matchesSearch && matchesCareer;
+ });
+
+ async function handleDelete(id: number) {
+ if (!confirm("Delete candidate?")) return;
+ await deleteCandidateApi(id);
+ fetchAll();
+ }
+
+ const handleExport = () => {
+ const exportData = filteredCandidates.map((item) => ({
+ ID: item.id,
+ Name: item.fullName,
+ Phone: item.mobile,
+ Email: item.email,
+ Career: item.career?.post,
+ Designation: item.career?.designation,
+ Subject: item.subject,
+ CoverLetter: item.coverLetter,
+ Date: new Date(item.createdAt).toLocaleDateString(),
+ }));
+
+ exportToExcel(exportData, "candidates");
+ };
+
+ return (
+
+
+
+
+
+ Candidate List
+
+
+
+
+
+
+
+ ID
+ Name
+ Phone
+ Email
+ Career
+ Designation
+ Subject
+ Cover Letter
+ Applied On
+ Actions
+
+
+
+
+ {loading ? (
+
+
+
+
+
+ ) : filteredCandidates.length === 0 ? (
+
+
+ No candidates found
+
+
+ ) : (
+ filteredCandidates.map((item) => (
+
+ {item.id}
+ {item.fullName}
+ {item.mobile}
+ {item.email}
+
+ {item.career?.post}
+ {item.career?.designation}
+
+ {item.subject}
+
+
+ {item.coverLetter}
+
+
+
+ {new Date(item.createdAt).toLocaleDateString()}
+
+
+
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+ );
+}