140 lines
4.3 KiB
TypeScript
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
|