chargingPenaltyComponent.tsx 12 KB

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