freshyo/apps/admin-ui/components/VendorSnippetForm.tsx
2026-01-24 00:13:15 +05:30

220 lines
No EOL
7.3 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 { trpc } from '../src/trpc-client';
interface VendorSnippet {
id: number;
snippetCode: string;
slotId: number;
productIds: number[];
validTill: string | null;
createdAt: string;
}
interface VendorSnippetFormProps {
snippet?: VendorSnippet | null;
onClose: () => void;
onSuccess: () => void;
}
const VendorSnippetForm: React.FC<VendorSnippetFormProps> = ({
snippet,
onClose,
onSuccess,
}) => {
// Fetch slots and products
const { data: slotsData } = trpc.user.slots.getSlots.useQuery();
const { data: productsData } = trpc.common.product.getAllProductsSummary.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() || '',
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';
}
if (!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 {
const submitData = {
snippetCode: values.snippetCode,
slotId: parseInt(values.slotId),
productIds: values.productIds.map(id => parseInt(id)),
validTill: values.validTill?.toISOString(),
};
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}`.toUpperCase());
}
}, [isEditing]); // Removed formik.values.snippetCode from deps
const slotOptions = slotsData?.slots.map(slot => ({
label: new Date(slot.deliveryTime).toLocaleString(),
value: slot.id.toString(),
})) || [];
const productOptions = productsData?.products.map(product => ({
label: `${product.name} (${product.unit})`,
value: product.id.toString(),
})) || [];
const selectedProductLabels = formik.values.productIds
.map(id => productOptions.find(opt => opt.value === id)?.label)
.filter(Boolean)
.join(', ');
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>
{/* Slot Selection */}
<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>
<MyText style={tw`text-gray-700 font-medium mb-2`}>Products</MyText>
<BottomDropdown
label="Select Products"
value={formik.values.productIds}
options={productOptions}
onValueChange={(values) => formik.setFieldValue('productIds', values)}
multiple={true}
placeholder="Select products"
/>
{formik.values.productIds.length > 0 && (
<MyText style={tw`text-sm text-gray-600 mt-2`}>
Selected: {selectedProductLabels}
</MyText>
)}
{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;