recentlyBookedScrollView.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 '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 () => {
  91. // Fetch user's all reservations
  92. const reservationResponse = await chargeStationService.fetchReservationHistories();
  93. if (!reservationResponse || reservationResponse.length === 0) {
  94. console.log('No reservation data returned');
  95. return [];
  96. }
  97. // Find the two closest reservations
  98. const now = new Date();
  99. const closestReservations = reservationResponse
  100. .sort((a, b) => {
  101. const diffA = Math.abs(new Date(a.end_time) - now);
  102. const diffB = Math.abs(new Date(b.end_time) - now);
  103. return diffA - diffB;
  104. })
  105. .slice(0, 2);
  106. // Fetch all charge station info
  107. const allStations = await chargeStationService.fetchAllChargeStations();
  108. if (!allStations) {
  109. console.log('No charge station data returned');
  110. return [];
  111. }
  112. //helper method to fetch all station
  113. const findStationByConnectorId = (allStations, targetConnectorId) => {
  114. console.log('Searching for connector ID:', targetConnectorId);
  115. const station = allStations.find((station) => {
  116. console.log(`Checking station ID: ${station.id}`);
  117. if (!station.snapshot || !station.snapshot.EquipmentInfos) {
  118. console.log('Station snapshot or EquipmentInfos is missing, skipping...');
  119. return false;
  120. }
  121. return station.snapshot.EquipmentInfos.some((equipment) => {
  122. console.log(` Checking equipment ID: ${equipment.EquipmentID}`);
  123. if (!equipment.ConnectorInfos) {
  124. console.log(' ConnectorInfos is missing, skipping...');
  125. return false;
  126. }
  127. return equipment.ConnectorInfos.some((connector) => {
  128. console.log(` Checking connector ID: ${connector.ConnectorID}`);
  129. const isMatch = connector.ConnectorID === targetConnectorId;
  130. if (isMatch) console.log(' Match found!');
  131. return isMatch;
  132. });
  133. });
  134. });
  135. if (station) {
  136. console.log('Matching station found:', station.id);
  137. } else {
  138. console.log('No matching station found');
  139. }
  140. return station;
  141. };
  142. // Extract station IDs from the closest reservations
  143. const stationIds = closestReservations
  144. .map((reservation) => {
  145. try {
  146. const snapshot = JSON.parse(reservation.snapshot);
  147. if (snapshot.stationID) {
  148. return snapshot.stationID;
  149. } else if (snapshot.connector) {
  150. const station = findStationByConnectorId(allStations, snapshot.connector);
  151. return station ? station.id : null;
  152. }
  153. return null;
  154. } catch (error) {
  155. console.error('Error processing reservation:', error);
  156. return null;
  157. }
  158. })
  159. .filter((id) => id !== null);
  160. if (stationIds.length === 0) {
  161. console.log('No valid station IDs found');
  162. return [];
  163. }
  164. // Filter and extract station information
  165. const extractedInfo = stationIds
  166. .map((id) => allStations.find((station) => station.id === id))
  167. .filter((station) => station !== undefined)
  168. .map((station) => ({
  169. stationID: station.id,
  170. address: station.snapshot.Address,
  171. stationName: station.snapshot.StationName
  172. }));
  173. // Update the store with extracted information
  174. extractedInfoStore.getState().setExtractedInfo(extractedInfo);
  175. return extractedInfo;
  176. };
  177. // const fetchRecentBookings = async () => {
  178. // //fetch user's all reservations
  179. // const reservationResponse = await chargeStationService.fetchReservationHistories();
  180. // if (!reservationResponse) {
  181. // console.log('No reservation data returned');
  182. // return [];
  183. // }
  184. // // Find the two closest reservations
  185. // const now = new Date();
  186. // // PLAN A: I filter out reservations that has no station ID in the snapshot.
  187. // const closestReservations = reservationResponse
  188. // .filter((reservation) => {
  189. // const snapshot = JSON.parse(reservation.snapshot);
  190. // return snapshot.stationID !== undefined && snapshot.stationID !== null;
  191. // })
  192. // .sort((a, b) => {
  193. // const diffA = Math.abs(new Date(a.end_time) - now);
  194. // const diffB = Math.abs(new Date(b.end_time) - now);
  195. // return diffA - diffB;
  196. // })
  197. // .slice(0, 2);
  198. // //and the two closest reservation's station ID
  199. // const stationIds = closestReservations.map((reservation) => {
  200. // const snapshot = JSON.parse(reservation.snapshot);
  201. // return snapshot.stationID;
  202. // });
  203. // // .filter((id) => id !== undefined);
  204. // if (stationIds.length === 0) {
  205. // console.log('No valid station IDs found');
  206. // return [];
  207. // }
  208. // const stationsResponse = await chargeStationService.fetchAllChargeStations();
  209. // if (!stationsResponse) {
  210. // console.log('No charge station data returned');
  211. // return [];
  212. // }
  213. // if (stationIds === undefined || stationIds.length === 0) {
  214. // return [];
  215. // }
  216. // const filteredStations = stationIds.map((id) => stationsResponse.find((station) => station.id === id));
  217. // const extractedInfo = filteredStations.map((station) => ({
  218. // stationID: station.id,
  219. // address: station.snapshot.Address,
  220. // stationName: station.snapshot.StationName
  221. // }));
  222. // extractedInfoStore.getState().setExtractedInfo(extractedInfo);
  223. // return extractedInfo;
  224. // };
  225. const RecentlyBookedScrollView = () => {
  226. const {
  227. data: extractedInfo,
  228. isLoading,
  229. error
  230. } = useQuery('recentBookings', fetchRecentBookings, {
  231. staleTime: 5 * 60 * 1000, // 5 minutes
  232. cacheTime: 10 * 60 * 1000 // 10 minutes
  233. });
  234. const memoizedExtractedInfo = useMemo(() => extractedInfo || [], [extractedInfo]);
  235. if (error) {
  236. console.error('Error fetching data:', error);
  237. }
  238. return (
  239. <View className="py-6 flex-column">
  240. <Text
  241. style={{
  242. fontWeight: 400,
  243. fontSize: 16,
  244. color: '#222222',
  245. marginBottom: '5%'
  246. }}
  247. >
  248. 近期預約過
  249. </Text>
  250. {isLoading ? (
  251. <ActivityIndicator color="#34657b" />
  252. ) : (
  253. <View className="">
  254. {memoizedExtractedInfo.map((item, index) => (
  255. <RecentBookedRowItems
  256. key={`${item.stationName}+${index}`}
  257. chargingStationName={item.stationName}
  258. chargingStationAddress={item.address}
  259. chargeStationID={item.stationID}
  260. />
  261. ))}
  262. </View>
  263. )}
  264. </View>
  265. );
  266. };
  267. const RecentlyBookedScrollViewWithQueryClient = () => (
  268. <QueryClientProvider client={queryClient}>
  269. <RecentlyBookedScrollView />
  270. </QueryClientProvider>
  271. );
  272. export default RecentlyBookedScrollViewWithQueryClient;