chargingRecord.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. //the size of the TabView will follow its parent-container's size.
  2. import * as React from 'react';
  3. import * as Location from 'expo-location';
  4. import {
  5. View,
  6. Text,
  7. useWindowDimensions,
  8. Dimensions,
  9. StyleSheet,
  10. Image,
  11. ImageSourcePropType,
  12. ActivityIndicator,
  13. Pressable
  14. } from 'react-native';
  15. import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
  16. import { FlashList } from '@shopify/flash-list';
  17. import { useEffect, useState } from 'react';
  18. import { calculateDistance } from './distanceCalculator';
  19. import { router } from 'expo-router';
  20. export interface TabItem {
  21. imgURL: ImageSourcePropType;
  22. date: string;
  23. time: string;
  24. chargeStationName: string;
  25. chargeStationAddress: string;
  26. stationLat: string | number;
  27. stationLng: string | number;
  28. distance: string;
  29. format_order_id: string;
  30. actual_total_power?: number;
  31. actual_end_time?: string;
  32. actual_fee?: number;
  33. current_price?: number;
  34. }
  35. interface TabViewComponentProps {
  36. titles: string[];
  37. tabItems: TabItem[];
  38. isLoading?: boolean;
  39. }
  40. const TabViewComponent: React.FC<TabViewComponentProps> = ({
  41. titles,
  42. isLoading,
  43. tabItems,
  44. }) => {
  45. const layout = useWindowDimensions();
  46. const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
  47. useEffect(() => {
  48. const getCurrentLocation = async () => {
  49. let { status } = await Location.requestForegroundPermissionsAsync();
  50. if (status !== 'granted') {
  51. console.error('Permission to access location was denied');
  52. return;
  53. }
  54. let location = await Location.getLastKnownPositionAsync({});
  55. setCurrentLocation(location);
  56. };
  57. getCurrentLocation();
  58. }, []);
  59. const FirstRoute = ({ tabItems, isLoading, currentLocation }: {
  60. tabItems: TabItem[];
  61. isLoading?: boolean;
  62. currentLocation: Location.LocationObject | null
  63. }) => (
  64. <View style={{ flex: 1, backgroundColor: 'white' }}>
  65. {isLoading ? (
  66. <View className="items-center justify-center flex-1">
  67. <ActivityIndicator color="#34657b" />
  68. </View>
  69. ) : (
  70. <FlashList
  71. nestedScrollEnabled={true}
  72. data={tabItems.filter((item) => item?.actual_total_power && item?.actual_total_power !== 0)}
  73. renderItem={({ item }) => <TabItem item={item} currentLocation={currentLocation} />}
  74. keyExtractor={(item, index) => index.toString()}
  75. />
  76. )}
  77. </View>
  78. );
  79. return (
  80. <FirstRoute tabItems={tabItems} isLoading={isLoading} currentLocation={currentLocation} />
  81. );
  82. };
  83. const TabItem = ({ item, currentLocation }: { item: TabItem; currentLocation: Location.LocationObject | null }) => {
  84. const [distance, setDistance] = useState<number | null>(null);
  85. useEffect(() => {
  86. const getDistance = async () => {
  87. if (currentLocation) {
  88. const result = await calculateDistance(
  89. Number(item.stationLat),
  90. Number(item.stationLng),
  91. currentLocation
  92. );
  93. setDistance(result);
  94. }
  95. };
  96. getDistance();
  97. }, [currentLocation, item.stationLat, item.stationLng]);
  98. return (
  99. <Pressable
  100. onPress={() => {
  101. console.log(item.format_order_id);
  102. }}
  103. >
  104. <View style={styles.container}>
  105. <Image style={styles.image} source={item.imgURL} />
  106. <View className="flex flex-col gap-2 mr-2">
  107. <Text
  108. style={{
  109. fontWeight: '700',
  110. color: '#02677D',
  111. fontSize: 16
  112. }}
  113. >
  114. {`${item.date}日 - ${item.time}至${item.actual_end_time}`}
  115. </Text>
  116. <View className="flex flex-row justify-between space-x-2">
  117. <Text
  118. style={{
  119. fontWeight: '400',
  120. fontSize: 14,
  121. color: '#222222'
  122. }}
  123. >
  124. 已充電度數:{' '}
  125. {item.actual_total_power
  126. ? item.actual_total_power % 1 === 0
  127. ? item.actual_total_power
  128. : item.actual_total_power.toFixed(1)
  129. : ''}
  130. </Text>
  131. <Text
  132. style={{
  133. fontWeight: '400',
  134. fontSize: 14,
  135. color: '#222222'
  136. }}
  137. >
  138. 應付金額:{' '}
  139. {item.actual_fee !== undefined && item.actual_fee !== null
  140. ? item.actual_fee <= 0
  141. ? '$0'
  142. : item.actual_fee % 1 === 0
  143. ? `$${item.actual_fee}`
  144. : `$${item.actual_fee.toFixed(1)}`
  145. : ''}
  146. </Text>
  147. </View>
  148. <View className="flex flex-row space-x-2">
  149. <Text
  150. style={{
  151. fontWeight: '400',
  152. fontSize: 14,
  153. color: '#222222'
  154. }}
  155. >
  156. 每度電金額: ${item.current_price}
  157. </Text>
  158. </View>
  159. <Text
  160. style={{
  161. fontWeight: '400',
  162. fontSize: 14,
  163. color: '#888888'
  164. }}
  165. >
  166. {item.chargeStationName}
  167. </Text>
  168. </View>
  169. </View>
  170. </Pressable>
  171. );
  172. };
  173. export default TabViewComponent;
  174. const styles = StyleSheet.create({
  175. container: {
  176. flexDirection: 'row',
  177. width: '100%',
  178. flex: 1,
  179. alignItems: 'center'
  180. },
  181. image: {
  182. width: 100,
  183. height: 100,
  184. margin: 15,
  185. borderRadius: 10
  186. },
  187. textContainer: {
  188. flex: 1,
  189. flexDirection: 'column',
  190. gap: 8,
  191. marginTop: 20,
  192. marginRight: 8 // Add right margin to prevent text from touching the edge
  193. }
  194. });