Added working search bar with autocomplete in NavBar.

This commit is contained in:
FlorianSpeicher
2025-06-03 22:11:58 +02:00
parent c1dc022128
commit 770667087e
3 changed files with 102 additions and 89 deletions

View File

@@ -1,58 +1,25 @@
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu';
import MenuIcon from '@mui/icons-material/Menu';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';
import MenuItem from '@mui/material/MenuItem';
import AdbIcon from '@mui/icons-material/Adb';
import { alpha, InputBase, styled } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu';
import SearchIcon from '@mui/icons-material/Search';
import { useNavigate } from 'react-router-dom';
import { Autocomplete, TextField } from '@mui/material';
import AppBar from '@mui/material/AppBar';
import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import Toolbar from '@mui/material/Toolbar';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { useQuery } from '@tanstack/react-query';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import './NavBar.css';
import { useNavigate } from 'react-router-dom';
import Item from '../../components/Item';
import ThemeToggle from '../../theme/ThemeToggle';
const Search = styled('div')(({ theme }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: alpha(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
}));
const SearchIconWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}));
const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: 'white',
flexGrow: 1,
maxWidth: '100%',
'& .MuiInputBase-input': {
padding: theme.spacing(1, 1, 1, 0),
paddingLeft: `calc(1em + ${theme.spacing(3)})`,
transition: "none"
},
}));
import { fetchItemList } from '../query/Queries';
import './NavBar.css';
export default function NavBar() {
const { t } = useTranslation();
@@ -60,6 +27,8 @@ export default function NavBar() {
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);
const [itemNames, setItemNames] = React.useState<string[]>([]); // Für Autocomplete
const pageKeys = ['categories', 'checkout', 'contact'];
const settingKeys = ['account', 'orders', 'logout'];
@@ -84,9 +53,31 @@ export default function NavBar() {
navigate(`/${link.toLowerCase()}`);
};
// useQuery, um die Item-Namen zu laden
const { data: items = [] } = useQuery<Item[]>({
queryKey: ["fetchItemList"],
queryFn: fetchItemList,
});
React.useEffect(() => {
// Extrahiere die Namen der Items für Autocomplete
setItemNames(items.map((item) => item.name));
}, [items]);
const handleSearch = (event: React.SyntheticEvent, value: string | null) => {
if (!value) {
// Wenn der Suchwert leer ist, navigiere zur Homepage ohne Suchparameter
navigate("/");
} else {
// Navigiere zur Homepage mit dem Suchparameter
navigate(`/?search=${encodeURIComponent(value)}`);
}
};
return (
<AppBar position="static" color="primary" elevation={4}>
<Toolbar
<Toolbar
disableGutters
sx={{
display: 'flex',
@@ -94,38 +85,45 @@ export default function NavBar() {
px: 3
}}
>
<Box sx={{ display: "flex", alignItems: "center", minWidth: "0px" }}>
<AdbIcon className='navbar-logo' />
<Typography
variant="h6"
noWrap
component="a"
href="/"
sx={{
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: ".3rem",
color: "white",
textDecoration: "none",
ml: 1,
}}
>
Digitaler Produktionsshop
</Typography>
</Box>
<Box sx={{ display: "flex", alignItems: "center", minWidth: "0px" }}>
<AdbIcon className='navbar-logo' />
<Typography
variant="h6"
noWrap
component="a"
href="/"
sx={{
fontFamily: "monospace",
fontWeight: 700,
letterSpacing: ".3rem",
color: "white",
textDecoration: "none",
ml: 1,
}}
>
Digitaler Produktionsshop
</Typography>
</Box>
<Box sx={{ flexGrow: 1, display: "flex", justifyContent: "center", px: 3 }}>
<Search sx={{ flexGrow: 1, minWidth: "150px", maxWidth: "600px" }}>
<SearchIconWrapper>
<SearchIcon sx={{ color: "white" }} />
</SearchIconWrapper>
<StyledInputBase
placeholder={t('search') + "..."}
inputProps={{ "aria-label": t('search') }}
sx={{ width: "100%" }}
<Box sx={{ flexGrow: 1, display: "flex", justifyContent: "center", px: 3 }}>
<Autocomplete
freeSolo
options={itemNames} // Item-Namen für Autocomplete
onInputChange={handleSearch} // Suche auslösen
renderInput={(params) => (
<TextField
{...params}
placeholder={t("search") + "..."}
InputProps={{
...params.InputProps,
startAdornment: (
<SearchIcon sx={{ color: "white", mr: 1 }} />
),
}}
/>
</Search>
</Box>
)}
/>
</Box>
<Box sx={{ display: "flex", alignItems: "center", gap: 2, marginLeft: 'auto' }}>
<Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}>
@@ -184,7 +182,7 @@ export default function NavBar() {
))}
</Menu>
</Box>
</Toolbar>
</Toolbar>
</AppBar>
);
}

View File

@@ -46,7 +46,7 @@ export default function ProductInfo({ item }: { item: Item }) {
console.log(`Added {quantity} of €{item.name} to basket`);
};
const discountedPrice = item.price * (1 - item.discount / 100);
const discountedPrice = item.price100 * (1 - item.discount100 / 100);
const handleImageLoad = (event: React.SyntheticEvent<HTMLImageElement>) => {
const { naturalWidth, naturalHeight } = event.currentTarget;
@@ -89,7 +89,7 @@ export default function ProductInfo({ item }: { item: Item }) {
</Box>
<Stack direction="row" alignItems="center" spacing={2}>
{item.discount > 0 ? (
{item.discount100 > 0 ? (
<>
<Typography variant="h4" color="green">
{discountedPrice.toFixed(2)}
@@ -99,15 +99,15 @@ export default function ProductInfo({ item }: { item: Item }) {
color="text.secondary"
sx={{ textDecoration: 'line-through' }}
>
{item.price.toFixed(2)}
{item.price100.toFixed(2)}
</Typography>
<Typography variant="h6" color="error">
-{item.discount} %
-{item.discount100} %
</Typography>
</>
) : (
<Typography variant="h4" color="green">
{item.price.toFixed(2)}
{item.price100.toFixed(2)}
</Typography>
)}
</Stack>

View File

@@ -15,6 +15,7 @@ export default function Home() {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const [searchQuery, setSearchQuery] = useState<string | null>(null);
const categoriesFilter = useMemo(() => [
{ value: "", label: t("all") },
@@ -67,7 +68,7 @@ export default function Home() {
}, [location.search, categoriesFilter]);
// Filterfunktion bleibt gleich
const filteredItems = items
const filteredItems = useMemo(() => {return items
.filter((item) => {
const discountedPrice = item.price100 * (1 - item.discount100 / 100);
return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1];
@@ -79,7 +80,21 @@ export default function Home() {
.filter((item) => {
if (!selectedRating) return true;
return Math.round(item.rating) >= Number(selectedRating);
})
.filter((item) => {
if (!searchQuery) return true;
return (item.name.toLowerCase().includes(searchQuery.toLowerCase())
);
});
}, [items, priceRange, selectedCategory, selectedRating, searchQuery]);
// Lese die Suchanfrage aus der URL
useEffect(() => {
const params = new URLSearchParams(location.search);
const query = params.get("search");
setSearchQuery(query);
}, [location.search]);