Added working search bar with autocomplete in NavBar.
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user