diff --git a/frontend/src/pages/Academics.tsx b/frontend/src/pages/Academics.tsx index dd8aa09..7883e1c 100644 --- a/frontend/src/pages/Academics.tsx +++ b/frontend/src/pages/Academics.tsx @@ -13,11 +13,26 @@ import { } 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, Trash, RefreshCw, Download } from "lucide-react"; +import { + Loader2, + Trash, + RefreshCw, + Download, + ChevronLeft, + ChevronRight, + Eye, + BookOpen, +} from "lucide-react"; export default function AcademicsPage() { const [records, setRecords] = useState([]); @@ -25,6 +40,12 @@ export default function AcademicsPage() { const [searchText, setSearchText] = useState(""); + const [viewOpen, setViewOpen] = useState(false); + const [viewData, setViewData] = useState(null); + + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const fetchAll = useCallback(async () => { setLoading(true); try { @@ -51,6 +72,20 @@ export default function AcademicsPage() { ); }); + useEffect(() => { + setCurrentPage(1); + }, [searchText]); + + const totalPages = Math.ceil(filteredRecords.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredRecords.slice(indexOfFirstItem, indexOfLastItem); + + function openView(item: any) { + setViewData(item); + setViewOpen(true); + } + async function handleDelete(id: number) { if (!confirm("Delete record?")) return; await deleteAcademicsApi(id); @@ -74,24 +109,29 @@ export default function AcademicsPage() { return (
-
-

Academics & Research

+
+

Academics & Research

-
+
setSearchText(e.target.value)} - className="w-[260px]" + className="w-[280px] text-base" /> - -
@@ -99,65 +139,108 @@ export default function AcademicsPage() { - Academics Records + Academic Records - -
- - + +
+
+ - ID - Name - Phone - Email - Course - Subject - Message - Date - Actions + + ID + + + Full Name + + + Course + + + Subject + + + Applied Date + + + Message + + + Actions + {loading ? ( - - + + - ) : filteredRecords.length === 0 ? ( + ) : currentItems.length === 0 ? ( - + No records found ) : ( - filteredRecords.map((item) => ( - - {item.id} - {item.fullName} - {item.number} - {item.emailId} - - {item.courseName} - {item.subject} - - - {item.message} + currentItems.map((item) => ( + + + {item.id} - +
+ {item.fullName} +
+
+ {item.emailId} +
+
+ {item.number} +
+
+ +
+ {item.courseName || "-"} +
+
+ +
+ {item.subject || "-"} +
+
+ {new Date(item.createdAt).toLocaleDateString()} - - +
+ {item.message || "-"} +
+
+ +
+ + +
)) @@ -165,8 +248,117 @@ export default function AcademicsPage() {
+ + {!loading && filteredRecords.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredRecords.length)} + {" "} + of{" "} + {filteredRecords.length}{" "} + records +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
+ + + + + + Academic Detail View + + + {viewData && ( +
+
+
+
+

+ Applicant Information +

+

+ {viewData.fullName} +

+

{viewData.emailId}

+

{viewData.number}

+
+
+

+ Course & Subject +

+

+ {viewData.courseName || "N/A"} +

+

+ {viewData.subject} +

+
+
+

+ Submission Date +

+

+ {new Date(viewData.createdAt).toLocaleString()} +

+
+
+
+
+

+ Message / Research Inquiry +

+

+ {viewData.message || "No message content provided."} +

+
+
+
+
+ )} + + + +
+
); } diff --git a/frontend/src/pages/Appointment.tsx b/frontend/src/pages/Appointment.tsx index 4eb44d6..0ed6b7c 100644 --- a/frontend/src/pages/Appointment.tsx +++ b/frontend/src/pages/Appointment.tsx @@ -1,7 +1,7 @@ -import {useState, useEffect, useCallback} from "react"; +import { useState, useEffect, useCallback } from "react"; -import {getAppointmentsApi, deleteAppointmentApi} from "@/api/appointment"; -import {exportToExcel} from "@/utils/exportToExcel"; +import { getAppointmentsApi, deleteAppointmentApi } from "@/api/appointment"; +import { exportToExcel } from "@/utils/exportToExcel"; import { Table, @@ -12,12 +12,26 @@ import { TableRow, } from "@/components/ui/table"; -import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; +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 {Button} from "@/components/ui/button"; -import {Input} from "@/components/ui/input"; - -import {Loader2, Trash, RefreshCw, Download} from "lucide-react"; +import { + Loader2, + Trash, + RefreshCw, + Download, + ChevronLeft, + ChevronRight, + Eye, +} from "lucide-react"; export default function AppointmentPage() { const [appointments, setAppointments] = useState([]); @@ -25,9 +39,14 @@ export default function AppointmentPage() { const [searchText, setSearchText] = useState(""); const [filterDoctor, setFilterDoctor] = useState(""); - const [filterDepartment, setFilterDepartment] = useState(""); const [filterDate, setFilterDate] = useState(""); + const [viewOpen, setViewOpen] = useState(false); + const [viewData, setViewData] = useState(null); + + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const fetchAll = useCallback(async () => { setLoading(true); try { @@ -54,19 +73,30 @@ export default function AppointmentPage() { ? item.doctor?.name?.toLowerCase().includes(filterDoctor.toLowerCase()) : true; - const matchesDepartment = filterDepartment - ? item.department?.name - ?.toLowerCase() - .includes(filterDepartment.toLowerCase()) - : true; - const matchesDate = filterDate ? new Date(item.date).toISOString().split("T")[0] === filterDate : true; - return matchesSearch && matchesDoctor && matchesDepartment && matchesDate; + return matchesSearch && matchesDoctor && matchesDate; }); + useEffect(() => { + setCurrentPage(1); + }, [searchText, filterDoctor, filterDate]); + + const totalPages = Math.ceil(filteredAppointments.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredAppointments.slice( + indexOfFirstItem, + indexOfLastItem, + ); + + function openView(item: any) { + setViewData(item); + setViewOpen(true); + } + async function handleDelete(id: number) { if (!confirm("Delete appointment?")) return; await deleteAppointmentApi(id); @@ -84,51 +114,41 @@ export default function AppointmentPage() { Date: new Date(item.date).toLocaleDateString(), Message: item.message, })); - exportToExcel(exportData, "appointments"); }; return (
-
-

Appointments

+
+

Appointments

-
+
setSearchText(e.target.value)} - className="w-[220px]" - /> - - setFilterDoctor(e.target.value)} - className="w-[180px]" - /> - - setFilterDepartment(e.target.value)} - className="w-[200px]" + className="w-[220px] text-base" /> setFilterDate(e.target.value)} - className="w-[180px]" + className="w-[160px] text-base" /> - -
@@ -136,72 +156,102 @@ export default function AppointmentPage() { - Appointment List + Appointment List - -
- - + +
+
+ - ID - Name - Phone - Email - Doctor - Department - Appointment Date - Message - Generated on - - Actions + + ID + + + Patient + + + Doctor + + + Date + + + Message + + + Actions + {loading ? ( - - + + - ) : filteredAppointments.length === 0 ? ( + ) : currentItems.length === 0 ? ( - + No appointments found ) : ( - filteredAppointments.map((item) => ( - - {item.id} - {item.name} - {item.mobileNumber} - {item.email} - {item.doctor?.name} - {item.department?.name} - - {/* ✅ DATE ONLY */} - - {new Date(item.date).toLocaleDateString()} - - - - {item.message} + currentItems.map((item) => ( + + + {item.id} - {" "} - {new Date(item.createdAt).toLocaleDateString()} +
+ {item.name} +
+
+ {item.mobileNumber} +
- - +
+ {item.doctor?.name || "-"} +
+
+ {item.department?.name} +
+
+ +
+ {new Date(item.date).toLocaleDateString()} +
+
+ +
+ {item.message || "-"} +
+
+ +
+ + +
)) @@ -209,8 +259,123 @@ export default function AppointmentPage() {
+ + {!loading && filteredAppointments.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredAppointments.length)} + {" "} + of{" "} + + {filteredAppointments.length} + +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
+ + + + + + Appointment Details + + + {viewData && ( +
+
+
+
+

+ Patient Information +

+

+ {viewData.name} +

+

{viewData.mobileNumber}

+

+ {viewData.email || "No email provided"} +

+
+
+

+ Appointment Date +

+

+ {new Date(viewData.date).toLocaleDateString()} +

+

+ Booked on: {new Date(viewData.createdAt).toLocaleString()} +

+
+
+
+
+

+ Doctor / Department +

+

+ {viewData.doctor?.name || "Not Assigned"} +

+

+ {viewData.department?.name || "General"} +

+
+
+

+ Message from Patient +

+

+ {viewData.message || "No message provided."} +

+
+
+
+
+ )} + + + +
+
); } diff --git a/frontend/src/pages/Career.tsx b/frontend/src/pages/Career.tsx index 5782bd8..ec60512 100644 --- a/frontend/src/pages/Career.tsx +++ b/frontend/src/pages/Career.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { getCareersApi, deleteCareerApi } from "@/api/career"; - import apiClient from "@/api/client"; import { @@ -15,7 +14,6 @@ import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; - import { Dialog, DialogContent, @@ -25,8 +23,17 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; -import { Loader2, Plus, Pencil, Trash, RefreshCw } from "lucide-react"; +import { + Loader2, + Plus, + Pencil, + Trash, + RefreshCw, + ChevronLeft, + ChevronRight, +} from "lucide-react"; export default function CareerPage() { const [careers, setCareers] = useState([]); @@ -37,6 +44,9 @@ export default function CareerPage() { const [searchText, setSearchText] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const [form, setForm] = useState({ post: "", designation: "", @@ -63,10 +73,21 @@ export default function CareerPage() { fetchAll(); }, [fetchAll]); - const filteredCareers = careers.filter((item) => - item.post?.toLowerCase().includes(searchText.toLowerCase()), + const filteredCareers = careers.filter( + (item) => + item.post?.toLowerCase().includes(searchText.toLowerCase()) || + item.designation?.toLowerCase().includes(searchText.toLowerCase()), ); + useEffect(() => { + setCurrentPage(1); + }, [searchText]); + + const totalPages = Math.ceil(filteredCareers.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredCareers.slice(indexOfFirstItem, indexOfLastItem); + function handleChange(e: any) { setForm({ ...form, [e.target.name]: e.target.value }); } @@ -87,7 +108,6 @@ export default function CareerPage() { function openEdit(item: any) { setEditing(item); - setForm({ post: item.post || "", designation: item.designation || "", @@ -97,7 +117,6 @@ export default function CareerPage() { number: item.number || "", status: item.status || "new", }); - setOpenModal(true); } @@ -108,7 +127,6 @@ export default function CareerPage() { } else { await apiClient.post("/careers", form); } - setOpenModal(false); fetchAll(); } catch (err) { @@ -124,24 +142,29 @@ export default function CareerPage() { return (
-
-

Careers

+
+

Careers

-
+
setSearchText(e.target.value)} - className="w-[220px]" + className="w-[250px] text-base" /> - -
@@ -149,67 +172,113 @@ export default function CareerPage() { - Career List + Career Opportunities - -
- - + +
+
+ - ID - Post - Designation - Qualification - Experience - Email - Phone - Status - Actions + + ID + + + Post & Designation + + + Qualification + + + Experience + + + Contact Info + + + Status + + + Actions + {loading ? ( - - + + - ) : filteredCareers.length === 0 ? ( + ) : currentItems.length === 0 ? ( - + No careers found ) : ( - filteredCareers.map((item) => ( - - {item.id} - {item.post} - {item.designation} - {item.qualification} - {item.experienceNeed} - {item.email} - {item.number} - {item.status} - - - + {item.status} + + - + +
+ + + +
)) @@ -217,67 +286,126 @@ export default function CareerPage() {
+ + {!loading && filteredCareers.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredCareers.length)} + {" "} + of{" "} + {filteredCareers.length}{" "} + careers +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
- {/* MODAL */} - + - {editing ? "Edit Career" : "Add Career"} + + {editing ? "Edit Career" : "Add New Career"} + -
- - - - - - - +
+
+ + + + + + + +
- - - diff --git a/frontend/src/pages/Department.tsx b/frontend/src/pages/Department.tsx index b06dc0b..e56ac07 100644 --- a/frontend/src/pages/Department.tsx +++ b/frontend/src/pages/Department.tsx @@ -31,7 +31,16 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; -import { Loader2, RefreshCw, Plus, Pencil, Trash, Eye } from "lucide-react"; +import { + Loader2, + RefreshCw, + Plus, + Pencil, + Trash, + Eye, + ChevronLeft, + ChevronRight, +} from "lucide-react"; interface Department { departmentId: string; @@ -56,6 +65,9 @@ export default function DepartmentPage() { const [searchText, setSearchText] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const [form, setForm] = useState({ departmentId: "", name: "", @@ -88,8 +100,22 @@ export default function DepartmentPage() { fetchDepartments(); }, [fetchDepartments]); - const filteredDepartments = departments.filter((dep) => - dep.name.toLowerCase().includes(searchText.toLowerCase()), + const filteredDepartments = departments.filter( + (dep) => + dep.name.toLowerCase().includes(searchText.toLowerCase()) || + dep.departmentId.toLowerCase().includes(searchText.toLowerCase()), + ); + + useEffect(() => { + setCurrentPage(1); + }, [searchText]); + + const totalPages = Math.ceil(filteredDepartments.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredDepartments.slice( + indexOfFirstItem, + indexOfLastItem, ); function handleChange(e: any) { @@ -155,118 +181,153 @@ export default function DepartmentPage() { return (
-
-

Departments

+
+

Departments

setSearchText(e.target.value)} - className="w-[220px]" + className="w-[250px] text-base" /> -
{error && ( -
+
{error}
)} - Department List + Department List - -
- - + +
+
+ - ID - Name - Para1 - Facilities - Services - Actions + + ID + + + Name + + + Para 1 + + + Facilities + + + Services + + + Actions + {loading ? ( - - + + - ) : filteredDepartments.length === 0 ? ( + ) : currentItems.length === 0 ? ( - + No departments found ) : ( - filteredDepartments.map((dep) => ( - - {dep.departmentId} - - -
{dep.name}
+ currentItems.map((dep) => ( + + + {dep.departmentId} -
+
+ {dep.name} +
+ + + +
{truncate(dep.para1)}
-
+
{truncate(dep.facilities)}
-
+
{truncate(dep.services)}
- - + +
+ - + - + +
)) @@ -274,6 +335,52 @@ export default function DepartmentPage() {
+ + {!loading && filteredDepartments.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredDepartments.length)} + {" "} + of{" "} + + {filteredDepartments.length} + {" "} + departments +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
@@ -346,7 +453,6 @@ export default function DepartmentPage() { - {" "} Department Details @@ -355,35 +461,29 @@ export default function DepartmentPage() {

ID: {viewData.departmentId}

-

Name: {viewData.name}

-

Para1:
{viewData.para1}

-

Para2:
{viewData.para2}

-

Para3:
{viewData.para3}

-

Facilities:
{viewData.facilities}

-

Services:
diff --git a/frontend/src/pages/Doctor.tsx b/frontend/src/pages/Doctor.tsx index 999d554..36ce6a3 100644 --- a/frontend/src/pages/Doctor.tsx +++ b/frontend/src/pages/Doctor.tsx @@ -1,5 +1,5 @@ -import {useState, useEffect, useCallback} from "react"; -import {AxiosError} from "axios"; +import { useState, useEffect, useCallback } from "react"; +import { AxiosError } from "axios"; import { getDoctorsApi, createDoctorApi, @@ -7,7 +7,7 @@ import { deleteDoctorApi, getDoctorTimingApi, } from "@/api/doctor"; -import {getDepartmentsApi} from "@/api/department"; +import { getDepartmentsApi } from "@/api/department"; import { Table, @@ -17,8 +17,8 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; -import {Button} from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, @@ -26,9 +26,17 @@ import { DialogTitle, DialogFooter, } from "@/components/ui/dialog"; -import {Input} from "@/components/ui/input"; -import {Badge} from "@/components/ui/badge"; -import {Loader2, RefreshCw, Plus, Pencil, Trash} from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Loader2, + RefreshCw, + Plus, + Pencil, + Trash, + ChevronLeft, + ChevronRight, +} from "lucide-react"; interface Department { departmentId: string; @@ -58,6 +66,9 @@ export default function DoctorPage() { const [searchText, setSearchText] = useState(""); const [filterDepartment, setFilterDepartment] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const [form, setForm] = useState({ doctorId: "", name: "", @@ -104,8 +115,17 @@ export default function DoctorPage() { return matchesSearch && matchesDepartment; }); + useEffect(() => { + setCurrentPage(1); + }, [searchText, filterDepartment]); + + const totalPages = Math.ceil(filteredDoctors.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredDoctors.slice(indexOfFirstItem, indexOfLastItem); + function handleChange(e: any) { - setForm({...form, [e.target.name]: e.target.value}); + setForm({ ...form, [e.target.name]: e.target.value }); } function handleDepartmentToggle(depId: string) { @@ -120,7 +140,7 @@ export default function DoctorPage() { } else { setForm({ ...form, - departments: [...form.departments, {departmentId: depId, timing: {}}], + departments: [...form.departments, { departmentId: depId, timing: {} }], }); } } @@ -130,7 +150,7 @@ export default function DoctorPage() { ...form, departments: form.departments.map((d: any) => d.departmentId === depId - ? {...d, timing: {...d.timing, [day]: value}} + ? { ...d, timing: { ...d.timing, [day]: value } } : d, ), }); @@ -197,22 +217,21 @@ export default function DoctorPage() { return (

- {/* HEADER */} -
-

Doctors

+
+

Doctors

setSearchText(e.target.value)} - className="w-[200px]" + className="w-[250px] text-base" /> - -
- {/* TABLE */} {error && ( -
+
{error}
)} - Doctor List + Doctor List - -
- - + +
+
+ - ID - Name - Designation - Qualification - Departments - Actions + + ID + + + Name + + + Designation + + + Qualification + + + Departments + + + Actions + {loading ? ( - - + + - ) : filteredDoctors.length === 0 ? ( + ) : currentItems.length === 0 ? ( No doctors found ) : ( - filteredDoctors.map((doc) => ( - + currentItems.map((doc) => ( + {doc.doctorId} -
+
{doc.name}
-
+
{doc.workingStatus}
-
+
{doc.designation || "-"}
-
+
{doc.qualification || "-"}
-
+
{doc.departments?.map((d: any) => ( {d.departmentName} @@ -324,15 +368,17 @@ export default function DoctorPage() {
+ + {!loading && filteredDoctors.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredDoctors.length)} + {" "} + of{" "} + {filteredDoctors.length}{" "} + doctors +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
- {/* MODAL */} - {editing ? "Edit Doctor" : "Add Doctor"} + + {editing ? "Edit Doctor" : "Add Doctor"} + -
-
-

Basic Information

- - - - - +
+
+

+ Basic Information +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
-
-

Assign Departments

-
+
+

Assign Departments

+
{departments.map((dep) => { const isSelected = form.departments.some( (d: any) => d.departmentId === dep.departmentId, @@ -403,7 +520,7 @@ export default function DoctorPage() { type="button" variant={isSelected ? "default" : "outline"} size="sm" - className="justify-start" + className="justify-start text-sm h-9" onClick={() => handleDepartmentToggle(dep.departmentId)} > {dep.name} @@ -414,16 +531,16 @@ export default function DoctorPage() {
-
-

+
+

Working Hours / Timing

{form.departments.length === 0 ? ( -
- Select a department to set timings +
+ Select a department to configure timing slots
) : ( -
+
{form.departments.map((dep: any) => { const depName = departments.find( (d) => d.departmentId === dep.departmentId, @@ -431,20 +548,25 @@ export default function DoctorPage() { return (
-

- {depName} -

-
+
+

+ {depName} +

+ + Timing Slot + +
+
{DAYS.map((day) => (
-
- - - diff --git a/frontend/src/pages/candidates.tsx b/frontend/src/pages/candidates.tsx index 45a8ea7..b94ec27 100644 --- a/frontend/src/pages/candidates.tsx +++ b/frontend/src/pages/candidates.tsx @@ -13,11 +13,26 @@ import { } 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, Trash, RefreshCw, Download } from "lucide-react"; +import { + Loader2, + Trash, + RefreshCw, + Download, + ChevronLeft, + ChevronRight, + Eye, + User, +} from "lucide-react"; export default function CandidatePage() { const [candidates, setCandidates] = useState([]); @@ -26,6 +41,12 @@ export default function CandidatePage() { const [searchText, setSearchText] = useState(""); const [filterCareer, setFilterCareer] = useState(""); + const [viewOpen, setViewOpen] = useState(false); + const [viewData, setViewData] = useState(null); + + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const fetchAll = useCallback(async () => { setLoading(true); try { @@ -55,6 +76,23 @@ export default function CandidatePage() { return matchesSearch && matchesCareer; }); + useEffect(() => { + setCurrentPage(1); + }, [searchText, filterCareer]); + + const totalPages = Math.ceil(filteredCandidates.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredCandidates.slice( + indexOfFirstItem, + indexOfLastItem, + ); + + function openView(item: any) { + setViewData(item); + setViewOpen(true); + } + async function handleDelete(id: number) { if (!confirm("Delete candidate?")) return; await deleteCandidateApi(id); @@ -71,7 +109,7 @@ export default function CandidatePage() { Designation: item.career?.designation, Subject: item.subject, CoverLetter: item.coverLetter, - Date: new Date(item.createdAt).toLocaleDateString(), + AppliedDate: new Date(item.createdAt).toLocaleDateString(), })); exportToExcel(exportData, "candidates"); @@ -79,31 +117,36 @@ export default function CandidatePage() { return (
-
-

Candidates

+
+

Candidates

-
+
setSearchText(e.target.value)} - className="w-[220px]" + className="w-[250px] text-base" /> setFilterCareer(e.target.value)} - className="w-[200px]" + className="w-[200px] text-base" /> - -
@@ -111,68 +154,104 @@ export default function CandidatePage() { - Candidate List + Application List - -
- - + +
+
+ - ID - Name - Phone - Email - Career - Designation - Subject - Cover Letter - Applied On - Actions + + ID + + + Full Name + + + Career & Post + + + Contact + + + Applied On + + + Cover Letter + + + Actions + {loading ? ( - - + + - ) : filteredCandidates.length === 0 ? ( + ) : currentItems.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} + currentItems.map((item) => ( + + + {item.id} - +
+ {item.fullName} +
+
+ {item.email} +
+
+ +
+ {item.career?.post || "-"} +
+
+ {item.career?.designation} +
+
+ {item.mobile} + {new Date(item.createdAt).toLocaleDateString()} - - +
+ {item.coverLetter || "No cover letter provided."} +
+
+ +
+ + +
)) @@ -180,8 +259,126 @@ export default function CandidatePage() {
+ + {!loading && filteredCandidates.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredCandidates.length)} + {" "} + of{" "} + + {filteredCandidates.length} + +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
+ + + + + + Candidate Details + + + {viewData && ( +
+
+
+
+

+ Personal Information +

+

+ {viewData.fullName} +

+

{viewData.email}

+

{viewData.mobile}

+
+
+

+ Applied For +

+

+ {viewData.career?.post || "General Application"} +

+

+ {viewData.career?.designation} +

+
+
+

+ Application Date +

+

+ {new Date(viewData.createdAt).toLocaleString()} +

+
+
+
+
+

+ Subject +

+

+ {viewData.subject || "N/A"} +

+
+
+

+ Cover Letter / Message +

+

+ {viewData.coverLetter || "No cover letter provided."} +

+
+
+
+
+ )} + + + +
+
); } diff --git a/frontend/src/pages/inquiry.tsx b/frontend/src/pages/inquiry.tsx index 778243b..c2697d7 100644 --- a/frontend/src/pages/inquiry.tsx +++ b/frontend/src/pages/inquiry.tsx @@ -13,11 +13,26 @@ import { } 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, Trash, RefreshCw, Download } from "lucide-react"; +import { + Loader2, + Trash, + RefreshCw, + Download, + ChevronLeft, + ChevronRight, + Eye, + Mail, +} from "lucide-react"; export default function InquiryPage() { const [inquiries, setInquiries] = useState([]); @@ -25,6 +40,12 @@ export default function InquiryPage() { const [searchText, setSearchText] = useState(""); + const [viewOpen, setViewOpen] = useState(false); + const [viewData, setViewData] = useState(null); + + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 10; + const fetchAll = useCallback(async () => { setLoading(true); try { @@ -50,6 +71,23 @@ export default function InquiryPage() { ); }); + useEffect(() => { + setCurrentPage(1); + }, [searchText]); + + const totalPages = Math.ceil(filteredInquiries.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredInquiries.slice( + indexOfFirstItem, + indexOfLastItem, + ); + + function openView(item: any) { + setViewData(item); + setViewOpen(true); + } + async function handleDelete(id: number) { if (!confirm("Delete inquiry?")) return; await deleteInquiryApi(id); @@ -72,24 +110,29 @@ export default function InquiryPage() { return (
-
-

Inquiries

+
+

Inquiries

-
+
setSearchText(e.target.value)} - className="w-[260px]" + className="w-[280px] text-base" /> - -
@@ -97,62 +140,100 @@ export default function InquiryPage() { - Inquiry List + Customer Inquiries - -
- - + +
+
+ - ID - Name - Phone - Email - Subject - Message - Date - Actions + + ID + + + Customer Details + + + Subject + + + Date + + + Message Snippet + + + Actions + {loading ? ( - - + + - ) : filteredInquiries.length === 0 ? ( + ) : currentItems.length === 0 ? ( - + No inquiries found ) : ( - filteredInquiries.map((item) => ( - - {item.id} - {item.fullName} - {item.number} - {item.emailId} - {item.subject} - - - {item.message} + currentItems.map((item) => ( + + + {item.id} - +
+ {item.fullName} +
+
+ {item.emailId} +
+
+ {item.number} +
+
+ +
+ {item.subject || "-"} +
+
+ {new Date(item.createdAt).toLocaleDateString()} - - +
+ {item.message || "-"} +
+
+ +
+ + +
)) @@ -160,8 +241,115 @@ export default function InquiryPage() {
+ + {!loading && filteredInquiries.length > 0 && ( +
+
+ Showing{" "} + {indexOfFirstItem + 1} to{" "} + + {Math.min(indexOfLastItem, filteredInquiries.length)} + {" "} + of{" "} + + {filteredInquiries.length} + +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
+
+ )}
+ + + + + + Inquiry Details + + + {viewData && ( +
+
+
+
+

+ Customer Information +

+

+ {viewData.fullName} +

+

{viewData.emailId}

+

{viewData.number}

+
+
+

+ Received Date +

+

+ {new Date(viewData.createdAt).toLocaleString()} +

+
+
+
+
+

+ Subject +

+

+ {viewData.subject || "No Subject"} +

+
+
+

+ Message +

+

+ {viewData.message || "No message content."} +

+
+
+
+
+ )} + + + +
+
); } diff --git a/frontend/src/pages/newsMedia.tsx b/frontend/src/pages/newsMedia.tsx index fe267c6..1c48817 100644 --- a/frontend/src/pages/newsMedia.tsx +++ b/frontend/src/pages/newsMedia.tsx @@ -19,6 +19,7 @@ import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; import { Dialog, @@ -37,11 +38,13 @@ import { Eye, ChevronLeft, ChevronRight, + Newspaper, } from "lucide-react"; export default function NewsPage() { const [news, setNews] = useState([]); const [loading, setLoading] = useState(true); + const [totalItems, setTotalItems] = useState(0); const [searchText, setSearchText] = useState(""); @@ -66,29 +69,28 @@ export default function NewsPage() { const fetchAll = useCallback(async () => { setLoading(true); try { - const res = await getNewsApi(); + const res = await getNewsApi(currentPage, itemsPerPage); + setNews(res?.data || []); + setTotalItems(res?.meta?.total || 0); } catch (err) { console.error(err); } finally { setLoading(false); } - }, []); + }, [currentPage, itemsPerPage]); useEffect(() => { fetchAll(); }, [fetchAll]); - const filteredNews = news.filter((item) => - item.Headline?.toLowerCase().includes(searchText.toLowerCase()), + const filteredNews = news.filter( + (item) => + item.Headline?.toLowerCase().includes(searchText.toLowerCase()) || + item.Author?.toLowerCase().includes(searchText.toLowerCase()), ); - const totalPages = Math.ceil(filteredNews.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const paginatedData = filteredNews.slice( - startIndex, - startIndex + itemsPerPage, - ); + const totalPages = Math.ceil(totalItems / itemsPerPage); function handleChange(e: any) { setForm({ ...form, [e.target.name]: e.target.value }); @@ -109,7 +111,6 @@ export default function NewsPage() { function openEdit(item: any) { setEditing(item); - setForm({ headline: item.Headline || "", content: item.Content || "", @@ -118,7 +119,6 @@ export default function NewsPage() { date: item.Date ? item.Date.split("T")[0] : "", author: item.Author || "", }); - setOpenModal(true); } @@ -149,18 +149,17 @@ export default function NewsPage() { return (
-
-

News Media

+
+

+ News Media +

-
+
{ - setSearchText(e.target.value); - setCurrentPage(1); - }} - className="w-[250px]" + onChange={(e) => setSearchText(e.target.value)} + className="w-[250px] text-base" /> - -
- + - News List + News Archives - -
- - + +
+
+ - ID - Headline - Author - Date - Content - Actions + + ID + + + Headline + + + Author + + + Date + + + Content Preview + + + Actions + {loading ? ( - - + + - ) : paginatedData.length === 0 ? ( + ) : filteredNews.length === 0 ? ( - - No news found + + No news articles found ) : ( - paginatedData.map((item) => ( - - {item.Id} - - - {item.Headline} + filteredNews.map((item) => ( + + + {item.Id} - - {item.Author} - +
+ {item.Headline} +
+
+ + {item.Author || "-"} + + {item.Date ? new Date(item.Date).toLocaleDateString() : "-"} - - - {item.Content} + +
+ {item.Content} +
- - - - - - - + +
+ + + +
)) @@ -269,129 +301,197 @@ export default function NewsPage() {
- {/* PAGINATION */} -
-

- Page {currentPage} of {totalPages} -

- -
- - - + {!loading && totalItems > 0 && ( +
+
+ Total {totalItems} articles + (Page {currentPage} of{" "} + {totalPages}) +
+
+
+ Page {currentPage} of {totalPages} +
+
+ + +
+
-
+ )} - {/* CREATE / EDIT MODAL */} - + - {editing ? "Edit News" : "Add News"} + + {editing ? "Edit News Article" : "Add New News Article"} + -
- - - +
+
+

+ Article Information +

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +