diff --git a/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java b/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java index 86705bf..ad3a9fa 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/config/ControllerPathConfig.java @@ -24,6 +24,7 @@ public class ControllerPathConfig { //ArticleController public static final String ARTICLE_BASE = "/article"; public static final String ARTICLE_GET_ALL = ARTICLE_BASE + "/all"; + public static final String ARTICLE_GET_ALL_WITH_IMAGE = ARTICLE_BASE + "/all/image"; //CustomerController public static final String CUSTOMER_BASE = "/customer"; diff --git a/00-backend/src/main/java/de/htwsaar/webshop/controller/ArticleController.java b/00-backend/src/main/java/de/htwsaar/webshop/controller/ArticleController.java index e9edf46..f751c7a 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/controller/ArticleController.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/controller/ArticleController.java @@ -1,6 +1,7 @@ package de.htwsaar.webshop.controller; import de.htwsaar.webshop.model.ArticleModel; +import de.htwsaar.webshop.model.ArticleWithImageModel; import de.htwsaar.webshop.repository.entities.Article; import de.htwsaar.webshop.service.ArticleService; import jakarta.servlet.http.HttpServletRequest; @@ -14,6 +15,7 @@ import java.util.UUID; import static de.htwsaar.webshop.config.ControllerPathConfig.ARTICLE_BASE; import static de.htwsaar.webshop.config.ControllerPathConfig.ARTICLE_GET_ALL; +import static de.htwsaar.webshop.config.ControllerPathConfig.ARTICLE_GET_ALL_WITH_IMAGE; import static de.htwsaar.webshop.config.ParameterConfig.PARAM_UUID; import static de.htwsaar.webshop.util.LoggerUtil.logRequest; @@ -33,6 +35,12 @@ public class ArticleController { return ResponseEntity.ok(articleService.from(articleService.findAll())); } + @RequestMapping(path = ARTICLE_GET_ALL_WITH_IMAGE, method = RequestMethod.GET, produces = "application/json") + public ResponseEntity> getAllWithImageData(HttpServletRequest request) { + logRequest(request); + return ResponseEntity.ok(articleService.fromWithImage(articleService.findAll())); + } + @RequestMapping(path = ARTICLE_BASE, method = RequestMethod.GET, produces = "application/json") public ResponseEntity getByUUID(HttpServletRequest request, @RequestParam(value = PARAM_UUID) UUID uuid) { diff --git a/00-backend/src/main/java/de/htwsaar/webshop/model/ArticleWithImageModel.java b/00-backend/src/main/java/de/htwsaar/webshop/model/ArticleWithImageModel.java new file mode 100644 index 0000000..663bd9c --- /dev/null +++ b/00-backend/src/main/java/de/htwsaar/webshop/model/ArticleWithImageModel.java @@ -0,0 +1,42 @@ +package de.htwsaar.webshop.model; + +import de.htwsaar.webshop.repository.entities.Image; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +/** + * What the Frontend gets when requesting an Article, POJO + */ +@AllArgsConstructor +@Setter +@Getter +public class ArticleWithImageModel { + private long id; + private UUID uuid; + private String name; + private String description; + private int price100; + private int discount100; + private int stock; + private int stockExpected; + private String category; + private double rating; + private String image; + + public ArticleWithImageModel(ArticleModel article, Image image){ + this.id = article.getId(); + this.uuid = article.getUuid(); + this.name = article.getName(); + this.description = article.getDescription(); + this.price100 = article.getPrice100(); + this.discount100 = article.getDiscount100(); + this.stock = article.getStock(); + this.stockExpected = article.getStockExpected(); + this.category = article.getCategory(); + this.rating = article.getRating(); + this.image = image.getBase64(); + } +} diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/ArticleService.java b/00-backend/src/main/java/de/htwsaar/webshop/service/ArticleService.java index 6cc2c48..0bac9e3 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/service/ArticleService.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/ArticleService.java @@ -1,6 +1,7 @@ package de.htwsaar.webshop.service; import de.htwsaar.webshop.model.ArticleModel; +import de.htwsaar.webshop.model.ArticleWithImageModel; import de.htwsaar.webshop.repository.entities.Article; import java.util.List; @@ -33,4 +34,6 @@ public interface ArticleService { * @return an unmodifiable {@link List} of {@link ArticleModel}s */ List from(List
articles); + + List fromWithImage(List
articles); } diff --git a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/ArticleServiceImpl.java b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/ArticleServiceImpl.java index 4bce015..0637e1c 100644 --- a/00-backend/src/main/java/de/htwsaar/webshop/service/impl/ArticleServiceImpl.java +++ b/00-backend/src/main/java/de/htwsaar/webshop/service/impl/ArticleServiceImpl.java @@ -1,11 +1,14 @@ package de.htwsaar.webshop.service.impl; import de.htwsaar.webshop.model.ArticleModel; +import de.htwsaar.webshop.model.ArticleWithImageModel; import de.htwsaar.webshop.repository.ArticleRepository; +import de.htwsaar.webshop.repository.ImageRepository; import de.htwsaar.webshop.repository.ReviewRepository; import de.htwsaar.webshop.repository.entities.Article; import de.htwsaar.webshop.repository.entities.Review; import de.htwsaar.webshop.service.ArticleService; +import de.htwsaar.webshop.service.ImageService; import jakarta.transaction.Transactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -19,11 +22,13 @@ import java.util.UUID; public class ArticleServiceImpl implements ArticleService { private final ArticleRepository articleRepository; private final ReviewRepository reviewRepository; + private final ImageRepository imageService; @Autowired - public ArticleServiceImpl(ArticleRepository articleRepository, ReviewRepository reviewRepository) { + public ArticleServiceImpl(ArticleRepository articleRepository, ReviewRepository reviewRepository, ImageRepository imageService) { this.articleRepository = articleRepository; this.reviewRepository = reviewRepository; + this.imageService = imageService; } @Override @@ -107,4 +112,10 @@ public class ArticleServiceImpl implements ArticleService { public List from(List
articles) { return articles.stream().map(this::from).toList(); } + + @Override + public List fromWithImage(List
articles) { + return articles.stream().map(this::from).map( + (a) -> new ArticleWithImageModel(a, imageService.findImageByArticle_Uuid(a.getUuid()))).toList(); + } } diff --git a/01-frontend/src/components/Item.tsx b/01-frontend/src/components/Item.tsx index 08b2d1c..79adeb2 100644 --- a/01-frontend/src/components/Item.tsx +++ b/01-frontend/src/components/Item.tsx @@ -1,4 +1,4 @@ -type Item = { +export type Item = { id: number; uuid: string; name: string; @@ -11,4 +11,18 @@ type Item = { discount100: number; }; -export default Item; \ No newline at end of file +type ItemWithImage = { + id: number; + uuid: string; + name: string; + description: string; + price100: number; + stock: number; + stockExpected: number; + category: string; + rating: number; + discount100: number; + image: string; +}; + +export default ItemWithImage; \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/ItemsInfo.tsx b/01-frontend/src/helper/adminpanel/ItemsInfo.tsx index b67b91e..838183e 100644 --- a/01-frontend/src/helper/adminpanel/ItemsInfo.tsx +++ b/01-frontend/src/helper/adminpanel/ItemsInfo.tsx @@ -27,7 +27,7 @@ export default function ItemsInfo() { const { data } = useQuery({ queryKey: ["fetchItems", loginData], - queryFn: () => fetchItems(loginData? loginData : {email: "", password: "", session: "", customerId: -1, isAdmin: false}), + queryFn: () => fetchItems(), retry: 3, retryDelay: 1000, }); diff --git a/01-frontend/src/helper/homepage/ItemCard.tsx b/01-frontend/src/helper/homepage/ItemCard.tsx index b9d6e13..4f11e58 100644 --- a/01-frontend/src/helper/homepage/ItemCard.tsx +++ b/01-frontend/src/helper/homepage/ItemCard.tsx @@ -1,13 +1,13 @@ import { AddShoppingCart } from "@mui/icons-material"; import { Box, Card, CardActionArea, CardContent, CardMedia, IconButton, Paper, Rating, Typography } from "@mui/material"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { useTranslation } from 'react-i18next'; import { useNavigate } from "react-router-dom"; -import Item from "../../components/Item"; +import ItemWithImage from "../../components/Item"; import { useBasket } from "../BasketProvider"; import "../helper.css"; -export default function ItemCard({ item }: { item: Item }) { +export default function ItemCard({ item }: { item: ItemWithImage }) { const { t } = useTranslation(); const navigate = useNavigate() @@ -21,24 +21,7 @@ export default function ItemCard({ item }: { item: Item }) { const handleClick = () => { navigate(`/product/${item.id}`, { state: { item } }); } - const [imageUrl, setImageUrl] = useState("/src/assets/default.jpg"); // Fallback-Bild - - useEffect(() => { - const fetchImage = async () => { - try { - const response = await fetch(`http://localhost:8085/image?uuid=${item.uuid}`); //image/* as base64 - var data = await response.text(); - if(!data.startsWith("data:image/")) { - data = "data:image/jpeg;base64," + data - } - setImageUrl(data); - } catch (error) { - console.error("Fehler beim Laden des Bildes:", error); - } - }; - - fetchImage(); - }, [item.uuid]); + const [imageUrl] = useState(item.image || "/src/assets/default.jpg"); // Fallback-Bild return ( diff --git a/01-frontend/src/helper/productpage/ProductInfo.tsx b/01-frontend/src/helper/productpage/ProductInfo.tsx index 642cd76..2819b49 100644 --- a/01-frontend/src/helper/productpage/ProductInfo.tsx +++ b/01-frontend/src/helper/productpage/ProductInfo.tsx @@ -59,7 +59,7 @@ export default function ProductInfo({ item }: { item: Item }) { const fetchImage = async () => { try { const response = await fetch(`http://localhost:8085/image?uuid=${item.uuid}`); - var data = await response.text(); + let data = await response.text(); if(data.length == 0) { console.error("Got emtpy picture for article ", item.uuid); } diff --git a/01-frontend/src/helper/query/Queries.tsx b/01-frontend/src/helper/query/Queries.tsx index bf2a953..597653d 100644 --- a/01-frontend/src/helper/query/Queries.tsx +++ b/01-frontend/src/helper/query/Queries.tsx @@ -13,6 +13,15 @@ export const fetchItemList = async () => { return data; }; +export const fetchItemListWithImage = async () => { + const response = await fetch('http://localhost:8085/article/all/image'); + if (!response.ok) { + throw new Error('Fehler beim Laden der Items'); + } + const data = await response.json(); + return data; +}; + export const submitRating = async (ratingData: RatingSubmitType) => { const response = await fetch('http://localhost:8085/review?uuid=' + ratingData.articleId + '&rating=' + ratingData.rating * 2, { method: 'POST', @@ -159,7 +168,7 @@ export const fetchAccounts = async (loginData: User) => { return response.json(); }; -export const fetchItems = async (loginData: User) => { //TODO: remove and use above +export const fetchItems = async () => { //TODO: remove and use above const response = await fetch("http://localhost:8085/article/all"); if (!response.ok) { throw new Error("fetching items failed"); diff --git a/01-frontend/src/pages/Home.tsx b/01-frontend/src/pages/Home.tsx index 10b02a8..349de8c 100644 --- a/01-frontend/src/pages/Home.tsx +++ b/01-frontend/src/pages/Home.tsx @@ -3,11 +3,11 @@ import { useQuery } from "@tanstack/react-query"; import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; -import Item from "../components/Item"; +import ItemWithImage from "../components/Item"; import FilterItem from "../helper/homepage/FilterItem"; import ItemCard from "../helper/homepage/ItemCard"; import PriceSlider from "../helper/homepage/PriceSlider"; -import { fetchItemList } from '../helper/query/Queries'; +import { fetchItemListWithImage } from '../helper/query/Queries'; import "./pages.css"; // Import der CSS-Datei export default function Home() { @@ -37,14 +37,14 @@ export default function Home() { const [selectedCategory, setSelectedCategory] = useState(null); const [selectedRating, setSelectedRating] = useState(null); - const { data = [] } = useQuery({ - queryKey: ['fetchItemList'], - queryFn: fetchItemList, + const { data = [] } = useQuery({ + queryKey: ['fetchItemListWithImage'], + queryFn: fetchItemListWithImage, retry: 3, // Versucht es 3-mal erneut retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms) }); - const items:Item[] = useMemo(() => data || [], [data]); + const items:ItemWithImage[] = useMemo(() => data || [], [data]); const discountedPrices = items.map( (item) => item.price100 * (1 - item.discount100 / 100) @@ -68,7 +68,7 @@ export default function Home() { }, [location.search, categoriesFilter]); // Filterfunktion bleibt gleich - const filteredItems = useMemo(() => {return items + const filteredItems: ItemWithImage[] = useMemo(() => {return items .filter((item) => { const discountedPrice = item.price100 * (1 - item.discount100 / 100); return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1]; @@ -100,7 +100,7 @@ export default function Home() { // Items, die aktuell angezeigt werden - const visibleItems: Item[] = filteredItems; + const visibleItems: ItemWithImage[] = filteredItems; // Container Ref const containerRef = useRef(null);