diff --git a/backend/prisma/migrations/20260525122431_health_check_seo/migration.sql b/backend/prisma/migrations/20260525122431_health_check_seo/migration.sql
new file mode 100644
index 0000000..77e55f5
--- /dev/null
+++ b/backend/prisma/migrations/20260525122431_health_check_seo/migration.sql
@@ -0,0 +1,14 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[seoId]` on the table `HealthPackage` will be added. If there are existing duplicate values, this will fail.
+
+*/
+-- AlterTable
+ALTER TABLE "HealthPackage" ADD COLUMN "seoId" INTEGER;
+
+-- CreateIndex
+CREATE UNIQUE INDEX "HealthPackage_seoId_key" ON "HealthPackage"("seoId");
+
+-- AddForeignKey
+ALTER TABLE "HealthPackage" ADD CONSTRAINT "HealthPackage_seoId_fkey" FOREIGN KEY ("seoId") REFERENCES "Seo"("id") ON DELETE SET NULL ON UPDATE CASCADE;
diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma
index d8ca932..5b326b5 100644
--- a/backend/prisma/schema.prisma
+++ b/backend/prisma/schema.prisma
@@ -259,6 +259,9 @@ model HealthPackage {
inquiries HealthPackageInquiry[]
+ seoId Int? @unique
+ seo Seo? @relation(fields: [seoId], references: [id])
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -294,7 +297,9 @@ model DoctorSpecialization {
model Seo {
id Int @id @default(autoincrement())
- doctor Doctor?
+ doctor Doctor?
+ healthPackage HealthPackage?
+
seoTitle String?
metaDescription String? @db.Text
diff --git a/backend/src/controllers/doctor.controller.js b/backend/src/controllers/doctor.controller.js
index bcf2f71..88ade7f 100644
--- a/backend/src/controllers/doctor.controller.js
+++ b/backend/src/controllers/doctor.controller.js
@@ -363,11 +363,15 @@ export const updateDoctor = async (req, res) => {
professionalSummary,
seoTitle,
metaDescription,
+ ogTitle,
+ ogDescription,
focusKeyphrase,
slug,
tags,
+ ogImage,
specializations,
} = req.body;
+
if (!doctorId) {
return res.status(400).json({
success: false,
@@ -436,6 +440,9 @@ export const updateDoctor = async (req, res) => {
data: {
seoTitle,
metaDescription,
+ ogTitle,
+ ogDescription,
+ ogImage,
focusKeyphrase,
slug: slug ? slug : null,
tags: tags || [],
@@ -444,8 +451,11 @@ export const updateDoctor = async (req, res) => {
} else {
const seo = await prisma.seo.create({
data: {
- seoTitle,
+ ogImage,
metaDescription,
+ seoTitle,
+ ogDescription,
+ ogTitle,
focusKeyphrase,
slug: slug ? slug : null,
tags: tags || [],
diff --git a/backend/src/controllers/healthCheck.controller.js b/backend/src/controllers/healthCheck.controller.js
index 7ee86bb..ea8d16c 100644
--- a/backend/src/controllers/healthCheck.controller.js
+++ b/backend/src/controllers/healthCheck.controller.js
@@ -121,7 +121,10 @@ export const getAllPackages = async (req, res) => {
categorySlug ? { category: { slug: categorySlug } } : {},
],
},
- include: { category: true },
+ include: {
+ category: true,
+ seo: true,
+ },
orderBy: [{ sortOrder: "asc" }, { createdAt: "desc" }],
});
@@ -148,6 +151,7 @@ export const createPackage = async (req, res) => {
isActive,
isFeatured,
sortOrder,
+ seo,
} = req.body;
const healthPackage = await prisma.healthPackage.create({
@@ -163,6 +167,25 @@ export const createPackage = async (req, res) => {
isActive: isActive ?? true,
isFeatured: isFeatured ?? false,
sortOrder: sortOrder ? Number(sortOrder) : 1000,
+
+ ...(seo && {
+ seo: {
+ create: {
+ seoTitle: seo.seoTitle,
+ metaDescription: seo.metaDescription,
+ focusKeyphrase: seo.focusKeyphrase,
+ slug: slug,
+ tags: seo.tags || [],
+ ogTitle: seo.ogTitle,
+ ogDescription: seo.ogDescription,
+ ogImage: seo.ogImage,
+ },
+ },
+ }),
+ },
+ include: {
+ category: true,
+ seo: true,
},
});
@@ -183,13 +206,58 @@ export const updatePackage = async (req, res) => {
const data = { ...req.body };
delete data.id;
delete data.category;
+ delete data.createdAt;
+ delete data.updatedAt;
+ delete data.seoId;
if (data.categoryId) data.categoryId = Number(data.categoryId);
if (data.sortOrder) data.sortOrder = Number(data.sortOrder);
+ const existingPackage = await prisma.healthPackage.findUnique({
+ where: { id: Number(id) },
+ select: { slug: true },
+ });
+ const seoSlug = data.slug || existingPackage.slug;
+
const updated = await prisma.healthPackage.update({
where: { id: Number(id) },
- data,
+
+ data: {
+ ...data,
+
+ seo: data.seo
+ ? {
+ upsert: {
+ create: {
+ seoTitle: data.seo.seoTitle,
+ metaDescription: data.seo.metaDescription,
+ focusKeyphrase: data.seo.focusKeyphrase,
+ slug: seoSlug,
+ tags: data.seo.tags || [],
+ ogTitle: data.seo.ogTitle,
+ ogDescription: data.seo.ogDescription,
+ ogImage: data.seo.ogImage,
+ },
+
+ update: {
+ seoTitle: data.seo.seoTitle,
+ metaDescription: data.seo.metaDescription,
+ focusKeyphrase: data.seo.focusKeyphrase,
+ slug: seoSlug,
+ tags: data.seo.tags || [],
+ ogTitle: data.seo.ogTitle,
+ ogDescription: data.seo.ogDescription,
+ ogImage: data.seo.ogImage,
+ },
+ },
+ }
+ : undefined,
+ },
+
+ include: {
+ category: true,
+ seo: true,
+ },
});
return res
@@ -204,11 +272,21 @@ export const updatePackage = async (req, res) => {
export const deletePackage = async (req, res) => {
try {
const { id } = req.params;
- await prisma.healthPackage.delete({ where: { id: Number(id) } });
- return res.status(200).json({ success: true, message: "Package deleted" });
+
+ await prisma.healthPackage.delete({
+ where: { id: Number(id) },
+ });
+
+ return res.status(200).json({
+ success: true,
+ message: "Package deleted",
+ });
} catch (error) {
console.error(error);
- return res.status(500).json({ success: false, message: "Delete failed" });
+ return res.status(500).json({
+ success: false,
+ message: "Delete failed",
+ });
}
};
@@ -363,7 +441,11 @@ export const getPackageBySlug = async (req, res) => {
const { slug } = req.params;
const healthPackage = await prisma.healthPackage.findFirst({
where: { slug, isActive: true },
- include: { category: true },
+
+ include: {
+ category: true,
+ seo: true,
+ },
});
if (!healthPackage) {
@@ -372,7 +454,10 @@ export const getPackageBySlug = async (req, res) => {
.json({ success: false, message: "Package not found" });
}
- return res.status(200).json({ success: true, data: healthPackage });
+ return res.status(200).json({
+ success: true,
+ data: healthPackage,
+ });
} catch (error) {
console.error(error);
return res
@@ -414,7 +499,9 @@ export const getAllInquiries = async (req, res) => {
take: queryLimit,
include: {
healthPackage: {
- include: { category: true },
+ include: {
+ category: true,
+ },
},
},
orderBy: { createdAt: "desc" },
diff --git a/frontend/src/api/healthCheck.ts b/frontend/src/api/healthCheck.ts
index d6eb04a..8d7de22 100644
--- a/frontend/src/api/healthCheck.ts
+++ b/frontend/src/api/healthCheck.ts
@@ -1,6 +1,15 @@
import apiClient from "@/api/client";
import toast from "react-hot-toast";
+export interface SeoData {
+ seoTitle?: string;
+ metaDescription?: string;
+ focusKeyphrase?: string;
+ tags?: string[];
+ ogTitle?: string;
+ ogDescription?: string;
+ ogImage?: string;
+}
export interface HealthPackage {
id?: number;
name: string;
@@ -14,6 +23,7 @@ export interface HealthPackage {
isActive: boolean;
isFeatured: boolean;
sortOrder: number;
+ seo?: SeoData | null;
category?: {
name: string;
};
diff --git a/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx b/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx
index 04b3f46..e5430e7 100644
--- a/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx
+++ b/frontend/src/components/BytescaleUploader/BytescaleUploader.tsx
@@ -7,11 +7,12 @@ interface BytescaleUploaderProps {
value: string;
onChange: (url: string) => void;
folderPath:
+ | "/health-packages"
+ | "/seo"
| "/doctors"
| "/departments"
| "/news"
| "/blog"
- | "/health-packages"
| "/doctor-og";
}
diff --git a/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx b/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx
new file mode 100644
index 0000000..4cb70cb
--- /dev/null
+++ b/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx
@@ -0,0 +1,436 @@
+import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
+import SeoFields from "@/components/SeoFields/SeoFields";
+import { useEffect } from "react";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Button } from "@/components/ui/button";
+import { Label } from "@/components/ui/label";
+import { Badge } from "@/components/ui/badge";
+import { Switch } from "@/components/ui/switch";
+
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/components/ui/accordion";
+
+import { Plus, Trash2 } from "lucide-react";
+
+interface Props {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ editingPackage: any;
+ pkgForm: any;
+ setPkgForm: any;
+ inclusionsList: any[];
+ setInclusionsList: any;
+ categories: any[];
+ onSave: () => void;
+}
+
+export default function HealthPackageModal({
+ open,
+ onOpenChange,
+ editingPackage,
+ pkgForm,
+ setPkgForm,
+ inclusionsList,
+ setInclusionsList,
+ categories,
+ onSave,
+}: Props) {
+ useEffect(() => {
+ if (!editingPackage && pkgForm.name) {
+ setPkgForm((prev: any) => ({
+ ...prev,
+ slug: prev.slug
+ ? prev.slug
+ : pkgForm.name
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/(^-|-$)/g, ""),
+ }));
+ }
+ }, [pkgForm.name]);
+
+ const handleAddInclusionField = () => {
+ setInclusionsList([
+ ...inclusionsList,
+ {
+ id: Date.now(),
+ category: "",
+ items: "",
+ },
+ ]);
+ };
+
+ const handleRemoveInclusionField = (id: number) => {
+ setInclusionsList(inclusionsList.filter((item) => item.id !== id));
+ };
+
+ const handleUpdateInclusionField = (
+ id: number,
+ field: string,
+ value: string,
+ ) => {
+ setInclusionsList(
+ inclusionsList.map((item) =>
+ item.id === id
+ ? {
+ ...item,
+ [field]: value,
+ }
+ : item,
+ ),
+ );
+ };
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/SeoFields/SeoFields.tsx b/frontend/src/components/SeoFields/SeoFields.tsx
new file mode 100644
index 0000000..c77d74a
--- /dev/null
+++ b/frontend/src/components/SeoFields/SeoFields.tsx
@@ -0,0 +1,211 @@
+import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
+
+import { Badge } from "@/components/ui/badge";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+
+import { X } from "lucide-react";
+
+interface SeoData {
+ seoTitle?: string;
+ metaDescription?: string;
+ focusKeyphrase?: string;
+ tags?: string[];
+ ogTitle?: string;
+ ogDescription?: string;
+ ogImage?: string;
+}
+
+interface SeoFieldsProps {
+ value?: SeoData;
+ onChange: (seo: SeoData) => void;
+ slug?: string;
+ folderPath?: "/seo";
+}
+
+export default function SeoFields({
+ value,
+ onChange,
+ slug,
+ folderPath = "/seo",
+}: SeoFieldsProps) {
+ const seo = value || {};
+
+ const updateSeo = (field: keyof SeoData, fieldValue: any) => {
+ onChange({
+ ...seo,
+ [field]: fieldValue,
+ });
+ };
+
+ const removeTag = (index: number) => {
+ updateSeo(
+ "tags",
+ (seo.tags || []).filter((_, i) => i !== index),
+ );
+ };
+
+ return (
+
+
+
+
SEO Settings
+
+
+ Optimize for Google & social sharing
+
+
+
+
Optional
+
+
+
+
+
+
+
+ {seo.seoTitle?.length || 0}/60
+
+
+
+
updateSeo("seoTitle", e.target.value)}
+ />
+
+
+ Recommended: 50–60 characters
+
+
+
+
+
+
+
+
+ {seo.metaDescription?.length || 0}/160
+
+
+
+
+
+
+
+
+ updateSeo("focusKeyphrase", e.target.value)}
+ />
+
+
+
+
+
+
+ {(seo.tags || []).map((tag, index) => (
+
+ {tag}
+
+
+
+ ))}
+
+
{
+ if (e.key === "Enter" && e.currentTarget.value.trim()) {
+ e.preventDefault();
+
+ const newTag = e.currentTarget.value.trim();
+
+ if (!(seo.tags || []).includes(newTag)) {
+ updateSeo("tags", [...(seo.tags || []), newTag]);
+ }
+
+ e.currentTarget.value = "";
+ }
+ }}
+ />
+
+
+
Press Enter to add tags
+
+
+
+
+
+
Open Graph (Social Preview)
+
+
+ Facebook, WhatsApp & Twitter sharing
+
+
+
+
Optional
+
+
+
+
+
+
updateSeo("ogTitle", e.target.value)}
+ />
+
+
+ If empty, SEO title will be used
+
+
+
+
+
+
+
+
+ updateSeo("ogImage", url)}
+ />
+
+
+
+ );
+}
diff --git a/frontend/src/components/SeoPreview/SeoPreview.tsx b/frontend/src/components/SeoPreview/SeoPreview.tsx
new file mode 100644
index 0000000..9da8838
--- /dev/null
+++ b/frontend/src/components/SeoPreview/SeoPreview.tsx
@@ -0,0 +1,158 @@
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+
+interface SeoPreviewData {
+ seo?: {
+ ogImage?: string;
+ ogTitle?: string;
+ seoTitle?: string;
+ ogDescription?: string;
+ metaDescription?: string;
+ slug?: string;
+ };
+ doctorId?: string;
+ name?: string;
+}
+
+interface SeoPreviewProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ previewData?: SeoPreviewData | null;
+ url?: string;
+ title?: string;
+}
+
+export default function SeoPreview({
+ open,
+ onOpenChange,
+ previewData,
+ url,
+ title = "SEO Preview",
+}: SeoPreviewProps) {
+ const previewUrl = url || "https://www.gg-hospital.com";
+
+ const hasSeoData =
+ !!previewData?.seo &&
+ !!(
+ previewData.seo.ogImage ||
+ previewData.seo.ogTitle ||
+ previewData.seo.seoTitle ||
+ previewData.seo.ogDescription ||
+ previewData.seo.metaDescription
+ );
+
+ const imageUrl =
+ previewData?.seo?.ogImage ||
+ "https://placehold.co/1200x630?text=GG+Hospital";
+
+ const ogTitle =
+ previewData?.seo?.ogTitle || previewData?.seo?.seoTitle || "GG Hospital";
+
+ const ogDescription =
+ previewData?.seo?.ogDescription ||
+ previewData?.seo?.metaDescription ||
+ "No description available";
+
+ const searchTitle =
+ previewData?.seo?.seoTitle ||
+ previewData?.seo?.ogTitle ||
+ "SEO title preview";
+
+ const searchDescription =
+ previewData?.seo?.metaDescription ||
+ previewData?.seo?.ogDescription ||
+ "No meta description available";
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..fcfee5c
--- /dev/null
+++ b/frontend/src/components/ui/accordion.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+import { Accordion as AccordionPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+
+function Accordion({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/frontend/src/pages/Doctor.tsx b/frontend/src/pages/Doctor.tsx
index b84ee08..c127f6f 100644
--- a/frontend/src/pages/Doctor.tsx
+++ b/frontend/src/pages/Doctor.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from "react";
import { AxiosError } from "axios";
-
+import { Eye } from "lucide-react";
import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
import {
@@ -28,6 +28,7 @@ import {
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
+import SeoPreview from "@/components/SeoPreview/SeoPreview";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
@@ -60,6 +61,8 @@ const DAYS = [
];
export default function DoctorPage() {
+ const WEBSITE_URL = import.meta.env.VITE_WEBSITE_URL;
+
const [doctors, setDoctors] = useState([]);
const [departments, setDepartments] = useState([]);
const [loading, setLoading] = useState(true);
@@ -100,6 +103,8 @@ export default function DoctorPage() {
slug: "",
tags: [],
});
+ const [openOgPreview, setOpenOgPreview] = useState(false);
+ const [previewDoctor, setPreviewDoctor] = useState(null);
const fetchAll = useCallback(async () => {
setLoading(true);
@@ -316,7 +321,10 @@ export default function DoctorPage() {
}
}
- console.log("Current form state:", form); // Debug log to check form state
+ function handlePreview(doc: any) {
+ setPreviewDoctor(doc);
+ setOpenOgPreview(true);
+ }
async function handleSubmit() {
try {
@@ -332,6 +340,24 @@ export default function DoctorPage() {
}
}
+ const createSlug = (text: string) => {
+ if (!text) return "";
+
+ return text
+ .toString()
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, "-")
+ .replace(/[^\w-]+/g, "")
+ .replace(/--+/g, "-");
+ };
+
+ const getDoctorUrl = (doctor: any) => {
+ const slug = doctor?.seo?.slug || createSlug(doctor?.name);
+
+ return `${WEBSITE_URL}/${doctor?.doctorId}/${slug}`;
+ };
+
return (
@@ -492,6 +518,14 @@ export default function DoctorPage() {
+
);
}
diff --git a/frontend/src/pages/HealthPackagePage.tsx b/frontend/src/pages/HealthPackagePage.tsx
index 27e6232..6b8fa17 100644
--- a/frontend/src/pages/HealthPackagePage.tsx
+++ b/frontend/src/pages/HealthPackagePage.tsx
@@ -1,21 +1,21 @@
import { useState, useEffect, useCallback, useMemo } from "react";
import toast from "react-hot-toast";
import { AxiosError } from "axios";
-import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
import {
getHealthPackagesApi,
getHealthCategoriesApi,
- createHealthPackageApi,
updateHealthPackageApi,
+ createHealthPackageApi,
createCategoryApi,
updateCategoryApi,
- deleteCategoryApi,
HealthPackage,
HealthCategory,
} from "@/api/healthCheck";
import PackageInquiriesTab from "@/components/PackageInquiriesTab/PackageInquiriesTab";
+import HealthPackageModal from "@/components/HealthPackageModal/HealthPackageModal";
+import SeoPreview from "@/components/SeoPreview/SeoPreview";
import {
Table,
@@ -35,18 +35,10 @@ import {
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
import { Badge } from "@/components/ui/badge";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
import {
Loader2,
@@ -57,10 +49,10 @@ import {
ChevronRight,
LayoutGrid,
Eye,
- Trash2,
} from "lucide-react";
export default function HealthPackagePage() {
+ const WEBSITE_URL = import.meta.env.VITE_WEBSITE_URL;
const [packages, setPackages] = useState([]);
const [categories, setCategories] = useState([]);
const [loading, setLoading] = useState(true);
@@ -99,6 +91,15 @@ export default function HealthPackagePage() {
categoryId: 0,
isActive: true,
sortOrder: 1000,
+ seo: {
+ seoTitle: "",
+ metaDescription: "",
+ focusKeyphrase: "",
+ tags: [],
+ ogTitle: "",
+ ogDescription: "",
+ ogImage: "",
+ },
});
const [inclusionsList, setInclusionsList] = useState([
{ id: Date.now(), category: "", items: "" },
@@ -209,6 +210,15 @@ export default function HealthPackagePage() {
categoryId: categories[0]?.id || 0,
isActive: true,
sortOrder: 1000,
+ seo: {
+ seoTitle: "",
+ metaDescription: "",
+ focusKeyphrase: "",
+ tags: [],
+ ogTitle: "",
+ ogDescription: "",
+ ogImage: "",
+ },
});
setInclusionsList([{ id: Date.now(), category: "", items: "" }]);
setPackageModal(true);
@@ -241,28 +251,6 @@ export default function HealthPackagePage() {
setPackageModal(true);
};
- const handleAddInclusionField = () => {
- setInclusionsList([
- ...inclusionsList,
- { id: Date.now(), category: "", items: "" },
- ]);
- };
-
- const handleRemoveInclusionField = (id: number) => {
- setInclusionsList(inclusionsList.filter((item) => item.id !== id));
- };
-
- const handleUpdateInclusionField = (
- id: number,
- field: string,
- value: string,
- ) => {
- setInclusionsList(
- inclusionsList.map((item) =>
- item.id === id ? { ...item, [field]: value } : item,
- ),
- );
- };
const savePackage = async () => {
if (!pkgForm.image) return toast.error("Package image is required.");
@@ -283,7 +271,6 @@ export default function HealthPackagePage() {
}
try {
- // Convert the dynamic array back into the required JSON object format
const parsedInclusions: Record = {};
inclusionsList.forEach((entry) => {
const catName = entry.category.trim();
@@ -310,6 +297,7 @@ export default function HealthPackagePage() {
finalData.discountedPrice !== null
? Number(finalData.discountedPrice)
: null;
+
if (editingPackage?.id) {
const changedFields: Record = {};
Object.keys(finalData).forEach((key) => {
@@ -382,18 +370,7 @@ export default function HealthPackagePage() {
}
};
- const deleteCategory = async (id: number) => {
- if (confirm("Delete this category? Ensure no packages are linked to it.")) {
- try {
- await deleteCategoryApi(id);
- toast.success("Category deleted successfully!");
- fetchData();
- } catch (err) {
- console.error(err);
- toast.error("Failed to delete category.");
- }
- }
- };
+ const previewUrl = `${WEBSITE_URL}/preventivecheckupdirectory/${selectedPackage?.slug}`;
return (
@@ -730,274 +707,18 @@ export default function HealthPackagePage() {
- {/* --- PACKAGE MODAL --- */}
-
+ {/* --- REPLACED MODAL CONTAINER --- */}
+
{/* --- CATEGORY MODAL --- */}
- {/* --- VIEW MODAL --- */}
-
+
);
}