Files
gg-backend/frontend/src/components/HealthPackageModal/HealthPackageModal.tsx
T

437 lines
11 KiB
TypeScript
Raw Normal View History

2026-05-26 11:56:22 +05:30
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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full !max-w-7xl h-[92vh] flex flex-col p-0 overflow-hidden">
<DialogHeader className="px-6 py-5 border-b bg-background sticky top-0 z-20">
<DialogTitle className="text-2xl font-bold">
{editingPackage ? "Edit Health Package" : "Create Health Package"}
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto">
<div className="grid grid-cols-1 xl:grid-cols-[1.2fr_0.8fr] gap-8 p-6">
{/* LEFT COLUMN */}
<div className="space-y-8">
<div className="space-y-5">
<div className="sticky top-0 bg-background z-10 pb-2">
<h3 className="text-lg font-bold">Profile & Pricing</h3>
<p className="text-sm text-muted-foreground">
Main package information
</p>
</div>
<div className="space-y-5">
<div className="space-y-2">
<Label className="font-semibold">Package Image</Label>
<p className="text-xs text-muted-foreground">
Recommended size: 650 × 250
</p>
<BytescaleUploader
value={pkgForm.image || ""}
folderPath="/health-packages"
onChange={(url) =>
setPkgForm({
...pkgForm,
image: url,
})
}
/>
</div>
<div className="flex items-center justify-between border rounded-xl p-4 bg-muted/30">
<div>
<p className="font-semibold">Active Visibility</p>
<p className="text-sm text-muted-foreground">
Show this package publicly
</p>
</div>
<Switch
checked={pkgForm.isActive}
onCheckedChange={(val) =>
setPkgForm({
...pkgForm,
isActive: val,
})
}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label className="font-semibold">Package Name</Label>
<Input
value={pkgForm.name}
onChange={(e) =>
setPkgForm({
...pkgForm,
name: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label className="font-semibold">URL Slug</Label>
<Input
value={pkgForm.slug}
onChange={(e) =>
setPkgForm({
...pkgForm,
slug: e.target.value,
})
}
/>
</div>
</div>
<div className="space-y-2">
<Label className="font-semibold">Category</Label>
<Select
value={pkgForm.categoryId?.toString()}
onValueChange={(v) =>
setPkgForm({
...pkgForm,
categoryId: Number(v),
})
}
>
<SelectTrigger>
<SelectValue placeholder="Select category" />
</SelectTrigger>
<SelectContent>
{categories.map((c) => (
<SelectItem key={c.id} value={c.id.toString()}>
{c.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="space-y-2">
<Label className="font-semibold">Regular Price ()</Label>
<Input
type="number"
value={pkgForm.price || ""}
onChange={(e) => {
const value = e.target.value
? Number(e.target.value)
: undefined;
setPkgForm({
...pkgForm,
price: value,
});
}}
/>
</div>
<div className="space-y-2">
<Label className="font-semibold">
Discounted Price ()
</Label>
<Input
type="number"
disabled={!pkgForm.price}
value={pkgForm.discountedPrice || ""}
onChange={(e) =>
setPkgForm({
...pkgForm,
discountedPrice: e.target.value
? Number(e.target.value)
: undefined,
})
}
/>
</div>
<div className="space-y-2">
<Label className="font-semibold">Sort Priority</Label>
<Input
type="number"
value={pkgForm.sortOrder}
onChange={(e) =>
setPkgForm({
...pkgForm,
sortOrder: Number(e.target.value),
})
}
/>
</div>
</div>
<div className="space-y-2">
<Label className="font-semibold">Description</Label>
<Textarea
rows={5}
value={pkgForm.description}
onChange={(e) =>
setPkgForm({
...pkgForm,
description: e.target.value,
})
}
/>
</div>
</div>
</div>
{/* INCLUSIONS */}
<div className="space-y-5">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-bold">Tests & Inclusions</h3>
<p className="text-sm text-muted-foreground">
Group tests into categories
</p>
</div>
<Badge variant="outline">
{inclusionsList.length} Groups
</Badge>
</div>
<Accordion type="multiple" className="space-y-4">
{inclusionsList.map((inc, index) => {
const testCount = inc.items
?.split(",")
.filter(Boolean).length;
return (
<AccordionItem
key={inc.id}
value={inc.id.toString()}
className="border rounded-xl bg-background px-5 shadow-sm"
>
<AccordionTrigger className="hover:no-underline w-full">
<div className="flex w-full items-center justify-between">
<div className="flex flex-col items-start text-left">
<p className="font-semibold">
{inc.category || `Group ${index + 1}`}
</p>
<p className="text-xs text-muted-foreground">
{testCount || 0} tests included
</p>
</div>
<Button
variant="ghost"
size="sm"
className="text-red-500 hover:text-red-600"
onClick={() => handleRemoveInclusionField(inc.id)}
>
<Trash2 className="h-4 w-4 mr-1" />
Remove
</Button>
</div>
</AccordionTrigger>
<AccordionContent className="pt-4">
<div className="space-y-4">
<div className="space-y-2">
<Label>Category Title</Label>
<Input
placeholder="Routine Blood Tests"
value={inc.category}
onChange={(e) =>
handleUpdateInclusionField(
inc.id,
"category",
e.target.value,
)
}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>Included Tests</Label>
</div>
<Textarea
rows={4}
placeholder="CBC, LFT, RFT, TSH"
value={inc.items}
onChange={(e) =>
handleUpdateInclusionField(
inc.id,
"items",
e.target.value,
)
}
/>
<p className="text-xs text-muted-foreground">
Separate each test using commas
</p>
</div>
</div>
</AccordionContent>
</AccordionItem>
);
})}
</Accordion>
<Button
variant="outline"
className="w-full border-dashed border-2 h-12"
onClick={handleAddInclusionField}
>
<Plus className="h-4 w-4 mr-2" />
Add New Inclusion Group
</Button>
</div>
</div>
{/* RIGHT COLUMN */}
<div className="space-y-6">
<SeoFields
value={pkgForm.seo}
slug={pkgForm.slug}
folderPath="/seo"
onChange={(seo) =>
setPkgForm({
...pkgForm,
seo,
})
}
/>
</div>
</div>
</div>
<DialogFooter className="p-6 border-t bg-background sticky bottom-0 z-20">
<Button variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button className="px-10" onClick={onSave}>
{editingPackage ? "Save Changes" : "Create Package"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}