freshyo/apps/admin-ui/components/VendorSnippetForm.tsx
2026-02-01 20:36:21 +05:30

231 lines
No EOL
8.4 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { View, TouchableOpacity, Alert, ScrollView } from 'react-native';
import { useFormik } from 'formik';
import { MyText, tw, DatePicker, MyTextInput } from 'common-ui';
import BottomDropdown from 'common-ui/src/components/bottom-dropdown';
import ProductsSelector from '../components/ProductsSelector';
import { trpc } from '../src/trpc-client';
import { VendorSnippetForm as VendorSnippetFormType, VendorSnippet } from '../types/vendor-snippets';
interface VendorSnippetFormProps {
snippet?: VendorSnippetFormType | null;
onClose: () => void;
onSuccess: () => void;
showIsPermanentCheckbox?: boolean;
}
const VendorSnippetForm: React.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,
productIds: snippet?.productIds?.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.productIds.length === 0) {
errors.productIds = 'At least one product must be selected';
}
return errors;
},
onSubmit: async (values) => {
try {
console.log({values})
const submitData = {
snippetCode: values.snippetCode,
slotId: values.isPermanent ? undefined : parseInt(values.slotId || '0'),
isPermanent: values.isPermanent,
productIds: values.productIds.map(id => parseInt(id)),
validTill: values.validTill ? values.validTill.toISOString() : undefined,
};
if (isEditing && snippet) {
await updateSnippet.mutateAsync({
id: snippet.id,
updates: submitData,
});
Alert.alert('Success', 'Vendor snippet updated successfully');
} else {
await createSnippet.mutateAsync(submitData);
Alert.alert('Success', 'Vendor snippet created successfully');
}
onSuccess();
onClose();
} catch (error: any) {
Alert.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 (
<View style={tw`flex-1 bg-white`}>
<View style={tw`px-6 py-4 border-b border-gray-200`}>
<View style={tw`flex-row justify-between items-center`}>
<MyText style={tw`text-xl font-bold text-gray-800`}>
{isEditing ? 'Edit Vendor Snippet' : 'Create Vendor Snippet'}
</MyText>
<TouchableOpacity
onPress={onClose}
style={tw`p-2`}
>
<MyText style={tw`text-gray-500 text-lg`}></MyText>
</TouchableOpacity>
</View>
</View>
<ScrollView style={tw`flex-1 px-6`} showsVerticalScrollIndicator={false}>
<View style={tw`py-6 space-y-6`}>
{/* Snippet Code */}
<View>
<MyTextInput
topLabel="Snippet Code"
value={formik.values.snippetCode}
onChangeText={formik.handleChange('snippetCode')}
placeholder="Enter snippet code"
error={!!formik.errors.snippetCode && formik.touched.snippetCode}
/>
{formik.errors.snippetCode && formik.touched.snippetCode && (
<MyText style={tw`text-red-500 text-sm mt-1`}>{formik.errors.snippetCode}</MyText>
)}
</View>
{/* Is Permanent Checkbox */}
<View>
<TouchableOpacity
onPress={() => formik.setFieldValue('isPermanent', !formik.values.isPermanent)}
style={tw`flex-row items-center mb-2`}
>
<View style={[
tw`w-6 h-6 border-2 rounded-md mr-3 justify-center items-center`,
formik.values.isPermanent ? tw`bg-blue-500 border-blue-500` : tw`border-gray-300`
]}>
{formik.values.isPermanent && (
<MyText style={tw`text-white text-center text-lg`}></MyText>
)}
</View>
<MyText style={tw`text-gray-700 font-medium`}>Is Permanent?</MyText>
</TouchableOpacity>
<MyText style={tw`text-sm text-gray-500`}>
Check if this snippet is permanent and not tied to a specific delivery slot
</MyText>
</View>
{/* Slot Selection - Only show if not permanent */}
{!formik.values.isPermanent && (
<View>
<MyText style={tw`text-gray-700 font-medium mb-2`}>Delivery Slot</MyText>
<BottomDropdown
label="Select Slot"
value={formik.values.slotId}
options={slotOptions}
onValueChange={(value) => formik.setFieldValue('slotId', value)}
placeholder="Choose a delivery slot"
/>
{formik.errors.slotId && formik.touched.slotId && (
<MyText style={tw`text-red-500 text-sm mt-1`}>{formik.errors.slotId}</MyText>
)}
</View>
)}
{/* Product Selection */}
<View>
<ProductsSelector
value={formik.values.productIds.map(id => parseInt(id))}
onChange={(selectedProductIds) => formik.setFieldValue('productIds', (selectedProductIds as number[]).map(id => id.toString()))}
multiple={true}
label="Select Products"
placeholder="Select products"
labelFormat={(product) => `${product.name} (${product.unit})`}
/>
{formik.errors.productIds && formik.touched.productIds && (
<MyText style={tw`text-red-500 text-sm mt-1`}>{formik.errors.productIds}</MyText>
)}
</View>
{/* Valid Till Date */}
<View>
<MyText style={tw`text-gray-700 font-medium mb-2`}>Valid Till (Optional)</MyText>
<DatePicker
value={formik.values.validTill}
setValue={(date) => formik.setFieldValue('validTill', date)}
placeholder="Select expiry date"
showLabel={false}
/>
<MyText style={tw`text-sm text-gray-500 mt-1`}>
Leave empty for no expiry
</MyText>
</View>
</View>
</ScrollView>
{/* Submit Button */}
<View style={tw`px-6 py-4 border-t border-gray-200`}>
<TouchableOpacity
onPress={() => formik.handleSubmit()}
disabled={formik.isSubmitting}
style={tw`bg-blue-500 py-3 rounded-lg ${formik.isSubmitting ? 'opacity-50' : ''}`}
>
<MyText style={tw`text-white text-center font-semibold`}>
{formik.isSubmitting
? 'Saving...'
: isEditing ? 'Update Snippet' : 'Create Snippet'
}
</MyText>
</TouchableOpacity>
</View>
</View>
);
};
export default VendorSnippetForm;