feat: add featured doctors and health packages APIs #50
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Doctor" ADD COLUMN "isFeatured" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -27,6 +27,7 @@ model Doctor {
|
|||||||
workingStatus String?
|
workingStatus String?
|
||||||
qualification String?
|
qualification String?
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
|
isFeatured Boolean @default(false)
|
||||||
globalSortOrder Int @default(1000)
|
globalSortOrder Int @default(1000)
|
||||||
specializations DoctorSpecialization[]
|
specializations DoctorSpecialization[]
|
||||||
professionalSummary String? @db.Text
|
professionalSummary String? @db.Text
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const getAllDoctors = async (req, res) => {
|
|||||||
workingStatus: doc.workingStatus,
|
workingStatus: doc.workingStatus,
|
||||||
qualification: doc.qualification,
|
qualification: doc.qualification,
|
||||||
isActive: doc.isActive,
|
isActive: doc.isActive,
|
||||||
|
isFeatured: doc.isFeatured,
|
||||||
experience: doc.experience,
|
experience: doc.experience,
|
||||||
professionalSummary: doc.professionalSummary,
|
professionalSummary: doc.professionalSummary,
|
||||||
globalSortOrder: doc.globalSortOrder,
|
globalSortOrder: doc.globalSortOrder,
|
||||||
@@ -129,6 +130,7 @@ export const getDoctorByDoctorId = async (req, res) => {
|
|||||||
experience: doctor.experience,
|
experience: doctor.experience,
|
||||||
professionalSummary: doctor.professionalSummary,
|
professionalSummary: doctor.professionalSummary,
|
||||||
isActive: doctor.isActive,
|
isActive: doctor.isActive,
|
||||||
|
isFeatured: doctor.isFeatured,
|
||||||
seo: {
|
seo: {
|
||||||
seoTitle: doctor.seo?.seoTitle ?? '',
|
seoTitle: doctor.seo?.seoTitle ?? '',
|
||||||
metaDescription: doctor.seo?.metaDescription ?? '',
|
metaDescription: doctor.seo?.metaDescription ?? '',
|
||||||
@@ -240,6 +242,7 @@ export const createDoctor = async (req, res) => {
|
|||||||
workingStatus,
|
workingStatus,
|
||||||
qualification,
|
qualification,
|
||||||
isActive,
|
isActive,
|
||||||
|
isFeatured,
|
||||||
globalSortOrder,
|
globalSortOrder,
|
||||||
departments,
|
departments,
|
||||||
experience,
|
experience,
|
||||||
@@ -297,6 +300,7 @@ export const createDoctor = async (req, res) => {
|
|||||||
professionalSummary,
|
professionalSummary,
|
||||||
seoId: seo.id,
|
seoId: seo.id,
|
||||||
isActive: isActive !== undefined ? isActive : true,
|
isActive: isActive !== undefined ? isActive : true,
|
||||||
|
isFeatured: isFeatured !== undefined ? isFeatured : false,
|
||||||
globalSortOrder: globalSortOrder !== undefined ? Number(globalSortOrder) : 0,
|
globalSortOrder: globalSortOrder !== undefined ? Number(globalSortOrder) : 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -361,6 +365,7 @@ export const updateDoctor = async (req, res) => {
|
|||||||
workingStatus,
|
workingStatus,
|
||||||
qualification,
|
qualification,
|
||||||
isActive,
|
isActive,
|
||||||
|
isFeatured,
|
||||||
globalSortOrder,
|
globalSortOrder,
|
||||||
departments,
|
departments,
|
||||||
experience,
|
experience,
|
||||||
@@ -397,6 +402,19 @@ export const updateDoctor = async (req, res) => {
|
|||||||
message: `Doctor has been ${doctor.isActive ? 'deactivated' : 'activated'} successfully`,
|
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 = [];
|
const messages = [];
|
||||||
if (!doctorId) messages.push('Doctor ID is required');
|
if (!doctorId) messages.push('Doctor ID is required');
|
||||||
@@ -423,7 +441,8 @@ export const updateDoctor = async (req, res) => {
|
|||||||
image,
|
image,
|
||||||
workingStatus,
|
workingStatus,
|
||||||
qualification,
|
qualification,
|
||||||
isActive,
|
isActive: isActive !== undefined ? isActive : undefined,
|
||||||
|
isFeatured: isFeatured !== undefined ? isFeatured : undefined,
|
||||||
experience: experience ? Number(experience) : null,
|
experience: experience ? Number(experience) : null,
|
||||||
professionalSummary,
|
professionalSummary,
|
||||||
globalSortOrder: globalSortOrder !== undefined ? Number(globalSortOrder) : undefined,
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -486,3 +486,33 @@ export const getAllInquiries = async (req, res) => {
|
|||||||
return res.status(500).json({ success: false, message: 'Failed to fetch inquiries' });
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
getDoctorTimingById,
|
getDoctorTimingById,
|
||||||
getDoctorByDoctorId,
|
getDoctorByDoctorId,
|
||||||
getDoctorsByDepartmentId,
|
getDoctorsByDepartmentId,
|
||||||
|
getFeaturedDoctors,
|
||||||
} from '../controllers/doctor.controller.js';
|
} from '../controllers/doctor.controller.js';
|
||||||
|
|
||||||
import jwtAuthMiddleware from '../middleware/auth.js';
|
import jwtAuthMiddleware from '../middleware/auth.js';
|
||||||
@@ -19,6 +20,7 @@ router.get('/search', getDoctorsByDepartmentId);
|
|||||||
router.get('/getTimings', getDoctorTimings);
|
router.get('/getTimings', getDoctorTimings);
|
||||||
router.get('/getTimings/:doctorId', getDoctorTimingById);
|
router.get('/getTimings/:doctorId', getDoctorTimingById);
|
||||||
router.get('/:doctorId', getDoctorByDoctorId);
|
router.get('/:doctorId', getDoctorByDoctorId);
|
||||||
|
router.get('/featured', getFeaturedDoctors);
|
||||||
|
|
||||||
router.post('/', jwtAuthMiddleware, createDoctor);
|
router.post('/', jwtAuthMiddleware, createDoctor);
|
||||||
router.patch('/:doctorId/:action', jwtAuthMiddleware, updateDoctor);
|
router.patch('/:doctorId/:action', jwtAuthMiddleware, updateDoctor);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
createPackage,
|
createPackage,
|
||||||
updatePackage,
|
updatePackage,
|
||||||
deletePackage,
|
deletePackage,
|
||||||
|
getFeaturedPackages,
|
||||||
|
|
||||||
// Inquiries
|
// Inquiries
|
||||||
createPackageInquiry,
|
createPackageInquiry,
|
||||||
@@ -26,6 +27,7 @@ router.get('/packages', getAllPackages);
|
|||||||
router.get('/packages/:slug', getPackageBySlug);
|
router.get('/packages/:slug', getPackageBySlug);
|
||||||
router.get('/categories', getAllCategories);
|
router.get('/categories', getAllCategories);
|
||||||
router.post('/inquiry', createPackageInquiry);
|
router.post('/inquiry', createPackageInquiry);
|
||||||
|
router.get('/featured', getFeaturedPackages);
|
||||||
|
|
||||||
router.get('/inquiries', jwtAuthMiddleware, getAllInquiries);
|
router.get('/inquiries', jwtAuthMiddleware, getAllInquiries);
|
||||||
router.post('/', jwtAuthMiddleware, createPackage);
|
router.post('/', jwtAuthMiddleware, createPackage);
|
||||||
|
|||||||
@@ -120,22 +120,40 @@ export default function HealthPackageModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between border rounded-xl p-4 bg-muted/30">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div className="flex items-center justify-between border rounded-xl p-4 bg-muted/30">
|
||||||
<p className="font-semibold">Active Visibility</p>
|
<div>
|
||||||
|
<p className="font-semibold">Active</p>
|
||||||
|
<p className="text-sm text-muted-foreground">Show publicly</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground">Show this package publicly</p>
|
<Switch
|
||||||
|
checked={pkgForm.isActive}
|
||||||
|
onCheckedChange={(val) =>
|
||||||
|
setPkgForm({
|
||||||
|
...pkgForm,
|
||||||
|
isActive: val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch
|
<div className="flex items-center justify-between border rounded-xl p-4 bg-muted/30">
|
||||||
checked={pkgForm.isActive}
|
<div>
|
||||||
onCheckedChange={(val) =>
|
<p className="font-semibold">Featured</p>
|
||||||
setPkgForm({
|
<p className="text-sm text-muted-foreground">Show on homepage</p>
|
||||||
...pkgForm,
|
</div>
|
||||||
isActive: val,
|
|
||||||
})
|
<Switch
|
||||||
}
|
checked={pkgForm.isFeatured || false}
|
||||||
/>
|
onCheckedChange={(val) =>
|
||||||
|
setPkgForm({
|
||||||
|
...pkgForm,
|
||||||
|
isFeatured: val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export default function HealthPackagePage() {
|
|||||||
discountedPrice: undefined,
|
discountedPrice: undefined,
|
||||||
categoryId: 0,
|
categoryId: 0,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
isFeatured: false,
|
||||||
sortOrder: 1000,
|
sortOrder: 1000,
|
||||||
seo: {
|
seo: {
|
||||||
seoTitle: '',
|
seoTitle: '',
|
||||||
@@ -167,6 +168,7 @@ export default function HealthPackagePage() {
|
|||||||
discountedPrice: undefined,
|
discountedPrice: undefined,
|
||||||
categoryId: categories[0]?.id || 0,
|
categoryId: categories[0]?.id || 0,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
isFeatured: false,
|
||||||
sortOrder: 1000,
|
sortOrder: 1000,
|
||||||
seo: {
|
seo: {
|
||||||
seoTitle: '',
|
seoTitle: '',
|
||||||
@@ -366,6 +368,7 @@ export default function HealthPackagePage() {
|
|||||||
<TableHead className="w-[150px] bg-background text-sm font-bold">Category</TableHead>
|
<TableHead className="w-[150px] bg-background text-sm font-bold">Category</TableHead>
|
||||||
<TableHead className="w-[150px] bg-background text-sm font-bold">Pricing</TableHead>
|
<TableHead className="w-[150px] bg-background text-sm font-bold">Pricing</TableHead>
|
||||||
<TableHead className="w-[120px] bg-background text-sm font-bold">Status</TableHead>
|
<TableHead className="w-[120px] bg-background text-sm font-bold">Status</TableHead>
|
||||||
|
<TableHead className="w-[100px] bg-background text-sm font-bold">Featured</TableHead>
|
||||||
<TableHead className="w-[120px] bg-background text-right text-sm font-bold">Actions</TableHead>
|
<TableHead className="w-[120px] bg-background text-right text-sm font-bold">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -418,6 +421,17 @@ export default function HealthPackagePage() {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Switch
|
||||||
|
checked={pkg.isFeatured}
|
||||||
|
onCheckedChange={async () => {
|
||||||
|
await updateHealthPackageApi(pkg.id!, {
|
||||||
|
isFeatured: !pkg.isFeatured,
|
||||||
|
});
|
||||||
|
fetchData();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user