diff --git a/01-frontend/package-lock.json b/01-frontend/package-lock.json index 9724050..d74c56d 100644 --- a/01-frontend/package-lock.json +++ b/01-frontend/package-lock.json @@ -8,16 +8,21 @@ "name": "01-frontend", "version": "0.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", "@tanstack/react-query": "^5.79.2", + "chart.js": "^4.4.9", "i18next": "^25.2.0", "i18next-browser-languagedetector": "^8.1.0", "i18next-http-backend": "^3.0.2", "mui": "^0.0.1", "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", "react-i18next": "^15.5.1", "react-router-dom": "^7.5.3" @@ -338,6 +343,73 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -1177,6 +1249,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@mui/core-downloads-tracker": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz", @@ -2347,6 +2425,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chart.js": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3621,6 +3711,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "license": "MIT", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-dom": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", @@ -4018,6 +4118,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/01-frontend/package.json b/01-frontend/package.json index 197ac46..d2db740 100644 --- a/01-frontend/package.json +++ b/01-frontend/package.json @@ -12,16 +12,21 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@mui/icons-material": "^7.0.2", "@mui/material": "^7.0.2", "@tanstack/react-query": "^5.79.2", + "chart.js": "^4.4.9", "i18next": "^25.2.0", "i18next-browser-languagedetector": "^8.1.0", "i18next-http-backend": "^3.0.2", "mui": "^0.0.1", "react": "^19.0.0", + "react-chartjs-2": "^5.3.0", "react-dom": "^19.1.0", "react-i18next": "^15.5.1", "react-router-dom": "^7.5.3" diff --git a/01-frontend/public/locales/de/translation.json b/01-frontend/public/locales/de/translation.json index a17839f..94fc6cb 100644 --- a/01-frontend/public/locales/de/translation.json +++ b/01-frontend/public/locales/de/translation.json @@ -84,5 +84,7 @@ "wrongTurn": "Ein falscher Weg wurde eingeschlagen. Hier geht es zurück auf den richtigen Pfad.", "yourOrderNumber": "Die Bestellnummer lautet", "articleNumber": "Artikelnummer", - "noRatingsYet": "Noch keine Bewertungen vorhanden." + "noRatingsYet": "Noch keine Bewertungen vorhanden.", + "accounts": "Konten", + "statistics": "Statistiken" } diff --git a/01-frontend/public/locales/en/translation.json b/01-frontend/public/locales/en/translation.json index bf76cd0..037b067 100644 --- a/01-frontend/public/locales/en/translation.json +++ b/01-frontend/public/locales/en/translation.json @@ -84,5 +84,7 @@ "wrongTurn": "It seems you may have taken a wrong turn. Let's get you back on track.", "yourOrderNumber": "Your order number is", "articleNumber": "Article number", - "noRatingsYet": "No ratings yet" + "noRatingsYet": "No ratings yet", + "accounts": "Accounts", + "statistics": "Statistics" } diff --git a/01-frontend/src/App.tsx b/01-frontend/src/App.tsx index 3e13277..83a28ab 100644 --- a/01-frontend/src/App.tsx +++ b/01-frontend/src/App.tsx @@ -5,7 +5,6 @@ import './App.css'; import { BasketProvider } from './helper/BasketProvider'; import NavBar from './helper/navbar/NavBar'; import Account from './pages/Account'; -import Category from './pages/Category'; import Contact from './pages/Contact'; import Home from './pages/Home'; import NoPage from './pages/NoPage'; @@ -13,6 +12,8 @@ import Orders from './pages/Orders'; import Payment from './pages/Payment'; import Product from './pages/Product'; import { CustomThemeProvider } from './theme/ThemeContext'; +import FSModel from './pages/FSModel'; +import AdminPanel from './pages/AdminPanel'; export default function App() { @@ -31,10 +32,11 @@ export default function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> + } /> diff --git a/01-frontend/src/components/Account.tsx b/01-frontend/src/components/Account.tsx new file mode 100644 index 0000000..add147d --- /dev/null +++ b/01-frontend/src/components/Account.tsx @@ -0,0 +1,8 @@ +type AccountType = { + id: string; + name: string; + email: string; + status: "active" | "inactive"; +}; + +export default AccountType; \ No newline at end of file diff --git a/01-frontend/src/components/Order.tsx b/01-frontend/src/components/Order.tsx new file mode 100644 index 0000000..42c388e --- /dev/null +++ b/01-frontend/src/components/Order.tsx @@ -0,0 +1,10 @@ +type OrderType = { + id: string; + date: string; + status: "active" | "inactive" | "running" | "cancelled"; + items: { name: string; quantity: number; price: number }[]; + total: number; + address: string; +}; + +export default OrderType; diff --git a/01-frontend/src/helper/NavBar.css b/01-frontend/src/helper/NavBar.css new file mode 100644 index 0000000..dc16864 --- /dev/null +++ b/01-frontend/src/helper/NavBar.css @@ -0,0 +1,70 @@ +/* Navbar styles */ +.navbar { + background-color: #1976d2; /* Material-UI Primary Color */ + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + position: absolute; + top: 0; + height: 3rem; +} + +/* Logo styles */ +.navbar-logo { + font-family: 'Roboto', sans-serif; + font-weight: bold; + color: white; + text-decoration: none; + margin-right: auto; +} + +/* Menu styles */ +.navbar-menu { + display: flex; + align-items: center; + margin-left: auto; +} + +/* Search styles */ +.search { + position: relative; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0.15); +} +.search:hover { + background-color: rgba(255, 255, 255, 0.25); +} +.search-icon-wrapper { + padding: 8px; + height: 100%; + position: absolute; + pointer-events: none; + display: flex; + align-items: center; + justify-content: center; +} +.search-input { + color: inherit; + width: 100%; + padding: 8px 8px 8px 40px; + font-size: 1rem; +} + +/* User avatar styles */ +.navbar-user { + margin-left: 16px; +} + +/* Typography styles */ +.navbar-typography { + font-family: 'monospace'; + font-weight: 700; + letter-spacing: .3rem; + color: inherit; + text-decoration: none; +} + +/* Button styles */ +.navbar-button { + margin: 2; + color: white; + display: block; +} \ No newline at end of file diff --git a/01-frontend/src/helper/NavBar.tsx b/01-frontend/src/helper/NavBar.tsx new file mode 100644 index 0000000..27838da --- /dev/null +++ b/01-frontend/src/helper/NavBar.tsx @@ -0,0 +1,216 @@ +import * as React from 'react'; +import AppBar from '@mui/material/AppBar'; +import Box from '@mui/material/Box'; +import Toolbar from '@mui/material/Toolbar'; +import IconButton from '@mui/material/IconButton'; +import Typography from '@mui/material/Typography'; +import Menu from '@mui/material/Menu'; +import MenuIcon from '@mui/icons-material/Menu'; +import Container from '@mui/material/Container'; +import Avatar from '@mui/material/Avatar'; +import Button from '@mui/material/Button'; +import Tooltip from '@mui/material/Tooltip'; +import MenuItem from '@mui/material/MenuItem'; +import AdbIcon from '@mui/icons-material/Adb'; +import { alpha, InputBase, styled } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import { useNavigate } from 'react-router-dom'; + +const pages = ['Categories', 'Checkout', 'Contact']; +const settings = ['Account', 'Orders', 'Logout']; + +const Search = styled('div')(({ theme }) => ({ + position: 'relative', + borderRadius: theme.shape.borderRadius, + backgroundColor: alpha(theme.palette.common.white, 0.15), + '&:hover': { + backgroundColor: alpha(theme.palette.common.white, 0.25), + }, + marginLeft: 0, + width: '100%', + [theme.breakpoints.up('sm')]: { + marginLeft: theme.spacing(1), + width: 'auto', + }, + })); + + const SearchIconWrapper = styled('div')(({ theme }) => ({ + padding: theme.spacing(0, 2), + height: '100%', + position: 'absolute', + pointerEvents: 'none', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + })); + + const StyledInputBase = styled(InputBase)(({ theme }) => ({ + color: 'inherit', + width: '100%', + '& .MuiInputBase-input': { + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)})`, + transition: theme.transitions.create('width'), + [theme.breakpoints.up('sm')]: { + width: '12ch', + '&:focus': { + width: '20ch', + }, + }, + }, + })); + +export default function NavBar() { + const navigate = useNavigate(); + const [anchorElNav, setAnchorElNav] = React.useState(null); + const [anchorElUser, setAnchorElUser] = React.useState(null); + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = (link: string) => { + setAnchorElNav(null); + navigate(`/${link.toLowerCase()}`); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + + return ( + + + + + + Digitaler Produktionsshop + + + + + + + + + + + + + + + {pages.map((page) => ( + handleCloseNavMenu(page)}> + {page} + + ))} + + + + + DPS + + + {pages.map((page) => ( + + ))} + + + + + + + + + {settings.map((setting) => ( + + {setting} + + ))} + + + + + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/AccountsInfo.tsx b/01-frontend/src/helper/adminpanel/AccountsInfo.tsx new file mode 100644 index 0000000..eb8cfdd --- /dev/null +++ b/01-frontend/src/helper/adminpanel/AccountsInfo.tsx @@ -0,0 +1,156 @@ +import DeleteIcon from "@mui/icons-material/Delete"; +import EditIcon from "@mui/icons-material/Edit"; +import { + Box, + Button, + IconButton, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, +} from "@mui/material"; +import { useState } from "react"; +import AccountType from "../../components/Account"; + +const mockAccounts: AccountType[] = [ + { id: "1", name: "John Doe", email: "john.doe@example.com", status: "active" }, + { id: "2", name: "Jane Smith", email: "jane.smith@example.com", status: "inactive" }, + { id: "3", name: "Alice Johnson", email: "alice.johnson@example.com", status: "active" }, +]; + +export default function AccountsInfo() { + const [accounts, setAccounts] = useState(mockAccounts); + const [searchTerm, setSearchTerm] = useState(""); + const [editMode, setEditMode] = useState<{ [key: string]: boolean }>({}); + + const handleSearch = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value.toLowerCase()); + }; + + const handleDelete = (id: string) => { + setAccounts((prevAccounts) => prevAccounts.filter((account) => account.id !== id)); + }; + + const handleToggleStatus = (id: string) => { + setAccounts((prevAccounts) => + prevAccounts.map((account) => + account.id === id + ? { ...account, status: account.status === "active" ? "inactive" : "active" } + : account + ) + ); + }; + + const handleEdit = (id: string, field: keyof AccountType, value: string) => { + setAccounts((prevAccounts) => + prevAccounts.map((account) => + account.id === id ? { ...account, [field]: value } : account + ) + ); + }; + + const toggleEditMode = (id: string) => { + setEditMode((prevEditMode) => ({ + ...prevEditMode, + [id]: !prevEditMode[id], + })); + }; + + const filteredAccounts = accounts.filter( + (account) => + account.name.toLowerCase().includes(searchTerm) || + account.email.toLowerCase().includes(searchTerm) + ); + + return ( + + + Accounts Management + + + + + + + ID + Name + Email + Status + Actions + + + + {filteredAccounts.map((account) => ( + + {account.id} + + {editMode[account.id] ? ( + + handleEdit(account.id, "name", e.target.value) + } + variant="standard" + /> + ) : ( + account.name + )} + + + {editMode[account.id] ? ( + + handleEdit(account.id, "email", e.target.value) + } + variant="standard" + /> + ) : ( + account.email + )} + + {account.status} + + handleDelete(account.id)} + > + + + + toggleEditMode(account.id)} + > + + + + + ))} + +
+
+
+ ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/DroppableContainer.tsx b/01-frontend/src/helper/adminpanel/DroppableContainer.tsx new file mode 100644 index 0000000..015bd58 --- /dev/null +++ b/01-frontend/src/helper/adminpanel/DroppableContainer.tsx @@ -0,0 +1,27 @@ +import { useDroppable } from "@dnd-kit/core"; +import { Box } from "@mui/material"; + +type DroppableContainerProps = { + id: string; + children: React.ReactNode; +}; + +export function DroppableContainer({ id, children }: DroppableContainerProps) { + const { setNodeRef } = useDroppable({ id }); + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/OrdersInfo.tsx b/01-frontend/src/helper/adminpanel/OrdersInfo.tsx new file mode 100644 index 0000000..361c4f4 --- /dev/null +++ b/01-frontend/src/helper/adminpanel/OrdersInfo.tsx @@ -0,0 +1,171 @@ +import { closestCenter, DndContext } from "@dnd-kit/core"; +import { restrictToParentElement } from "@dnd-kit/modifiers"; +import { Box, Button, Dialog, DialogContent, DialogTitle, Typography } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { DroppableContainer } from "./DroppableContainer.tsx"; // Hilfskomponente für Droppable-Bereiche +import SortableItem from "./SortableItem.tsx"; // Hilfskomponente für Sortable Items +import OrderType from "../../components/Order"; + +const mockOrders: OrderType[] = [ + { + id: "1001", + date: "2025-05-20", + status: "active", + items: [ + { name: "Tomatensamen", quantity: 2, price: 3.99 }, + { name: "Blumenerde", quantity: 1, price: 7.49 }, + ], + total: 15.47, + address: "Musterstraße 1, 12345 Musterstadt", + }, + { + id: "1000", + date: "2025-05-10", + status: "inactive", + items: [{ name: "Gießkanne", quantity: 1, price: 12.99 }], + total: 12.99, + address: "Musterstraße 1, 12345 Musterstadt", + }, + { + id: "1002", + date: "2025-05-15", + status: "running", + items: [{ name: "Pflanzendünger", quantity: 1, price: 8.99 }], + total: 8.99, + address: "Musterstraße 1, 12345 Musterstadt", + }, + { + id: "1003", + date: "2025-05-18", + status: "cancelled", + items: [{ name: "Blumentopf", quantity: 2, price: 5.99 }], + total: 11.98, + address: "Musterstraße 1, 12345 Musterstadt", + }, +]; + +const statusOrder = ["active", "running", "inactive", "cancelled"]; + +export default function OrdersInfo() { + const { t } = useTranslation(); + const [orders, setOrders] = useState(mockOrders); + const [selectedOrder, setSelectedOrder] = useState(null); + + const handleDragEnd = (event: any) => { + const { active, over } = event; + + if (!over || active.id === over.id) return; + + const newStatus = over.id; // Zielspalte (status) + setOrders((prevOrders) => + prevOrders.map((order) => + order.id === active.id ? { ...order, status: newStatus } : order + ) + ); + }; + + const handleNextStatus = (order: OrderType) => { + const currentIndex = statusOrder.indexOf(order.status); + if (currentIndex < statusOrder.length - 1) { + setOrders((prevOrders) => + prevOrders.map((o) => + o.id === order.id + ? { ...o, status: statusOrder[currentIndex + 1] as OrderType['status'] } + : o + ) + ); + } + }; + + const renderOrders = (status: string) => { + const filteredOrders = orders.filter((order) => order.status === status); + + return ( + + + {t(status)} + + {filteredOrders.map((order) => ( + setSelectedOrder(order)} + /> + ))} + + ); + }; + + return ( + + + {t("Orders Management")} + + + + {statusOrder.map((status) => ( + + {renderOrders(status)} + + ))} + + + + {/* Dialog für Bestelldetails */} + setSelectedOrder(null)}> + Order Details + + {selectedOrder && ( + + + Order ID: {selectedOrder.id} + + + Date: {selectedOrder.date} + + + Address: {selectedOrder.address} + + + Items: + + {selectedOrder.items.map((item, index) => ( + + {item.quantity}x {item.name} - {item.price.toFixed(2)} € + + ))} + + Total: {selectedOrder.total.toFixed(2)} € + + + + )} + + + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/SortableItem.tsx b/01-frontend/src/helper/adminpanel/SortableItem.tsx new file mode 100644 index 0000000..2f4c1ba --- /dev/null +++ b/01-frontend/src/helper/adminpanel/SortableItem.tsx @@ -0,0 +1,43 @@ +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { Paper, Typography } from "@mui/material"; + +type SortableItemProps = { + id: string; + order: { + id: string; + total: number; + }; + onClick: () => void; +}; + +export default function SortableItem({ id, order, onClick }: SortableItemProps) { + const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + cursor: "grab", + }; + + return ( + + Order ID: {order.id} + Total: {order.total.toFixed(2)} € + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx b/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx new file mode 100644 index 0000000..c1c8d8f --- /dev/null +++ b/01-frontend/src/helper/adminpanel/StatisticsInfo.tsx @@ -0,0 +1,101 @@ +import { Box, Typography } from "@mui/material"; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, + ArcElement, + LineElement, + PointElement, +} from "chart.js"; +import { Bar, Pie, Line } from "react-chartjs-2"; + +// Chart.js registrieren +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Title, + Tooltip, + Legend, + ArcElement, + LineElement, + PointElement +); + +// Fake-Daten +const weeklySalesData = { + labels: ["Week 1", "Week 2", "Week 3", "Week 4"], + datasets: [ + { + label: "Weekly Sales (€)", + data: [1200, 2100, 800, 1600], + backgroundColor: "rgba(75, 192, 192, 0.5)", + borderColor: "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: ["#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0"], + hoverBackgroundColor: ["#FF6384", "#36A2EB", "#FFCE56", "#4BC0C0"], + }, + ], +}; + +const userSalesData = { + labels: ["John Doe", "Jane Smith", "Alice Johnson", "Bob Brown"], + datasets: [ + { + label: "Sales by User", + data: [5, 8, 3, 6], + fill: false, + borderColor: "rgba(153, 102, 255, 1)", + backgroundColor: "rgba(153, 102, 255, 0.5)", + tension: 0.1, + }, + ], +}; + +export default function StatisticsInfo() { + return ( + + + Sales Statistics + + + {/* Gesamtverkaufszahlen pro Woche */} + + + Weekly Sales + + + + + {/* Verkaufsanzahl der einzelnen Items */} + + + Item Sales Distribution + + + + + {/* Verkäufe pro Benutzer */} + + + Sales by User + + + + + ); +} \ No newline at end of file diff --git a/01-frontend/src/helper/homepage/ItemCard.tsx b/01-frontend/src/helper/homepage/ItemCard.tsx index 962f3c0..57f64d7 100644 --- a/01-frontend/src/helper/homepage/ItemCard.tsx +++ b/01-frontend/src/helper/homepage/ItemCard.tsx @@ -1,11 +1,11 @@ import { AddShoppingCart } from "@mui/icons-material"; -import {Box, Card, CardActionArea, CardContent, CardMedia, IconButton, Paper, Rating, Typography } from "@mui/material"; +import { Box, Card, CardActionArea, CardContent, CardMedia, IconButton, Paper, Rating, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import { useTranslation } from 'react-i18next'; import { useNavigate } from "react-router-dom"; import Item from "../../components/Item"; import { useBasket } from "../BasketProvider"; import "../helper.css"; -import { useTranslation } from 'react-i18next'; -import { useEffect, useMemo, useRef, useState } from "react"; export default function ItemCard({ item }: { item: Item }) { diff --git a/01-frontend/src/helper/navbar/NavBar.tsx b/01-frontend/src/helper/navbar/NavBar.tsx index fc6f05c..fa42f96 100644 --- a/01-frontend/src/helper/navbar/NavBar.tsx +++ b/01-frontend/src/helper/navbar/NavBar.tsx @@ -29,7 +29,7 @@ export default function NavBar() { const [itemNames, setItemNames] = React.useState([]); // Für Autocomplete - const pageKeys = ['categories', 'checkout', 'contact']; + const pageKeys = ['fsmodel', 'admin', 'checkout', 'contact']; const settingKeys = ['account', 'orders', 'logout']; const pages = pageKeys.map(key => ({ key, label: t(key) })); diff --git a/01-frontend/src/pages/AdminPanel.tsx b/01-frontend/src/pages/AdminPanel.tsx new file mode 100644 index 0000000..d44a48a --- /dev/null +++ b/01-frontend/src/pages/AdminPanel.tsx @@ -0,0 +1,82 @@ +import { AccountCircle, QueryStats, ReceiptLong } from "@mui/icons-material"; +import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography } from "@mui/material"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import AccountsInfo from "../helper/adminpanel/AccountsInfo"; +import OrdersInfo from "../helper/adminpanel/OrdersInfo"; +import StatisticsInfo from "../helper/adminpanel/StatisticsInfo"; + +export default function AdminPanel() { + const { t } = useTranslation(); + const [infoStatus, setInfoStatus] = useState("statistics"); // Standardseite + + const handleInfoStatus = (path: string) => { + console.log("Button clicked:", path); // Debugging + setInfoStatus(path); + }; + + const renderContent = () => { + console.log("Rendering content for:", infoStatus); // Debugging + switch (infoStatus) { + case "statistics": + return ; + case "orders": + return ; + case "accounts": + return ; + default: + return ; // Fallback, falls kein gültiger Status + } + }; + + return ( +
+ + + Admin Panel + + + Manage your application settings and content here. + + + + {/* Sidebar */} + + + + + {/* Content */} + + {renderContent()} + + +
+ ); +} \ No newline at end of file diff --git a/01-frontend/src/pages/Category.tsx b/01-frontend/src/pages/Category.tsx deleted file mode 100644 index 5e09d04..0000000 --- a/01-frontend/src/pages/Category.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import CategoryIcon from "@mui/icons-material/Category"; -import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing"; -import ScienceIcon from "@mui/icons-material/Science"; -import SpaIcon from "@mui/icons-material/Spa"; -import { Box, Card, CardActionArea, CardContent, Grid, Typography } from "@mui/material"; -import { useNavigate } from "react-router-dom"; -import "./pages.css"; -import {useTranslation} from "react-i18next"; - -export default function Category() { - - const { t } = useTranslation(); - const navigate = useNavigate(); - - const categories = [ - { - name: t('seeds'), - icon: , - filter: "Seeds", - }, - { - name: t('gardenSupplies'), - icon: , - filter: "GardenSupplies" - }, - { - name: t('technicalComponents'), - icon: , - filter: "TechnicalComponents" - }, - { - name: t('other'), - icon: , - filter: "Other" - } - ]; - - const handleCategoryClick = (filter: string) => { - // Navigiere zur Home-Seite und übergebe die gewählte Kategorie als Query-Parameter - navigate(`/?category=${encodeURIComponent(filter)}`); - }; - - return ( - - - {t('categories')} - - - ... - - - {categories.map((cat) => ( - - - handleCategoryClick(cat.filter)}> - - {cat.icon} - - - {cat.name} - - - - - - - ))} - - - ); -} \ No newline at end of file diff --git a/01-frontend/src/pages/FSModel.tsx b/01-frontend/src/pages/FSModel.tsx new file mode 100644 index 0000000..ea0710c --- /dev/null +++ b/01-frontend/src/pages/FSModel.tsx @@ -0,0 +1,24 @@ +import { Box, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import "./pages.css"; + +export default function FSModel() { + + const { t } = useTranslation(); + + return ( + + + {t('categories')} + + + ... + + + ); +} \ No newline at end of file diff --git a/01-frontend/src/pages/Orders.tsx b/01-frontend/src/pages/Orders.tsx index 27aa69b..0fe8ad9 100644 --- a/01-frontend/src/pages/Orders.tsx +++ b/01-frontend/src/pages/Orders.tsx @@ -2,17 +2,9 @@ import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider import { useState } from "react"; import "./pages.css"; import {useTranslation} from "react-i18next"; +import OrderType from "../components/Order"; -type Order = { - id: string; - date: string; - status: "laufend" | "inactive"; - items: { name: string; quantity: number; price: number }[]; - total: number; - address: string; -}; - -const mockOrders: Order[] = [ +const mockOrders: OrderType[] = [ { id: "1001", date: "2025-05-20", @@ -41,7 +33,7 @@ export default function Orders() { const { t } = useTranslation(); const [tab, setTab] = useState(0); - const [selectedOrder, setSelectedOrder] = useState(null); + const [selectedOrder, setSelectedOrder] = useState(null); const activeOrders = mockOrders.filter(o => o.status === "active"); const inactiveOrders = mockOrders.filter(o => o.status === "inactive");