recentlyBookedScrollView.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { View, Text, ScrollView, Dimensions, ActivityIndicator } from 'react-native';
  2. import React, { useEffect, useMemo, useState } from 'react';
  3. import NormalButton from './normal_button';
  4. import Svg, { Path } from 'react-native-svg';
  5. import { BookingIconSvg } from './SVG';
  6. import { chargeStationService } from '../../service/chargeStationService';
  7. import extractedInfoStore from '../../providers/extractedReservationInfo_store';
  8. import { router } from 'expo-router';
  9. import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query';
  10. const queryClient = new QueryClient();
  11. const calculateResponsivePadding = () => {
  12. const screenHeight = Dimensions.get('window').height;
  13. return screenHeight * 0.01; // 3% of screen height, adjust as needed
  14. };
  15. const RecentBookedRowItems = ({
  16. chargingStationName,
  17. chargingStationAddress,
  18. chargeStationID
  19. }: {
  20. chargingStationName: string;
  21. chargingStationAddress: string;
  22. chargeStationID: string;
  23. }) => (
  24. <View
  25. style={{
  26. flexDirection: 'row',
  27. alignItems: 'center'
  28. }}
  29. >
  30. <BookingIconSvg />
  31. <View
  32. style={{
  33. flexDirection: 'row',
  34. justifyContent: 'space-between',
  35. flex: 1,
  36. borderBottomWidth: 0.5,
  37. paddingVertical: calculateResponsivePadding(),
  38. borderColor: '#ccc',
  39. borderRadius: 8
  40. }}
  41. >
  42. <View
  43. style={{
  44. marginLeft: 15,
  45. gap: 3
  46. }}
  47. >
  48. <Text
  49. style={{
  50. fontSize: 16,
  51. color: '#222222'
  52. }}
  53. >
  54. {chargingStationName}
  55. </Text>
  56. <Text
  57. style={{
  58. fontSize: 14,
  59. color: '#888888'
  60. }}
  61. >
  62. {chargingStationAddress}
  63. </Text>
  64. </View>
  65. <NormalButton
  66. title={<Text style={{ color: '#061E25' }}>重新預約</Text>}
  67. onPress={() =>
  68. router.push({
  69. pathname: '/resultDetailPage',
  70. params: {
  71. chargeStationAddress: chargingStationAddress,
  72. chargeStationID: chargeStationID,
  73. chargeStationName: chargingStationName
  74. }
  75. })
  76. }
  77. buttonPressedStyle={{
  78. backgroundColor: '#CFDEE4'
  79. }}
  80. extendedStyle={{
  81. backgroundColor: '#E3F2F8',
  82. paddingHorizontal: 16,
  83. paddingVertical: 1,
  84. borderRadius: 8
  85. }}
  86. />
  87. </View>
  88. </View>
  89. );
  90. const fetchRecentBookings = async (retryCount = 0) => {
  91. try {
  92. // Fetch user's all reservations
  93. const reservationResponse = await chargeStationService.fetchReservationHistories();
  94. if (!reservationResponse || reservationResponse.length === 0) {
  95. console.log('No reservation data returned');
  96. return [];
  97. }
  98. // Find the two closest reservations
  99. const now = new Date();
  100. const closestReservations = reservationResponse
  101. .sort((a, b) => {
  102. const diffA = Math.abs(new Date(a.end_time) - now);
  103. const diffB = Math.abs(new Date(b.end_time) - now);
  104. return diffA - diffB;
  105. })
  106. .slice(0, 2);
  107. // Fetch all charge station info
  108. const allStations = await chargeStationService.fetchAllChargeStations();
  109. if (!allStations) {
  110. console.log('No charge station data returned');
  111. return [];
  112. }
  113. //helper method to fetch all station
  114. const findStationByConnectorId = (allStations, targetConnectorId) => {
  115. console.log('Searching for connector ID:', targetConnectorId);
  116. const station = allStations.find((station) => {
  117. console.log(`Checking station ID: ${station.id}`);
  118. if (!station.snapshot || !station.snapshot.EquipmentInfos) {
  119. console.log('Station snapshot or EquipmentInfos is missing, skipping...');
  120. return false;
  121. }
  122. return station.snapshot.EquipmentInfos.some((equipment) => {
  123. console.log(` Checking equipment ID: ${equipment.EquipmentID}`);
  124. if (!equipment.ConnectorInfos) {
  125. console.log(' ConnectorInfos is missing, skipping...');
  126. return false;
  127. }
  128. return equipment.ConnectorInfos.some((connector) => {
  129. console.log(` Checking connector ID: ${connector.ConnectorID}`);
  130. const isMatch = connector.ConnectorID === targetConnectorId;
  131. if (isMatch) console.log(' Match found!');
  132. return isMatch;
  133. });
  134. });
  135. });
  136. if (station) {
  137. console.log('Matching station found:', station.id);
  138. } else {
  139. console.log('No matching station found');
  140. }
  141. return station;
  142. };
  143. // Extract station IDs from the closest reservations
  144. const stationIds = closestReservations
  145. .map((reservation) => {
  146. try {
  147. const snapshot = JSON.parse(reservation.snapshot);
  148. if (snapshot.stationID) {
  149. return snapshot.stationID;
  150. } else if (snapshot.connector) {
  151. const station = findStationByConnectorId(allStations, snapshot.connector);
  152. return station ? station.id : null;
  153. }
  154. return null;
  155. } catch (error) {
  156. console.error('Error processing reservation:', error);
  157. return null;
  158. }
  159. })
  160. .filter((id) => id !== null);
  161. if (stationIds.length === 0) {
  162. console.log('No valid station IDs found');
  163. return [];
  164. }
  165. // Filter and extract station information
  166. const extractedInfo = stationIds
  167. .map((id) => allStations.find((station) => station.id === id))
  168. .filter((station) => station !== undefined)
  169. .map((station) => ({
  170. stationID: station.id,
  171. address: station.snapshot.Address,
  172. stationName: station.snapshot.StationName
  173. }));
  174. // Update the store with extracted information
  175. extractedInfoStore.getState().setExtractedInfo(extractedInfo);
  176. return extractedInfo;
  177. } catch (error) {
  178. console.error(`Error in fetchRecentBookings (attempt ${retryCount + 1}):`, error);
  179. if (retryCount < 2) {
  180. // Retry up to 3 times (0, 1, 2)
  181. console.log(`Retrying... (attempt ${retryCount + 2})`);
  182. return fetchRecentBookings(retryCount + 1);
  183. } else {
  184. console.log('Max retries reached. Returning empty array.');
  185. return [];
  186. }
  187. }
  188. };
  189. const RecentlyBookedScrollView = () => {
  190. const {
  191. data: extractedInfo,
  192. isLoading,
  193. error
  194. } = useQuery('recentBookings', () => fetchRecentBookings(), {
  195. staleTime: 5 * 60 * 1000, // 5 minutes
  196. cacheTime: 10 * 60 * 1000, // 10 minutes
  197. retry: 3, // This will work alongside our custom retry logic
  198. retryDelay: 1000 // Wait 1 second between retries
  199. });
  200. const memoizedExtractedInfo = useMemo(() => extractedInfo || [], [extractedInfo]);
  201. if (isLoading) {
  202. return <ActivityIndicator color="#34657b" />;
  203. }
  204. if (error) {
  205. console.log('Error fetching data:', error);
  206. return <Text>Error loading recent bookings. Please try again later.</Text>;
  207. }
  208. return (
  209. <View className="py-6 flex-column">
  210. <Text
  211. style={{
  212. fontWeight: 400,
  213. fontSize: 16,
  214. color: '#222222',
  215. marginBottom: '5%'
  216. }}
  217. >
  218. 近期預約過
  219. </Text>
  220. {isLoading ? (
  221. <ActivityIndicator color="#34657b" />
  222. ) : (
  223. <View className="">
  224. {memoizedExtractedInfo.map((item, index) => (
  225. <RecentBookedRowItems
  226. key={`${item.stationName}+${index}`}
  227. chargingStationName={item.stationName}
  228. chargingStationAddress={item.address}
  229. chargeStationID={item.stationID}
  230. />
  231. ))}
  232. </View>
  233. )}
  234. </View>
  235. );
  236. };
  237. const RecentlyBookedScrollViewWithQueryClient = () => (
  238. <QueryClientProvider client={queryClient}>
  239. <RecentlyBookedScrollView />
  240. </QueryClientProvider>
  241. );
  242. export default RecentlyBookedScrollViewWithQueryClient;