work on services tab
parent
d1fbd92b2e
commit
5cde8dbbc6
|
|
@ -8,6 +8,7 @@ import { useSnackbar } from "@/contexts/Snackbar";
|
||||||
import NetInfo from "@react-native-community/netinfo";
|
import NetInfo from "@react-native-community/netinfo";
|
||||||
import { getUserDetails } from "@/store/userSlice";
|
import { getUserDetails } from "@/store/userSlice";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
import { View } from "react-native";
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
const { isLoggedIn } = useSelector((state: RootState) => state.auth);
|
const { isLoggedIn } = useSelector((state: RootState) => state.auth);
|
||||||
|
|
@ -70,6 +71,9 @@ export default function TabLayout() {
|
||||||
const IconComponent = focused ? IconFilled : Icon;
|
const IconComponent = focused ? IconFilled : Icon;
|
||||||
return <IconComponent />;
|
return <IconComponent />;
|
||||||
},
|
},
|
||||||
|
headerBackground: () => (
|
||||||
|
<View style={{ backgroundColor: "#252A34" }} />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -357,11 +357,11 @@ const styles = StyleSheet.create({
|
||||||
paddingBottom: 110,
|
paddingBottom: 110,
|
||||||
},
|
},
|
||||||
iconContainer: {
|
iconContainer: {
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#F3F5F8",
|
||||||
},
|
},
|
||||||
headerTitleContainer: {
|
headerTitleContainer: {
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#F3F5F8",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|
@ -378,7 +378,7 @@ const styles = StyleSheet.create({
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingRight: 16,
|
paddingRight: 16,
|
||||||
gap: 8,
|
gap: 8,
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#F3F5F8",
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
backgroundColor: "#FEE2E2",
|
backgroundColor: "#FEE2E2",
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,477 @@
|
||||||
import { StyleSheet } from "react-native";
|
import React, { JSX, useState } from "react";
|
||||||
import { Text, View } from "react-native";
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function ServiceTabScreen() {
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<>
|
||||||
<Text style={styles.title}>Coming Soon</Text>
|
<ScrollView style={styles.screen}>
|
||||||
</View>
|
<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({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
inputContainer: {},
|
||||||
|
screen: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: "center",
|
backgroundColor: "#F3F5F8",
|
||||||
|
marginBottom: 116,
|
||||||
|
},
|
||||||
|
topBar: {
|
||||||
|
height: 56,
|
||||||
|
backgroundColor: "#F3F5F8",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
paddingHorizontal: 16,
|
||||||
},
|
},
|
||||||
title: {
|
topBarText: {
|
||||||
fontSize: 20,
|
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",
|
fontWeight: "bold",
|
||||||
|
lineHeight: 20,
|
||||||
},
|
},
|
||||||
separator: {
|
dropdown: {
|
||||||
marginVertical: 30,
|
height: 40,
|
||||||
height: 1,
|
borderColor: "#D8DDE7",
|
||||||
width: "80%",
|
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",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<mask id="mask0_14_5357" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="18" height="18">
|
||||||
|
<rect width="18" height="18" fill="#D9D9D9"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_14_5357)">
|
||||||
|
<path d="M4.04922 15.3C3.67797 15.3 3.36016 15.1678 3.09578 14.9035C2.83141 14.6391 2.69922 14.3213 2.69922 13.95V4.05001C2.69922 3.67876 2.83141 3.36095 3.09578 3.09658C3.36016 2.8322 3.67797 2.70001 4.04922 2.70001H9.52422C9.74922 2.70001 9.92422 2.80001 10.0492 3.00001C10.1742 3.20001 10.1867 3.40764 10.0867 3.62289C10.0242 3.79514 9.97734 3.97501 9.94609 4.16251C9.91484 4.35001 9.89922 4.53926 9.89922 4.73026C9.89922 5.66251 10.2278 6.4572 10.8849 7.11433C11.542 7.77145 12.3367 8.10001 13.269 8.10001C13.46 8.10001 13.6492 8.08439 13.8367 8.05314C14.0242 8.02189 14.2068 7.97764 14.3846 7.92039C14.6068 7.84014 14.8148 7.85939 15.0086 7.97814C15.2023 8.09689 15.2992 8.26251 15.2992 8.47501V13.95C15.2992 14.3213 15.167 14.6391 14.9027 14.9035C14.6383 15.1678 14.3205 15.3 13.9492 15.3H4.04922ZM4.94922 12.6H13.0492L10.3492 9.00001L8.32422 11.7L6.97422 9.90001L4.94922 12.6ZM13.2782 6.75001C13.088 6.75001 12.9281 6.68533 12.7983 6.55595C12.6685 6.42658 12.6035 6.26626 12.6035 6.07501V5.40001H11.9265C11.7346 5.40001 11.5737 5.33533 11.4438 5.20595C11.3141 5.07658 11.2492 4.91626 11.2492 4.72501C11.2492 4.53376 11.3141 4.37345 11.4438 4.24407C11.5737 4.1147 11.7346 4.05001 11.9265 4.05001H12.6035V3.37501C12.6035 3.18376 12.6685 3.02345 12.7983 2.89407C12.9281 2.7647 13.088 2.70001 13.2782 2.70001C13.4683 2.70001 13.6277 2.7647 13.7563 2.89407C13.8849 3.02345 13.9492 3.18376 13.9492 3.37501V4.05001H14.6203C14.8104 4.05001 14.9711 4.11432 15.1023 4.24295C15.2336 4.37157 15.2992 4.53095 15.2992 4.72107C15.2992 4.9112 15.2345 5.07095 15.1052 5.20032C14.9758 5.3297 14.8155 5.39439 14.6242 5.39439H13.9492V6.06939C13.9492 6.26064 13.8849 6.42189 13.7563 6.55314C13.6277 6.68439 13.4683 6.75001 13.2782 6.75001Z" fill="#565F70"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,170 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
Modal,
|
||||||
|
StyleSheet,
|
||||||
|
} from "react-native";
|
||||||
|
import Checkbox from "expo-checkbox";
|
||||||
|
import { issueConfig } from "@/constants/config";
|
||||||
|
|
||||||
|
interface IssueSelectorModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSelect: (selectedValues: string[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IssueSelectorModal({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
onSelect,
|
||||||
|
}: IssueSelectorModalProps) {
|
||||||
|
const [selectedValues, setSelectedValues] = useState<string[]>([]);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const toggleValue = (value: string) => {
|
||||||
|
setSelectedValues((prev) =>
|
||||||
|
prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredConfig = issueConfig
|
||||||
|
.map((group) => ({
|
||||||
|
...group,
|
||||||
|
options: group.options.filter((option) =>
|
||||||
|
option.label.toLowerCase().includes(search.toLowerCase())
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
.filter((group) => group.options.length > 0);
|
||||||
|
|
||||||
|
const clearSelection = () => setSelectedValues([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal visible={visible} animationType="slide">
|
||||||
|
<View style={styles.container}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<TextInput
|
||||||
|
placeholder="Search"
|
||||||
|
placeholderTextColor="#939DAE"
|
||||||
|
value={search}
|
||||||
|
onChangeText={setSearch}
|
||||||
|
style={styles.searchBar}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Selection Counter and Clear Button */}
|
||||||
|
<View style={styles.counterBar}>
|
||||||
|
<Text
|
||||||
|
style={styles.counterText}
|
||||||
|
>{`${selectedValues.length}/23 Selected`}</Text>
|
||||||
|
<TouchableOpacity onPress={clearSelection}>
|
||||||
|
<Text style={styles.clearText}>Clear</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView style={styles.scrollArea}>
|
||||||
|
{filteredConfig.map((group) => (
|
||||||
|
<View key={group.category}>
|
||||||
|
<Text style={styles.category}>{group.category}</Text>
|
||||||
|
{group.options.map((option) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={option.value}
|
||||||
|
style={styles.itemRow}
|
||||||
|
onPress={() => toggleValue(option.value)}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
value={selectedValues.includes(option.value)}
|
||||||
|
onValueChange={() => toggleValue(option.value)}
|
||||||
|
color={
|
||||||
|
selectedValues.includes(option.value)
|
||||||
|
? "#252A34"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text style={styles.itemLabel}>{option.label}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* Done Button */}
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.doneButton}
|
||||||
|
onPress={() => {
|
||||||
|
onSelect(selectedValues);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text style={styles.doneText}>Done</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: { flex: 1, backgroundColor: "#fff" },
|
||||||
|
header: {
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
},
|
||||||
|
searchBar: {
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
borderColor: "#D8DDE6",
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
height: 36,
|
||||||
|
fontSize: 14,
|
||||||
|
color: "#252A34",
|
||||||
|
},
|
||||||
|
counterBar: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 12,
|
||||||
|
},
|
||||||
|
counterText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#555",
|
||||||
|
},
|
||||||
|
clearText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: "#ADB4BD",
|
||||||
|
},
|
||||||
|
scrollArea: { paddingHorizontal: 0 },
|
||||||
|
category: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: "#252A34",
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
backgroundColor: "#F9F9F9",
|
||||||
|
},
|
||||||
|
itemRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
backgroundColor: "#FCFCFC",
|
||||||
|
},
|
||||||
|
itemLabel: {
|
||||||
|
marginLeft: 12,
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#252A34",
|
||||||
|
},
|
||||||
|
doneButton: {
|
||||||
|
backgroundColor: "#00875F",
|
||||||
|
padding: 16,
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
doneText: {
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -109,3 +109,69 @@ export const AWS = {
|
||||||
BUCKET_NAME: "battery-as-a-service",
|
BUCKET_NAME: "battery-as-a-service",
|
||||||
REGION: "us-east-1",
|
REGION: "us-east-1",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const issueConfig = [
|
||||||
|
{
|
||||||
|
category: "Physical Damage",
|
||||||
|
options: [
|
||||||
|
{ label: "Top cover crack/damage", value: "top_cover_damage" },
|
||||||
|
{ label: "Chogori connector damage", value: "chogori_connector_damage" },
|
||||||
|
{ label: "Flip handle damage/missing", value: "flip_handle_damage" },
|
||||||
|
{
|
||||||
|
label: "Handle gripper damage/missing",
|
||||||
|
value: "handle_gripper_damage",
|
||||||
|
},
|
||||||
|
{ label: "TCU cover damage", value: "tcu_cover_damage" },
|
||||||
|
{ label: "Gore Vent damage", value: "gore_vent_damage" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Water Ingress Issues",
|
||||||
|
options: [
|
||||||
|
{ label: "BMS water ingress", value: "bms_water_ingress" },
|
||||||
|
{ label: "SD card water ingress", value: "sd_card_water_ingress" },
|
||||||
|
{ label: "Cell pack water ingress", value: "cell_pack_water_ingress" },
|
||||||
|
{
|
||||||
|
label: "Battery completely submerged in water",
|
||||||
|
value: "battery_submerged",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Electrical & Functional Failures",
|
||||||
|
options: [
|
||||||
|
{ label: "BMS failure/card burnt", value: "bms_failure" },
|
||||||
|
{
|
||||||
|
label: "SD card failure/data unable to copy/SD card not detected",
|
||||||
|
value: "sd_card_failure",
|
||||||
|
},
|
||||||
|
{ label: "Deep discharge (DD)", value: "deep_discharge" },
|
||||||
|
{ label: "Cell imbalance", value: "cell_imbalance" },
|
||||||
|
{ label: "CT sensing faulty", value: "ct_sensing_faulty" },
|
||||||
|
{ label: "TCU hang", value: "tcu_hang" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Alarms & Warnings",
|
||||||
|
options: [
|
||||||
|
{ label: "UV1, UV2 alarms (Under Voltage)", value: "uv1_uv2_alarm" },
|
||||||
|
{ label: "EOD (End of Discharge)", value: "eod_alarm" },
|
||||||
|
{
|
||||||
|
label: "Charge & discharge OT (Over Temperature) alarm",
|
||||||
|
value: "ot_alarm",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Other Issues",
|
||||||
|
options: [
|
||||||
|
{ label: "BMS full dust formed", value: "bms_dust" },
|
||||||
|
{ label: "NFF (No Fault Found)", value: "nff" },
|
||||||
|
{
|
||||||
|
label: "Under warranty (not an issue, but listed in data)",
|
||||||
|
value: "under_warranty",
|
||||||
|
},
|
||||||
|
{ label: "NA (Not Applicable)", value: "not_applicable" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
|
"@react-native-community/datetimepicker": "^8.4.2",
|
||||||
"@react-native-community/netinfo": "^11.4.1",
|
"@react-native-community/netinfo": "^11.4.1",
|
||||||
|
"@react-native-picker/picker": "^2.11.1",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
"@react-navigation/stack": "^7.4.2",
|
"@react-navigation/stack": "^7.4.2",
|
||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"expo": "^53.0.13",
|
"expo": "^53.0.13",
|
||||||
|
"expo-checkbox": "^4.1.4",
|
||||||
"expo-constants": "~17.1.6",
|
"expo-constants": "~17.1.6",
|
||||||
"expo-font": "~13.3.1",
|
"expo-font": "~13.3.1",
|
||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "~16.1.4",
|
||||||
|
|
@ -3349,6 +3352,29 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-community/datetimepicker": {
|
||||||
|
"version": "8.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-8.4.2.tgz",
|
||||||
|
"integrity": "sha512-V/s+foBfjlWGV8MKdMhxugq0SPMtYqUEYlf+sMrKUUm5Gx3pA9Qoum2ZQUqBfI4A8kgaEPIGyG/YsNX7ycnNSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"invariant": "^2.2.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"expo": ">=52.0.0",
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-windows": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"expo": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native-windows": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native-community/netinfo": {
|
"node_modules/@react-native-community/netinfo": {
|
||||||
"version": "11.4.1",
|
"version": "11.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.4.1.tgz",
|
||||||
|
|
@ -3358,6 +3384,19 @@
|
||||||
"react-native": ">=0.59"
|
"react-native": ">=0.59"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-picker/picker": {
|
||||||
|
"version": "2.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz",
|
||||||
|
"integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"example"
|
||||||
|
],
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native/assets-registry": {
|
"node_modules/@react-native/assets-registry": {
|
||||||
"version": "0.79.4",
|
"version": "0.79.4",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.4.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.4.tgz",
|
||||||
|
|
@ -7003,6 +7042,22 @@
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expo-checkbox": {
|
||||||
|
"version": "4.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/expo-checkbox/-/expo-checkbox-4.1.4.tgz",
|
||||||
|
"integrity": "sha512-sahBTVble5/6EnHgLyGvX6fAytkZ7vmllHUbX5ko1kTQ59qTdiVmCznxqaT5DNWfxRZ0gdQVlao46dGQ3hbmeQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*",
|
||||||
|
"react-native": "*",
|
||||||
|
"react-native-web": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-native-web": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/expo-constants": {
|
"node_modules/expo-constants": {
|
||||||
"version": "17.1.6",
|
"version": "17.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.6.tgz",
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,15 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||||
|
"@react-native-community/datetimepicker": "^8.4.2",
|
||||||
"@react-native-community/netinfo": "^11.4.1",
|
"@react-native-community/netinfo": "^11.4.1",
|
||||||
|
"@react-native-picker/picker": "^2.11.1",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
"@react-navigation/stack": "^7.4.2",
|
"@react-navigation/stack": "^7.4.2",
|
||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"expo": "^53.0.13",
|
"expo": "^53.0.13",
|
||||||
|
"expo-checkbox": "^4.1.4",
|
||||||
"expo-constants": "~17.1.6",
|
"expo-constants": "~17.1.6",
|
||||||
"expo-font": "~13.3.1",
|
"expo-font": "~13.3.1",
|
||||||
"expo-image-picker": "~16.1.4",
|
"expo-image-picker": "~16.1.4",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue