implemented infinity scroll
This commit is contained in:
@@ -1,11 +1,10 @@
|
|||||||
import { Box, Pagination } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import {useCallback, useEffect, useRef, useState} from "react";
|
||||||
import Item from "../components/Item";
|
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 { useNavigate } from "react-router-dom"
|
||||||
import { useLocation } 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";
|
||||||
|
|
||||||
@@ -254,6 +253,16 @@ export default function Home() {
|
|||||||
category: "Other",
|
category: "Other",
|
||||||
rating: 1.0,
|
rating: 1.0,
|
||||||
discount: 0,
|
discount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "212",
|
||||||
|
name: "Item 4",
|
||||||
|
description: "Description 3",
|
||||||
|
price: 50,
|
||||||
|
stock: 300,
|
||||||
|
category: "Other",
|
||||||
|
rating: 1.0,
|
||||||
|
discount: 0,
|
||||||
}
|
}
|
||||||
// Weitere Items hinzufügen
|
// Weitere Items hinzufügen
|
||||||
];
|
];
|
||||||
@@ -274,27 +283,87 @@ export default function Home() {
|
|||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
|
|
||||||
const [currentPage, setCurrentPage] = useState(1); // Zustand für die aktuelle Seite
|
const [itemsToShow, setItemsToShow] = useState(20); // Anzahl Items, die geladen sind
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(9); // Dynamische Anzahl der Items pro Seite
|
|
||||||
const [visibleItems, setVisibleItems] = useState<Item[]>([]); // Zustand für die sichtbaren Items
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||||
const [selectedRating, setSelectedRating] = useState<string | null>(null);
|
const [selectedRating, setSelectedRating] = useState<string | null>(null);
|
||||||
|
|
||||||
const discountedPrices = items.map(item => item.price * (1 - item.discount / 100));
|
const discountedPrices = items.map(
|
||||||
|
(item) => item.price * (1 - item.discount / 100)
|
||||||
|
);
|
||||||
const minPrice = Math.min(...discountedPrices);
|
const minPrice = Math.min(...discountedPrices);
|
||||||
const maxPrice = Math.max(...discountedPrices);
|
const maxPrice = Math.max(...discountedPrices);
|
||||||
const [priceRange, setPriceRange] = useState<[number, number]>([minPrice, maxPrice]);
|
const [priceRange, setPriceRange] = useState<[number, number]>([
|
||||||
|
minPrice,
|
||||||
const location = useLocation();
|
maxPrice,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Filter aus URL übernehmen
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const category = params.get("category");
|
const category = params.get("category");
|
||||||
if (category && categoriesFilter.some(filter => filter.value === category)) {
|
if (category && categoriesFilter.some((f) => f.value === category)) {
|
||||||
setSelectedCategory(category);
|
setSelectedCategory(category);
|
||||||
|
} else {
|
||||||
|
setSelectedCategory(null);
|
||||||
}
|
}
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
|
|
||||||
|
// Filterfunktion bleibt gleich
|
||||||
|
const filteredItems = items
|
||||||
|
.filter((item) => {
|
||||||
|
const discountedPrice = item.price * (1 - item.discount / 100);
|
||||||
|
return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1];
|
||||||
|
})
|
||||||
|
.filter((item) => {
|
||||||
|
if (!selectedCategory) return true;
|
||||||
|
return item.category === selectedCategory;
|
||||||
|
})
|
||||||
|
.filter((item) => {
|
||||||
|
if (!selectedRating) return true;
|
||||||
|
return Math.round(item.rating) >= Number(selectedRating);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Items, die aktuell angezeigt werden
|
||||||
|
const visibleItems = filteredItems.slice(0, itemsToShow);
|
||||||
|
|
||||||
|
// Intersection Observer Ref
|
||||||
|
const loaderRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// Callback für Observer
|
||||||
|
const handleObserver = useCallback(
|
||||||
|
(entries: IntersectionObserverEntry[]) => {
|
||||||
|
const target = entries[0];
|
||||||
|
if (target.isIntersecting) {
|
||||||
|
// Wenn noch mehr Items da sind, lade 20 weitere
|
||||||
|
setItemsToShow((prev) => {
|
||||||
|
if (prev >= filteredItems.length) return prev;
|
||||||
|
return prev + 20;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[filteredItems.length]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Observer Setup
|
||||||
|
useEffect(() => {
|
||||||
|
const option = {
|
||||||
|
root: null,
|
||||||
|
rootMargin: "20px",
|
||||||
|
threshold: 1.0,
|
||||||
|
};
|
||||||
|
const observer = new IntersectionObserver(handleObserver, option);
|
||||||
|
if (loaderRef.current) observer.observe(loaderRef.current);
|
||||||
|
return () => {
|
||||||
|
if (loaderRef.current) observer.unobserve(loaderRef.current);
|
||||||
|
};
|
||||||
|
}, [handleObserver]);
|
||||||
|
|
||||||
|
// Reset itemsToShow bei Filteränderungen
|
||||||
|
useEffect(() => {
|
||||||
|
setItemsToShow(20);
|
||||||
|
}, [selectedCategory, selectedRating, priceRange]);
|
||||||
|
|
||||||
|
// Kategorie-Änderung
|
||||||
const handleCategoryChange = (category: string) => {
|
const handleCategoryChange = (category: string) => {
|
||||||
if (category === "") {
|
if (category === "") {
|
||||||
setSelectedCategory(null);
|
setSelectedCategory(null);
|
||||||
@@ -305,65 +374,20 @@ export default function Home() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handler für die Pagination
|
// Rating-Änderung (bleibt gleich)
|
||||||
const handlePageChange = (event: React.ChangeEvent<unknown>, page: number) => {
|
const handleRatingChange = (rating: string) => {
|
||||||
setCurrentPage(page);
|
if (rating === "") {
|
||||||
};
|
setSelectedRating(null);
|
||||||
|
} else {
|
||||||
const filteredItems = items
|
setSelectedRating(rating);
|
||||||
.filter(item => {
|
}
|
||||||
const discountedPrice = item.price * (1 - item.discount / 100);
|
|
||||||
return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1];
|
|
||||||
})
|
|
||||||
.filter(item => {
|
|
||||||
if (!selectedCategory) return true;
|
|
||||||
return item.category === selectedCategory;
|
|
||||||
})
|
|
||||||
.filter(item => {
|
|
||||||
if (!selectedRating) return true;
|
|
||||||
return Math.round(item.rating) >= Number(selectedRating);
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredCount = filteredItems.length;
|
|
||||||
|
|
||||||
// Aktualisiert die sichtbaren Items bei Seitenwechsel oder Fenstergrößenänderung
|
|
||||||
useEffect(() => {
|
|
||||||
const updateItems = () => {
|
|
||||||
const newItemsPerPage = calculateItemsPerPage();
|
|
||||||
setItemsPerPage(newItemsPerPage);
|
|
||||||
|
|
||||||
const startIndex = (currentPage - 1) * newItemsPerPage;
|
|
||||||
const newVisibleItems = filteredItems.slice(startIndex, startIndex + newItemsPerPage);
|
|
||||||
|
|
||||||
setVisibleItems(prev => (JSON.stringify(prev) !== JSON.stringify(newVisibleItems) ? newVisibleItems : prev));
|
|
||||||
};
|
|
||||||
|
|
||||||
updateItems();
|
|
||||||
|
|
||||||
const handleResize = () => {
|
|
||||||
updateItems();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
|
||||||
}, [currentPage, priceRange, selectedCategory, selectedRating]);
|
|
||||||
|
|
||||||
|
|
||||||
// Dynamische Berechnung der Anzahl der Items pro Seite basierend auf der Fensterbreite
|
|
||||||
const calculateItemsPerPage = () => {
|
|
||||||
const cardHeight = 220; // Höhe einer Karte (inkl. Margin/Padding)
|
|
||||||
const paginationHeight = 60; // Höhe der Pagination
|
|
||||||
const availableHeight = window.innerHeight - paginationHeight - 60; // Verfügbare Höhe (inkl. Padding)
|
|
||||||
const itemsPerRow = Math.floor(window.innerWidth / 220); // Karten pro Zeile (220px Breite inkl. Margin)
|
|
||||||
const rows = Math.floor(availableHeight / cardHeight); // Maximale Anzahl an vollständigen Reihen
|
|
||||||
return itemsPerRow * rows;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home-page-background">
|
<div className="home-page-background">
|
||||||
<div className="filter-container">
|
<div className="filter-container">
|
||||||
<FilterItem
|
<FilterItem
|
||||||
filterName={t('category')}
|
filterName={t("category")}
|
||||||
filterItems={categoriesFilter}
|
filterItems={categoriesFilter}
|
||||||
value={selectedCategory}
|
value={selectedCategory}
|
||||||
onChange={handleCategoryChange}
|
onChange={handleCategoryChange}
|
||||||
@@ -373,14 +397,13 @@ export default function Home() {
|
|||||||
max={maxPrice}
|
max={maxPrice}
|
||||||
onChange={(range) => {
|
onChange={(range) => {
|
||||||
setPriceRange(range);
|
setPriceRange(range);
|
||||||
setCurrentPage(1);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FilterItem
|
<FilterItem
|
||||||
filterName={t('rating')}
|
filterName={t("rating")}
|
||||||
filterItems={ratingFilter}
|
filterItems={ratingFilter}
|
||||||
value={selectedRating}
|
value={selectedRating}
|
||||||
onChange={setSelectedRating}
|
onChange={handleRatingChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-background">
|
<div className="page-background">
|
||||||
@@ -390,14 +413,11 @@ export default function Home() {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Pagination
|
{/* Loader für Intersection Observer */}
|
||||||
count={Math.ceil(filteredCount / itemsPerPage)}
|
<div ref={loaderRef} className="loader-container">
|
||||||
page={currentPage}
|
{/* Hier kann ein Lade-Spinner rein, wenn gewünscht */}
|
||||||
onChange={handlePageChange}
|
{itemsToShow < filteredItems.length ? "Laden..." : "Keine weiteren Items"}
|
||||||
variant="outlined"
|
</div>
|
||||||
shape="rounded"
|
|
||||||
className="pagination"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,15 +43,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cardgrid {
|
.cardgrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 2%;
|
gap: 2%;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); /* Dynamische Spalten */
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-height: calc(100vh - 100px); /* Dynamische Höhe, abhängig von der Pagination */
|
box-sizing: border-box;
|
||||||
box-sizing: border-box; /* Stellt sicher, dass Padding in die Breite einbezogen wird */
|
padding-bottom: 50px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-background {
|
.page-background {
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
overflow: hidden;
|
overflow-y: 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 */
|
||||||
@@ -126,12 +125,12 @@
|
|||||||
|
|
||||||
.home-page-background {
|
.home-page-background {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
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; /* Abstand oben und unten */
|
/* padding: 20px 0 40px 0; /* Abstand oben und unten */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
@@ -144,6 +143,11 @@
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
place-self: start;
|
place-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 60px; /* Abstand zu den Items */
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user