Files
biiproject-kit-v1/mobile/components/DynamicFormPopup.tsx
T

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',
},
});