BaaS_Driver_Android_App/app/(tabs)/service.tsx

478 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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