penaltyPaymentPageComponent.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import { View, Text, ScrollView, Pressable, StyleSheet, Alert } from 'react-native';
  2. import { SafeAreaView } from 'react-native-safe-area-context';
  3. import { router, useLocalSearchParams, useNavigation } from 'expo-router';
  4. import NormalButton from '../global/normal_button';
  5. import { useEffect, useState } from 'react';
  6. import { chargeStationService } from '../../service/chargeStationService';
  7. import useUserInfoStore from '../../providers/userinfo_store';
  8. import { PreviousPageBlackSvg } from '../global/SVG';
  9. import { useTranslation } from '../../util/hooks/useTranslation';
  10. const PenaltyPaymentPageComponent = () => {
  11. const params = useLocalSearchParams();
  12. const { userID } = useUserInfoStore();
  13. const navigation = useNavigation();
  14. const [leaving_time, setLeavingTime] = useState<string | null>(null)
  15. const { t } = useTranslation();
  16. useEffect(() => {
  17. navigation.setOptions({
  18. gestureEnabled: false
  19. });
  20. }, [navigation]);
  21. useEffect(() => {
  22. chargeStationService.fetchChargingDetails(params.id.toString()).then(res => {
  23. if (res && res[0].charging.length > 0) {
  24. const { date, time } = convertBookingDateTime(res[0].charging[0].leaving_time as string);
  25. setLeavingTime(time);
  26. }
  27. })
  28. }, [params]);
  29. const convertBookingDateTime = (isoDateString: string): { date: string; time: string } => {
  30. const bookingDate = new Date(isoDateString);
  31. // Adjust to local time (+8 hours)
  32. bookingDate.setHours(bookingDate.getHours());
  33. // Format date as "MM-DD"
  34. const date = `${(bookingDate.getMonth() + 1).toString().padStart(2, '0')}-${bookingDate
  35. .getDate()
  36. .toString()
  37. .padStart(2, '0')}`;
  38. // Format time as "HH:mm"
  39. const time = `${bookingDate.getHours().toString().padStart(2, '0')}:${bookingDate
  40. .getMinutes()
  41. .toString()
  42. .padStart(2, '0')}`;
  43. return { date, time };
  44. };
  45. const calculateUserEndTime = (actualEndTimeStr: string, penaltyFee: string): string => {
  46. const actualEndTime = new Date(actualEndTimeStr);
  47. const penaltyMinutes = Math.floor(parseFloat(penaltyFee) / 3) + 15 // $3 per minute
  48. const userEndTime = new Date(actualEndTime.getTime() + penaltyMinutes * 60000); // add minutes
  49. return userEndTime.toISOString();
  50. };
  51. const { date, time } = convertBookingDateTime(params.book_time as string);
  52. const { date: actual_end_date, time: actual_end_time } = convertBookingDateTime(params.actual_end_time as string);
  53. const { time: user_end_time } = convertBookingDateTime(
  54. calculateUserEndTime(params.actual_end_time as string, params.penalty_fee as string)
  55. );
  56. const payload = {
  57. userId: userID,
  58. amount: parseFloat(params.penalty_fee as string),
  59. reservationId: params.id
  60. };
  61. const handlePayment = async () => {
  62. try {
  63. const result = await chargeStationService.payPenalty(payload);
  64. console.log('Payment result:', result);
  65. if (result.status) {
  66. Alert.alert(t('penalty_payment.payment_success'), t('penalty_payment.penalty_paid'), [
  67. { text: t('common.confirm'), onPress: () => router.replace('/mainPage') }
  68. ]);
  69. } else {
  70. Alert.alert(t('penalty_payment.payment_failed'), t('penalty_payment.insufficient_balance'), [
  71. { text: t('common.confirm'), onPress: () => router.push('/(account)/(wallet)/walletPage') }
  72. ]);
  73. }
  74. } catch (error) {
  75. console.error('Payment error:', error);
  76. Alert.alert(t('common.error'), t('penalty_payment.payment_error'));
  77. }
  78. };
  79. return (
  80. <SafeAreaView style={{ flex: 1, backgroundColor: 'white' }} edges={['top', 'left', 'right']}>
  81. <ScrollView className="flex-1" showsVerticalScrollIndicator={false}>
  82. <View style={{ marginTop: 25 }} className="mx-[5%]">
  83. <Pressable
  84. onPress={() => {
  85. if (router.canGoBack()) {
  86. router.back();
  87. } else {
  88. router.replace('/mainPage');
  89. }
  90. }}
  91. >
  92. <PreviousPageBlackSvg />
  93. </Pressable>
  94. <Text className="text-3xl my-8 text-center">{t('penalty_payment.unpaid_penalty_title')}</Text>
  95. </View>
  96. <View className="flex-1 mt-4 mx-[5%]">
  97. <View className="flex-1 flex-row items-center pb-3">
  98. <View className="flex-1 flex-column">
  99. <Text style={styles.grayColor} className="text-base">
  100. {t('penalty_payment.actual_end_time')}
  101. </Text>
  102. <Text style={styles.greenColor} className="text-4xl text-center pt-2">
  103. {actual_end_time}
  104. </Text>
  105. </View>
  106. <View className="flex-1 flex-column">
  107. <Text style={styles.grayColor} className="text-base pl-7">
  108. {t('penalty_payment.actual_charging_end_time')}
  109. </Text>
  110. <Text style={styles.greenColor} className="text-4xl text-center pt-2">
  111. {leaving_time || user_end_time}
  112. </Text>
  113. </View>
  114. </View>
  115. <View className="flex-1 flex-column justify-center space-y-1 pb-3">
  116. <Text style={styles.grayColor} className="text-base">
  117. {t('penalty_payment.charging_date')}
  118. </Text>
  119. <Text style={styles.greenColor} className="text-base">
  120. {date}
  121. </Text>
  122. </View>
  123. {/* <View className="flex-1 flex-column justify-center space-y-1 pb-3">
  124. <Text style={styles.grayColor} className="text-base">
  125. {t('penalty_payment.charging_location')}
  126. </Text>
  127. <Text style={styles.greenColor} className="text-base ">
  128. Crazy Charge(偉業街)
  129. </Text>
  130. </View> */}
  131. <View className="flex-1 flex-column justify-center space-y-1 pb-3">
  132. <Text style={styles.grayColor} className="text-base">
  133. {t('penalty_payment.penalty_amount')}
  134. </Text>
  135. <Text style={styles.greenColor} className="text-lg ">
  136. {params.penalty_fee}
  137. </Text>
  138. </View>
  139. <View className="flex-1 flex-column justify-center space-y-1 pb-3">
  140. <Text style={styles.grayColor} className="text-base">
  141. {t('penalty_payment.order_number')}
  142. </Text>
  143. <Text style={styles.greenColor} className=" ">
  144. {params.format_order_id}
  145. </Text>
  146. </View>
  147. </View>
  148. <View className="border-t mx-4 border-[#CCCCCC]"></View>
  149. <View className="flex-1 mx-[5%] mt-4 space-y-1">
  150. <View className="mt-4">
  151. <NormalButton
  152. title={
  153. <Text
  154. style={{
  155. color: 'white',
  156. fontSize: 16,
  157. fontWeight: '800'
  158. }}
  159. >
  160. {t('penalty_payment.pay_penalty')}
  161. </Text>
  162. }
  163. // onPress={handlePayment}
  164. onPress={() => {
  165. Alert.alert(
  166. t('penalty_payment.payment_confirmation'),
  167. '',
  168. [
  169. { text: t('common.cancel'), style: 'cancel' },
  170. { text: t('common.confirm'), onPress: handlePayment }
  171. ]
  172. );
  173. }}
  174. extendedStyle={{ padding: 24, marginTop: 24 }}
  175. />
  176. </View>
  177. </View>
  178. </ScrollView>
  179. </SafeAreaView>
  180. );
  181. };
  182. export default PenaltyPaymentPageComponent;
  183. const styles = StyleSheet.create({
  184. grayColor: {
  185. color: '#888888'
  186. },
  187. greenColor: {
  188. color: '#02677D'
  189. }
  190. });