2026-05-26 15:48:01 +05:30
|
|
|
import prisma from '../prisma/client.js';
|
|
|
|
|
import { sendEmail } from '../utils/sendEmail.js';
|
|
|
|
|
import { getEmailsByType } from '../utils/getEmailByTypes.js';
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
export const createAppointment = async (req, res) => {
|
|
|
|
|
try {
|
2026-05-26 15:48:01 +05:30
|
|
|
const { name, mobileNumber, email, message, date, doctorId, departmentId } = req.body;
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
if (!name || !mobileNumber || !doctorId || !departmentId || !date) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Required fields missing',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const appointment = await prisma.appointment.create({
|
|
|
|
|
data: {
|
|
|
|
|
name,
|
|
|
|
|
mobileNumber,
|
|
|
|
|
email,
|
|
|
|
|
message,
|
|
|
|
|
date: new Date(date),
|
|
|
|
|
doctorId,
|
|
|
|
|
departmentId,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
doctor: true,
|
|
|
|
|
department: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-19 13:12:04 +05:30
|
|
|
try {
|
2026-05-26 15:48:01 +05:30
|
|
|
const emailList = await getEmailsByType('APPOINTMENT');
|
2026-03-19 13:12:04 +05:30
|
|
|
|
|
|
|
|
if (emailList) {
|
|
|
|
|
await sendEmail({
|
|
|
|
|
to: emailList,
|
2026-05-26 15:48:01 +05:30
|
|
|
subject: 'New Appointment Booked',
|
2026-03-19 13:12:04 +05:30
|
|
|
html: `
|
2026-04-27 17:29:33 +05:30
|
|
|
<div style="font-family: Arial, sans-serif; background-color: #f4f6f8; padding: 20px;">
|
|
|
|
|
|
|
|
|
|
<div style="max-width: 600px; margin: auto; background: #ffffff; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 10px rgba(0,0,0,0.05);">
|
|
|
|
|
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
<div style="background-color: #0d6efd; color: #ffffff; padding: 20px;">
|
|
|
|
|
<h2 style="margin: 0;">GG Hospital</h2>
|
|
|
|
|
<p style="margin: 5px 0 0; font-size: 14px;">
|
|
|
|
|
New Appointment Booked
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Body -->
|
|
|
|
|
<div style="padding: 20px; color: #333;">
|
|
|
|
|
|
|
|
|
|
<h3 style="margin-top: 0;">Patient Details</h3>
|
|
|
|
|
|
|
|
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
|
|
|
<tr>
|
|
|
|
|
<td style="padding: 8px 0;"><b>Name:</b></td>
|
|
|
|
|
<td style="padding: 8px 0;">${name}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style="padding: 8px 0;"><b>Phone:</b></td>
|
|
|
|
|
<td style="padding: 8px 0;">${mobileNumber}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style="padding: 8px 0;"><b>Email:</b></td>
|
2026-05-26 15:48:01 +05:30
|
|
|
<td style="padding: 8px 0;">${email || '-'}</td>
|
2026-04-27 17:29:33 +05:30
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
<h3 style="margin-top: 20px;">Appointment Details</h3>
|
|
|
|
|
|
|
|
|
|
<table style="width: 100%; border-collapse: collapse;">
|
|
|
|
|
<tr>
|
|
|
|
|
<td style="padding: 8px 0;"><b>Doctor:</b></td>
|
2026-05-26 15:48:01 +05:30
|
|
|
<td style="padding: 8px 0;">${appointment.doctor?.name || '-'}</td>
|
2026-04-27 17:29:33 +05:30
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style="padding: 8px 0;"><b>Department:</b></td>
|
2026-05-26 15:48:01 +05:30
|
|
|
<td style="padding: 8px 0;">${appointment.department?.name || '-'}</td>
|
2026-04-27 17:29:33 +05:30
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td style="padding: 8px 0;"><b>Date:</b></td>
|
|
|
|
|
<td style="padding: 8px 0;">
|
2026-05-26 15:48:01 +05:30
|
|
|
${new Date(date).toLocaleDateString('en-GB', {
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
month: 'long',
|
|
|
|
|
year: 'numeric',
|
2026-05-13 11:57:33 +05:30
|
|
|
})}
|
|
|
|
|
</td>
|
2026-04-27 17:29:33 +05:30
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
<!-- Message Box -->
|
|
|
|
|
<div style="margin-top: 20px;">
|
|
|
|
|
<h3>Message</h3>
|
|
|
|
|
<div style="
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
padding: 15px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
overflow-wrap: anywhere;
|
|
|
|
|
">
|
2026-05-26 15:48:01 +05:30
|
|
|
${message ? message.replace(/\n/g, '<br/>') : '-'}
|
2026-04-27 17:29:33 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Footer -->
|
|
|
|
|
<div style="background: #f1f1f1; padding: 15px; text-align: center; font-size: 12px; color: #666;">
|
|
|
|
|
This appointment was booked via the GG Hospital website.
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
2026-03-19 13:12:04 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
2026-05-26 15:48:01 +05:30
|
|
|
console.error('Email failed:', err);
|
2026-03-19 13:12:04 +05:30
|
|
|
}
|
|
|
|
|
|
2026-03-16 10:16:27 +05:30
|
|
|
res.status(201).json({
|
|
|
|
|
success: true,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Appointment booked successfully',
|
2026-03-16 10:16:27 +05:30
|
|
|
data: appointment,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to create appointment',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// GET ALL APPOINTMENTS
|
|
|
|
|
|
|
|
|
|
export const getAppointments = async (req, res) => {
|
|
|
|
|
try {
|
2026-04-24 11:40:14 +05:30
|
|
|
const page = parseInt(req.query.page) || 1;
|
|
|
|
|
const limit = parseInt(req.query.limit) || 10;
|
|
|
|
|
const skip = (page - 1) * limit;
|
2026-05-13 14:20:51 +05:30
|
|
|
|
|
|
|
|
const { date, startDate, endDate, search } = req.query;
|
2026-04-24 11:40:14 +05:30
|
|
|
|
|
|
|
|
const where = {};
|
|
|
|
|
|
2026-05-26 15:48:01 +05:30
|
|
|
const hasSingleDate = date && date.trim() !== '';
|
2026-05-13 14:20:51 +05:30
|
|
|
|
2026-05-26 15:48:01 +05:30
|
|
|
const hasRange = (startDate && startDate.trim() !== '') || (endDate && endDate.trim() !== '');
|
2026-05-13 14:20:51 +05:30
|
|
|
|
|
|
|
|
if (hasSingleDate) {
|
2026-04-24 11:40:14 +05:30
|
|
|
const start = new Date(date);
|
2026-05-13 14:20:51 +05:30
|
|
|
start.setHours(0, 0, 0, 0);
|
|
|
|
|
|
2026-04-24 11:40:14 +05:30
|
|
|
const end = new Date(date);
|
2026-05-13 14:20:51 +05:30
|
|
|
end.setHours(23, 59, 59, 999);
|
|
|
|
|
|
|
|
|
|
where.date = {
|
|
|
|
|
gte: start,
|
|
|
|
|
lte: end,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!hasSingleDate && hasRange) {
|
|
|
|
|
const dateFilter = {};
|
|
|
|
|
|
2026-05-26 15:48:01 +05:30
|
|
|
if (startDate && startDate.trim() !== '') {
|
2026-05-13 14:20:51 +05:30
|
|
|
const start = new Date(startDate);
|
|
|
|
|
start.setHours(0, 0, 0, 0);
|
|
|
|
|
|
|
|
|
|
dateFilter.gte = start;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 15:48:01 +05:30
|
|
|
if (endDate && endDate.trim() !== '') {
|
2026-05-13 14:20:51 +05:30
|
|
|
const end = new Date(endDate);
|
|
|
|
|
end.setHours(23, 59, 59, 999);
|
|
|
|
|
|
|
|
|
|
dateFilter.lte = end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
where.date = dateFilter;
|
2026-04-24 11:40:14 +05:30
|
|
|
}
|
|
|
|
|
|
2026-05-26 15:48:01 +05:30
|
|
|
if (search && search.trim() !== '') {
|
2026-04-24 11:40:14 +05:30
|
|
|
where.OR = [
|
2026-05-26 15:48:01 +05:30
|
|
|
{ name: { contains: search, mode: 'insensitive' } },
|
2026-04-24 11:40:14 +05:30
|
|
|
{ mobileNumber: { contains: search } },
|
2026-05-26 15:48:01 +05:30
|
|
|
{ email: { contains: search, mode: 'insensitive' } },
|
2026-04-24 11:40:14 +05:30
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [appointments, total] = await Promise.all([
|
|
|
|
|
prisma.appointment.findMany({
|
|
|
|
|
where,
|
2026-05-13 14:20:51 +05:30
|
|
|
include: {
|
|
|
|
|
doctor: true,
|
|
|
|
|
department: true,
|
|
|
|
|
},
|
|
|
|
|
orderBy: {
|
2026-05-26 15:48:01 +05:30
|
|
|
createdAt: 'desc',
|
2026-05-13 14:20:51 +05:30
|
|
|
},
|
2026-04-24 11:40:14 +05:30
|
|
|
skip,
|
|
|
|
|
take: limit,
|
|
|
|
|
}),
|
2026-05-13 14:20:51 +05:30
|
|
|
|
|
|
|
|
prisma.appointment.count({
|
|
|
|
|
where,
|
|
|
|
|
}),
|
2026-04-24 11:40:14 +05:30
|
|
|
]);
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: appointments,
|
2026-05-13 14:20:51 +05:30
|
|
|
pagination: {
|
|
|
|
|
total,
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
totalPages: Math.ceil(total / limit),
|
|
|
|
|
},
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
2026-05-13 14:20:51 +05:30
|
|
|
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to fetch appointments',
|
2026-05-13 14:20:51 +05:30
|
|
|
});
|
2026-03-16 10:16:27 +05:30
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// GET SINGLE APPOINTMENT
|
|
|
|
|
|
|
|
|
|
export const getAppointment = async (req, res) => {
|
|
|
|
|
try {
|
2026-04-24 11:40:14 +05:30
|
|
|
const { id } = req.params;
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
const appointment = await prisma.appointment.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
id: Number(id),
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
doctor: true,
|
|
|
|
|
department: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!appointment) {
|
|
|
|
|
return res.status(404).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Appointment not found',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: appointment,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to fetch appointment',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// GET APPOINTMENTS BY DOCTOR
|
|
|
|
|
|
|
|
|
|
export const getAppointmentsByDoctor = async (req, res) => {
|
|
|
|
|
try {
|
2026-04-24 11:40:14 +05:30
|
|
|
const { doctorId } = req.params;
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
const appointments = await prisma.appointment.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
doctorId,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
doctor: true,
|
|
|
|
|
department: true,
|
|
|
|
|
},
|
|
|
|
|
orderBy: {
|
2026-05-26 15:48:01 +05:30
|
|
|
date: 'asc',
|
2026-03-16 10:16:27 +05:30
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: appointments,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to fetch doctor appointments',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// GET APPOINTMENTS BY DEPARTMENT
|
|
|
|
|
|
|
|
|
|
export const getAppointmentsByDepartment = async (req, res) => {
|
|
|
|
|
try {
|
2026-04-24 11:40:14 +05:30
|
|
|
const { departmentId } = req.params;
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
const appointments = await prisma.appointment.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
departmentId,
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
doctor: true,
|
|
|
|
|
department: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: appointments,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to fetch department appointments',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// UPDATE APPOINTMENT
|
|
|
|
|
|
|
|
|
|
export const updateAppointment = async (req, res) => {
|
|
|
|
|
try {
|
2026-04-24 11:40:14 +05:30
|
|
|
const { id } = req.params;
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
const appointment = await prisma.appointment.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: Number(id),
|
|
|
|
|
},
|
|
|
|
|
data: req.body,
|
|
|
|
|
include: {
|
|
|
|
|
doctor: true,
|
|
|
|
|
department: true,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
success: true,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Appointment updated successfully',
|
2026-03-16 10:16:27 +05:30
|
|
|
data: appointment,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to update appointment',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//DELETE APPOINTMENT
|
|
|
|
|
|
|
|
|
|
export const deleteAppointment = async (req, res) => {
|
|
|
|
|
try {
|
2026-04-24 11:40:14 +05:30
|
|
|
const { id } = req.params;
|
2026-03-16 10:16:27 +05:30
|
|
|
|
|
|
|
|
await prisma.appointment.delete({
|
|
|
|
|
where: {
|
|
|
|
|
id: Number(id),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
success: true,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Appointment deleted successfully',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
2026-05-26 15:48:01 +05:30
|
|
|
message: 'Failed to delete appointment',
|
2026-03-16 10:16:27 +05:30
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|