Skip to content

Commit

Permalink
feature: Enhance Search bar with query parameter
Browse files Browse the repository at this point in the history
-Add SearchResults which will react with query parameter,
- Enhance Search mechanizm.

Refs: 8697653ha
Signed-off-by: Jimmy <[email protected]>
  • Loading branch information
JimTheCat committed Dec 23, 2024
1 parent 13179db commit 0d20585
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 60 deletions.
173 changes: 114 additions & 59 deletions frontend/src/Features/Search/Search.tsx
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>

);
};
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>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {SearchResults} from './SearchResults';
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const suggestedUsers: SuggestedUser[] = [

export const SuggestedUsers = () => {
return (
<Card p="lg" withBorder mt="lg">
<Card w={'100%'} p="lg" withBorder mt="lg">
<Text size="lg" mb="sm">
Suggested Users
</Text>
Expand Down

0 comments on commit 0d20585

Please sign in to comment.