chargingPenaltyComponent.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { View, Text, ScrollView, StyleSheet, Image, ActivityIndicator } from 'react-native';
  2. import React, { useEffect, useState } from 'react';
  3. import { SafeAreaView } from 'react-native-safe-area-context';
  4. import NormalButton from '../global/normal_button';
  5. import ExpectedFeeBox from '../global/expectedFeeBox';
  6. import OtherInformationBox from '../global/otherInformationBox';
  7. import { BatteryLogoSvg, TimeClockLogoSvg, WarningTriangleLogoSvg } from '../global/SVG';
  8. import { router } from 'expo-router';
  9. const IdlingGreyBox = () => {
  10. return (
  11. <View className="flex-1 ">
  12. <View className="p-4 bg-[#5a5c7c] w-full rounded-t-xl flex-row items-center ">
  13. <WarningTriangleLogoSvg />
  14. <Text className="text-white font-black text-xl pl-4">待機中..</Text>
  15. </View>
  16. <View className="bg-[#555776] rounded-b-xl p-6 space-y-1">
  17. <View className="flex-row items-center">
  18. <View className="w-[40%] ">
  19. <Text className="text-base text-white">剩餘待機時間:</Text>
  20. </View>
  21. <View className="w-[60%]">
  22. <Text style={styles.warningBoldText}>01:55</Text>
  23. </View>
  24. </View>
  25. <View className="flex-row items-center ">
  26. <View>
  27. <Text className="text-sm text-white">請於下方確認完成充電以免進入罰款時間</Text>
  28. </View>
  29. </View>
  30. </View>
  31. </View>
  32. );
  33. };
  34. const ChargingPenaltyPageComponent = ({ data }) => {
  35. const [isIdling, setIsIdling] = React.useState<boolean>(false);
  36. const reservationData = Array.isArray(data) ? data[0] : data;
  37. const [loading, setLoading] = useState(false);
  38. //用來計充電歷時 //用來計充電歷時 //用來計充電歷時 //用來計充電歷時 //用來計充電歷時
  39. const [timeSince, setTimeSince] = useState<string>('');
  40. useEffect(() => {
  41. const updateTimeSince = () => {
  42. if (reservationData && reservationData.actual_start_time) {
  43. setTimeSince(timeSinceBooking(reservationData.actual_start_time) || '計算中...');
  44. } else {
  45. setTimeSince('計算中...');
  46. }
  47. };
  48. updateTimeSince();
  49. // Update every minute
  50. const intervalId = setInterval(updateTimeSince, 60000);
  51. // Cleanup interval on component unmount
  52. return () => clearInterval(intervalId);
  53. }, [reservationData]);
  54. function timeSinceBooking(timeString) {
  55. if (timeString) {
  56. const startTime = new Date(timeString);
  57. const now = new Date();
  58. const diffInMilliseconds = now - startTime;
  59. const diffInMinutes = Math.floor(diffInMilliseconds / (1000 * 60));
  60. if (diffInMinutes < 1) {
  61. return '< 1 m';
  62. } else {
  63. return `${diffInMinutes} m${diffInMinutes !== 1 ? 's' : ''}`;
  64. }
  65. }
  66. }
  67. //用來計充電歷時 //用來計充電歷時 //用來計充電歷時 //用來計充電歷時 //用來計充電歷時
  68. const PenaltyRedBox = () => {
  69. const penalty_time = reservationData.actual_end_time;
  70. return (
  71. <>
  72. <View className="bg-[#bb2d12] flex-1 p-4 rounded-t-xl flex-row items-center px-[5%]">
  73. <WarningTriangleLogoSvg />
  74. <Text className="text-white font-black text-xl pl-4">罰款中</Text>
  75. </View>
  76. <View className="bg-[#b12a11] flex-column rounded-b-xl p-6">
  77. <View className="flex-1 flex-row items-center pb-2">
  78. <View className="w-[40%]">
  79. <Text style={styles.warningLightText}>罰款時間歷時:</Text>
  80. </View>
  81. <View className="w-[60%]">
  82. <Text style={styles.warningBoldText}>{penaltyTime}</Text>
  83. </View>
  84. </View>
  85. <View className="flex-1 flex-row items-center pb-2">
  86. <View className="w-[40%]">
  87. <Text style={styles.warningLightText}>累積:</Text>
  88. </View>
  89. <View className="w-[60%]">
  90. <Text style={styles.warningBoldText}>01:55</Text>
  91. </View>
  92. </View>
  93. <View className="flex-1 flex-row items-center pb-2">
  94. <View>
  95. <Text style={styles.warningLightText}>請於下方確認完成充電以解鎖</Text>
  96. </View>
  97. </View>
  98. </View>
  99. </>
  100. );
  101. };
  102. //用來計算penalty
  103. const [penaltyTime, setPenaltyTime] = useState<string>('');
  104. useEffect(() => {
  105. const updatePenaltyTime = () => {
  106. if (reservationData.actual_end_time) {
  107. setPenaltyTime(calculatePenaltyTime(reservationData.actual_end_time) || '計算中...');
  108. } else {
  109. setPenaltyTime('尚未結束充電');
  110. }
  111. };
  112. updatePenaltyTime();
  113. // Update every 30 seconds
  114. const intervalId = setInterval(updatePenaltyTime, 30000);
  115. // Cleanup interval on component unmount
  116. return () => clearInterval(intervalId);
  117. }, [reservationData.actual_end_time]);
  118. function calculatePenaltyTime(endTimeString: any) {
  119. if (endTimeString) {
  120. const endTime = new Date(endTimeString);
  121. const graceEndTime = new Date(endTime.getTime() + 15 * 60000); // Add 15 minutes
  122. const now = new Date();
  123. const diffInMilliseconds = now - graceEndTime;
  124. const diffInMinutes = Math.floor(diffInMilliseconds / (1000 * 60));
  125. if (diffInMinutes < 0) {
  126. return '0 m'; // No penalty yet
  127. } else if (diffInMinutes === 0) {
  128. return '< 1 m';
  129. } else {
  130. return `${diffInMinutes} m ${diffInMinutes !== 1 ? 's' : ''}`;
  131. }
  132. }
  133. }
  134. if (loading) {
  135. return (
  136. <SafeAreaView style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
  137. <ActivityIndicator size="large" />
  138. <Text>Loading...</Text>
  139. </SafeAreaView>
  140. );
  141. }
  142. return (
  143. <SafeAreaView className="flex-1 bg-white" edges={['top', 'left', 'right']}>
  144. <ScrollView className="flex-1" showsVerticalScrollIndicator={false}>
  145. <View className="mx-[5%]">
  146. <View className="my-4">
  147. <Text className="text-xl" style={styles.greenColor}>
  148. 已完成充電
  149. </Text>
  150. <Text className="text-2xl">
  151. {reservationData.car.car_brand.name} {reservationData.car.car_type.name}
  152. </Text>
  153. </View>
  154. <View className="flex-row items-center ">
  155. <View className="w-[50%] items-center justify-center">
  156. <Image
  157. source={require('../../assets/car.png')}
  158. resizeMode="contain"
  159. style={{ height: 127, width: 220 }}
  160. />
  161. </View>
  162. <View className="w-[50%] flex-column items-center ">
  163. <View className="flex-row items-center space-x-4">
  164. <View>
  165. <BatteryLogoSvg />
  166. </View>
  167. <View className="flex-column">
  168. <Text className="text-sm" style={styles.grayColor}>
  169. 已充電
  170. </Text>
  171. <Text style={styles.greenColor} className="text-3xl font-light">
  172. {reservationData.Soc}%
  173. </Text>
  174. </View>
  175. </View>
  176. <View className="flex-row items-center space-x-4">
  177. <View>
  178. <TimeClockLogoSvg />
  179. </View>
  180. <View className="flex-column">
  181. <Text className="text-sm" style={styles.grayColor}>
  182. 充電歷時
  183. </Text>
  184. <View className="flex-row items-end">
  185. <Text style={styles.greenColor} className="text-2xl font-light">
  186. {timeSince}
  187. </Text>
  188. </View>
  189. </View>
  190. </View>
  191. </View>
  192. </View>
  193. {isIdling ? (
  194. <View className=" flex-column py-3">
  195. <IdlingGreyBox />
  196. </View>
  197. ) : (
  198. <View className="flex-column py-3">
  199. <PenaltyRedBox />
  200. </View>
  201. )}
  202. <View>
  203. <NormalButton
  204. onPress={() => {
  205. router.push('chargingPage');
  206. }}
  207. title={
  208. <Text className="text-xl text-white " style={{ fontWeight: 900 }}>
  209. 完成充電
  210. </Text>
  211. }
  212. />
  213. </View>
  214. <ExpectedFeeBox extendedStyle={{ marginTop: 12, padding: 12 }} price={reservationData.total_fee} />
  215. <OtherInformationBox
  216. extendedStyle={{ marginTop: 12, padding: 12 }}
  217. actual_start_time={reservationData.actual_start_time}
  218. />
  219. <View className="mt-2">
  220. <NormalButton
  221. title={<Text className="text-white text-xl font-semibold">觀看閒置狀態頁面</Text>}
  222. onPress={() => setIsIdling((prevState) => !prevState)}
  223. />
  224. </View>
  225. </View>
  226. </ScrollView>
  227. </SafeAreaView>
  228. );
  229. };
  230. export default ChargingPenaltyPageComponent;
  231. const styles = StyleSheet.create({
  232. grayColor: {
  233. color: '#888888'
  234. },
  235. greenColor: {
  236. color: '#02677D'
  237. },
  238. text: {
  239. fontWeight: '300',
  240. color: '#000000'
  241. },
  242. warningLightText: {
  243. color: 'white',
  244. fontSize: 15,
  245. fontWeight: '400'
  246. },
  247. warningBoldText: {
  248. color: 'white',
  249. fontSize: 20,
  250. fontWeight: '700'
  251. }
  252. });