Implemented infinite scroll to replace malfunctioning pagination
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { AddShoppingCart } from "@mui/icons-material";
|
import { AddShoppingCart } from "@mui/icons-material";
|
||||||
import { Card, CardActionArea, CardContent, CardMedia, IconButton, Paper, Rating, Typography } from "@mui/material";
|
import {Box, Card, CardActionArea, CardContent, CardMedia, IconButton, Paper, Rating, Typography } from "@mui/material";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import Item from "../../components/Item";
|
import Item from "../../components/Item";
|
||||||
import { useBasket } from "../BasketProvider";
|
import { useBasket } from "../BasketProvider";
|
||||||
@@ -36,16 +36,20 @@ export default function ItemCard({ item }: { item: Item }) {
|
|||||||
<Typography gutterBottom variant="h5" component="div">
|
<Typography gutterBottom variant="h5" component="div">
|
||||||
{item.name}
|
{item.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Rating name="half-rating" readOnly defaultValue={item.rating} precision={0.5} />
|
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end" }}>
|
||||||
<Typography variant="body2" sx={{ color: 'text.secondary' }} className="item-description">
|
<Typography variant="body2" sx={{ color: 'text.secondary' }} className="item-description">
|
||||||
{(item.price * (1 - item.discount / 100)).toFixed(2)} €
|
{(item.price * (1 - item.discount / 100)).toFixed(2)} €
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<IconButton aria-label={t('addToCart')} onClick={handleAddToCart}>
|
||||||
|
<AddShoppingCart />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{item.stock > 10 ? (
|
{item.stock > 10 ? (
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
{t('inStock')}
|
{t('inStock')}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : item.stock > 0 ?(
|
) : item.stock > 0 ? (
|
||||||
<Typography variant="body2" sx={{ color: 'orange' }}>
|
<Typography variant="body2" sx={{ color: 'orange' }}>
|
||||||
{t('almostSoldOut')}
|
{t('almostSoldOut')}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -56,11 +60,6 @@ export default function ItemCard({ item }: { item: Item }) {
|
|||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
<CardContent>
|
|
||||||
<IconButton aria-label={t('addToCart')} onClick={handleAddToCart}>
|
|
||||||
<AddShoppingCart />
|
|
||||||
</IconButton>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Item from "../components/Item";
|
|||||||
import FilterItem from "../helper/homepage/FilterItem";
|
import FilterItem from "../helper/homepage/FilterItem";
|
||||||
import ItemCard from "../helper/homepage/ItemCard";
|
import ItemCard from "../helper/homepage/ItemCard";
|
||||||
import "./pages.css"; // Import der CSS-Datei
|
import "./pages.css"; // Import der CSS-Datei
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useLocation, useNavigate } from "react-router-dom"
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import PriceSlider from "../helper/homepage/PriceSlider";
|
import PriceSlider from "../helper/homepage/PriceSlider";
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ export default function Home() {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const items: Item[] = [
|
const items: Item[] = [
|
||||||
{
|
{
|
||||||
@@ -245,14 +246,114 @@ export default function Home() {
|
|||||||
discount: 15,
|
discount: 15,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "112",
|
id: "201",
|
||||||
name: "Item 4",
|
name: "Item 1",
|
||||||
|
description: "Description 1",
|
||||||
|
price: 10,
|
||||||
|
stock: 100,
|
||||||
|
category: "Seeds",
|
||||||
|
rating: 4.5,
|
||||||
|
discount: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "202",
|
||||||
|
name: "Item 2",
|
||||||
|
description: "Description 2",
|
||||||
|
price: 20,
|
||||||
|
stock: 9,
|
||||||
|
category: "GardenSupplies",
|
||||||
|
rating: 4.0,
|
||||||
|
discount: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "203",
|
||||||
|
name: "Item 3",
|
||||||
description: "Description 3",
|
description: "Description 3",
|
||||||
price: 50,
|
price: 30,
|
||||||
|
stock: 10,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 4.8,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "204",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 0,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 1.8,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "205",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
stock: 300,
|
stock: 300,
|
||||||
category: "Other",
|
category: "TechnicalComponents",
|
||||||
rating: 1.0,
|
rating: 2.2,
|
||||||
discount: 0,
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "206",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 300,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 3.8,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "207",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 300,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 4.8,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "208",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 300,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 3.4,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "209",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 300,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 2.9,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "210",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 300,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 4.4,
|
||||||
|
discount: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "211",
|
||||||
|
name: "Item 3",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 30,
|
||||||
|
stock: 300,
|
||||||
|
category: "TechnicalComponents",
|
||||||
|
rating: 4.0,
|
||||||
|
discount: 15,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "212",
|
id: "212",
|
||||||
@@ -329,6 +430,9 @@ export default function Home() {
|
|||||||
// Intersection Observer Ref
|
// Intersection Observer Ref
|
||||||
const loaderRef = useRef<HTMLDivElement | null>(null);
|
const loaderRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// Container Ref
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Callback für Observer
|
// Callback für Observer
|
||||||
const handleObserver = useCallback(
|
const handleObserver = useCallback(
|
||||||
(entries: IntersectionObserverEntry[]) => {
|
(entries: IntersectionObserverEntry[]) => {
|
||||||
@@ -349,7 +453,7 @@ export default function Home() {
|
|||||||
const option = {
|
const option = {
|
||||||
root: null,
|
root: null,
|
||||||
rootMargin: "20px",
|
rootMargin: "20px",
|
||||||
threshold: 1.0,
|
threshold: 1,
|
||||||
};
|
};
|
||||||
const observer = new IntersectionObserver(handleObserver, option);
|
const observer = new IntersectionObserver(handleObserver, option);
|
||||||
if (loaderRef.current) observer.observe(loaderRef.current);
|
if (loaderRef.current) observer.observe(loaderRef.current);
|
||||||
@@ -361,8 +465,27 @@ export default function Home() {
|
|||||||
// Reset itemsToShow bei Filteränderungen
|
// Reset itemsToShow bei Filteränderungen
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setItemsToShow(20);
|
setItemsToShow(20);
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
containerRef.current?.scrollTo(0, 0);
|
||||||
|
});
|
||||||
}, [selectedCategory, selectedRating, priceRange]);
|
}, [selectedCategory, selectedRating, priceRange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (items.length > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
containerRef.current?.scrollTo(0, 0);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Container Höhe:", containerRef.current?.scrollHeight);
|
||||||
|
console.log("Container sichtbare Höhe:", containerRef.current?.clientHeight);
|
||||||
|
console.log("page-background Scrollhöhe:", document.querySelector(".page-background")?.scrollHeight);
|
||||||
|
console.log("page-background Sichtbare Höhe:", document.querySelector(".page-background")?.clientHeight);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Kategorie-Änderung
|
// Kategorie-Änderung
|
||||||
const handleCategoryChange = (category: string) => {
|
const handleCategoryChange = (category: string) => {
|
||||||
if (category === "") {
|
if (category === "") {
|
||||||
@@ -406,7 +529,7 @@ export default function Home() {
|
|||||||
onChange={handleRatingChange}
|
onChange={handleRatingChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-background">
|
<div className="page-background" ref={containerRef}>
|
||||||
<Box className="cardgrid">
|
<Box className="cardgrid">
|
||||||
{visibleItems.map((item) => (
|
{visibleItems.map((item) => (
|
||||||
<ItemCard key={item.id} item={item} />
|
<ItemCard key={item.id} item={item} />
|
||||||
@@ -416,7 +539,6 @@ export default function Home() {
|
|||||||
{/* Loader für Intersection Observer */}
|
{/* Loader für Intersection Observer */}
|
||||||
<div ref={loaderRef} className="loader-container">
|
<div ref={loaderRef} className="loader-container">
|
||||||
{/* Hier kann ein Lade-Spinner rein, wenn gewünscht */}
|
{/* Hier kann ein Lade-Spinner rein, wenn gewünscht */}
|
||||||
{itemsToShow < filteredItems.length ? "Laden..." : "Keine weiteren Items"}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,13 +44,13 @@
|
|||||||
|
|
||||||
.cardgrid {
|
.cardgrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 2%;
|
gap: 24px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 0 auto;
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-bottom: 50px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-background {
|
.page-background {
|
||||||
@@ -61,18 +61,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
overflow-y: auto;
|
overflow: auto;
|
||||||
padding: 20px 0; /* Abstand oben und unten */
|
padding: 20px 0; /* Abstand oben und unten */
|
||||||
box-sizing: border-box; /* Stellt sicher, dass Padding in die Höhe einbezogen wird */
|
box-sizing: border-box; /* Stellt sicher, dass Padding in die Höhe einbezogen wird */
|
||||||
width: 100%; /* Damit der Hintergrund die gesamte Breite abdeckt */
|
width: 100%; /* Damit der Hintergrund die gesamte Breite abdeckt */
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.impressum-container {
|
.impressum-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -127,7 +121,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto !important;
|
||||||
|
scroll-behavior: smooth;
|
||||||
height: calc(100vh - 3rem); /* Damit der Hintergrund die gesamte Seite abdeckt */
|
height: calc(100vh - 3rem); /* Damit der Hintergrund die gesamte Seite abdeckt */
|
||||||
min-height: 600px;
|
min-height: 600px;
|
||||||
/* padding: 20px 0 40px 0; /* Abstand oben und unten */
|
/* padding: 20px 0 40px 0; /* Abstand oben und unten */
|
||||||
@@ -138,10 +133,7 @@
|
|||||||
.filter-container {
|
.filter-container {
|
||||||
width: 17%;
|
width: 17%;
|
||||||
display: grid;
|
display: grid;
|
||||||
margin-left: 20px;
|
margin: 20px -20px 20px 20px;
|
||||||
margin-right: -20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
place-self: start;
|
place-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,5 +141,5 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 60px; /* Abstand zu den Items */
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user