makingBookingPageComponent.tsx 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  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 ChooseCarForChargingRow from '../global/chooseCarForChargingRow';
  20. import { useEffect, useState } from 'react';
  21. import DropdownSelect from '../global/dropdown_select';
  22. import { chargeStationService } from '../../service/chargeStationService';
  23. import { authenticationService } from '../../service/authService';
  24. import * as Location from 'expo-location';
  25. import { calculateDistance } from '../global/distanceCalculator';
  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 [openDrawer, setOpenDrawer] = useState<number | null>(0);
  45. const [selectedTime, setSelectedTime] = useState<string>('');
  46. const [availableTimeSlots, setAvailableTimeSlots] = useState<string[]>([]);
  47. const [selectedDrawer, setSelectedDrawer] = useState<number>(0);
  48. const [isLoading, setIsLoading] = useState(true);
  49. const [selectedDate, setSelectedDate] = useState<string>('');
  50. const toggleDrawer = (index: number) => {
  51. setOpenDrawer(openDrawer === index ? null : index);
  52. setSelectedDrawer(index);
  53. };
  54. const [availableDate, setAvailableDate] = useState<string[]>([]);
  55. const [car, setCar] = useState([]);
  56. const [selectedCarID, setSelectedCarID] = useState('');
  57. const [selectedChargingGun, setSelectedChargingGun] = useState('');
  58. const [chargingBasedOnWatt, setChargingBasedOnWatt] = useState(false);
  59. const [stopChargingUponBatteryFull, setStopChargingUponBatteryFull] = useState(false);
  60. const [selectedCar, setSelectedCar] = useState('');
  61. const [selectedDuration, setSelectedDuration] = useState('');
  62. const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
  63. const [price, setPrice] = useState(0);
  64. const layoutWidth = screenWidth;
  65. const layoutHeight = screenHeight * 0.32;
  66. const [userID, setUserID] = useState('');
  67. const params = useLocalSearchParams();
  68. const chargeStationID = params.chargeStationID as string;
  69. const chargeStationName = params.chargeStationName as string;
  70. const chargeStationAddress = params.chargeStationAddress as string;
  71. const chargeStationLat = params.chargeStationLat as string;
  72. const chargeStationLng = params.chargeStationLng as string;
  73. const [selectedWatt, setSelectedWatt] = useState('');
  74. const [availableConnectorDropdownOptions, setAvailableConnectorDropdownOptions] = useState([]);
  75. const [carCapacitance, setCarCapacitance] = useState('');
  76. const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
  77. const [distance, setDistance] = useState<string | null>(null);
  78. const [upcomingReservations, setUpcomingReservation] = useState([]);
  79. const [carLoadingState, setCarLoadingState] = useState(false);
  80. const [isDateLoading, setIsDateLoading] = useState(true);
  81. useEffect(() => {
  82. const getCurrentLocation = async () => {
  83. let { status } = await Location.requestForegroundPermissionsAsync();
  84. if (status !== 'granted') {
  85. console.error('Permission to access location was denied');
  86. return;
  87. }
  88. let location = await Location.getLastKnownPositionAsync({});
  89. setCurrentLocation(location);
  90. };
  91. getCurrentLocation();
  92. }, []);
  93. const formatDistance = (distanceInMeters: number): string => {
  94. if (distanceInMeters < 1000) {
  95. return `${Math.round(distanceInMeters)}米`;
  96. } else {
  97. const distanceInKm = distanceInMeters / 1000;
  98. return `${distanceInKm.toFixed(1)}公里`;
  99. }
  100. };
  101. const getUpcomingReservations = (reservations, daysAhead = 3) => {
  102. const today = new Date();
  103. const threeDaysLater = new Date(today);
  104. threeDaysLater.setDate(today.getDate() + daysAhead);
  105. return reservations
  106. .filter((reservation) => {
  107. const reservationDate = new Date(reservation.book_time);
  108. return reservationDate >= today && reservationDate <= threeDaysLater;
  109. })
  110. .sort((a, b) => new Date(a.reservationDate) - new Date(b.reservationDate));
  111. };
  112. //USE BELOW to find upcoming reservations, then I filter availableDate and time based on the upcoming ones so user cannot book the same time twice.
  113. const formatReservations = (reservations) => {
  114. const formattedReservations = {};
  115. reservations.forEach((reservation) => {
  116. const bookTime = new Date(reservation.book_time);
  117. const date = formatDate(bookTime);
  118. const time = formatTime(bookTime);
  119. if (!formattedReservations[date]) {
  120. formattedReservations[date] = [];
  121. }
  122. formattedReservations[date].push(time);
  123. });
  124. return Object.entries(formattedReservations).map(([date, times]) => ({
  125. date,
  126. times
  127. }));
  128. };
  129. const formatDate = (date) => {
  130. const month = String(date.getMonth() + 1).padStart(2, '0');
  131. const day = String(date.getDate()).padStart(2, '0');
  132. return `${month}/${day}`;
  133. };
  134. const formatTime = (date) => {
  135. return date.toTimeString().slice(0, 5);
  136. };
  137. useEffect(() => {
  138. const fetchReservationHistories = async () => {
  139. try {
  140. const response = await chargeStationService.fetchReservationHistories();
  141. if (response) {
  142. // console.log('response', response);
  143. // console.log('Reservation histories:', response);
  144. const upcomingReservations = getUpcomingReservations(response);
  145. // console.log('upcomingReservations', upcomingReservations);
  146. const formattedReservations = formatReservations(upcomingReservations);
  147. // console.log('formattedReservations', formattedReservations);
  148. setUpcomingReservation(formattedReservations);
  149. }
  150. 2;
  151. } catch (error) {
  152. console.log(error);
  153. }
  154. };
  155. fetchReservationHistories();
  156. }, []);
  157. //USE ABOVE to find upcoming reservations, then I filter availableDate and time based on the upcoming ones so user cannot book the same time twice.
  158. useEffect(() => {
  159. const getDistance = async () => {
  160. if (currentLocation) {
  161. try {
  162. const distance = await calculateDistance(
  163. Number(params.chargeStationLat),
  164. Number(params.chargeStationLng),
  165. currentLocation
  166. );
  167. setDistance(formatDistance(distance));
  168. } catch (error) {
  169. console.error('Error calculating distance:', error);
  170. }
  171. }
  172. };
  173. getDistance();
  174. }, [params.chargeStationLat, params.chargeStationLng, currentLocation]);
  175. useEffect(() => {
  176. const fetchChargeStation = async () => {
  177. try {
  178. const data = await chargeStationService.fetchPriceForCharging();
  179. setPrice(data.data[0].price);
  180. // console.log(JSON.stringify(fetchedChargeStation.data));
  181. } catch (error) {
  182. console.log(error);
  183. }
  184. };
  185. fetchChargeStation();
  186. }, []);
  187. function appendImageUrlToCarResult(carData, updatedVehicles) {
  188. return carData.map((car) => {
  189. const matchingVehicle = updatedVehicles.find((vehicle) => vehicle.car_type.name === car.car_name);
  190. if (matchingVehicle) {
  191. return {
  192. ...car,
  193. type_image_url: matchingVehicle.car_type.type_image_url
  194. };
  195. }
  196. return car;
  197. });
  198. }
  199. useEffect(() => {
  200. setCarLoadingState(true);
  201. const fetchUserInfoAndCarData = async () => {
  202. try {
  203. const fetchedUserInfo = await authenticationService.getUserInfo();
  204. const userData = fetchedUserInfo?.data;
  205. // console.log('userData', userData);
  206. if (userData) {
  207. setUserID(userData.id);
  208. const carData = userData.cars.map((car: any) => ({
  209. car_name: car.name,
  210. car_capacitance: car.capacitance,
  211. car_id: car.id,
  212. isDefault: car.id === userData.defaultCar?.id
  213. }));
  214. // console.log('carDarta', carData);
  215. setCar(carData);
  216. const carResult = await chargeStationService.getUserCars();
  217. let updatedVehicles = [...carResult.data];
  218. // console.log('updatedVehiaacles', updatedVehicles);
  219. const updatedCarResult = appendImageUrlToCarResult(carData, updatedVehicles);
  220. setCar(updatedCarResult);
  221. let updatedCarResultWithProcessedUrl = [...updatedCarResult];
  222. for (let i = 0; i < updatedCarResultWithProcessedUrl.length; i++) {
  223. const car = updatedCarResultWithProcessedUrl[i];
  224. const processedUrl = await chargeStationService.getProcessedImageUrl(car.type_image_url);
  225. updatedCarResultWithProcessedUrl[i] = {
  226. ...car,
  227. processedImageUrl: processedUrl
  228. };
  229. }
  230. // console.log(updatedCarResultWithProcessedUrl);
  231. setCar(updatedCarResultWithProcessedUrl);
  232. }
  233. } catch (error) {
  234. console.error('Error fetching user info:', error);
  235. } finally {
  236. setCarLoadingState(false);
  237. }
  238. };
  239. fetchUserInfoAndCarData();
  240. }, []);
  241. useEffect(() => {
  242. const fetchingAvailableTimeSlots = async () => {
  243. setIsLoading(true);
  244. try {
  245. const fetchedTimeSlots = await chargeStationService.fetchAvailableTimeSlots(
  246. chargeStationID,
  247. selectedDate
  248. );
  249. const now = new Date();
  250. const today = `${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(
  251. 2,
  252. '0'
  253. )}`;
  254. let filteredTimeSlots = fetchedTimeSlots;
  255. //filter out today's time slots that have already passed
  256. if (selectedDate === today) {
  257. const currentHours = now.getHours();
  258. const currentMinutes = now.getMinutes();
  259. filteredTimeSlots = fetchedTimeSlots.filter((time) => {
  260. const [hours, minutes] = time.split(':').map(Number);
  261. if (hours > currentHours) return true;
  262. if (hours === currentHours && minutes > currentMinutes) return true;
  263. return false;
  264. });
  265. }
  266. //filter out time slots that are already fully booked
  267. const reservedSlotsForDate = upcomingReservations.find((res) => res.date === selectedDate);
  268. if (reservedSlotsForDate) {
  269. filteredTimeSlots = filteredTimeSlots.filter((time) => !reservedSlotsForDate.times.includes(time));
  270. }
  271. setAvailableTimeSlots(filteredTimeSlots);
  272. } catch (error) {
  273. console.error('Error fetching time slots:', error);
  274. } finally {
  275. setIsLoading(false);
  276. }
  277. };
  278. if (selectedDate) {
  279. fetchingAvailableTimeSlots();
  280. }
  281. }, [selectedDate]);
  282. useEffect(() => {
  283. const fetchConnectorOptions = async () => {
  284. try {
  285. const fetchedData = await chargeStationService.fetchSpecificChargeStation(chargeStationID);
  286. console.log('fetchedDate', fetchedData);
  287. const dateObject = fetchedData.find((item) => item.date === selectedDate);
  288. console.log('dateObject', dateObject);
  289. if (!dateObject) {
  290. setAvailableConnectorDropdownOptions([]);
  291. return;
  292. }
  293. const rangeObject = dateObject.range.find((range) => range.start === selectedTime);
  294. console.log('rangeObject', rangeObject);
  295. if (!rangeObject) {
  296. setAvailableConnectorDropdownOptions([]);
  297. return;
  298. }
  299. const connectorIDs = rangeObject.connectors
  300. .filter((connector) => connector.Status === 2)
  301. .map((connector) => connector.ConnectorID);
  302. console.log('connectorIDs', connectorIDs);
  303. setAvailableConnectorDropdownOptions(connectorIDs);
  304. } catch (error) {
  305. console.error('Error fetching charge station data:', error);
  306. setAvailableConnectorDropdownOptions([]);
  307. }
  308. };
  309. fetchConnectorOptions();
  310. }, [chargeStationID, selectedDate, selectedTime]);
  311. const formattedConnectorDropdownOptions = availableConnectorDropdownOptions.map((id, index) => ({
  312. label: (index + 1).toString(),
  313. value: id
  314. }));
  315. useEffect(() => {
  316. const fetchingAvailableDates = async () => {
  317. setIsDateLoading(true);
  318. try {
  319. const fetchedDates = await chargeStationService.fetchAvailableDates(chargeStationID);
  320. console.log('fetchedDates', fetchedDates);
  321. setAvailableDate(fetchedDates);
  322. console.log(fetchedDates.slice(0, 3));
  323. } catch (error) {
  324. console.error('Error fetching available dates:', error);
  325. } finally {
  326. setIsDateLoading(false);
  327. }
  328. };
  329. fetchingAvailableDates();
  330. }, [chargeStationID]);
  331. const handleNavigationPress = () => {
  332. const latitude = chargeStationLat;
  333. const longitude = chargeStationLng;
  334. // console.log('latitude', latitude);
  335. // console.log('longitude', longitude);
  336. const label = encodeURIComponent(chargeStationName);
  337. // Google Maps App URL
  338. const googleMapsUrl = `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;
  339. // Fallback URL for web browser
  340. const webUrl = `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`;
  341. Linking.canOpenURL(googleMapsUrl)
  342. .then((supported) => {
  343. if (supported) {
  344. Linking.openURL(googleMapsUrl);
  345. } else {
  346. Linking.openURL(webUrl).catch((err) => {
  347. console.error('An error occurred', err);
  348. Alert.alert(
  349. 'Error',
  350. "Unable to open Google Maps. Please make sure it's installed on your device.",
  351. [{ text: 'OK' }],
  352. { cancelable: false }
  353. );
  354. });
  355. }
  356. })
  357. .catch((err) => console.error('An error occurred', err));
  358. };
  359. return (
  360. <SafeAreaView
  361. style={{
  362. flex: 1,
  363. backgroundColor: 'white'
  364. }}
  365. edges={['right', 'top', 'left']}
  366. >
  367. <ScrollView className="flex-1 bg-white" showsVerticalScrollIndicator={false}>
  368. <View className="pb-4 ">
  369. <View className="ml-[5%] pt-8">
  370. <Pressable
  371. style={{ alignSelf: 'flex-start' }}
  372. onPress={() => {
  373. if (router.canGoBack()) {
  374. router.back();
  375. } else {
  376. router.replace('./');
  377. }
  378. }}
  379. >
  380. <PreviousPageBlackSvg />
  381. </Pressable>
  382. <Text className="text-3xl mt-8">{chargeStationName}</Text>
  383. <View className="flex-column">
  384. <View className="flex-row justify-between items-center mr-[5%]">
  385. <Text className="text-base" style={styles.grayColor}>
  386. {chargeStationAddress}
  387. </Text>
  388. <NormalButton
  389. title={
  390. <View className="flex-row items-center justify-center text-center space-x-1">
  391. <DirectionLogoSvg />
  392. <Text className="text-base">路線</Text>
  393. </View>
  394. }
  395. // onPress={() => console.log('路線')}
  396. onPress={handleNavigationPress}
  397. extendedStyle={{
  398. backgroundColor: '#E3F2F8',
  399. borderRadius: 61,
  400. paddingHorizontal: 20,
  401. paddingVertical: 8
  402. }}
  403. />
  404. </View>
  405. <View className="flex-row space-x-2 items-center">
  406. <CheckMarkLogoSvg />
  407. <Text>Walk-In</Text>
  408. {/* <Text>{distance}</Text> */}
  409. </View>
  410. </View>
  411. </View>
  412. </View>
  413. <View>
  414. {selectedCar !== '' ? (
  415. <>
  416. <Pressable
  417. onPress={() => {
  418. setSelectedCar('');
  419. setSelectedWatt('');
  420. setOpenDrawer(0);
  421. setSelectedDrawer(0);
  422. setSelectedDuration('');
  423. setChargingBasedOnWatt(false);
  424. setStopChargingUponBatteryFull(false);
  425. setSelectedDate('');
  426. setSelectedTime('');
  427. }}
  428. >
  429. <View className="mx-[5%]">
  430. <View className="flex-row items-center pt-4">
  431. <Text className="text-lg pr-2 text-[#34667c]">選擇充電車輛</Text>
  432. <CheckMarkLogoSvg />
  433. </View>
  434. <Text className="text-lg pb-4">{selectedCar}</Text>
  435. </View>
  436. </Pressable>
  437. </>
  438. ) : (
  439. <AccordionItem
  440. title="選擇充電車輛"
  441. isOpen={openDrawer === 0}
  442. onToggle={() => toggleDrawer(0)}
  443. isSelected={selectedDrawer === 0}
  444. >
  445. {carLoadingState ? (
  446. <View>
  447. <ActivityIndicator color="#34657b" />
  448. </View>
  449. ) : (
  450. <ScrollView
  451. horizontal={true}
  452. contentContainerStyle={{
  453. alignItems: 'center',
  454. flexDirection: 'row',
  455. marginVertical: 8
  456. }}
  457. className="space-x-2 "
  458. >
  459. {car
  460. .sort((a, b) => (b.isDefault ? 1 : 0) - (a.isDefault ? 1 : 0))
  461. .map((car, index) => (
  462. <ChooseCarForChargingRow
  463. onPress={() => {
  464. setSelectedCar(car.car_name);
  465. setSelectedCarID(car.car_id);
  466. setCarCapacitance(car.car_capacitance);
  467. setSelectedDrawer(1);
  468. setOpenDrawer(1);
  469. }}
  470. image={car.processedImageUrl}
  471. key={`${car.car_name}+${index}`}
  472. VehicleName={car.car_name}
  473. isDefault={car.isDefault}
  474. />
  475. ))}
  476. </ScrollView>
  477. )}
  478. </AccordionItem>
  479. )}
  480. {stopChargingUponBatteryFull === true || selectedWatt !== '' ? (
  481. <Pressable
  482. onPress={() => {
  483. setSelectedDuration('');
  484. setChargingBasedOnWatt(false);
  485. setStopChargingUponBatteryFull(false);
  486. setSelectedTime('');
  487. setSelectedDate('');
  488. setSelectedWatt('');
  489. setOpenDrawer(1);
  490. setSelectedDrawer(1);
  491. }}
  492. >
  493. <View className="mx-[5%] ">
  494. <View className="flex-row items-center pt-4">
  495. <Text className="text-lg pr-2 text-[#34667c]">選擇充電方案</Text>
  496. <CheckMarkLogoSvg />
  497. </View>
  498. <Text className="text-lg pb-4">
  499. {selectedWatt !== '' ? `按每道電 - ${selectedWatt.split('~')[0]}` : '充滿停機'}
  500. </Text>
  501. </View>
  502. </Pressable>
  503. ) : (
  504. <AccordionItem
  505. title="選擇充電方案"
  506. isOpen={openDrawer === 1}
  507. onToggle={() => {}}
  508. isSelected={selectedDrawer === 1}
  509. >
  510. <View className="flex-row justify-between mt-2 mb-3">
  511. <Pressable
  512. className={`border rounded-lg border-[#34667c] w-[47%] items-center bg-white ${
  513. chargingBasedOnWatt ? 'bg-[#34667c] ' : ''
  514. }`}
  515. onPress={() => {
  516. setChargingBasedOnWatt(!chargingBasedOnWatt);
  517. setStopChargingUponBatteryFull(false);
  518. }}
  519. >
  520. <Text
  521. className={`text-base p-2 text-[#34667c] ${
  522. chargingBasedOnWatt ? ' text-white' : 'text-[#34667c]'
  523. }`}
  524. >
  525. 按每度電
  526. </Text>
  527. </Pressable>
  528. <Pressable
  529. onPress={() => {
  530. setStopChargingUponBatteryFull(!stopChargingUponBatteryFull);
  531. setChargingBasedOnWatt(false);
  532. setSelectedDrawer(2);
  533. setOpenDrawer(2);
  534. }}
  535. className={`border rounded-lg border-[#34667c] w-[47%] items-center bg-white ${
  536. stopChargingUponBatteryFull ? ' bg-[#34667c]' : ''
  537. }`}
  538. >
  539. <Text
  540. className={`text-base p-2 text-[#34667c] ${
  541. stopChargingUponBatteryFull ? ' text-white' : 'text-[#34667c]'
  542. }`}
  543. >
  544. 充滿停機
  545. </Text>
  546. </Pressable>
  547. </View>
  548. {chargingBasedOnWatt === true && (
  549. <View className="flex-row w-full justify-between mb-3">
  550. {['20 kWh~25mins', '25 kWh~30mins', '30 kWh~40mins', '40 kWh~45mins'].map(
  551. (watt) => (
  552. <Pressable
  553. key={watt}
  554. className={`${
  555. selectedWatt === watt ? 'bg-[#34667c] ' : 'bg-white'
  556. } border border-[#34667c] rounded-lg w-[22%] items-center`}
  557. onPress={() => {
  558. setSelectedWatt(watt);
  559. setOpenDrawer(2);
  560. setSelectedDrawer(2);
  561. }}
  562. >
  563. <Text
  564. className={`text-base pt-2 pl-2 pr-2 ${
  565. selectedWatt === watt ? 'text-white' : 'text-[#34667c]'
  566. } `}
  567. >
  568. {watt.split('~')[0]}
  569. </Text>
  570. <Text className="text-xs pt-0 pb-1 text-[#666666]">
  571. {watt.split('~')[1]}
  572. </Text>
  573. </Pressable>
  574. )
  575. )}
  576. </View>
  577. )}
  578. </AccordionItem>
  579. )}
  580. {selectedTime !== '' ? (
  581. <Pressable
  582. onPress={() => {
  583. setOpenDrawer(2);
  584. setSelectedDrawer(2);
  585. setSelectedDate('');
  586. setSelectedTime('');
  587. }}
  588. >
  589. <View className="mx-[5%] ">
  590. <View className="flex-row items-center pt-4">
  591. <Text className="text-lg pr-2 text-[#34667c]">選擇日期</Text>
  592. <CheckMarkLogoSvg />
  593. </View>
  594. <Text className="text-lg pb-4">
  595. {selectedDate} - {selectedTime}
  596. </Text>
  597. </View>
  598. </Pressable>
  599. ) : (
  600. <AccordionItem
  601. title="選擇日期 (月/日)"
  602. isOpen={openDrawer === 2}
  603. onToggle={() => {
  604. if (stopChargingUponBatteryFull !== false || selectedDuration !== '') {
  605. toggleDrawer(2);
  606. }
  607. }}
  608. isSelected={selectedDrawer === 2}
  609. >
  610. {isDateLoading ? (
  611. <View className="flex-1 items-center justify-center py-4">
  612. <ActivityIndicator size="large" color="#34667c" />
  613. </View>
  614. ) : (
  615. <View className="flex-row w-full flex-wrap mb-1 ">
  616. {availableDate.slice(0, 3).map((date) => (
  617. <Pressable
  618. key={date}
  619. className={`${
  620. selectedDate === date ? 'bg-[#34667c] ' : 'bg-white'
  621. } border border-[#34667c] rounded-lg w-[22%] items-center mt-1 mr-1 mb-1`}
  622. onPress={() => {
  623. setSelectedDate(date);
  624. }}
  625. >
  626. <Text
  627. className={`text-base p-2 ${
  628. selectedDate === date ? 'text-white' : 'text-[#34667c]'
  629. } `}
  630. >
  631. {date}
  632. </Text>
  633. </Pressable>
  634. ))}
  635. </View>
  636. )}
  637. {selectedDate !== '' && (
  638. <>
  639. <Text className="text-lg pr-2 ">選擇時間</Text>
  640. {isLoading ? (
  641. <View className="flex-1 mb-2">
  642. <ActivityIndicator />
  643. </View>
  644. ) : (
  645. <View className="flex-row w-full mb-3 flex-wrap my-2 ">
  646. {availableTimeSlots.map((time) => (
  647. <Pressable
  648. key={time}
  649. className={`${
  650. selectedTime === time ? 'bg-[#34667c] ' : 'bg-white'
  651. } border border-[#34667c] mr-2 rounded-lg w-[22%] items-center mb-2`}
  652. onPress={() => {
  653. setSelectedTime(time);
  654. setOpenDrawer(3);
  655. setSelectedDrawer(3);
  656. }}
  657. >
  658. <Text
  659. className={`text-base p-2 ${
  660. selectedTime === time ? 'text-white' : 'text-[#34667c]'
  661. } `}
  662. >
  663. {time}
  664. </Text>
  665. </Pressable>
  666. ))}
  667. </View>
  668. )}
  669. </>
  670. )}
  671. </AccordionItem>
  672. )}
  673. <View className="">
  674. <AccordionItem
  675. title="選擇充電座"
  676. isOpen={openDrawer === 3}
  677. onToggle={() => {
  678. if (selectedTime) {
  679. // toggleDrawer(3);
  680. }
  681. }}
  682. isSelected={selectedDrawer === 3}
  683. >
  684. <View className="">
  685. <DropdownSelect
  686. dropdownOptions={formattedConnectorDropdownOptions}
  687. placeholder={'選擇充電座號碼'}
  688. onSelect={(value) => {
  689. setSelectedChargingGun(value);
  690. router.push({
  691. pathname: '/bookingConfirmationPage',
  692. params: {
  693. chargeStationName,
  694. chargeStationAddress,
  695. chargeStationID,
  696. connectorID: value,
  697. userID,
  698. carCapacitance: carCapacitance,
  699. carID: selectedCarID,
  700. carName: selectedCar,
  701. date: selectedDate,
  702. bookTime: selectedTime,
  703. chargingMethod: stopChargingUponBatteryFull
  704. ? 'stopChargingUponBatteryFull'
  705. : 'chargingBasedOnWatt',
  706. chargingWatt: selectedWatt || '',
  707. price: price
  708. }
  709. });
  710. }}
  711. extendedStyle={{
  712. borderColor: '#34667c',
  713. marginTop: 4
  714. }}
  715. />
  716. <Image
  717. style={{
  718. width: layoutWidth * 0.9,
  719. height: layoutHeight
  720. }}
  721. resizeMode="contain"
  722. source={require('../../assets/floorPlan1.png')}
  723. />
  724. </View>
  725. </AccordionItem>
  726. </View>
  727. </View>
  728. <View className="mx-[5%] flex-1">
  729. <View className="flex-row border-slate-300 mt-3 mb-6 rounded-2xl flex-1" style={{ borderWidth: 1 }}>
  730. <View className="flex-1 m-4">
  731. <View className="flex-1 flex-row ">
  732. <View className=" flex-1 flex-column justify-between">
  733. <Text className="text-xl " style={styles.text}>
  734. 收費
  735. </Text>
  736. <View className="flex-row items-center space-x-2">
  737. <Text className="text-3xl text-[#02677D]">$20</Text>
  738. <Text style={styles.text}>每15分鐘</Text>
  739. </View>
  740. </View>
  741. <View className="items-center justify-center">
  742. <View className="w-[1px] h-[60%] bg-[#CCCCCC]" />
  743. </View>
  744. <View className="flex-1 flex-column ">
  745. <View className="flex-1"></View>
  746. <View className="flex-row items-center ml-4 space-x-2 ">
  747. <Text className="text-3xl text-[#02677D]">${price}</Text>
  748. <Text style={styles.text}>每度電</Text>
  749. </View>
  750. </View>
  751. </View>
  752. </View>
  753. </View>
  754. <Text className="text-xl pb-2 " style={styles.text}>
  755. 充電站資訊
  756. </Text>
  757. <View className="h-[250px]">
  758. <ChargingStationTabView titles={['充電插頭', '其他']} />
  759. </View>
  760. </View>
  761. </ScrollView>
  762. </SafeAreaView>
  763. );
  764. };
  765. export default MakingBookingPageComponent;
  766. const styles = StyleSheet.create({
  767. grayColor: {
  768. color: '#888888'
  769. },
  770. topLeftTriangle: {
  771. width: 0,
  772. height: 0,
  773. borderLeftWidth: 50,
  774. borderBottomWidth: 50,
  775. borderLeftColor: '#02677D',
  776. borderBottomColor: 'transparent',
  777. position: 'absolute',
  778. top: 0,
  779. left: 0
  780. },
  781. text: {
  782. fontWeight: 300,
  783. color: '#000000'
  784. }
  785. });