diff --git a/backend/prisma/migrations/20260623120651_featured_field_for_doctor/migration.sql b/backend/prisma/migrations/20260623120651_featured_field_for_doctor/migration.sql new file mode 100644 index 0000000..1851719 --- /dev/null +++ b/backend/prisma/migrations/20260623120651_featured_field_for_doctor/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Doctor" ADD COLUMN "isFeatured" BOOLEAN NOT NULL DEFAULT false; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 993825d..2833572 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -27,6 +27,7 @@ model Doctor { workingStatus String? qualification String? isActive Boolean @default(true) + isFeatured Boolean @default(false) globalSortOrder Int @default(1000) specializations DoctorSpecialization[] professionalSummary String? @db.Text diff --git a/backend/src/controllers/doctor.controller.js b/backend/src/controllers/doctor.controller.js index 6692e2a..0dfa4f6 100644 --- a/backend/src/controllers/doctor.controller.js +++ b/backend/src/controllers/doctor.controller.js @@ -34,6 +34,7 @@ export const getAllDoctors = async (req, res) => { workingStatus: doc.workingStatus, qualification: doc.qualification, isActive: doc.isActive, + isFeatured: doc.isFeatured, experience: doc.experience, professionalSummary: doc.professionalSummary, globalSortOrder: doc.globalSortOrder, @@ -129,6 +130,7 @@ export const getDoctorByDoctorId = async (req, res) => { experience: doctor.experience, professionalSummary: doctor.professionalSummary, isActive: doctor.isActive, + isFeatured: doctor.isFeatured, seo: { seoTitle: doctor.seo?.seoTitle ?? '', metaDescription: doctor.seo?.metaDescription ?? '', @@ -240,6 +242,7 @@ export const createDoctor = async (req, res) => { workingStatus, qualification, isActive, + isFeatured, globalSortOrder, departments, experience, @@ -297,6 +300,7 @@ export const createDoctor = async (req, res) => { professionalSummary, seoId: seo.id, isActive: isActive !== undefined ? isActive : true, + isFeatured: isFeatured !== undefined ? isFeatured : false, globalSortOrder: globalSortOrder !== undefined ? Number(globalSortOrder) : 0, }, }); @@ -361,6 +365,7 @@ export const updateDoctor = async (req, res) => { workingStatus, qualification, isActive, + isFeatured, globalSortOrder, departments, experience, @@ -397,6 +402,19 @@ export const updateDoctor = async (req, res) => { message: `Doctor has been ${doctor.isActive ? 'deactivated' : 'activated'} successfully`, }); } + if (action === 'toggleFeatured') { + await prisma.doctor.update({ + where: { id: doctor.id }, + data: { + isFeatured: !doctor.isFeatured, + }, + }); + + return res.status(200).json({ + success: true, + message: `Doctor has been ${doctor.isFeatured ? 'removed from featured' : 'marked as featured'} successfully`, + }); + } const messages = []; if (!doctorId) messages.push('Doctor ID is required'); @@ -423,7 +441,8 @@ export const updateDoctor = async (req, res) => { image, workingStatus, qualification, - isActive, + isActive: isActive !== undefined ? isActive : undefined, + isFeatured: isFeatured !== undefined ? isFeatured : undefined, experience: experience ? Number(experience) : null, professionalSummary, globalSortOrder: globalSortOrder !== undefined ? Number(globalSortOrder) : undefined, @@ -700,3 +719,52 @@ export const getDoctorTimingById = async (req, res) => { }); } }; + +export const getFeaturedDoctors = async (req, res) => { + try { + const doctors = await prisma.doctor.findMany({ + where: { + isActive: true, + isFeatured: true, + }, + include: { + seo: { + select: { + slug: true, + }, + }, + departments: { + include: { + department: true, + }, + }, + }, + orderBy: [{ globalSortOrder: 'asc' }, { name: 'asc' }], + }); + + const data = doctors.map((doc) => ({ + doctorId: doc.doctorId, + name: doc.name, + image: doc.image ?? '', + designation: doc.designation, + qualification: doc.qualification, + experience: doc.experience, + slug: doc.seo?.slug ?? '', + departments: doc.departments.map((d) => ({ + departmentId: d.department.departmentId, + departmentName: d.department.name, + })), + })); + + res.status(200).json({ + success: true, + data, + }); + } catch (error) { + console.error(error); + res.status(500).json({ + success: false, + message: 'Failed to fetch featured doctors', + }); + } +}; diff --git a/backend/src/controllers/healthCheck.controller.js b/backend/src/controllers/healthCheck.controller.js index 8b05595..eb0361a 100644 --- a/backend/src/controllers/healthCheck.controller.js +++ b/backend/src/controllers/healthCheck.controller.js @@ -486,3 +486,33 @@ export const getAllInquiries = async (req, res) => { return res.status(500).json({ success: false, message: 'Failed to fetch inquiries' }); } }; + +export const getFeaturedPackages = async (req, res) => { + try { + const packages = await prisma.healthPackage.findMany({ + where: { + isActive: true, + isFeatured: true, + category: { + isActive: true, + }, + }, + include: { + category: true, + seo: true, + }, + orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }], + }); + + return res.status(200).json({ + success: true, + data: packages, + }); + } catch (error) { + console.error(error); + return res.status(500).json({ + success: false, + message: 'Failed to fetch featured packages', + }); + } +}; diff --git a/backend/src/routes/doctor.routes.js b/backend/src/routes/doctor.routes.js index 3a4bcf1..86b8dfd 100644 --- a/backend/src/routes/doctor.routes.js +++ b/backend/src/routes/doctor.routes.js @@ -8,6 +8,7 @@ import { getDoctorTimingById, getDoctorByDoctorId, getDoctorsByDepartmentId, + getFeaturedDoctors, } from '../controllers/doctor.controller.js'; import jwtAuthMiddleware from '../middleware/auth.js'; @@ -18,6 +19,7 @@ router.get('/getAll', getAllDoctors); router.get('/search', getDoctorsByDepartmentId); router.get('/getTimings', getDoctorTimings); router.get('/getTimings/:doctorId', getDoctorTimingById); +router.get('/featured', getFeaturedDoctors); router.get('/:doctorId', getDoctorByDoctorId); router.post('/', jwtAuthMiddleware, createDoctor); diff --git a/backend/src/routes/healthCheck.route.js b/backend/src/routes/healthCheck.route.js index fcc7db2..3b7503a 100644 --- a/backend/src/routes/healthCheck.route.js +++ b/backend/src/routes/healthCheck.route.js @@ -12,6 +12,7 @@ import { createPackage, updatePackage, deletePackage, + getFeaturedPackages, // Inquiries createPackageInquiry, @@ -26,6 +27,7 @@ router.get('/packages', getAllPackages); router.get('/packages/:slug', getPackageBySlug); router.get('/categories', getAllCategories); router.post('/inquiry', createPackageInquiry); +router.get('/featured', getFeaturedPackages); router.get('/inquiries', jwtAuthMiddleware, getAllInquiries); router.post('/', jwtAuthMiddleware, createPackage); diff --git a/frontend/src/api/doctor.ts b/frontend/src/api/doctor.ts index 1f5a3e4..7180c5a 100644 --- a/frontend/src/api/doctor.ts +++ b/frontend/src/api/doctor.ts @@ -9,6 +9,7 @@ export interface Doctor { workingStatus?: string; qualification?: string; isActive: boolean; + isFeatured: boolean; globalSortOrder: number; departments?: { @@ -53,7 +54,7 @@ export const createDoctorApi = async (data: Doctor) => { export const updateDoctorApi = async ( doctorId: string, data: Partial, - action: 'toggleStatus' | 'updateDetails' = 'updateDetails' + action: 'toggleStatus' | 'toggleFeatured' | 'updateDetails' = 'updateDetails' ) => { try { const res = await apiClient.patch(`/doctors/${doctorId}/${action}`, data); diff --git a/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx b/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx index b76638b..7dcc199 100644 --- a/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx +++ b/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx @@ -120,22 +120,40 @@ export default function HealthPackageModal({ /> -
-
-

Active Visibility

+
+
+
+

Active

+

Show publicly

+
-

Show this package publicly

+ + setPkgForm({ + ...pkgForm, + isActive: val, + }) + } + />
- - setPkgForm({ - ...pkgForm, - isActive: val, - }) - } - /> +
+
+

Featured

+

Show on homepage

+
+ + + setPkgForm({ + ...pkgForm, + isFeatured: val, + }) + } + /> +
diff --git a/frontend/src/pages/Doctor.tsx b/frontend/src/pages/Doctor.tsx index 1a147fb..099d112 100644 --- a/frontend/src/pages/Doctor.tsx +++ b/frontend/src/pages/Doctor.tsx @@ -51,6 +51,7 @@ export default function DoctorPage() { workingStatus: '', qualification: '', isActive: true, + isFeatured: false, globalSortOrder: 0, departments: [], professionalSummary: '', @@ -156,6 +157,22 @@ export default function DoctorPage() { } }; + const handleToggleFeatured = async (doc: any) => { + try { + const newFeaturedStatus = !doc.isFeatured; + + const payload = { + isFeatured: newFeaturedStatus, + }; + + await updateDoctorApi(doc.doctorId, payload, 'toggleFeatured'); + + fetchAll(); + } catch (err) { + console.error('Failed to update featured status', err); + } + }; + function handleDepartmentToggle(depId: string) { const exists = form.departments.find((d: any) => d.departmentId === depId); if (exists) { @@ -201,6 +218,7 @@ export default function DoctorPage() { experience: '', professionalSummary: '', isActive: true, + isFeatured: false, globalSortOrder: 0, specializations: [ { @@ -235,6 +253,7 @@ export default function DoctorPage() { workingStatus: doc.workingStatus, qualification: doc.qualification, isActive: doc.isActive ?? true, + isFeatured: doc.isFeatured ?? false, globalSortOrder: doc.globalSortOrder ?? 0, experience: doc.experience || '', professionalSummary: doc.professionalSummary || '', @@ -353,14 +372,15 @@ export default function DoctorPage() {
- +
Priority Doctor Info Designation Departments (Hierarchy) - Status (Active) + Status (Active) + Featured Actions @@ -368,13 +388,13 @@ export default function DoctorPage() { {loading ? ( - + ) : currentItems.length === 0 ? ( - + No doctors found @@ -423,6 +443,15 @@ export default function DoctorPage() { + +
+ handleToggleFeatured(doc)} /> + + {doc.isFeatured ? 'Featured' : 'Hidden'} + +
+
+
+
+ + setForm({ ...form, isFeatured: val })} + /> +
+
+ + { + await updateHealthPackageApi(pkg.id!, { + isFeatured: !pkg.isFeatured, + }); + fetchData(); + }} + /> +