478 lines
13 KiB
TypeScript
478 lines
13 KiB
TypeScript
import React, { JSX, useState } from "react";
|
||
import {
|
||
View,
|
||
Text,
|
||
TextInput,
|
||
TouchableOpacity,
|
||
Image,
|
||
StyleSheet,
|
||
GestureResponderEvent,
|
||
ScrollView,
|
||
} from "react-native";
|
||
import { Dropdown } from "react-native-element-dropdown";
|
||
import { DateTimePickerAndroid } from "@react-native-community/datetimepicker";
|
||
import * as ImagePicker from "expo-image-picker";
|
||
import { Formik, FormikHelpers } from "formik";
|
||
import * as Yup from "yup";
|
||
import ChevronRight from "../../assets/icons/chevron_rightside.svg";
|
||
import AddPhoto from "../../assets/icons/add_photo_alternate.svg";
|
||
import IssueSelectorModal from "@/components/service/IssueSelectorModal";
|
||
|
||
interface FormValues {
|
||
serviceType: string | null;
|
||
issues: string[];
|
||
comments: string | null;
|
||
date: Date | null;
|
||
photos: string[];
|
||
}
|
||
|
||
const validationSchema = Yup.object().shape({
|
||
serviceType: Yup.string().required("Service Type is required"),
|
||
issues: Yup.array().min(1, "At least one issue is required"),
|
||
date: Yup.date().required("Date and Time is required"),
|
||
photos: Yup.array().min(1, "At least one photo is required"),
|
||
comments: Yup.string(),
|
||
});
|
||
|
||
export default function ServiceFormScreen(): JSX.Element {
|
||
const [isFocus, setIsFocus] = useState<boolean>(false);
|
||
const [isIssueSelectorVisible, setIssueSelectorVisible] = useState(false);
|
||
|
||
function toggleIssueSelector() {
|
||
setIssueSelectorVisible(!isIssueSelectorVisible);
|
||
}
|
||
|
||
const initialValues: FormValues = {
|
||
serviceType: null,
|
||
issues: [],
|
||
comments: "",
|
||
date: null,
|
||
photos: [],
|
||
};
|
||
|
||
const handlePhotoPick = async (
|
||
setFieldValue: (field: string, value: any) => void,
|
||
currentPhotos: string[]
|
||
) => {
|
||
let result = await ImagePicker.launchImageLibraryAsync({
|
||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||
allowsEditing: true,
|
||
quality: 1,
|
||
allowsMultipleSelection: true,
|
||
});
|
||
|
||
if (!result.canceled) {
|
||
const newPhotos = result.assets.map((asset) => asset.uri);
|
||
const allPhotos = [...currentPhotos, ...newPhotos];
|
||
setFieldValue("photos", allPhotos);
|
||
}
|
||
};
|
||
|
||
const showPicker = (
|
||
currentDate: Date | null,
|
||
setFieldValue: (field: string, value: any) => void
|
||
) => {
|
||
const now = currentDate || new Date();
|
||
|
||
// First, show the date picker
|
||
DateTimePickerAndroid.open({
|
||
value: now,
|
||
mode: "date",
|
||
is24Hour: true,
|
||
display: "default",
|
||
onChange: (event, selectedDate) => {
|
||
if (event.type === "set" && selectedDate) {
|
||
// When date is selected, show time picker next
|
||
DateTimePickerAndroid.open({
|
||
value: selectedDate,
|
||
mode: "time",
|
||
is24Hour: true,
|
||
display: "default",
|
||
onChange: (timeEvent, selectedTime) => {
|
||
if (timeEvent.type === "set" && selectedTime) {
|
||
// Combine date and time into one Date object
|
||
const combinedDate = new Date(
|
||
selectedDate.getFullYear(),
|
||
selectedDate.getMonth(),
|
||
selectedDate.getDate(),
|
||
selectedTime.getHours(),
|
||
selectedTime.getMinutes()
|
||
);
|
||
setFieldValue("date", combinedDate);
|
||
}
|
||
},
|
||
});
|
||
}
|
||
},
|
||
});
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<ScrollView style={styles.screen}>
|
||
<Formik
|
||
initialValues={initialValues}
|
||
validationSchema={validationSchema}
|
||
onSubmit={(
|
||
values: FormValues,
|
||
actions: FormikHelpers<FormValues>
|
||
) => {
|
||
console.log(values);
|
||
}}
|
||
>
|
||
{({
|
||
handleChange,
|
||
handleBlur,
|
||
handleSubmit,
|
||
values,
|
||
setFieldValue,
|
||
errors,
|
||
touched,
|
||
}) => (
|
||
<View style={styles.formContainer}>
|
||
<View style={styles.inputContainer}>
|
||
<Text style={styles.label}>
|
||
Service Type <Text style={styles.required}>*</Text>
|
||
</Text>
|
||
<Dropdown
|
||
style={[
|
||
styles.dropdown,
|
||
isFocus && { borderColor: "#00be88" },
|
||
]}
|
||
placeholderStyle={styles.placeholderStyle}
|
||
selectedTextStyle={styles.selectedTextStyle}
|
||
inputSearchStyle={styles.inputSearchStyle}
|
||
iconStyle={styles.iconStyle}
|
||
data={[
|
||
{ label: "Regular", value: "Regular" },
|
||
{ label: "On-demand", value: "On-demand" },
|
||
]}
|
||
maxHeight={200}
|
||
labelField="label"
|
||
valueField="value"
|
||
placeholder={"-Select-"}
|
||
value={values.serviceType}
|
||
onFocus={() => setIsFocus(true)}
|
||
onBlur={() => setIsFocus(false)}
|
||
onChange={(item) => {
|
||
setFieldValue("serviceType", item.value);
|
||
setIsFocus(false);
|
||
}}
|
||
renderLeftIcon={() => (
|
||
<View style={{ marginRight: 10 }}>
|
||
{/* Add your icon component here if needed */}
|
||
</View>
|
||
)}
|
||
/>
|
||
{touched.serviceType && errors.serviceType && (
|
||
<Text style={styles.error}>{errors.serviceType}</Text>
|
||
)}
|
||
</View>
|
||
<View style={{ marginTop: 8 }}>
|
||
<Text style={styles.label}>
|
||
Issues <Text style={styles.required}>*</Text>
|
||
</Text>
|
||
<TouchableOpacity
|
||
style={styles.inputBox}
|
||
onPress={toggleIssueSelector}
|
||
>
|
||
<Text style={styles.issueText}>
|
||
{values.issues.length > 0
|
||
? values.issues.length + " issues selected"
|
||
: "Select Issue"}
|
||
</Text>
|
||
<ChevronRight />
|
||
</TouchableOpacity>
|
||
{touched.issues && errors.issues && (
|
||
<Text style={styles.error}>{errors.issues}</Text>
|
||
)}
|
||
</View>
|
||
|
||
<View style={{ marginTop: 8 }}>
|
||
<Text style={styles.label}>
|
||
Select Date and Time <Text style={styles.required}>*</Text>
|
||
</Text>
|
||
<TouchableOpacity
|
||
onPress={() => showPicker(values.date, setFieldValue)}
|
||
style={styles.inputBoxDate}
|
||
>
|
||
<Text style={styles.dateText}>
|
||
{values.date && values.date.toLocaleString()}
|
||
</Text>
|
||
</TouchableOpacity>
|
||
{touched.date && errors.date && (
|
||
<Text style={styles.error}>{`${errors.date}`}</Text>
|
||
)}
|
||
</View>
|
||
<TouchableOpacity
|
||
style={styles.photoBox}
|
||
onPress={() => handlePhotoPick(setFieldValue, values.photos)}
|
||
>
|
||
<AddPhoto />
|
||
<Text style={styles.addPhotoText}>Add photos</Text>
|
||
</TouchableOpacity>
|
||
<Text style={styles.helperText}>
|
||
Supported formats include JPG, JPEG and PNG.
|
||
</Text>
|
||
|
||
{/* Selected Images Preview */}
|
||
<View style={styles.photosContainer}>
|
||
{values.photos.map((uri, index) => (
|
||
<View key={index} style={styles.photoPreviewContainer}>
|
||
<Image source={{ uri }} style={styles.photoPreview} />
|
||
<TouchableOpacity
|
||
style={styles.removePhotoButton}
|
||
onPress={() => {
|
||
const updatedPhotos = [...values.photos];
|
||
updatedPhotos.splice(index, 1);
|
||
setFieldValue("photos", updatedPhotos);
|
||
}}
|
||
>
|
||
<Text style={styles.removePhotoText}>×</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
))}
|
||
</View>
|
||
|
||
{touched.photos && errors.photos && (
|
||
<Text style={styles.error}>{errors.photos}</Text>
|
||
)}
|
||
|
||
<Text style={styles.label}>Comments</Text>
|
||
<TextInput
|
||
style={styles.commentInput}
|
||
multiline
|
||
maxLength={100}
|
||
onChangeText={handleChange("comments")}
|
||
onBlur={handleBlur("comments")}
|
||
value={values.comments}
|
||
/>
|
||
<Text style={styles.wordCount}>
|
||
{values.comments.length}/100 words
|
||
</Text>
|
||
|
||
<TouchableOpacity
|
||
style={styles.submitButton}
|
||
onPress={handleSubmit as (e?: GestureResponderEvent) => void}
|
||
>
|
||
<Text style={styles.submitText}>Submit</Text>
|
||
</TouchableOpacity>
|
||
|
||
<IssueSelectorModal
|
||
visible={isIssueSelectorVisible}
|
||
onClose={toggleIssueSelector}
|
||
onSelect={(selectedIssues) => {
|
||
setFieldValue("issues", selectedIssues);
|
||
}}
|
||
/>
|
||
</View>
|
||
)}
|
||
</Formik>
|
||
</ScrollView>
|
||
</>
|
||
);
|
||
}
|
||
|
||
// styles stay unchanged
|
||
|
||
const styles = StyleSheet.create({
|
||
inputContainer: {},
|
||
screen: {
|
||
flex: 1,
|
||
backgroundColor: "#F3F5F8",
|
||
marginBottom: 116,
|
||
},
|
||
topBar: {
|
||
height: 56,
|
||
backgroundColor: "#F3F5F8",
|
||
justifyContent: "center",
|
||
paddingHorizontal: 16,
|
||
},
|
||
topBarText: {
|
||
fontSize: 16,
|
||
fontWeight: "600",
|
||
color: "#252A34",
|
||
},
|
||
formContainer: {
|
||
paddingHorizontal: 16,
|
||
},
|
||
label: {
|
||
fontSize: 14,
|
||
fontWeight: "500",
|
||
color: "#252A34",
|
||
marginBottom: 4,
|
||
},
|
||
required: {
|
||
color: "#D42210",
|
||
},
|
||
inputBox: {
|
||
backgroundColor: "#FFFFFF",
|
||
borderColor: "#D8DDE7",
|
||
borderRadius: 4,
|
||
paddingHorizontal: 8,
|
||
height: 40,
|
||
marginTop: 8,
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
justifyContent: "space-between",
|
||
},
|
||
inputBoxDate: {
|
||
backgroundColor: "#FFFFFF",
|
||
borderColor: "#D8DDE7",
|
||
borderWidth: 1,
|
||
borderRadius: 4,
|
||
paddingHorizontal: 8,
|
||
height: 40,
|
||
flexDirection: "row",
|
||
alignItems: "center",
|
||
justifyContent: "space-between",
|
||
marginBottom: 4,
|
||
},
|
||
picker: {
|
||
height: 40,
|
||
width: "100%",
|
||
},
|
||
issueText: {
|
||
color: "#006C4D",
|
||
},
|
||
dateText: {
|
||
color: "#949CAC",
|
||
},
|
||
photoBox: {
|
||
backgroundColor: "#FFFFFF",
|
||
borderColor: "#949CAC",
|
||
borderWidth: 1,
|
||
borderRadius: 4,
|
||
height: 64,
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
marginBottom: 8,
|
||
borderStyle: "dashed",
|
||
marginTop: 8,
|
||
},
|
||
addPhotoText: {
|
||
fontSize: 14,
|
||
color: "#252A34",
|
||
fontWeight: "500",
|
||
},
|
||
helperText: {
|
||
fontSize: 12,
|
||
color: "#717B8F",
|
||
marginBottom: 8,
|
||
},
|
||
commentInput: {
|
||
backgroundColor: "#FFFFFF",
|
||
borderColor: "#D8DDE7",
|
||
borderWidth: 1,
|
||
borderRadius: 4,
|
||
padding: 10,
|
||
height: 80,
|
||
textAlignVertical: "top",
|
||
},
|
||
wordCount: {
|
||
textAlign: "right",
|
||
color: "#717B8F",
|
||
fontSize: 14,
|
||
marginTop: 4,
|
||
},
|
||
submitButton: {
|
||
marginTop: 24,
|
||
backgroundColor: "#00875F",
|
||
borderRadius: 4,
|
||
height: 48,
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
},
|
||
submitText: {
|
||
color: "#FCFCFC",
|
||
fontSize: 14,
|
||
fontWeight: "500",
|
||
},
|
||
photo: {
|
||
width: 80,
|
||
height: 80,
|
||
resizeMode: "cover",
|
||
},
|
||
error: {
|
||
color: "#D42210",
|
||
fontSize: 12,
|
||
marginBottom: 8,
|
||
},
|
||
photosContainer: {
|
||
flexDirection: "row",
|
||
flexWrap: "wrap",
|
||
marginTop: 8,
|
||
gap: 8,
|
||
},
|
||
photoPreviewContainer: {
|
||
position: "relative",
|
||
},
|
||
photoPreview: {
|
||
width: 80,
|
||
height: 80,
|
||
borderRadius: 4,
|
||
},
|
||
removePhotoButton: {
|
||
position: "absolute",
|
||
top: -8,
|
||
right: -8,
|
||
backgroundColor: "#D42210",
|
||
borderRadius: 12,
|
||
width: 24,
|
||
height: 24,
|
||
justifyContent: "center",
|
||
alignItems: "center",
|
||
},
|
||
removePhotoText: {
|
||
color: "white",
|
||
fontSize: 18,
|
||
fontWeight: "bold",
|
||
lineHeight: 20,
|
||
},
|
||
dropdown: {
|
||
height: 40,
|
||
borderColor: "#D8DDE7",
|
||
borderWidth: 1,
|
||
borderRadius: 4,
|
||
paddingHorizontal: 8,
|
||
backgroundColor: "#FFFFFF",
|
||
marginTop: 8,
|
||
},
|
||
placeholderStyle: {
|
||
fontSize: 14,
|
||
color: "#252A34",
|
||
},
|
||
selectedTextStyle: {
|
||
fontSize: 14,
|
||
color: "#252A34",
|
||
},
|
||
iconStyle: {
|
||
width: 20,
|
||
height: 20,
|
||
},
|
||
inputSearchStyle: {
|
||
height: 40,
|
||
fontSize: 16,
|
||
},
|
||
dropdownMenu: {
|
||
backgroundColor: "#FCFCFC",
|
||
borderRadius: 4,
|
||
shadowColor: "#0E1118",
|
||
shadowOffset: { width: 0, height: 4 },
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 32,
|
||
elevation: 4,
|
||
},
|
||
dropdownItem: {
|
||
padding: 16,
|
||
height: 36,
|
||
flexDirection: "row",
|
||
justifyContent: "space-between",
|
||
alignItems: "center",
|
||
},
|
||
dropdownItemText: {
|
||
fontSize: 14,
|
||
color: "#252A34",
|
||
},
|
||
});
|