index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. import React, { useEffect, useState } from 'react';
  2. import { View, Text, TouchableOpacity, ScrollView } from 'react-native';
  3. import ReactModal from 'react-native-modal';
  4. import { useNavigation } from '@react-navigation/native';
  5. import { PageWrapper, Header, Input, WarningModal } from 'src/components';
  6. import RegionItem from '../Components/RegionItem';
  7. import RangeCalendar from 'src/components/Calendars/RangeCalendar';
  8. import { StoreType, storage } from 'src/storage';
  9. import { Colors } from 'src/theme';
  10. import { NAVIGATION_PAGES } from 'src/types';
  11. import { RegionAddData } from '../utils/types';
  12. import {
  13. useGetTripQuery,
  14. usePostDeleteTripMutation,
  15. usePostUpdateTripMutation,
  16. usePostSetNewTripMutation
  17. } from '@api/trips';
  18. import { qualityOptions } from '../utils/constants';
  19. import { styles } from './styles';
  20. import CalendarSvg from '../../../../../assets/icons/calendar.svg';
  21. const AddNewTripScreen = ({ route }: { route: any }) => {
  22. const editTripId = route.params?.editTripId ?? null;
  23. const token = storage.get('token', StoreType.STRING) as string;
  24. const { data: editData } = useGetTripQuery(token, editTripId, Boolean(editTripId));
  25. const navigation = useNavigation();
  26. const [calendarVisible, setCalendarVisible] = useState(false);
  27. const [selectedDates, setSelectedDates] = useState<string | null>(null);
  28. const [description, setDescription] = useState<string>('');
  29. const [regions, setRegions] = useState<RegionAddData[] | null>(null);
  30. const [disabled, setDisabled] = useState(true);
  31. const [qualitySelectorVisible, setQualitySelectorVisible] = useState(false);
  32. const [selectedRegionId, setSelectedRegionId] = useState<number | null>(null);
  33. const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
  34. const { mutate: saveNewTrip } = usePostSetNewTripMutation();
  35. const { mutate: updateTrip } = usePostUpdateTripMutation();
  36. const { mutate: deleteTrip } = usePostDeleteTripMutation();
  37. useEffect(() => {
  38. if (route.params?.regionsToSave) {
  39. setRegions((currentRegions) => {
  40. const newRegionsIds = route.params.regionsToSave.map((region: RegionAddData) => region.id);
  41. const existingRegions = currentRegions?.filter((region) =>
  42. newRegionsIds.includes(region.id)
  43. );
  44. const updatedRegions = route.params.regionsToSave.map((newRegion: RegionAddData) => {
  45. const existingRegion = existingRegions?.find((region) => region.id === newRegion.id);
  46. return {
  47. ...newRegion,
  48. quality: existingRegion ? existingRegion.quality : 3,
  49. status: existingRegion ? existingRegion.status : 0,
  50. can_be_hidden: existingRegion ? existingRegion.can_be_hidden : newRegion.hidden,
  51. hidden: existingRegion ? existingRegion.hidden : false
  52. };
  53. });
  54. return updatedRegions;
  55. });
  56. }
  57. }, [route.params?.regionsToSave]);
  58. function extractNumberAndExtension(path: string | null) {
  59. if (!path) return null;
  60. const slashIndex = path.lastIndexOf('/');
  61. return path.substring(slashIndex + 1);
  62. }
  63. useEffect(() => {
  64. if (editData && editData.trip) {
  65. setSelectedDates(editData.trip.date_from + ' - ' + editData.trip.date_to);
  66. setDescription(editData.trip.description);
  67. setRegions(
  68. editData.trip.regions.map((region: any) => {
  69. return {
  70. ...region,
  71. flag1: extractNumberAndExtension(region.flag1),
  72. flag2: extractNumberAndExtension(region.flag2)
  73. };
  74. })
  75. );
  76. }
  77. }, [editData]);
  78. useEffect(() => {
  79. if (regions?.length && selectedDates) {
  80. setDisabled(false);
  81. } else {
  82. setDisabled(true);
  83. }
  84. }, [regions, selectedDates]);
  85. const changeQualityForRegion = (regionId: number | null, newQuality: number) => {
  86. regions &&
  87. setRegions(
  88. regions.map((region) => {
  89. if (region.id === regionId) {
  90. return { ...region, quality: newQuality };
  91. }
  92. return region;
  93. })
  94. );
  95. };
  96. const changeStatusForRegion = (regionId: number | null) => {
  97. regions &&
  98. setRegions(
  99. regions.map((region) => {
  100. if (region.id === regionId) {
  101. return { ...region, status: region.status === 1 ? 0 : 1 };
  102. }
  103. return region;
  104. })
  105. );
  106. };
  107. const changeHiddenForRegion = (regionId: number | null) => {
  108. regions &&
  109. setRegions(
  110. regions.map((region) => {
  111. if (region.id === regionId) {
  112. return { ...region, hidden: !region.hidden };
  113. }
  114. return region;
  115. })
  116. );
  117. };
  118. const handleDeleteRegion = (regionId: number) => {
  119. regions && setRegions(regions.filter((region) => region.id !== regionId));
  120. };
  121. const handleDeleteTrip = () => {
  122. deleteTrip(
  123. {
  124. token,
  125. trip_id: editTripId
  126. },
  127. {
  128. onSuccess: () => {
  129. setIsWarningModalVisible(false);
  130. navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { deleted: true }] as never));
  131. }
  132. }
  133. );
  134. };
  135. const handleSaveNewTrip = () => {
  136. if (regions && selectedDates) {
  137. const isStartDateInFuture =
  138. selectedDates.split(' - ')[0] > new Date().toISOString().split('T')[0];
  139. const regionsData = regions.map((region) => {
  140. return {
  141. id: region.id,
  142. quality: region.quality ?? 3,
  143. status: isStartDateInFuture ? 0 : (Number(region.status) as 0 | 1),
  144. hidden: region.hidden
  145. };
  146. });
  147. saveNewTrip(
  148. {
  149. token,
  150. date_from: selectedDates.split(' - ')[0],
  151. date_to: selectedDates.split(' - ')[1],
  152. description,
  153. regions: regionsData
  154. },
  155. {
  156. onSuccess: () => {
  157. navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { saved: true }] as never));
  158. }
  159. }
  160. );
  161. }
  162. };
  163. const handleUpdateTrip = () => {
  164. if (regions && selectedDates) {
  165. const isStartDateInFuture =
  166. selectedDates.split(' - ')[0] > new Date().toISOString().split('T')[0];
  167. const regionsData = regions.map((region) => {
  168. return {
  169. id: region.id,
  170. quality: region.quality ?? 3,
  171. status: isStartDateInFuture ? 0 : (Number(region.status) as 0 | 1),
  172. hidden: region.hidden
  173. };
  174. });
  175. updateTrip(
  176. {
  177. token,
  178. trip_id: editTripId,
  179. date_from: selectedDates.split(' - ')[0],
  180. date_to: selectedDates.split(' - ')[1],
  181. description,
  182. regions: regionsData
  183. },
  184. {
  185. onSuccess: (res) => {
  186. navigation.navigate(...([NAVIGATION_PAGES.TRIPS, { updated: true }] as never));
  187. }
  188. }
  189. );
  190. }
  191. };
  192. return (
  193. <PageWrapper style={{ flex: 1 }}>
  194. <Header label={editTripId ? 'Edit Trip' : 'Add New Trip'} />
  195. <ScrollView
  196. contentContainerStyle={{ flexGrow: 1, gap: 16 }}
  197. showsVerticalScrollIndicator={false}
  198. >
  199. <TouchableOpacity style={styles.regionSelector} onPress={() => setCalendarVisible(true)}>
  200. <CalendarSvg />
  201. <Text style={styles.regionText}>{selectedDates ?? 'Add dates'}</Text>
  202. </TouchableOpacity>
  203. <Input
  204. placeholder="Add description and all interesting moments of your trip"
  205. inputMode={'text'}
  206. onChange={(text) => setDescription(text)}
  207. value={description}
  208. header="Description"
  209. height={54}
  210. multiline={true}
  211. />
  212. <View style={{ marginBottom: 8 }}>
  213. <Text style={styles.regionsLabel}>Regions</Text>
  214. <TouchableOpacity
  215. style={styles.addRegionBtn}
  216. onPress={() =>
  217. navigation.navigate(
  218. ...([
  219. NAVIGATION_PAGES.ADD_REGIONS,
  220. { regionsParams: regions, editId: editTripId }
  221. ] as never)
  222. )
  223. }
  224. >
  225. <Text style={styles.addRegionBtntext}>Add Region</Text>
  226. </TouchableOpacity>
  227. {regions && regions.length ? (
  228. <View style={styles.regionsContainer}>
  229. {regions.map((region) => {
  230. return (
  231. <RegionItem
  232. key={region.id}
  233. region={region}
  234. onDelete={() => handleDeleteRegion(region.id)}
  235. onToggleStatus={() => changeStatusForRegion(region.id)}
  236. onQualityChange={() => {
  237. setSelectedRegionId(region.id);
  238. setQualitySelectorVisible(true);
  239. }}
  240. onHiddenChange={() => changeHiddenForRegion(region.id)}
  241. startDate={selectedDates ? selectedDates.split(' - ')[0] : null}
  242. endDate={selectedDates ? selectedDates.split(' - ')[1] : null}
  243. isEditing={editTripId}
  244. />
  245. );
  246. })}
  247. </View>
  248. ) : (
  249. <Text style={styles.noRegiosText}>No regions at the moment</Text>
  250. )}
  251. </View>
  252. </ScrollView>
  253. <View style={styles.tabContainer}>
  254. {editTripId ? (
  255. <>
  256. <TouchableOpacity
  257. style={[styles.tabStyle, styles.deleteTab]}
  258. onPress={() => setIsWarningModalVisible(true)}
  259. >
  260. <Text style={[styles.tabText, styles.deleteTabText]}>Delete Trip</Text>
  261. </TouchableOpacity>
  262. <TouchableOpacity
  263. style={[
  264. styles.tabStyle,
  265. styles.addNewTab,
  266. disabled && { backgroundColor: Colors.LIGHT_GRAY, borderColor: Colors.LIGHT_GRAY }
  267. ]}
  268. onPress={handleUpdateTrip}
  269. disabled={disabled}
  270. >
  271. <Text style={[styles.tabText, styles.addNewTabText]}>Save Trip</Text>
  272. </TouchableOpacity>
  273. </>
  274. ) : (
  275. <TouchableOpacity
  276. style={[
  277. styles.tabStyle,
  278. styles.addNewTab,
  279. disabled && { backgroundColor: Colors.LIGHT_GRAY, borderColor: Colors.LIGHT_GRAY },
  280. { paddingVertical: 12 }
  281. ]}
  282. onPress={handleSaveNewTrip}
  283. disabled={disabled}
  284. >
  285. <Text style={[styles.tabText, styles.addNewTabText]}>Add New Trip</Text>
  286. </TouchableOpacity>
  287. )}
  288. </View>
  289. <RangeCalendar
  290. isModalVisible={calendarVisible}
  291. closeModal={(startDate?: string | null, endDate?: string | null) => {
  292. startDate &&
  293. setSelectedDates(
  294. startDate.toString() + ' - ' + (endDate ? endDate?.toString() : startDate?.toString())
  295. );
  296. setCalendarVisible(false);
  297. }}
  298. />
  299. <ReactModal
  300. isVisible={qualitySelectorVisible}
  301. onBackdropPress={() => setQualitySelectorVisible(false)}
  302. style={styles.modal}
  303. statusBarTranslucent={true}
  304. presentationStyle="overFullScreen"
  305. >
  306. <View style={styles.wrapper}>
  307. <View style={{ paddingBottom: 16 }}>
  308. {qualityOptions.map((option) => (
  309. <TouchableOpacity
  310. key={option.id}
  311. style={styles.btnOption}
  312. onPress={() => {
  313. setQualitySelectorVisible(false);
  314. changeQualityForRegion(selectedRegionId, option.id);
  315. }}
  316. >
  317. <Text style={styles.btnOptionText}>{option.name}</Text>
  318. </TouchableOpacity>
  319. ))}
  320. </View>
  321. </View>
  322. </ReactModal>
  323. <WarningModal
  324. type={'delete'}
  325. isVisible={isWarningModalVisible}
  326. onClose={() => setIsWarningModalVisible(false)}
  327. title="Delete Trip"
  328. message="Are you sure you want to delete your trip?"
  329. action={handleDeleteTrip}
  330. />
  331. </PageWrapper>
  332. );
  333. };
  334. export default AddNewTripScreen;