chargingPage.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import { View, Text, ActivityIndicator, AppState, ScrollView, RefreshControl } from 'react-native';
  2. import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
  3. import ChargingPageComponent from '../../../../component/chargingPage/chargingPageComponent';
  4. import { chargeStationService } from '../../../../service/chargeStationService';
  5. import ChargingPenaltyPageComponent from '../../../../component/chargingPage/chargingPenaltyComponent';
  6. import NoChargingOngoingPageComponent from '../../../../component/chargingPage/noChargingOngoingPageComponent';
  7. import { useFocusEffect } from 'expo-router';
  8. import ChargingHurryUpPageComponent from '../../../../component/chargingPage/chargingHurryUpPageComponent';
  9. import FutureReservationPageComponent from '../../../../component/chargingPage/futureReservationPageComponent';
  10. import ChargingFinishPageComponent from '../../../../component/chargingPage/chargingFinishPageComponent';
  11. import { AuthContext } from '../../../../context/AuthProvider';
  12. const fakeData = [
  13. {
  14. createdAt: '2024-08-26T10:15:30.123Z',
  15. updatedAt: '2024-08-26T10:15:30.123Z',
  16. id: 'a1b2c3d4-e5f6-7890-abcd-1234567890ab',
  17. book_time: '2024-08-26T11:00:00.000Z',
  18. end_time: '2024-08-26T13:00:00.000Z',
  19. actual_start_time: '2024-08-26T11:05:23.456Z',
  20. actual_end_time: '2024-08-26T12:01:23.456Z',
  21. total_power: null,
  22. type: 'charging',
  23. penalty_fee: null,
  24. total_fee: 150,
  25. snapshot:
  26. '{"type":"charging","stationID":"2408261122116801000","connector":"101708260802474001","user":"user123-456-789","book_time":"2024-08-26T11:00:00.000Z","end_time":"2024-08-26T13:00:00.000Z","total_fee":150,"car":"car123-456-789"}',
  27. remark: null,
  28. promotion_name: null,
  29. promotion_msg: null,
  30. format_order_id: '730998640260820241015301234',
  31. Soc: 45,
  32. connector: {
  33. createdAt: '2024-08-01T09:00:00.000Z',
  34. updatedAt: '2024-08-26T10:00:00.000Z',
  35. id: 'conn123-456-789',
  36. ConnectorID: '101708260802474001',
  37. VoltageLowerLimits: 380,
  38. ConnectorType: 4,
  39. VoltageUpperLimits: 1000,
  40. NationalStandard: 2015,
  41. ConnectorName: 'A枪',
  42. Current: 250,
  43. Power: 180000,
  44. Status: 'charging',
  45. ParkStatus: 'occupied',
  46. LockStatus: 'locked',
  47. EquipmentID: {
  48. createdAt: '2024-08-01T09:00:00.000Z',
  49. updatedAt: '2024-08-26T10:00:00.000Z',
  50. id: 'equip123-456-789',
  51. EquipmentID: '1017082608024740',
  52. EquipmentLat: 22.310709,
  53. EquipmentLng: 114.22301,
  54. EquipmentType: 1,
  55. EquipmentName: '1桩',
  56. EquipmentModel: 'GCE-D180-Y-F',
  57. Power: 180000,
  58. StationID: {
  59. createdAt: '2024-08-01T09:00:00.000Z',
  60. updatedAt: '2024-08-26T10:00:00.000Z',
  61. id: '2408261122116801000',
  62. snapshot:
  63. '{"StationName":"CRAZY CHARGE (Example Street)","Address":"123 Example Street, Hong Kong"}',
  64. self_active_status_fk: 'active',
  65. business_hours_fk: '24/7',
  66. qr_code: 'qr_code_data',
  67. image: 'station_image_url',
  68. price: 3
  69. }
  70. }
  71. },
  72. user: {
  73. createdAt: '2024-01-01T00:00:00.000Z',
  74. updatedAt: '2024-08-26T10:00:00.000Z',
  75. id: 'user123-456-789',
  76. firstname: 'John',
  77. lastname: 'Doe',
  78. nickname: 'JD',
  79. email: 'john.doe@example.com',
  80. password: '$2b$10$hashedpasswordexample',
  81. phone: 12345678,
  82. ic_card: '0000001234567890',
  83. wallet: 5000,
  84. icon_url: 'user_icon_url',
  85. remark: null,
  86. address: '456 User Street, Hong Kong',
  87. status_fk: '1',
  88. gender: 'man',
  89. birthday: '1990/01/01',
  90. ic_car_id: null
  91. },
  92. status: {
  93. id: '2',
  94. createdAt: '2024-07-16T14:58:40.630Z',
  95. updatedAt: '2024-07-16T14:58:40.630Z',
  96. description: 'charging'
  97. },
  98. car: {
  99. createdAt: '2024-01-01T00:00:00.000Z',
  100. updatedAt: '2024-08-26T10:00:00.000Z',
  101. id: 'car123-456-789',
  102. license_plate: 'AB1234',
  103. car_brand: {
  104. createdAt: '2024-01-01T00:00:00.000Z',
  105. updatedAt: '2024-01-01T00:00:00.000Z',
  106. id: 'brand123-456-789',
  107. name: 'BWD',
  108. img_url: 'tesla_logo_url'
  109. },
  110. car_type: {
  111. createdAt: '2024-01-01T00:00:00.000Z',
  112. updatedAt: '2024-01-01T00:00:00.000Z',
  113. id: 'type123-456-789',
  114. name: 'Model 3',
  115. capacitance: 75,
  116. capacitance_unit: 'kwh',
  117. type_image_url: 'model3_image_url'
  118. }
  119. }
  120. }
  121. ];
  122. //***********************************************************************
  123. //而家start time & end time 一樣就當Walk in, walkin 會改左d字眼在ChargingHurryUpPageComponent
  124. //如果以後scan qr code可選時間, ChargingHurryUp display 字眼個logic 就要改
  125. //***********************************************************************
  126. const ChargingPage = () => {
  127. const [data, setData] = useState();
  128. const [isLoading, setIsLoading] = useState(false);
  129. const [currentStatus, setCurrentStatus] = useState('');
  130. const intervalRef = useRef(null);
  131. const lastUpdateTimeRef = useRef(Date.now());
  132. const { user } = useContext(AuthContext);
  133. const [refreshing, setRefreshing] = useState(false);
  134. const [refetchTrigger, setRefetchTrigger] = useState(0);
  135. const fetchReservationData = useCallback(async () => {
  136. setIsLoading(true);
  137. try {
  138. const now = new Date();
  139. const response = await chargeStationService.fetchReservationHistories();
  140. lastUpdateTimeRef.current = Date.now();
  141. if (Object.keys(response).length === 0) {
  142. console.log('no reservation data');
  143. setCurrentStatus('noReservation');
  144. } else {
  145. // Check for recently finished reservations
  146. const finishedReservation = response.find((r) => {
  147. if (r.status.id === '8' && r.actual_end_time) {
  148. const endTime = new Date(r.actual_end_time);
  149. const timeDifference = now.getTime() - endTime.getTime();
  150. return timeDifference <= 1 * 60 * 1000; // Within 1 minutes
  151. }
  152. return false;
  153. });
  154. if (finishedReservation) {
  155. console.log('now', now);
  156. console.log('Finished reservation found:', finishedReservation);
  157. setCurrentStatus('finishReservation');
  158. setData(finishedReservation);
  159. } else {
  160. // Existing checks for other reservation types
  161. const penaltyReservation = response.filter(
  162. (r) => r.actual_start_time != null && r.actual_end_time != null && r.connector.Status === 7
  163. );
  164. if (penaltyReservation.length > 0) {
  165. setCurrentStatus('penaltyReservation');
  166. setData(penaltyReservation);
  167. } else {
  168. // Check for ongoing reservations (已插槍的預約)
  169. const onGoingReservation = response.filter(
  170. (r) => r.status.id === '7' && r.actual_end_time == null
  171. );
  172. // console.log('onGoingReservation', onGoingReservation);
  173. if (onGoingReservation.length > 0) {
  174. setCurrentStatus('onGoingReservation');
  175. setData(onGoingReservation);
  176. } else {
  177. // Check for recently passed reservations (仍未開始插槍充電的預約)
  178. const recentlyPassedReservations = response.filter((r) => {
  179. const bookTime = new Date(r.book_time);
  180. const fifteenMinutesAfterBookTime = new Date(bookTime.getTime() + 15 * 60 * 1000);
  181. const isWithin15MinutesAndStatus6 =
  182. now > bookTime && now <= fifteenMinutesAfterBookTime && r.status.id === '6';
  183. return isWithin15MinutesAndStatus6;
  184. });
  185. if (recentlyPassedReservations.length > 0) {
  186. console.log(' i am recentlyPassedReservation', recentlyPassedReservations);
  187. setCurrentStatus('recentlyPassedReservations');
  188. setData(recentlyPassedReservations);
  189. } else {
  190. const futureReservation = response.filter((r) => {
  191. const bookTime = new Date(r.book_time);
  192. const fifteenMinutesAfterBookTime = new Date(bookTime.getTime() + 15 * 60 * 1000);
  193. return now < bookTime || (now > bookTime && now <= fifteenMinutesAfterBookTime);
  194. });
  195. if (futureReservation.length > 0) {
  196. futureReservation.sort((a, b) => new Date(a.end_time) - new Date(b.end_time));
  197. const closestReservation = futureReservation[0];
  198. setCurrentStatus('futureReservation');
  199. setData(closestReservation);
  200. } else {
  201. //if you are here, it means there are no future reservations (but there are past reservations)
  202. setCurrentStatus('noReservation');
  203. }
  204. }
  205. }
  206. }
  207. }
  208. }
  209. } catch (error) {
  210. console.error('Error fetching reservation histories:', error);
  211. } finally {
  212. setIsLoading(false);
  213. }
  214. }, []);
  215. const onRefresh = useCallback(() => {
  216. setRefreshing(true);
  217. setRefetchTrigger((prev) => prev + 1);
  218. // Simulate a delay to show the refresh indicator
  219. setTimeout(() => {
  220. setRefreshing(false);
  221. fetchReservationData().finally(() => {
  222. setRefreshing(false);
  223. });
  224. }, 1000);
  225. }, []);
  226. const checkAndUpdateData = useCallback(() => {
  227. const currentTime = Date.now();
  228. const timeSinceLastUpdate = currentTime - lastUpdateTimeRef.current;
  229. if (timeSinceLastUpdate > 60000) {
  230. // If more than a minute has passed
  231. fetchReservationData();
  232. }
  233. }, [fetchReservationData]);
  234. useEffect(() => {
  235. const subscription = AppState.addEventListener('change', (nextAppState) => {
  236. if (nextAppState === 'active') {
  237. checkAndUpdateData();
  238. }
  239. });
  240. return () => {
  241. subscription.remove();
  242. };
  243. }, [checkAndUpdateData]);
  244. useEffect(() => {
  245. fetchReservationData();
  246. intervalRef.current = setInterval(fetchReservationData, 60000);
  247. return () => {
  248. if (intervalRef.current) {
  249. clearInterval(intervalRef.current);
  250. }
  251. };
  252. }, [fetchReservationData]);
  253. useFocusEffect(
  254. useCallback(() => {
  255. let isActive = true;
  256. const fetchData = async () => {
  257. try {
  258. await fetchReservationData();
  259. } catch (error) {
  260. console.error('Error in useFocusEffect:', error);
  261. }
  262. };
  263. fetchData();
  264. // Cleanup function
  265. return () => {
  266. isActive = false;
  267. // Any additional cleanup logic can go here
  268. };
  269. }, [fetchReservationData])
  270. );
  271. if (isLoading) {
  272. return (
  273. <View className="flex-1 justify-center bg-white">
  274. <ActivityIndicator color="#34657b" size="large" />
  275. </View>
  276. );
  277. }
  278. return (
  279. <ScrollView
  280. className="bg-white flex-1"
  281. refreshControl={
  282. <RefreshControl
  283. refreshing={refreshing}
  284. onRefresh={onRefresh}
  285. colors={['#34657b']} // Android
  286. tintColor="#34657b" // iOS
  287. />
  288. }
  289. >
  290. <View className="flex-1">
  291. {currentStatus === 'noReservation' && <NoChargingOngoingPageComponent />}
  292. {currentStatus === 'penaltyReservation' && <ChargingPenaltyPageComponent data={data} />}
  293. {currentStatus === 'onGoingReservation' && <ChargingPageComponent data={data} />}
  294. {currentStatus === 'recentlyPassedReservations' && <ChargingHurryUpPageComponent data={data} />}
  295. {currentStatus === 'futureReservation' && <FutureReservationPageComponent data={data} />}
  296. {currentStatus === 'finishReservation' && <ChargingFinishPageComponent data={data} />}
  297. </View>
  298. </ScrollView>
  299. );
  300. };
  301. export default ChargingPage;