diff --git a/01-frontend/package-lock.json b/01-frontend/package-lock.json index f6517eb..9724050 100644 --- a/01-frontend/package-lock.json +++ b/01-frontend/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", + "@tanstack/react-query": "^5.79.2", "i18next": "^25.2.0", "i18next-browser-languagedetector": "^8.1.0", "i18next-http-backend": "^3.0.2", @@ -1744,6 +1745,32 @@ "win32" ] }, + "node_modules/@tanstack/query-core": { + "version": "5.79.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.79.2.tgz", + "integrity": "sha512-kr+KQrBuqd6495eP9S41BoftFI1H50XA9O+6FmbnTx/Te6bjiq1mj8rt9rJjW3YZSO2aaUNUres0TWesJW1j1g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.79.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.79.2.tgz", + "integrity": "sha512-kadeprsH6bWuhHCpqukXHRykJkxcLBxAaF0cQ05yawPmLZ/KiCpR1DyQenonF7A/70rnRUxhJD0RJejqk9wImQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.79.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/01-frontend/package.json b/01-frontend/package.json index a97d500..dba239c 100644 --- a/01-frontend/package.json +++ b/01-frontend/package.json @@ -15,6 +15,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", + "@tanstack/react-query": "^5.79.2", "i18next": "^25.2.0", "i18next-browser-languagedetector": "^8.1.0", "i18next-http-backend": "^3.0.2", diff --git a/01-frontend/src/App.tsx b/01-frontend/src/App.tsx index 0f595ff..3e13277 100644 --- a/01-frontend/src/App.tsx +++ b/01-frontend/src/App.tsx @@ -1,21 +1,25 @@ -import ReactDOM from 'react-dom/client'; -import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { StyledEngineProvider } from '@mui/material/styles'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { BrowserRouter, Route, Routes } from 'react-router-dom'; import './App.css'; +import { BasketProvider } from './helper/BasketProvider'; import NavBar from './helper/navbar/NavBar'; +import Account from './pages/Account'; +import Category from './pages/Category'; +import Contact from './pages/Contact'; import Home from './pages/Home'; import NoPage from './pages/NoPage'; -import Product from './pages/Product'; -import Payment from './pages/Payment'; -import Contact from './pages/Contact'; -import Category from './pages/Category'; -import { BasketProvider } from './helper/BasketProvider'; -import { CustomThemeProvider } from './theme/ThemeContext'; import Orders from './pages/Orders'; -import Account from './pages/Account'; +import Payment from './pages/Payment'; +import Product from './pages/Product'; +import { CustomThemeProvider } from './theme/ThemeContext'; export default function App() { + + const queryClient = new QueryClient(); + return ( + @@ -36,5 +40,6 @@ export default function App() { + ) } diff --git a/01-frontend/src/components/Item.tsx b/01-frontend/src/components/Item.tsx index fbb31b4..8c1ddd9 100644 --- a/01-frontend/src/components/Item.tsx +++ b/01-frontend/src/components/Item.tsx @@ -1,12 +1,13 @@ type Item = { id: string; + uuid: string; name: string; description: string; - price: number; + price100: number; stock: number; category: string; rating: number; - discount: number; + discount100: number; }; export default Item; \ No newline at end of file diff --git a/01-frontend/src/helper/NavBar.css b/01-frontend/src/helper/NavBar.css new file mode 100644 index 0000000..dc16864 --- /dev/null +++ b/01-frontend/src/helper/NavBar.css @@ -0,0 +1,70 @@ +/* Navbar styles */ +.navbar { + background-color: #1976d2; /* Material-UI Primary Color */ + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + position: absolute; + top: 0; + height: 3rem; +} + +/* Logo styles */ +.navbar-logo { + font-family: 'Roboto', sans-serif; + font-weight: bold; + color: white; + text-decoration: none; + margin-right: auto; +} + +/* Menu styles */ +.navbar-menu { + display: flex; + align-items: center; + margin-left: auto; +} + +/* Search styles */ +.search { + position: relative; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0.15); +} +.search:hover { + background-color: rgba(255, 255, 255, 0.25); +} +.search-icon-wrapper { + padding: 8px; + height: 100%; + position: absolute; + pointer-events: none; + display: flex; + align-items: center; + justify-content: center; +} +.search-input { + color: inherit; + width: 100%; + padding: 8px 8px 8px 40px; + font-size: 1rem; +} + +/* User avatar styles */ +.navbar-user { + margin-left: 16px; +} + +/* Typography styles */ +.navbar-typography { + font-family: 'monospace'; + font-weight: 700; + letter-spacing: .3rem; + color: inherit; + text-decoration: none; +} + +/* Button styles */ +.navbar-button { + margin: 2; + color: white; + display: block; +} \ No newline at end of file diff --git a/01-frontend/src/helper/NavBar.tsx b/01-frontend/src/helper/NavBar.tsx new file mode 100644 index 0000000..27838da --- /dev/null +++ b/01-frontend/src/helper/NavBar.tsx @@ -0,0 +1,216 @@ +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 Container from '@mui/material/Container'; +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 SearchIcon from '@mui/icons-material/Search'; +import { useNavigate } from 'react-router-dom'; + +const pages = ['Categories', 'Checkout', 'Contact']; +const settings = ['Account', 'Orders', 'Logout']; + +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: 'inherit', + width: '100%', + '& .MuiInputBase-input': { + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)})`, + transition: theme.transitions.create('width'), + [theme.breakpoints.up('sm')]: { + width: '12ch', + '&:focus': { + width: '20ch', + }, + }, + }, + })); + +export default function NavBar() { + const navigate = useNavigate(); + const [anchorElNav, setAnchorElNav] = React.useState(null); + const [anchorElUser, setAnchorElUser] = React.useState(null); + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = (link: string) => { + setAnchorElNav(null); + navigate(`/${link.toLowerCase()}`); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + + return ( + + + + + + Digitaler Produktionsshop + + + + + + + + + + + + + + + {pages.map((page) => ( + handleCloseNavMenu(page)}> + {page} + + ))} + + + + + DPS + + + {pages.map((page) => ( + + ))} + + + + + + + + + {settings.map((setting) => ( + + {setting} + + ))} + + + + + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/homepage/ItemCard.tsx b/01-frontend/src/helper/homepage/ItemCard.tsx index b803ce4..14cd554 100644 --- a/01-frontend/src/helper/homepage/ItemCard.tsx +++ b/01-frontend/src/helper/homepage/ItemCard.tsx @@ -39,7 +39,7 @@ export default function ItemCard({ item }: { item: Item }) { - {(item.price * (1 - item.discount / 100)).toFixed(2)} € + {(item.price100 * (1 - item.discount100 / 100)).toFixed(2)} € { + const response = await fetch('http://localhost:8085/item/all'); + console.log("API Response:", response); + if (!response.ok) { + throw new Error('Fehler beim Laden der Items'); + } + const data = await response.json(); + console.log("Fetched Items:", data); + return data; +}; \ No newline at end of file diff --git a/01-frontend/src/pages/Home.tsx b/01-frontend/src/pages/Home.tsx index 00d5299..f78dd11 100644 --- a/01-frontend/src/pages/Home.tsx +++ b/01-frontend/src/pages/Home.tsx @@ -1,12 +1,14 @@ -import { Box } from "@mui/material"; -import {useCallback, useEffect, useRef, useState} from "react"; +import { Box, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useLocation, useNavigate } from "react-router-dom"; import Item from "../components/Item"; import FilterItem from "../helper/homepage/FilterItem"; import ItemCard from "../helper/homepage/ItemCard"; -import "./pages.css"; // Import der CSS-Datei -import { useLocation, useNavigate } from "react-router-dom" -import { useTranslation } from "react-i18next"; import PriceSlider from "../helper/homepage/PriceSlider"; +import { fetchItemList } from '../helper/query/Queries'; +import "./pages.css"; // Import der CSS-Datei export default function Home() { @@ -14,367 +16,13 @@ export default function Home() { const navigate = useNavigate(); const location = useLocation(); - const items: Item[] = [ - { - id: "1", - name: "Item 1", - description: "Description 1", - price: 10, - stock: 100, - category: "Seeds", - rating: 4.5, - discount: 10, - }, - { - id: "2", - name: "Item 2", - description: "Description 2", - price: 20, - stock: 9, - category: "GardenSupplies", - rating: 4.0, - discount: 20, - }, - { - id: "3", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 10, - category: "TechnicalComponents", - rating: 4.8, - discount: 15, - }, - { - id: "4", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 0, - category: "TechnicalComponents", - rating: 1.8, - discount: 15, - }, - { - id: "5", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 2.2, - discount: 15, - }, - { - id: "6", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 3.8, - discount: 15, - }, - { - id: "7", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 4.8, - discount: 15, - }, - { - id: "8", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 3.4, - discount: 15, - }, - { - id: "9", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 2.9, - discount: 15, - }, - { - id: "10", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 4.4, - discount: 15, - }, - { - id: "11", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 4.0, - discount: 15, - }, - { - id: "12", - name: "Item 4", - description: "Description 3", - price: 50, - stock: 300, - category: "Other", - rating: 1.0, - discount: 0, - }, - { - id: "101", - name: "Item 1", - description: "Description 1", - price: 10, - stock: 100, - category: "Seeds", - rating: 4.5, - discount: 10, - }, - { - id: "102", - name: "Item 2", - description: "Description 2", - price: 20, - stock: 9, - category: "GardenSupplies", - rating: 4.0, - discount: 20, - }, - { - id: "103", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 10, - category: "TechnicalComponents", - rating: 4.8, - discount: 15, - }, - { - id: "104", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 0, - category: "TechnicalComponents", - rating: 1.8, - discount: 15, - }, - { - id: "105", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 2.2, - discount: 15, - }, - { - id: "106", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 3.8, - discount: 15, - }, - { - id: "107", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 4.8, - discount: 15, - }, - { - id: "108", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 3.4, - discount: 15, - }, - { - id: "109", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 2.9, - discount: 15, - }, - { - id: "110", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 4.4, - discount: 15, - }, - { - id: "111", - name: "Item 3", - description: "Description 3", - price: 30, - stock: 300, - category: "TechnicalComponents", - rating: 4.0, - discount: 15, - }, - { - id: "201", - 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", - 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, - category: "TechnicalComponents", - rating: 2.2, - 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", - name: "Item 4", - description: "Description 3", - price: 50, - stock: 300, - category: "Other", - rating: 1.0, - discount: 0, - } - // Weitere Items hinzufügen - ]; - - const categoriesFilter = [ - { value: "", label: t('allCategories') }, + const categoriesFilter = useMemo(() => [ + { value: "", label: t("all") }, { value: "Seeds", label: t("seeds") }, { value: "GardenSupplies", label: t("gardenSupplies") }, { value: "TechnicalComponents", label: t("technicalComponents") }, { value: "Other", label: t("other") } - ]; + ], [t]); const ratingFilter = [ { value: "", label: t('allRatings') }, @@ -384,15 +32,24 @@ export default function Home() { })) ]; - const [itemsToShow, setItemsToShow] = useState(20); // Anzahl Items, die geladen sind + const [itemsToShow, setItemsToShow] = useState(20); // Anzahl Items, die geladen sind const [selectedCategory, setSelectedCategory] = useState(null); const [selectedRating, setSelectedRating] = useState(null); + const { data = [] } = useQuery({ + queryKey: ['fetchItemList'], + queryFn: fetchItemList, + retry: 3, // Versucht es 3-mal erneut + retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms) + }); + + const items:Item[] = useMemo(() => data || [], [data]); + const discountedPrices = items.map( - (item) => item.price * (1 - item.discount / 100) + (item) => item.price100 * (1 - item.discount100 / 100) ); - const minPrice = Math.min(...discountedPrices); - const maxPrice = Math.max(...discountedPrices); + const minPrice = discountedPrices.length > 0 ? Math.min(...discountedPrices) : 0; + const maxPrice = discountedPrices.length > 0 ? Math.max(...discountedPrices) : 1000; const [priceRange, setPriceRange] = useState<[number, number]>([ minPrice, maxPrice, @@ -407,12 +64,12 @@ export default function Home() { } else { setSelectedCategory(null); } - }, [location.search]); + }, [location.search, categoriesFilter]); // Filterfunktion bleibt gleich const filteredItems = items .filter((item) => { - const discountedPrice = item.price * (1 - item.discount / 100); + const discountedPrice = item.price100 * (1 - item.discount100 / 100); return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1]; }) .filter((item) => { @@ -424,8 +81,10 @@ export default function Home() { return Math.round(item.rating) >= Number(selectedRating); }); + + // Items, die aktuell angezeigt werden - const visibleItems = filteredItems.slice(0, itemsToShow); + const visibleItems:Item[] = filteredItems.slice(0, itemsToShow); // Intersection Observer Ref const loaderRef = useRef(null); @@ -456,9 +115,10 @@ export default function Home() { threshold: 1, }; const observer = new IntersectionObserver(handleObserver, option); - if (loaderRef.current) observer.observe(loaderRef.current); + const currentLoaderRef = loaderRef.current; + if (currentLoaderRef) observer.observe(currentLoaderRef); return () => { - if (loaderRef.current) observer.unobserve(loaderRef.current); + if (currentLoaderRef) observer.unobserve(currentLoaderRef); }; }, [handleObserver]); @@ -513,6 +173,7 @@ export default function Home() { } }; + return (
@@ -549,7 +210,8 @@ export default function Home() { {/* Loader für Intersection Observer */}
- {/* Hier kann ein Lade-Spinner rein, wenn gewünscht */} + Im Moment keine Items verfpgbar +