From 2568a0dfe9a9668cd11ffc95bc588fa966a7a76b Mon Sep 17 00:00:00 2001 From: katyhonguyen Date: Wed, 11 Dec 2024 05:36:19 -0800 Subject: [PATCH] updated directory, tree search & filter --- package-lock.json | 34 ++++- package.json | 2 + src/components/TreeFilter.tsx | 79 +++++++++++ src/components/searchBar.tsx | 27 ++-- src/icons/{ScanBarcode.tsx => Scanner.tsx} | 0 src/screens/Contact/Contact.tsx | 8 +- src/screens/Contact/Directory.tsx | 77 ++++++++-- src/screens/Contact/styles.ts | 66 ++++++++- src/screens/TreeSearch/TreeSearch.tsx | 117 +++++++++------ src/screens/TreeSearch/styles.ts | 157 ++++++++++++++++++--- src/types/navigation.ts | 1 + 11 files changed, 479 insertions(+), 89 deletions(-) create mode 100644 src/components/TreeFilter.tsx rename src/icons/{ScanBarcode.tsx => Scanner.tsx} (100%) diff --git a/package-lock.json b/package-lock.json index 81d870b..5eea85c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@expo/metro-runtime": "~4.0.0", "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-community/blur": "^4.4.1", + "@react-native-community/checkbox": "^0.5.17", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "@supabase/supabase-js": "^2.45.6", @@ -3871,6 +3873,32 @@ "react-native": "^0.0.0-0 || >=0.60 <1.0" } }, + "node_modules/@react-native-community/blur": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.4.1.tgz", + "integrity": "sha512-XBSsRiYxE/MOEln2ayunShfJtWztHwUxLFcSL20o+HNNRnuUDv+GXkF6FmM2zE8ZUfrnhQ/zeTqvnuDPGw6O8A==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/@react-native-community/checkbox": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@react-native-community/checkbox/-/checkbox-0.5.17.tgz", + "integrity": "sha512-ZZx/eOH3SIxBg+hvA2zRd7lX5zPQEWcZU8C8Dn7WLdpJkiTkPmIUDFHWrmzxCnwZcyoD+BIt6gUZPKGGb9WmeQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": ">= 0.62", + "react-native-windows": ">=0.62" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, "node_modules/@react-native-community/cli": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-16.0.0.tgz", @@ -13401,9 +13429,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 01e92c1..1121b45 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "dependencies": { "@expo/metro-runtime": "~4.0.0", "@react-native-async-storage/async-storage": "1.23.1", + "@react-native-community/blur": "^4.4.1", + "@react-native-community/checkbox": "^0.5.17", "@react-navigation/native": "^6.1.18", "@react-navigation/native-stack": "^6.11.0", "@supabase/supabase-js": "^2.45.6", diff --git a/src/components/TreeFilter.tsx b/src/components/TreeFilter.tsx new file mode 100644 index 0000000..77ae2f5 --- /dev/null +++ b/src/components/TreeFilter.tsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import { Modal, Text, TouchableOpacity, View } from 'react-native'; +import CheckBox from '@react-native-community/checkbox'; +import { styles } from '../screens/TreeSearch/styles'; + +type TreeFilterModalProps = { + visible: boolean; + onClose: () => void; +}; + +// type SelectedHeightFilters = { +// [key: string]: boolean; +// }; + +const TreeFilterModal: React.FC = ({ + visible, + onClose, +}) => { + // const [selectedHeightFilters, setSelectedHeightFilters] = useState({ + // small: false, + // medium: false, + // large: false, + // }); + + // const handleCheckboxChange = (filter: string) => { + // setSelectedHeightFilters(prevState => ({ + // ...prevState, + // [filter]: !prevState[filter], + // })); + // }; + + return ( + + + + + Filter + + + {/* + + handleCheckboxChange('small')} + /> + Small(< 40’) + + + + handleCheckboxChange('medium')} + /> + Medium (40 - 60’) + + + + handleCheckboxChange('large')} + /> + Large(60’ +) + */} + + + Complete + + + + + ); +}; + +export default TreeFilterModal; diff --git a/src/components/searchBar.tsx b/src/components/searchBar.tsx index 38b9988..a9115b1 100644 --- a/src/components/searchBar.tsx +++ b/src/components/searchBar.tsx @@ -1,24 +1,35 @@ -import React from 'react'; -import { TextInput, View } from 'react-native'; +import React, { useState } from 'react'; +import { TextInput, TouchableOpacity, View } from 'react-native'; +import TreeFilterModal from '@/components/TreeFilter'; +import Search from '@/icons/Search'; +import Filter from '@/icons/Sort'; import { styles } from '../screens/TreeSearch/styles'; -interface SearchBarProps { +type SearchBarProps = { value: string; onChange: (text: string) => void; -} +}; const SearchBar: React.FC = ({ value, onChange }) => { + const [modalVisible, setModalVisible] = useState(false); + + const openModal = () => setModalVisible(true); + const closeModal = () => setModalVisible(false); + return ( - + + + + + + ); diff --git a/src/icons/ScanBarcode.tsx b/src/icons/Scanner.tsx similarity index 100% rename from src/icons/ScanBarcode.tsx rename to src/icons/Scanner.tsx diff --git a/src/screens/Contact/Contact.tsx b/src/screens/Contact/Contact.tsx index 8a9bb29..831b432 100644 --- a/src/screens/Contact/Contact.tsx +++ b/src/screens/Contact/Contact.tsx @@ -67,8 +67,8 @@ export default function Contact({ navigation }: ContactProps) { console.log('Rendering Contact screen'); return ( - - + + Contact Us @@ -112,7 +112,9 @@ export default function Contact({ navigation }: ContactProps) { /> - Logout + + Log Out + ); diff --git a/src/screens/Contact/Directory.tsx b/src/screens/Contact/Directory.tsx index 03bf2ef..32b05cf 100644 --- a/src/screens/Contact/Directory.tsx +++ b/src/screens/Contact/Directory.tsx @@ -9,17 +9,78 @@ import { styles } from './styles'; type DirectoryProps = NativeStackScreenProps; export default function Directory({ navigation }: DirectoryProps) { + const handleGoBack = () => { + navigation.navigate('Contact'); + }; + return ( - - navigation.goBack()} - > + + - - + + + + Directory + + + + + Nursery + + For inventory and availability + + 408-123-2345 Ext: 123 + + treenursery@ourcityforest.org + + + + + Planting + + For delivery and planting status{' '} + + + + + San Jose + 408-123-2345 Ext: 123 + + treenursery@ourcityforest.org{' '} + + + + + + Campbell, Morgan Hill, Saratoga, Gilroy + + 408-123-2345 Ext: 123 + + treenursery@ourcityforest.org{' '} + + + + + Tree Care + + Inquiries of already-planted trees + + 408-123-2345 Ext: 123 + + treenursery@ourcityforest.org{' '} + + + + + Lawn Busters + Lawn conversions + 408-123-2345 Ext: 123 + + treenursery@ourcityforest.org{' '} + + - + ); } diff --git a/src/screens/Contact/styles.ts b/src/screens/Contact/styles.ts index ac3a16a..5a6e3c7 100644 --- a/src/screens/Contact/styles.ts +++ b/src/screens/Contact/styles.ts @@ -4,14 +4,14 @@ import colors from '@/styles/colors'; const { height } = Dimensions.get('window'); export const styles = StyleSheet.create({ - backgroundContainer: { + contactContainer: { flexGrow: 1, flexDirection: 'column', backgroundColor: 'white', alignItems: 'center', }, - headerContainer: { + contactHeaderContainer: { paddingTop: height * 0.1, height: height / 3, flexDirection: 'column', @@ -122,7 +122,65 @@ export const styles = StyleSheet.create({ fontWeight: 'bold', }, - backButton: {}, + // Directory - backIcon: {}, + directoryContainer: { + paddingTop: height * 0.07, + paddingLeft: 42, + paddingRight: 42, + paddingBottom: 42, + }, + + directoryHeader: { + alignItems: 'center', + justifyContent: 'center', + }, + + backButton: { + flexDirection: 'row', + alignItems: 'center', + }, + + backIcon: { + width: 24, + height: 24, + }, + + callIcon: { + width: 64, + height: 64, + paddingTop: 45, + paddingBottom: 45, + }, + + directoryContent: { + gap: 29, + }, + + directoryDetailsContainer: { + gap: 10, + }, + + directoryHeading: { + fontSize: 18, + fontWeight: '700', + color: 'black', + }, + + directorySubHeading: { + fontSize: 14, + fontWeight: '700', + color: colors.gray2, + }, + + directoryTextBold: { + fontSize: 14, + fontWeight: '500', + color: colors.gray2, + }, + + directoryTextLight: { + fontSize: 14, + color: colors.gray3, + }, }); diff --git a/src/screens/TreeSearch/TreeSearch.tsx b/src/screens/TreeSearch/TreeSearch.tsx index 5c017cf..88ce165 100644 --- a/src/screens/TreeSearch/TreeSearch.tsx +++ b/src/screens/TreeSearch/TreeSearch.tsx @@ -1,15 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { - FlatList, - ImageBackground, - ScrollView, - Text, - View, -} from 'react-native'; +import { FlatList, ImageBackground, Text, View } from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; +import QrCodeScanner from '@/components/QRCodeScanner/QRCodeScanner'; +import Scanner from '@/icons/Scanner'; import { RootStackParamList } from '@/types/navigation'; import SearchBar from '../../components/searchBar'; -import { getAllSpecies } from '../../supabase/queries/species'; +import { supabase } from '../../supabase/client'; import { styles } from './styles'; type TreeSearchProps = NativeStackScreenProps; @@ -17,35 +13,64 @@ type TreeSearchProps = NativeStackScreenProps; type TreeItem = { tree_id: number; species: string; - image_url: string; + image_link: string; sold: boolean; + stockCount: number; }; export default function TreeSearch({ navigation }: TreeSearchProps) { const [trees, setTrees] = useState([]); const [searchQuery, setSearchQuery] = useState(''); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { const loadTreeData = async () => { - const speciesData = await getAllSpecies(); + try { + setLoading(true); + const { data, error } = await supabase + .from('trees') + .select('tree_id, species, sold, species (name, image_link)'); - if (!speciesData || speciesData.length === 0) { - console.error('Failed to fetch tree data'); - return; - } + if (error) { + throw new Error(`Error fetching tree data: ${error.message}`); + } - const treesData: TreeItem[] = speciesData.map((species: any) => ({ - tree_id: species.id, - species: species.name, - image_url: species.image, - sold: species.sold ?? false, - })); + const treeCounts: Record = data.reduce( + (acc, tree) => { + if (!tree.sold) { + acc[tree.species.name] = (acc[tree.species.name] || 0) + 1; + } + return acc; + }, + {} as Record, + ); - const remainingTrees = treesData.filter(tree => !tree.sold); + const treesData: TreeItem[] = Object.keys(treeCounts).map( + speciesName => { + const treeSample = data.find( + tree => tree.species.name === speciesName, + ); - setTrees(remainingTrees); - }; + return { + tree_id: treeSample?.tree_id || 0, + species: speciesName, + image_link: treeSample?.species?.image_link || '', + sold: false, + stockCount: treeCounts[speciesName], + }; + }, + ); + setTrees(treesData); + setError(null); + } catch (err) { + console.error('Failed to fetch tree data:', err); + setError('Unable to load tree data. Please try again later.'); + } finally { + setLoading(false); + } + }; loadTreeData(); }, []); @@ -53,34 +78,44 @@ export default function TreeSearch({ navigation }: TreeSearchProps) { tree.species.toLowerCase().includes(searchQuery.toLowerCase()), ); - const remainingCount = filteredTrees.length; - const renderTreeCard = ({ item }: { item: TreeItem }) => ( - - {item.species} - + + {item.species} + + {item.stockCount} in stock ); return ( - - - Trees Availability - - item.tree_id.toString()} - numColumns={2} - columnWrapperStyle={styles.treeGrid} - scrollEnabled={false} + <> + + Trees Inventory + navigation.navigate('QRCodeScanner')} /> - + + + item.tree_id.toString()} + renderItem={renderTreeCard} + numColumns={2} + contentContainerStyle={styles.treeContainer} + ListEmptyComponent={ + + No trees found matching your search. + + } + /> + ); } diff --git a/src/screens/TreeSearch/styles.ts b/src/screens/TreeSearch/styles.ts index b61c3fa..f4456f9 100644 --- a/src/screens/TreeSearch/styles.ts +++ b/src/screens/TreeSearch/styles.ts @@ -5,35 +5,62 @@ const { width, height } = Dimensions.get('window'); const responsivePadding = width * 0.05; -const screenWidth = Dimensions.get('window').width; - export const styles = StyleSheet.create({ - backgroundContainer: { - flexGrow: 1, - backgroundColor: 'white', - paddingTop: 18, - paddingLeft: 27, - paddingRight: 27, + headingContainer: { + paddingLeft: responsivePadding, + paddingRight: responsivePadding, + paddingTop: height * 0.1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + + scannerIcon: { + height: 53, + width: 53, + borderRadius: 26.5, + backgroundColor: colors.primary_green, + justifyContent: 'center', + padding: 10, + alignItems: 'center', }, // Search Component searchContainer: { - paddingLeft: responsivePadding, - paddingRight: responsivePadding, - paddingTop: height * 0.1, - paddingBottom: height * 0.02, + flexDirection: 'row', + alignItems: 'center', borderBottomWidth: 2, borderBottomColor: colors.gray6, + paddingTop: 5, + paddingBottom: 20, + paddingRight: 27, + paddingLeft: 27, }, searchBarInput: { + flex: 1, height: 42, - borderColor: colors.gray6, borderWidth: 1, borderRadius: 30, paddingHorizontal: 10, + borderColor: colors.gray6, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'transparent', + }, + + inputContainer: { + flex: 1, + height: 42, + flexDirection: 'row', + alignItems: 'center', + width: '100%', + position: 'relative', + borderColor: colors.gray6, backgroundColor: colors.gray6, + borderRadius: 30, + paddingHorizontal: 10, }, searchHeading: { @@ -44,11 +71,28 @@ export const styles = StyleSheet.create({ textAlign: 'left', }, + filterIconContainer: { + width: 40, + height: 40, + borderRadius: 30, + backgroundColor: 'white', + padding: 5, + justifyContent: 'center', + alignItems: 'center', + }, + // Tree Cards + treeContainer: { + flexGrow: 1, + backgroundColor: 'white', + paddingTop: 18, + paddingLeft: 27, + paddingRight: 27, + }, + treeGrid: { width: '100%', - gap: 16, paddingHorizontal: width * 0.04, flexDirection: 'row', flexWrap: 'wrap', @@ -57,11 +101,12 @@ export const styles = StyleSheet.create({ }, treeCard: { - width: '48%', - marginBottom: 10, + width: width > 600 ? '30%' : '48%', + marginBottom: width * 0.02, + marginHorizontal: width * 0.01, justifyContent: 'flex-start', alignItems: 'flex-start', - borderRadius: 8, + borderRadius: 10, overflow: 'hidden', }, @@ -79,28 +124,96 @@ export const styles = StyleSheet.create({ justifyContent: 'space-between', alignItems: 'center', width: '100%', - marginTop: 5, + marginTop: width * 0.01, }, treeName: { - fontSize: 18, + fontSize: width > 600 ? 20 : 18, fontWeight: 'bold', textAlign: 'left', - marginTop: 5, + marginTop: width * 0.01, flex: 1, overflow: 'hidden', }, treeStock: { - fontSize: 14, + fontSize: width > 600 ? 16 : 14, fontWeight: 'medium', color: colors.gray3, textAlign: 'left', }, treeError: { - fontSize: 14, + fontSize: width > 600 ? 16 : 14, fontWeight: 'medium', padding: 0, }, + + // Filter Modal + + filterContainer: { + flex: 1, + justifyContent: 'flex-end', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + + filterContent: { + width: '100%', + height: height * 0.8, + backgroundColor: 'white', + padding: 40, + borderTopLeftRadius: 44, + borderTopRightRadius: 44, + alignItems: 'flex-start', + }, + + filterHeading: {}, + + filterHeadingText: { + color: colors.primary_green, + fontSize: 24, + fontWeight: '700', + marginBottom: 10, + }, + + filterText: { + fontSize: 16, + color: colors.gray4, + }, + + resetButton: {}, + + resetText: {}, + + completeButton: { + backgroundColor: colors.primary_green, + borderRadius: 10, + height: 53, + width: '100%', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + padding: 5, + }, + + completeButtonText: { + color: 'white', + fontSize: 18, + fontWeight: '700', + }, + + checkboxContainer: { + marginVertical: 20, + width: '100%', + }, + checkboxRow: { + flexDirection: 'row', + alignItems: 'center', + marginVertical: 8, + }, + checkboxLabel: { + marginLeft: 8, + fontSize: 16, + }, }); diff --git a/src/types/navigation.ts b/src/types/navigation.ts index 4d86803..8cd9c43 100644 --- a/src/types/navigation.ts +++ b/src/types/navigation.ts @@ -12,4 +12,5 @@ export type RootStackParamList = { TreeSearch: undefined; Contact: undefined; Directory: undefined; + QRCodeScanner: undefined; };