237 lines
8 KiB
TypeScript
237 lines
8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, TouchableOpacity, Alert, ScrollView } from 'react-native';
|
|
import { useFormik } from 'formik';
|
|
import { MyText, tw, MyTextInput, MyTouchableOpacity, DateTimePickerMod } from 'common-ui';
|
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
|
import ProductsSelector from './ProductsSelector';
|
|
import { trpc } from '../src/trpc-client';
|
|
|
|
interface AvailabilityScheduleFormProps {
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
initialProductIds?: number[];
|
|
initialGroupIds?: number[];
|
|
}
|
|
|
|
const AvailabilityScheduleForm: React.FC<AvailabilityScheduleFormProps> = ({
|
|
onClose,
|
|
onSuccess,
|
|
initialProductIds,
|
|
initialGroupIds,
|
|
}) => {
|
|
const createSchedule = trpc.admin.productAvailabilitySchedules.create.useMutation();
|
|
const { data: groupsData } = trpc.admin.product.getGroups.useQuery();
|
|
|
|
// Map groups data to match ProductsSelector types (convert price from string to number)
|
|
const groups = (groupsData?.groups || []).map(group => ({
|
|
...group,
|
|
products: group.products.map(product => ({
|
|
...product,
|
|
price: parseFloat(product.price as unknown as string) || 0,
|
|
})),
|
|
}));
|
|
|
|
const formik = useFormik({
|
|
initialValues: {
|
|
scheduleName: '',
|
|
timeDate: null as Date | null,
|
|
action: 'in' as 'in' | 'out',
|
|
productIds: initialProductIds || ([] as number[]),
|
|
groupIds: initialGroupIds || ([] as number[]),
|
|
},
|
|
validate: (values) => {
|
|
const errors: {[key: string]: string} = {};
|
|
|
|
if (!values.scheduleName.trim()) {
|
|
errors.scheduleName = 'Schedule name is required';
|
|
}
|
|
|
|
if (!values.timeDate) {
|
|
errors.timeDate = 'Time is required';
|
|
}
|
|
|
|
if (!values.action) {
|
|
errors.action = 'Action is required';
|
|
}
|
|
|
|
if (values.productIds.length === 0) {
|
|
errors.productIds = 'At least one product must be selected';
|
|
}
|
|
|
|
return errors;
|
|
},
|
|
onSubmit: async (values) => {
|
|
try {
|
|
// Convert Date to HH:MM string
|
|
const hours = values.timeDate!.getHours().toString().padStart(2, '0');
|
|
const minutes = values.timeDate!.getMinutes().toString().padStart(2, '0');
|
|
const timeString = `${hours}:${minutes}`;
|
|
|
|
await createSchedule.mutateAsync({
|
|
scheduleName: values.scheduleName,
|
|
time: timeString,
|
|
action: values.action,
|
|
productIds: values.productIds,
|
|
groupIds: values.groupIds,
|
|
});
|
|
|
|
Alert.alert('Success', 'Schedule created successfully');
|
|
onSuccess();
|
|
onClose();
|
|
} catch (error: any) {
|
|
Alert.alert('Error', error.message || 'Failed to create schedule');
|
|
}
|
|
},
|
|
});
|
|
|
|
const actionOptions = [
|
|
{ label: 'In Stock', value: 'in' },
|
|
{ label: 'Out of Stock', value: 'out' },
|
|
];
|
|
|
|
return (
|
|
<View style={tw`flex-1 bg-white`}>
|
|
{/* Header */}
|
|
<View style={tw`flex-row items-center justify-between p-4 border-b border-gray-200 bg-white`}>
|
|
<MyText style={tw`text-xl font-bold text-gray-900`}>
|
|
Create Availability Schedule
|
|
</MyText>
|
|
<MyTouchableOpacity onPress={onClose}>
|
|
<MaterialIcons name="close" size={24} color="#6B7280" />
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
|
|
<ScrollView style={tw`flex-1 p-4`} showsVerticalScrollIndicator={false}>
|
|
{/* Schedule Name */}
|
|
<View style={tw`mb-4`}>
|
|
<MyText style={tw`text-sm font-medium text-gray-700 mb-2`}>
|
|
Schedule Name
|
|
</MyText>
|
|
<MyTextInput
|
|
placeholder="Enter schedule name"
|
|
value={formik.values.scheduleName}
|
|
onChangeText={formik.handleChange('scheduleName')}
|
|
onBlur={formik.handleBlur('scheduleName')}
|
|
style={tw`border rounded-lg p-3 ${
|
|
formik.touched.scheduleName && formik.errors.scheduleName
|
|
? 'border-red-500'
|
|
: 'border-gray-300'
|
|
}`}
|
|
/>
|
|
{formik.touched.scheduleName && formik.errors.scheduleName && (
|
|
<MyText style={tw`text-red-500 text-xs mt-1`}>
|
|
{formik.errors.scheduleName}
|
|
</MyText>
|
|
)}
|
|
</View>
|
|
|
|
{/* Time */}
|
|
<View style={tw`mb-4`}>
|
|
<MyText style={tw`text-sm font-medium text-gray-700 mb-2`}>
|
|
Time
|
|
</MyText>
|
|
<DateTimePickerMod
|
|
value={formik.values.timeDate}
|
|
setValue={(date) => formik.setFieldValue('timeDate', date)}
|
|
timeOnly={true}
|
|
showLabels={false}
|
|
/>
|
|
{formik.touched.timeDate && formik.errors.timeDate && (
|
|
<MyText style={tw`text-red-500 text-xs mt-1`}>
|
|
{formik.errors.timeDate}
|
|
</MyText>
|
|
)}
|
|
</View>
|
|
|
|
{/* Action */}
|
|
<View style={tw`mb-4`}>
|
|
<MyText style={tw`text-sm font-medium text-gray-700 mb-2`}>
|
|
Action
|
|
</MyText>
|
|
<View style={tw`flex-row gap-3`}>
|
|
{actionOptions.map((option) => (
|
|
<TouchableOpacity
|
|
key={option.value}
|
|
onPress={() => formik.setFieldValue('action', option.value)}
|
|
style={tw`flex-1 flex-row items-center p-4 rounded-lg border ${
|
|
formik.values.action === option.value
|
|
? 'bg-blue-50 border-blue-500'
|
|
: 'bg-white border-gray-300'
|
|
}`}
|
|
>
|
|
<View
|
|
style={tw`w-5 h-5 rounded-full border-2 mr-3 items-center justify-center ${
|
|
formik.values.action === option.value
|
|
? 'border-blue-500'
|
|
: 'border-gray-300'
|
|
}`}
|
|
>
|
|
{formik.values.action === option.value && (
|
|
<View style={tw`w-3 h-3 rounded-full bg-blue-500`} />
|
|
)}
|
|
</View>
|
|
<MyText
|
|
style={tw`font-medium ${
|
|
formik.values.action === option.value
|
|
? 'text-blue-700'
|
|
: 'text-gray-700'
|
|
}`}
|
|
>
|
|
{option.label}
|
|
</MyText>
|
|
</TouchableOpacity>
|
|
))}
|
|
</View>
|
|
</View>
|
|
|
|
{/* Products and Groups */}
|
|
<View style={tw`mb-4`}>
|
|
<MyText style={tw`text-sm font-medium text-gray-700 mb-2`}>
|
|
Products & Groups
|
|
</MyText>
|
|
<ProductsSelector
|
|
value={formik.values.productIds}
|
|
onChange={(value) => formik.setFieldValue('productIds', value)}
|
|
groups={groups}
|
|
selectedGroupIds={formik.values.groupIds}
|
|
onGroupChange={(groupIds) => formik.setFieldValue('groupIds', groupIds)}
|
|
showGroups={true}
|
|
label="Select Products"
|
|
placeholder="Select products for this schedule"
|
|
/>
|
|
{formik.touched.productIds && formik.errors.productIds && (
|
|
<MyText style={tw`text-red-500 text-xs mt-1`}>
|
|
{formik.errors.productIds}
|
|
</MyText>
|
|
)}
|
|
</View>
|
|
|
|
{/* Spacer for bottom padding */}
|
|
<View style={tw`h-24`} />
|
|
</ScrollView>
|
|
|
|
{/* Footer Buttons */}
|
|
<View style={tw`p-4 border-t border-gray-200 bg-white flex-row gap-3`}>
|
|
<MyTouchableOpacity
|
|
onPress={onClose}
|
|
style={tw`flex-1 py-3 px-4 rounded-lg border border-gray-300 items-center`}
|
|
>
|
|
<MyText style={tw`font-medium text-gray-700`}>Cancel</MyText>
|
|
</MyTouchableOpacity>
|
|
<MyTouchableOpacity
|
|
onPress={() => formik.handleSubmit()}
|
|
disabled={formik.isSubmitting}
|
|
style={tw`flex-1 py-3 px-4 rounded-lg bg-blue-600 items-center ${
|
|
formik.isSubmitting ? 'opacity-50' : ''
|
|
}`}
|
|
>
|
|
<MyText style={tw`font-medium text-white`}>
|
|
{formik.isSubmitting ? 'Creating...' : 'Create Schedule'}
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default AvailabilityScheduleForm;
|