[1.0.0] #19

Merged
ashir merged 80 commits from dev into main 2026-04-30 18:37:18 +00:00
3 changed files with 97 additions and 53 deletions
Showing only changes of commit 65e6413129 - Show all commits
@@ -1,10 +1,10 @@
import prisma from "../prisma/client.js"; import prisma from "../prisma/client.js";
import {sendEmail} from "../utils/sendEmail.js"; import { sendEmail } from "../utils/sendEmail.js";
import {getEmailsByType} from "../utils/getEmailByTypes.js"; import { getEmailsByType } from "../utils/getEmailByTypes.js";
export const createAppointment = async (req, res) => { export const createAppointment = async (req, res) => {
try { try {
const {name, mobileNumber, email, message, date, doctorId, departmentId} = const { name, mobileNumber, email, message, date, doctorId, departmentId } =
req.body; req.body;
if (!name || !mobileNumber || !doctorId || !departmentId || !date) { if (!name || !mobileNumber || !doctorId || !departmentId || !date) {
@@ -71,26 +71,49 @@ export const createAppointment = async (req, res) => {
export const getAppointments = async (req, res) => { export const getAppointments = async (req, res) => {
try { try {
const appointments = await prisma.appointment.findMany({ const page = parseInt(req.query.page) || 1;
include: { const limit = parseInt(req.query.limit) || 10;
doctor: true, const skip = (page - 1) * limit;
department: true, const { date, search } = req.query;
},
orderBy: { const where = {};
createdAt: "desc",
}, if (date) {
}); const start = new Date(date);
const end = new Date(date);
end.setDate(end.getDate() + 1);
where.date = { gte: start, lt: end };
}
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
{ mobileNumber: { contains: search } },
{ email: { contains: search, mode: "insensitive" } },
];
}
const [appointments, total] = await Promise.all([
prisma.appointment.findMany({
where,
include: { doctor: true, department: true },
orderBy: { createdAt: "desc" },
skip,
take: limit,
}),
prisma.appointment.count({ where }),
]);
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: appointments, data: appointments,
pagination: { total, page, limit, totalPages: Math.ceil(total / limit) },
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
res.status(500).json({ res
success: false, .status(500)
message: "Failed to fetch appointments", .json({ success: false, message: "Failed to fetch appointments" });
});
} }
}; };
@@ -98,7 +121,7 @@ export const getAppointments = async (req, res) => {
export const getAppointment = async (req, res) => { export const getAppointment = async (req, res) => {
try { try {
const {id} = req.params; const { id } = req.params;
const appointment = await prisma.appointment.findUnique({ const appointment = await prisma.appointment.findUnique({
where: { where: {
@@ -134,7 +157,7 @@ export const getAppointment = async (req, res) => {
export const getAppointmentsByDoctor = async (req, res) => { export const getAppointmentsByDoctor = async (req, res) => {
try { try {
const {doctorId} = req.params; const { doctorId } = req.params;
const appointments = await prisma.appointment.findMany({ const appointments = await prisma.appointment.findMany({
where: { where: {
@@ -166,7 +189,7 @@ export const getAppointmentsByDoctor = async (req, res) => {
export const getAppointmentsByDepartment = async (req, res) => { export const getAppointmentsByDepartment = async (req, res) => {
try { try {
const {departmentId} = req.params; const { departmentId } = req.params;
const appointments = await prisma.appointment.findMany({ const appointments = await prisma.appointment.findMany({
where: { where: {
@@ -195,7 +218,7 @@ export const getAppointmentsByDepartment = async (req, res) => {
export const updateAppointment = async (req, res) => { export const updateAppointment = async (req, res) => {
try { try {
const {id} = req.params; const { id } = req.params;
const appointment = await prisma.appointment.update({ const appointment = await prisma.appointment.update({
where: { where: {
@@ -226,7 +249,7 @@ export const updateAppointment = async (req, res) => {
export const deleteAppointment = async (req, res) => { export const deleteAppointment = async (req, res) => {
try { try {
const {id} = req.params; const { id } = req.params;
await prisma.appointment.delete({ await prisma.appointment.delete({
where: { where: {
+13 -2
View File
@@ -1,7 +1,18 @@
import apiClient from "@/api/client"; import apiClient from "@/api/client";
export const getAppointmentsApi = async () => { export const getAppointmentsApi = async (
const res = await apiClient.get("/appointments/getall"); page = 1,
limit = 10,
date = "",
search = "",
) => {
const params = new URLSearchParams({
page: String(page),
limit: String(limit),
...(date && { date }),
...(search && { search }),
});
const res = await apiClient.get(`/appointments/getall?${params}`);
return res.data; return res.data;
}; };
+40 -30
View File
@@ -45,52 +45,46 @@ export default function AppointmentPage() {
const [viewData, setViewData] = useState<any>(null); const [viewData, setViewData] = useState<any>(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10; const [totalPages, setTotalPages] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(10);
const fetchAll = useCallback(async () => { const fetchAll = useCallback(async () => {
setLoading(true); setLoading(true);
try { try {
const res = await getAppointmentsApi(); const res = await getAppointmentsApi(
currentPage,
itemsPerPage,
filterDate,
searchText,
);
setAppointments(res?.data || []); setAppointments(res?.data || []);
setTotalPages(res?.pagination?.totalPages || 1);
setTotalItems(res?.pagination?.total || 0);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [currentPage, itemsPerPage, filterDate, searchText]);
useEffect(() => { useEffect(() => {
fetchAll(); fetchAll();
}, [fetchAll]); }, [fetchAll]);
const filteredAppointments = appointments.filter((item) => { 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 const matchesDoctor = filterDoctor
? item.doctor?.name?.toLowerCase().includes(filterDoctor.toLowerCase()) ? item.doctor?.name?.toLowerCase().includes(filterDoctor.toLowerCase())
: true; : true;
const matchesDate = filterDate return matchesDoctor;
? new Date(item.date).toISOString().split("T")[0] === filterDate
: true;
return matchesSearch && matchesDoctor && matchesDate;
}); });
useEffect(() => { useEffect(() => {
setCurrentPage(1); setCurrentPage(1);
}, [searchText, filterDoctor, filterDate]); }, [searchText, filterDoctor, filterDate]);
const totalPages = Math.ceil(filteredAppointments.length / itemsPerPage); const indexOfFirstItem = (currentPage - 1) * itemsPerPage;
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = filteredAppointments.slice(
indexOfFirstItem,
indexOfLastItem,
);
function openView(item: any) { function openView(item: any) {
setViewData(item); setViewData(item);
@@ -126,17 +120,36 @@ export default function AppointmentPage() {
<Input <Input
placeholder="Search name / phone..." placeholder="Search name / phone..."
value={searchText} value={searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => {
setSearchText(e.target.value);
setCurrentPage(1);
}}
className="w-[220px] text-base" className="w-[220px] text-base"
/> />
<Input <Input
type="date" type="date"
value={filterDate} value={filterDate}
onChange={(e) => setFilterDate(e.target.value)} onChange={(e) => {
setFilterDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base" className="w-[160px] text-base"
/> />
<select
value={itemsPerPage}
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1);
}}
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-primary"
>
<option value={5}>5 / page</option>
<option value={10}>10 / page</option>
<option value={20}>20 / page</option>
</select>
<Button <Button
variant="outline" variant="outline"
onClick={fetchAll} onClick={fetchAll}
@@ -192,7 +205,7 @@ export default function AppointmentPage() {
<Loader2 className="h-8 w-8 animate-spin mx-auto" /> <Loader2 className="h-8 w-8 animate-spin mx-auto" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) : currentItems.length === 0 ? ( ) : filteredAppointments.length === 0 ? (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={6} colSpan={6}
@@ -202,7 +215,7 @@ export default function AppointmentPage() {
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
currentItems.map((item) => ( filteredAppointments.map((item) => (
<TableRow key={item.id} className="hover:bg-muted/50"> <TableRow key={item.id} className="hover:bg-muted/50">
<TableCell className="font-mono text-xs"> <TableCell className="font-mono text-xs">
{item.id} {item.id}
@@ -260,18 +273,15 @@ export default function AppointmentPage() {
</Table> </Table>
</div> </div>
{!loading && filteredAppointments.length > 0 && ( {!loading && totalItems > 0 && (
<div className="flex items-center justify-between px-2 py-6 border-t"> <div className="flex items-center justify-between px-2 py-6 border-t">
<div className="text-base text-muted-foreground"> <div className="text-base text-muted-foreground">
Showing{" "} Showing{" "}
<span className="font-semibold">{indexOfFirstItem + 1}</span> to{" "} <span className="font-semibold">{indexOfFirstItem + 1}</span> to{" "}
<span className="font-semibold"> <span className="font-semibold">
{Math.min(indexOfLastItem, filteredAppointments.length)} {Math.min(currentPage * itemsPerPage, totalItems)}
</span>{" "} </span>{" "}
of{" "} of <span className="font-semibold">{totalItems}</span>
<span className="font-semibold">
{filteredAppointments.length}
</span>
</div> </div>
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
<div className="text-base font-semibold"> <div className="text-base font-semibold">