123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635 |
- import React, { useEffect, useState, useRef } from 'react';
- import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
- import ReactModal from 'react-native-modal';
- import { useNavigation } from '@react-navigation/native';
- import { Picker as WheelPicker } from 'react-native-wheel-pick';
- import moment from 'moment';
- import ActionSheet from 'react-native-actions-sheet';
- import { PageWrapper, Header, Input, WarningModal } from 'src/components';
- import RegionItem from '../Components/RegionItem';
- import RangeCalendar from 'src/components/Calendars/RangeCalendar';
- import { StoreType, storage } from 'src/storage';
- import { Colors } from 'src/theme';
- import { NAVIGATION_PAGES } from 'src/types';
- import { RegionAddData } from '../utils/types';
- import {
- useGetTripQuery,
- usePostDeleteTripMutation,
- usePostUpdateTripMutation,
- usePostSetNewTripMutation
- } from '@api/trips';
- import { qualityOptions } from '../utils/constants';
- import { styles } from './styles';
- import CalendarSvg from '../../../../../assets/icons/calendar.svg';
- interface DateValue {
- year: number | null;
- month: number | null;
- day: number | null;
- }
- interface RegionWithDates extends RegionAddData {
- visitStartDate?: DateValue | null;
- visitEndDate?: DateValue | null;
- year_from?: number;
- year_to?: number;
- month_from?: number;
- month_to?: number;
- day_from?: number | null;
- day_to?: number | null;
- }
- interface DatePickerState {
- regionId: number;
- field: 'visitStartDate' | 'visitEndDate';
- }
- const AddNewTripScreen = ({ route }: { route: any }) => {
- const editTripId = route.params?.editTripId ?? null;
- const token = storage.get('token', StoreType.STRING) as string;
- const { data: editData } = useGetTripQuery(token, editTripId, Boolean(editTripId));
- const navigation = useNavigation();
- const [calendarVisible, setCalendarVisible] = useState(false);
- const [selectedDates, setSelectedDates] = useState<string | null>(null);
- const [description, setDescription] = useState<string>('');
- const [regions, setRegions] = useState<RegionWithDates[] | null>(null);
- const [disabled, setDisabled] = useState(true);
- const [qualitySelectorVisible, setQualitySelectorVisible] = useState(false);
- const [selectedRegionId, setSelectedRegionId] = useState<number | null>(null);
- const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
- const [showDatePicker, setShowDatePicker] = useState<DatePickerState | null>(null);
- const actionSheetRef = useRef<any>(null);
- const [selectedYear, setSelectedYear] = useState<number>(new Date().getFullYear());
- const [selectedMonth, setSelectedMonth] = useState<number | null>(new Date().getMonth() + 1);
- const [selectedDay, setSelectedDay] = useState<number | null>(null);
- const { mutate: saveNewTrip } = usePostSetNewTripMutation();
- const { mutate: updateTrip } = usePostUpdateTripMutation();
- const { mutate: deleteTrip } = usePostDeleteTripMutation();
- const fillRegionDatesFromSelectedDates = (regionsToUpdate: RegionWithDates[]) => {
- if (!selectedDates || !regionsToUpdate) return regionsToUpdate;
- const from = selectedDates.split(' - ')[0];
- const to = selectedDates.split(' - ')[1];
- const updatedRegions = regionsToUpdate.map((region) => {
- const hasEmptyStartDate = !region.visitStartDate?.year || !region.visitStartDate?.month;
- const hasEmptyEndDate = !region.visitEndDate?.year || !region.visitEndDate?.month;
- if (hasEmptyStartDate || hasEmptyEndDate) {
- const updatedRegion = { ...region };
- if (hasEmptyStartDate) {
- updatedRegion.visitStartDate = {
- year: moment(from, 'YYYY-MM-DD').year(),
- month: moment(from, 'YYYY-MM-DD').month() + 1,
- day: null
- };
- updatedRegion.year_from = moment(from, 'YYYY-MM-DD').year();
- updatedRegion.month_from = moment(from, 'YYYY-MM-DD').month() + 1;
- }
- if (hasEmptyEndDate) {
- updatedRegion.visitEndDate = {
- year: moment(to, 'YYYY-MM-DD').year(),
- month: moment(to, 'YYYY-MM-DD').month() + 1,
- day: null
- };
- updatedRegion.year_to = moment(to, 'YYYY-MM-DD').year();
- updatedRegion.month_to = moment(to, 'YYYY-MM-DD').month() + 1;
- }
- return updatedRegion;
- }
- return region;
- });
- return updatedRegions;
- };
- useEffect(() => {
- if (route.params?.regionsToSave) {
- setRegions((currentRegions) => {
- const newRegionsIds = route.params.regionsToSave.map((region: RegionAddData) => region.id);
- const existingRegions = currentRegions?.filter((region) =>
- newRegionsIds.includes(region.id)
- );
- const updatedRegions = route.params.regionsToSave.map((newRegion: RegionAddData) => {
- const existingRegion = existingRegions?.find((region) => region.id === newRegion.id);
- return {
- ...newRegion,
- quality: existingRegion ? existingRegion.quality : 3,
- can_be_hidden: existingRegion ? existingRegion.can_be_hidden : newRegion.hidden,
- hidden: existingRegion ? existingRegion.hidden : false,
- visitStartDate: existingRegion?.visitStartDate || {
- year: null,
- month: null,
- day: null
- },
- visitEndDate: existingRegion?.visitEndDate || {
- year: null,
- month: null,
- day: null
- }
- };
- });
- return fillRegionDatesFromSelectedDates(updatedRegions);
- });
- }
- }, [route.params?.regionsToSave]);
- function extractNumberAndExtension(path: string | null) {
- if (!path) return null;
- const slashIndex = path.lastIndexOf('/');
- return path.substring(slashIndex + 1);
- }
- useEffect(() => {
- if (editData && editData.trip) {
- setSelectedDates(editData.trip.date_from + ' - ' + editData.trip.date_to);
- setDescription(editData.trip.description);
- setRegions(
- editData.trip.regions.map((region: any) => {
- return {
- ...region,
- id: region.region,
- flag1: extractNumberAndExtension(region.flag1),
- flag2: extractNumberAndExtension(region.flag2),
- visitStartDate: {
- year: region.year_from || null,
- month: region.month_from || null,
- day: region.day_from || null
- },
- visitEndDate: {
- year: region.year_to || null,
- month: region.month_to || null,
- day: region.day_to || null
- }
- };
- })
- );
- }
- }, [editData]);
- useEffect(() => {
- if (regions?.length && selectedDates) {
- setRegions((currentRegions) => {
- if (!currentRegions) return null;
- return fillRegionDatesFromSelectedDates(currentRegions);
- });
- setDisabled(false);
- } else {
- setDisabled(true);
- }
- }, [selectedDates]);
- useEffect(() => {
- setDisabled(!(regions?.length && selectedDates));
- }, [regions, selectedDates]);
- const currentYear = new Date().getFullYear();
- const years = Array.from({ length: 120 }, (_, i) => currentYear - 80 + i);
- const getAvailableMonths = (year: number) => {
- const allMonths = [
- { 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 = (
- regionId: number,
- field: 'visitStartDate' | 'visitEndDate',
- initialDate?: DateValue | null
- ) => {
- setShowDatePicker({ regionId, field });
- if (initialDate && initialDate.year && initialDate.month) {
- setSelectedYear(initialDate.year);
- setSelectedMonth(initialDate.month);
- 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 && selectedMonth) {
- const dateValue: DateValue = {
- year: selectedYear,
- month: selectedMonth,
- day: selectedDay
- };
- setRegions(
- (prevRegions) =>
- prevRegions?.map((region) =>
- region.id === showDatePicker.regionId
- ? { ...region, [showDatePicker.field]: dateValue }
- : region
- ) || null
- );
- setShowDatePicker(null);
- actionSheetRef.current?.hide();
- }
- };
- const handleDateCancel = () => {
- setShowDatePicker(null);
- actionSheetRef.current?.hide();
- };
- const changeQualityForRegion = (regionId: number | null, newQuality: number) => {
- regions &&
- setRegions(
- regions.map((region) => {
- if (region.id === regionId) {
- return { ...region, quality: newQuality };
- }
- return region;
- })
- );
- };
- const changeHiddenForRegion = (regionId: number | null) => {
- regions &&
- setRegions(
- regions.map((region) => {
- if (region.id === regionId) {
- return { ...region, hidden: !region.hidden };
- }
- return region;
- })
- );
- };
- const handleDeleteRegion = (regionId: number) => {
- regions && setRegions(regions.filter((region) => region.id !== regionId));
- };
- const handleDeleteTrip = () => {
- setIsWarningModalVisible(false);
- deleteTrip(
- {
- token,
- trip_id: editTripId
- },
- {
- onSuccess: () => {
- navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { deleted: true }] as never));
- }
- }
- );
- };
- const handleSaveNewTrip = () => {
- if (regions && selectedDates) {
- const regionsData = regions.map((region) => {
- return {
- id: region.id,
- quality: region.quality ?? 3,
- hidden: region.hidden,
- year_from: region.visitStartDate?.year || new Date().getFullYear(),
- year_to: region.visitEndDate?.year || new Date().getFullYear(),
- month_from: region.visitStartDate?.month || new Date().getMonth() + 1,
- month_to: region.visitEndDate?.month || new Date().getMonth() + 1,
- day_from: region.visitStartDate?.day || null,
- day_to: region.visitEndDate?.day || null
- };
- });
- saveNewTrip(
- {
- token,
- date_from: selectedDates.split(' - ')[0],
- date_to: selectedDates.split(' - ')[1],
- description,
- regions: regionsData
- },
- {
- onSuccess: () => {
- navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { saved: true }] as never));
- }
- }
- );
- }
- };
- const handleUpdateTrip = () => {
- if (regions && selectedDates) {
- const isStartDateInFuture =
- selectedDates.split(' - ')[0] > new Date().toISOString().split('T')[0];
- const regionsData = regions.map((region) => {
- return {
- id: region.id,
- quality: region.quality ?? 3,
- hidden: region.hidden,
- year_from: region.visitStartDate?.year || new Date().getFullYear(),
- year_to: region.visitEndDate?.year || new Date().getFullYear(),
- month_from: region.visitStartDate?.month || new Date().getMonth() + 1,
- month_to: region.visitEndDate?.month || new Date().getMonth() + 1,
- day_from: region.visitStartDate?.day || null,
- day_to: region.visitEndDate?.day || null
- };
- });
- updateTrip(
- {
- token,
- trip_id: editTripId,
- date_from: selectedDates.split(' - ')[0],
- date_to: selectedDates.split(' - ')[1],
- description,
- regions: regionsData
- },
- {
- onSuccess: (res) => {
- navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { updated: true }] as never));
- }
- }
- );
- }
- };
- return (
- <PageWrapper style={{ flex: 1 }}>
- <Header label={editTripId ? 'Edit Trip' : 'Add New Trip'} />
- <ScrollView
- contentContainerStyle={{ flexGrow: 1, gap: 16 }}
- showsVerticalScrollIndicator={false}
- >
- <TouchableOpacity style={styles.regionSelector} onPress={() => setCalendarVisible(true)}>
- <CalendarSvg />
- <Text style={styles.regionText}>{selectedDates ?? 'Add dates'}</Text>
- </TouchableOpacity>
- <Input
- placeholder="Add description and all interesting moments of your trip"
- inputMode={'text'}
- onChange={(text) => setDescription(text)}
- value={description}
- header="Description"
- height={54}
- multiline={true}
- />
- <View style={{ marginBottom: 8 }}>
- <Text style={styles.regionsLabel}>Regions</Text>
- <TouchableOpacity
- style={styles.addRegionBtn}
- onPress={() =>
- navigation.navigate(
- ...([
- NAVIGATION_PAGES.ADD_REGIONS,
- { regionsParams: regions, editId: editTripId }
- ] as never)
- )
- }
- >
- <Text style={styles.addRegionBtntext}>Add Region</Text>
- </TouchableOpacity>
- {regions && regions.length ? (
- <View style={styles.regionsContainer}>
- {regions.map((region) => {
- return (
- <RegionItem
- key={region.id}
- region={region}
- onDelete={() => handleDeleteRegion(region.id)}
- onQualityChange={() => {
- setSelectedRegionId(region.id);
- setQualitySelectorVisible(true);
- }}
- onHiddenChange={() => changeHiddenForRegion(region.id)}
- openDatePicker={openDatePicker}
- visitStartDate={region.visitStartDate}
- visitEndDate={region.visitEndDate}
- />
- );
- })}
- </View>
- ) : (
- <Text style={styles.noRegiosText}>No regions at the moment</Text>
- )}
- </View>
- </ScrollView>
- <View style={styles.tabContainer}>
- {editTripId ? (
- <>
- <TouchableOpacity
- style={[styles.tabStyle, styles.deleteTab]}
- onPress={() => setIsWarningModalVisible(true)}
- >
- <Text style={[styles.tabText, styles.deleteTabText]}>Delete Trip</Text>
- </TouchableOpacity>
- <TouchableOpacity
- style={[
- styles.tabStyle,
- styles.addNewTab,
- disabled && { backgroundColor: Colors.LIGHT_GRAY, borderColor: Colors.LIGHT_GRAY }
- ]}
- onPress={handleUpdateTrip}
- disabled={disabled}
- >
- <Text style={[styles.tabText, styles.addNewTabText]}>Save Trip</Text>
- </TouchableOpacity>
- </>
- ) : (
- <TouchableOpacity
- style={[
- styles.tabStyle,
- styles.addNewTab,
- disabled && { backgroundColor: Colors.LIGHT_GRAY, borderColor: Colors.LIGHT_GRAY },
- { paddingVertical: 12 }
- ]}
- onPress={handleSaveNewTrip}
- disabled={disabled}
- >
- <Text style={[styles.tabText, styles.addNewTabText]}>Add New Trip</Text>
- </TouchableOpacity>
- )}
- </View>
- <ActionSheet
- ref={actionSheetRef}
- gestureEnabled={false}
- headerAlwaysVisible={true}
- CustomHeaderComponent={
- <View style={styles.datePickerHeader}>
- <TouchableOpacity onPress={handleDateCancel}>
- <Text style={styles.datePickerCancel}>Cancel</Text>
- </TouchableOpacity>
- <Text style={styles.datePickerTitle}>Select Date</Text>
- <TouchableOpacity onPress={handleDateConfirm}>
- <Text style={styles.datePickerConfirm}>Done</Text>
- </TouchableOpacity>
- </View>
- }
- >
- <View style={styles.wheelContainer}>
- <View style={styles.wheelColumn}>
- <Text style={styles.wheelLabel}>Day</Text>
- <WheelPicker
- style={styles.wheelPicker}
- textColor={Colors.DARK_BLUE}
- itemStyle={{ fontSize: 16, fontFamily: 'montserrat-600', padding: 0 }}
- pickerData={days?.map((d) => 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);
- }}
- />
- </View>
- <View style={styles.wheelColumn}>
- <Text style={styles.wheelLabel}>Month</Text>
- <WheelPicker
- style={styles.wheelPicker}
- textColor={Colors.DARK_BLUE}
- itemStyle={{
- fontSize: 16,
- fontFamily: 'montserrat-600'
- }}
- pickerData={months ? months?.map((m) => m.label) : []}
- selectedValue={months?.find((m) => m.value === selectedMonth)?.label || 'Jan'}
- onValueChange={(value: string) => {
- const month = months?.find((m) => m.label === value);
- setSelectedMonth(month?.value || null);
- if (selectedDay && month?.value) {
- const maxDaysInMonth = moment(
- `${selectedYear}-${month.value}`,
- 'YYYY-M'
- ).daysInMonth();
- if (selectedDay > maxDaysInMonth) {
- setSelectedDay(maxDaysInMonth);
- }
- }
- }}
- />
- </View>
- <View style={styles.wheelColumn}>
- <Text style={styles.wheelLabel}>Year</Text>
- <WheelPicker
- style={styles.wheelPicker}
- textColor={Colors.DARK_BLUE}
- itemStyle={{ fontSize: 16, fontFamily: 'montserrat-600' }}
- isCyclic={true}
- pickerData={years}
- selectedValue={selectedYear}
- onValueChange={(value: number) => {
- setSelectedYear(value);
- if (selectedMonth) {
- const maxDaysInMonth = moment(
- `${value}-${selectedMonth}`,
- 'YYYY-M'
- ).daysInMonth();
- if (selectedDay && selectedDay > maxDaysInMonth) {
- setSelectedDay(maxDaysInMonth);
- }
- }
- }}
- />
- </View>
- </View>
- </ActionSheet>
- <RangeCalendar
- isModalVisible={calendarVisible}
- closeModal={(startDate?: string | null, endDate?: string | null) => {
- startDate &&
- setSelectedDates(
- startDate.toString() + ' - ' + (endDate ? endDate?.toString() : startDate?.toString())
- );
- setCalendarVisible(false);
- }}
- />
- <ReactModal
- isVisible={qualitySelectorVisible}
- onBackdropPress={() => setQualitySelectorVisible(false)}
- style={styles.modal}
- statusBarTranslucent={true}
- presentationStyle="overFullScreen"
- >
- <View style={styles.wrapper}>
- <View style={{ paddingBottom: 16 }}>
- {qualityOptions.map((option) => (
- <TouchableOpacity
- key={option.id}
- style={styles.btnOption}
- onPress={() => {
- setQualitySelectorVisible(false);
- changeQualityForRegion(selectedRegionId, option.id);
- }}
- >
- <Text style={styles.btnOptionText}>{option.name}</Text>
- </TouchableOpacity>
- ))}
- </View>
- </View>
- </ReactModal>
- <WarningModal
- type={'delete'}
- isVisible={isWarningModalVisible}
- onClose={() => setIsWarningModalVisible(false)}
- title="Delete Trip"
- message="Are you sure you want to delete your trip?"
- action={handleDeleteTrip}
- />
- </PageWrapper>
- );
- };
- export default AddNewTripScreen;
|