feat: health checkup CRUD apis #30
+2
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "HealthPackage" ADD COLUMN "image" TEXT;
|
||||
@@ -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),
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface HealthPackage {
|
||||
slug: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
image?: string;
|
||||
discountedPrice?: number;
|
||||
inclusions: Record<string, string[]>;
|
||||
categoryId: number;
|
||||
|
||||
@@ -6,7 +6,12 @@ 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({
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user