import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Alert, Animated, Easing } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import DateTimePicker from '@react-native-community/datetimepicker'; import { Picker } from '@react-native-picker/picker'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { Picker as WheelPicker } from 'react-native-wheel-pick'; import moment from 'moment'; import { Button, Header, PageWrapper, WarningModal } from 'src/components'; import { Colors } from 'src/theme'; import { Dropdown } from 'react-native-searchable-dropdown-kj'; import { qualityOptions } from '../utils/constants'; import { getFontSize } from 'src/utils'; import TrashSVG from 'assets/icons/travels-screens/trash-solid.svg'; import { storage, StoreType } from 'src/storage'; import { useGetVisitsQuery, usePostAddVisitMutation, usePostDeleteVisitMutation, usePostGetSingleRegionMutation, usePostUpdateVisitMutation } from '@api/myRegions'; import ActionSheet from 'react-native-actions-sheet'; import { ButtonVariants } from 'src/types/components'; import EditSvg from 'assets/icons/travels-screens/pen-to-square.svg'; import TripIcon from 'assets/icons/travels-section/trip.svg'; import { useRegion } from 'src/contexts/RegionContext'; import { NmRegion } from '../utils/types'; type DateMode = 'year' | 'month' | 'full'; type QualityType = { id: number; name: string; }; interface DateValue { year: number | null; month: number | null; day: number | null; } interface Visit { id: number; trip_id?: number | null; startDate: DateValue | null; endDate: DateValue | null; quality: QualityType; isExisting: boolean; isEditing?: boolean; animatedValue: Animated.Value; } interface ExistingVisit { id: number; startDate: DateValue; endDate: DateValue; quality: QualityType; } interface RouteParams { existingVisits?: ExistingVisit[]; } interface DatePickerState { visitId: number; field: 'startDate' | 'endDate'; } const EditNmDataScreen = ({ navigation, route }: { navigation: any; route: any }) => { const id = route.params?.regionId; const isFromRegionsList = route.params?.regionsList ?? false; const token = storage.get('token', StoreType.STRING) as string; const { data: existingVisits } = useGetVisitsQuery(id, token, token ? true : false); const { mutateAsync: addVisit } = usePostAddVisitMutation(); const { mutateAsync: updateVisitAsync } = usePostUpdateVisitMutation(); const { mutateAsync: deleteVisitAsync } = usePostDeleteVisitMutation(); const { mutateAsync: getRegion } = usePostGetSingleRegionMutation(); const [visits, setVisits] = useState([]); const [showDatePicker, setShowDatePicker] = useState(null); const [isLoading, setIsLoading] = useState(false); const actionSheetRef = useRef(null); const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()); const [selectedMonth, setSelectedMonth] = useState(null); const [selectedDay, setSelectedDay] = useState(null); const { userData, setUserData, nmRegions, setNmRegions } = useRegion(); const [modalState, setModalState] = useState({ isWarningVisible: false, type: 'success', title: '', buttonTitle: '', message: '', action: () => {} }); const createEmptyVisit = useCallback( (): Visit => ({ id: Date.now() + Math.random(), startDate: null, endDate: null, quality: qualityOptions[2], isExisting: false, animatedValue: new Animated.Value(0) }), [] ); useEffect(() => { if (existingVisits && existingVisits.data && existingVisits.data?.length > 0) { const mappedVisits = existingVisits.data.map( (visit): Visit => ({ ...visit, isExisting: true, isEditing: false, animatedValue: new Animated.Value(1), startDate: { year: visit.year_from || null, month: visit.month_from || null, day: visit.day_from || null }, endDate: { year: visit.year_to || null, month: visit.month_to || null, day: visit.day_to || null }, quality: qualityOptions.find((q) => q.id === visit.quality) || qualityOptions[2] }) ); setVisits(mappedVisits); } }, [existingVisits]); const renderOption = (name: string) => ( {name} ); const addNewVisit = useCallback((): void => { const hasEmptyVisit = visits.some( (visit: Visit) => !visit.startDate || !visit.endDate || !visit.quality ); if (hasEmptyVisit) { Alert.alert('Please fill all fields in existing visits before adding a new one.'); return; } const newVisit = createEmptyVisit(); setVisits((prev) => [newVisit, ...prev]); Animated.timing(newVisit.animatedValue, { toValue: 1, duration: 300, easing: Easing.out(Easing.quad), useNativeDriver: false }).start(); }, [visits, createEmptyVisit]); const updateVisit = useCallback((id: number, field: keyof Visit, value: any): void => { setVisits((prevVisits) => prevVisits.map((visit) => (visit.id === id ? { ...visit, [field]: value } : visit)) ); }, []); const toggleEditVisit = useCallback( async (visitId: number): Promise => { const visit = visits.find((v) => v.id === visitId); if (visit && visit.isEditing) { if (!compareDates(visit.startDate, visit.endDate)) { Alert.alert('Start date cannot be after end date.'); return; } await updateVisitAsync( { token, region: id, id: visitId, quality: visit.quality.id, year_from: visit.startDate?.year || null, month_from: visit.startDate?.month || null, day_from: visit.startDate?.day || null, year_to: visit.endDate?.year || null, month_to: visit.endDate?.month || null, day_to: visit.endDate?.day || null, // completed: 1, hidden: 0 }, { onSuccess: async (data) => { await getRegion( { token, id }, { onSuccess: (res) => { if (isFromRegionsList && res.region) { const updatedNM = nmRegions.map((item: NmRegion) => { if (item.id === id) { return { ...item, year: res.region?.first_visited_in_year, last: res.region?.last_visited_in_year, quality: res.region?.best_visit_quality, visits: res.region?.no_of_visits }; } return item; }); setNmRegions(updatedNM); return; } if (res.region) { const updatedNM = { ...userData, first_visit_year: res.region.first_visited_in_year, last_visit_year: res.region.last_visited_in_year, best_visit_quality: res.region.best_visit_quality, no_of_visits: res.region.no_of_visits, visited: true }; setUserData(updatedNM); } }, onError: (err) => { console.log('err', err); } } ); Alert.alert('Success', 'Visit updated successfully!', [ { text: 'OK', onPress: () => navigation.goBack() } ]); }, onError: (error) => { console.log('updateVisitAsync error', error); } } ); } setVisits((prevVisits) => prevVisits.map((visit) => visit.id === visitId ? { ...visit, isEditing: !visit.isEditing } : visit ) ); }, [visits] ); const deleteVisit = useCallback( async (visitId: number): Promise => { const visitToDelete = visits.find((visit: Visit) => visit.id === visitId); if (!visitToDelete) return; if (visitToDelete.isExisting) { setModalState({ type: 'delete', title: `Delete visit`, message: `Are you sure you want to delete this visit?`, action: async () => { await deleteVisitAsync( { token, id: visitToDelete.id }, { onSuccess: async (res) => { Animated.timing(visitToDelete.animatedValue, { toValue: 0, duration: 300, easing: Easing.in(Easing.quad), useNativeDriver: false }).start(() => { setVisits((prevVisits) => prevVisits.filter((visit) => visit.id !== visitId)); }); getRegion( { token, id }, { onSuccess: (res) => { if (isFromRegionsList && res.not_visited) { const updatedNM = nmRegions.map((item: NmRegion) => { if (item.id === id) { return { ...item, year: 0, last: 0, quality: 3, visits: 0 }; } return item; }); setNmRegions(updatedNM); return; } else if (isFromRegionsList && res.region) { const updatedNM = nmRegions.map((item: NmRegion) => { if (item.id === id) { return { ...item, year: res.region?.first_visited_in_year, last: res.region?.last_visited_in_year, quality: res.region?.best_visit_quality, visits: res.region?.no_of_visits }; } return item; }); setNmRegions(updatedNM); return; } if (res.not_visited) { const updatedNM = { ...userData, first_visit_year: 0, last_visit_year: 0, best_visit_quality: 3, no_of_visits: 0, visited: false }; setUserData(updatedNM); return; } if (res.region) { const updatedNM = { ...userData, first_visit_year: res.region.first_visited_in_year, last_visit_year: res.region.last_visited_in_year, best_visit_quality: res.region.best_visit_quality, no_of_visits: res.region.no_of_visits, visited: true }; setUserData(updatedNM); } }, onError: (err) => { console.log('err', err); } } ); }, onError: (err) => { console.log('delete err', err); } } ); }, buttonTitle: 'Delete', isWarningVisible: true }); } else { Animated.timing(visitToDelete.animatedValue, { toValue: 0, duration: 300, easing: Easing.in(Easing.quad), useNativeDriver: false }).start(() => { setVisits((prevVisits) => prevVisits.filter((visit) => visit.id !== visitId)); }); } }, [visits] ); const formatDateForDisplay = useCallback((dateValue: DateValue | null): string => { if (!dateValue || !dateValue.year) return 'Select date'; let result = dateValue.year.toString(); if (dateValue.month) { result = `${dateValue.month.toString().padStart(2, '0')}.${result}`; if (dateValue.day) { result = `${dateValue.day.toString().padStart(2, '0')}.${result}`; } } return result; }, []); const isDateValid = useCallback((dateValue: DateValue | null): boolean => { return dateValue !== null && dateValue.year !== null; }, []); const compareDates = useCallback( (startDate: DateValue | null, endDate: DateValue | null): boolean => { if (!startDate || !endDate || !startDate.year || !endDate.year) return true; const maxEndDay = moment(`${endDate.year}-${endDate.month ?? 12}`, 'YYYY-M').daysInMonth(); const start = moment({ year: startDate.year, month: (startDate.month || 1) - 1, day: startDate.day || 1 }); const end = moment({ year: endDate.year, month: (endDate.month || 12) - 1, day: endDate.day || maxEndDay }); if (!start || !end) return true; return start.isSameOrBefore(end); }, [] ); const validateVisits = useCallback((): boolean => { const newVisits = visits.filter((visit: Visit) => !visit.isExisting); for (const visit of newVisits) { if (!isDateValid(visit.startDate) || !isDateValid(visit.endDate) || !visit.quality) { Alert.alert('Please fill all fields for each visit.'); return false; } if (!compareDates(visit.startDate, visit.endDate)) { Alert.alert('Start date cannot be after end date.'); return false; } } return true; }, [visits, isDateValid, compareDates]); const handleSave = useCallback(async (): Promise => { if (!validateVisits()) return; setIsLoading(true); try { const newVisits = visits.filter((visit: Visit) => !visit.isExisting); if (newVisits.length === 0) { // Alert.alert('No new visits to save.'); return; } newVisits.forEach(async (v) => { await addVisit( { token, region: id, quality: v.quality.id, year_from: v.startDate?.year || null, month_from: v.startDate?.month || null, day_from: v.startDate?.day || null, year_to: v.endDate?.year || null, month_to: v.endDate?.month || null, day_to: v.endDate?.day || null, // completed: 1, hidden: 0 }, { onSuccess: async (data) => { await getRegion( { token, id }, { onSuccess: (res) => { if (isFromRegionsList && res.region) { const updatedNM = nmRegions.map((item: NmRegion) => { if (item.id === id) { return { ...item, year: res.region?.first_visited_in_year, last: res.region?.last_visited_in_year, quality: res.region?.best_visit_quality, visits: res.region?.no_of_visits }; } return item; }); setNmRegions(updatedNM); return; } if (res.region) { const updatedNM = { ...userData, first_visit_year: res.region.first_visited_in_year, last_visit_year: res.region.last_visited_in_year, best_visit_quality: res.region.best_visit_quality, no_of_visits: res.region.no_of_visits, visited: true }; setUserData(updatedNM); } }, onError: (err) => { console.log('err', err); } } ); }, onError: (error) => { console.log('addVisit error', error); } } ); }); Alert.alert('Success', 'Visits saved successfully!', [ { text: 'OK', onPress: () => navigation.goBack() } ]); } catch (error) { console.log('Error saving visits:', error); } finally { setIsLoading(false); } }, [validateVisits, visits, navigation]); const currentYear = new Date().getFullYear(); const years = Array.from({ length: 120 }, (_, i) => currentYear - 80 + i); const getAvailableMonths = (year: number) => { const allMonths = [ { label: '-', value: null }, { label: 'Jan', value: 1 }, { label: 'Feb', value: 2 }, { label: 'Mar', value: 3 }, { label: 'Apr', value: 4 }, { label: 'May', value: 5 }, { label: 'Jun', value: 6 }, { label: 'Jul', value: 7 }, { label: 'Aug', value: 8 }, { label: 'Sep', value: 9 }, { label: 'Oct', value: 10 }, { label: 'Nov', value: 11 }, { label: 'Dec', value: 12 } ]; return allMonths; }; const months = getAvailableMonths(selectedYear); const getDaysInMonth = ( year: number, month: number | null ): Array<{ label: string; value: number | null }> => { if (!month) return [{ label: '-', value: null }]; const daysCount = moment(`${year}-${month}`, 'YYYY-M').daysInMonth(); const days = [{ label: '-', value: null }]; for (let i = 1; i <= daysCount; i++) { days.push({ label: i.toString(), value: i as never }); } return days; }; const days = getDaysInMonth(selectedYear, selectedMonth); const openDatePicker = ( visitId: number, field: 'startDate' | 'endDate', initialDate?: DateValue | null ) => { setShowDatePicker({ visitId, field }); if (initialDate && initialDate.year) { setSelectedYear(initialDate.year); setSelectedMonth(initialDate.month || null); setSelectedDay(initialDate.day || null); } else { const today = new Date(); setSelectedYear(today.getFullYear()); setSelectedMonth(today.getMonth() + 1); setSelectedDay(today.getDate()); } actionSheetRef.current?.show(); }; const handleDateConfirm = () => { if (showDatePicker) { const dateValue: DateValue = { year: selectedYear, month: selectedMonth, day: selectedDay }; updateVisit(showDatePicker.visitId, showDatePicker.field, dateValue); setShowDatePicker(null); actionSheetRef.current?.hide(); } }; const handleDateCancel = () => { setShowDatePicker(null); actionSheetRef.current?.hide(); }; const renderDatePicker = useCallback( ( visitId: number, field: 'startDate' | 'endDate', currentDate: DateValue | null ): JSX.Element => ( openDatePicker(visitId, field, currentDate)} > {formatDateForDisplay(currentDate)} ), [formatDateForDisplay, isDateValid] ); const renderVisitItem = useCallback( (visit: Visit, index: number): JSX.Element => { const isEditable = !visit.isExisting || visit.isEditing; return ( {visit.trip_id ? : null} Start of visit {isEditable ? ( renderDatePicker(visit.id, 'startDate', visit.startDate) ) : ( {formatDateForDisplay(visit.startDate)} )} End of visit {isEditable ? ( renderDatePicker(visit.id, 'endDate', visit.endDate) ) : ( {formatDateForDisplay(visit.endDate)} )} Quality {isEditable ? ( updateVisit(visit.id, 'quality', { id: item.id, name: item.name }) } renderItem={(item) => renderOption(item.name)} /> ) : ( {visit.quality.name} )} {visit.isExisting && ( toggleEditVisit(visit.id)} > {visit.isEditing ? ( ) : ( )} )} deleteVisit(visit.id)}> ); }, [ renderDatePicker, deleteVisit, toggleEditVisit, updateVisit, formatDateForDisplay, renderOption, visits ] ); return (
Add new visit {visits.map(renderVisitItem)} Cancel Select Date Done } > Day d.label)} selectedValue={days?.find((d) => d.value === selectedDay)?.label || '-'} onValueChange={(value: string) => { const day = days?.find((d) => d.label === value); setSelectedDay(day?.value || null); }} /> Month m.label) : []} selectedValue={months?.find((m) => m.value === selectedMonth)?.label || '-'} onValueChange={(value: string) => { const month = months?.find((m) => m.label === value); setSelectedMonth(month?.value || null); if (!month?.value) { setSelectedDay(null); } }} /> Year { setSelectedYear(value); }} /> setModalState({ ...modalState, isWarningVisible: false })} title={modalState.title} /> ); }; const styles = StyleSheet.create({ scrollView: { flex: 1 }, visitItem: { backgroundColor: Colors.WHITE, borderRadius: 8, padding: 12, marginBottom: 12, borderWidth: 0.5, borderColor: Colors.LIGHT_GRAY }, existingVisitItem: {}, visitContent: { gap: 12, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, dateRow: { flexDirection: 'row', alignItems: 'flex-start', gap: 12 }, qualityRow: { flexDirection: 'row' }, qualityColumn: { flex: 1 }, column: { flex: 1 }, actionButtons: { flexDirection: 'column', alignItems: 'center', gap: 8 }, editButton: { width: 30, height: 30, borderRadius: 15, justifyContent: 'center', alignItems: 'center', borderWidth: 1, borderColor: Colors.LIGHT_GRAY }, saveEditButton: { borderColor: Colors.ORANGE, backgroundColor: Colors.ORANGE }, label: { fontSize: getFontSize(12), color: Colors.DARK_BLUE, marginBottom: 4, fontWeight: '600' }, dateInput: { borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', backgroundColor: Colors.FILL_LIGHT, height: 36 }, readOnlyInput: { justifyContent: 'center' }, dateText: { fontSize: getFontSize(12), color: Colors.DARK_BLUE, fontWeight: '500' }, placeholderText: { color: Colors.TEXT_GRAY, flexWrap: 'wrap' }, deleteButton: { width: 30, height: 30, borderRadius: 15, backgroundColor: Colors.RED, alignItems: 'center', justifyContent: 'center' }, buttonContainer: { marginBottom: 32, gap: 12 }, datePickerHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingVertical: 16, borderBottomWidth: 1, borderBottomColor: '#eee' }, datePickerTitle: { fontSize: 18, fontWeight: '600', color: Colors.DARK_BLUE }, datePickerCancel: { fontSize: 16, color: '#666' }, datePickerConfirm: { fontSize: 16, color: Colors.DARK_BLUE, fontWeight: '600' }, wheelContainer: { flexDirection: 'row', paddingHorizontal: 14, paddingTop: 16, paddingBottom: 24 }, wheelColumn: { flex: 1, alignItems: 'center' }, wheelLabel: { fontSize: 14, color: Colors.DARK_BLUE, marginBottom: 8, fontWeight: '600' }, wheelPicker: { height: 210, width: '100%', backgroundColor: 'white' }, dropdown: { height: 36, backgroundColor: '#F4F4F4', borderRadius: 4, paddingHorizontal: 8 }, dropdownOption: { paddingVertical: 12, paddingHorizontal: 16 }, placeholderStyle: { fontSize: 12, color: Colors.DARK_BLUE, fontWeight: '500' }, addRegionBtn: { display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'row', borderRadius: 4, gap: 10, padding: 10, borderColor: Colors.DARK_BLUE, borderWidth: 1, borderStyle: 'solid', marginBottom: 12 }, addRegionBtntext: { color: Colors.DARK_BLUE, fontSize: getFontSize(14), fontFamily: 'redhat-700' } }); export default EditNmDataScreen;