| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- import {
- View,
- Text,
- ScrollView,
- Image,
- useWindowDimensions,
- StyleSheet,
- Pressable,
- Platform,
- Linking,
- ActivityIndicator,
- Alert
- } from 'react-native';
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
- import { SceneMap, TabBar, TabView } from 'react-native-tab-view';
- import NormalButton from '../global/normal_button';
- import { router, useFocusEffect, useLocalSearchParams } from 'expo-router';
- import { CheckMarkLogoSvg, DirectionLogoSvg, PreviousPageSvg } from '../global/SVG';
- import { SafeAreaView } from 'react-native-safe-area-context';
- import { chargeStationService } from '../../service/chargeStationService';
- import * as Location from 'expo-location';
- import { calculateDistance } from '../global/distanceCalculator';
- import { ChargingDetails, Remark, PriceWeek, Special } from '../../service/type/chargeStationType';
- interface ChargingStationTabViewProps {
- titles: string[];
- pricemodel_id: string;
- }
- interface ChargingPeriod{
- event_name: string;
- price: number;
- from: string;
- to: string;
- }
- const ChargingStationTabView: React.FC<ChargingStationTabViewProps> = ({ titles, pricemodel_id }) => {
- const layout = useWindowDimensions();
- const [list, setList] = useState<ChargingPeriod []>([])
- const [strWeek, setStrWeek] = useState<string>('')
- // 添加AM/PM标识但保持24小时制
- const addPeriodToTime = (timeString: string): string => {
- // 假设输入格式为 HH.mm 或 HH:mm
- const [hours] = timeString.split(/[.:]/).map(Number);
-
- if (isNaN(hours)) return timeString;
-
- const period = hours >= 12 ? 'PM' : 'AM';
-
- return `${timeString}${period}`;
- };
- useEffect(() => {
- chargeStationService.fetchElectricityPrice(pricemodel_id || 'a').then(res => {
- const date = new Date();
- const str = (date.toLocaleString('en-US', { weekday: 'short' })).toLowerCase();
- setStrWeek(date.toLocaleString('zh', { weekday: 'long' }))
- const newList = [] as ChargingPeriod[]
- res?.forEach((item) => {
- const obj = item[str as keyof PriceWeek]
- newList.push({event_name: item.event_name, price: obj.price, from: addPeriodToTime(obj.from),to: addPeriodToTime(obj.to)})
- setList(newList)
- })
- })
- }, [pricemodel_id])
- //tab 1
- const FirstRoute = () => (
- <ScrollView style={{ flex: 1, marginHorizontal: '5%' }}>
- <View>
- <View className='w-full flex-row justify-between mt-4'>
- <Text style={styles.leftLable}>時段</Text>
- <Text style={styles.rightLable}>價格(/度)</Text>
- </View>
- {
- list.map((item, index) => (
- <View key={index} className='w-full flex-row justify-between mt-3'>
- <Text style={styles.leftLable}>{item.from} - {item.to}</Text>
- <Text style={styles.rightLable}>${item.price}</Text>
- </View>
- ))
- }
- </View>
- </ScrollView>
- );
- //tab 2
- const SecondRoute = () => (
- <ScrollView style={{ flex: 1, marginHorizontal: '5%' }}>
- <Text className="text-lg " style={styles.text}></Text>
- </ScrollView>
- );
- const renderScene = SceneMap({
- firstRoute: FirstRoute,
- secondRoute: SecondRoute
- });
- const [routes] = React.useState([
- { key: 'firstRoute', title: titles[0] },
- { key: 'secondRoute', title: titles[1] }
- ]);
- const [index, setIndex] = React.useState(0);
- const renderTabBar = (props: any) => (
- <TabBar
- {...props}
- indicatorStyle={{
- backgroundColor: '#000000',
- height: 1
- }}
- style={{
- backgroundColor: 'white',
- elevation: 0,
- marginHorizontal: 15,
- borderBottomWidth: 0.5
- }}
- />
- );
- return (
- <TabView
- navigationState={{ index, routes }}
- renderScene={renderScene}
- onIndexChange={setIndex}
- initialLayout={{ width: layout.width }}
- renderTabBar={renderTabBar}
- commonOptions={{
- label: ({ route, focused }) => (
- <Text
- style={{
- color: focused ? '#000000' : '#CCCCCC',
- fontWeight: focused ? '300' : 'thin',
- fontSize: 17
- }}
- >
- {route.title}
- </Text>
- )
- }}
- />
- );
- };
- const ResultDetailPageComponent = () => {
- const params = useLocalSearchParams();
- const chargeStationID = params.chargeStationID as string;
- const chargeStationName = params.chargeStationName as string;
- const chargeStationAddress = params.chargeStationAddress as string;
- const pricemodel_id = params.pricemodel_id as string;
- const imageSourceProps = params.imageSource;
- const stationLng = params.stationLng as string;
- const stationLat = params.stationLat as string;
- const [isLoading, setIsLoading] = useState(true);
- const [imageSource, setImageSource] = useState();
- const [currentLocation, setCurrentLocation] = useState<Location.LocationObject | null>(null);
- const [price, setPrice] = useState('');
- const [newAvailableConnectors, setNewAvailableConnectors] = useState<any>([]);
- useEffect(() => {
- const imgObj = imageSourceProps? {uri: imageSourceProps} : require('../../assets/dummyStationPicture.png')
- setImageSource(imgObj);
- }, [imageSourceProps])
- useEffect(() => {
- const fetchPrice = async () => {
- try {
- const price = await chargeStationService.fetchChargeStationPrice(chargeStationID);
- setPrice(price);
- } catch (error) {
- console.error('Error fetching price:', error);
- }
- };
- const getCurrentLocation = async () => {
- let { status } = await Location.requestForegroundPermissionsAsync();
- if (status !== 'granted') {
- console.error('Permission to access location was denied');
- return;
- }
- let location = await Location.getLastKnownPositionAsync({});
- setCurrentLocation(location);
- };
- getCurrentLocation();
- fetchPrice();
- }, []);
- useFocusEffect(
- useCallback(() => {
- setIsLoading(true);
- let isMounted = true; // Simple cleanup flag
- const fetchAllConnectors = async () => {
- try {
- const newAvailableConnectors = await chargeStationService.NewfetchAvailableConnectors();
- // Only update state if component is still mounted
- if (isMounted) {
- setNewAvailableConnectors(newAvailableConnectors);
- }
- } catch (error) {
- console.error('Fetch error:', error);
- }
- };
- fetchAllConnectors();
- setIsLoading(false);
- // Simple cleanup - prevents state updates if component unmounts
- return () => {
- isMounted = false;
- };
- }, []) // Add any missing dependencies here if needed
- );
- const handleNavigationPress = (StationLat: string, StationLng: string) => {
- console.log('starting navigation press in resultDetail', StationLat, StationLng);
- if (StationLat && StationLng) {
- const label = encodeURIComponent(chargeStationName);
- const googleMapsUrl = `https://www.google.com/maps/search/?api=1&query=${StationLat},${StationLng}`;
- // Fallback URL for web browser
- const webUrl = `https://www.google.com/maps/dir/?api=1&destination=${StationLat},${StationLng}`;
- Linking.canOpenURL(googleMapsUrl)
- .then((supported) => {
- if (supported) {
- Linking.openURL(googleMapsUrl);
- } else {
- Linking.openURL(webUrl).catch((err) => {
- console.error('An error occurred', err);
- Alert.alert(
- 'Error',
- "Unable to open Google Maps. Please make sure it's installed on your device.",
- [{ text: 'OK' }],
- { cancelable: false }
- );
- });
- }
- })
- .catch((err) => console.error('An error occurred', err));
- }
- };
- return (
- <SafeAreaView edges={['top', 'left', 'right']} className="flex-1 bg-white">
- <ScrollView className="flex-1 ">
- <View className="relative">
- <Image
- source={ imageSource }
- resizeMode="cover"
- style={{ flex: 1, width: '100%', height: 300 }}
- />
- <View className="absolute top-8 left-7 ">
- <Pressable
- onPress={() => {
- if (router.canGoBack()) {
- router.back();
- } else {
- router.replace('./');
- }
- }}
- >
- <PreviousPageSvg />
- </Pressable>
- </View>
- </View>
- <View className="flex-column mx-[5%] mt-[5%]">
- <View>
- <Text className="text-3xl ">{chargeStationName}</Text>
- </View>
- <View className="flex-row justify-between items-center">
- <Text className="text-base" style={{ color: '#888888' }}>
- {chargeStationAddress}
- </Text>
- <NormalButton
- title={
- <View className="flex-row items-center justify-center text-center space-x-1">
- <DirectionLogoSvg />
- <Text className="text-base ">路線</Text>
- </View>
- }
- onPress={() => handleNavigationPress(stationLat, stationLng)}
- extendedStyle={{
- backgroundColor: '#E3F2F8',
- borderRadius: 61,
- paddingHorizontal: 20,
- paddingVertical: 6
- }}
- />
- </View>
- <View className="flex-row space-x-2 items-center pb-4 ">
- <CheckMarkLogoSvg />
- <Text>Walk-In</Text>
- {/* <Text>{distance} </Text> */}
- </View>
- <View
- className="flex-1 flex-row min-h-[20px] border-slate-300 my-6 rounded-2xl"
- style={{ borderWidth: 1 }}
- >
- <View className="flex-1 m-4">
- <View className="flex-1 flex-row ">
- <View className=" flex-1 flex-column ustify-between">
- <Text className="text-xl " style={styles.text}>
- 收費
- </Text>
- <View className="flex-row items-center space-x-2">
- <Text className="text-3xl text-[#02677D]">${price}</Text>
- <Text style={styles.text}>每度電</Text>
- </View>
- </View>
- <View className="items-center justify-center">
- <View className="w-[1px] h-[60%] bg-[#CCCCCC]" />
- </View>
- <View className=" flex-1 pl-4 flex-column justify-between">
- <Text className="text-xl " style={styles.text}>
- 現時可用充電槍數目
- </Text>
- <View className="flex-row items-center space-x-2">
- {isLoading ? (
- <Text>Loading...</Text>
- ) : (
- // <ActivityIndicator />
- <Text className="text-3xl text-[#02677D]">
- {
- newAvailableConnectors.find(
- (station: any) => station.stationID === chargeStationID
- )?.availableConnectors
- }
- </Text>
- )}
- </View>
- </View>
- </View>
- </View>
- </View>
- </View>
- <View className="min-h-[300px]">
- <Text className="text-xl pb-2 mx-[5%]" style={styles.text}>
- 充電站資訊
- </Text>
- <ChargingStationTabView titles={['收費詳情', '其他']} pricemodel_id={pricemodel_id} />
- </View>
- </ScrollView>
- </SafeAreaView>
- );
- };
- export default ResultDetailPageComponent;
- const styles = StyleSheet.create({
- text: {
- fontWeight: 300,
- color: '#000000'
- },
- leftLable: {
- width: '70%',
- fontSize: 17,
- color:'#000000',
- textAlign: 'center'
- },
- rightLable: {
- fontSize: 17,
- width: '30%',
- textAlign: 'center',
- borderLeftWidth: 1,
- paddingLeft: 0,
- },
- });
|