diff --git a/01-frontend/src/App.css b/01-frontend/src/App.css index c00f0e8..73492be 100644 --- a/01-frontend/src/App.css +++ b/01-frontend/src/App.css @@ -1,63 +1,63 @@ /* App.css - CSS-Variablen-basierte Version */ :root { - --background-color: #fafafa; - --text-color: #000000; - --navbar-height: 6vh; - --page-height: 94vh; + --background-color: #fafafa; + --text-color: #000000; + --navbar-height: 6vh; + --page-height: 94vh; } #root { - width: 100%; - min-height: 100vh; - background-color: var(--background-color) !important; - color: var(--text-color) !important; - transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1); + width: 100%; + min-height: 100vh; + background-color: var(--background-color) !important; + color: var(--text-color) !important; + transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1); } html, body { - margin: 0; - padding: 0; - overflow: hidden; - background-color: var(--background-color) !important; - color: var(--text-color) !important; - transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1); + margin: 0; + padding: 0; + overflow: hidden; + background-color: var(--background-color) !important; + color: var(--text-color) !important; + transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1); } .logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; } .logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); + filter: drop-shadow(0 0 2em #646cffaa); } .logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); + filter: drop-shadow(0 0 2em #61dafbaa); } @keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } @media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } } .card { - padding: 2em; - border-radius: 8px; + padding: 2em; + border-radius: 8px; } .read-the-docs { - opacity: 0.7; + opacity: 0.7; } diff --git a/01-frontend/src/App.tsx b/01-frontend/src/App.tsx index 1c40177..8f7bdcc 100644 --- a/01-frontend/src/App.tsx +++ b/01-frontend/src/App.tsx @@ -1,8 +1,8 @@ -import { StyledEngineProvider } from '@mui/material/styles'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -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 {BasketProvider} from './helper/BasketProvider'; import NavBar from './helper/navbar/NavBar'; import Account from './pages/Account'; import Contact from './pages/Contact'; @@ -11,42 +11,42 @@ import NoPage from './pages/NoPage'; import Orders from './pages/Orders'; import Payment from './pages/Payment'; import Product from './pages/Product'; -import { CustomThemeProvider } from './theme/ThemeContext'; +import {CustomThemeProvider} from './theme/ThemeContext'; import FSComponents from './pages/FSComponents.tsx'; import AdminPanel from './pages/AdminPanel'; -import { AccountProvider } from './helper/AccountProvider.tsx'; -import { CookiesProvider } from 'react-cookie'; +import {AccountProvider} from './helper/AccountProvider.tsx'; +import {CookiesProvider} from 'react-cookie'; export default function App() { - const queryClient = new QueryClient(); + const queryClient = new QueryClient(); - return ( - - - - - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - - - - - ) + return ( + + + + + + + + + + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + }/> + + + + + + + + + ) } diff --git a/01-frontend/src/components/Account.tsx b/01-frontend/src/components/Account.tsx index b987124..6484640 100644 --- a/01-frontend/src/components/Account.tsx +++ b/01-frontend/src/components/Account.tsx @@ -17,14 +17,14 @@ type AccountType = { export default AccountType; export type CustomerType = -{ - id: number; - name: string; - surname: string; - address: string; - country: string; - zip: string; -} + { + id: number; + name: string; + surname: string; + address: string; + country: string; + zip: string; + } export type SubmitLogin = { email: string; @@ -32,27 +32,27 @@ export type SubmitLogin = { }; export type SubmitLoginSession = { - email: string; - session: string; + email: string; + session: string; }; export type AdminAccountOperation = { - email: string; - session: string; - accountId: number; + email: string; + session: string; + accountId: number; } export type User = { - password: string; - email: string; - customerId: number; - session: string; - isAdmin: boolean; - // weitere Felder nach Bedarf + password: string; + email: string; + customerId: number; + session: string; + isAdmin: boolean; + // weitere Felder nach Bedarf }; export type AccountContextType = { - user: User | null; - login: (userData: User) => void; - logout: () => void; + user: User | null; + login: (userData: User) => void; + logout: () => void; }; \ No newline at end of file diff --git a/01-frontend/src/components/Order.tsx b/01-frontend/src/components/Order.tsx index 6bedf13..d1a0e61 100644 --- a/01-frontend/src/components/Order.tsx +++ b/01-frontend/src/components/Order.tsx @@ -4,7 +4,7 @@ export enum OrderStatusEnum { ISSUES = 'ISSUES', DELIVERED = 'DELIVERED', CANCELLED = 'CANCELLED', -}; +} type OrderType = { id: number; @@ -37,4 +37,4 @@ export type OrderItem = { export type OrderPatch = { id: number; status: OrderStatusEnum; - }; +}; diff --git a/01-frontend/src/components/i18n/i18n.ts b/01-frontend/src/components/i18n/i18n.ts index 285dc88..d7fc833 100644 --- a/01-frontend/src/components/i18n/i18n.ts +++ b/01-frontend/src/components/i18n/i18n.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import { initReactI18next } from "react-i18next"; +import {initReactI18next} from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import HttpBackend from "i18next-http-backend"; diff --git a/01-frontend/src/helper/AccountProvider.tsx b/01-frontend/src/helper/AccountProvider.tsx index c416221..47e58d5 100644 --- a/01-frontend/src/helper/AccountProvider.tsx +++ b/01-frontend/src/helper/AccountProvider.tsx @@ -1,16 +1,16 @@ -import { createContext, ReactNode, useContext, useEffect, useState } from "react"; -import { AccountContextType, User } from "../components/Account"; -import { useCookies } from 'react-cookie'; +import {createContext, ReactNode, useContext, useEffect, useState} from "react"; +import {AccountContextType, User} from "../components/Account"; +import {useCookies} from 'react-cookie'; const AccountContext = createContext(undefined); -export const AccountProvider = ({ children }: { children: ReactNode }) => { +export const AccountProvider = ({children}: { children: ReactNode }) => { const [cookies, setCookie] = useCookies(["account"]); const initialAccount = - typeof cookies.account === "object" && !Array.isArray(cookies.account) - ? cookies.account - : null; + typeof cookies.account === "object" && !Array.isArray(cookies.account) + ? cookies.account + : null; const [user, setUser] = useState(initialAccount); const login = (userData: User) => { @@ -22,11 +22,11 @@ export const AccountProvider = ({ children }: { children: ReactNode }) => { }; useEffect(() => { - setCookie("account", user, { path: "/", maxAge: 3600 * 24 * 7 }); - }, [user, setCookie]); + setCookie("account", user, {path: "/", maxAge: 3600 * 24 * 7}); + }, [user, setCookie]); return ( - + {children} ); diff --git a/01-frontend/src/helper/BasketProvider.tsx b/01-frontend/src/helper/BasketProvider.tsx index 1a29bfa..9487188 100644 --- a/01-frontend/src/helper/BasketProvider.tsx +++ b/01-frontend/src/helper/BasketProvider.tsx @@ -1,6 +1,6 @@ -import React, { createContext, useContext, useEffect, useState } from 'react'; +import React, {createContext, useContext, useEffect, useState} from 'react'; import Item from '../components/Item'; -import { useCookies } from 'react-cookie'; +import {useCookies} from 'react-cookie'; export interface BasketItem { item: Item; @@ -15,7 +15,7 @@ interface BasketContextType { const BasketContext = createContext(undefined); -export const BasketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { +export const BasketProvider: React.FC<{ children: React.ReactNode }> = ({children}) => { const [cookies, setCookie] = useCookies(["basket"]); const [basket, setBasket] = useState(cookies.basket || []); @@ -26,12 +26,12 @@ export const BasketProvider: React.FC<{ children: React.ReactNode }> = ({ childr // Update quantity if item already exists return prevBasket.map((basketItem) => basketItem.item.uuid === item.uuid - ? { ...basketItem, quantity: basketItem.quantity + quantity } + ? {...basketItem, quantity: basketItem.quantity + quantity} : basketItem ); } // Add new item to basket - return [...prevBasket, { item, quantity }]; + return [...prevBasket, {item, quantity}]; }); }; @@ -40,11 +40,11 @@ export const BasketProvider: React.FC<{ children: React.ReactNode }> = ({ childr }; useEffect(() => { - setCookie("basket", basket, { path: "/", maxAge: 3600 * 24 * 7 }); // 7 Tage - }, [basket, setCookie]); + setCookie("basket", basket, {path: "/", maxAge: 3600 * 24 * 7}); // 7 Tage + }, [basket, setCookie]); return ( - + {children} ); diff --git a/01-frontend/src/helper/adminpanel/AccountsInfo.tsx b/01-frontend/src/helper/adminpanel/AccountsInfo.tsx index 5d9b62a..0004d2c 100644 --- a/01-frontend/src/helper/adminpanel/AccountsInfo.tsx +++ b/01-frontend/src/helper/adminpanel/AccountsInfo.tsx @@ -1,18 +1,18 @@ import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; -import { Box, Button, IconButton, Toolbar, useTheme } from "@mui/material"; -import { DataGrid, GridColDef, GridRowId, GridRowSelectionModel } from "@mui/x-data-grid"; -import { useMutation, useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import AccountType, { AdminAccountOperation, CustomerType } from "../../components/Account"; -import { useAccount } from "../AccountProvider"; -import { deleteAccountAdmin, fetchAccounts, updateAccountAdmin } from "../query/Queries"; +import {Box, Button, IconButton, Toolbar, useTheme} from "@mui/material"; +import {DataGrid, GridColDef, GridRowId, GridRowSelectionModel} from "@mui/x-data-grid"; +import {useMutation, useQuery} from "@tanstack/react-query"; +import {useEffect, useState} from "react"; +import {useTranslation} from "react-i18next"; +import AccountType, {AdminAccountOperation, CustomerType} from "../../components/Account"; +import {useAccount} from "../AccountProvider"; +import {deleteAccountAdmin, fetchAccounts, updateAccountAdmin} from "../query/Queries"; import CustomerEditDialog from "./CustomerEditDialog"; export default function AccountsInfo() { const theme = useTheme(); - const { t } = useTranslation(); + const {t} = useTranslation(); const [customerData, setCustomerData] = useState({ id: 0, @@ -31,11 +31,17 @@ export default function AccountsInfo() { const [rows, setRows] = useState([]); const [selectedRows, setSelectedRows] = useState>(new Set()); - const { user: loginData } = useAccount(); + const {user: loginData} = useAccount(); - const { data, refetch } = useQuery({ + const {data, refetch} = useQuery({ queryKey: ["fetchAccounts", loginData], - queryFn: () => fetchAccounts(loginData ? loginData : { email: "", password: "", session: "", customerId: -1, isAdmin: false }), + queryFn: () => fetchAccounts(loginData ? loginData : { + email: "", + password: "", + session: "", + customerId: -1, + isAdmin: false + }), retry: 3, retryDelay: 1000, }); @@ -57,7 +63,11 @@ export default function AccountsInfo() { const handleDeleteSelected = async () => { selectedRows.forEach(async (row) => { - await deleteAccount.mutateAsync({ email: loginData?.email || '', session: loginData?.session || '', accountId: row.id as number }); + await deleteAccount.mutateAsync({ + email: loginData?.email || '', + session: loginData?.session || '', + accountId: row.id as number + }); }) setRows(rows.filter((row) => !selectedRows.has(row.id))); @@ -65,12 +75,18 @@ export default function AccountsInfo() { const updateAdmin = useMutation({ mutationFn: (account: AccountType) => - updateAccountAdmin(account, loginData ? loginData : { email: "", password: "", session: "", customerId: -1, isAdmin: false }), + updateAccountAdmin(account, loginData ? loginData : { + email: "", + password: "", + session: "", + customerId: -1, + isAdmin: false + }), }); const columns: GridColDef<(typeof rows)[number]>[] = [ - { field: 'id', headerName: 'ID', width: 60 }, + {field: 'id', headerName: 'ID', width: 60}, { field: 'admin', headerName: t('admin'), @@ -97,7 +113,7 @@ export default function AccountsInfo() { sortable: false, disableReorder: true, disableColumnMenu: true, - renderCell: params => handleCustomerEdit(params.row)}> , + renderCell: params => handleCustomerEdit(params.row)}> , } ]; @@ -130,7 +146,7 @@ export default function AccountsInfo() { @@ -178,7 +177,7 @@ export default function ItemImageDialog({ open, onClose, item, onSuccess, isFarm {/* Image Preview */} {preview && ( - + Preview - + {selectedFile?.name} ({(selectedFile?.size || 0 / 1024).toFixed(1)} KB) @@ -220,7 +219,7 @@ export default function ItemImageDialog({ open, onClose, item, onSuccess, isFarm onClick={handleUpload} variant="contained" disabled={!selectedFile || loading} - startIcon={loading ? : undefined} + startIcon={loading ? : undefined} > {loading ? t('uploading') : t('upload')} diff --git a/01-frontend/src/helper/adminpanel/ItemsInfo.tsx b/01-frontend/src/helper/adminpanel/ItemsInfo.tsx index 380be05..6c6b6c4 100644 --- a/01-frontend/src/helper/adminpanel/ItemsInfo.tsx +++ b/01-frontend/src/helper/adminpanel/ItemsInfo.tsx @@ -1,21 +1,21 @@ import DeleteIcon from "@mui/icons-material/Delete"; import EditIcon from "@mui/icons-material/Edit"; -import { Box, Button, IconButton, Toolbar, useTheme } from "@mui/material"; -import { Gauge, gaugeClasses } from "@mui/x-charts"; -import { DataGrid, GridColDef, GridRowId, GridRowSelectionModel } from "@mui/x-data-grid"; -import { useMutation, useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; +import {Box, Button, IconButton, Toolbar, useTheme} from "@mui/material"; +import {Gauge, gaugeClasses} from "@mui/x-charts"; +import {DataGrid, GridColDef, GridRowId, GridRowSelectionModel} from "@mui/x-data-grid"; +import {useMutation, useQuery} from "@tanstack/react-query"; +import {useEffect, useState} from "react"; +import {useTranslation} from "react-i18next"; import Item from "../../components/Item"; -import { mapValueToColor } from "../../util/ColorUtil.tsx"; -import { useAccount } from "../AccountProvider.tsx"; -import { deleteItemQuery, fetchItems } from "../query/Queries.tsx"; +import {mapValueToColor} from "../../util/ColorUtil.tsx"; +import {useAccount} from "../AccountProvider.tsx"; +import {deleteItemQuery, fetchItems} from "../query/Queries.tsx"; import ItemImageDialog from "./ItemImageDialog.tsx"; import NewItemDialog from "./NewItemDialog.tsx"; export default function ItemsInfo() { const theme = useTheme(); - const { t } = useTranslation(); + const {t} = useTranslation(); const [rows, setRows] = useState([]); const [selectedRows, setSelectedRows] = useState>(new Set()); @@ -41,14 +41,13 @@ export default function ItemsInfo() { } - function handleAddItem() { setNewItemDialog(true); } - const { user: loginData } = useAccount(); + const {user: loginData} = useAccount(); - const { data } = useQuery({ + const {data} = useQuery({ queryKey: ["fetchItems", loginData], queryFn: () => fetchItems(), retry: 3, @@ -81,7 +80,7 @@ export default function ItemsInfo() { }; const columns: GridColDef<(typeof rows)[number]>[] = [ - { field: 'id', headerName: 'ID', width: 60 }, + {field: 'id', headerName: 'ID', width: 60}, { field: 'uuid', headerName: t('uuid'), @@ -129,11 +128,14 @@ export default function ItemsInfo() { width: 100, editable: true, type: 'number', - renderCell: params => { return mapValueToColor(0, params.row.stockExpected, params.row.stock) }, + fill: () => { + return mapValueToColor(0, params.row.stockExpected, params.row.stock) + }, }, - }} text={() => `${params.row.stock}`} /> + }} text={() => `${params.row.stock}`}/> }, { field: 'rating', @@ -141,11 +143,14 @@ export default function ItemsInfo() { width: 100, editable: false, //the rating is averaged from ratings type: 'number', - renderCell: params => { return mapValueToColor(0, 10, params.row.rating) }, + fill: () => { + return mapValueToColor(0, 10, params.row.rating) + }, }, - }} text={() => `${params.row.rating.toFixed(2)}`} /> + }} text={() => `${params.row.rating.toFixed(2)}`}/> }, { field: "actualPrice", @@ -159,14 +164,15 @@ export default function ItemsInfo() { headerName: t('images'), width: 90, editable: false, - renderCell: params => handleImageEdit(params.row)}> , + renderCell: params => handleImageEdit(params.row)}> , }, { field: 'farmImage', headerName: t('fsImage'), width: 90, editable: false, - renderCell: params => handleFarmImageEdit(params.row)}> , + renderCell: params => handleFarmImageEdit(params.row)}> + , } ]; @@ -191,7 +197,7 @@ export default function ItemsInfo() { @@ -247,23 +246,23 @@ export default function NavBar() { })} - + - + setAnchorElNav(null)} > - {filteredPages.map(({ key, label }) => ( + {filteredPages.map(({key, label}) => ( handleCloseNavMenu(key)}> {label} @@ -271,20 +270,20 @@ export default function NavBar() { - + - - + + setAnchorElUser(null)} > - {settings.map(({ key, label, disabled }) => ( + {settings.map(({key, label, disabled}) => ( { @@ -292,7 +291,7 @@ export default function NavBar() { }} disabled={disabled} > - {label} + {label} ))} @@ -306,7 +305,7 @@ export default function NavBar() { loginData={loginData} setLoginData={setLoginData} /> -
+
); } diff --git a/01-frontend/src/helper/productpage/ProductInfo.tsx b/01-frontend/src/helper/productpage/ProductInfo.tsx index e60045e..f4fb8fc 100644 --- a/01-frontend/src/helper/productpage/ProductInfo.tsx +++ b/01-frontend/src/helper/productpage/ProductInfo.tsx @@ -1,19 +1,33 @@ -import { Close, LocalShipping, ShoppingCart } from "@mui/icons-material"; -import { Alert, Box, Button, Card, Divider, Grid, IconButton, Rating, Snackbar, SnackbarCloseReason, Stack, TextField, Typography } from "@mui/material"; -import React, { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; +import {Close, LocalShipping, ShoppingCart} from "@mui/icons-material"; +import { + Alert, + Box, + Button, + Card, + Divider, + Grid, + IconButton, + Rating, + Snackbar, + SnackbarCloseReason, + Stack, + TextField, + Typography +} from "@mui/material"; +import React, {useEffect, useState} from "react"; +import {useTranslation} from "react-i18next"; import Item from "../../components/Item"; -import { useBasket } from "../BasketProvider"; +import {useBasket} from "../BasketProvider"; -export default function ProductInfo({ item }: { item: Item }) { +export default function ProductInfo({item}: { item: Item }) { - const { t } = useTranslation(); + const {t} = useTranslation(); const [quantity, setQuantity] = useState(1); const [open, setOpen] = useState(false); - const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); + const [imageDimensions, setImageDimensions] = useState({width: 0, height: 0}); - const { addToBasket } = useBasket(); + const {addToBasket} = useBasket(); const handleClose = ( _: React.SyntheticEvent | Event, @@ -34,7 +48,7 @@ export default function ProductInfo({ item }: { item: Item }) { color="inherit" onClick={handleClose} > - + ); @@ -49,8 +63,8 @@ export default function ProductInfo({ item }: { item: Item }) { const discountedPrice = item.price100 * (1 - item.discount100 / 100); const handleImageLoad = (event: React.SyntheticEvent) => { - const { naturalWidth, naturalHeight } = event.currentTarget; - setImageDimensions({ width: naturalWidth, height: naturalHeight }); + const {naturalWidth, naturalHeight} = event.currentTarget; + setImageDimensions({width: naturalWidth, height: naturalHeight}); }; const [imageUrl, setImageUrl] = useState("/src/assets/default.jpg"); // Fallback-Bild @@ -60,10 +74,10 @@ export default function ProductInfo({ item }: { item: Item }) { try { const response = await fetch(`http://localhost:8085/image?uuid=${item.uuid}`); let data = await response.text(); - if(data.length == 0) { + if (data.length == 0) { console.error("Got emtpy picture for article ", item.uuid); } - if(!data.startsWith("data:image/")) { + if (!data.startsWith("data:image/")) { data = "data:image/jpeg;base64," + data } setImageUrl(data); @@ -79,7 +93,7 @@ export default function ProductInfo({ item }: { item: Item }) { {/* Left Column - Image */} - + - + {item.rating > 0 ? `(${item.rating / 2} / 5)` : t('noRatingsYet')} @@ -123,7 +137,7 @@ export default function ProductInfo({ item }: { item: Item }) { {(item.price100 / 100).toFixed(2)} € @@ -138,7 +152,7 @@ export default function ProductInfo({ item }: { item: Item }) { )} - + {item.stock > 10 ? ( @@ -146,7 +160,8 @@ export default function ProductInfo({ item }: { item: Item }) { {t('inStock')} ({item.stock} {t('available')}) ) : item.stock > 0 ? ( - {t('almostSoldOut')} ({item.stock} {t('available')}) + {t('almostSoldOut')} ({item.stock} {t('available')}) ) : ( {t('outOfStock')} )} @@ -158,13 +173,13 @@ export default function ProductInfo({ item }: { item: Item }) { label={t('quantity')} value={quantity} onChange={(e) => setQuantity(Math.max(1, parseInt(e.target.value)))} - InputProps={{ inputProps: { min: 1, max: item.stock } }} - sx={{ width: 100 }} + InputProps={{inputProps: {min: 1, max: item.stock}}} + sx={{width: 100}} /> - + - + {t('freeShipping')} diff --git a/01-frontend/src/helper/productpage/RatingCard.tsx b/01-frontend/src/helper/productpage/RatingCard.tsx index 516b1f9..1de778a 100644 --- a/01-frontend/src/helper/productpage/RatingCard.tsx +++ b/01-frontend/src/helper/productpage/RatingCard.tsx @@ -1,17 +1,9 @@ -import { - Card, - CardActionArea, - CardContent, - Paper, - Rating, - Typography, - useTheme -} from "@mui/material"; +import {Card, CardActionArea, CardContent, Paper, Rating, Typography, useTheme} from "@mui/material"; import RatingType from "../../components/Rating"; -import { useTranslation } from 'react-i18next'; +import {useTranslation} from 'react-i18next'; export default function RatingCard(ratingType: RatingType) { - const { t } = useTranslation(); + const {t} = useTranslation(); const theme = useTheme(); // Zugriff auf Light/Dark-Mode const handleClick = () => { @@ -37,7 +29,7 @@ export default function RatingCard(ratingType: RatingType) { gutterBottom variant="h6" component="div" - sx={{ color: theme.palette.text.primary }} + sx={{color: theme.palette.text.primary}} > {t('ratingFrom')} {new Date(ratingType.timestamp).toLocaleDateString('de-DE')} @@ -51,7 +43,7 @@ export default function RatingCard(ratingType: RatingType) { {ratingType.content} diff --git a/01-frontend/src/helper/productpage/Ratings.tsx b/01-frontend/src/helper/productpage/Ratings.tsx index d9ed80c..fd36b93 100644 --- a/01-frontend/src/helper/productpage/Ratings.tsx +++ b/01-frontend/src/helper/productpage/Ratings.tsx @@ -10,17 +10,17 @@ import { Typography, useTheme } from "@mui/material"; -import { Close } from "@mui/icons-material"; -import { useQuery } from "@tanstack/react-query"; -import React, { useMemo, useState } from "react"; -import { useTranslation } from 'react-i18next'; +import {Close} from "@mui/icons-material"; +import {useQuery} from "@tanstack/react-query"; +import React, {useMemo, useState} from "react"; +import {useTranslation} from 'react-i18next'; import RatingType from "../../components/Rating"; -import { fetchRatingList, submitRating } from "../query/Queries"; +import {fetchRatingList, submitRating} from "../query/Queries"; import RatingCard from "./RatingCard"; import RatingSubmitType from "../../components/RatingSubmit"; -export default function Ratings({ itemId }: { itemId: string }) { - const { t } = useTranslation(); +export default function Ratings({itemId}: { itemId: string }) { + const {t} = useTranslation(); const theme = useTheme(); const [open, setOpen] = useState(false); @@ -33,7 +33,7 @@ export default function Ratings({ itemId }: { itemId: string }) { articleId: itemId, }; - const { refetch } = useQuery({ + const {refetch} = useQuery({ queryKey: ["submitRating", ratingData], queryFn: () => submitRating(ratingData), retry: 3, @@ -62,12 +62,12 @@ export default function Ratings({ itemId }: { itemId: string }) { color="inherit" onClick={handleClose} > - + ); - const { data = [] } = useQuery({ + const {data = []} = useQuery({ queryKey: ["fetchRatingList", itemId], queryFn: () => fetchRatingList(itemId), retry: 3, @@ -79,7 +79,7 @@ export default function Ratings({ itemId }: { itemId: string }) { const getRatings = () => { if (ratings.length === 0) { return ( - + {t("noRatingsYet")} ); @@ -92,10 +92,10 @@ export default function Ratings({ itemId }: { itemId: string }) { return ( <> - + - - + + {t("rateThisProduct")}: @@ -148,7 +148,7 @@ export default function Ratings({ itemId }: { itemId: string }) { - + {getRatings()} diff --git a/01-frontend/src/helper/query/Queries.tsx b/01-frontend/src/helper/query/Queries.tsx index dd07909..f5bfaea 100644 --- a/01-frontend/src/helper/query/Queries.tsx +++ b/01-frontend/src/helper/query/Queries.tsx @@ -1,9 +1,9 @@ // api/queries.js -import AccountType, { AdminAccountOperation, CustomerType, SubmitLogin, User } from "../../components/Account"; -import OrderType, { OrderPatch } from "../../components/Order"; +import AccountType, {AdminAccountOperation, CustomerType, SubmitLogin, User} from "../../components/Account"; +import OrderType, {OrderPatch} from "../../components/Order"; import RatingSubmitType from "../../components/RatingSubmit"; -import { Item, ItemWithFSImage } from "../../components/Item"; +import {Item, ItemWithFSImage} from "../../components/Item"; export const fetchItemList = async () => { const response = await fetch('http://localhost:8085/article/all'); @@ -36,7 +36,7 @@ export const submitRating = async (ratingData: RatingSubmitType) => { throw new Error('Fehler beim Senden der Bewertung'); } - const data = await response.json(); + const data = await response.json(); return data; } @@ -219,8 +219,8 @@ export const editAccount = async (customer: CustomerType) => { export const orderPatch = async (order: OrderPatch) => { const response = await fetch("http://localhost:8085/order?id=" + order.id + "&status=" + order.status, { - method: "PATCH", - } + method: "PATCH", + } ); if (!response.ok) { throw new Error("Order patch failed"); diff --git a/01-frontend/src/index.css b/01-frontend/src/index.css index 08a3ac9..ed24c19 100644 --- a/01-frontend/src/index.css +++ b/01-frontend/src/index.css @@ -1,68 +1,73 @@ :root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } + a:hover { - color: #535bf2; + color: #535bf2; } body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; } h1 { - font-size: 3.2em; - line-height: 1.1; + font-size: 3.2em; + line-height: 1.1; } button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; } + button:hover { - border-color: #646cff; + border-color: #646cff; } + button:focus, button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + outline: 4px auto -webkit-focus-ring-color; } @media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } + :root { + color: #213547; + background-color: #ffffff; + } + + a:hover { + color: #747bff; + } + + button { + background-color: #f9f9f9; + } } diff --git a/01-frontend/src/main.tsx b/01-frontend/src/main.tsx index 2a5a0dc..56b8897 100644 --- a/01-frontend/src/main.tsx +++ b/01-frontend/src/main.tsx @@ -1,6 +1,6 @@ import './components/i18n/i18n'; -import { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; import './index.css'; import App from './App.tsx'; @@ -10,6 +10,6 @@ if (!rootElement) throw new Error("Root element not found"); const root = createRoot(rootElement); root.render( - + ); \ No newline at end of file diff --git a/01-frontend/src/pages/Account.tsx b/01-frontend/src/pages/Account.tsx index 999a765..35a2242 100644 --- a/01-frontend/src/pages/Account.tsx +++ b/01-frontend/src/pages/Account.tsx @@ -1,239 +1,239 @@ import { - Box, - Button, - Divider, - Paper, - Stack, - TextField, - Typography, - Dialog, - DialogTitle, - DialogContent, - DialogActions, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + Paper, + Stack, + TextField, + Typography, } from "@mui/material"; -import { useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { CustomerType, User } from "../components/Account"; -import { useAccount } from "../helper/AccountProvider"; -import { deleteAccount, editAccount, fetchCustomer } from "../helper/query/Queries"; +import {useQuery} from "@tanstack/react-query"; +import {useEffect, useState} from "react"; +import {useTranslation} from "react-i18next"; +import {useNavigate} from "react-router-dom"; +import {CustomerType, User} from "../components/Account"; +import {useAccount} from "../helper/AccountProvider"; +import {deleteAccount, editAccount, fetchCustomer} from "../helper/query/Queries"; import "./pages.css"; export default function Account() { - const { t } = useTranslation(); - const navigate = useNavigate(); - const { user: userData, logout } = useAccount(); + const {t} = useTranslation(); + const navigate = useNavigate(); + const {user: userData, logout} = useAccount(); - const [user, setUser] = useState({ - name: "", - surname: "", - address: "", - country: "", - zip: "", - id: userData?.customerId || 0, - }); + const [user, setUser] = useState({ + name: "", + surname: "", + address: "", + country: "", + zip: "", + id: userData?.customerId || 0, + }); - const [userDataState, setUserDataState] = useState(userData || { - password: "", - email: "", - customerId: 0, - session: "", - isAdmin: false, - }); + const [userDataState, setUserDataState] = useState(userData || { + password: "", + email: "", + customerId: 0, + session: "", + isAdmin: false, + }); - useEffect(() => { - if (userData?.customerId) { - setUser((prev) => ({ - ...prev, - id: userData.customerId, - })); - } - }, [userData]); + useEffect(() => { + if (userData?.customerId) { + setUser((prev) => ({ + ...prev, + id: userData.customerId, + })); + } + }, [userData]); - const [edit, setEdit] = useState(false); - const [form, setForm] = useState(user); + const [edit, setEdit] = useState(false); + const [form, setForm] = useState(user); - // Neu: Passwort-Dialog-Status und Passwort-Input - const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); - const [passwordInput, setPasswordInput] = useState(""); + // Neu: Passwort-Dialog-Status und Passwort-Input + const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); + const [passwordInput, setPasswordInput] = useState(""); - const { data } = useQuery({ - queryKey: ["fetchCustomer", userData?.customerId], - queryFn: () => fetchCustomer(userData?.customerId || 0), - retry: 1, - retryDelay: 1000, - }); + const {data} = useQuery({ + queryKey: ["fetchCustomer", userData?.customerId], + queryFn: () => fetchCustomer(userData?.customerId || 0), + retry: 1, + retryDelay: 1000, + }); - const { refetch: deleteRefetch } = useQuery({ - queryKey: ["deleteAccount", userDataState], - queryFn: () => deleteAccount(userDataState!), - enabled: false, - }); + const {refetch: deleteRefetch} = useQuery({ + queryKey: ["deleteAccount", userDataState], + queryFn: () => deleteAccount(userDataState!), + enabled: false, + }); - const { refetch: editRefetch } = useQuery({ - queryKey: ["editAccount", form], - queryFn: () => editAccount(form), - enabled: false, - }); + const {refetch: editRefetch} = useQuery({ + queryKey: ["editAccount", form], + queryFn: () => editAccount(form), + enabled: false, + }); - useEffect(() => { - if (data) { - setUser(data); - setForm(data); - } - }, [data]); + useEffect(() => { + if (data) { + setUser(data); + setForm(data); + } + }, [data]); - const handleEdit = () => setEdit(true); - const handleCancel = () => { - setForm(user); - setEdit(false); - }; - const handleChange = (e: React.ChangeEvent) => { - setForm({ ...form, [e.target.name]: e.target.value }); - }; - const handleSave = async () => { - setUser(form); - setEdit(false); - await editRefetch(); - }; + const handleEdit = () => setEdit(true); + const handleCancel = () => { + setForm(user); + setEdit(false); + }; + const handleChange = (e: React.ChangeEvent) => { + setForm({...form, [e.target.name]: e.target.value}); + }; + const handleSave = async () => { + setUser(form); + setEdit(false); + await editRefetch(); + }; - // Neu: Passwort-Dialog öffnen - const handleDeleteClick = () => { - setPasswordInput(""); - setPasswordDialogOpen(true); - }; + // Neu: Passwort-Dialog öffnen + const handleDeleteClick = () => { + setPasswordInput(""); + setPasswordDialogOpen(true); + }; - // Neu: Passwort-Dialog schließen - const handlePasswordDialogClose = () => { - setPasswordDialogOpen(false); - }; + // Neu: Passwort-Dialog schließen + const handlePasswordDialogClose = () => { + setPasswordDialogOpen(false); + }; - // Neu: Passwort-Eingabe bestätigen - const handlePasswordConfirm = async () => { - if (!passwordInput) { - alert(t("pleaseEnterPassword")); - return; - } - // Passwort in Form aktualisieren (hier z.B. als field "password", anpassen falls anders) - setUserDataState({ ...userDataState, password: passwordInput }); + // Neu: Passwort-Eingabe bestätigen + const handlePasswordConfirm = async () => { + if (!passwordInput) { + alert(t("pleaseEnterPassword")); + return; + } + // Passwort in Form aktualisieren (hier z.B. als field "password", anpassen falls anders) + setUserDataState({...userDataState, password: passwordInput}); - // Erst User-Daten mit Passwort aktualisieren - try { - await editRefetch(); // Achtung: editRefetch verwendet immer noch alten form, daher call direkt mit updatedForm: - // Danach Account löschen - await deleteRefetch(); - logout(); - navigate("/"); - } catch (error) { - console.error("Fehler beim Löschen des Accounts:", error); - alert(t("deleteAccountFailed")); - } finally { - setPasswordDialogOpen(false); - } - }; + // Erst User-Daten mit Passwort aktualisieren + try { + await editRefetch(); // Achtung: editRefetch verwendet immer noch alten form, daher call direkt mit updatedForm: + // Danach Account löschen + await deleteRefetch(); + logout(); + navigate("/"); + } catch (error) { + console.error("Fehler beim Löschen des Accounts:", error); + alert(t("deleteAccountFailed")); + } finally { + setPasswordDialogOpen(false); + } + }; - return ( - - - - {t("myAccount")} - - - - - - - - - - - {edit ? ( - <> - - - - ) : ( - - )} - - - + return ( + + + + {t("myAccount")} + + + + + + + + + + + {edit ? ( + <> + + + + ) : ( + + )} + + + - {/* Passwort-Dialog */} - - {t("confirmDeleteAccount")} - - {t("enterPasswordToConfirmDeletion")} - setPasswordInput(e.target.value)} - /> - - - - - - - - ); + {/* Passwort-Dialog */} + + {t("confirmDeleteAccount")} + + {t("enterPasswordToConfirmDeletion")} + setPasswordInput(e.target.value)} + /> + + + + + + + + ); } diff --git a/01-frontend/src/pages/AdminPanel.tsx b/01-frontend/src/pages/AdminPanel.tsx index 8c9483f..4c03d4c 100644 --- a/01-frontend/src/pages/AdminPanel.tsx +++ b/01-frontend/src/pages/AdminPanel.tsx @@ -1,27 +1,14 @@ -import { - AccountCircle, - Category, - QueryStats, - ReceiptLong, -} from "@mui/icons-material"; -import { - Box, - List, - ListItem, - ListItemButton, - ListItemIcon, - ListItemText, - useTheme, -} from "@mui/material"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; +import {AccountCircle, Category, QueryStats, ReceiptLong,} from "@mui/icons-material"; +import {Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, useTheme,} from "@mui/material"; +import {useState} from "react"; +import {useTranslation} from "react-i18next"; import AccountsInfo from "../helper/adminpanel/AccountsInfo"; import ItemInfo from "../helper/adminpanel/ItemsInfo"; import OrdersInfo from "../helper/adminpanel/OrdersInfo"; import StatisticsInfo from "../helper/adminpanel/StatisticsInfo"; export default function AdminPanel() { - const { t } = useTranslation(); + const {t} = useTranslation(); const theme = useTheme(); const [infoStatus, setInfoStatus] = useState("statistics"); @@ -32,27 +19,27 @@ export default function AdminPanel() { const renderContent = () => { switch (infoStatus) { case "statistics": - return ; + return ; case "orders": - return ; + return ; case "accounts": - return ; + return ; case "items": - return ; + return ; default: - return ; + return ; } }; const menuItems = [ - { key: "statistics", icon: , label: t("statistics") }, - { key: "orders", icon: , label: t("orders") }, - { key: "accounts", icon: , label: t("accounts") }, - { key: "items", icon: , label: t("items") }, + {key: "statistics", icon: , label: t("statistics")}, + {key: "orders", icon: , label: t("orders")}, + {key: "accounts", icon: , label: t("accounts")}, + {key: "items", icon: , label: t("items")}, ]; return ( - +
- {item.icon} - + {item.icon} + ))} diff --git a/01-frontend/src/pages/Contact.tsx b/01-frontend/src/pages/Contact.tsx index 9a608a6..b5bc7c6 100644 --- a/01-frontend/src/pages/Contact.tsx +++ b/01-frontend/src/pages/Contact.tsx @@ -1,83 +1,90 @@ -import { Box, Divider, Typography } from "@mui/material"; +import {Box, Divider, Typography} from "@mui/material"; import "./pages.css"; export default function Impressum() { return ( - + Impressum - - Hochschule für Technik und Wirtschaft
- des Saarlandes
- Goebenstraße 40
- 66117 Saarbrücken

- Telefon: (0681) 58 67 - 0
- Telefax: (0681) 58 67 - 122
- E-Mail: info@htwsaar.de

- Aufsichtsbehörde:
+ + Hochschule für Technik und Wirtschaft
+ des Saarlandes
+ Goebenstraße 40
+ 66117 Saarbrücken

+ Telefon: (0681) 58 67 - 0
+ Telefax: (0681) 58 67 - 122
+ E-Mail: info@htwsaar.de

+ Aufsichtsbehörde:
Ministerium der Finanzen und für Wissenschaft des Saarlandes
- + - + Datenschutzerklärung - + Personenbezogene Daten (nachfolgend zumeist nur „Daten“ genannt) ... - + Gemäß Art. 4 Ziffer 1. der Verordnung (EU) 2016/679, also der Datenschutz-Grundverordnung ... - - Unsere Datenschutzerklärung ist wie folgt gegliedert:
- I. Informationen über uns als Verantwortliche
- II. Rechte der Nutzer und Betroffenen
+ + Unsere Datenschutzerklärung ist wie folgt gegliedert:
+ I. Informationen über uns als Verantwortliche
+ II. Rechte der Nutzer und Betroffenen
III. Informationen zur Datenverarbeitung
- + I. Informationen über uns als Verantwortliche - + Verantwortlicher Anbieter dieses Internetauftritts ... - + II. Rechte der Nutzer und Betroffenen - - Mit Blick auf die nachfolgend noch näher beschriebene Datenverarbeitung haben die Nutzer und Betroffenen ... + + Mit Blick auf die nachfolgend noch näher beschriebene Datenverarbeitung haben die Nutzer und Betroffenen + ... - -
  • • Auskunft über die verarbeiteten Daten (Art. 15 DSGVO)
  • -
  • • Berichtigung unrichtiger Daten (Art. 16 DSGVO)
  • -
  • • Löschung der Daten (Art. 17 DSGVO)
  • -
  • • Einschränkung der Verarbeitung (Art. 18 DSGVO)
  • -
  • • Datenübertragbarkeit (Art. 20 DSGVO)
  • + +
  • • Auskunft über die verarbeiteten + Daten (Art. 15 DSGVO)
  • +
  • • Berichtigung unrichtiger Daten + (Art. 16 DSGVO)
  • +
  • • Löschung der Daten (Art. 17 + DSGVO)
  • +
  • • Einschränkung der Verarbeitung + (Art. 18 DSGVO)
  • +
  • • Datenübertragbarkeit (Art. 20 + DSGVO)
  • - + III. Informationen zur Datenverarbeitung - + Ihre bei Nutzung unseres Internetauftritts verarbeiteten Daten ... {/* Du kannst einfach alle weiteren Absätze so fortsetzen – copy & paste, jeweils in: */} - - Mehr Infos unter: CloudFlare Datenschutzerklärung + + Mehr Infos unter: CloudFlare Datenschutzerklärung
    ); diff --git a/01-frontend/src/pages/FSComponents.tsx b/01-frontend/src/pages/FSComponents.tsx index 79c447d..95ed906 100644 --- a/01-frontend/src/pages/FSComponents.tsx +++ b/01-frontend/src/pages/FSComponents.tsx @@ -1,26 +1,28 @@ -import { Box, Button, Typography, useTheme } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { useBasket } from "../helper/BasketProvider"; +import {Box, Button, Typography, useTheme} from "@mui/material"; +import {useTranslation} from "react-i18next"; +import {useState} from "react"; +import {useQuery} from "@tanstack/react-query"; +import {useBasket} from "../helper/BasketProvider"; import ItemCard from "../helper/homepage/ItemCard"; import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart'; import farmingStation from '../assets/fscomponents/fs_components_0.png'; -import { ItemWithFSImage } from "../components/Item"; "../components/Item"; -import { fetchFarmingStationItemList } from "../helper/query/Queries"; +import {ItemWithFSImage} from "../components/Item"; +import {fetchFarmingStationItemList} from "../helper/query/Queries"; + +"../components/Item"; export default function FSComponents() { - const { t } = useTranslation(); + const {t} = useTranslation(); const theme = useTheme(); - const { addToBasket } = useBasket(); + const {addToBasket} = useBasket(); const [hoverIndex, setHoverIndex] = useState(null); // Sehr sehr dummer Weg das zu machen, aber wird später noch refactored const wantedIds = ["60", "67", "68", "69", "70", "71", "72", "73", "74", "75"]; // Daten mit react-query laden - const { data = [], isLoading, error } = useQuery({ + const {data = [], isLoading, error} = useQuery({ queryKey: ['fetchFarmingStationItemList'], queryFn: fetchFarmingStationItemList, retry: 3, @@ -38,7 +40,7 @@ export default function FSComponents() { if (error) return {t('errorLoadingItems')}; return ( - + {/* Bild links */} } + startIcon={} onClick={handleAddAllToCart} > {t('addAllToCart')} @@ -83,7 +85,7 @@ export default function FSComponents() { padding: 2, }}> - + {t('componentsFarmingStation')} @@ -94,7 +96,7 @@ export default function FSComponents() { onMouseEnter={() => setHoverIndex(index)} onMouseLeave={() => setHoverIndex(null)} > - +
    ))}
    diff --git a/01-frontend/src/pages/Home.tsx b/01-frontend/src/pages/Home.tsx index 3053971..54b0dd4 100644 --- a/01-frontend/src/pages/Home.tsx +++ b/01-frontend/src/pages/Home.tsx @@ -1,33 +1,33 @@ -import { Alert, Box, useTheme } from "@mui/material"; -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 {Alert, Box, useTheme} from "@mui/material"; +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 ItemWithImage from "../components/Item"; import FilterItem from "../helper/homepage/FilterItem"; import ItemCard from "../helper/homepage/ItemCard"; import PriceSlider from "../helper/homepage/PriceSlider"; -import { fetchItemListWithImage } from '../helper/query/Queries'; +import {fetchItemListWithImage} from '../helper/query/Queries'; import "./pages.css"; // Import der CSS-Datei export default function Home() { - const { t } = useTranslation(); + const {t} = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const theme = useTheme(); const [searchQuery, setSearchQuery] = useState(null); const categoriesFilter = useMemo(() => [ - { value: "", label: t("allCategories") }, - { value: "Seeds", label: t("seeds") }, - { value: "GardenSupplies", label: t("gardenSupplies") }, - { value: "TechnicalComponents", label: t("technicalComponents") }, - { value: "Other", label: t("other") } + {value: "", label: t("allCategories")}, + {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') }, + {value: "", label: t('allRatings')}, ...[5, 4, 3, 2, 1].map(value => ({ value: value.toString(), label: value.toString() @@ -37,7 +37,7 @@ export default function Home() { const [selectedCategory, setSelectedCategory] = useState(null); const [selectedRating, setSelectedRating] = useState(null); - const { data = [], isLoading } = useQuery({ + const {data = [], isLoading} = useQuery({ queryKey: ['fetchItemListWithImage'], queryFn: fetchItemListWithImage, retry: 3, // Versucht es 3-mal erneut @@ -68,25 +68,26 @@ export default function Home() { }, [location.search, categoriesFilter]); // Filterfunktion bleibt gleich - const filteredItems: ItemWithImage[] = useMemo(() => {return items - .filter((item) => { - const discountedPrice = item.price100 * (1 - item.discount100 / 100); - return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1]; - }) - .filter((item) => { - if (!selectedCategory) return true; - return item.category.toLowerCase() === selectedCategory.toLowerCase(); - }) - .filter((item) => { - if (!selectedRating) return true; - const rating = item.rating; - return rating >= (Number(selectedRating) * 2); - }) - .filter((item) => { - if (!searchQuery) return true; - return (item.name.toLowerCase().includes(searchQuery.toLowerCase()) - ); - }); + const filteredItems: ItemWithImage[] = useMemo(() => { + return items + .filter((item) => { + const discountedPrice = item.price100 * (1 - item.discount100 / 100); + return discountedPrice >= priceRange[0] && discountedPrice <= priceRange[1]; + }) + .filter((item) => { + if (!selectedCategory) return true; + return item.category.toLowerCase() === selectedCategory.toLowerCase(); + }) + .filter((item) => { + if (!selectedRating) return true; + const rating = item.rating; + return rating >= (Number(selectedRating) * 2); + }) + .filter((item) => { + if (!searchQuery) return true; + return (item.name.toLowerCase().includes(searchQuery.toLowerCase()) + ); + }); }, [items, priceRange, selectedCategory, selectedRating, searchQuery]); @@ -98,10 +99,6 @@ export default function Home() { }, [location.search]); - - - - // Container Ref const containerRef = useRef(null); @@ -143,7 +140,7 @@ export default function Home() { return (
    {isLoading && t('loading')} {!isLoading &&
    ) : ( filteredItems.map(item => ( - + )) )} diff --git a/01-frontend/src/pages/NoPage.tsx b/01-frontend/src/pages/NoPage.tsx index c5264a7..5d97f67 100644 --- a/01-frontend/src/pages/NoPage.tsx +++ b/01-frontend/src/pages/NoPage.tsx @@ -1,12 +1,12 @@ -import {Box, Typography, Button, useTheme} from "@mui/material"; -import { useNavigate } from "react-router-dom"; +import {Box, Button, Typography, useTheme} from "@mui/material"; +import {useNavigate} from "react-router-dom"; import "./pages.css"; import {useTranslation} from "react-i18next"; export default function NoPage() { const theme = useTheme(); - const { t } = useTranslation(); + const {t} = useTranslation(); const navigate = useNavigate(); const handleGoHome = () => { @@ -16,7 +16,7 @@ export default function NoPage() { return ( 404 diff --git a/01-frontend/src/pages/Orders.tsx b/01-frontend/src/pages/Orders.tsx index 0794b73..3c118ae 100644 --- a/01-frontend/src/pages/Orders.tsx +++ b/01-frontend/src/pages/Orders.tsx @@ -1,18 +1,34 @@ -import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, List, ListItemButton, ListItemText, Paper, Stack, Tab, Tabs, Typography } from "@mui/material"; -import { useQuery } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import OrderType, { OrderStatusEnum } from "../components/Order"; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + List, + ListItemButton, + ListItemText, + Paper, + Stack, + Tab, + Tabs, + Typography +} from "@mui/material"; +import {useQuery} from "@tanstack/react-query"; +import {useEffect, useState} from "react"; +import {useTranslation} from "react-i18next"; +import OrderType, {OrderStatusEnum} from "../components/Order"; import "./pages.css"; -import { useAccount } from "../helper/AccountProvider"; -import { fetchOrders, orderPatch } from "../helper/query/Queries"; +import {useAccount} from "../helper/AccountProvider"; +import {fetchOrders, orderPatch} from "../helper/query/Queries"; export default function Orders() { const {user} = useAccount(); const [orders, setOrders] = useState([]) - const { data: accountOrders, refetch } = useQuery({ + const {data: accountOrders, refetch} = useQuery({ queryKey: ['fetchOrders', user?.customerId], // Hier sollte die tatsächliche Kunden-ID verwendet werden queryFn: () => user ? fetchOrders(user.customerId) : Promise.resolve([]), // Simulierte API-Antwort enabled: !!user, // Nur ausführen, wenn user existiert @@ -25,7 +41,7 @@ export default function Orders() { console.log("Orders fetched:", accountOrders); }, [accountOrders]); - const { t } = useTranslation(); + const {t} = useTranslation(); const [tab, setTab] = useState(0); const [selectedOrder, setSelectedOrder] = useState(null); @@ -36,31 +52,31 @@ export default function Orders() { const handleTabChange = (_: React.SyntheticEvent, newValue: number) => setTab(newValue); - const { refetch: cancleOrder } = useQuery({ - queryKey: ["orderPatch", {id: selectedOrder?.id || -1, status: OrderStatusEnum.CANCELLED}], - queryFn: () => orderPatch({id: selectedOrder?.id || -1, status: OrderStatusEnum.CANCELLED}), - retry: 0, - retryDelay: 1000, - enabled: false, - }); + const {refetch: cancleOrder} = useQuery({ + queryKey: ["orderPatch", {id: selectedOrder?.id || -1, status: OrderStatusEnum.CANCELLED}], + queryFn: () => orderPatch({id: selectedOrder?.id || -1, status: OrderStatusEnum.CANCELLED}), + retry: 0, + retryDelay: 1000, + enabled: false, + }); - const handleCancelOrder = async() => { + const handleCancelOrder = async () => { await cancleOrder(); setSelectedOrder(null); refetch(); }; return ( - - + + {t('myOrders')} - - - + + + - + {tab === 0 ? ( activeOrders.length > 0 ? ( @@ -68,7 +84,7 @@ export default function Orders() { setSelectedOrder(order)}> ))} @@ -83,7 +99,7 @@ export default function Orders() { setSelectedOrder(order)}> ))} @@ -100,8 +116,9 @@ export default function Orders() { {selectedOrder && ( - {`${t('orderDate')}: ${new Date(selectedOrder.time).toUTCString()}`} - + {`${t('orderDate')}: ${new Date(selectedOrder.time).toUTCString()}`} + {t('orderedItems')}: {selectedOrder.orderItems.map((item, idx) => ( @@ -111,8 +128,9 @@ export default function Orders() { /> ))} - - {`${t('sum')}: ${(selectedOrder.total/100).toFixed(2)} €`} + + {`${t('sum')}: ${(selectedOrder.total / 100).toFixed(2)} €`} )} diff --git a/01-frontend/src/pages/Payment.tsx b/01-frontend/src/pages/Payment.tsx index a2908c0..e6b9621 100644 --- a/01-frontend/src/pages/Payment.tsx +++ b/01-frontend/src/pages/Payment.tsx @@ -15,25 +15,25 @@ import { TextField, Typography } from '@mui/material'; -import { useMutation, useQuery } from '@tanstack/react-query'; -import { TFunction } from 'i18next'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from "react-i18next"; -import { useNavigate } from 'react-router-dom'; -import { CustomerType } from '../components/Account'; +import {useMutation, useQuery} from '@tanstack/react-query'; +import {TFunction} from 'i18next'; +import React, {useEffect, useState} from 'react'; +import {useTranslation} from "react-i18next"; +import {useNavigate} from 'react-router-dom'; +import {CustomerType} from '../components/Account'; import Item from '../components/Item'; -import OrderType, { OrderStatusEnum } from '../components/Order'; -import { useAccount } from '../helper/AccountProvider'; -import { BasketItem, useBasket } from '../helper/BasketProvider'; -import { fetchCustomer, submitCustomer, submitOrder } from '../helper/query/Queries'; +import OrderType, {OrderStatusEnum} from '../components/Order'; +import {useAccount} from '../helper/AccountProvider'; +import {BasketItem, useBasket} from '../helper/BasketProvider'; +import {fetchCustomer, submitCustomer, submitOrder} from '../helper/query/Queries'; function getDiscountedPrice(item: Item): number { - return (item.price100 / 100 * (100-item.discount100)/100); + return (item.price100 / 100 * (100 - item.discount100) / 100); } function generateBasket(t: TFunction<"translation", undefined>, basket: BasketItem[]) { return basket.length === 0 ? ( - + {t('basketEmpty')} ) : ( @@ -44,7 +44,7 @@ function generateBasket(t: TFunction<"translation", undefined>, basket: BasketIt primary={`${item.quantity}x ${item.item.name}`} secondary={`Item ID: ${item.item.uuid}`} /> - +
    {`${(item.quantity * getDiscountedPrice(item.item)).toFixed(2)} €`}
    {item.item.discount100 > 0 ? {-item.item.discount100}% : ""} @@ -59,15 +59,15 @@ function generateTotal(t: TFunction<"translation", undefined>, basket: BasketIte return basket.length === 0 ? "" :
    {t('total') + ": " + basket.map((item) => item.quantity * getDiscountedPrice(item.item)) - .reduce((prev: number, cur: number) => prev + cur, 0).toFixed(2) + ` €`} + .reduce((prev: number, cur: number) => prev + cur, 0).toFixed(2) + ` €`}
    } export default function Payment() { - const { t } = useTranslation(); - const { basket, clearBasket } = useBasket(); + const {t} = useTranslation(); + const {basket, clearBasket} = useBasket(); const navigator = useNavigate(); const [activeStep, setActiveStep] = useState(0); const [shippingDetails, setShippingDetails] = useState({ @@ -80,7 +80,7 @@ export default function Payment() { }); const [orderNumber, setOrderNumber] = useState(null); const steps = [t('reviewCart'), t('shippingDetails'), t('payment'), t('orderSummary')]; - const { user } = useAccount(); + const {user} = useAccount(); const submitOrderData: OrderType = { id: 0, // This will be set by the backend @@ -95,7 +95,7 @@ export default function Payment() { total: basket.reduce((total, item) => total + (item.quantity * getDiscountedPrice(item.item)), 0), }; - const { refetch: refetchCustomer } = useQuery({ + const {refetch: refetchCustomer} = useQuery({ queryKey: ["submitCustomer", shippingDetails], queryFn: () => submitCustomer(shippingDetails), retry: 0, @@ -109,30 +109,30 @@ export default function Payment() { }; - const { refetch: customerData } = useQuery({ - queryKey: ['fetchCustomer', user?.customerId], - queryFn: () => fetchCustomer(user?.customerId || 0), // Funktion zum Abrufen der Kundendaten - enabled: false - }); + const {refetch: customerData} = useQuery({ + queryKey: ['fetchCustomer', user?.customerId], + queryFn: () => fetchCustomer(user?.customerId || 0), // Funktion zum Abrufen der Kundendaten + enabled: false + }); - useEffect(() => { - const fetchShippingDetails = async () => { - if (user) { - try { - const userShippingDetails = (await customerData()).data; - setShippingDetails(userShippingDetails || shippingDetails); - } catch (error) { - console.error("Fehler beim Laden der Kundendaten:", error); - } + useEffect(() => { + const fetchShippingDetails = async () => { + if (user) { + try { + const userShippingDetails = (await customerData()).data; + setShippingDetails(userShippingDetails || shippingDetails); + } catch (error) { + console.error("Fehler beim Laden der Kundendaten:", error); } - }; - - fetchShippingDetails(); - }, [user, customerData, shippingDetails]); - + } + }; + + fetchShippingDetails(); + }, [user, customerData, shippingDetails]); + // Verwende useMutation statt useQuery für submitOrder - const { mutateAsync: submitOrderMutation } = useMutation({ + const {mutateAsync: submitOrderMutation} = useMutation({ mutationFn: (orderData: OrderType) => submitOrder(orderData), }); @@ -158,12 +158,12 @@ export default function Payment() { try { await submitOrderMutation(orderData); - // eslint-disable-next-line @typescript-eslint/no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { next = false; } } - if(next) { + if (next) { setActiveStep((prevStep) => prevStep + 1); } else { showAlert(); @@ -175,7 +175,7 @@ export default function Payment() { }; const handleInputChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; + const {name, value} = e.target; setShippingDetails((prevDetails) => ({ ...prevDetails, [name]: value, @@ -205,11 +205,12 @@ export default function Payment() { {t('reviewCart')} {generateBasket(t, basket)} - - - {generateTotal(t,basket)} + {generateTotal(t, basket)} ); case 1: @@ -290,17 +291,17 @@ export default function Payment() { {t('yourOrderNumber')}: {orderNumber} - + {t('shippingDetails')}: - {shippingDetails.name} {shippingDetails.surname}
    - {shippingDetails.address}
    - {shippingDetails.zip} {shippingDetails.country}
    + {shippingDetails.name} {shippingDetails.surname}
    + {shippingDetails.address}
    + {shippingDetails.zip} {shippingDetails.country}
    - + {t('orderedItems')}: {generateBasket(t, basket)} - + {generateTotal(t, basket)}
    @@ -320,7 +321,7 @@ export default function Payment() { overflowY: 'auto' }} > - + {t('completeYourOrder')} @@ -331,8 +332,8 @@ export default function Payment() { ))} - {renderStepContent(activeStep)} - + {renderStepContent(activeStep)} +