226 lines
6.9 KiB
TypeScript
226 lines
6.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView } from 'react-native';
|
|
import { Popup } from './Popup';
|
|
import { Input } from './Input';
|
|
import { Dropdown } from './Dropdown';
|
|
import { Button } from './Button';
|
|
import { Feather } from '@expo/vector-icons';
|
|
import { useAppTheme } from '../context/ThemeContext';
|
|
import * as ImagePicker from 'expo-image-picker';
|
|
import { useToast } from '../context/ToastContext';
|
|
|
|
interface DynamicFormPopupProps {
|
|
visible: boolean;
|
|
onClose: () => void;
|
|
formType: string | null;
|
|
}
|
|
|
|
const LIME = '#C6F135';
|
|
|
|
export const DynamicFormPopup: React.FC<DynamicFormPopupProps> = ({ visible, onClose, formType }) => {
|
|
const { colors, isDark } = useAppTheme();
|
|
const { showToast } = useToast();
|
|
const [images, setImages] = useState<string[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [field1, setField1] = useState('');
|
|
const [field2, setField2] = useState('');
|
|
const [field3, setField3] = useState('');
|
|
|
|
const pickImage = async (useCamera: boolean) => {
|
|
try {
|
|
let result;
|
|
if (useCamera) {
|
|
const permission = await ImagePicker.requestCameraPermissionsAsync();
|
|
if (!permission.granted) {
|
|
showToast('Camera permission denied', 'error');
|
|
return;
|
|
}
|
|
result = await ImagePicker.launchCameraAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
quality: 0.8,
|
|
});
|
|
} else {
|
|
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
if (!permission.granted) {
|
|
showToast('Gallery permission denied', 'error');
|
|
return;
|
|
}
|
|
result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
quality: 0.8,
|
|
});
|
|
}
|
|
|
|
if (!result.canceled && result.assets && result.assets.length > 0) {
|
|
setImages([...images, result.assets[0].uri]);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
showToast('Error selecting image', 'error');
|
|
}
|
|
};
|
|
|
|
const removeImage = (index: number) => {
|
|
const newImages = [...images];
|
|
newImages.splice(index, 1);
|
|
setImages(newImages);
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
setLoading(true);
|
|
setTimeout(() => {
|
|
setLoading(false);
|
|
showToast(`${formType} submitted successfully!`, 'success');
|
|
setField1('');
|
|
setField2('');
|
|
setField3('');
|
|
setImages([]);
|
|
onClose();
|
|
}, 1500);
|
|
};
|
|
|
|
const renderFields = () => {
|
|
switch (formType) {
|
|
case 'Feature A':
|
|
return (
|
|
<>
|
|
<Input label="Reference ID" value={field1} onChangeText={setField1} required placeholder="Example: REF-001" />
|
|
<Dropdown label="Category" value={field2} options={['Option 1', 'Option 2', 'Option 3']} onSelect={setField2} required />
|
|
<Input label="Description" value={field3} onChangeText={setField3} multiline required infoText="Enter detailed information here." placeholder="Lorem ipsum dolor sit amet..." />
|
|
</>
|
|
);
|
|
case 'Feature B':
|
|
return (
|
|
<>
|
|
<Dropdown label="Type" value={field1} options={['Type A', 'Type B', 'Type C']} onSelect={setField1} required />
|
|
<Input label="Location" value={field2} onChangeText={setField2} required placeholder="Example: Area 51" />
|
|
<Input label="Notes" value={field3} onChangeText={setField3} multiline required infoText="Additional notes or comments." />
|
|
</>
|
|
);
|
|
default:
|
|
return (
|
|
<>
|
|
<Input label="Title" value={field1} onChangeText={setField1} required placeholder="Enter entry title" />
|
|
<Input label="Additional Info" value={field2} onChangeText={setField2} multiline placeholder="Enter details..." />
|
|
</>
|
|
);
|
|
}
|
|
};
|
|
|
|
if (!formType) return null;
|
|
|
|
const bg = isDark ? '#1A1A1A' : '#FFFFFF';
|
|
const border = isDark ? '#2A2A2A' : '#EEEEEE';
|
|
|
|
return (
|
|
<Popup visible={visible} onClose={onClose} title={`${formType}`} type="bottom">
|
|
<View style={styles.container}>
|
|
{renderFields()}
|
|
|
|
<Text style={[styles.label, { color: isDark ? '#6B6B6B' : '#9B9B9B' }]}>Attachments</Text>
|
|
|
|
<View style={styles.actionRow}>
|
|
<TouchableOpacity
|
|
style={[styles.attachBtn, { backgroundColor: isDark ? '#222' : '#F5F5F5', borderColor: border }]}
|
|
onPress={() => pickImage(true)}
|
|
>
|
|
<Feather name="camera" size={18} color={LIME} />
|
|
<Text style={[styles.attachText, { color: colors.text }]}>Camera</Text>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity
|
|
style={[styles.attachBtn, { backgroundColor: isDark ? '#222' : '#F5F5F5', borderColor: border }]}
|
|
onPress={() => pickImage(false)}
|
|
>
|
|
<Feather name="image" size={18} color={LIME} />
|
|
<Text style={[styles.attachText, { color: colors.text }]}>Gallery</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{images.length > 0 && (
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.imageList}>
|
|
{images.map((uri, idx) => (
|
|
<View key={idx} style={styles.imageWrapper}>
|
|
<Image source={{ uri }} style={[styles.imagePreview, { borderColor: border }]} />
|
|
<TouchableOpacity
|
|
style={[styles.deleteBadge, { backgroundColor: '#EF4444' }]}
|
|
onPress={() => removeImage(idx)}
|
|
>
|
|
<Feather name="x" size={10} color="#FFF" />
|
|
</TouchableOpacity>
|
|
</View>
|
|
))}
|
|
</ScrollView>
|
|
)}
|
|
|
|
<Button
|
|
title="Submit Entry"
|
|
onPress={handleSubmit}
|
|
loading={loading}
|
|
style={{ marginTop: 24 }}
|
|
/>
|
|
</View>
|
|
</Popup>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
paddingTop: 8,
|
|
},
|
|
label: {
|
|
fontSize: 11,
|
|
fontFamily: 'Outfit_700Bold',
|
|
marginBottom: 10,
|
|
marginTop: 16,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
},
|
|
actionRow: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
marginBottom: 16,
|
|
},
|
|
attachBtn: {
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
paddingVertical: 14,
|
|
borderRadius: 14,
|
|
borderWidth: 1,
|
|
gap: 10,
|
|
},
|
|
attachText: {
|
|
fontSize: 14,
|
|
fontFamily: 'Outfit_600SemiBold',
|
|
},
|
|
imageList: {
|
|
flexDirection: 'row',
|
|
marginBottom: 10,
|
|
},
|
|
imageWrapper: {
|
|
marginRight: 14,
|
|
position: 'relative',
|
|
},
|
|
imagePreview: {
|
|
width: 80,
|
|
height: 80,
|
|
borderRadius: 14,
|
|
borderWidth: 1,
|
|
},
|
|
deleteBadge: {
|
|
position: 'absolute',
|
|
top: -6,
|
|
right: -6,
|
|
width: 22,
|
|
height: 22,
|
|
borderRadius: 11,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderWidth: 2,
|
|
borderColor: '#FFF',
|
|
},
|
|
});
|