freshyo/apps/admin-web/src/components/ProductGroupForm.tsx
2026-09-06 23:05:20 +05:30

140 lines
4.3 KiB
TypeScript

import { type FC } from 'react'
import { useFormik } from 'formik'
import { p as P, pInput as PInput } from 'web-components'
import ProductsSelector from './ProductsSelector'
import { trpc } from '@/lib/trpc-client'
import { X } from 'lucide-react'
import type { AdminProductGroup as SharedAdminProductGroup } from '@packages/shared'
// Serialized product group (string date, loosely typed products) anchored to shared
export type ProductGroup = Omit<SharedAdminProductGroup, 'createdAt' | 'products'> & {
createdAt: string
products: any[]
}
interface ProductGroupFormProps {
group?: ProductGroup | null
onClose: () => void
onSuccess: () => void
}
const ProductGroupForm: FC<ProductGroupFormProps> = ({
group,
onClose,
onSuccess,
}) => {
// Fetch products
const { data: productsData } = trpc.common.product.getAllProductsSummary.useQuery()
const createGroup = trpc.admin.product.createGroup.useMutation()
const updateGroup = trpc.admin.product.updateGroup.useMutation()
const isEditing = !!group
const products = productsData?.products || []
const formik = useFormik({
initialValues: {
group_name: group?.groupName || '',
description: group?.description || '',
product_ids: group?.products?.map(p => p.id) || [],
},
validate: (values) => {
const errors: {[key: string]: string} = {}
if (!values.group_name.trim()) {
errors.group_name = 'Group name is required'
}
return errors
},
onSubmit: async (values) => {
try {
if (isEditing) {
await updateGroup.mutateAsync({
id: group.id,
group_name: values.group_name,
description: values.description,
product_ids: values.product_ids,
})
} else {
await createGroup.mutateAsync({
group_name: values.group_name,
description: values.description,
product_ids: values.product_ids,
})
}
onSuccess()
} catch (error: any) {
window.alert(`Error: ${error.message || 'Failed to save group'}`)
}
},
})
return (
<div className="flex-1 bg-white">
{/* Header */}
<div className="flex flex-row items-center justify-between p-6 border-b border-gray-200">
<P className="text-xl font-bold text-gray-900">
{isEditing ? 'Edit Product Group' : 'Create Product Group'}
</P>
<button type="button" onClick={onClose} className="p-2" aria-label="Close">
<X className="h-6 w-6 text-gray-500" />
</button>
</div>
<div className="flex-1 overflow-auto px-6 py-4">
{/* Group Name */}
<PInput
topLabel="Group Name *"
placeholder="Enter group name"
value={formik.values.group_name}
onChange={(e) => formik.setFieldValue('group_name', e.target.value)}
style={{ marginBottom: 16 }}
/>
{formik.errors.group_name && (
<P className="text-red-500 text-sm mt-1 mb-4">{formik.errors.group_name}</P>
)}
{/* Description */}
<PInput
topLabel="Description"
placeholder="Enter description (optional)"
multiline
numberOfLines={3}
value={formik.values.description}
onChange={(e) => formik.setFieldValue('description', e.target.value)}
style={{ marginBottom: 16 }}
/>
{/* Products Selection */}
<ProductsSelector
value={formik.values.product_ids}
onChange={(value) => formik.setFieldValue('product_ids', value as number[])}
multiple={true}
label="Products"
placeholder="Select products"
labelFormat={(product) => product.label}
/>
{/* Actions */}
<div className="flex flex-row gap-4">
<div
onClick={onClose}
className="flex-1 bg-gray-200 rounded-lg py-4 items-center text-center text-gray-700 font-medium cursor-pointer"
>
Cancel
</div>
<div
onClick={() => formik.handleSubmit()}
className="flex-1 bg-brand-600 rounded-lg py-4 items-center text-center text-white font-medium cursor-pointer"
>
{createGroup.isPending || updateGroup.isPending ? 'Saving...' : 'Save'}
</div>
</div>
</div>
</div>
)
}
export default ProductGroupForm