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
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";

View File

@@ -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<List<ArticleWithImageModel>> getAllWithImageData(HttpServletRequest request) {
logRequest(request);
return ResponseEntity.ok(articleService.fromWithImage(articleService.findAll()));
}
@RequestMapping(path = ARTICLE_BASE, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ArticleModel> getByUUID(HttpServletRequest request,
@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;
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 <b>unmodifiable</b> {@link List} of {@link ArticleModel}s
*/
List<ArticleModel> from(List<Article> articles);
List<ArticleWithImageModel> fromWithImage(List<Article> articles);
}

View File

@@ -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<ArticleModel> from(List<Article> articles) {
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;
uuid: string;
name: string;
@@ -11,4 +11,18 @@ type Item = {
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({
queryKey: ["fetchItems", loginData],
queryFn: () => fetchItems(loginData? loginData : {email: "", password: "", session: "", customerId: -1, isAdmin: false}),
queryFn: () => fetchItems(),
retry: 3,
retryDelay: 1000,
});

View File

@@ -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<string>("/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<string>(item.image || "/src/assets/default.jpg"); // Fallback-Bild
return (

View File

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

View File

@@ -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");

View File

@@ -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<string | null>(null);
const [selectedRating, setSelectedRating] = useState<string | null>(null);
const { data = [] } = useQuery<Item[]>({
queryKey: ['fetchItemList'],
queryFn: fetchItemList,
const { data = [] } = useQuery<ItemWithImage[]>({
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<HTMLDivElement>(null);