Added Article With Image

This commit is contained in:
FlorianSpeicher
2025-06-16 17:22:34 +02:00
parent b3ef4ba144
commit 7176ca7c93
11 changed files with 106 additions and 35 deletions

View File

@@ -24,6 +24,7 @@ public class ControllerPathConfig {
//ArticleController //ArticleController
public static final String ARTICLE_BASE = "/article"; public static final String ARTICLE_BASE = "/article";
public static final String ARTICLE_GET_ALL = ARTICLE_BASE + "/all"; public static final String ARTICLE_GET_ALL = ARTICLE_BASE + "/all";
public static final String ARTICLE_GET_ALL_WITH_IMAGE = ARTICLE_BASE + "/all/image";
//CustomerController //CustomerController
public static final String CUSTOMER_BASE = "/customer"; public static final String CUSTOMER_BASE = "/customer";

View File

@@ -1,6 +1,7 @@
package de.htwsaar.webshop.controller; package de.htwsaar.webshop.controller;
import de.htwsaar.webshop.model.ArticleModel; import de.htwsaar.webshop.model.ArticleModel;
import de.htwsaar.webshop.model.ArticleWithImageModel;
import de.htwsaar.webshop.repository.entities.Article; import de.htwsaar.webshop.repository.entities.Article;
import de.htwsaar.webshop.service.ArticleService; import de.htwsaar.webshop.service.ArticleService;
import jakarta.servlet.http.HttpServletRequest; 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_BASE;
import static de.htwsaar.webshop.config.ControllerPathConfig.ARTICLE_GET_ALL; 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.config.ParameterConfig.PARAM_UUID;
import static de.htwsaar.webshop.util.LoggerUtil.logRequest; import static de.htwsaar.webshop.util.LoggerUtil.logRequest;
@@ -33,6 +35,12 @@ public class ArticleController {
return ResponseEntity.ok(articleService.from(articleService.findAll())); return ResponseEntity.ok(articleService.from(articleService.findAll()));
} }
@RequestMapping(path = ARTICLE_GET_ALL_WITH_IMAGE, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<List<ArticleWithImageModel>> getAllWithImageData(HttpServletRequest request) {
logRequest(request);
return ResponseEntity.ok(articleService.fromWithImage(articleService.findAll()));
}
@RequestMapping(path = ARTICLE_BASE, method = RequestMethod.GET, produces = "application/json") @RequestMapping(path = ARTICLE_BASE, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ArticleModel> getByUUID(HttpServletRequest request, public ResponseEntity<ArticleModel> getByUUID(HttpServletRequest request,
@RequestParam(value = PARAM_UUID) UUID uuid) { @RequestParam(value = PARAM_UUID) UUID uuid) {

View File

@@ -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();
}
}

View File

@@ -1,6 +1,7 @@
package de.htwsaar.webshop.service; package de.htwsaar.webshop.service;
import de.htwsaar.webshop.model.ArticleModel; import de.htwsaar.webshop.model.ArticleModel;
import de.htwsaar.webshop.model.ArticleWithImageModel;
import de.htwsaar.webshop.repository.entities.Article; import de.htwsaar.webshop.repository.entities.Article;
import java.util.List; import java.util.List;
@@ -33,4 +34,6 @@ public interface ArticleService {
* @return an <b>unmodifiable</b> {@link List} of {@link ArticleModel}s * @return an <b>unmodifiable</b> {@link List} of {@link ArticleModel}s
*/ */
List<ArticleModel> from(List<Article> articles); List<ArticleModel> from(List<Article> articles);
List<ArticleWithImageModel> fromWithImage(List<Article> articles);
} }

View File

@@ -1,11 +1,14 @@
package de.htwsaar.webshop.service.impl; package de.htwsaar.webshop.service.impl;
import de.htwsaar.webshop.model.ArticleModel; import de.htwsaar.webshop.model.ArticleModel;
import de.htwsaar.webshop.model.ArticleWithImageModel;
import de.htwsaar.webshop.repository.ArticleRepository; import de.htwsaar.webshop.repository.ArticleRepository;
import de.htwsaar.webshop.repository.ImageRepository;
import de.htwsaar.webshop.repository.ReviewRepository; import de.htwsaar.webshop.repository.ReviewRepository;
import de.htwsaar.webshop.repository.entities.Article; import de.htwsaar.webshop.repository.entities.Article;
import de.htwsaar.webshop.repository.entities.Review; import de.htwsaar.webshop.repository.entities.Review;
import de.htwsaar.webshop.service.ArticleService; import de.htwsaar.webshop.service.ArticleService;
import de.htwsaar.webshop.service.ImageService;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -19,11 +22,13 @@ import java.util.UUID;
public class ArticleServiceImpl implements ArticleService { public class ArticleServiceImpl implements ArticleService {
private final ArticleRepository articleRepository; private final ArticleRepository articleRepository;
private final ReviewRepository reviewRepository; private final ReviewRepository reviewRepository;
private final ImageRepository imageService;
@Autowired @Autowired
public ArticleServiceImpl(ArticleRepository articleRepository, ReviewRepository reviewRepository) { public ArticleServiceImpl(ArticleRepository articleRepository, ReviewRepository reviewRepository, ImageRepository imageService) {
this.articleRepository = articleRepository; this.articleRepository = articleRepository;
this.reviewRepository = reviewRepository; this.reviewRepository = reviewRepository;
this.imageService = imageService;
} }
@Override @Override
@@ -107,4 +112,10 @@ public class ArticleServiceImpl implements ArticleService {
public List<ArticleModel> from(List<Article> articles) { public List<ArticleModel> from(List<Article> articles) {
return articles.stream().map(this::from).toList(); return articles.stream().map(this::from).toList();
} }
@Override
public List<ArticleWithImageModel> fromWithImage(List<Article> articles) {
return articles.stream().map(this::from).map(
(a) -> new ArticleWithImageModel(a, imageService.findImageByArticle_Uuid(a.getUuid()))).toList();
}
} }

View File

@@ -1,4 +1,4 @@
type Item = { export type Item = {
id: number; id: number;
uuid: string; uuid: string;
name: string; name: string;
@@ -11,4 +11,18 @@ type Item = {
discount100: number; discount100: number;
}; };
export default Item; 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;

View File

@@ -27,7 +27,7 @@ export default function ItemsInfo() {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ["fetchItems", loginData], queryKey: ["fetchItems", loginData],
queryFn: () => fetchItems(loginData? loginData : {email: "", password: "", session: "", customerId: -1, isAdmin: false}), queryFn: () => fetchItems(),
retry: 3, retry: 3,
retryDelay: 1000, retryDelay: 1000,
}); });

View File

@@ -1,13 +1,13 @@
import { AddShoppingCart } from "@mui/icons-material"; import { AddShoppingCart } from "@mui/icons-material";
import { Box, 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 { useEffect, useState } from "react"; import { useState } from "react";
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Item from "../../components/Item"; import ItemWithImage from "../../components/Item";
import { useBasket } from "../BasketProvider"; import { useBasket } from "../BasketProvider";
import "../helper.css"; import "../helper.css";
export default function ItemCard({ item }: { item: Item }) { export default function ItemCard({ item }: { item: ItemWithImage }) {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate() const navigate = useNavigate()
@@ -21,24 +21,7 @@ export default function ItemCard({ item }: { item: Item }) {
const handleClick = () => { const handleClick = () => {
navigate(`/product/${item.id}`, { state: { item } }); navigate(`/product/${item.id}`, { state: { item } });
} }
const [imageUrl, setImageUrl] = useState<string>("/src/assets/default.jpg"); // Fallback-Bild const [imageUrl] = useState<string>(item.image || "/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]);
return ( return (

View File

@@ -59,7 +59,7 @@ export default function ProductInfo({ item }: { item: Item }) {
const fetchImage = async () => { const fetchImage = async () => {
try { try {
const response = await fetch(`http://localhost:8085/image?uuid=${item.uuid}`); 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) { if(data.length == 0) {
console.error("Got emtpy picture for article ", item.uuid); console.error("Got emtpy picture for article ", item.uuid);
} }

View File

@@ -13,6 +13,15 @@ export const fetchItemList = async () => {
return data; 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) => { export const submitRating = async (ratingData: RatingSubmitType) => {
const response = await fetch('http://localhost:8085/review?uuid=' + ratingData.articleId + '&rating=' + ratingData.rating * 2, { const response = await fetch('http://localhost:8085/review?uuid=' + ratingData.articleId + '&rating=' + ratingData.rating * 2, {
method: 'POST', method: 'POST',
@@ -159,7 +168,7 @@ export const fetchAccounts = async (loginData: User) => {
return response.json(); 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"); const response = await fetch("http://localhost:8085/article/all");
if (!response.ok) { if (!response.ok) {
throw new Error("fetching items failed"); throw new Error("fetching items failed");

View File

@@ -3,11 +3,11 @@ import { useQuery } from "@tanstack/react-query";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import Item from "../components/Item"; import ItemWithImage 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 PriceSlider from "../helper/homepage/PriceSlider"; 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 import "./pages.css"; // Import der CSS-Datei
export default function Home() { export default function Home() {
@@ -37,14 +37,14 @@ export default function Home() {
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 { data = [] } = useQuery<Item[]>({ const { data = [] } = useQuery<ItemWithImage[]>({
queryKey: ['fetchItemList'], queryKey: ['fetchItemListWithImage'],
queryFn: fetchItemList, queryFn: fetchItemListWithImage,
retry: 3, // Versucht es 3-mal erneut retry: 3, // Versucht es 3-mal erneut
retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms) retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
}); });
const items:Item[] = useMemo(() => data || [], [data]); const items:ItemWithImage[] = useMemo(() => data || [], [data]);
const discountedPrices = items.map( const discountedPrices = items.map(
(item) => item.price100 * (1 - item.discount100 / 100) (item) => item.price100 * (1 - item.discount100 / 100)
@@ -68,7 +68,7 @@ export default function Home() {
}, [location.search, categoriesFilter]); }, [location.search, categoriesFilter]);
// Filterfunktion bleibt gleich // Filterfunktion bleibt gleich
const filteredItems = useMemo(() => {return items const filteredItems: ItemWithImage[] = useMemo(() => {return items
.filter((item) => { .filter((item) => {
const discountedPrice = item.price100 * (1 - item.discount100 / 100); const discountedPrice = item.price100 * (1 - item.discount100 / 100);
return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1]; return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1];
@@ -100,7 +100,7 @@ export default function Home() {
// Items, die aktuell angezeigt werden // Items, die aktuell angezeigt werden
const visibleItems: Item[] = filteredItems; const visibleItems: ItemWithImage[] = filteredItems;
// Container Ref // Container Ref
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);