From a38612c200fbd41ec3da07dbe836a8b3f08ac5a7 Mon Sep 17 00:00:00 2001 From: Tim <47184194+imgde@users.noreply.github.com> Date: Sun, 15 Jun 2025 17:34:29 +0200 Subject: [PATCH] Revert "Darkmode verbessert, Preisfilter geht wieder und Impressum wurde bearbeitet" This reverts commit aab1fa182b0d4ad50ebafcc21cbc7868f2716a66. --- 01-frontend/package-lock.json | 33 ++- 01-frontend/package.json | 2 +- .../src/helper/adminpanel/StatisticsInfo.tsx | 102 +------- .../src/helper/homepage/FilterItem.css | 51 ---- .../src/helper/homepage/FilterItem.tsx | 41 ++- .../src/helper/homepage/PriceSlider.css | 32 --- .../src/helper/homepage/PriceSlider.tsx | 52 ++-- 01-frontend/src/index.css | 59 ++--- 01-frontend/src/pages/Contact.css | 58 ----- 01-frontend/src/pages/Contact.tsx | 116 +++------ 01-frontend/src/pages/Home.tsx | 242 +++++++++--------- 01-frontend/src/theme/ThemeContext.tsx | 17 +- 12 files changed, 270 insertions(+), 535 deletions(-) delete mode 100644 01-frontend/src/helper/homepage/FilterItem.css delete mode 100644 01-frontend/src/helper/homepage/PriceSlider.css delete mode 100644 01-frontend/src/pages/Contact.css diff --git a/01-frontend/package-lock.json b/01-frontend/package-lock.json index 66e64d6..1b740ee 100644 --- a/01-frontend/package-lock.json +++ b/01-frontend/package-lock.json @@ -15,7 +15,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", - "@mui/x-charts": "^8.5.2", + "@mui/x-charts": "^8.5.1", "@mui/x-data-grid": "^8.5.2", "@tanstack/react-query": "^5.79.2", "chart.js": "^4.4.9", @@ -1491,15 +1491,15 @@ } }, "node_modules/@mui/x-charts": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-8.5.2.tgz", - "integrity": "sha512-JLPTtd9m8CWMoIxwHFM9QpPDpfdsetfkCErJUvsyQnj/rC8sBMmQqk0c1olusA+OqTyVT3gGmiqXXFar/0cvkw==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-8.5.1.tgz", + "integrity": "sha512-6g0Gdyf2x/2UFZUWdifg7l8L1xl+YB8mz3NlsgK/Oa4Mf9EqPJUvXnzodxyNRT2UkX0l40p6yuOX7o+Mql20/w==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.27.6", + "@babel/runtime": "^7.27.4", "@mui/utils": "^7.1.1", - "@mui/x-charts-vendor": "8.5.2", - "@mui/x-internals": "8.5.2", + "@mui/x-charts-vendor": "8.5.1", + "@mui/x-internals": "8.5.1", "bezier-easing": "^2.1.0", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -1527,12 +1527,12 @@ } }, "node_modules/@mui/x-charts-vendor": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-8.5.2.tgz", - "integrity": "sha512-93KFrEpo3Xhr0g2TQsbtPVqGAsbkKBN5J57ykrCM5GxFmq3kDGFU4k9+FpKiaIYYL8ijzgHGNh+jNVbP0pq3rQ==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-8.5.1.tgz", + "integrity": "sha512-da6QET4FBSzBYjhaaEIA+nrprc2revJMuwXPtDE14KAjEpIluchxsKTqn2XBg0j1NWm40FZX+fLh8m3w2crGIQ==", "license": "MIT AND ISC", "dependencies": { - "@babel/runtime": "^7.27.6", + "@babel/runtime": "^7.27.4", "@types/d3-color": "^3.1.3", "@types/d3-delaunay": "^6.0.4", "@types/d3-interpolate": "^3.0.4", @@ -1611,14 +1611,13 @@ } }, "node_modules/@mui/x-internals": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.2.tgz", - "integrity": "sha512-5YhB2AekK7G8d0YrAjg3WNf0uy3V73JD98WNxJhbIlCraQgl8QOQzr2zNO7MAf/X7mZQtjpjuAsiG3+gI2NVyg==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.5.1.tgz", + "integrity": "sha512-7rAWK7SB6FxEIXKgsHsJjIzeeKOLxFJ16gePgZVWlvyew+xDb4P0fgjwW3ThcJjgvkUm0UhGGfLh/JP8l514IA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.27.6", - "@mui/utils": "^7.1.1", - "reselect": "^5.1.1" + "@babel/runtime": "^7.27.4", + "@mui/utils": "^7.1.1" }, "engines": { "node": ">=14.0.0" diff --git a/01-frontend/package.json b/01-frontend/package.json index ce64a4f..15bb1d7 100644 --- a/01-frontend/package.json +++ b/01-frontend/package.json @@ -19,7 +19,7 @@ "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", - "@mui/x-charts": "^8.5.2", + "@mui/x-charts": "^8.5.1", "@mui/x-data-grid": "^8.5.2", "@tanstack/react-query": "^5.79.2", "chart.js": "^4.4.9", diff --git a/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx b/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx index c3ca48d..4ad3510 100644 --- a/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx +++ b/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx @@ -1,20 +1,18 @@ import { Box, Typography, useTheme } from "@mui/material"; +import { BarChart } from '@mui/x-charts/BarChart'; +import { PieChart } from '@mui/x-charts/PieChart'; import { - Chart as ChartJS, - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend, ArcElement, + BarElement, + CategoryScale, + Chart as ChartJS, + Legend, + LinearScale, LineElement, PointElement, + Title, + Tooltip, } from "chart.js"; -import { Bar, Pie, Line } from "react-chartjs-2"; -import { BarChart } from '@mui/x-charts/BarChart'; -import { RadarChart } from '@mui/x-charts/RadarChart'; -import { PieChart } from '@mui/x-charts/PieChart'; import { useTranslation } from "react-i18next"; // Chart.js registrieren @@ -34,88 +32,6 @@ export default function StatisticsInfo() { const theme = useTheme(); const {t} = useTranslation(); - const weeklySalesData = { - labels: ["Week 1", "Week 2", "Week 3", "Week 4"], - datasets: [ - { - label: "Weekly Sales (€)", - data: [1200, 2100, 800, 1600], - backgroundColor: theme.palette.mode === "dark" - ? "rgba(0, 230, 255, 0.5)" - : "rgba(75, 192, 192, 0.5)", - borderColor: theme.palette.mode === "dark" - ? "rgba(0, 230, 255, 1)" - : "rgba(75, 192, 192, 1)", - borderWidth: 1, - }, - ], - }; - - const itemSalesData = { - labels: ["Tomatensamen", "Blumenerde", "Gießkanne", "Pflanzendünger"], - datasets: [ - { - label: "Item Sales", - data: [400, 300, 200, 100], - backgroundColor: [ - theme.palette.warning.main, - theme.palette.info.main, - theme.palette.success.main, - theme.palette.secondary.main, - ], - hoverBackgroundColor: [ - theme.palette.warning.light, - theme.palette.info.light, - theme.palette.success.light, - theme.palette.secondary.light, - ], - }, - ], - }; - - const userSalesData = { - labels: ["John Doe", "Jane Smith", "Alice Johnson", "Bob Brown"], - datasets: [ - { - label: "Sales by User", - data: [5, 8, 3, 6], - fill: false, - borderColor: theme.palette.primary.main, - backgroundColor: theme.palette.primary.light, - tension: 0.1, - }, - ], - }; - - const baseOptions = { - responsive: true, - plugins: { - legend: { - position: "top" as const, - labels: { - color: theme.palette.text.primary, - }, - }, - title: { - display: false, - }, - }, - scales: { - x: { - ticks: { color: theme.palette.text.primary }, - grid: { - color: theme.palette.divider, - }, - }, - y: { - ticks: { color: theme.palette.text.primary }, - grid: { - color: theme.palette.divider, - }, - }, - }, - }; - return ( diff --git a/01-frontend/src/helper/homepage/FilterItem.css b/01-frontend/src/helper/homepage/FilterItem.css deleted file mode 100644 index d5c90af..0000000 --- a/01-frontend/src/helper/homepage/FilterItem.css +++ /dev/null @@ -1,51 +0,0 @@ -/* FilterItem.css */ - -/* Container rund um jedes Filter-Widget */ -.filter-item { - margin-bottom: 1.5rem; -} - -/* Überschrift (FormLabel) */ -.filter-item__label { - font-weight: bold; - margin-bottom: 0.5rem; - color: var(--text-color); -} - -/* Das Material-UI FormControl-Element */ -.filter-item__group { - display: flex; - flex-direction: column; -} - -/* Jeder Radio-Button mit Label */ -.filter-item__option { - color: var(--text-color); -} - -/* Dark-Mode Anpassungen */ -body.dark .filter-item__label, -body.dark .filter-item__option { - color: #ffffff; - - /* FilterItem.css (am Ende) */ - - /* Sterne-Icons (gefüllt) */ - .filter-item__option .MuiRating-root .MuiRating-iconFilled { - color: #FFC107; /* Gold-Ton für alle Modi */ - } - - /* Optional: leere Sterne etwas abgedunkelt darstellen */ - .filter-item__option .MuiRating-root .MuiRating-iconEmpty { - color: rgba(255, 255, 255, 0.3); - } - - /* Dark-Mode Überschreibungen */ - body.dark .filter-item__option .MuiRating-root .MuiRating-iconFilled { - color: #FFC107; /* bleibt Gold */ - } - body.dark .filter-item__option .MuiRating-root .MuiRating-iconEmpty { - color: rgba(255, 255, 255, 0.3); - } - -} diff --git a/01-frontend/src/helper/homepage/FilterItem.tsx b/01-frontend/src/helper/homepage/FilterItem.tsx index f838581..234cb70 100644 --- a/01-frontend/src/helper/homepage/FilterItem.tsx +++ b/01-frontend/src/helper/homepage/FilterItem.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { FormControl, FormControlLabel, @@ -6,8 +5,9 @@ import { Radio, RadioGroup, Rating, + useTheme, } from "@mui/material"; -import "./FilterItem.css"; +import React from "react"; type FilterItemOption = { value: string; @@ -22,26 +22,37 @@ type FilterItemProps = { }; export default function FilterItem({ - filterName, - filterItems, - value, - onChange, - }: FilterItemProps) { - // Default-Wert, falls noch nichts ausgewählt + filterName, + filterItems, + value, + onChange, +}: FilterItemProps) { + const theme = useTheme(); + if (!value && filterItems.length > 0) { value = filterItems[0].value; } - const handleChange = (e: React.ChangeEvent) => { - onChange?.(e.target.value); + const handleChange = (event: React.ChangeEvent) => { + if (onChange) { + onChange(event.target.value); + } }; return ( -
- +
+ {filterName} - + + {filterItems.map((item, idx) => ( ))} diff --git a/01-frontend/src/helper/homepage/PriceSlider.css b/01-frontend/src/helper/homepage/PriceSlider.css deleted file mode 100644 index ddebe06..0000000 --- a/01-frontend/src/helper/homepage/PriceSlider.css +++ /dev/null @@ -1,32 +0,0 @@ -/* PriceSlider.css */ - -.price-slider-container { - margin-bottom: 2rem; -} - -.price-slider-title { - font-weight: bold; - margin-bottom: 8px; - color: var(--text-color); -} - -.price-slider-wrapper { - padding: 0 8px; -} - -.price-slider-value { - margin-top: 8px; - font-size: 0.9rem; - text-align: center; - color: var(--text-color); -} - -/* 🌙 Dark Mode Unterstützung */ -body.dark .price-slider-title, -body.dark .price-slider-value { - color: #ffffff; -} - -body.dark .MuiSlider-root { - color: #0fd13f; -} diff --git a/01-frontend/src/helper/homepage/PriceSlider.tsx b/01-frontend/src/helper/homepage/PriceSlider.tsx index 48947f6..780dd40 100644 --- a/01-frontend/src/helper/homepage/PriceSlider.tsx +++ b/01-frontend/src/helper/homepage/PriceSlider.tsx @@ -1,62 +1,37 @@ import { Slider, Typography, Box, useTheme } from "@mui/material"; -import { useState, useEffect } from "react"; +import { useState, useEffect, SyntheticEvent } from "react"; import { useTranslation } from "react-i18next"; type PriceSliderProps = { - /** in Cent übergeben */ min?: number; - /** in Cent übergeben */ max?: number; - /** in Euro zurückbekommen */ onChange?: (range: [number, number]) => void; }; -export default function PriceSlider({ - min = 0, - max = 10000, - onChange, - }: PriceSliderProps) { +export default function PriceSlider({ min = 0, max = 10000, onChange }: PriceSliderProps) { const { t } = useTranslation(); const theme = useTheme(); - // Slider-Werte in Euro - const [value, setValue] = useState<[number, number]>([ - min / 100, - max / 100, - ]); + const [value, setValue] = useState<[number, number]>([min, max]); - // Wenn sich min/max ändern, neu setzen useEffect(() => { - const euroMin = min / 100; - const euroMax = max / 100; - setValue([euroMin, euroMax]); - onChange?.([euroMin, euroMax]); + setValue([min, max]); + onChange?.([min, max]); }, [min, max]); - const handleChange = ( - _: Event | React.SyntheticEvent, - newValue: number | number[] - ) => { + const handleChange = (_: Event, newValue: number | number[]) => { if (Array.isArray(newValue)) { setValue([newValue[0], newValue[1]]); } }; - const handleCommitted = ( - _: Event | React.SyntheticEvent, - newValue: number | number[] - ) => { + const handleCommitted = (_: Event | SyntheticEvent, newValue: number | number[]) => { if (Array.isArray(newValue)) { - // Direkt in Euro zurückgeben onChange?.([newValue[0], newValue[1]]); } }; - const formatValueToEuro = (v: number) => - new Intl.NumberFormat("de-DE", { - style: "currency", - currency: "EUR", - }).format(v); + const formatValueToEuro = (val: number) => `${(val/100).toFixed(2)} €`; return ( @@ -70,6 +45,7 @@ export default function PriceSlider({ > {t("price")} + + diff --git a/01-frontend/src/index.css b/01-frontend/src/index.css index ad5c67a..08a3ac9 100644 --- a/01-frontend/src/index.css +++ b/01-frontend/src/index.css @@ -1,39 +1,18 @@ -/* index.css (oder App.css), ganz oben importieren */ - :root { - /* Light-Mode Defaults */ - --background-color: #fafafa; - --text-color: #000000; - --divider-color: rgba(0, 0, 0, 0.6); - --primary-main: #0fd13f; - - /* generelle System-Fonts & -Resets */ 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; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -body.dark { - /* Dark-Mode Overrides */ - --background-color: #121212; - --text-color: #ffffff; - --divider-color: rgba(255, 255, 255, 0.7); - --primary-main: #0fd13f; -} - -/* Basis-Styling, kann bleiben */ -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; - background-color: var(--background-color); - color: var(--text-color); -} - -/* Link-Styles */ a { font-weight: 500; color: #646cff; @@ -43,7 +22,19 @@ a:hover { color: #535bf2; } -/* Button-Styles */ +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + button { border-radius: 8px; border: 1px solid transparent; @@ -63,8 +54,14 @@ button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } -/* Optional: systemweite Light-Mode-Ergänzungen */ @media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } button { background-color: #f9f9f9; } diff --git a/01-frontend/src/pages/Contact.css b/01-frontend/src/pages/Contact.css deleted file mode 100644 index 4b300fb..0000000 --- a/01-frontend/src/pages/Contact.css +++ /dev/null @@ -1,58 +0,0 @@ -/* Impressum.css */ - -.impressum-container { - background-color: var(--background-color); - color: var(--text-color); - min-height: 100vh; - padding: 2rem; /* entspricht etwa theme.spacing(3) */ -} - -.impressum-title { - font-size: 2rem; /* entspricht variant h3 */ - margin-bottom: 1rem; -} - -.impressum-subtitle { - font-size: 1.25rem; /* entspricht variant h4 */ - margin-top: 2rem; - margin-bottom: 1rem; -} - -.impressum-heading { - font-size: 1.125rem; /* entspricht variant h5 */ - margin-top: 1.5rem; - margin-bottom: 0.5rem; - font-weight: bold; -} - -.impressum-paragraph { - margin-bottom: 1rem; - line-height: 1.6; -} - -.impressum-divider { - margin: 2rem 0; - background-color: var(--divider-color); - height: 1px; - border: none; -} - -.impressum-list { - margin-left: 1.5rem; - margin-bottom: 1rem; -} - -.impressum-list li { - margin-bottom: 0.5rem; -} - -.impressum-link { - color: var(--primary-main); - text-decoration: none; -} - -.impressum-caption { - display: block; - margin-top: 2rem; - font-size: 0.75rem; /* entspricht variant caption */ -} diff --git a/01-frontend/src/pages/Contact.tsx b/01-frontend/src/pages/Contact.tsx index a2ec664..d6df019 100644 --- a/01-frontend/src/pages/Contact.tsx +++ b/01-frontend/src/pages/Contact.tsx @@ -1,21 +1,14 @@ import { Box, Divider, Typography } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import "./Contact.css"; +import "./pages.css"; export default function Impressum() { - const theme = useTheme(); - - // Body-Klasse für Darkmode setzen - // (Alternativ übernimmt das dein ThemeProvider global per document.body.classList) - document.body.classList.toggle("dark", theme.palette.mode === "dark"); - return ( - + Impressum - + Hochschule für Technik und Wirtschaft
des Saarlandes
Goebenstraße 40
@@ -27,97 +20,64 @@ export default function Impressum() { Ministerium der Finanzen und für Wissenschaft des Saarlandes
- + - + Datenschutzerklärung - - Personenbezogene Daten („Daten“) werden von uns nur verarbeitet, wenn es notwendig ist, - um einen funktionierenden und benutzerfreundlichen Internetauftritt bereitzustellen. + + Personenbezogene Daten (nachfolgend zumeist nur „Daten“ genannt) ... - - Gliederung: + + 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
- - I. Verantwortlicher Anbieter - - - Hochschule für Technik und Wirtschaft
- des Saarlandes
- Goebenstraße 40
- 66117 Saarbrücken
- Telefon: (0681) 58 67 - 0
- E-Mail: info@htwsaar.de + + I. Informationen über uns als Verantwortliche - + + Verantwortlicher Anbieter dieses Internetauftritts ... + + + II. Rechte der Nutzer und Betroffenen -
    -
  • Auskunft über verarbeitete Daten (Art. 15 DSGVO)
  • -
  • Berichtigung unrichtiger Daten (Art. 16 DSGVO)
  • -
  • Löschung oder Einschränkung (Art. 17, 18 DSGVO)
  • -
  • Datenübertragbarkeit (Art. 20 DSGVO)
  • -
  • Widerspruch gegen Verarbeitung (Art. 21 DSGVO)
  • -
  • Beschwerde bei der Aufsichtsbehörde (Art. 77 DSGVO)
  • + + + 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)
    - + III. Informationen zur Datenverarbeitung - - Daten werden gelöscht oder gesperrt, sobald der Zweck entfällt oder keine gesetzliche - Aufbewahrungspflicht mehr besteht. + + + Ihre bei Nutzung unseres Internetauftritts verarbeiteten Daten ... - - Cookies - - - Unsere Website verwendet Cookies, um Funktionen wie Sprache, Warenkorb oder Benutzerkomfort zu ermöglichen. - + {/* Du kannst einfach alle weiteren Absätze so fortsetzen – copy & paste, + jeweils in: */} - - Serverdaten - - - Ihr Browser übermittelt technische Daten (IP, Browsertyp etc.) an unseren Server, um die Website auszuliefern. - - - - Cloudflare - - - Unser CDN-Anbieter Cloudflare verarbeitet Zugriffsdaten zur Sicherheit und Optimierung. Mehr unter:  - - cloudflare.com/privacypolicy - - - - - Quelle:  - - Datenschutz Generator der Kanzlei Weiß & Partner - + + Mehr Infos unter: CloudFlare Datenschutzerklärung ); diff --git a/01-frontend/src/pages/Home.tsx b/01-frontend/src/pages/Home.tsx index ea2e593..10b02a8 100644 --- a/01-frontend/src/pages/Home.tsx +++ b/01-frontend/src/pages/Home.tsx @@ -8,116 +8,138 @@ import FilterItem from "../helper/homepage/FilterItem"; import ItemCard from "../helper/homepage/ItemCard"; import PriceSlider from "../helper/homepage/PriceSlider"; import { fetchItemList } from '../helper/query/Queries'; -import "./pages.css"; +import "./pages.css"; // Import der CSS-Datei export default function Home() { + const { t } = useTranslation(); const navigate = useNavigate(); const location = useLocation(); const theme = useTheme(); - - // URL‐Such‐Query const [searchQuery, setSearchQuery] = useState(null); - // Kategorie & Rating - const [selectedCategory, setSelectedCategory] = useState(null); - const [selectedRating, setSelectedRating] = useState(null); - - // Items laden - const { data = [] } = useQuery({ - queryKey: ['fetchItemList'], - queryFn: fetchItemList, - retry: 3, - retryDelay: 1000, - }); - const items: Item[] = useMemo(() => data, [data]); - - // Discount‐Preis in Cent berechnen - const pricesInCent = items.map(i => - Math.round(i.price100 * (1 - i.discount100 / 100)) - ); - const minCent = pricesInCent.length ? Math.min(...pricesInCent) : 0; - const maxCent = pricesInCent.length ? Math.max(...pricesInCent) : 0; - - // **Euro‐Bereich** für den Slider - const [priceRangeEuro, setPriceRangeEuro] = useState<[number, number]>([ - minCent / 100, - maxCent / 100, - ]); - - // Kategorie aus URL - useEffect(() => { - const p = new URLSearchParams(location.search).get("category"); - setSelectedCategory( - p && pagesCategoryOptions.some(f => f.value === p) ? p : null - ); - }, [location.search]); - - // Such‐Query aus URL - useEffect(() => { - setSearchQuery(new URLSearchParams(location.search).get("search")); - }, [location.search]); - - // Filter‐Funktionen - const filteredItems = useMemo(() => { - return items - // 1) nach Preis: konvertiere Cent→Euro - .filter(i => { - const euro = (i.price100 * (1 - i.discount100 / 100)) / 100; - return euro >= priceRangeEuro[0] && euro <= priceRangeEuro[1]; - }) - // 2) Kategorie - .filter(i => - !selectedCategory - ? true - : i.category.toLowerCase() === selectedCategory.toLowerCase() - ) - // 3) Rating (z.B. selectedRating=“4” → mind. 4 Sterne) - .filter(i => - !selectedRating - ? true - : Math.round(i.rating) >= Number(selectedRating) - ) - // 4) Suche - .filter(i => - !searchQuery - ? true - : i.name.toLowerCase().includes(searchQuery.toLowerCase()) - ); - }, [items, priceRangeEuro, selectedCategory, selectedRating, searchQuery]); - - // Scroll‐Reset, wenn Items schrumpfen - const containerRef = useRef(null); - const prevLen = useRef(items.length); - useEffect(() => { - if (items.length < prevLen.current) { - containerRef.current?.scrollTo(0, 0); - } - prevLen.current = items.length; - }, [items]); - - // Handler - const handleCategoryChange = (cat: string) => { - setSelectedCategory(cat || null); - navigate(cat ? `/?category=${encodeURIComponent(cat)}` : "/"); - }; - const handleRatingChange = (rt: string) => { - setSelectedRating(rt || null); - }; - - // Definiere hier deine Kategoriendaten nochmal kurz: - const pagesCategoryOptions = useMemo(() => [ + 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: "Other", label: t("other") } ], [t]); - const ratingFilter = useMemo(() => [ - { value: "", label: t("allRatings") }, - ...[5,4,3,2,1].map(v => ({ value: v.toString(), label: v.toString() })) - ], [t]); + 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 { data = [] } = useQuery({ + queryKey: ['fetchItemList'], + queryFn: fetchItemList, + retry: 3, // Versucht es 3-mal erneut + retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms) + }); + + const items:Item[] = 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, + ]); + + // 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 = 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 = Math.trunc(item.rating); + return rating === (Number(selectedRating) * 2) -1 || 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]); + + + + // Items, die aktuell angezeigt werden + const visibleItems: Item[] = filteredItems; + + // Container Ref + const containerRef = useRef(null); + + const prevItemsLength = useRef(items.length); + + useEffect(() => { + if (items.length >= prevItemsLength.current) { + prevItemsLength.current = items.length; + return; + } + + setTimeout(() => { + containerRef.current?.scrollTo(0, 0); + }, 50); + + 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); + }} />
    - -
    +
    - {filteredItems.length === 0 ? ( - - {t("noItemsFound")} - + {visibleItems.length === 0 ? ( + {t('noItemsFound')} ) : ( - filteredItems.map(item => ( + visibleItems.map((item) => ( )) )} diff --git a/01-frontend/src/theme/ThemeContext.tsx b/01-frontend/src/theme/ThemeContext.tsx index 4ddac01..e999896 100644 --- a/01-frontend/src/theme/ThemeContext.tsx +++ b/01-frontend/src/theme/ThemeContext.tsx @@ -23,14 +23,14 @@ interface CustomThemeProviderProps { } export const CustomThemeProvider: React.FC = ({ children }) => { - // ✅ SSR-sichere System-Präferenz-Erkennung + // SSR-sichere System-Präferenz-Erkennung const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)', { noSsr: true }); - // ✅ SSR-sichere Initialisierung + // SSR-sichere Initialisierung const [mode, setMode] = useState('light'); const [mounted, setMounted] = useState(false); - // ✅ Nach dem ersten Render ausführen (SSR-sicher) + // Nach dem ersten Render ausführen (SSR-sicher) useEffect(() => { setMounted(true); @@ -51,7 +51,7 @@ export const CustomThemeProvider: React.FC = ({ childr } }, [mode, mounted]); - // ✅ Browser-only DOM-Manipulation + // Browser-only DOM-Manipulation useEffect(() => { if (mounted && typeof window !== 'undefined') { const backgroundColor = mode === 'dark' ? '#121212' : '#fafafa'; @@ -61,7 +61,6 @@ export const CustomThemeProvider: React.FC = ({ childr document.documentElement.style.setProperty('--background-color', backgroundColor); document.documentElement.style.backgroundColor = backgroundColor; document.body.style.backgroundColor = backgroundColor; - document.body.classList.toggle('dark', mode === 'dark'); const root = document.getElementById('root'); if (root) { @@ -89,7 +88,7 @@ export const CustomThemeProvider: React.FC = ({ childr palette: { mode, primary: { - main: '#0fd13f', // Ihre grüne NavBar + main: '#0fd13f', // Grüne NavBar light: mode === 'dark' ? '#4caf50' : '#42a5f5', dark: mode === 'dark' ? '#388e3c' : '#1565c0', contrastText: '#fff', @@ -127,7 +126,7 @@ export const CustomThemeProvider: React.FC = ({ childr MuiAppBar: { styleOverrides: { colorPrimary: { - backgroundColor: '#0fd13f', + backgroundColor: mode === 'dark' ? '#388e3c' : '#0fd13f', color: '#ffffff', }, }, @@ -137,7 +136,7 @@ export const CustomThemeProvider: React.FC = ({ childr [mode] ); - // ✅ Aggressive GlobalStyles mit CSS-Variablen + // Aggressive GlobalStyles mit CSS-Variablen const globalStyles = mounted ? ( = ({ childr /> ) : null; - // ✅ SSR-Fallback während mounted = false + // SSR-Fallback während mounted = false if (!mounted) { return (