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