225 lines
7.8 KiB
TypeScript
225 lines
7.8 KiB
TypeScript
import { useEffect, type FC } from 'react'
|
|
import { useFormik } from 'formik'
|
|
import { p as P, Dropdown, pInput as PInput, Checkbox } from 'web-components'
|
|
import ProductsSelector from './ProductsSelector'
|
|
import DateInput from './DateInput'
|
|
import { trpc } from '@/lib/trpc-client'
|
|
import type { VendorSnippetForm as VendorSnippetFormType } from '@/types/vendor-snippets'
|
|
|
|
interface VendorSnippetFormProps {
|
|
snippet?: VendorSnippetFormType | null
|
|
onClose: () => void
|
|
onSuccess: () => void
|
|
showIsPermanentCheckbox?: boolean
|
|
}
|
|
|
|
const VendorSnippetForm: FC<VendorSnippetFormProps> = ({
|
|
snippet,
|
|
onClose,
|
|
onSuccess,
|
|
showIsPermanentCheckbox = false,
|
|
}) => {
|
|
|
|
// Fetch slots
|
|
const { data: slotsData } = trpc.user.slots.getSlots.useQuery()
|
|
|
|
const createSnippet = trpc.admin.vendorSnippets.create.useMutation()
|
|
const updateSnippet = trpc.admin.vendorSnippets.update.useMutation()
|
|
|
|
const isEditing = !!snippet
|
|
|
|
const formik = useFormik({
|
|
initialValues: {
|
|
snippetCode: snippet?.snippetCode || '',
|
|
slotId: snippet?.slotId?.toString() || '',
|
|
isPermanent: snippet?.isPermanent || false,
|
|
skuIds: snippet?.skuIds?.map(id => id.toString()) || [],
|
|
validTill: snippet?.validTill ? new Date(snippet.validTill) : null,
|
|
},
|
|
validate: (values) => {
|
|
const errors: {[key: string]: string} = {}
|
|
|
|
if (!values.snippetCode.trim()) {
|
|
errors.snippetCode = 'Snippet code is required'
|
|
}
|
|
|
|
// Validate snippet code only contains alphanumeric characters and underscore
|
|
const snippetCodeRegex = /^[a-zA-Z0-9_]+$/
|
|
if (values.snippetCode && !snippetCodeRegex.test(values.snippetCode)) {
|
|
errors.snippetCode = 'Snippet code can only contain letters, numbers, and underscores'
|
|
}
|
|
|
|
// Only require slotId if isPermanent is false
|
|
if (!values.isPermanent && !values.slotId) {
|
|
errors.slotId = 'Slot selection is required'
|
|
}
|
|
|
|
if (values.skuIds.length === 0) {
|
|
errors.skuIds = 'At least one product must be selected'
|
|
}
|
|
|
|
return errors
|
|
},
|
|
onSubmit: async (values) => {
|
|
try {
|
|
|
|
const submitData = {
|
|
snippetCode: values.snippetCode,
|
|
slotId: values.isPermanent ? undefined : parseInt(values.slotId || '0'),
|
|
isPermanent: values.isPermanent,
|
|
skuIds: values.skuIds.map(id => parseInt(id)),
|
|
validTill: values.validTill ? values.validTill.toISOString() : undefined,
|
|
}
|
|
|
|
if (isEditing && snippet) {
|
|
await updateSnippet.mutateAsync({
|
|
id: snippet.id,
|
|
updates: submitData,
|
|
})
|
|
window.alert('Success: Vendor snippet updated successfully')
|
|
} else {
|
|
await createSnippet.mutateAsync(submitData)
|
|
window.alert('Success: Vendor snippet created successfully')
|
|
}
|
|
|
|
onSuccess()
|
|
onClose()
|
|
} catch (error: any) {
|
|
window.alert(`Error: ${error.message || 'Failed to save vendor snippet'}`)
|
|
}
|
|
},
|
|
})
|
|
|
|
// Generate unique snippet code if creating new (only on mount)
|
|
useEffect(() => {
|
|
if (!isEditing && !formik.values.snippetCode) {
|
|
const timestamp = Date.now()
|
|
const random = Math.random().toString(36).substring(2, 8)
|
|
formik.setFieldValue('snippetCode', `VS_${timestamp}_${random}`)
|
|
}
|
|
}, [isEditing]) // Removed formik.values.snippetCode from deps
|
|
|
|
const slotOptions = slotsData?.slots.map(slot => ({
|
|
label: new Date(slot.deliveryTime).toLocaleString(),
|
|
value: slot.id.toString(),
|
|
})) || []
|
|
|
|
return (
|
|
<div className="flex-1 bg-white">
|
|
<div className="px-6 py-4 border-b border-gray-200">
|
|
<div className="flex flex-row justify-between items-center">
|
|
<P className="text-xl font-bold text-gray-800">
|
|
{isEditing ? 'Edit Vendor Snippet' : 'Create Vendor Snippet'}
|
|
</P>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="p-2 text-gray-500 text-lg"
|
|
aria-label="Close"
|
|
>
|
|
✕
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-auto px-6">
|
|
<div className="py-6 space-y-6">
|
|
{/* Snippet Code */}
|
|
<div>
|
|
<PInput
|
|
topLabel="Snippet Code"
|
|
value={formik.values.snippetCode}
|
|
onChange={formik.handleChange('snippetCode')}
|
|
placeholder="Enter snippet code"
|
|
error={!!formik.errors.snippetCode && !!formik.touched.snippetCode}
|
|
/>
|
|
{formik.errors.snippetCode && formik.touched.snippetCode && (
|
|
<P className="text-red-500 text-sm mt-1">{formik.errors.snippetCode}</P>
|
|
)}
|
|
</div>
|
|
|
|
{/* Is Permanent Checkbox */}
|
|
<div>
|
|
<button
|
|
type="button"
|
|
onClick={() => formik.setFieldValue('isPermanent', !formik.values.isPermanent)}
|
|
className="flex flex-row items-center mb-2"
|
|
>
|
|
<Checkbox
|
|
checked={formik.values.isPermanent}
|
|
onPress={() => formik.setFieldValue('isPermanent', !formik.values.isPermanent)}
|
|
/>
|
|
<P className="text-gray-700 font-medium ml-3">Is Permanent?</P>
|
|
</button>
|
|
<P className="text-sm text-gray-500 block">
|
|
Check if this snippet is permanent and not tied to a specific delivery slot
|
|
</P>
|
|
</div>
|
|
|
|
{/* Slot Selection - Only show if not permanent */}
|
|
{!formik.values.isPermanent && (
|
|
<div>
|
|
<P className="text-gray-700 font-medium mb-2 block">Delivery Slot</P>
|
|
<Dropdown
|
|
label="Select Slot"
|
|
value={formik.values.slotId}
|
|
options={slotOptions}
|
|
onValueChange={(value) => formik.setFieldValue('slotId', String(value))}
|
|
className="w-full"
|
|
/>
|
|
{formik.errors.slotId && formik.touched.slotId && (
|
|
<P className="text-red-500 text-sm mt-1">{formik.errors.slotId}</P>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Product Selection */}
|
|
<div>
|
|
<ProductsSelector
|
|
value={formik.values.skuIds.map(id => parseInt(id))}
|
|
onChange={(selectedProductIds) => formik.setFieldValue('skuIds', (selectedProductIds as number[]).map(id => id.toString()))}
|
|
multiple={true}
|
|
label="Select Products"
|
|
placeholder="Select products"
|
|
labelFormat={(product) => product.label}
|
|
/>
|
|
{formik.errors.skuIds && formik.touched.skuIds && (
|
|
<P className="text-red-500 text-sm mt-1">{formik.errors.skuIds}</P>
|
|
)}
|
|
</div>
|
|
|
|
{/* Valid Till Date */}
|
|
<div>
|
|
<P className="text-gray-700 font-medium mb-2 block">Valid Till (Optional)</P>
|
|
<DateInput
|
|
value={formik.values.validTill}
|
|
setValue={(date) => formik.setFieldValue('validTill', date)}
|
|
placeholder="Select expiry date"
|
|
showLabel={false}
|
|
/>
|
|
<P className="text-sm text-gray-500 mt-1 block">
|
|
Leave empty for no expiry
|
|
</P>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Submit Button */}
|
|
<div className="px-6 py-4 border-t border-gray-200">
|
|
<button
|
|
type="button"
|
|
onClick={() => formik.handleSubmit()}
|
|
disabled={formik.isSubmitting}
|
|
className={`bg-blue-500 py-3 rounded-lg w-full text-white text-center font-semibold disabled:opacity-50 ${formik.isSubmitting ? 'opacity-50' : ''}`}
|
|
>
|
|
{formik.isSubmitting
|
|
? 'Saving...'
|
|
: isEditing ? 'Update Snippet' : 'Create Snippet'
|
|
}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default VendorSnippetForm
|