-
- {/* Sidebar */}
-
-
-
- {menuItems.map((item) => (
-
- handleInfoStatus(item.key)}
- sx={{
- "&.Mui-selected": {
- bgcolor: theme.palette.action.selected,
- color: theme.palette.primary.main,
- },
- "&:hover": {
- bgcolor: theme.palette.action.hover,
- },
- }}>
- {item.icon}
-
-
-
- ))}
-
-
-
-
- {/* Content */}
- {renderContent()}
-
+ return (
+
+
+ {/* Sidebar */}
+
+
+
+ {menuItems.map((item) => (
+
+ handleInfoStatus(item.key)}
+ sx={{
+ "&.Mui-selected": {
+ bgcolor: theme.palette.action.selected,
+ color: theme.palette.primary.main,
+ },
+ "&:hover": {
+ bgcolor: theme.palette.action.hover,
+ },
+ }}
+ >
+
+ {item.icon}
+
+
+
+
+ ))}
+
+
- );
+
+ {/* Content */}
+ {renderContent()}
+
+
+ );
}
diff --git a/01-frontend/src/pages/Contact.tsx b/01-frontend/src/pages/Contact.tsx
index b5bc7c6..39aa27a 100644
--- a/01-frontend/src/pages/Contact.tsx
+++ b/01-frontend/src/pages/Contact.tsx
@@ -1,91 +1,130 @@
-import {Box, Divider, Typography} from "@mui/material";
+import { Box, Divider, Typography } from "@mui/material";
import "./pages.css";
export default function Impressum() {
- return (
-
-
- 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:
- Ministerium der Finanzen und für Wissenschaft des Saarlandes
-
+
+ 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
-
+
+ Datenschutzerklärung
+
-
- Personenbezogene Daten (nachfolgend zumeist nur „Daten“ genannt) ...
-
+
+ Personenbezogene Daten (nachfolgend zumeist nur „Daten“ genannt) ...
+
-
- Gemäß Art. 4 Ziffer 1. der Verordnung (EU) 2016/679, also der Datenschutz-Grundverordnung ...
-
+
+ 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
- III. Informationen zur Datenverarbeitung
-
+
+ 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
-
+
+ I. Informationen über uns als Verantwortliche
+
-
- Verantwortlicher Anbieter dieses Internetauftritts ...
-
+
+ Verantwortlicher Anbieter dieses Internetauftritts ...
+
-
- II. Rechte der Nutzer und Betroffenen
-
+
+ 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
-
+
+ III. Informationen zur Datenverarbeitung
+
-
- Ihre bei Nutzung unseres Internetauftritts verarbeiteten Daten ...
-
+
+ Ihre bei Nutzung unseres Internetauftritts verarbeiteten Daten ...
+
- {/* Du kannst einfach alle weiteren Absätze so fortsetzen – copy & paste,
+ {/* 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 95ed906..d512493 100644
--- a/01-frontend/src/pages/FSComponents.tsx
+++ b/01-frontend/src/pages/FSComponents.tsx
@@ -1,106 +1,142 @@
-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 AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";
-import farmingStation from '../assets/fscomponents/fs_components_0.png';
-import {ItemWithFSImage} from "../components/Item";
-import {fetchFarmingStationItemList} from "../helper/query/Queries";
+import farmingStation from "../assets/fscomponents/fs_components_0.png";
+import { ItemWithFSImage } from "../components/Item";
+import { fetchFarmingStationItemList } from "../helper/query/Queries";
-"../components/Item";
+("../components/Item");
export default function FSComponents() {
- const {t} = useTranslation();
- const theme = useTheme();
- const {addToBasket} = useBasket();
- const [hoverIndex, setHoverIndex] = useState(null);
+ const { t } = useTranslation();
+ const theme = useTheme();
+ 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"];
+ // 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({
- queryKey: ['fetchFarmingStationItemList'],
- queryFn: fetchFarmingStationItemList,
- retry: 3,
- retryDelay: 1000,
+ // Daten mit react-query laden
+ const {
+ data = [],
+ isLoading,
+ error,
+ } = useQuery({
+ queryKey: ["fetchFarmingStationItemList"],
+ queryFn: fetchFarmingStationItemList,
+ retry: 3,
+ retryDelay: 1000,
+ });
+
+ // Button-Funktion: alle gefilterten Items in den Warenkorb packen
+ const handleAddAllToCart = () => {
+ data.forEach((item) => {
+ addToBasket(item, 1);
});
+ };
- // Button-Funktion: alle gefilterten Items in den Warenkorb packen
- const handleAddAllToCart = () => {
- data.forEach(item => {
- addToBasket(item, 1);
- });
- };
+ if (isLoading) return {t("loading")} ;
+ if (error)
+ return {t("errorLoadingItems")} ;
- if (isLoading) return {t('loading')} ;
- if (error) return {t('errorLoadingItems')} ;
+ return (
+
+ {/* Bild links */}
+
+
+ }
+ onClick={handleAddAllToCart}
+ >
+ {t("addAllToCart")}
+
+
- return (
-
- {/* Bild links */}
-
-
- }
- onClick={handleAddAllToCart}
- >
- {t('addAllToCart')}
-
-
-
- {/* Items rechts */}
-
-
-
- {t('componentsFarmingStation')}
-
-
-
- {data.map((item, index) => (
- setHoverIndex(index)}
- onMouseLeave={() => setHoverIndex(null)}
- >
-
-
- ))}
-
-
+ {/* Items rechts */}
+
+
+
+ {t("componentsFarmingStation")}
+
- );
+
+ {data.map((item, index) => (
+ setHoverIndex(index)}
+ onMouseLeave={() => setHoverIndex(null)}
+ >
+
+
+ ))}
+
+
+
+ );
}
diff --git a/01-frontend/src/pages/Home.tsx b/01-frontend/src/pages/Home.tsx
index a4b56f4..7ba45f4 100644
--- a/01-frontend/src/pages/Home.tsx
+++ b/01-frontend/src/pages/Home.tsx
@@ -1,201 +1,206 @@
-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 navigate = useNavigate();
+ const location = useLocation();
+ const theme = useTheme();
+ const [searchQuery, setSearchQuery] = useState(null);
- 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") },
+ ],
+ [t],
+ );
- 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")}
- ], [t]);
+ const ratingFilter = [
+ { value: "", label: t("allRatings") },
+ ...[5, 4, 3, 2, 1].map((value) => ({
+ value: value.toString(),
+ label: value.toString(),
+ })),
+ ];
- const ratingFilter = [
- {value: "", label: t('allRatings')},
- ...[5, 4, 3, 2, 1].map(value => ({
- value: value.toString(),
- label: value.toString()
- }))
- ];
+ const [selectedCategory, setSelectedCategory] = useState(null);
+ const [selectedRating, setSelectedRating] = useState(null);
- const [selectedCategory, setSelectedCategory] = useState(null);
- const [selectedRating, setSelectedRating] = useState(null);
+ const { data = [], isLoading } = useQuery({
+ queryKey: ["fetchItemListWithImage"],
+ queryFn: fetchItemListWithImage,
+ retry: 3, // Versucht es 3-mal erneut
+ retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
+ });
- const {data = [], isLoading} = useQuery({
- queryKey: ['fetchItemListWithImage'],
- queryFn: fetchItemListWithImage,
- retry: 3, // Versucht es 3-mal erneut
- retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
- });
+ const items: ItemWithImage[] = useMemo(() => data || [], [data]);
- const items: ItemWithImage[] = useMemo(() => data || [], [data]);
+ const discountedPrices = items.map(
+ (item) => item.price100 * (1 - item.discount100 / 100),
+ );
+ const minPrice =
+ discountedPrices.length > 0 ? Math.min(...discountedPrices) : 0;
+ const maxPrice =
+ discountedPrices.length > 0 ? Math.max(...discountedPrices) : 1000;
+ const [priceRange, setPriceRange] = useState<[number, number]>([
+ minPrice,
+ maxPrice,
+ ]);
- const discountedPrices = items.map(
- (item) => item.price100 * (1 - item.discount100 / 100)
- );
- const minPrice = discountedPrices.length > 0 ? Math.min(...discountedPrices) : 0;
- const maxPrice = discountedPrices.length > 0 ? Math.max(...discountedPrices) : 1000;
- const [priceRange, setPriceRange] = useState<[number, number]>([
- minPrice,
- maxPrice,
- ]);
+ // Filter aus URL übernehmen
+ useEffect(() => {
+ const params = new URLSearchParams(location.search);
+ const category = params.get("category");
+ if (category && categoriesFilter.some((f) => f.value === category)) {
+ setSelectedCategory(category);
+ } else {
+ setSelectedCategory(null);
+ }
+ }, [location.search, categoriesFilter]);
- // Filter aus URL übernehmen
- useEffect(() => {
- const params = new URLSearchParams(location.search);
- const category = params.get("category");
- if (category && categoriesFilter.some((f) => f.value === category)) {
- setSelectedCategory(category);
- } else {
- setSelectedCategory(null);
- }
- }, [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());
+ });
+ }, [items, priceRange, selectedCategory, selectedRating, searchQuery]);
- // 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())
- );
- });
- }, [items, priceRange, selectedCategory, selectedRating, searchQuery]);
+ // Lese die Suchanfrage aus der URL
+ useEffect(() => {
+ const params = new URLSearchParams(location.search);
+ const query = params.get("search");
+ setSearchQuery(query);
+ }, [location.search]);
+ // Container Ref
+ const containerRef = useRef(null);
- // Lese die Suchanfrage aus der URL
- useEffect(() => {
- const params = new URLSearchParams(location.search);
- const query = params.get("search");
- setSearchQuery(query);
- }, [location.search]);
+ const prevItemsLength = useRef(items.length);
+ useEffect(() => {
+ if (items.length >= prevItemsLength.current) {
+ prevItemsLength.current = items.length;
+ return;
+ }
- // Container Ref
- const containerRef = useRef(null);
+ setTimeout(() => {
+ containerRef.current?.scrollTo(0, 0);
+ }, 50);
- const prevItemsLength = useRef(items.length);
+ prevItemsLength.current = items.length;
+ }, [items]);
- useEffect(() => {
- if (items.length >= prevItemsLength.current) {
- prevItemsLength.current = items.length;
- return;
- }
+ // Kategorie-Änderung
+ const handleCategoryChange = (category: string) => {
+ if (category === "") {
+ setSelectedCategory(null);
+ navigate(`/`);
+ } else {
+ setSelectedCategory(category);
+ navigate(`/?category=${encodeURIComponent(category)}`);
+ }
+ };
- setTimeout(() => {
- containerRef.current?.scrollTo(0, 0);
- }, 50);
+ // Rating-Änderung (bleibt gleich)
+ const handleRatingChange = (rating: string) => {
+ if (rating === "") {
+ setSelectedRating(null);
+ } else {
+ setSelectedRating(rating);
+ }
+ };
- prevItemsLength.current = items.length;
- }, [items]);
-
- // Kategorie-Änderung
- const handleCategoryChange = (category: string) => {
- if (category === "") {
- setSelectedCategory(null);
- navigate(`/`);
- } else {
- setSelectedCategory(category);
- navigate(`/?category=${encodeURIComponent(category)}`);
- }
- };
-
- // Rating-Änderung (bleibt gleich)
- const handleRatingChange = (rating: string) => {
- if (rating === "") {
- setSelectedRating(null);
- } else {
- setSelectedRating(rating);
- }
- };
-
- return (
-
-
-
-
{
- setPriceRange(range);
- }}
- />
-
-
-
- {isLoading && t('loading')}
- {!isLoading &&
+
+
+
{
+ setPriceRange(range);
+ }}
+ />
+
+
+
+ {isLoading && t("loading")}
+ {!isLoading && (
+
+
+ {filteredItems.length === 0 ? (
+
-
- {filteredItems.length === 0 ? (
-
- {t("noItemsFound")}
-
- ) : (
- filteredItems.map(item => (
-
- ))
- )}
-
- }
-
-
- );
+ {t("noItemsFound")}
+
+ ) : (
+ filteredItems.map((item) => (
+
+ ))
+ )}
+
+
+ )}
+
+
+ );
}
diff --git a/01-frontend/src/pages/NoPage.tsx b/01-frontend/src/pages/NoPage.tsx
index 5d97f67..45cdd7f 100644
--- a/01-frontend/src/pages/NoPage.tsx
+++ b/01-frontend/src/pages/NoPage.tsx
@@ -1,41 +1,40 @@
-import {Box, Button, Typography, 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";
+import { useTranslation } from "react-i18next";
export default function NoPage() {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const navigate = useNavigate();
- const theme = useTheme();
- const {t} = useTranslation();
- const navigate = useNavigate();
+ const handleGoHome = () => {
+ navigate("/");
+ };
- const handleGoHome = () => {
- navigate("/");
- };
-
- return (
-
-
- 404
-
-
- {t('pageDoesNotExist')}
-
-
- {t('wrongTurn')}
-
-
- {t('backToHome')}
-
-
- );
-}
\ No newline at end of file
+ return (
+
+
+ 404
+
+
+ {t("pageDoesNotExist")}
+
+
+ {t("wrongTurn")}
+
+
+ {t("backToHome")}
+
+
+ );
+}
diff --git a/01-frontend/src/pages/Orders.tsx b/01-frontend/src/pages/Orders.tsx
index 3c118ae..6b952f5 100644
--- a/01-frontend/src/pages/Orders.tsx
+++ b/01-frontend/src/pages/Orders.tsx
@@ -1,149 +1,185 @@
import {
- Box,
- Button,
- Dialog,
- DialogActions,
- DialogContent,
- DialogTitle,
- Divider,
- List,
- ListItemButton,
- ListItemText,
- Paper,
- Stack,
- Tab,
- Tabs,
- Typography
+ 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 { 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 {user} = useAccount();
- const [orders, setOrders] = useState([])
+ 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
+ retry: 3, // Versucht es 3-mal erneut
+ retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
+ });
- 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
- retry: 3, // Versucht es 3-mal erneut
- retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
- });
+ useEffect(() => {
+ setOrders(accountOrders ?? []);
+ console.log("Orders fetched:", accountOrders);
+ }, [accountOrders]);
- useEffect(() => {
- setOrders(accountOrders ?? []);
- console.log("Orders fetched:", accountOrders);
- }, [accountOrders]);
+ const { t } = useTranslation();
- const {t} = useTranslation();
+ const [tab, setTab] = useState(0);
+ const [selectedOrder, setSelectedOrder] = useState(null);
- const [tab, setTab] = useState(0);
- const [selectedOrder, setSelectedOrder] = useState(null);
+ const activeOrders = orders.filter(
+ (o) =>
+ o.status === OrderStatusEnum.ISSUES ||
+ o.status === OrderStatusEnum.IN_PROGRESS ||
+ o.status === OrderStatusEnum.ORDERED,
+ );
+ const inactiveOrders = orders.filter(
+ (o) =>
+ o.status === OrderStatusEnum.CANCELLED ||
+ o.status === OrderStatusEnum.DELIVERED,
+ );
- const activeOrders = orders.filter(o => o.status === OrderStatusEnum.ISSUES || o.status === OrderStatusEnum.IN_PROGRESS || o.status === OrderStatusEnum.ORDERED);
- const inactiveOrders = orders.filter(o => o.status === OrderStatusEnum.CANCELLED || o.status === OrderStatusEnum.DELIVERED);
+ const handleTabChange = (_: React.SyntheticEvent, newValue: number) =>
+ setTab(newValue);
- 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 handleCancelOrder = async () => {
+ await cancleOrder();
+ setSelectedOrder(null);
+ refetch();
+ };
- 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,
- });
+ return (
+
+
+
+ {t("myOrders")}
+
+
+
+
+
+
+ {tab === 0 ? (
+ activeOrders.length > 0 ? (
+
+ {activeOrders.map((order) => (
+ setSelectedOrder(order)}
+ >
+
+
+ ))}
+
+ ) : (
+
+ {t("noActiveOrders")}
+
+ )
+ ) : inactiveOrders.length > 0 ? (
+
+ {inactiveOrders.map((order) => (
+ setSelectedOrder(order)}
+ >
+
+
+ ))}
+
+ ) : (
+
+ {t("noPreviousOrders")}
+
+ )}
- const handleCancelOrder = async () => {
- await cancleOrder();
- setSelectedOrder(null);
- refetch();
- };
-
- return (
-
-
-
- {t('myOrders')}
+ setSelectedOrder(null)}
+ maxWidth="sm"
+ fullWidth
+ >
+
+ {`${selectedOrder?.status === OrderStatusEnum.IN_PROGRESS ? t("activeOrder") : t("previousOrder")} #${selectedOrder?.id}`}
+
+
+ {selectedOrder && (
+
+ {`${t("orderDate")}: ${new Date(selectedOrder.time).toUTCString()}`}
+
+
+ {t("orderedItems")}:
-
-
-
-
-
- {tab === 0 ? (
- activeOrders.length > 0 ? (
-
- {activeOrders.map(order => (
- setSelectedOrder(order)}>
-
-
- ))}
-
- ) : (
- {t('noActiveOrders')}
- )
- ) : (
- inactiveOrders.length > 0 ? (
-
- {inactiveOrders.map(order => (
- setSelectedOrder(order)}>
-
-
- ))}
-
- ) : (
- {t('noPreviousOrders')}
- )
- )}
-
- setSelectedOrder(null)} maxWidth="sm" fullWidth>
-
- {`${selectedOrder?.status === OrderStatusEnum.IN_PROGRESS ? t('activeOrder') : t('previousOrder')} #${selectedOrder?.id}`}
-
-
- {selectedOrder && (
-
- {`${t('orderDate')}: ${new Date(selectedOrder.time).toUTCString()}`}
-
- {t('orderedItems')}:
-
- {selectedOrder.orderItems.map((item, idx) => (
-
- ))}
-
-
- {`${t('sum')}: ${(selectedOrder.total / 100).toFixed(2)} €`}
-
- )}
-
-
- {(selectedOrder?.status === OrderStatusEnum.ISSUES || selectedOrder?.status === OrderStatusEnum.IN_PROGRESS || selectedOrder?.status === OrderStatusEnum.ORDERED) && (
- handleCancelOrder()}>
- {t('cancelOrder')}
-
- )}
- setSelectedOrder(null)}>{t('close')}
-
-
-
-
- );
+
+ {selectedOrder.orderItems.map((item, idx) => (
+
+ ))}
+
+
+ {`${t("sum")}: ${(selectedOrder.total / 100).toFixed(2)} €`}
+
+ )}
+
+
+ {(selectedOrder?.status === OrderStatusEnum.ISSUES ||
+ selectedOrder?.status === OrderStatusEnum.IN_PROGRESS ||
+ selectedOrder?.status === OrderStatusEnum.ORDERED) && (
+ handleCancelOrder()}>
+ {t("cancelOrder")}
+
+ )}
+ setSelectedOrder(null)}>{t("close")}
+
+
+
+
+ );
}
diff --git a/01-frontend/src/pages/Payment.tsx b/01-frontend/src/pages/Payment.tsx
index e010eb0..b747e03 100644
--- a/01-frontend/src/pages/Payment.tsx
+++ b/01-frontend/src/pages/Payment.tsx
@@ -1,373 +1,403 @@
import {
- Alert,
- Box,
- Button,
- Container,
- Divider,
- Grid,
- List,
- ListItem,
- ListItemText,
- Paper,
- Step,
- StepLabel,
- Stepper,
- 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 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';
+ Alert,
+ Box,
+ Button,
+ Container,
+ Divider,
+ Grid,
+ List,
+ ListItem,
+ ListItemText,
+ Paper,
+ Step,
+ StepLabel,
+ Stepper,
+ 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 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";
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')}
-
- ) : (
-
- {basket.map((item) => (
-
-
+function generateBasket(
+ t: TFunction<"translation", undefined>,
+ basket: BasketItem[],
+) {
+ return basket.length === 0 ? (
+
+ {t("basketEmpty")}
+
+ ) : (
+
+ {basket.map((item) => (
+
+
-
- {`${(item.quantity * getDiscountedPrice(item.item)).toFixed(2)} €`}
- {item.item.discount100 > 0 ?
{-item.item.discount100}% : ""}
-
-
- ))}
-
- )
+
+ {`${(item.quantity * getDiscountedPrice(item.item)).toFixed(2)} €`}
+
+ {item.item.discount100 > 0 ? (
+
{-item.item.discount100}%
+ ) : (
+ ""
+ )}
+
+
+ ))}
+
+ );
}
-function generateTotal(t: TFunction<"translation", undefined>, basket: BasketItem[]) {
- return basket.length === 0 ? "" :
-
- {t('total') + ": " + basket.map((item) => item.quantity * getDiscountedPrice(item.item))
- .reduce((prev: number, cur: number) => prev + cur, 0).toFixed(2) + ` €`}
-
+function generateTotal(
+ t: TFunction<"translation", undefined>,
+ basket: BasketItem[],
+) {
+ return basket.length === 0 ? (
+ ""
+ ) : (
+
+ {t("total") +
+ ": " +
+ basket
+ .map((item) => item.quantity * getDiscountedPrice(item.item))
+ .reduce((prev: number, cur: number) => prev + cur, 0)
+ .toFixed(2) +
+ ` €`}
+
+ );
}
export default function Payment() {
+ const { t } = useTranslation();
+ const { basket, clearBasket } = useBasket();
+ const navigator = useNavigate();
+ const [activeStep, setActiveStep] = useState(0);
+ const [shippingDetails, setShippingDetails] = useState({
+ id: 0, // This will be set by the backend or user data
+ name: "",
+ surname: "",
+ address: "",
+ zip: "",
+ country: "Deutschland",
+ });
+ const [orderNumber, setOrderNumber] = useState(null);
+ const steps = [
+ t("reviewCart"),
+ t("shippingDetails"),
+ t("payment"),
+ t("orderSummary"),
+ ];
+ const { user } = useAccount();
+ const submitOrderData: OrderType = {
+ id: 0, // This will be set by the backend
+ customerId: user ? user.customerId : 0, // Use user ID if logged in, otherwise 0
+ time: Date.now(),
+ status: OrderStatusEnum.ORDERED, // Initial status when order is placed
+ orderItems: basket.map((item) => ({
+ id: item.item.id,
+ amount: item.quantity,
+ article: item.item.uuid, // Assuming UUID is the identifier for the item
+ })),
+ total: basket.reduce(
+ (total, item) => total + item.quantity * getDiscountedPrice(item.item),
+ 0,
+ ),
+ };
- const {t} = useTranslation();
- const {basket, clearBasket} = useBasket();
- const navigator = useNavigate();
- const [activeStep, setActiveStep] = useState(0);
- const [shippingDetails, setShippingDetails] = useState({
- id: 0, // This will be set by the backend or user data
- name: '',
- surname: '',
- address: '',
- zip: '',
- country: 'Deutschland',
- });
- const [orderNumber, setOrderNumber] = useState(null);
- const steps = [t('reviewCart'), t('shippingDetails'), t('payment'), t('orderSummary')];
- const {user} = useAccount();
+ const { refetch: refetchCustomer } = useQuery({
+ queryKey: ["submitCustomer", shippingDetails],
+ queryFn: () => submitCustomer(shippingDetails),
+ retry: 0,
+ retryDelay: 1000,
+ enabled: false,
+ });
- const submitOrderData: OrderType = {
- id: 0, // This will be set by the backend
- customerId: user ? user.customerId : 0, // Use user ID if logged in, otherwise 0
- time: Date.now(),
- status: OrderStatusEnum.ORDERED, // Initial status when order is placed
- orderItems: basket.map(item => ({
- id: item.item.id,
- amount: item.quantity,
- article: item.item.uuid, // Assuming UUID is the identifier for the item
- })),
- total: basket.reduce((total, item) => total + (item.quantity * getDiscountedPrice(item.item)), 0),
- };
+ const showAlert = () => {
+ return //TODO: ;
+ };
- const {refetch: refetchCustomer} = useQuery({
- queryKey: ["submitCustomer", shippingDetails],
- queryFn: () => submitCustomer(shippingDetails),
- retry: 0,
- retryDelay: 1000,
- enabled: false,
- });
+ const { refetch: customerData } = useQuery({
+ queryKey: ["fetchCustomer", user?.customerId],
+ queryFn: () => fetchCustomer(user?.customerId || 0), // Funktion zum Abrufen der Kundendaten
+ enabled: false,
+ });
- const showAlert = () => {
- return
- //TODO:
-
- };
-
- 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);
- }
- }
- };
-
- fetchShippingDetails();
- }, [user, customerData]);
-
-
- // Verwende useMutation statt useQuery für submitOrder
- const {mutateAsync: submitOrderMutation} = useMutation({
- mutationFn: (orderData: OrderType) => submitOrder(orderData),
- });
-
- const handleNext = async () => {
- let next: boolean = true;
-
- if (activeStep === steps.length - 2) {
- // Simulate order placement and generate order number
- const generatedOrderNumber = `ORD-${Math.floor(Math.random() * 1000000)}`;
- setOrderNumber(generatedOrderNumber);
-
- let customerId = user ? user.customerId : 0;
- if (!customerId) {
- const customerResponse = await refetchCustomer();
- customerId = customerResponse.data.id;
- }
-
- // Erzeuge die Orderdaten mit der richtigen customerId
- const orderData: OrderType = {
- ...submitOrderData,
- customerId,
- };
-
- try {
- await submitOrderMutation(orderData);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- } catch (e) {
- next = false;
- }
- }
- if (next) {
- setActiveStep((prevStep) => prevStep + 1);
- } else {
- showAlert();
+ 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);
}
+ }
};
- const handleBack = () => {
- setActiveStep((prevStep) => prevStep - 1);
- };
+ fetchShippingDetails();
+ }, [user, customerData]);
- const handleInputChange = (e: React.ChangeEvent) => {
- const {name, value} = e.target;
- setShippingDetails((prevDetails) => ({
- ...prevDetails,
- [name]: value,
- }));
- };
- const handleClearBasket = () => {
- clearBasket();
- };
+ // Verwende useMutation statt useQuery für submitOrder
+ const { mutateAsync: submitOrderMutation } = useMutation({
+ mutationFn: (orderData: OrderType) => submitOrder(orderData),
+ });
- // Hilfsfunktion prüfen, ob alle Pflichtfelder ausgefüllt sind
- const isShippingDetailsValid = () => {
- return (
- shippingDetails.name.trim() !== '' &&
- shippingDetails.surname.trim() !== '' &&
- shippingDetails.address.trim() !== '' &&
- shippingDetails.zip.trim() !== '' &&
- shippingDetails.country.trim() !== ''
- );
- };
+ const handleNext = async () => {
+ let next: boolean = true;
- const renderStepContent = (step: number) => {
- switch (step) {
- case 0:
- return (
-
-
- {t('reviewCart')}
-
- {generateBasket(t, basket)}
-
-
- {t('clearCart')}
-
- {generateTotal(t, basket)}
-
- );
- case 1:
- return (
-
-
- {t('shippingDetails')}
-
-
+ if (activeStep === steps.length - 2) {
+ // Simulate order placement and generate order number
+ const generatedOrderNumber = `ORD-${Math.floor(Math.random() * 1000000)}`;
+ setOrderNumber(generatedOrderNumber);
-
+ let customerId = user ? user.customerId : 0;
+ if (!customerId) {
+ const customerResponse = await refetchCustomer();
+ customerId = customerResponse.data.id;
+ }
-
+ // Erzeuge die Orderdaten mit der richtigen customerId
+ const orderData: OrderType = {
+ ...submitOrderData,
+ customerId,
+ };
-
+ try {
+ await submitOrderMutation(orderData);
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (e) {
+ next = false;
+ }
+ }
+ if (next) {
+ setActiveStep((prevStep) => prevStep + 1);
+ } else {
+ showAlert();
+ }
+ };
-
+ const handleBack = () => {
+ setActiveStep((prevStep) => prevStep - 1);
+ };
-
-
-
- );
- case 2:
- return (
-
-
- {t('payment')}
-
-
- {t('paymentNotAvailable')}
-
-
- );
- case 3:
- return (
-
-
- {t('orderSummary')}
-
-
- {t('thanksForOrder')}
-
-
- {t('yourOrderNumber')}: {orderNumber}
-
-
- {t('shippingDetails')}:
-
- {shippingDetails.name} {shippingDetails.surname}
- {shippingDetails.address}
- {shippingDetails.zip} {shippingDetails.country}
-
-
- {t('orderedItems')}:
- {generateBasket(t, basket)}
-
- {generateTotal(t, basket)}
-
-
- );
- default:
- return Unknown step
;
- }
- };
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setShippingDetails((prevDetails) => ({
+ ...prevDetails,
+ [name]: value,
+ }));
+ };
+ const handleClearBasket = () => {
+ clearBasket();
+ };
+ // Hilfsfunktion prüfen, ob alle Pflichtfelder ausgefüllt sind
+ const isShippingDetailsValid = () => {
return (
-
-
-
-
- {t('completeYourOrder')}
-
-
- {steps.map((label) => (
-
- {label}
-
- ))}
-
- {renderStepContent(activeStep)}
-
-
- {t('back')}
-
- {activeStep === steps.length - 1 ? (
- {
- handleClearBasket();
- navigator('/');
- }}
- >
- {t('finish')}
-
- ) : (
-
- {activeStep === steps.length - 2 ? t('placeOrder') : t('next')}
-
- )}
-
-
-
-
+ shippingDetails.name.trim() !== "" &&
+ shippingDetails.surname.trim() !== "" &&
+ shippingDetails.address.trim() !== "" &&
+ shippingDetails.zip.trim() !== "" &&
+ shippingDetails.country.trim() !== ""
);
-}
\ No newline at end of file
+ };
+
+ const renderStepContent = (step: number) => {
+ switch (step) {
+ case 0:
+ return (
+
+
+ {t("reviewCart")}
+
+ {generateBasket(t, basket)}
+
+
+ {t("clearCart")}
+
+ {generateTotal(t, basket)}
+
+ );
+ case 1:
+ return (
+
+
+ {t("shippingDetails")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ case 2:
+ return (
+
+
+ {t("payment")}
+
+ {t("paymentNotAvailable")}
+
+ );
+ case 3:
+ return (
+
+
+ {t("orderSummary")}
+
+
+ {t("thanksForOrder")}
+
+
+ {t("yourOrderNumber")}: {orderNumber}
+
+
+ {t("shippingDetails")}:
+
+ {shippingDetails.name} {shippingDetails.surname}
+
+ {shippingDetails.address}
+
+ {shippingDetails.zip} {shippingDetails.country}
+
+
+
+ {t("orderedItems")}:
+ {generateBasket(t, basket)}
+
+ {generateTotal(t, basket)}
+
+
+ );
+ default:
+ return Unknown step
;
+ }
+ };
+
+ return (
+
+
+
+
+ {t("completeYourOrder")}
+
+
+ {steps.map((label) => (
+
+ {label}
+
+ ))}
+
+ {renderStepContent(activeStep)}
+
+
+ {t("back")}
+
+ {activeStep === steps.length - 1 ? (
+ {
+ handleClearBasket();
+ navigator("/");
+ }}
+ >
+ {t("finish")}
+
+ ) : (
+
+ {activeStep === steps.length - 2 ? t("placeOrder") : t("next")}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/01-frontend/src/pages/Product.tsx b/01-frontend/src/pages/Product.tsx
index 064c0d9..2cef099 100644
--- a/01-frontend/src/pages/Product.tsx
+++ b/01-frontend/src/pages/Product.tsx
@@ -1,92 +1,88 @@
-import {Box, Button, Container, Divider, Typography} from '@mui/material';
-import {useLocation, useNavigate} from 'react-router-dom';
-import Item from '../components/Item';
-import ProductInfo from '../helper/productpage/ProductInfo';
-import Ratings from '../helper/productpage/Ratings';
-import {useTranslation} from "react-i18next";
-import {useTheme} from '@mui/material/styles';
+import { Box, Button, Container, Divider, Typography } from "@mui/material";
+import { useLocation, useNavigate } from "react-router-dom";
+import Item from "../components/Item";
+import ProductInfo from "../helper/productpage/ProductInfo";
+import Ratings from "../helper/productpage/Ratings";
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@mui/material/styles";
export default function Product() {
- const {t} = useTranslation();
- const location = useLocation();
- const item = location.state?.item as Item;
- const navigate = useNavigate();
- const theme = useTheme(); // Zugriff auf das aktive Theme
+ const { t } = useTranslation();
+ const location = useLocation();
+ const item = location.state?.item as Item;
+ const navigate = useNavigate();
+ const theme = useTheme(); // Zugriff auf das aktive Theme
- const handleGoHome = () => {
- navigate("/");
- };
-
- // Wenn kein Produkt vorhanden ist
- if (!item) {
- return (
-
-
- {t('productNotFound')}
-
-
- {t('productDoesNotExist')}
-
-
- {t('backToHome')}
-
-
- );
- }
+ const handleGoHome = () => {
+ navigate("/");
+ };
+ // Wenn kein Produkt vorhanden ist
+ if (!item) {
return (
-
+ {t("productNotFound")}
+ {t("productDoesNotExist")}
+
-
-
-
-
-
-
-
-
- {t('articleNumber')}: {item.uuid}
-
-
-
- {item.description}
-
-
-
-
-
+ {t("backToHome")}
+
+
);
-};
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {t("articleNumber")}: {item.uuid}
+
+
+
+ {item.description}
+
+
+
+
+
+ );
+}
diff --git a/01-frontend/src/pages/pages.css b/01-frontend/src/pages/pages.css
index b5270f4..faa68e1 100644
--- a/01-frontend/src/pages/pages.css
+++ b/01-frontend/src/pages/pages.css
@@ -1,170 +1,170 @@
.no-page-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 100vh;
- text-align: center;
- gap: 2%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ text-align: center;
+ gap: 2%;
}
.no-page-title {
- font-size: 8rem;
- font-weight: bold;
- margin-bottom: 16px;
+ font-size: 8rem;
+ font-weight: bold;
+ margin-bottom: 16px;
}
.no-page-subtitle {
- font-size: 1.5rem;
- margin-bottom: 8px;
+ font-size: 1.5rem;
+ margin-bottom: 8px;
}
.no-page-description {
- font-size: 1rem;
- margin-bottom: 24px;
+ font-size: 1rem;
+ margin-bottom: 24px;
}
.no-page-button {
- font-size: 1rem;
- padding: 12px 24px;
- background-color: #0fd13f;
- color: white;
- border-radius: 8px;
- transition: background-color 0.3s ease;
+ font-size: 1rem;
+ padding: 12px 24px;
+ background-color: #0fd13f;
+ color: white;
+ border-radius: 8px;
+ transition: background-color 0.3s ease;
}
.no-page-button:hover {
- background-color: #0cc634;
+ background-color: #0cc634;
}
.cardgrid {
- display: grid;
- gap: 24px;
- width: 90%;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- padding: 16px 16px 64px 16px;
- margin: 0 auto;
- box-sizing: border-box;
+ display: grid;
+ gap: 24px;
+ width: 90%;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ padding: 16px 16px 64px 16px;
+ margin: 0 auto;
+ box-sizing: border-box;
}
.page-background {
- background-color: var(--background-color);
- min-height: var(--page-height);
- display: flex;
- flex-direction: column;
- overflow: auto;
- box-sizing: border-box;
- width: 100%;
- color: var(--text-color);
+ background-color: var(--background-color);
+ min-height: var(--page-height);
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ box-sizing: border-box;
+ width: 100%;
+ color: var(--text-color);
}
.toppad {
- padding: 20px 0;
+ padding: 20px 0;
}
.page-background-center {
- align-items: center;
- justify-content: space-between;
+ align-items: center;
+ justify-content: space-between;
}
.page-background.page-background--no-space-between {
- justify-content: flex-start !important;
+ justify-content: flex-start !important;
}
.page-table {
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
- height: var(--page-height);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ height: var(--page-height);
}
.impressum-container {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- justify-content: flex-start;
- padding: 20px;
- text-align: left;
- background-color: var(--background-color);
- color: var(--text-color);
- height: var(--page-height);
- margin: 0;
- width: 100%;
- box-sizing: border-box;
- overflow: auto;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-start;
+ padding: 20px;
+ text-align: left;
+ background-color: var(--background-color);
+ color: var(--text-color);
+ height: var(--page-height);
+ margin: 0;
+ width: 100%;
+ box-sizing: border-box;
+ overflow: auto;
}
.impressum-title {
- font-size: 2.5rem;
- font-weight: bold;
- margin-bottom: 16px;
- color: var(--text-color);
+ font-size: 2.5rem;
+ font-weight: bold;
+ margin-bottom: 16px;
+ color: var(--text-color);
}
.impressum-content {
- font-size: 1rem;
- line-height: 1.6;
- color: var(--text-color);
+ font-size: 1rem;
+ line-height: 1.6;
+ color: var(--text-color);
}
.contact-divider {
- background-color: var(--text-color);
- height: 2px;
+ background-color: var(--text-color);
+ height: 2px;
}
.contact-divider-box {
- width: 100%;
- margin: 25px 0;
+ width: 100%;
+ margin: 25px 0;
}
.product-page-background {
- background-color: var(--background-color);
- height: 100%;
- min-height: 600px;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: space-between;
- padding: 20px 0;
- box-sizing: border-box;
- color: var(--text-color);
+ background-color: var(--background-color);
+ height: 100%;
+ min-height: 600px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ padding: 20px 0;
+ box-sizing: border-box;
+ color: var(--text-color);
}
.home-page-background {
- display: flex;
- align-items: stretch;
- width: 100%;
- scroll-behavior: smooth;
- height: 100vh;
- box-sizing: border-box;
- color: var(--text-color);
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ scroll-behavior: smooth;
+ height: 100vh;
+ box-sizing: border-box;
+ color: var(--text-color);
}
.sidebar {
- width: fit-content;
- display: grid;
- place-self: start;
- white-space: nowrap;
- margin-top: 2vh;
+ width: fit-content;
+ display: grid;
+ place-self: start;
+ white-space: nowrap;
+ margin-top: 2vh;
}
.sidebar-filter {
- margin: 30px
+ margin: 30px;
}
.no-results {
- text-align: center;
- font-size: 1rem;
- min-width: 600px;
- background-color: var(--background-color);
- color: var(--text-color);
+ text-align: center;
+ font-size: 1rem;
+ min-width: 600px;
+ background-color: var(--background-color);
+ color: var(--text-color);
}
.rightBound {
- float: right;
+ float: right;
}
.red {
- color: #F00;
+ color: #f00;
}
diff --git a/01-frontend/src/theme/ThemeContext.tsx b/01-frontend/src/theme/ThemeContext.tsx
index 91e1293..de7077e 100644
--- a/01-frontend/src/theme/ThemeContext.tsx
+++ b/01-frontend/src/theme/ThemeContext.tsx
@@ -1,185 +1,204 @@
-'use client';
-import React, {createContext, ReactNode, useContext, useEffect, useState} from 'react';
-import {createTheme, ThemeProvider} from '@mui/material/styles';
-import {CssBaseline, GlobalStyles} from '@mui/material';
-import useMediaQuery from '@mui/material/useMediaQuery';
+"use client";
+import React, {
+ createContext,
+ ReactNode,
+ useContext,
+ useEffect,
+ useState,
+} from "react";
+import { createTheme, ThemeProvider } from "@mui/material/styles";
+import { CssBaseline, GlobalStyles } from "@mui/material";
+import useMediaQuery from "@mui/material/useMediaQuery";
-type ThemeMode = 'light' | 'dark';
+type ThemeMode = "light" | "dark";
interface ThemeContextType {
- mode: ThemeMode;
- toggleMode: () => void;
+ mode: ThemeMode;
+ toggleMode: () => void;
}
const ThemeContext = createContext({
- mode: 'light',
- toggleMode: () => {
- },
+ mode: "light",
+ toggleMode: () => {},
});
export const useThemeMode = () => useContext(ThemeContext);
interface CustomThemeProviderProps {
- children: ReactNode;
+ children: ReactNode;
}
-export const CustomThemeProvider: React.FC = ({children}) => {
- // SSR-sichere System-Präferenz-Erkennung
- const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)', {noSsr: true});
+export const CustomThemeProvider: React.FC = ({
+ children,
+}) => {
+ // SSR-sichere System-Präferenz-Erkennung
+ const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)", {
+ noSsr: true,
+ });
- // SSR-sichere Initialisierung
- const [mode, setMode] = useState('light');
- const [mounted, setMounted] = useState(false);
+ // SSR-sichere Initialisierung
+ const [mode, setMode] = useState("light");
+ const [mounted, setMounted] = useState(false);
- // Nach dem ersten Render ausführen (SSR-sicher)
- useEffect(() => {
- setMounted(true);
+ // Nach dem ersten Render ausführen (SSR-sicher)
+ useEffect(() => {
+ setMounted(true);
- if (typeof window !== 'undefined') {
- const savedMode = localStorage.getItem('themeMode') as ThemeMode;
- if (savedMode === 'light' || savedMode === 'dark') {
- setMode(savedMode);
- } else {
- setMode(prefersDarkMode ? 'dark' : 'light');
- }
- }
- }, [prefersDarkMode]);
-
- // Mode in localStorage speichern
- useEffect(() => {
- if (mounted && typeof window !== 'undefined') {
- localStorage.setItem('themeMode', mode);
- }
- }, [mode, mounted]);
-
- // Browser-only DOM-Manipulation
- useEffect(() => {
- if (mounted && typeof window !== 'undefined') {
- const backgroundColor = mode === 'dark' ? '#121212' : '#fafafa';
-
- // Warten bis DOM geladen ist
- const updateBackground = () => {
- document.documentElement.style.setProperty('--background-color', backgroundColor);
- document.documentElement.style.backgroundColor = backgroundColor;
- document.body.style.backgroundColor = backgroundColor;
-
- const root = document.getElementById('root');
- if (root) {
- root.style.backgroundColor = backgroundColor;
- }
- };
-
- // Sofort ausführen
- updateBackground();
-
- // Nach einem kurzen Delay nochmal (für hartnäckige Cases)
- const timeoutId = setTimeout(updateBackground, 100);
-
- return () => clearTimeout(timeoutId);
- }
- }, [mode, mounted]);
-
- const toggleMode = () => {
- setMode(prevMode => prevMode === 'light' ? 'dark' : 'light');
- };
-
- // Theme basierend auf Mode erstellen
- const theme = React.useMemo(() =>
- createTheme({
- palette: {
- mode,
- primary: {
- main: '#0fd13f', // Grüne NavBar
- light: mode === 'dark' ? '#4caf50' : '#42a5f5',
- dark: mode === 'dark' ? '#388e3c' : '#1565c0',
- contrastText: '#fff',
- },
- secondary: {
- main: mode === 'dark' ? '#bb86fc' : '#9c27b0',
- light: mode === 'dark' ? '#d7aefb' : '#ba68c8',
- dark: mode === 'dark' ? '#985eff' : '#7b1fa2',
- contrastText: '#fff',
- },
- background: {
- default: mode === 'dark' ? '#121212' : '#fafafa',
- paper: mode === 'dark' ? '#1e1e1e' : '#ffffff',
- },
- text: {
- primary: mode === 'dark' ? '#ffffff' : '#000000',
- secondary: mode === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.6)',
- },
- homepage: mode === 'dark' ? '#1e1e1e' : '#f4f4f4',
- },
- components: {
- MuiCssBaseline: {
- styleOverrides: {
- html: {
- backgroundColor: `${mode === 'dark' ? '#121212' : '#fafafa'} !important`,
- transition: 'background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)',
- },
- body: {
- backgroundColor: `${mode === 'dark' ? '#121212' : '#fafafa'} !important`,
- transition: 'background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)',
- margin: 0,
- },
- },
- },
- MuiAppBar: {
- styleOverrides: {
- colorPrimary: {
- backgroundColor: mode === 'dark' ? '#388e3c' : '#0fd13f',
- color: '#ffffff',
- },
- },
- },
- },
- }),
- [mode]
- );
-
- // Aggressive GlobalStyles mit CSS-Variablen
- const globalStyles = mounted ? (
-
- ) : null;
-
- // SSR-Fallback während mounted = false
- if (!mounted) {
- return (
-
-
- {children}
-
- );
+ if (typeof window !== "undefined") {
+ const savedMode = localStorage.getItem("themeMode") as ThemeMode;
+ if (savedMode === "light" || savedMode === "dark") {
+ setMode(savedMode);
+ } else {
+ setMode(prefersDarkMode ? "dark" : "light");
+ }
}
+ }, [prefersDarkMode]);
+ // Mode in localStorage speichern
+ useEffect(() => {
+ if (mounted && typeof window !== "undefined") {
+ localStorage.setItem("themeMode", mode);
+ }
+ }, [mode, mounted]);
+
+ // Browser-only DOM-Manipulation
+ useEffect(() => {
+ if (mounted && typeof window !== "undefined") {
+ const backgroundColor = mode === "dark" ? "#121212" : "#fafafa";
+
+ // Warten bis DOM geladen ist
+ const updateBackground = () => {
+ document.documentElement.style.setProperty(
+ "--background-color",
+ backgroundColor,
+ );
+ document.documentElement.style.backgroundColor = backgroundColor;
+ document.body.style.backgroundColor = backgroundColor;
+
+ const root = document.getElementById("root");
+ if (root) {
+ root.style.backgroundColor = backgroundColor;
+ }
+ };
+
+ // Sofort ausführen
+ updateBackground();
+
+ // Nach einem kurzen Delay nochmal (für hartnäckige Cases)
+ const timeoutId = setTimeout(updateBackground, 100);
+
+ return () => clearTimeout(timeoutId);
+ }
+ }, [mode, mounted]);
+
+ const toggleMode = () => {
+ setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
+ };
+
+ // Theme basierend auf Mode erstellen
+ const theme = React.useMemo(
+ () =>
+ createTheme({
+ palette: {
+ mode,
+ primary: {
+ main: "#0fd13f", // Grüne NavBar
+ light: mode === "dark" ? "#4caf50" : "#42a5f5",
+ dark: mode === "dark" ? "#388e3c" : "#1565c0",
+ contrastText: "#fff",
+ },
+ secondary: {
+ main: mode === "dark" ? "#bb86fc" : "#9c27b0",
+ light: mode === "dark" ? "#d7aefb" : "#ba68c8",
+ dark: mode === "dark" ? "#985eff" : "#7b1fa2",
+ contrastText: "#fff",
+ },
+ background: {
+ default: mode === "dark" ? "#121212" : "#fafafa",
+ paper: mode === "dark" ? "#1e1e1e" : "#ffffff",
+ },
+ text: {
+ primary: mode === "dark" ? "#ffffff" : "#000000",
+ secondary:
+ mode === "dark"
+ ? "rgba(255, 255, 255, 0.7)"
+ : "rgba(0, 0, 0, 0.6)",
+ },
+ homepage: mode === "dark" ? "#1e1e1e" : "#f4f4f4",
+ },
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ html: {
+ backgroundColor: `${mode === "dark" ? "#121212" : "#fafafa"} !important`,
+ transition:
+ "background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)",
+ },
+ body: {
+ backgroundColor: `${mode === "dark" ? "#121212" : "#fafafa"} !important`,
+ transition:
+ "background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)",
+ margin: 0,
+ },
+ },
+ },
+ MuiAppBar: {
+ styleOverrides: {
+ colorPrimary: {
+ backgroundColor: mode === "dark" ? "#388e3c" : "#0fd13f",
+ color: "#ffffff",
+ },
+ },
+ },
+ },
+ }),
+ [mode],
+ );
+
+ // Aggressive GlobalStyles mit CSS-Variablen
+ const globalStyles = mounted ? (
+
+ ) : null;
+
+ // SSR-Fallback während mounted = false
+ if (!mounted) {
return (
-
-
-
- {globalStyles}
- {children}
-
-
+
+
+ {children}
+
);
+ }
+
+ return (
+
+
+
+ {globalStyles}
+ {children}
+
+
+ );
};
diff --git a/01-frontend/src/theme/ThemeToggle.tsx b/01-frontend/src/theme/ThemeToggle.tsx
index ba1b71e..b28360b 100644
--- a/01-frontend/src/theme/ThemeToggle.tsx
+++ b/01-frontend/src/theme/ThemeToggle.tsx
@@ -1,31 +1,30 @@
// theme/ThemeToggle.tsx
-import React from 'react';
-import {IconButton, Tooltip} from '@mui/material';
-import {Brightness4, Brightness7} from '@mui/icons-material';
-import {useThemeMode} from './ThemeContext';
-import {useTranslation} from "react-i18next";
+import React from "react";
+import { IconButton, Tooltip } from "@mui/material";
+import { Brightness4, Brightness7 } from "@mui/icons-material";
+import { useThemeMode } from "./ThemeContext";
+import { useTranslation } from "react-i18next";
const ThemeToggle: React.FC = () => {
- const {mode, toggleMode} = useThemeMode();
- const {t} = useTranslation();
+ const { mode, toggleMode } = useThemeMode();
+ const { t } = useTranslation();
- return (
-
-
-
- {mode === 'dark' ? : }
-
-
- );
+ return (
+
+
+ {mode === "dark" ? : }
+
+
+ );
};
export default ThemeToggle;
diff --git a/01-frontend/src/theme/theme-augmentation.d.ts b/01-frontend/src/theme/theme-augmentation.d.ts
index 54f2f10..646201b 100644
--- a/01-frontend/src/theme/theme-augmentation.d.ts
+++ b/01-frontend/src/theme/theme-augmentation.d.ts
@@ -1,13 +1,13 @@
-import '@mui/material/styles';
+import "@mui/material/styles";
-declare module '@mui/material/styles' {
- interface Palette {
- tertiary: Palette['primary'];
- homepage: string;
- }
+declare module "@mui/material/styles" {
+ interface Palette {
+ tertiary: Palette["primary"];
+ homepage: string;
+ }
- interface PaletteOptions {
- tertiary?: PaletteOptions['primary'];
- homepage?: string;
- }
+ interface PaletteOptions {
+ tertiary?: PaletteOptions["primary"];
+ homepage?: string;
+ }
}
diff --git a/01-frontend/src/theme/theme.ts b/01-frontend/src/theme/theme.ts
index 94e571c..88b01cb 100644
--- a/01-frontend/src/theme/theme.ts
+++ b/01-frontend/src/theme/theme.ts
@@ -1,206 +1,206 @@
-import {createTheme} from '@mui/material/styles';
-import './theme-augmentation.d.ts'; // Falls vorhanden
+import { createTheme } from "@mui/material/styles";
+import "./theme-augmentation.d.ts"; // Falls vorhanden
export const darkmode = createTheme({
- palette: {
- primary: {
- main: '#0fd13f', // Grüne Standardfarbe
- light: '#42a5f5',
- dark: '#15650',
- contrastText: '#fff',
- },
- secondary: {
- main: '#9c27b0',
- light: '#ba68c8',
- dark: '#7b1fa2',
- contrastText: '#fff',
- },
- error: {
- main: '#f44336',
- light: '#e57373',
- dark: '#d32f2f',
- contrastText: '#fff',
- },
- warning: {
- main: '#ed6c02',
- light: '#ff9800',
- dark: '#e65100',
- contrastText: '#fff',
- },
- info: {
- main: '#0288d1',
- light: '#03a9f4',
- dark: '#01579b',
- contrastText: '#fff',
- },
- success: {
- main: '#2e7d32',
- light: '#4caf50',
- dark: '#1b5e20',
- contrastText: '#fff',
- },
- background: {
- default: '#fafafa',
- paper: '#ffffff',
- },
- text: {
- primary: '#000000',
- secondary: 'rgba(0, 0, 0, 0.6)',
- },
+ palette: {
+ primary: {
+ main: "#0fd13f", // Grüne Standardfarbe
+ light: "#42a5f5",
+ dark: "#15650",
+ contrastText: "#fff",
},
- // Sanfte Übergänge
- transitions: {
- duration: {
- shortest: 150,
- shorter: 200,
- short: 250,
- standard: 300,
- complex: 375,
- enteringScreen: 225,
- leavingScreen: 195,
- },
- easing: {
- easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
- easeOut: 'cubic-bezier(0.0, 0, 0.2, 1)',
- easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
- sharp: 'cubic-bezier(0.4, 0, 0.6, 1)',
- },
+ secondary: {
+ main: "#9c27b0",
+ light: "#ba68c8",
+ dark: "#7b1fa2",
+ contrastText: "#fff",
},
- // Verbesserte Komponenten-Overrides
- components: {
- MuiCssBaseline: {
- styleOverrides: {
- body: {
- backgroundColor: '#fafafa',
- transition: 'background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)',
- },
- html: {
- backgroundColor: '#fafafa',
- transition: 'background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)',
- },
- },
- },
- MuiAppBar: {
- styleOverrides: {
- colorPrimary: {
- backgroundColor: '#0fd13f', // Grüne NavBar
- color: '#ffffff',
- },
- },
- },
- MuiButton: {
- styleOverrides: {
- root: {
- borderRadius: 8,
- textTransform: 'none', // Entfernt automatische Großschreibung
- fontWeight: 600,
- },
- contained: {
- boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
- '&:hover': {
- boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
- },
- },
- },
- },
- MuiCard: {
- styleOverrides: {
- root: {
- borderRadius: 12,
- boxShadow: '0 2px 12px rgba(0,0,0,0.08)',
- transition: 'box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1)',
- '&:hover': {
- boxShadow: '0 4px 20px rgba(0,0,0,0.12)',
- },
- },
- },
- },
- MuiTextField: {
- styleOverrides: {
- root: {
- '& .MuiOutlinedInput-root': {
- borderRadius: 8,
- },
- },
- },
- },
- MuiChip: {
- styleOverrides: {
- root: {
- borderRadius: 16,
- },
- },
- },
- MuiPaper: {
- styleOverrides: {
- root: {
- borderRadius: 8,
- },
- elevation1: {
- boxShadow: '0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)',
- },
- elevation2: {
- boxShadow: '0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)',
- },
- },
- },
+ error: {
+ main: "#f44336",
+ light: "#e57373",
+ dark: "#d32f2f",
+ contrastText: "#fff",
},
- // Benutzerdefinierte Typografie
- typography: {
- fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
- h1: {
- fontWeight: 700,
- fontSize: '2.5rem',
- lineHeight: 1.2,
- },
- h2: {
- fontWeight: 600,
- fontSize: '2rem',
- lineHeight: 1.3,
- },
- h3: {
- fontWeight: 600,
- fontSize: '1.75rem',
- lineHeight: 1.3,
- },
- h4: {
- fontWeight: 600,
- fontSize: '1.5rem',
- lineHeight: 1.4,
- },
- h5: {
- fontWeight: 600,
- fontSize: '1.25rem',
- lineHeight: 1.4,
- },
- h6: {
- fontWeight: 600,
- fontSize: '1rem',
- lineHeight: 1.4,
- },
- body1: {
- fontSize: '1rem',
- lineHeight: 1.5,
- },
- body2: {
- fontSize: '0.875rem',
- lineHeight: 1.4,
- },
- button: {
- fontWeight: 600,
- textTransform: 'none',
- },
+ warning: {
+ main: "#ed6c02",
+ light: "#ff9800",
+ dark: "#e65100",
+ contrastText: "#fff",
},
- // Benutzerdefinierte Breakpoints
- breakpoints: {
- values: {
- xs: 0,
- sm: 600,
- md: 960,
- lg: 1280,
- xl: 1920,
- },
+ info: {
+ main: "#0288d1",
+ light: "#03a9f4",
+ dark: "#01579b",
+ contrastText: "#fff",
},
- // Angepasste Spacing-Funktion
- spacing: 8, // 8px als Basis-Einheit
+ success: {
+ main: "#2e7d32",
+ light: "#4caf50",
+ dark: "#1b5e20",
+ contrastText: "#fff",
+ },
+ background: {
+ default: "#fafafa",
+ paper: "#ffffff",
+ },
+ text: {
+ primary: "#000000",
+ secondary: "rgba(0, 0, 0, 0.6)",
+ },
+ },
+ // Sanfte Übergänge
+ transitions: {
+ duration: {
+ shortest: 150,
+ shorter: 200,
+ short: 250,
+ standard: 300,
+ complex: 375,
+ enteringScreen: 225,
+ leavingScreen: 195,
+ },
+ easing: {
+ easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)",
+ easeOut: "cubic-bezier(0.0, 0, 0.2, 1)",
+ easeIn: "cubic-bezier(0.4, 0, 1, 1)",
+ sharp: "cubic-bezier(0.4, 0, 0.6, 1)",
+ },
+ },
+ // Verbesserte Komponenten-Overrides
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ body: {
+ backgroundColor: "#fafafa",
+ transition: "background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)",
+ },
+ html: {
+ backgroundColor: "#fafafa",
+ transition: "background-color 300ms cubic-bezier(0.4, 0, 0.2, 1)",
+ },
+ },
+ },
+ MuiAppBar: {
+ styleOverrides: {
+ colorPrimary: {
+ backgroundColor: "#0fd13f", // Grüne NavBar
+ color: "#ffffff",
+ },
+ },
+ },
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: 8,
+ textTransform: "none", // Entfernt automatische Großschreibung
+ fontWeight: 600,
+ },
+ contained: {
+ boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
+ "&:hover": {
+ boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
+ },
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ borderRadius: 12,
+ boxShadow: "0 2px 12px rgba(0,0,0,0.08)",
+ transition: "box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1)",
+ "&:hover": {
+ boxShadow: "0 4px 20px rgba(0,0,0,0.12)",
+ },
+ },
+ },
+ },
+ MuiTextField: {
+ styleOverrides: {
+ root: {
+ "& .MuiOutlinedInput-root": {
+ borderRadius: 8,
+ },
+ },
+ },
+ },
+ MuiChip: {
+ styleOverrides: {
+ root: {
+ borderRadius: 16,
+ },
+ },
+ },
+ MuiPaper: {
+ styleOverrides: {
+ root: {
+ borderRadius: 8,
+ },
+ elevation1: {
+ boxShadow: "0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)",
+ },
+ elevation2: {
+ boxShadow: "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)",
+ },
+ },
+ },
+ },
+ // Benutzerdefinierte Typografie
+ typography: {
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
+ h1: {
+ fontWeight: 700,
+ fontSize: "2.5rem",
+ lineHeight: 1.2,
+ },
+ h2: {
+ fontWeight: 600,
+ fontSize: "2rem",
+ lineHeight: 1.3,
+ },
+ h3: {
+ fontWeight: 600,
+ fontSize: "1.75rem",
+ lineHeight: 1.3,
+ },
+ h4: {
+ fontWeight: 600,
+ fontSize: "1.5rem",
+ lineHeight: 1.4,
+ },
+ h5: {
+ fontWeight: 600,
+ fontSize: "1.25rem",
+ lineHeight: 1.4,
+ },
+ h6: {
+ fontWeight: 600,
+ fontSize: "1rem",
+ lineHeight: 1.4,
+ },
+ body1: {
+ fontSize: "1rem",
+ lineHeight: 1.5,
+ },
+ body2: {
+ fontSize: "0.875rem",
+ lineHeight: 1.4,
+ },
+ button: {
+ fontWeight: 600,
+ textTransform: "none",
+ },
+ },
+ // Benutzerdefinierte Breakpoints
+ breakpoints: {
+ values: {
+ xs: 0,
+ sm: 600,
+ md: 960,
+ lg: 1280,
+ xl: 1920,
+ },
+ },
+ // Angepasste Spacing-Funktion
+ spacing: 8, // 8px als Basis-Einheit
});
diff --git a/01-frontend/src/util/ColorUtil.tsx b/01-frontend/src/util/ColorUtil.tsx
index 836c170..f2dc8e2 100644
--- a/01-frontend/src/util/ColorUtil.tsx
+++ b/01-frontend/src/util/ColorUtil.tsx
@@ -1,58 +1,66 @@
-export function mapValueToColor(minVal: number, maxVal: number, actualVal: number): string {
- const clamped = Math.min(Math.max(actualVal, minVal), maxVal);
+export function mapValueToColor(
+ minVal: number,
+ maxVal: number,
+ actualVal: number,
+): string {
+ const clamped = Math.min(Math.max(actualVal, minVal), maxVal);
- // Calculate interpolation ratio (0-1)
- const ratio = maxVal !== minVal
- ? (clamped - minVal) / (maxVal - minVal)
- : 0;
- return hsvDegToHex(120 * ratio);//120° is green, 0° is red
+ // Calculate interpolation ratio (0-1)
+ const ratio = maxVal !== minVal ? (clamped - minVal) / (maxVal - minVal) : 0;
+ return hsvDegToHex(120 * ratio); //120° is green, 0° is red
}
export function getColorFromPercent(percent: string): string {
- let perc = Number(percent) / 100
- if (perc > 1) {
- perc = 2.5;
- }
- return hsvDegToHex(120 * perc);
+ let perc = Number(percent) / 100;
+ if (perc > 1) {
+ perc = 2.5;
+ }
+ return hsvDegToHex(120 * perc);
}
export function hsvDegToHex(h: number): string {
- h = Math.max(0, Math.min(360, h));
+ h = Math.max(0, Math.min(360, h));
- const c = 1;
- const x = (1 - Math.abs(((h / 60) % 2) - 1));
+ const c = 1;
+ const x = 1 - Math.abs(((h / 60) % 2) - 1);
- let r, g, b;
- if (h < 60) {
- r = c;
- g = x;
- b = 0;
- } else if (h < 120) {
- r = x;
- g = c;
- b = 0;
- } else if (h < 180) {
- r = 0;
- g = c;
- b = x;
- } else if (h < 240) {
- r = 0;
- g = x;
- b = c;
- } else if (h < 300) {
- r = x;
- g = 0;
- b = c;
- } else {
- r = c;
- g = 0;
- b = x;
- }
+ let r, g, b;
+ if (h < 60) {
+ r = c;
+ g = x;
+ b = 0;
+ } else if (h < 120) {
+ r = x;
+ g = c;
+ b = 0;
+ } else if (h < 180) {
+ r = 0;
+ g = c;
+ b = x;
+ } else if (h < 240) {
+ r = 0;
+ g = x;
+ b = c;
+ } else if (h < 300) {
+ r = x;
+ g = 0;
+ b = c;
+ } else {
+ r = c;
+ g = 0;
+ b = x;
+ }
- // scale to 0-255
- const rHex = Math.round(r * 255).toString(16).padStart(2, '0');
- const gHex = Math.round(g * 255).toString(16).padStart(2, '0');
- const bHex = Math.round(b * 255).toString(16).padStart(2, '0');
+ // scale to 0-255
+ const rHex = Math.round(r * 255)
+ .toString(16)
+ .padStart(2, "0");
+ const gHex = Math.round(g * 255)
+ .toString(16)
+ .padStart(2, "0");
+ const bHex = Math.round(b * 255)
+ .toString(16)
+ .padStart(2, "0");
- return `#${rHex}${gHex}${bHex}`;
+ return `#${rHex}${gHex}${bHex}`;
}