-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: Enhance Search bar with query parameter
-Add SearchResults which will react with query parameter, - Enhance Search mechanizm. Refs: 8697653ha Signed-off-by: Jimmy <[email protected]>
- Loading branch information
Showing
4 changed files
with
183 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,93 +1,148 @@ | ||
import {Autocomplete, AutocompleteProps, Avatar, Box, ComboboxItem, Group, OptionsFilter, Text} from '@mantine/core'; | ||
import { | ||
Autocomplete, | ||
AutocompleteProps, | ||
Avatar, | ||
Center, | ||
ComboboxItem, | ||
Group, | ||
OptionsFilter, | ||
Stack, | ||
Text, | ||
} from '@mantine/core'; | ||
import {useState} from 'react'; | ||
import {IconSearch, IconX} from "@tabler/icons-react"; | ||
import {SuggestedUsers} from "./components/SuggestedUsers"; | ||
import {useNavigate} from "react-router-dom"; | ||
import {BasicUserInfo} from "../shared/types/User.tsx"; | ||
import {SearchResults} from "./components/SearchResults"; | ||
|
||
type SuggestedUser = { | ||
id: number; | ||
name: string; | ||
avatar: string; | ||
tag: string; | ||
}; | ||
|
||
// Mockowe dane użytkowników | ||
const users: SuggestedUser[] = [ | ||
{id: 1, name: 'Emily Johnson', avatar: 'https://via.placeholder.com/40', tag: '@emily'}, | ||
{id: 2, name: 'Ava Rodriguez', avatar: 'https://via.placeholder.com/40', tag: '@ava'}, | ||
{id: 3, name: 'Olivia Chen', avatar: 'https://via.placeholder.com/40', tag: '@olivia'}, | ||
{id: 4, name: 'Ethan Barnes', avatar: 'https://via.placeholder.com/40', tag: '@ethan'}, | ||
{id: 5, name: 'Mason Taylor', avatar: 'https://via.placeholder.com/40', tag: '@mason'}, | ||
{id: 6, name: "Jan Kowalski", avatar: 'https://via.placeholder.com/40', tag: '@johndoe'}, | ||
// Mocked users data | ||
const users: BasicUserInfo[] = [ | ||
{id: '1', name: 'Emily', surname: 'Johnson', profilePicture: 'https://via.placeholder.com/40', login: 'emily'}, | ||
{id: '2', name: 'Ava', surname: 'Rodriguez', profilePicture: 'https://via.placeholder.com/40', login: 'ava'}, | ||
{id: '3', name: 'Olivia', surname: 'Chen', profilePicture: 'https://via.placeholder.com/40', login: 'olivia'}, | ||
{id: '4', name: 'Ethan', surname: 'Barnes', profilePicture: 'https://via.placeholder.com/40', login: 'ethan'}, | ||
{id: '5', name: 'Mason', surname: 'Taylor', profilePicture: 'https://via.placeholder.com/40', login: 'mason'}, | ||
{id: '6', name: "Jan", surname: 'Kowalski', profilePicture: 'https://via.placeholder.com/40', login: 'johndoe'}, | ||
]; | ||
|
||
const usersData = users.reduce((acc, user) => { | ||
acc[user.name] = {avatar: user.avatar, tag: user.tag}; | ||
acc[`${user.name} ${user.surname}`] = {profilePicture: user.profilePicture, tag: `@${user.login}`}; | ||
return acc; | ||
}, {} as Record<string, { avatar: string; tag: string }>); | ||
|
||
const renderAutocompleteOption: AutocompleteProps['renderOption'] = ({option}) => ( | ||
<Group gap="sm"> | ||
<Avatar src={usersData[option.value].avatar} size={36} radius="xl"/> | ||
<div> | ||
<Text size="sm">{option.value}</Text> | ||
<Text size="xs" opacity={0.5}> | ||
{usersData[option.value].tag} | ||
}, {} as Record<string, { profilePicture: string | null; tag: string }>); | ||
|
||
const renderAutocompleteOption: AutocompleteProps['renderOption'] = ({option}) => { | ||
if (option.value.startsWith("search:")) { | ||
const searchText = option.value.replace("search:", ""); | ||
return ( | ||
<Text size="sm" opacity={0.7}> | ||
Wyszukaj "{searchText}" | ||
</Text> | ||
</div> | ||
</Group> | ||
); | ||
); | ||
} | ||
|
||
return ( | ||
<Group gap="sm"> | ||
<Avatar src={usersData[option.value]?.profilePicture} size={36} radius="xl"/> | ||
<div> | ||
<Text size="sm">{option.value}</Text> | ||
<Text size="xs" opacity={0.5}> | ||
{usersData[option.value]?.tag} | ||
</Text> | ||
</div> | ||
</Group> | ||
); | ||
}; | ||
|
||
export const Search = () => { | ||
const [query, setQuery] = useState(''); | ||
const [dropdownOpened, setDropdownOpened] = useState(false); | ||
const navigate = useNavigate(); | ||
const itemsNumber = 5; | ||
|
||
const convertedData = users | ||
.map((user) => ({ | ||
value: user.name, | ||
label: user.name, | ||
tag: user.tag, | ||
avatar: user.avatar, | ||
value: `${user.name} ${user.surname}`, | ||
label: `${user.name} ${user.surname}`, | ||
tag: `@${user.login}`, | ||
profilePicture: user.profilePicture, | ||
})); | ||
|
||
// Dane do Autocomplete (nazwy użytkowników i tagi użytkowników) | ||
// Data for autocomplete options | ||
const optionsFilter: OptionsFilter = ({options, search}) => { | ||
|
||
if (!search) { | ||
return options; | ||
} | ||
|
||
return (options as ComboboxItem[]).filter((option) => { | ||
const filteredOptions = (options as ComboboxItem[]).filter((option) => { | ||
const searchLower = search.toLowerCase(); | ||
return option.value.toLowerCase().includes(searchLower) || usersData[option.value].tag.toLowerCase().includes(searchLower); | ||
return option.value.toLowerCase().includes(searchLower) || usersData[option.value]?.tag.toLowerCase().includes(searchLower); | ||
}); | ||
|
||
// Add dynamic option for searching users | ||
return [ | ||
{ | ||
value: `search:${search}`, | ||
label: `Wyszukaj "${search}"`, | ||
}, | ||
...filteredOptions, | ||
].slice(0, itemsNumber); | ||
}; | ||
|
||
|
||
const handleSearchSubmit = () => { | ||
if (query) { | ||
navigate(`/search?query=${encodeURIComponent(query)}`); | ||
} | ||
}; | ||
|
||
const handleClear = () => { | ||
setQuery(''); | ||
} | ||
setDropdownOpened(false); | ||
}; | ||
|
||
//TODO: Get random {amount} users and display them in SuggestedUsers component | ||
//TODO: Get filtered users based on query and display them in SearchResults component | ||
|
||
return ( | ||
<Box p="xl" w="60dvw"> | ||
<Autocomplete | ||
data={convertedData} | ||
leftSection={<IconSearch/>} | ||
rightSection={query ? <IconX onClick={handleClear} style={{cursor: "pointer"}}/> : null} | ||
renderOption={renderAutocompleteOption} | ||
maxDropdownHeight={300} | ||
placeholder="Wyszukaj" | ||
filter={optionsFilter} | ||
limit={5} | ||
size="xl" | ||
radius="xl" | ||
value={query} | ||
onChange={setQuery} | ||
onOptionSubmit={(value) => { | ||
navigate(`/profile/${usersData[value].tag}`) | ||
}} | ||
/> | ||
|
||
<SuggestedUsers/> | ||
</Box> | ||
<Center> | ||
<Stack align={'center'} p="xl" w="60dvw"> | ||
<Autocomplete | ||
data={convertedData} | ||
leftSection={<IconSearch/>} | ||
rightSection={query ? <IconX onClick={handleClear} style={{cursor: "pointer"}}/> : null} | ||
renderOption={renderAutocompleteOption} | ||
maxDropdownHeight={300} | ||
placeholder="Wyszukaj" | ||
filter={optionsFilter} | ||
w={"100%"} | ||
size="xl" | ||
radius="xl" | ||
value={query} | ||
dropdownOpened={dropdownOpened} | ||
onDropdownOpen={() => setDropdownOpened(true)} | ||
onDropdownClose={() => setDropdownOpened(false)} | ||
onChange={setQuery} | ||
onOptionSubmit={(value) => { | ||
if (value.startsWith("search:")) { | ||
const searchText = value.replace("search:", ""); | ||
navigate(`/search?query=${encodeURIComponent(searchText)}`); | ||
handleClear(); | ||
} else { | ||
navigate(`/profile/${usersData[value]?.tag}`); | ||
} | ||
}} | ||
onKeyDown={(event) => { | ||
if (event.key === "Enter") { | ||
handleSearchSubmit(); | ||
} | ||
}} | ||
/> | ||
|
||
<SuggestedUsers/> | ||
<SearchResults/> | ||
</Stack> | ||
</Center> | ||
|
||
); | ||
}; |
67 changes: 67 additions & 0 deletions
67
frontend/src/Features/Search/components/SearchResults/SearchResults.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import {ActionIcon, Avatar, Card, Divider, Group, Stack, Text} from '@mantine/core'; | ||
import {useSearchParams} from "react-router-dom"; | ||
import {BasicUserInfo} from "../../../shared/types/User.tsx"; | ||
import {IconPlus, IconUserPlus} from "@tabler/icons-react"; | ||
|
||
export const SearchResults = () => { | ||
const [searchParams] = useSearchParams(); | ||
const query = searchParams.get('query') || ''; | ||
|
||
// Mocked users data | ||
const filteredUsers: BasicUserInfo[] = [ | ||
{id: '1', name: 'Emily', surname: 'Johnson', profilePicture: 'https://via.placeholder.com/40', login: 'emily'}, | ||
{id: '2', name: 'Ava', surname: 'Rodriguez', profilePicture: 'https://via.placeholder.com/40', login: 'ava'}, | ||
{id: '3', name: 'Olivia', surname: 'Chen', profilePicture: 'https://via.placeholder.com/40', login: 'olivia'}, | ||
]; | ||
|
||
if (!query) { | ||
return null; | ||
} | ||
|
||
//TODO: Get filtered users based on query | ||
|
||
return ( | ||
<Card w={'100%'} p="lg" withBorder mt="lg"> | ||
<Group justify={'space-between'} mb='md'> | ||
<Text size="lg" fw={500}> | ||
Wyniki wyszukiwania: | ||
</Text> | ||
<Text size="sm" c="dimmed"> | ||
{filteredUsers.length} results | ||
</Text> | ||
</Group> | ||
<Divider mb={'lg'}/> | ||
<Stack gap="md"> | ||
{filteredUsers.length > 0 ? ( | ||
filteredUsers.map((user) => ( | ||
<Group justify={'space-between'} key={user.id}> | ||
<Group key={user.id} mb="sm" gap="sm"> | ||
<Avatar src={user.profilePicture} size={"lg"} radius="xl"/> | ||
<div> | ||
<Text size="md">{user.name} {user.surname}</Text> | ||
<Text size="sm" opacity={0.5}> | ||
@{user.login} | ||
</Text> | ||
</div> | ||
</Group> | ||
<Group> | ||
<ActionIcon variant={'subtle'} color={'gray'} size="lg" radius={'lg'} onClick={() => { | ||
}}> | ||
<IconPlus stroke={0.8}/> | ||
</ActionIcon> | ||
<ActionIcon variant={'subtle'} color={'gray'} size="lg" radius={'lg'} onClick={() => { | ||
}}> | ||
<IconUserPlus stroke={0.8}/> | ||
</ActionIcon> | ||
</Group> | ||
</Group> | ||
)) | ||
) : ( | ||
<Text size="sm" c="dimmed"> | ||
Brak wyników dla "{query}". | ||
</Text> | ||
)} | ||
</Stack> | ||
</Card> | ||
); | ||
}; |
1 change: 1 addition & 0 deletions
1
frontend/src/Features/Search/components/SearchResults/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {SearchResults} from './SearchResults'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters