diff --git a/backend/src/controllers/appointment.controller.js b/backend/src/controllers/appointment.controller.js index 3984c17..35e6cf6 100644 --- a/backend/src/controllers/appointment.controller.js +++ b/backend/src/controllers/appointment.controller.js @@ -1,10 +1,10 @@ import prisma from "../prisma/client.js"; -import {sendEmail} from "../utils/sendEmail.js"; -import {getEmailsByType} from "../utils/getEmailByTypes.js"; +import { sendEmail } from "../utils/sendEmail.js"; +import { getEmailsByType } from "../utils/getEmailByTypes.js"; export const createAppointment = async (req, res) => { try { - const {name, mobileNumber, email, message, date, doctorId, departmentId} = + const { name, mobileNumber, email, message, date, doctorId, departmentId } = req.body; if (!name || !mobileNumber || !doctorId || !departmentId || !date) { @@ -71,19 +71,98 @@ export const createAppointment = async (req, res) => { export const getAppointments = async (req, res) => { try { - const appointments = await prisma.appointment.findMany({ - include: { - doctor: true, - department: true, - }, - orderBy: { - createdAt: "desc", - }, - }); + const page = parseInt(req.query.page); + const limit = parseInt(req.query.limit); - res.status(200).json({ + const search = req.query.search || ""; + const doctor = req.query.doctor || ""; + const department = req.query.department || ""; + const date = req.query.date || ""; + + if (!page && !limit) { + const appointments = await prisma.appointment.findMany({ + include: { + doctor: true, + department: true, + }, + orderBy: { createdAt: "desc" }, + }); + + return res.status(200).json({ + success: true, + data: appointments, + meta: null, + }); + } + + const currentPage = page || 1; + const currentLimit = limit || 10; + const skip = (currentPage - 1) * currentLimit; + + const where = { + AND: [ + search + ? { + OR: [ + { name: { contains: search, mode: "insensitive" } }, + { mobileNumber: { contains: search } }, + { email: { contains: search, mode: "insensitive" } }, + ], + } + : {}, + + doctor + ? { + doctor: { + name: { contains: doctor, mode: "insensitive" }, + }, + } + : {}, + + department + ? { + department: { + name: { contains: department, mode: "insensitive" }, + }, + } + : {}, + + date + ? { + date: { + gte: new Date(date), + lt: new Date( + new Date(date).setDate(new Date(date).getDate() + 1), + ), + }, + } + : {}, + ], + }; + + const [appointments, total] = await Promise.all([ + prisma.appointment.findMany({ + where, + include: { + doctor: true, + department: true, + }, + orderBy: { createdAt: "desc" }, + skip, + take: currentLimit, + }), + prisma.appointment.count({ where }), + ]); + + return res.status(200).json({ success: true, data: appointments, + meta: { + total, + page: currentPage, + limit: currentLimit, + totalPages: Math.ceil(total / currentLimit), + }, }); } catch (error) { console.error(error); @@ -98,7 +177,7 @@ export const getAppointments = async (req, res) => { export const getAppointment = async (req, res) => { try { - const {id} = req.params; + const { id } = req.params; const appointment = await prisma.appointment.findUnique({ where: { @@ -134,7 +213,7 @@ export const getAppointment = async (req, res) => { export const getAppointmentsByDoctor = async (req, res) => { try { - const {doctorId} = req.params; + const { doctorId } = req.params; const appointments = await prisma.appointment.findMany({ where: { @@ -166,7 +245,7 @@ export const getAppointmentsByDoctor = async (req, res) => { export const getAppointmentsByDepartment = async (req, res) => { try { - const {departmentId} = req.params; + const { departmentId } = req.params; const appointments = await prisma.appointment.findMany({ where: { @@ -195,7 +274,7 @@ export const getAppointmentsByDepartment = async (req, res) => { export const updateAppointment = async (req, res) => { try { - const {id} = req.params; + const { id } = req.params; const appointment = await prisma.appointment.update({ where: { @@ -226,7 +305,7 @@ export const updateAppointment = async (req, res) => { export const deleteAppointment = async (req, res) => { try { - const {id} = req.params; + const { id } = req.params; await prisma.appointment.delete({ where: { diff --git a/frontend/src/api/appointment.ts b/frontend/src/api/appointment.ts index c8ce146..27c62c3 100644 --- a/frontend/src/api/appointment.ts +++ b/frontend/src/api/appointment.ts @@ -1,7 +1,22 @@ import apiClient from "@/api/client"; -export const getAppointmentsApi = async () => { - const res = await apiClient.get("/appointments/getall"); +export const getAppointmentsApi = async ( + page?: number, + limit?: number, + search?: string, + doctor?: string, + department?: string, + date?: string, +) => { + let url = "/appointments/getAll"; + + if (page && limit) { + url += `?page=${page}&limit=${limit}&search=${search || ""}&doctor=${ + doctor || "" + }&department=${department || ""}&date=${date || ""}`; + } + + const res = await apiClient.get(url); return res.data; }; diff --git a/frontend/src/pages/Appointment.tsx b/frontend/src/pages/Appointment.tsx index 4eb44d6..8a93008 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,18 @@ 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 {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, +} from "lucide-react"; export default function AppointmentPage() { const [appointments, setAppointments] = useState([]); @@ -28,106 +34,153 @@ export default function AppointmentPage() { const [filterDepartment, setFilterDepartment] = useState(""); const [filterDate, setFilterDate] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(10); + + const [meta, setMeta] = useState({}); + const fetchAll = useCallback(async () => { setLoading(true); try { - const res = await getAppointmentsApi(); + const res = await getAppointmentsApi( + currentPage, + itemsPerPage, + searchText, + filterDoctor, + filterDepartment, + filterDate, + ); + setAppointments(res?.data || []); + setMeta(res?.meta || {}); } catch (err) { console.error(err); } finally { setLoading(false); } - }, []); + }, [ + currentPage, + itemsPerPage, + searchText, + filterDoctor, + filterDepartment, + filterDate, + ]); useEffect(() => { fetchAll(); }, [fetchAll]); - const filteredAppointments = appointments.filter((item) => { - const matchesSearch = - item.name?.toLowerCase().includes(searchText.toLowerCase()) || - item.mobileNumber?.includes(searchText) || - item.email?.toLowerCase().includes(searchText.toLowerCase()); - - const matchesDoctor = filterDoctor - ? 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; - }); - async function handleDelete(id: number) { if (!confirm("Delete appointment?")) return; await deleteAppointmentApi(id); fetchAll(); } - const handleExport = () => { - const exportData = filteredAppointments.map((item) => ({ - ID: item.id, - Name: item.name, - Phone: item.mobileNumber, - Email: item.email, - Doctor: item.doctor?.name, - Department: item.department?.name, - Date: new Date(item.date).toLocaleDateString(), - Message: item.message, - })); + const handleExport = async () => { + try { + const res = await getAppointmentsApi(); - exportToExcel(exportData, "appointments"); + let data = res?.data || []; + + data = data.filter((item: any) => { + const matchesSearch = + item.name?.toLowerCase().includes(searchText.toLowerCase()) || + item.mobileNumber?.includes(searchText) || + item.email?.toLowerCase().includes(searchText.toLowerCase()); + + const matchesDoctor = filterDoctor + ? 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 + ); + }); + + const exportData = data.map((item: any) => ({ + ID: item.id, + Name: item.name, + Phone: item.mobileNumber, + Email: item.email, + Doctor: item.doctor?.name, + Department: item.department?.name, + Date: new Date(item.date).toLocaleDateString(), + Message: item.message, + })); + + exportToExcel(exportData, "appointments"); + } catch (err) { + console.error(err); + } }; return (
-
+

Appointments

-
+
setSearchText(e.target.value)} - className="w-[220px]" + onChange={(e) => { + setSearchText(e.target.value); + setCurrentPage(1); + }} + className="w-[200px]" /> setFilterDoctor(e.target.value)} - className="w-[180px]" + className="w-[160px]" /> setFilterDepartment(e.target.value)} - className="w-[200px]" + className="w-[160px]" /> setFilterDate(e.target.value)} - className="w-[180px]" + className="w-[160px]" /> - - @@ -140,74 +193,77 @@ export default function AppointmentPage() { -
- - +
+ + + ID + Name + Phone + Doctor + Department + Date + Actions + + + + + {loading ? ( - ID - Name - Phone - Email - Doctor - Department - Appointment Date - Message - Generated on - - Actions + + + - - - - {loading ? ( - - - + ) : appointments.length === 0 ? ( + + + No appointments found + + + ) : ( + appointments.map((item) => ( + + {item.id} + {item.name} + {item.mobileNumber} + {item.doctor?.name} + {item.department?.name} + + {new Date(item.date).toLocaleDateString()} + + + - ) : filteredAppointments.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()} - +
+

+ Page {meta.page || 1} of {meta.totalPages || 1} +

- - {item.message} - - - {" "} - {new Date(item.createdAt).toLocaleDateString()} - +
+ - - - - - )) - )} - - + +