485 lines
16 KiB
TypeScript
485 lines
16 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
TouchableOpacity,
|
|
ScrollView,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
Alert,
|
|
Switch,
|
|
ActivityIndicator,
|
|
} from 'react-native';
|
|
import Icon from 'react-native-vector-icons/Ionicons';
|
|
import { getStyles } from './addCustomer.styles';
|
|
import { useTheme } from '@theme';
|
|
import { FormInput, FormPicker } from '@components';
|
|
import { RootState, useAppDispatch, useAppSelector } from '@store';
|
|
import { addCustomer, resetAddCustomerState } from './thunk';
|
|
import { getCustomers } from '../customers/thunk';
|
|
import { useNavigation } from '@react-navigation/native';
|
|
import { customerGroups } from '@mock-data';
|
|
|
|
export const AddCustomerScreen = () => {
|
|
const { theme: colors } = useTheme();
|
|
const styles = getStyles(colors);
|
|
const dispatch = useAppDispatch();
|
|
const navigation = useNavigation();
|
|
|
|
const userData = useAppSelector((state: RootState) => state.auth.user_data);
|
|
const { loading, successMessage, error } = useAppSelector(
|
|
(state: RootState) => state.addCustomer,
|
|
);
|
|
const { items: countryItems } = useAppSelector(
|
|
(state: RootState) => state.countryList,
|
|
);
|
|
const { items: languageItems } = useAppSelector(
|
|
(state: RootState) => state.languageList,
|
|
);
|
|
const { currencies: currencyItems } = useAppSelector(
|
|
(state: RootState) => state.currencyList,
|
|
);
|
|
|
|
// Form inputs
|
|
const [company, setCompany] = useState('');
|
|
const [vat, setVat] = useState('');
|
|
const [phonenumber, setPhonenumber] = useState('');
|
|
const [website, setWebsite] = useState('');
|
|
const [selectedGroups, setSelectedGroups] = useState<number[]>([1]); // default group 1 selected
|
|
const [defaultLanguage, setDefaultLanguage] = useState('system_default');
|
|
const [defaultCurrency, setDefaultCurrency] = useState('1'); // default to USD
|
|
|
|
// Address
|
|
const [address, setAddress] = useState('');
|
|
const [city, setCity] = useState('');
|
|
const [state, setState] = useState('');
|
|
const [zip, setZip] = useState('');
|
|
const [country, setCountry] = useState('');
|
|
|
|
// Billing Address
|
|
const [billingStreet, setBillingStreet] = useState('');
|
|
const [billingCity, setBillingCity] = useState('');
|
|
const [billingState, setBillingState] = useState('');
|
|
const [billingZip, setBillingZip] = useState('');
|
|
const [billingCountry, setBillingCountry] = useState('');
|
|
|
|
// Shipping Address
|
|
const [shippingStreet, setShippingStreet] = useState('');
|
|
const [shippingCity, setShippingCity] = useState('');
|
|
const [shippingState, setShippingState] = useState('');
|
|
const [shippingZip, setShippingZip] = useState('');
|
|
const [shippingCountry, setShippingCountry] = useState('');
|
|
|
|
// Auto-fill switches
|
|
const [billingSameAsGeneral, setBillingSameAsGeneral] = useState(false);
|
|
const [shippingSameAsBilling, setShippingSameAsBilling] = useState(false);
|
|
|
|
const countryOptions = useMemo(
|
|
() => countryItems.map(c => ({ label: c.short_name, value: c.country_id })),
|
|
[countryItems],
|
|
);
|
|
|
|
const languageOptions = useMemo(
|
|
() => languageItems.map(lang => ({ label: lang.value, value: lang.id })),
|
|
[languageItems],
|
|
);
|
|
|
|
|
|
const currencyOptions = useMemo(
|
|
() => currencyItems.map(c => ({ label: `${c.name} (${c.symbol})`, value: c.id })),
|
|
[currencyItems],
|
|
);
|
|
|
|
// Handle auto-fill logic for Billing
|
|
useEffect(() => {
|
|
if (billingSameAsGeneral) {
|
|
setBillingStreet(address);
|
|
setBillingCity(city);
|
|
setBillingState(state);
|
|
setBillingZip(zip);
|
|
setBillingCountry(country);
|
|
}
|
|
}, [billingSameAsGeneral, address, city, state, zip, country]);
|
|
|
|
// Handle auto-fill logic for Shipping
|
|
useEffect(() => {
|
|
if (shippingSameAsBilling) {
|
|
setShippingStreet(billingStreet);
|
|
setShippingCity(billingCity);
|
|
setShippingState(billingState);
|
|
setShippingZip(billingZip);
|
|
setShippingCountry(billingCountry);
|
|
}
|
|
}, [shippingSameAsBilling, billingStreet, billingCity, billingState, billingZip, billingCountry]);
|
|
|
|
// Handle Success/Error from Redux State
|
|
useEffect(() => {
|
|
if (successMessage) {
|
|
Alert.alert('Success', successMessage, [
|
|
{
|
|
text: 'OK',
|
|
onPress: () => {
|
|
dispatch(resetAddCustomerState());
|
|
dispatch(getCustomers()); // Refresh customers list view
|
|
navigation.goBack();
|
|
},
|
|
},
|
|
]);
|
|
} else if (error) {
|
|
Alert.alert('Error', error);
|
|
dispatch(resetAddCustomerState());
|
|
}
|
|
}, [successMessage, error, dispatch, navigation]);
|
|
|
|
const toggleGroup = (groupId: number) => {
|
|
if (selectedGroups.includes(groupId)) {
|
|
setSelectedGroups(selectedGroups.filter(id => id !== groupId));
|
|
} else {
|
|
setSelectedGroups([...selectedGroups, groupId]);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
if (!company.trim()) {
|
|
Alert.alert('Error', 'Company Name is required.');
|
|
return;
|
|
}
|
|
|
|
const staffId = userData?.staffid || '';
|
|
|
|
// Convert selectedGroups array to JSON format matching requirement: {"0":1,"1":2}
|
|
const groupsInObj: { [key: string]: number } = {};
|
|
selectedGroups.forEach((groupId, index) => {
|
|
groupsInObj[index.toString()] = groupId;
|
|
});
|
|
|
|
const payload = {
|
|
company: company.trim(),
|
|
vat: vat.trim(),
|
|
phonenumber: phonenumber.trim(),
|
|
website: website.trim(),
|
|
groups_in: JSON.stringify(groupsInObj),
|
|
default_language: defaultLanguage,
|
|
default_currency: defaultCurrency,
|
|
address: address.trim(),
|
|
city: city.trim(),
|
|
state: state.trim(),
|
|
zip: zip.trim(),
|
|
country: country,
|
|
billing_street: billingStreet.trim(),
|
|
billing_city: billingCity.trim(),
|
|
billing_state: billingState.trim(),
|
|
billing_zip: billingZip.trim(),
|
|
billing_country: billingCountry,
|
|
shipping_street: shippingStreet.trim(),
|
|
shipping_city: shippingCity.trim(),
|
|
shipping_state: shippingState.trim(),
|
|
shipping_zip: shippingZip.trim(),
|
|
shipping_country: shippingCountry,
|
|
addedfrom: staffId,
|
|
};
|
|
|
|
dispatch(addCustomer(payload));
|
|
};
|
|
|
|
return (
|
|
<KeyboardAvoidingView
|
|
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
style={styles.container}
|
|
>
|
|
<ScrollView contentContainerStyle={styles.scrollContainer}>
|
|
<View style={styles.formCard}>
|
|
<Text style={styles.sectionHeader}>Customer Info</Text>
|
|
|
|
<FormInput
|
|
label="Company / Name *"
|
|
value={company}
|
|
onChangeText={setCompany}
|
|
placeholder="Enter company name"
|
|
autoCapitalize="words"
|
|
/>
|
|
|
|
<FormInput
|
|
label="VAT Number"
|
|
value={vat}
|
|
onChangeText={setVat}
|
|
placeholder="Enter VAT number"
|
|
keyboardType="numeric"
|
|
/>
|
|
|
|
<FormInput
|
|
label="Phone Number"
|
|
value={phonenumber}
|
|
onChangeText={setPhonenumber}
|
|
placeholder="Enter phone number"
|
|
keyboardType="phone-pad"
|
|
/>
|
|
|
|
<FormInput
|
|
label="Website"
|
|
value={website}
|
|
onChangeText={setWebsite}
|
|
placeholder="Enter website (e.g. www.google.com)"
|
|
keyboardType="url"
|
|
autoCapitalize="none"
|
|
/>
|
|
|
|
{/* Groups Selection */}
|
|
<Text style={{ fontSize: 13, color: colors.textSecondary, fontWeight: '600', marginBottom: 8 }}>
|
|
Groups
|
|
</Text>
|
|
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 16 }}>
|
|
{customerGroups.map(group => {
|
|
const isSelected = selectedGroups.includes(group.id);
|
|
return (
|
|
<TouchableOpacity
|
|
key={group.id}
|
|
onPress={() => toggleGroup(group.id)}
|
|
style={{
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 8,
|
|
borderRadius: 20,
|
|
borderWidth: 1,
|
|
borderColor: isSelected ? colors.icon : colors.border,
|
|
backgroundColor: isSelected ? colors.icon : colors.surface,
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Text style={{ fontSize: 12, fontWeight: '600', color: isSelected ? '#FFFFFF' : colors.text }}>
|
|
{group.name}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
);
|
|
})}
|
|
</View>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormPicker
|
|
label="Language"
|
|
value={defaultLanguage}
|
|
onValueChange={setDefaultLanguage}
|
|
options={languageOptions}
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormPicker
|
|
label="Currency"
|
|
value={defaultCurrency}
|
|
onValueChange={setDefaultCurrency}
|
|
options={currencyOptions}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Primary Address */}
|
|
<View style={[styles.formCard, styles.cardSpacing]}>
|
|
<Text style={styles.sectionHeader}>Primary Address</Text>
|
|
|
|
<FormInput
|
|
label="Street Address"
|
|
value={address}
|
|
onChangeText={setAddress}
|
|
placeholder="Enter street address"
|
|
/>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="City"
|
|
value={city}
|
|
onChangeText={setCity}
|
|
placeholder="City"
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="State"
|
|
value={state}
|
|
onChangeText={setState}
|
|
placeholder="State"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="Zip Code"
|
|
value={zip}
|
|
onChangeText={setZip}
|
|
placeholder="Zip"
|
|
keyboardType="numeric"
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormPicker
|
|
label="Country"
|
|
value={country}
|
|
onValueChange={setCountry}
|
|
options={countryOptions}
|
|
placeholder="Select Country"
|
|
searchable
|
|
/>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Billing Address */}
|
|
<View style={[styles.formCard, styles.cardSpacing]}>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: colors.border, paddingBottom: 10, marginBottom: 20 }}>
|
|
<Text style={{ fontSize: 15, fontWeight: '700', color: colors.text }}>Billing Address</Text>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
<Text style={{ fontSize: 12, color: colors.textMuted }}>Same as primary</Text>
|
|
<Switch
|
|
value={billingSameAsGeneral}
|
|
onValueChange={setBillingSameAsGeneral}
|
|
trackColor={{ false: colors.border, true: colors.icon }}
|
|
thumbColor="#FFFFFF"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{!billingSameAsGeneral && (
|
|
<>
|
|
<FormInput
|
|
label="Street Address"
|
|
value={billingStreet}
|
|
onChangeText={setBillingStreet}
|
|
placeholder="Enter billing address"
|
|
/>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="City"
|
|
value={billingCity}
|
|
onChangeText={setBillingCity}
|
|
placeholder="City"
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="State"
|
|
value={billingState}
|
|
onChangeText={setBillingState}
|
|
placeholder="State"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="Zip Code"
|
|
value={billingZip}
|
|
onChangeText={setBillingZip}
|
|
placeholder="Zip"
|
|
keyboardType="numeric"
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormPicker
|
|
label="Country"
|
|
value={billingCountry}
|
|
onValueChange={setBillingCountry}
|
|
options={countryOptions}
|
|
placeholder="Select Country"
|
|
searchable
|
|
/>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
</View>
|
|
|
|
{/* Shipping Address */}
|
|
<View style={[styles.formCard, styles.cardSpacing]}>
|
|
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', borderBottomWidth: 1, borderBottomColor: colors.border, paddingBottom: 10, marginBottom: 20 }}>
|
|
<Text style={{ fontSize: 15, fontWeight: '700', color: colors.text }}>Shipping Address</Text>
|
|
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
|
<Text style={{ fontSize: 12, color: colors.textMuted }}>Same as billing</Text>
|
|
<Switch
|
|
value={shippingSameAsBilling}
|
|
onValueChange={setShippingSameAsBilling}
|
|
trackColor={{ false: colors.border, true: colors.icon }}
|
|
thumbColor="#FFFFFF"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{!shippingSameAsBilling && (
|
|
<>
|
|
<FormInput
|
|
label="Street Address"
|
|
value={shippingStreet}
|
|
onChangeText={setShippingStreet}
|
|
placeholder="Enter shipping address"
|
|
/>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="City"
|
|
value={shippingCity}
|
|
onChangeText={setShippingCity}
|
|
placeholder="City"
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="State"
|
|
value={shippingState}
|
|
onChangeText={setShippingState}
|
|
placeholder="State"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.row}>
|
|
<View style={styles.rowHalf}>
|
|
<FormInput
|
|
label="Zip Code"
|
|
value={shippingZip}
|
|
onChangeText={setShippingZip}
|
|
placeholder="Zip"
|
|
keyboardType="numeric"
|
|
/>
|
|
</View>
|
|
<View style={styles.rowHalf}>
|
|
<FormPicker
|
|
label="Country"
|
|
value={shippingCountry}
|
|
onValueChange={setShippingCountry}
|
|
options={countryOptions}
|
|
placeholder="Select Country"
|
|
searchable
|
|
/>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
</View>
|
|
|
|
{/* Submit Button */}
|
|
<TouchableOpacity
|
|
style={[styles.submitButton, loading && { opacity: 0.8 }]}
|
|
onPress={handleSubmit}
|
|
disabled={loading}
|
|
activeOpacity={0.8}
|
|
>
|
|
{loading ? (
|
|
<ActivityIndicator size="small" color="#FFFFFF" />
|
|
) : (
|
|
<>
|
|
<Icon name="checkmark" size={20} color="#FFFFFF" style={styles.buttonIcon} />
|
|
<Text style={styles.submitButtonText}>Create Customer</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
</ScrollView>
|
|
</KeyboardAvoidingView>
|
|
);
|
|
};
|