Ian Fung 1 tahun lalu
induk
melakukan
d1f599a18d

+ 13 - 0
app/(auth)/(tabs)/(home)/(booking)/searchResultPage.tsx

@@ -0,0 +1,13 @@
+import { View, Text } from 'react-native';
+import React from 'react';
+import SearchResultComponent from '../../../../../component/searchPage/searchResultComponent';
+
+const SearchResultPage = () => {
+    return (
+        <View className="flex-1">
+            <SearchResultComponent />
+        </View>
+    );
+};
+
+export default SearchResultPage;

+ 22 - 9
component/homePage/homePage.tsx

@@ -1,13 +1,11 @@
-import { View, Text, ScrollView, FlatList } from 'react-native';
+import { View, Text, ScrollView, FlatList, Pressable } from 'react-native';
 import NormalButton from '../global/normal_button';
-
-import NormalInput from '../global/normal_input';
 import { SafeAreaView } from 'react-native-safe-area-context';
 import { router } from 'expo-router';
 import RecentlyBookedScrollView from '../global/recentlyBookedScrollView';
 import { BellIconSvg, HomeIconSvg } from '../global/SVG';
 import { AuthContext } from '../../context/AuthProvider';
-import { useContext, useEffect } from 'react';
+import { useContext } from 'react';
 interface HomePageProps {}
 
 const HomePage: React.FC<HomePageProps> = () => {
@@ -32,10 +30,25 @@ const HomePage: React.FC<HomePageProps> = () => {
                             </Text>
                         </View>
                     </View>
-                    <NormalInput
-                        placeholder="搜尋充電站或地區"
-                        onChangeText={() => console.log('abc')}
-                    />
+                    <View className=" flex-1 justify-center ">
+                        <Pressable onPress={() => router.push('searchPage')}>
+                            <View
+                                style={{
+                                    borderWidth: 1,
+                                    padding: 24,
+                                    borderRadius: 12,
+                                    borderColor: '#bbbbbb',
+                                    maxWidth: '100%'
+                                }}
+                            >
+                                <Text
+                                    style={{ color: '#888888', fontSize: 16 }}
+                                >
+                                    搜尋充電站或地區..
+                                </Text>
+                            </View>
+                        </Pressable>
+                    </View>
                 </View>
 
                 {/* ************************近期預約過************************ */}
@@ -92,4 +105,4 @@ const HomePage: React.FC<HomePageProps> = () => {
         </SafeAreaView>
     );
 };
-export default HomePage;
+export default HomePage;

+ 1 - 1
component/reservationLocationPage/reservationLocationPageComponent.tsx

@@ -347,4 +347,4 @@ const ReservationLocationPage = () => {
     );
 };
 
-export default ReservationLocationPage;
+export default ReservationLocationPage;

+ 1 - 1
component/resultDetailPage/resultDetailPageComponent.tsx

@@ -174,7 +174,7 @@ const ResultDetailPageComponent = () => {
                             </View>
                         }
                         // onPress={() => console.log('ab')}
-                        onPress={()=>router.push('makingBookingPage')}
+                        onPress={() => router.push('makingBookingPage')}
                         extendedStyle={{ flex: 0.5 }}
                     />
                     <View

+ 118 - 5
component/searchPage/searchPageComponent.tsx

@@ -1,10 +1,17 @@
-import { ScrollView, Text, View, StyleSheet, Pressable } from 'react-native';
+import {
+    ScrollView,
+    Text,
+    View,
+    StyleSheet,
+    Pressable,
+    ImageSourcePropType
+} from 'react-native';
 import NormalInput from '../global/normal_input';
 import NormalButton from '../global/normal_button';
 import { FlashList } from '@shopify/flash-list';
 import { ArrowIconSvg } from '../global/SVG';
 import { SafeAreaView } from 'react-native-safe-area-context';
-import { useEffect, useRef } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { router } from 'expo-router';
 
 interface BookingHistory {
@@ -14,6 +21,40 @@ interface BookingHistory {
 
 interface SearchPageComponentProps {}
 
+interface TabItem {
+    imgURL: ImageSourcePropType;
+    date: string;
+    time: string;
+    chargeStationName: string;
+    chargeStationAddress: string;
+    distance: string;
+    latitude: number;
+    longitude: number;
+}
+
+const dummyTabItems: TabItem[] = [
+    {
+        imgURL: require('../../assets/dummyStationPicture.png'),
+        date: '今天',
+        time: '16:30',
+        chargeStationName: '觀塘偉業街充電站',
+        chargeStationAddress: '九龍觀塘偉業街143號地下',
+        distance: '400米',
+        latitude: 22.310958,
+        longitude: 114.226065
+    },
+    {
+        imgURL: require('../../assets/dummyStationPicture2.png'),
+        date: '3月15',
+        time: '17:45',
+        chargeStationName: '中環IFC充電站',
+        chargeStationAddress: '香港中環皇后大道中999號',
+        distance: '680米',
+        latitude: 22.28552,
+        longitude: 114.15769
+    }
+];
+
 const bookingHistoryData: BookingHistory[] = [
     {
         charingStationName: '充電站#1',
@@ -35,13 +76,24 @@ const bookingHistoryData: BookingHistory[] = [
 
 const SearchPageComponent: React.FC<SearchPageComponentProps> = () => {
     const inputRef = useRef(null);
+    const [searchInput, setSearchInput] = useState<string>('');
 
+    const [filteredItems, setFilteredItems] = useState<TabItem[]>([]);
     useEffect(() => {
         if (inputRef.current) {
             inputRef.current.focus();
         }
     }, []);
-
+    useEffect(() => {
+        if (searchInput === '') {
+            setFilteredItems([]);
+        } else {
+            const filteredData = dummyTabItems.filter((item) =>
+                item.chargeStationName.includes(searchInput.toLocaleUpperCase())
+            );
+            setFilteredItems(filteredData);
+        }
+    }, [searchInput]);
     return (
         <SafeAreaView className="flex-1 bg-white">
             <ScrollView className=" flex-1 px-[5%] pt-6 ">
@@ -61,11 +113,48 @@ const SearchPageComponent: React.FC<SearchPageComponentProps> = () => {
                         </Pressable>
                         <NormalInput
                             placeholder="搜尋這裡"
-                            onChangeText={(abc) => console.log(abc)}
+                            onChangeText={(text) => {
+                                setSearchInput(text);
+                                console.log(text);
+                            }}
                             extendedStyle={styles.textInput}
                             ref={inputRef}
                         />
                     </View>
+
+                    {filteredItems.length > 0 && (
+                        <View style={styles.dropdown}>
+                            <View>
+                                {filteredItems.map((item, index) => (
+                                    <Pressable
+                                        key={index}
+                                        onPress={() => {
+                                            setSearchInput(
+                                                item.chargeStationName
+                                            );
+                                            setFilteredItems([]);
+                                            router.push({
+                                                pathname: '/searchResultPage',
+                                                params: {
+                                                    latitude: item.latitude,
+                                                    longitude: item.longitude,
+                                                    chargeStationName:
+                                                        item.chargeStationName
+                                                }
+                                            });
+                                        }}
+                                        style={({ pressed }) => [
+                                            styles.dropdownItem,
+                                            pressed && styles.dropdownItemPress
+                                        ]}
+                                    >
+                                        <Text>{item.chargeStationName}</Text>
+                                    </Pressable>
+                                ))}
+                            </View>
+                        </View>
+                    )}
+
                     <ScrollView
                         horizontal={true}
                         className="space-x-4"
@@ -78,7 +167,8 @@ const SearchPageComponent: React.FC<SearchPageComponentProps> = () => {
                                         附近的充電站
                                     </Text>
                                 }
-                                onPress={() => console.log('附近的充電站')}
+                                // onPress={() => console.log('附近的充電站')}
+                                onPress={() => router.push('/searchResultPage')}
                                 buttonPressedStyle={{
                                     backgroundColor: '#CFDEE4'
                                 }}
@@ -208,5 +298,28 @@ const styles = StyleSheet.create({
         borderTopRightRadius: 12,
         borderRadius: 0,
         borderColor: '#bbbbbb'
+    },
+    dropdown: {
+        backgroundColor: 'white',
+        borderBottomLeftRadius: 12,
+        borderBottomRightRadius: 12,
+        borderLeftWidth: 1,
+        borderRightWidth: 1,
+        borderBottomWidth: 1,
+        marginTop: 10,
+        maxHeight: 200,
+        width: '100%',
+        position: 'absolute',
+        top: 50,
+        zIndex: 2,
+        borderColor: '#bbbbbb'
+    },
+    dropdownItem: {
+        padding: 10,
+        borderBottomWidth: 1,
+        borderBottomColor: '#ddd'
+    },
+    dropdownItemPress: {
+        backgroundColor: '#e8f8fc'
     }
 });

+ 421 - 0
component/searchPage/searchResultComponent.tsx

@@ -0,0 +1,421 @@
+import {
+    View,
+    Text,
+    StyleSheet,
+    Pressable,
+    Image,
+    ImageSourcePropType,
+    TouchableWithoutFeedback,
+    Keyboard
+} from 'react-native';
+import React, { useState, useEffect, useRef, useMemo } from 'react';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import MapView, { Marker, Region } from 'react-native-maps';
+import * as Location from 'expo-location';
+import { router, useLocalSearchParams } from 'expo-router';
+import { ArrowIconSvg, CheckMarkLogoSvg } from '../global/SVG';
+import NormalInput from '../global/normal_input';
+import BottomSheet, { BottomSheetScrollView } from '@gorhom/bottom-sheet';
+
+interface TabItem {
+    imgURL: ImageSourcePropType;
+    date: string;
+    time: string;
+    chargeStationName: string;
+    chargeStationAddress: string;
+    distance: string;
+    latitude: number;
+    longitude: number;
+}
+
+const dummyTabItems: TabItem[] = [
+    {
+        imgURL: require('../../assets/dummyStationPicture.png'),
+        date: '今天',
+        time: '16:30',
+        chargeStationName: '觀塘偉業街充電站',
+        chargeStationAddress: '九龍觀塘偉業街143號地下',
+        distance: '400米',
+        latitude: 22.310958,
+        longitude: 114.226065
+    },
+    {
+        imgURL: require('../../assets/dummyStationPicture2.png'),
+        date: '3月15',
+        time: '17:45',
+        chargeStationName: '中環IFC充電站',
+        chargeStationAddress: '香港中環皇后大道中999號',
+        distance: '680米',
+        latitude: 22.28552,
+        longitude: 114.15769
+    }
+];
+
+// **************************************
+// TODO: PUT THE GOOGLE MAP API KEY in ENV
+const GOOGLE_API_KEY = 'AIzaSyDYVSNuXFDNOhZAKfqeSwBTc8Pa7hKma1A';
+// ***************************************
+
+const SearchResultComponent = () => {
+    const [region, setRegion] = useState<Region | undefined>(undefined);
+    const [errorMsg, setErrorMsg] = useState<string | null>(null);
+    const [searchInput, setSearchInput] = useState<string>('');
+    const sheetRef = useRef<BottomSheet>(null);
+    const snapPoints = useMemo(() => ['25%', '65%'], []);
+    const mapRef = useRef<MapView>(null);
+    const params = useLocalSearchParams();
+    const [filteredItems, setFilteredItems] = useState<TabItem[]>([]);
+
+    useEffect(() => {
+        if (searchInput === '') {
+            setFilteredItems([]);
+        } else {
+            const filteredData = dummyTabItems.filter((item) =>
+                item.chargeStationName.includes(searchInput.toLocaleUpperCase())
+            );
+            setFilteredItems(filteredData);
+        }
+    }, [searchInput]);
+
+    useEffect(() => {
+        if (params.latitude && params.longitude) {
+            setRegion({
+                latitude: parseFloat(params.latitude as string),
+                longitude: parseFloat(params.longitude as string),
+                latitudeDelta: 0.01,
+                longitudeDelta: 0.01
+            });
+        } else {
+            (async () => {
+                let { status } =
+                    await Location.requestForegroundPermissionsAsync();
+                if (status !== 'granted') {
+                    setErrorMsg('Permission to access location was denied');
+                    return;
+                }
+                let myLocation = await Location.getLastKnownPositionAsync({});
+                if (myLocation) {
+                    setRegion({
+                        latitude: myLocation.coords.latitude,
+                        longitude: myLocation.coords.longitude,
+                        latitudeDelta: 0.01,
+                        longitudeDelta: 0.01
+                    });
+                }
+            })();
+        }
+    }, []);
+
+    useEffect(() => {
+        if (mapRef.current && region) {
+            mapRef.current.animateToRegion(region, 1000);
+        }
+    }, [region]);
+
+    if (errorMsg) {
+        return (
+            <View className="flex-1 justify-center items-center ">
+                <Text className="text-red-500">{errorMsg}</Text>
+            </View>
+        );
+    }
+
+    const handleRegionChange = (newRegion: Region) => {
+        if (mapRef.current) {
+            mapRef.current.animateToRegion(newRegion, 1000);
+        }
+        setRegion(newRegion);
+        sheetRef.current?.snapToIndex(0);
+    };
+
+    return (
+        <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
+            <SafeAreaView className="flex-1" edges={['top', 'left', 'right']}>
+                <View className="flex-1 relative">
+                    <View
+                        style={{
+                            position: 'absolute',
+                            top: 10,
+                            left: 10,
+                            right: 10,
+                            zIndex: 1,
+                            backgroundColor: 'transparent',
+                            alignItems: 'center'
+                        }}
+                    >
+                        <View className=" flex-1 flex-row bg-white rounded-xl">
+                            <Pressable
+                                style={styles.leftArrowBackButton}
+                                onPress={() => {
+                                    if (router.canGoBack()) {
+                                        router.back();
+                                    } else {
+                                        router.replace('/(auth)/(tabs)/(home)');
+                                    }
+                                }}
+                            >
+                                <ArrowIconSvg />
+                            </Pressable>
+                            <NormalInput
+                                placeholder="搜尋這裡"
+                                onChangeText={(text) => {
+                                    setSearchInput(text);
+                                    console.log(text);
+                                }}
+                                extendedStyle={styles.textInput}
+                            />
+                        </View>
+                        {filteredItems.length > 0 && (
+                            <View style={styles.dropdown}>
+                                <View>
+                                    {filteredItems.map((item, index) => (
+                                        <Pressable
+                                            key={index}
+                                            onPress={() => {
+                                                setSearchInput(
+                                                    item.chargeStationName
+                                                );
+                                                setFilteredItems([]);
+                                                handleRegionChange({
+                                                    latitude: item.latitude,
+                                                    longitude: item.longitude,
+                                                    latitudeDelta: 0.01,
+                                                    longitudeDelta: 0.01
+                                                });
+                                            }}
+                                            style={({ pressed }) => [
+                                                styles.dropdownItem,
+                                                pressed &&
+                                                    styles.dropdownItemPress
+                                            ]}
+                                        >
+                                            <Text>
+                                                {item.chargeStationName}
+                                            </Text>
+                                        </Pressable>
+                                    ))}
+                                </View>
+                            </View>
+                        )}
+                    </View>
+                    <MapView
+                        ref={mapRef}
+                        style={styles.map}
+                        region={region}
+                        cameraZoomRange={{
+                            minCenterCoordinateDistance: 500,
+                            maxCenterCoordinateDistance: 90000,
+                            animated: true
+                        }}
+                        showsUserLocation={true}
+                        showsMyLocationButton={false}
+                    >
+                        {dummyTabItems.map((item, index) => (
+                            <Marker
+                                key={index}
+                                coordinate={{
+                                    latitude: item.latitude,
+                                    longitude: item.longitude
+                                }}
+                                title={item.chargeStationName}
+                                description={item.chargeStationAddress}
+                            />
+                        ))}
+                    </MapView>
+                    <BottomSheet
+                        ref={sheetRef}
+                        index={0}
+                        snapPoints={snapPoints}
+                    >
+                        <BottomSheetScrollView
+                            contentContainerStyle={styles.contentContainer}
+                        >
+                            <View className="flex-1 mx-[5%]">
+                                {dummyTabItems
+                                    .filter((item) =>
+                                        item.chargeStationName.includes(
+                                            searchInput.toUpperCase()
+                                        )
+                                    )
+                                    .map((item, index) => {
+                                        return (
+                                            <Pressable
+                                                key={index}
+                                                onPress={() => {
+                                                    handleRegionChange({
+                                                        latitude: item.latitude,
+                                                        longitude:
+                                                            item.longitude,
+                                                        latitudeDelta: 0.01,
+                                                        longitudeDelta: 0.01
+                                                    });
+                                                    router.push(
+                                                        '/resultDetailPage'
+                                                    );
+                                                }}
+                                                style={({ pressed }) => [
+                                                    styles.container,
+                                                    {
+                                                        backgroundColor: pressed
+                                                            ? '#e7f2f8'
+                                                            : '#ffffff'
+                                                    }
+                                                ]}
+                                            >
+                                                <View
+                                                    style={styles.rowContainer}
+                                                >
+                                                    <Image
+                                                        style={styles.image}
+                                                        source={item.imgURL}
+                                                    />
+                                                    <View
+                                                        style={
+                                                            styles.textContainer
+                                                        }
+                                                    >
+                                                        <Text
+                                                            style={{
+                                                                fontWeight:
+                                                                    '400',
+                                                                fontSize: 18,
+                                                                color: '#222222'
+                                                            }}
+                                                        >
+                                                            {
+                                                                item.chargeStationName
+                                                            }
+                                                        </Text>
+                                                        <Text
+                                                            style={{
+                                                                fontWeight:
+                                                                    '400',
+                                                                fontSize: 14,
+                                                                color: '#888888'
+                                                            }}
+                                                        >
+                                                            {
+                                                                item.chargeStationAddress
+                                                            }
+                                                        </Text>
+                                                        <View className="flex-row space-x-2 items-center">
+                                                            <CheckMarkLogoSvg />
+                                                            <Text
+                                                                style={{
+                                                                    fontWeight:
+                                                                        '400',
+                                                                    fontSize: 14,
+                                                                    color: '#222222'
+                                                                }}
+                                                            >
+                                                                Walk-in
+                                                            </Text>
+                                                        </View>
+                                                    </View>
+                                                    <Text
+                                                        style={{
+                                                            fontWeight: '400',
+                                                            fontSize: 16,
+                                                            color: '#888888',
+                                                            marginTop: 22
+                                                        }}
+                                                        className="flex-1 text-right"
+                                                    >
+                                                        {item.distance}
+                                                    </Text>
+                                                </View>
+                                            </Pressable>
+                                        );
+                                    })}
+                            </View>
+                        </BottomSheetScrollView>
+                    </BottomSheet>
+                </View>
+            </SafeAreaView>
+        </TouchableWithoutFeedback>
+    );
+};
+
+export default SearchResultComponent;
+
+const styles = StyleSheet.create({
+    container: {
+        flex: 1
+    },
+    map: {
+        flex: 1,
+        width: '100%',
+        height: '100%'
+    },
+    contentContainer: {
+        backgroundColor: 'white'
+    },
+    itemContainer: {
+        padding: 6,
+        margin: 6,
+        backgroundColor: '#eee'
+    },
+    image: {
+        width: 100,
+        height: 100,
+        marginTop: 15,
+        marginRight: 15,
+
+        borderRadius: 10
+    },
+    textContainer: { flexDirection: 'column', gap: 8, marginTop: 22 },
+    rowContainer: { flexDirection: 'row' },
+    textInput: {
+        width: '85%',
+        maxWidth: '100%',
+        fontSize: 16,
+        padding: 20,
+        paddingLeft: 0,
+        borderLeftWidth: 0,
+        borderTopWidth: 1,
+        borderBottomWidth: 1,
+        borderRightWidth: 1,
+        borderBottomRightRadius: 12,
+        borderTopRightRadius: 12,
+        borderRadius: 0,
+        borderColor: '#bbbbbb'
+    },
+    leftArrowBackButton: {
+        width: '15%',
+        maxWidth: '100%',
+        fontSize: 16,
+        padding: 20,
+        paddingLeft: 30,
+        borderBottomLeftRadius: 12,
+        borderTopLeftRadius: 12,
+        borderColor: '#bbbbbb',
+        borderTopWidth: 1,
+        borderBottomWidth: 1,
+        borderLeftWidth: 1,
+        alignItems: 'center',
+        justifyContent: 'center'
+    },
+    dropdown: {
+        backgroundColor: 'white',
+        borderBottomLeftRadius: 12,
+        borderBottomRightRadius: 12,
+        borderLeftWidth: 1,
+        borderRightWidth: 1,
+        borderBottomWidth: 1,
+        marginTop: 10,
+        maxHeight: 200,
+        width: '100%',
+        position: 'absolute',
+        top: 50,
+        zIndex: 2,
+        borderColor: '#bbbbbb'
+    },
+    dropdownItem: {
+        padding: 10,
+        borderBottomWidth: 1,
+        borderBottomColor: '#ddd'
+    },
+    dropdownItemPress: {
+        backgroundColor: '#e8f8fc'
+    }
+});