feat: health checkup CRUD apis #30

Merged
kailasdevdas merged 5 commits from feat/healthcheckup-crud into dev 2026-05-18 06:26:27 +00:00
6 changed files with 73 additions and 28 deletions
Showing only changes of commit 098fe12fd7 - Show all commits
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "HealthPackage" ADD COLUMN "image" TEXT;
+1
View File
@@ -241,6 +241,7 @@ model HealthPackage {
slug String @unique
description String?
price Decimal? @db.Decimal(10, 2)
image String?
discountedPrice Decimal? @db.Decimal(10, 2)
inclusions Json @default("{}")
@@ -141,6 +141,7 @@ export const createPackage = async (req, res) => {
slug,
description,
price,
image,
discountedPrice,
inclusions,
categoryId,
@@ -155,6 +156,7 @@ export const createPackage = async (req, res) => {
slug,
description,
price,
image,
discountedPrice,
inclusions,
categoryId: Number(categoryId),
+1
View File
@@ -7,6 +7,7 @@ export interface HealthPackage {
slug: string;
description?: string;
price: number;
image?: string;
discountedPrice?: number;
inclusions: Record<string, string[]>;
categoryId: number;
@@ -1,12 +1,17 @@
import {useState, useRef} from "react";
import {Button} from "@/components/ui/button";
import {User, X, Loader2} from "lucide-react";
import { useState, useRef } from "react";
import { Button } from "@/components/ui/button";
import { User, X, Loader2 } from "lucide-react";
import axios from "axios";
interface BytescaleUploaderProps {
value: string;
onChange: (url: string) => void;
folderPath: "/doctors" | "/departments" | "/news" | "/blog";
folderPath:
| "/doctors"
| "/departments"
| "/news"
| "/blog"
| "/health-packages";
}
export function BytescaleUploader({
@@ -40,7 +45,7 @@ export function BytescaleUploader({
},
});
const {fileUrl} = response.data;
const { fileUrl } = response.data;
onChange(fileUrl);
} catch (e: any) {
console.error("Upload Error:", e);
+57 -23
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback, useMemo } from "react";
import { AxiosError } from "axios";
import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
import {
getHealthPackagesApi,
@@ -91,6 +92,7 @@ export default function HealthPackagePage() {
name: "",
slug: "",
description: "",
image: "",
price: 0,
discountedPrice: 0,
categoryId: 0,
@@ -190,6 +192,7 @@ export default function HealthPackagePage() {
name: "",
slug: "",
description: "",
image: "",
price: 0,
discountedPrice: 0,
categoryId: categories[0]?.id || 0,
@@ -355,7 +358,8 @@ export default function HealthPackagePage() {
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="flex h-10 w-[220px] rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
className="flex h-10 w-[220px] rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
<option value="">All Categories</option>
{categories.map((cat) => (
<option key={cat.id} value={cat.id}>
@@ -368,7 +372,8 @@ export default function HealthPackagePage() {
variant="outline"
onClick={fetchData}
disabled={loading}
className="text-base">
className="text-base"
>
<RefreshCw className="mr-2 h-5 w-5" />
Refresh
</Button>
@@ -435,7 +440,8 @@ export default function HealthPackagePage() {
<TableRow>
<TableCell
colSpan={6}
className="text-center text-muted-foreground py-10 text-base">
className="text-center text-muted-foreground py-10 text-base"
>
No packages found
</TableCell>
</TableRow>
@@ -448,7 +454,8 @@ export default function HealthPackagePage() {
<TableCell>
<div
className="font-semibold text-base truncate"
title={pkg.name}>
title={pkg.name}
>
{pkg.name}
</div>
<div className="text-xs text-muted-foreground truncate font-mono mt-0.5">
@@ -478,9 +485,8 @@ export default function HealthPackagePage() {
onCheckedChange={() => handleToggleStatus(pkg)}
/>
<Badge
variant={
pkg.isActive ? "default" : "secondary"
}>
variant={pkg.isActive ? "default" : "secondary"}
>
{pkg.isActive ? "Active" : "Hidden"}
</Badge>
</div>
@@ -494,14 +500,16 @@ export default function HealthPackagePage() {
onClick={() => {
setSelectedPackage(pkg);
setViewModal(true);
}}>
}}
>
<Eye className="h-4 w-4" />
</Button>
<Button
size="icon"
variant="ghost"
className="h-9 w-9"
onClick={() => openEditPackage(pkg)}>
onClick={() => openEditPackage(pkg)}
>
<Pencil className="h-4 w-4" />
</Button>
</div>
@@ -542,7 +550,8 @@ export default function HealthPackagePage() {
onClick={() =>
setCurrentPage((prev) => Math.max(prev - 1, 1))
}
disabled={currentPage === 1}>
disabled={currentPage === 1}
>
<ChevronLeft className="h-5 w-5" />
</Button>
<Button
@@ -556,7 +565,8 @@ export default function HealthPackagePage() {
}
disabled={
currentPage === totalPages || totalPages === 0
}>
}
>
<ChevronRight className="h-5 w-5" />
</Button>
</div>
@@ -583,7 +593,8 @@ export default function HealthPackagePage() {
isActive: true,
});
setCategoryModal(true);
}}>
}}
>
<LayoutGrid className="mr-2 h-4 w-4" /> Add Category
</Button>
</CardHeader>
@@ -625,7 +636,8 @@ export default function HealthPackagePage() {
}
/>
<Badge
variant={cat.isActive ? "default" : "secondary"}>
variant={cat.isActive ? "default" : "secondary"}
>
{cat.isActive ? "Active" : "Hidden"}
</Badge>
</div>
@@ -640,7 +652,8 @@ export default function HealthPackagePage() {
setEditingCategory(cat);
setCatForm(cat as any);
setCategoryModal(true);
}}>
}}
>
<Pencil className="h-4 w-4" />
</Button>
</div>
@@ -674,6 +687,22 @@ export default function HealthPackagePage() {
Profile & Pricing
</h3>
<div className="space-y-4">
<div className="space-y-2">
<Label className="text-sm font-semibold">
Package Image
</Label>
<BytescaleUploader
value={pkgForm.image || ""}
folderPath="/health-packages"
onChange={(url) =>
setPkgForm({
...pkgForm,
image: url,
})
}
/>
</div>
<div className="flex items-center justify-between p-3 border rounded-md bg-muted/30">
<Label className="text-base font-semibold cursor-pointer">
Active Visibility
@@ -733,7 +762,8 @@ export default function HealthPackagePage() {
value={pkgForm.categoryId?.toString()}
onValueChange={(v) =>
setPkgForm({ ...pkgForm, categoryId: Number(v) })
}>
}
>
<SelectTrigger className="text-base">
<SelectValue />
</SelectTrigger>
@@ -814,15 +844,15 @@ export default function HealthPackagePage() {
{inclusionsList.map((inc) => (
<div
key={inc.id}
className="p-4 border rounded-md bg-muted/10 relative">
className="p-4 border rounded-md bg-muted/10 relative"
>
{/* Remove Button */}
<Button
variant="ghost"
size="icon"
className="absolute top-2 right-2 h-8 w-8 text-red-500 hover:text-red-700 hover:bg-red-50"
onClick={() =>
handleRemoveInclusionField(inc.id)
}>
onClick={() => handleRemoveInclusionField(inc.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
@@ -873,7 +903,8 @@ export default function HealthPackagePage() {
<Button
variant="outline"
className="w-full mt-2 border-dashed border-2"
onClick={handleAddInclusionField}>
onClick={handleAddInclusionField}
>
<Plus className="mr-2 h-4 w-4" />
Add New Category Group
</Button>
@@ -888,7 +919,8 @@ export default function HealthPackagePage() {
<Button
variant="ghost"
onClick={() => setPackageModal(false)}
className="text-base">
className="text-base"
>
Cancel
</Button>
<Button onClick={savePackage} className="px-10 text-base">
@@ -1008,7 +1040,8 @@ export default function HealthPackagePage() {
tests.map((item, i) => (
<div
key={i}
className="text-sm border p-3 rounded bg-background shadow-sm">
className="text-sm border p-3 rounded bg-background shadow-sm"
>
{item}
</div>
))}
@@ -1022,7 +1055,8 @@ export default function HealthPackagePage() {
selectedPackage.inclusions.map((item, i) => (
<div
key={i}
className="text-sm border p-3 rounded bg-background shadow-sm">
className="text-sm border p-3 rounded bg-background shadow-sm"
>
{item}
</div>
))}