From c62d2ab05fb862cddd40b6a5442f327feedd44c0 Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Wed, 22 Oct 2025 15:07:57 -0700 Subject: [PATCH] Improve search layouts. --- app/_layout.tsx | 6 +- app/search/_layout.tsx | 2 + app/search/index.tsx | 12 +- babel-translations.babel | 94 +++ components/global-search/global-search.jsx | 109 ++++ components/jobs-list/job-list-item.jsx | 4 +- translations/en-US/common.json | 627 +++++++++++---------- translations/es-MX/common.json | 10 +- translations/fr-CA/common.json | 10 +- 9 files changed, 546 insertions(+), 328 deletions(-) create mode 100644 components/global-search/global-search.jsx diff --git a/app/_layout.tsx b/app/_layout.tsx index 3e45276..a826b32 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -47,12 +47,10 @@ function AuthenticatedLayout() { - {Platform.select({ + {Platform.select({ //ios: , android: ( - } - /> + } /> ), })} diff --git a/app/search/_layout.tsx b/app/search/_layout.tsx index 3a7b906..c0fe6df 100644 --- a/app/search/_layout.tsx +++ b/app/search/_layout.tsx @@ -11,6 +11,8 @@ export default function SearchLayout() { headerSearchBarOptions: { placement: "automatic", placeholder: "Search", + autoFocus: true, + shouldShowHintSearchIcon: true, onChangeText: (event) => { router.setParams({ globalSearch: event?.nativeEvent?.text, diff --git a/app/search/index.tsx b/app/search/index.tsx index 6193cb1..7b45230 100644 --- a/app/search/index.tsx +++ b/app/search/index.tsx @@ -1,12 +1,4 @@ -import { useLocalSearchParams } from "expo-router"; -import { ScrollView } from "react-native"; -import { Text } from "react-native-paper"; - +import GlobalSearch from "../../components/global-search/global-search"; export default function SearchIndex() { - const { globalSearch } = useLocalSearchParams(); - return ( - - Some search results here for: {globalSearch} - - ); + return ; } diff --git a/babel-translations.babel b/babel-translations.babel index 3eb8845..3fd190c 100644 --- a/babel-translations.babel +++ b/babel-translations.babel @@ -131,6 +131,27 @@ labels + + error + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + na false @@ -152,6 +173,58 @@ + + uploadprogress + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + + + globalsearch + + + labels + + + entersearch + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -4932,6 +5005,27 @@ + + wronginfo + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + wrongpassword false diff --git a/components/global-search/global-search.jsx b/components/global-search/global-search.jsx new file mode 100644 index 0000000..2615b95 --- /dev/null +++ b/components/global-search/global-search.jsx @@ -0,0 +1,109 @@ +import axios from "axios"; +import { useLocalSearchParams } from "expo-router"; +import debounce from "lodash/debounce"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { FlatList, View } from "react-native"; +import { ActivityIndicator, Text } from "react-native-paper"; +import env from "../../env"; +import ErrorDisplay from "../error/error-display"; +import JobListItem from "../jobs-list/job-list-item"; + +// Debounce delay (ms) – adjust as needed +const GLOBAL_SEARCH_DEBOUNCE_MS = 400; + +/** + * Hook returning a debounced search trigger. + * It recreates the debounced function only when the underlying callback changes. + * Placeholder: Replace the body of `performSearch` with real API / GraphQL logic. + */ +function useDebouncedGlobalSearch(onSearch, delay = GLOBAL_SEARCH_DEBOUNCE_MS) { + const debouncedRef = useRef(() => {}); + + useEffect(() => { + // Create debounced wrapper + const debounced = debounce((query) => { + onSearch(query); + }, delay); + debouncedRef.current = debounced; + return () => debounced.cancel(); + }, [onSearch, delay]); + + return useCallback((query) => { + debouncedRef.current && debouncedRef.current(query); + }, []); +} + +export default function GlobalSearch() { + const { globalSearch } = useLocalSearchParams(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [results, setResults] = useState([]); + const { t } = useTranslation(); + // Placeholder: Replace with actual API call (e.g., Apollo client query, REST fetch, Redux saga dispatch) + const performSearch = useCallback(async (query) => { + // Defensive trimr + const q = (query || "").trim(); + if (!q) return; + setLoading(true); + setError(null); + // TODO: Integrate real search endpoint + console.log(`[GlobalSearch] (debounced placeholder) searching for: "${q}"`); + try { + const searchData = await axios.post(`${env.API_URL}/search`, { + search: q, + }); + + if (searchData.data) { + const jobResults = searchData.data?.hits?.hits + ?.filter((hit) => hit._index === "jobs") + .map((hit) => hit._source); + + setResults(jobResults); + } else { + setError("No results available. Try again."); + } + } catch (error) { + console.error("Search error:", error); + setError(error.message); + } + setLoading(false); + }, []); + + const debouncedSearch = useDebouncedGlobalSearch(performSearch); + + // Trigger debounced search when the route param changes + useEffect(() => { + if (typeof globalSearch === "string" && globalSearch.length > 0) { + debouncedSearch(globalSearch); + } + }, [globalSearch, debouncedSearch]); + + if (globalSearch === undefined || globalSearch.trim() === "") { + return ( + + + {t("globalsearch.labels.entersearch")} + + + ); + } + + return ( + + {loading && } + {error && } + + + {results.length} results found + + + item.id?.toString()} + renderItem={(object) => } + /> + + ); +} diff --git a/components/jobs-list/job-list-item.jsx b/components/jobs-list/job-list-item.jsx index b104385..3f55e6a 100644 --- a/components/jobs-list/job-list-item.jsx +++ b/components/jobs-list/job-list-item.jsx @@ -3,7 +3,7 @@ import { useRouter } from "expo-router"; import React, { memo, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Pressable, StyleSheet, View } from "react-native"; -import { IconButton, Text, useTheme } from "react-native-paper"; +import { Chip, IconButton, Text, useTheme } from "react-native-paper"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { logImEXEvent } from "../../firebase/firebase.analytics"; @@ -86,6 +86,7 @@ function JobListItemComponent({ openImagePicker, item }) { {vehicle} )} + {item.status}