notificationTabViewComponent.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. // component/global/notificationTabViewComponent.tsx
  2. //the size of the TabView will follow its parent-container's size.
  3. import * as React from 'react';
  4. import { View, Text, useWindowDimensions, StyleSheet, ScrollView, ActivityIndicator, Pressable } from 'react-native';
  5. import { TabView, SceneMap, TabBar } from 'react-native-tab-view';
  6. import { formatToChineseDateTime } from '../../util/lib';
  7. import { useCallback, useMemo, useEffect, useState } from 'react';
  8. import { notificationStorage } from '../notificationStorage';
  9. import { router, useFocusEffect } from 'expo-router';
  10. import { useTranslation } from '../../util/hooks/useTranslation';
  11. export interface TabItem {
  12. title: string;
  13. description: string;
  14. date: string;
  15. }
  16. interface TabViewComponentProps {
  17. titles: string[];
  18. reservationAfter2025: any;
  19. passingThisPromotionToBell: any;
  20. }
  21. const NotificationRow = ({
  22. promotionObj,
  23. index,
  24. viewedNotifications
  25. }: {
  26. promotionObj: any;
  27. index: number;
  28. viewedNotifications: any;
  29. }) => {
  30. const isViewed = viewedNotifications.some(
  31. (notification: any) => notification.id === promotionObj.id && notification.type === 'promotion'
  32. );
  33. const handlePress = async () => {
  34. // Mark promotion as viewed
  35. await notificationStorage.markAsViewed(promotionObj.id, 'promotion');
  36. router.push({
  37. pathname: '/notificationDetailPage',
  38. params: { promotion: JSON.stringify(promotionObj) }
  39. });
  40. };
  41. return (
  42. <Pressable
  43. onPress={handlePress}
  44. style={{ opacity: isViewed ? 0.5 : 1 }}
  45. className={`flex flex-col w-full bg-white space-y-2 p-2 ${index % 2 === 0 ? 'bg-[#e7f2f8]' : 'bg-white'}`}
  46. >
  47. <View className="flex flex-row justify-between">
  48. <Text className="font-[500] text-base">{promotionObj.title}</Text>
  49. <Text>{formatToChineseDateTime(promotionObj.createdAt)}</Text>
  50. </View>
  51. <View>
  52. <Text numberOfLines={2} ellipsizeMode="tail" className="text-sm">
  53. {promotionObj.text}
  54. </Text>
  55. </View>
  56. </Pressable>
  57. );
  58. };
  59. const ReservationRow = ({
  60. reservationObj,
  61. index,
  62. viewedNotifications
  63. }: {
  64. reservationObj: any;
  65. index: number;
  66. viewedNotifications: any;
  67. }) => {
  68. const { t } = useTranslation();
  69. const isViewed = viewedNotifications.some(
  70. (notification: any) => notification?.id === reservationObj?.id && notification.type === 'reservation'
  71. );
  72. const handlePress = async () => {
  73. await notificationStorage.markAsViewed(reservationObj.id, 'reservation');
  74. if (reservationObj.status.id === '7') {
  75. router.push({ pathname: '/chargingPage' });
  76. } else {
  77. router.push({ pathname: '/chargingDetailPage', params: { promotion: JSON.stringify(reservationObj) } });
  78. }
  79. };
  80. const title =
  81. reservationObj.status.id === '8' || reservationObj.status.id === '13'
  82. ? t('notifications.chargingIn.charging_completed')
  83. : reservationObj.status.id === '7'
  84. ? t('notifications.chargingIn.charging_in_progress')
  85. : '';
  86. const text =
  87. reservationObj.status.id === '8' || reservationObj.status.id === '13'
  88. ? t('notifications.chargingIn.charging_completed_message')
  89. : reservationObj.status.id === '7'
  90. ? t('notifications.chargingIn.charging_in_progress_message', { soc: reservationObj.Soc })
  91. : '';
  92. return (
  93. <Pressable
  94. onPress={handlePress}
  95. style={{ opacity: isViewed ? 0.5 : 1 }}
  96. className={`flex flex-col w-full bg-white space-y-2 p-2 ${index % 2 === 0 ? 'bg-[#e7f2f8]' : 'bg-white'}`}
  97. >
  98. <View className="flex flex-row justify-between">
  99. <Text className={`font-[400] text-base ${reservationObj.status.id === '7' ? 'font-[700]' : ''}`}>
  100. {title}
  101. </Text>
  102. <Text>{formatToChineseDateTime(reservationObj.createdAt)}</Text>
  103. </View>
  104. <View>
  105. <Text numberOfLines={2} ellipsizeMode="tail" className="text-sm">
  106. {text}
  107. </Text>
  108. </View>
  109. </Pressable>
  110. );
  111. };
  112. const FirstRoute = ({ loading, reservationAfter2025 }: { loading: boolean; reservationAfter2025: any }) => {
  113. const { t } = useTranslation();
  114. const [viewedNotifications, setViewedNotifications] = useState([]);
  115. useFocusEffect(
  116. useCallback(() => {
  117. const getViewedNotifications = async () => {
  118. const notifications = await notificationStorage.getViewedNotifications();
  119. setViewedNotifications(notifications as any);
  120. };
  121. getViewedNotifications();
  122. }, [])
  123. );
  124. return (
  125. <ScrollView
  126. style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}
  127. contentContainerStyle={{ paddingBottom: 150 }}
  128. >
  129. <View className="flex-1 flex-col">
  130. {loading ? (
  131. <View className="items-center justify-center">
  132. <ActivityIndicator />
  133. </View>
  134. ) : (
  135. <View>
  136. <View>
  137. {reservationAfter2025.length === 0 ? (
  138. <Text className="pl-4">{t('notifications.chargingIn.no_charging_info')}</Text>
  139. ) : (
  140. reservationAfter2025
  141. .sort(
  142. (a: any, b: any) =>
  143. new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  144. )
  145. .filter(
  146. (reservationObj: any) =>
  147. reservationObj.status.id === '7' ||
  148. reservationObj.status.id === '8' ||
  149. reservationObj.status.id === '13'
  150. )
  151. .slice(0, 30)
  152. .map((reservationObj: any, index: any) => (
  153. <ReservationRow
  154. reservationObj={reservationObj}
  155. index={index}
  156. key={reservationObj.id}
  157. viewedNotifications={viewedNotifications}
  158. />
  159. ))
  160. )}
  161. </View>
  162. </View>
  163. )}
  164. </View>
  165. </ScrollView>
  166. );
  167. };
  168. const SecondRoute = ({ promotions, loading }: { promotions: TabItem[]; loading: boolean }) => {
  169. const { t } = useTranslation();
  170. const [viewedNotifications, setViewedNotifications] = useState([]);
  171. useFocusEffect(
  172. useCallback(() => {
  173. const getViewedNotifications = async () => {
  174. const notifications = await notificationStorage.getViewedNotifications();
  175. setViewedNotifications(notifications as any);
  176. };
  177. getViewedNotifications();
  178. }, [])
  179. );
  180. return (
  181. <ScrollView
  182. style={{ flex: 1, backgroundColor: 'white', marginTop: 14 }}
  183. contentContainerStyle={{ paddingBottom: 120 }}
  184. >
  185. <View className="flex-1 flex-col">
  186. {loading ? (
  187. <View className="items-center justify-center">
  188. <ActivityIndicator />
  189. </View>
  190. ) : (
  191. <View>
  192. <View>
  193. {promotions.length === 0 ? (
  194. <Text className="pl-4">{t('notifications.no_promotions')}</Text>
  195. ) : (
  196. promotions
  197. .sort(
  198. (a: any, b: any) =>
  199. new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  200. )
  201. .slice(0, 30)
  202. .map((promotionObj: any, index: any) => (
  203. <NotificationRow
  204. promotionObj={promotionObj}
  205. index={index}
  206. key={promotionObj.id}
  207. viewedNotifications={viewedNotifications}
  208. />
  209. ))
  210. )}
  211. </View>
  212. </View>
  213. )}
  214. </View>
  215. </ScrollView>
  216. );
  217. };
  218. const NotificationTabView: React.FC<TabViewComponentProps> = ({
  219. titles,
  220. reservationAfter2025,
  221. passingThisPromotionToBell
  222. }) => {
  223. const layout = useWindowDimensions();
  224. const [loading, setLoading] = useState(true);
  225. const renderScene = useCallback(
  226. ({ route }: { route: any }) => {
  227. switch (route.key) {
  228. case 'firstRoute':
  229. return <FirstRoute loading={loading} reservationAfter2025={reservationAfter2025} />;
  230. case 'secondRoute':
  231. return <SecondRoute promotions={passingThisPromotionToBell} loading={loading} />;
  232. default:
  233. return null;
  234. }
  235. },
  236. [reservationAfter2025, passingThisPromotionToBell]
  237. );
  238. useEffect(() => {
  239. setLoading(false)
  240. }, [reservationAfter2025, passingThisPromotionToBell]);
  241. const [routes] = React.useState([
  242. { key: 'firstRoute', title: titles[0] },
  243. { key: 'secondRoute', title: titles[1] }
  244. ]);
  245. const [index, setIndex] = React.useState(0);
  246. const renderTabBar = (props: any) => (
  247. <TabBar
  248. {...props}
  249. indicatorStyle={{
  250. backgroundColor: '#025c72'
  251. }}
  252. style={{
  253. backgroundColor: 'white',
  254. borderColor: '#DBE4E8',
  255. elevation: 0,
  256. marginHorizontal: 15,
  257. borderBottomWidth: 0.5
  258. }}
  259. />
  260. );
  261. return (
  262. <TabView
  263. navigationState={{ index, routes }}
  264. renderTabBar={renderTabBar}
  265. renderScene={renderScene}
  266. onIndexChange={setIndex}
  267. initialLayout={{ width: layout.width }}
  268. commonOptions={{
  269. label: ({ route, focused }) => (
  270. <Text
  271. style={{
  272. color: focused ? '#025c72' : '#888888',
  273. fontWeight: focused ? '900' : 'thin',
  274. fontSize: 20
  275. }}
  276. >
  277. {route.title}
  278. </Text>
  279. )
  280. }}
  281. lazy
  282. />
  283. );
  284. };
  285. export default NotificationTabView;
  286. const styles = StyleSheet.create({
  287. container: { flexDirection: 'row' },
  288. image: { width: 100, height: 100, margin: 15, borderRadius: 10 },
  289. textContainer: { flexDirection: 'column', gap: 8, marginTop: 20 },
  290. floatingButton: {
  291. elevation: 5,
  292. shadowColor: '#000',
  293. shadowOffset: { width: 0, height: 2 },
  294. shadowOpacity: 0.25,
  295. shadowRadius: 3.84
  296. }
  297. });