makingBookingPageComponent.tsx 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. import {
  2. View,
  3. Text,
  4. ScrollView,
  5. Pressable,
  6. StyleSheet,
  7. Image,
  8. Dimensions,
  9. ActivityIndicator,
  10. Platform,
  11. Linking,
  12. Alert
  13. } from 'react-native';
  14. import { SafeAreaView } from 'react-native-safe-area-context';
  15. import { router, useLocalSearchParams } from 'expo-router';
  16. import NormalButton from '../global/normal_button';
  17. import { CheckMarkLogoSvg, DirectionLogoSvg, PreviousPageBlackSvg } from '../global/SVG';
  18. import { ChargingStationTabView } from '../global/chargingStationTabView';
  19. import { useEffect, useState } from 'react';
  20. import DropdownSelect from '../global/dropdown_select';
  21. import { chargeStationService } from '../../service/chargeStationService';
  22. import * as Location from 'expo-location';
  23. import { calculateDistance } from '../global/distanceCalculator';
  24. import Modal from 'react-native-modal';
  25. import useUserInfoStore from '../../providers/userinfo_store';
  26. interface AccordionItemProps {
  27. title: string;
  28. children: React.ReactNode;
  29. isOpen: boolean;
  30. onToggle: () => void;
  31. isSelected: boolean;
  32. }
  33. const AccordionItem: React.FC<AccordionItemProps> = ({ title, children, isOpen, onToggle, isSelected }) => (
  34. <View className={`${isSelected ? 'bg-[#e7f5f8]' : 'bg-white'}`}>
  35. <View className="mx-[5%]">
  36. <Pressable onPress={onToggle}>
  37. <Text className={` pt-2 text-lg ${isSelected ? 'text-[#222222]' : 'text-[#888888] '}}`}>{title}</Text>
  38. </Pressable>
  39. {isOpen && <View>{children}</View>}
  40. </View>
  41. </View>
  42. );
  43. const MakingBookingPageComponent = () => {
  44. const [isModalVisible, setModalVisible] = useState(false);
  45. const [routerParams, setRouterParams] = useState(null);
  46. const [openDrawer, setOpenDrawer] = useState<number | null>(1);
  47. const [selectedTime, setSelectedTime] = useState<string>('');
  48. const [availableTimeSlots, setAvailableTimeSlots] = useState<string[]>([]);
  49. const [selectedDrawer, setSelectedDrawer] = useState<number>(1);
  50. const [isLoading, setIsLoading] = useState(true);
  51. const [selectedDate, setSelectedDate] = useState<string>('');
  52. const toggleDrawer = (index: number) => {
  53. setOpenDrawer(openDrawer === index ? null : index);
  54. setSelectedDrawer(index);
  55. };
  56. const [defaultCar, setDefaultCar] = useState(null);
  57. const [isDefaultCarLoading, setIsDefaultCarLoading] = useState(true);
  58. const [availableDate, setAvailableDate] = useState<string[]>([]);
  59. const [car, setCar] = useState([]);
  60. const [selectedCarID, setSelectedCarID] = useState('');
  61. const [selectedChargingGun, setSelectedChargingGun] = useState('');
  62. const [chargingBasedOnWatt, setChargingBasedOnWatt] = useState(true);
  63. const [stopChargingUponBatteryFull, setStopChargingUponBatteryFull] = useState(false);
  64. const [selectedCar, setSelectedCar] = useState('');
  65. const [selectedDuration, setSelectedDuration] = useState('');
  66. const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
  67. const [price, setPrice] = useState(0);
  68. const layoutWidth = screenWidth;
  69. const layoutHeight = screenHeight * 0.32;
  70. const { userID, setUserID } = useUserInfoStore();
  71. const [formattedDates, setFormattedDates] = useState([]);
  72. const params = useLocalSearchParams();
  73. const chargeStationID = params.chargeStationID as string;
  74. const chargeStationName = params.chargeStationName as string;
  75. const chargeStationAddress = params.chargeStationAddress as string;
  76. const chargeStationLat = params.chargeStationLat as string;
  77. const chargeStationLng = params.chargeStationLng as string;
  78. const [selectedWatt, setSelectedWatt] = useState('');
  79. const [availableConnectorDropdownOptions, setAvailableConnectorDropdownOptions] = useState([]);
  80. const [carCapacitance, setCarCapacitance] = useState('');
  81. const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
  82. const [distance, setDistance] = useState<string | null>(null);
  83. const [upcomingReservations, setUpcomingReservation] = useState([]);
  84. const [carLoadingState, setCarLoadingState] = useState(false);
  85. const [isDateLoading, setIsDateLoading] = useState(false);
  86. const [dataResponse, setDataResponse] = useState([]);
  87. //check for unpaid penalties
  88. useEffect(() => {
  89. const checkUnpaidPenalties = async () => {
  90. try {
  91. const reservationHistories = await chargeStationService.fetchReservationHistories();
  92. const unpaidPenalties = reservationHistories.filter(
  93. (reservation) => reservation.penalty_fee > 0 && reservation.penalty_paid_status === false
  94. );
  95. if (unpaidPenalties.length > 0) {
  96. const mostRecentUnpaidReservation = unpaidPenalties.reduce((mostRecent, current) => {
  97. return new Date(mostRecent.created_at) > new Date(current.created_at) ? mostRecent : current;
  98. }, unpaidPenalties[0]);
  99. Alert.alert(
  100. '未付罰款',
  101. '您有未支付的罰款。請先支付罰款後再開始充電。',
  102. [
  103. {
  104. text: '查看詳情',
  105. onPress: () => {
  106. // Navigate to a page showing penalty details
  107. router.push({
  108. pathname: '(auth)/(tabs)/(home)/penaltyPaymentPage',
  109. params: {
  110. book_time: mostRecentUnpaidReservation.book_time,
  111. end_time: mostRecentUnpaidReservation.end_time,
  112. actual_end_time: mostRecentUnpaidReservation.actual_end_time,
  113. penalty_fee: mostRecentUnpaidReservation.penalty_fee,
  114. format_order_id: mostRecentUnpaidReservation.format_order_id,
  115. id: mostRecentUnpaidReservation.id,
  116. stationName:
  117. mostRecentUnpaidReservation.connector.EquipmentID.StationID.snapshot
  118. .StationName,
  119. address:
  120. mostRecentUnpaidReservation.connector.EquipmentID.StationID.snapshot
  121. .Address
  122. }
  123. });
  124. }
  125. },
  126. {
  127. text: '返回',
  128. onPress: () => {
  129. if (router.canGoBack()) {
  130. router.back();
  131. } else {
  132. router.push('/mainPage');
  133. }
  134. }
  135. }
  136. ],
  137. { cancelable: false }
  138. );
  139. }
  140. } catch (error) {
  141. console.error('Error checking unpaid penalties:', error);
  142. // Handle the error appropriately (e.g., show an error message to the user)
  143. }
  144. };
  145. checkUnpaidPenalties();
  146. }, []);
  147. const handleSendingSize = async (watt) => {
  148. try {
  149. setIsLoading(true);
  150. const wattValue = parseInt(watt.split(' ')[0]);
  151. console.log('wattValue', wattValue);
  152. let size: number;
  153. //make the duration based on watt
  154. switch (wattValue) {
  155. case 20:
  156. size = 25;
  157. break;
  158. case 25:
  159. size = 30;
  160. break;
  161. case 30:
  162. size = 40;
  163. break;
  164. case 40:
  165. size = 45;
  166. break;
  167. default:
  168. console.error('Invalid selectedWatt value');
  169. return; // Exit the function if selectedWatt is invalid
  170. }
  171. const response = await chargeStationService.getReservationWithSize(size);
  172. if (response) {
  173. setDataResponse(response);
  174. // console.log('respoasdasdasdnse', response);
  175. const uniqueDates = new Set();
  176. response.forEach((item) => {
  177. item.timeSlot.forEach((slot) => {
  178. uniqueDates.add(slot.date);
  179. });
  180. });
  181. const availableDates = Array.from(uniqueDates).sort();
  182. setAvailableDate(availableDates);
  183. // console.log('formattedDates', formattedDates);
  184. } else {
  185. console.log('No response from getReservationWithSize');
  186. }
  187. } catch (error) {
  188. console.error('Error in handleSendingSize:', error);
  189. // Handle the error appropriately, maybe show an alert to the user
  190. } finally {
  191. setIsLoading(false);
  192. }
  193. };
  194. //get location
  195. useEffect(() => {
  196. const getCurrentLocation = async () => {
  197. let { status } = await Location.requestForegroundPermissionsAsync();
  198. if (status !== 'granted') {
  199. console.error('Permission to access location was denied');
  200. return;
  201. }
  202. let location = await Location.getLastKnownPositionAsync({});
  203. setCurrentLocation(location);
  204. };
  205. getCurrentLocation();
  206. }, []);
  207. useEffect(() => {
  208. const fetchPrice = async () => {
  209. try {
  210. const price = await chargeStationService.fetchChargeStationPrice(chargeStationID);
  211. setPrice(price);
  212. } catch (error) {
  213. console.error('Error fetching price:', error);
  214. }
  215. };
  216. fetchPrice();
  217. }, []);
  218. const connectorIDToLabelMap = {
  219. '101708240502475001': '1',
  220. '101708240502476001': '2',
  221. '101708240502477001': '3',
  222. '101708240502478001': '4',
  223. '101708240502474001': '5',
  224. '101708240502474002': '6'
  225. };
  226. const connectorIDToLabelMapArray = [
  227. { value: '101708240502475001', label: '1' },
  228. { value: '101708240502476001', label: '2' },
  229. { value: '101708240502477001', label: '3' },
  230. { value: '101708240502478001', label: '4' },
  231. { value: '101708240502474001', label: '5' },
  232. { value: '101708240502474002', label: '6' }
  233. ];
  234. const formatDateString = (dateString: string) => {
  235. const [year, month, day] = dateString.split('-');
  236. return `${month}/${day}`;
  237. };
  238. const handleNavigationPress = () => {
  239. const latitude = chargeStationLat;
  240. const longitude = chargeStationLng;
  241. const label = encodeURIComponent(chargeStationName);
  242. // Google Maps App URL
  243. const googleMapsUrl = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;
  244. // Fallback URL for web browser
  245. const webUrl = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`;
  246. Linking.canOpenURL(googleMapsUrl)
  247. .then((supported) => {
  248. if (supported) {
  249. Linking.openURL(googleMapsUrl);
  250. } else {
  251. Linking.openURL(webUrl).catch((err) => {
  252. console.error('An error occurred', err);
  253. Alert.alert(
  254. 'Error',
  255. "Unable to open Google Maps. Please make sure it's installed on your device.",
  256. [{ text: 'OK' }],
  257. { cancelable: false }
  258. );
  259. });
  260. }
  261. })
  262. .catch((err) => console.error('An error occurred', err));
  263. };
  264. const handleDateToTimeSlot = (date: string, connectorId: string) => {
  265. // Find the correct connector object
  266. const connectorData = dataResponse.find((item) => item.connector === connectorId);
  267. if (!connectorData) {
  268. console.error(`No data found for connector ${connectorId}`);
  269. setAvailableTimeSlots([]);
  270. return;
  271. }
  272. // Find the timeSlot object for the selected date
  273. const selectedTimeSlot = connectorData.timeSlot.find((slot) => slot.date === date);
  274. if (!selectedTimeSlot) {
  275. console.error(`No time slots found for date ${date}`);
  276. setAvailableTimeSlots([]);
  277. return;
  278. }
  279. const now = new Date();
  280. const selectedDateObj = new Date(date);
  281. const isToday = selectedDateObj.toDateString() === now.toDateString();
  282. let filteredSlots = selectedTimeSlot.availableTime;
  283. if (isToday) {
  284. filteredSlots = filteredSlots.filter((slot) => {
  285. const [hours, minutes] = slot.startTime.split(':').map(Number);
  286. const slotTime = new Date(selectedDateObj);
  287. slotTime.setHours(hours, minutes, 0, 0);
  288. return slotTime > now;
  289. });
  290. }
  291. setAvailableTimeSlots(filteredSlots);
  292. };
  293. const handleConfirmation = (value: any) => {
  294. setModalVisible(true);
  295. const selectedOption = formattedConnectorDropdownOptions.find((option) => option.value === value);
  296. const label = selectedOption ? selectedOption.label : '';
  297. setRouterParams({
  298. pathname: '/bookingConfirmationPage',
  299. params: {
  300. chargeStationName,
  301. chargeStationAddress,
  302. chargeStationID,
  303. connectorID: value,
  304. connectorLabel: label,
  305. userID,
  306. carCapacitance: carCapacitance,
  307. carID: selectedCarID,
  308. carName: selectedCar,
  309. date: selectedDate,
  310. bookTime: selectedTime,
  311. chargingMethod: stopChargingUponBatteryFull ? 'stopChargingUponBatteryFull' : 'chargingBasedOnWatt',
  312. chargingWatt: selectedWatt || '',
  313. price: price
  314. }
  315. });
  316. };
  317. const handleModalConfirm = () => {
  318. setModalVisible(false);
  319. if (routerParams) {
  320. router.push(routerParams);
  321. }
  322. };
  323. return (
  324. <SafeAreaView
  325. style={{
  326. flex: 1,
  327. backgroundColor: 'white'
  328. }}
  329. edges={['right', 'top', 'left']}
  330. >
  331. <ScrollView className="flex-1 bg-white" showsVerticalScrollIndicator={false}>
  332. <View className="pb-4 ">
  333. <View className="ml-[5%] pt-8">
  334. <Pressable
  335. style={{ alignSelf: 'flex-start' }}
  336. onPress={() => {
  337. if (router.canGoBack()) {
  338. router.back();
  339. } else {
  340. router.replace('./');
  341. }
  342. }}
  343. >
  344. <PreviousPageBlackSvg />
  345. </Pressable>
  346. <Text className="text-3xl mt-8">{chargeStationName}</Text>
  347. <View className="flex-column">
  348. <View className="flex-row justify-between items-center mr-[5%]">
  349. <Text className="text-base" style={styles.grayColor}>
  350. {chargeStationAddress}
  351. </Text>
  352. <NormalButton
  353. title={
  354. <View className="flex-row items-center justify-center text-center space-x-1">
  355. <DirectionLogoSvg />
  356. <Text className="text-base">路線</Text>
  357. </View>
  358. }
  359. // onPress={() => console.log('路線')}
  360. onPress={handleNavigationPress}
  361. extendedStyle={{
  362. backgroundColor: '#E3F2F8',
  363. borderRadius: 61,
  364. paddingHorizontal: 20,
  365. paddingVertical: 8
  366. }}
  367. />
  368. </View>
  369. <View className="flex-row space-x-2 items-center">
  370. <CheckMarkLogoSvg />
  371. <Text>Walk-In</Text>
  372. {/* <Text>{distance}</Text> */}
  373. </View>
  374. </View>
  375. </View>
  376. </View>
  377. <View>
  378. {stopChargingUponBatteryFull === true || selectedWatt !== '' ? (
  379. <Pressable
  380. onPress={() => {
  381. setSelectedDuration('');
  382. setChargingBasedOnWatt(false);
  383. setStopChargingUponBatteryFull(false);
  384. setSelectedTime('');
  385. setSelectedDate('');
  386. setSelectedWatt('');
  387. setOpenDrawer(1);
  388. setSelectedDrawer(1);
  389. setSelectedChargingGun('');
  390. }}
  391. >
  392. <View className="mx-[5%] ">
  393. <View className="flex-row items-center pt-4">
  394. <Text className="text-lg pr-2 text-[#34667c]">選擇充電方案</Text>
  395. <CheckMarkLogoSvg />
  396. </View>
  397. <Text className="text-lg pb-4">
  398. {selectedWatt !== '' ? `按每道電 - ${selectedWatt.split('~')[0]}` : '充滿停機'}
  399. </Text>
  400. </View>
  401. </Pressable>
  402. ) : (
  403. <AccordionItem
  404. title="選擇充電方案"
  405. isOpen={openDrawer === 1}
  406. onToggle={() => {}}
  407. isSelected={selectedDrawer === 1}
  408. >
  409. <View className="flex-row justify-between mt-2 mb-3">
  410. <Pressable
  411. className={`border rounded-lg border-[#34667c] w-[47%] items-center bg-white ${
  412. chargingBasedOnWatt ? 'bg-[#34667c] ' : ''
  413. }`}
  414. onPress={() => {
  415. setChargingBasedOnWatt(!chargingBasedOnWatt);
  416. setStopChargingUponBatteryFull(false);
  417. }}
  418. >
  419. <Text
  420. className={`text-base p-2 text-[#34667c] ${
  421. chargingBasedOnWatt ? ' text-white' : 'text-[#34667c]'
  422. }`}
  423. >
  424. 按每度電
  425. </Text>
  426. </Pressable>
  427. {/* <Pressable
  428. onPress={() => {
  429. setStopChargingUponBatteryFull(!stopChargingUponBatteryFull);
  430. setChargingBasedOnWatt(false);
  431. setSelectedDrawer(2);
  432. setOpenDrawer(2);
  433. }}
  434. className={`border rounded-lg border-[#34667c] w-[47%] items-center bg-white ${
  435. stopChargingUponBatteryFull ? ' bg-[#34667c]' : ''
  436. }`}
  437. >
  438. <Text
  439. className={`text-base p-2 text-[#34667c] ${
  440. stopChargingUponBatteryFull ? ' text-white' : 'text-[#34667c]'
  441. }`}
  442. >
  443. 充滿停機
  444. </Text>
  445. </Pressable> */}
  446. </View>
  447. {chargingBasedOnWatt === true && (
  448. <View className="flex-row w-full justify-between mb-3">
  449. {['20 kWh~25mins', '25 kWh~30mins', '30 kWh~40mins', '40 kWh~45mins'].map(
  450. (watt) => (
  451. <Pressable
  452. key={watt}
  453. className={`${
  454. selectedWatt === watt ? 'bg-[#34667c] ' : 'bg-white'
  455. } border border-[#34667c] rounded-lg w-[22%] items-center`}
  456. onPress={() => {
  457. setSelectedWatt(watt);
  458. setOpenDrawer(2);
  459. setSelectedDrawer(2);
  460. handleSendingSize(watt);
  461. }}
  462. >
  463. <Text
  464. className={`text-base pt-2 pl-2 pr-2 ${
  465. selectedWatt === watt ? 'text-white' : 'text-[#34667c]'
  466. } `}
  467. >
  468. {watt.split('~')[0]}
  469. </Text>
  470. <Text className="text-xs pt-0 pb-1 text-[#666666]">
  471. {watt.split('~')[1]}
  472. </Text>
  473. </Pressable>
  474. )
  475. )}
  476. </View>
  477. )}
  478. </AccordionItem>
  479. )}
  480. {/* select gun */}
  481. {/* select gun */}
  482. <View className="">
  483. {selectedChargingGun !== '' ? (
  484. <Pressable
  485. onPress={() => {
  486. setSelectedChargingGun('');
  487. setOpenDrawer(2);
  488. setSelectedDrawer(2);
  489. }}
  490. >
  491. <View className="mx-[5%]">
  492. <View className="flex-row items-center pt-4">
  493. <Text className="text-lg pr-2 text-[#34667c]">選擇充電座</Text>
  494. <CheckMarkLogoSvg />
  495. </View>
  496. <View className="text-lg pb-4 flex flex-row items-center">
  497. <Text className="text-[#34667c] font-[600] text-2xl pr-2">
  498. {connectorIDToLabelMap[selectedChargingGun]}
  499. </Text>
  500. <Text className="text-lg">號充電座</Text>
  501. </View>
  502. </View>
  503. </Pressable>
  504. ) : (
  505. <AccordionItem
  506. title="選擇充電座"
  507. isOpen={openDrawer === 2}
  508. onToggle={() => {
  509. if (selectedWatt) {
  510. toggleDrawer(2);
  511. }
  512. }}
  513. isSelected={selectedDrawer === 2}
  514. >
  515. {selectedWatt !== '' ? (
  516. <View className="">
  517. <DropdownSelect
  518. dropdownOptions={connectorIDToLabelMapArray}
  519. placeholder={'選擇充電座號碼'}
  520. onSelect={(value) => {
  521. setSelectedChargingGun(value);
  522. setSelectedDrawer(3);
  523. setOpenDrawer(3);
  524. }}
  525. extendedStyle={{
  526. borderColor: '#34667c',
  527. marginTop: 4,
  528. padding: 12
  529. }}
  530. />
  531. <Image
  532. style={{
  533. width: layoutWidth * 0.9,
  534. height: layoutHeight
  535. }}
  536. resizeMode="contain"
  537. source={require('../../assets/floorPlan1.png')}
  538. />
  539. </View>
  540. ) : (
  541. <Text className="text-base text-gray-500 py-2">請先選擇充電方案</Text>
  542. )}
  543. </AccordionItem>
  544. )}
  545. </View>
  546. <Modal
  547. isVisible={isModalVisible}
  548. // onBackdropPress={() => setModalVisible(false)}
  549. backdropOpacity={0.5}
  550. animationIn="fadeIn"
  551. animationOut="fadeOut"
  552. >
  553. <View style={styles.modalContent}>
  554. <Text className="text-2xl font-[500] text-[#34667c] mb-6">
  555. 已選擇日期時間: {formatDateString(selectedDate)} - {selectedTime}
  556. </Text>
  557. <Text style={styles.modalText} className="text-[#34667c]">
  558. 若客戶逾時超過15分鐘,系統將視作自動放棄預約,客戶需要重新預約一次。
  559. 本公司有權保留全數費用,恕不退還。按下確認代表您已閱讀並同意上述條款。
  560. </Text>
  561. <NormalButton
  562. title={<Text className="text-white">我確認</Text>}
  563. onPress={handleModalConfirm}
  564. extendedStyle={styles.confirmButton}
  565. />
  566. </View>
  567. </Modal>
  568. {selectedDate !== '' && selectedTime !== '' ? (
  569. <>
  570. <Pressable
  571. onPress={() => {
  572. setOpenDrawer(3);
  573. setSelectedDrawer(3);
  574. setSelectedDate('');
  575. setSelectedTime('');
  576. }}
  577. >
  578. <View className="mx-[5%] ">
  579. <View className="flex-row items-center pt-4">
  580. <Text className="text-lg pr-2 text-[#34667c]">選擇日期</Text>
  581. <CheckMarkLogoSvg />
  582. </View>
  583. <Text className="text-lg pb-4">
  584. {formatDateString(selectedDate)} - {selectedTime}
  585. </Text>
  586. </View>
  587. </Pressable>
  588. </>
  589. ) : (
  590. <AccordionItem
  591. title="選擇日期 (月/日)"
  592. isOpen={openDrawer === 3}
  593. onToggle={() => {
  594. if (stopChargingUponBatteryFull !== false || selectedDuration !== '') {
  595. toggleDrawer(3);
  596. }
  597. }}
  598. isSelected={selectedDrawer === 3}
  599. >
  600. {isDateLoading ? (
  601. <View className="flex-1 items-center justify-center py-4">
  602. <ActivityIndicator size="large" color="#34667c" />
  603. </View>
  604. ) : (
  605. <View className="flex-row w-full flex-wrap mb-1 ">
  606. {availableDate.map((date) => (
  607. <Pressable
  608. key={date}
  609. className={`${
  610. selectedDate === date ? 'bg-[#34667c] ' : 'bg-white'
  611. } border border-[#34667c] rounded-lg w-[22%] items-center mt-1 mr-1 mb-1`}
  612. onPress={() => {
  613. setSelectedDate(date);
  614. handleDateToTimeSlot(date, selectedChargingGun);
  615. }}
  616. >
  617. <Text
  618. className={`text-base p-2 ${
  619. selectedDate === date ? 'text-white' : 'text-[#34667c]'
  620. } `}
  621. >
  622. {formatDateString(date)}
  623. </Text>
  624. </Pressable>
  625. ))}
  626. </View>
  627. )}
  628. {selectedDate !== '' && (
  629. <>
  630. <Text className="text-lg pr-2 ">選擇時間</Text>
  631. {isLoading ? (
  632. <View className="flex-1 mb-2">
  633. <ActivityIndicator />
  634. </View>
  635. ) : (
  636. <View className="flex-row w-full mb-3 flex-wrap my-2 ">
  637. {availableTimeSlots.map((slot, index) => (
  638. <Pressable
  639. key={index}
  640. className={`${
  641. selectedTime === slot.startTime ? 'bg-[#34667c] ' : 'bg-white'
  642. } border border-[#34667c] mr-2 rounded-lg w-[22%] items-center mb-2`}
  643. onPress={() => {
  644. setSelectedTime(slot.startTime);
  645. setRouterParams({
  646. pathname: '/bookingConfirmationPage',
  647. params: {
  648. chargeStationName,
  649. chargeStationAddress,
  650. chargeStationID,
  651. connectorID: selectedChargingGun,
  652. connectorLabel:
  653. connectorIDToLabelMap[selectedChargingGun],
  654. userID,
  655. carCapacitance: carCapacitance,
  656. carID: selectedCarID,
  657. carName: selectedCar,
  658. date: selectedDate,
  659. bookTime: slot.startTime,
  660. endTime: slot.endTime,
  661. chargingMethod: stopChargingUponBatteryFull
  662. ? 'stopChargingUponBatteryFull'
  663. : 'chargingBasedOnWatt',
  664. chargingWatt: selectedWatt || '',
  665. price: price
  666. }
  667. });
  668. setModalVisible(true);
  669. }}
  670. >
  671. <Text
  672. className={`text-base p-2 ${
  673. selectedTime === slot.startTime
  674. ? 'text-white'
  675. : 'text-[#34667c]'
  676. } `}
  677. >
  678. {slot.startTime}
  679. </Text>
  680. </Pressable>
  681. ))}
  682. </View>
  683. )}
  684. </>
  685. )}
  686. </AccordionItem>
  687. )}
  688. {/* <View className="">
  689. <AccordionItem
  690. title="選擇充電座"
  691. isOpen={openDrawer === 3}
  692. onToggle={() => {
  693. if (selectedTime) {
  694. // toggleDrawer(3);
  695. }
  696. }}
  697. isSelected={selectedDrawer === 3}
  698. >
  699. <View className="">
  700. <DropdownSelect
  701. dropdownOptions={formattedConnectorDropdownOptions}
  702. placeholder={'選擇充電座號碼'}
  703. onSelect={(value) => {
  704. setSelectedChargingGun(value);
  705. handleConfirmation(value);
  706. }}
  707. extendedStyle={{
  708. borderColor: '#34667c',
  709. marginTop: 4,
  710. padding: 12
  711. }}
  712. />
  713. <Image
  714. style={{
  715. width: layoutWidth * 0.9,
  716. height: layoutHeight
  717. }}
  718. resizeMode="contain"
  719. source={require('../../assets/floorPlan1.png')}
  720. />
  721. <Modal
  722. isVisible={isModalVisible}
  723. // onBackdropPress={() => setModalVisible(false)}
  724. backdropOpacity={0.5}
  725. animationIn="fadeIn"
  726. animationOut="fadeOut"
  727. >
  728. <View style={styles.modalContent}>
  729. <Text style={styles.modalText}>
  730. 若客戶逾時超過15分鐘,系統將視作自動放棄預約,客戶需要重新預約一次。
  731. 本公司有權保留全數費用,恕不退還。按下確認代表您已閱讀並同意上述條款。
  732. </Text>
  733. <NormalButton
  734. title={<Text className="text-white">我確認</Text>}
  735. onPress={handleModalConfirm}
  736. extendedStyle={styles.confirmButton}
  737. />
  738. </View>
  739. </Modal>
  740. </View>
  741. </AccordionItem>
  742. </View> */}
  743. </View>
  744. <View className="flex-1">
  745. <Text className="text-xl pb-2 mt-6" style={styles.text}>
  746. 充電站資訊
  747. </Text>
  748. <View className="h-[250px]">
  749. <ChargingStationTabView titles={['預約充電事項', '其他']} />
  750. </View>
  751. </View>
  752. </ScrollView>
  753. </SafeAreaView>
  754. );
  755. };
  756. export default MakingBookingPageComponent;
  757. const styles = StyleSheet.create({
  758. grayColor: {
  759. color: '#888888'
  760. },
  761. topLeftTriangle: {
  762. width: 0,
  763. height: 0,
  764. borderLeftWidth: 50,
  765. borderBottomWidth: 50,
  766. borderLeftColor: '#02677D',
  767. borderBottomColor: 'transparent',
  768. position: 'absolute',
  769. top: 0,
  770. left: 0
  771. },
  772. modalContent: {
  773. backgroundColor: 'white',
  774. padding: 22,
  775. justifyContent: 'center',
  776. alignItems: 'center',
  777. borderRadius: 4,
  778. borderColor: 'rgba(0, 0, 0, 0.1)'
  779. },
  780. modalText: {
  781. fontSize: 18,
  782. marginBottom: 12,
  783. textAlign: 'center'
  784. },
  785. confirmButton: {
  786. backgroundColor: '#34667c',
  787. paddingHorizontal: 30,
  788. paddingVertical: 10,
  789. borderRadius: 5
  790. },
  791. text: {
  792. fontWeight: 300,
  793. color: '#000000'
  794. }
  795. });