import React, { JSX, useState } from "react"; import { View, Text, TextInput, TouchableOpacity, Image, StyleSheet, GestureResponderEvent, ScrollView, KeyboardAvoidingView, Platform, ActivityIndicator, Keyboard, } 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 { FastField, 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"; import { uploadImage } from "@/utils/User"; import api from "@/services/axiosClient"; import { useSnackbar } from "@/contexts/Snackbar"; import { BASE_URL, SERVICE } from "@/constants/config"; import { Overlay } from "@/components/common/Overlay"; import CrossIcon from "@/assets/icons/close_white.svg"; import { useTranslation } from "react-i18next"; import CalendarIcon from "@/assets/icons/calendar.svg"; import { useSelector } from "react-redux"; import { RootState } from "@/store"; interface FormValues { serviceType: string | null; issues: number[]; comments: string | null; date: Date | null; photos: string[]; } interface Issue { id: number; name: { en: String; hi: String; }; } export default function ServiceFormScreen(): JSX.Element { const { t } = useTranslation(); const validationSchema = Yup.object().shape({ serviceType: Yup.string().required(t("service.service-type-is-required")), issues: Yup.array().when("serviceType", { is: (val: string) => val !== "Regular", then: (schema) => schema.min(1, t("service.atleast-one-issue-is-required")), otherwise: (schema) => schema.notRequired(), }), date: Yup.date().required(t("service.date-and-time-is-required")), photos: Yup.array().min(1, t("service.atleast-one-photo-is-required")), comments: Yup.string(), }); const [isFocus, setIsFocus] = useState(false); const [isIssueSelectorVisible, setIssueSelectorVisible] = useState(false); const { data } = useSelector((state: RootState) => state.user); const [issues, setIssues] = useState([]); const [isLoadingIssues, setIsLoadingIssues] = useState(false); const { showSnackbar } = useSnackbar(); function toggleIssueSelector() { setIssueSelectorVisible(!isIssueSelectorVisible); } function checkSixMonthsCondition(warrantyStartDate: string | undefined) { if (warrantyStartDate) { const startDate = new Date(warrantyStartDate); const monthsLater = new Date(startDate); monthsLater.setMonth( monthsLater.getMonth() + SERVICE.ENABLE_REGULAR_SERVICE_AFTER_IN_MONTHS ); const today = new Date(); if (today >= monthsLater) { return true; } else { return false; } } return false; } const warrantyStartDay = data?.batteries[0]?.warranty_start_date; const isAfterSixMonths = checkSixMonthsCondition(warrantyStartDay); const dropdownData = [ { label: "Regular", value: "Regular", disabled: !isAfterSixMonths }, { label: "On-demand", value: "On-demand", disabled: false }, ]; const fetchIssues = async () => { try { setIsLoadingIssues(true); const response = await api.get(`${BASE_URL}/api/v1/service-issue-list`); if (response.data.success) { setIssues(response.data.data); } else { throw new Error(response.data?.message || "Failed to fetch issues"); } } catch (error: any) { console.error("Error fetching issues:", error); showSnackbar(`${t("common.something-went-wrong")}`, "error"); setIssues([]); } finally { setIsLoadingIssues(false); } }; 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: 0.5, allowsMultipleSelection: true, }); if (!result.canceled) { const newPhotos = result.assets.map((asset) => asset.uri); const allPhotos = [...currentPhotos, ...newPhotos]; setFieldValue("photos", allPhotos); } }; const showPicker = (setFieldValue: (field: string, value: any) => void) => { const now = new Date(); DateTimePickerAndroid.open({ value: now, mode: "date", is24Hour: false, display: "default", minimumDate: now, onChange: (event, selectedDate) => { if (event.type === "set" && selectedDate) { // When date is selected, show time picker next DateTimePickerAndroid.open({ value: selectedDate, mode: "time", is24Hour: false, 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() ); if (combinedDate < now) { showSnackbar(`${t("service.select-valid-time")}`, "error"); return; } //(10AM - 5PM) const hours = combinedDate.getHours(); if (hours < 10 || hours >= 17) { showSnackbar( t("service.time-must-be-between-10-and-5"), "error" ); return; } setFieldValue("date", combinedDate); } }, }); } }, }); }; const handleServiceTypeChange = async ( item: { label: string; value: string }, setFieldValue: (field: string, value: any) => void, setFieldTouched: (field: string, touched: boolean) => void ) => { if (item.value === "Regular") { // Regular service can be selected immediately setFieldValue("serviceType", item.value); setFieldValue("issues", []); setFieldTouched("issues", false); setIssues([]); setIsFocus(false); } else if (item.value === "On-demand") { try { setFieldValue("serviceType", item.value); setIsLoadingIssues(true); const response = await api.get(`${BASE_URL}/api/v1/service-issue-list`); if (response.data.success) { setIssues(response.data.data); setFieldValue("serviceType", item.value); setFieldValue("issues", []); setFieldTouched("issues", false); setIsFocus(false); } else { setFieldValue("serviceType", null); throw new Error(response.data?.message || "Failed to fetch issues"); } } catch (error: any) { console.error("Error fetching issues:", error); showSnackbar(`${t("common.something-went-wrong")}`, "error"); setIssues([]); setFieldValue("serviceType", null); } finally { setIsLoadingIssues(false); } } }; const getSelectedIssuesText = (selectedIssueIds: number[]) => { if (selectedIssueIds.length === 0) return `${t("service.select-issue")}`; if (selectedIssueIds.length === 1) return "1 issue selected"; return `${selectedIssueIds.length} issues selected`; }; return ( ) => { Keyboard.dismiss(); try { const uploadedPhotoUrls: string[] = []; for (const uri of values.photos) { const uploadedUrl = await uploadImage(uri); uploadedPhotoUrls.push(uploadedUrl); } console.log("IMAGES UPLOADED"); const payload = { service_type: values.serviceType, issue_types: values.issues, scheduled_time: values.date?.toISOString(), photos: uploadedPhotoUrls, comments: values.comments, }; const response = await api.post( `${BASE_URL}/api/v1/schedule-maintenance`, payload ); if (!response.data.success) { console.log(response.data?.message || "Submission failed"); throw new Error(response.data?.message || "Submission failed"); } console.log("Submission successful:", response.data); showSnackbar( `${t("service.service-request-success")}`, "success" ); actions.resetForm(); } catch (error: any) { console.error("Error during submission:", error); showSnackbar(`${t("common.something-went-wrong")}`, "error"); } finally { actions.setSubmitting(false); } }} > {({ handleChange, handleBlur, handleSubmit, values, setFieldValue, errors, touched, isSubmitting, setFieldTouched, }) => ( {t("service.service-type")}{" "} * setIsFocus(true)} onBlur={() => setIsFocus(false)} onChange={(item) => { if (item.disabled) { showSnackbar( t("service.regular-available-after-6-months"), "error" ); return; } handleServiceTypeChange( item, setFieldValue, setFieldTouched ); }} renderItem={(item) => ( {item.label} )} /> {touched.serviceType && errors.serviceType && ( {errors.serviceType} )} {t("service.issue")}{" "} {values.serviceType == "On-demand" && ( * )} {getSelectedIssuesText(values.issues)} {isLoadingIssues ? : } {touched.issues && errors.issues && ( {errors.issues} )} {t("service.select-datetime")}{" "} * showPicker(setFieldValue)} style={styles.inputBoxDate} > {values.date ? values.date.toLocaleString() : `${t("service.select")}`} {touched.date && errors.date && ( {`${errors.date}`} )} handlePhotoPick(setFieldValue, values.photos)} > {t("service.add-photos")}{" "} {t("service.supported-formats")} {/* Selected Images Preview */} {values.photos.map((uri, index) => ( { const updatedPhotos = [...values.photos]; updatedPhotos.splice(index, 1); setFieldValue("photos", updatedPhotos); }} > ))} {touched.photos && errors.photos && ( {errors.photos} )} {t("service.comments")} {values.comments?.length || 0}/100 {t("service.words")} void} > {t("service.submit")} { setFieldValue("issues", selectedIssues); }} initialSelectedValues={values.issues} issues={issues} /> {isSubmitting && } )} ); } const styles = StyleSheet.create({ issueTextDisabled: { color: "#949CAC", }, container: { flex: 1, }, inputContainer: {}, screen: { flex: 1, backgroundColor: "#F3F5F8", }, scrollContent: { paddingBottom: 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", marginTop: 8, }, 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: 4, right: 4, backgroundColor: "#252A345C", 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", }, });