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 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 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 { useTranslation } from 'react-i18next';
import './NavBar.css'; import { useNavigate } from 'react-router-dom';
import Item from '../../components/Item';
import ThemeToggle from '../../theme/ThemeToggle'; import ThemeToggle from '../../theme/ThemeToggle';
import { fetchItemList } from '../query/Queries';
const Search = styled('div')(({ theme }) => ({ import './NavBar.css';
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"
},
}));
export default function NavBar() { export default function NavBar() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -60,6 +27,8 @@ export default function NavBar() {
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null); const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
const [anchorElUser, setAnchorElUser] = 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 pageKeys = ['categories', 'checkout', 'contact'];
const settingKeys = ['account', 'orders', 'logout']; const settingKeys = ['account', 'orders', 'logout'];
@@ -84,9 +53,31 @@ export default function NavBar() {
navigate(`/${link.toLowerCase()}`); 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 ( return (
<AppBar position="static" color="primary" elevation={4}> <AppBar position="static" color="primary" elevation={4}>
<Toolbar <Toolbar
disableGutters disableGutters
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -94,38 +85,45 @@ export default function NavBar() {
px: 3 px: 3
}} }}
> >
<Box sx={{ display: "flex", alignItems: "center", minWidth: "0px" }}> <Box sx={{ display: "flex", alignItems: "center", minWidth: "0px" }}>
<AdbIcon className='navbar-logo' /> <AdbIcon className='navbar-logo' />
<Typography <Typography
variant="h6" variant="h6"
noWrap noWrap
component="a" component="a"
href="/" href="/"
sx={{ sx={{
fontFamily: "monospace", fontFamily: "monospace",
fontWeight: 700, fontWeight: 700,
letterSpacing: ".3rem", letterSpacing: ".3rem",
color: "white", color: "white",
textDecoration: "none", textDecoration: "none",
ml: 1, ml: 1,
}} }}
> >
Digitaler Produktionsshop Digitaler Produktionsshop
</Typography> </Typography>
</Box> </Box>
<Box sx={{ flexGrow: 1, display: "flex", justifyContent: "center", px: 3 }}> <Box sx={{ flexGrow: 1, display: "flex", justifyContent: "center", px: 3 }}>
<Search sx={{ flexGrow: 1, minWidth: "150px", maxWidth: "600px" }}> <Autocomplete
<SearchIconWrapper> freeSolo
<SearchIcon sx={{ color: "white" }} /> options={itemNames} // Item-Namen für Autocomplete
</SearchIconWrapper> onInputChange={handleSearch} // Suche auslösen
<StyledInputBase renderInput={(params) => (
placeholder={t('search') + "..."} <TextField
inputProps={{ "aria-label": t('search') }} {...params}
sx={{ width: "100%" }} 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: "flex", alignItems: "center", gap: 2, marginLeft: 'auto' }}>
<Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}> <Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}>
@@ -184,7 +182,7 @@ export default function NavBar() {
))} ))}
</Menu> </Menu>
</Box> </Box>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
); );
} }

View File

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

View File

@@ -15,6 +15,7 @@ export default function Home() {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const [searchQuery, setSearchQuery] = useState<string | null>(null);
const categoriesFilter = useMemo(() => [ const categoriesFilter = useMemo(() => [
{ value: "", label: t("all") }, { value: "", label: t("all") },
@@ -67,7 +68,7 @@ export default function Home() {
}, [location.search, categoriesFilter]); }, [location.search, categoriesFilter]);
// Filterfunktion bleibt gleich // Filterfunktion bleibt gleich
const filteredItems = items const filteredItems = useMemo(() => {return items
.filter((item) => { .filter((item) => {
const discountedPrice = item.price100 * (1 - item.discount100 / 100); const discountedPrice = item.price100 * (1 - item.discount100 / 100);
return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1]; return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1];
@@ -79,7 +80,21 @@ export default function Home() {
.filter((item) => { .filter((item) => {
if (!selectedRating) return true; if (!selectedRating) return true;
return Math.round(item.rating) >= Number(selectedRating); 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]);