From 6e999c36c576a453cc8c8c070aad6f5554172da8 Mon Sep 17 00:00:00 2001 From: ARJUN S THAMPI <61703062+arjun-thampi@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:41:46 +0530 Subject: [PATCH] feat: add email page --- backend/src/routes/emailConfig.routes.js | 2 +- frontend/src/App.tsx | 2 + frontend/src/api/email.ts | 36 +++ frontend/src/components/layout/Sidebar.tsx | 4 + frontend/src/pages/email.tsx | 265 +++++++++++++++++++++ 5 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 frontend/src/api/email.ts create mode 100644 frontend/src/pages/email.tsx diff --git a/backend/src/routes/emailConfig.routes.js b/backend/src/routes/emailConfig.routes.js index 1308e89..eec6dde 100644 --- a/backend/src/routes/emailConfig.routes.js +++ b/backend/src/routes/emailConfig.routes.js @@ -13,7 +13,7 @@ const router = express.Router(); router.get("/getAll", getEmailConfigs); router.post("/", jwtAuthMiddleware, createEmailConfig); -router.put("/:id", jwtAuthMiddleware, updateEmailConfig); +router.patch("/:id", jwtAuthMiddleware, updateEmailConfig); router.delete("/:id", jwtAuthMiddleware, deleteEmailConfig); export default router; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e73b2d4..9a1e549 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -14,6 +14,7 @@ import Doctor from "./pages/Doctor"; import Blog from "./pages/Blog"; import BlogEditorPage from "./pages/BlogEditor"; import Appointment from "./pages/Appointment"; +import EmailPage from "./pages/email"; export default function App() { return ( @@ -32,6 +33,7 @@ export default function App() { } /> } /> } /> + } /> diff --git a/frontend/src/api/email.ts b/frontend/src/api/email.ts new file mode 100644 index 0000000..ff9f7eb --- /dev/null +++ b/frontend/src/api/email.ts @@ -0,0 +1,36 @@ +import apiClient from "@/api/client"; + +export interface EmailConfig { + id?: number; + name: string; + email: string; + type: string; + isActive?: boolean; +} + +// GET ALL +export const getEmailConfigsApi = async () => { + const res = await apiClient.get("/email/getAll"); + return res.data; +}; + +// CREATE +export const createEmailConfigApi = async (data: EmailConfig) => { + const res = await apiClient.post("/email", data); + return res.data; +}; + +// UPDATE +export const updateEmailConfigApi = async ( + id: number, + data: Partial, +) => { + const res = await apiClient.patch(`/email/${id}`, data); + return res.data; +}; + +// DELETE +export const deleteEmailConfigApi = async (id: number) => { + const res = await apiClient.delete(`/email/${id}`); + return res.data; +}; diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 384069a..a36c7df 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -19,6 +19,10 @@ export default function Sidebar() { name: "Appointments", path: "/appointment", }, + { + name: "Email", + path: "/email", + }, { name: "Blog", path: "/blog", diff --git a/frontend/src/pages/email.tsx b/frontend/src/pages/email.tsx new file mode 100644 index 0000000..220c4c5 --- /dev/null +++ b/frontend/src/pages/email.tsx @@ -0,0 +1,265 @@ +import {useState, useEffect, useCallback} from "react"; + +import { + getEmailConfigsApi, + createEmailConfigApi, + updateEmailConfigApi, + deleteEmailConfigApi, +} from "@/api/email"; + +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 { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; + +import {Loader2, Plus, Pencil, Trash, RefreshCw} from "lucide-react"; + +export default function EmailPage() { + const [emails, setEmails] = useState([]); + const [loading, setLoading] = useState(true); + + const [openModal, setOpenModal] = useState(false); + const [editing, setEditing] = useState(null); + + const [searchText, setSearchText] = useState(""); + + const [form, setForm] = useState({ + name: "", + email: "", + type: "APPOINTMENT", + isActive: true, + }); + + const fetchAll = useCallback(async () => { + setLoading(true); + try { + const res = await getEmailConfigsApi(); + setEmails(res?.data || []); + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchAll(); + }, [fetchAll]); + + const filtered = emails.filter( + (e) => + e.email.toLowerCase().includes(searchText.toLowerCase()) || + e.name.toLowerCase().includes(searchText.toLowerCase()), + ); + + function handleChange(e: any) { + setForm({...form, [e.target.name]: e.target.value}); + } + + function openAdd() { + setEditing(null); + setForm({ + name: "", + email: "", + type: "APPOINTMENT", + isActive: true, + }); + setOpenModal(true); + } + + function openEdit(item: any) { + setEditing(item); + setForm(item); + setOpenModal(true); + } + + async function handleSubmit() { + try { + if (editing) { + await updateEmailConfigApi(editing.id, form); + } else { + await createEmailConfigApi(form); + } + + setOpenModal(false); + fetchAll(); + } catch (err) { + console.error(err); + } + } + + async function handleDelete(id: number) { + if (!confirm("Delete email config?")) return; + await deleteEmailConfigApi(id); + fetchAll(); + } + + return ( + + + Email Config + + + setSearchText(e.target.value)} + className="w-[200px]" + /> + + + + Refresh + + + + + Add Email + + + + + + + Email List + + + + + + + ID + Name + Email + Type + Status + Actions + + + + + {loading ? ( + + + + + + ) : filtered.length === 0 ? ( + + + No data + + + ) : ( + filtered.map((item) => ( + + {item.id} + {item.name} + {item.email} + {item.type} + + {item.isActive ? "Active" : "Inactive"} + + + + openEdit(item)} + > + + + + handleDelete(item.id)} + > + + + + + )) + )} + + + + + + + + + {editing ? "Edit Email" : "Add Email"} + + + + + + + + + APPOINTMENT + CANDIDATE + INQUIRY + + + + setForm({ + ...form, + isActive: e.target.value === "true", + }) + } + className="border rounded px-2 py-2 w-full" + > + Active + Inactive + + + + + setOpenModal(false)}> + Cancel + + + {editing ? "Update" : "Create"} + + + + + + ); +}