Added Admin Panel dummy ui.
This commit is contained in:
106
01-frontend/package-lock.json
generated
106
01-frontend/package-lock.json
generated
@@ -8,16 +8,21 @@
|
|||||||
"name": "01-frontend",
|
"name": "01-frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"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/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.0.2",
|
"@mui/icons-material": "^7.0.2",
|
||||||
"@mui/material": "^7.0.2",
|
"@mui/material": "^7.0.2",
|
||||||
"@tanstack/react-query": "^5.79.2",
|
"@tanstack/react-query": "^5.79.2",
|
||||||
|
"chart.js": "^4.4.9",
|
||||||
"i18next": "^25.2.0",
|
"i18next": "^25.2.0",
|
||||||
"i18next-browser-languagedetector": "^8.1.0",
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"mui": "^0.0.1",
|
"mui": "^0.0.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
"react-router-dom": "^7.5.3"
|
"react-router-dom": "^7.5.3"
|
||||||
@@ -338,6 +343,73 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@emotion/babel-plugin": {
|
||||||
"version": "11.13.5",
|
"version": "11.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
@@ -1177,6 +1249,12 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz",
|
"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"
|
"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": {
|
"node_modules/clsx": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
@@ -3621,6 +3711,16 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
@@ -4018,6 +4118,12 @@
|
|||||||
"typescript": ">=4.8.4"
|
"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": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
|
|||||||
@@ -12,16 +12,21 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^7.0.2",
|
"@mui/icons-material": "^7.0.2",
|
||||||
"@mui/material": "^7.0.2",
|
"@mui/material": "^7.0.2",
|
||||||
"@tanstack/react-query": "^5.79.2",
|
"@tanstack/react-query": "^5.79.2",
|
||||||
|
"chart.js": "^4.4.9",
|
||||||
"i18next": "^25.2.0",
|
"i18next": "^25.2.0",
|
||||||
"i18next-browser-languagedetector": "^8.1.0",
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"mui": "^0.0.1",
|
"mui": "^0.0.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
"react-router-dom": "^7.5.3"
|
"react-router-dom": "^7.5.3"
|
||||||
|
|||||||
@@ -84,5 +84,7 @@
|
|||||||
"wrongTurn": "Ein falscher Weg wurde eingeschlagen. Hier geht es zurück auf den richtigen Pfad.",
|
"wrongTurn": "Ein falscher Weg wurde eingeschlagen. Hier geht es zurück auf den richtigen Pfad.",
|
||||||
"yourOrderNumber": "Die Bestellnummer lautet",
|
"yourOrderNumber": "Die Bestellnummer lautet",
|
||||||
"articleNumber": "Artikelnummer",
|
"articleNumber": "Artikelnummer",
|
||||||
"noRatingsYet": "Noch keine Bewertungen vorhanden."
|
"noRatingsYet": "Noch keine Bewertungen vorhanden.",
|
||||||
|
"accounts": "Konten",
|
||||||
|
"statistics": "Statistiken"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,5 +84,7 @@
|
|||||||
"wrongTurn": "It seems you may have taken a wrong turn. Let's get you back on track.",
|
"wrongTurn": "It seems you may have taken a wrong turn. Let's get you back on track.",
|
||||||
"yourOrderNumber": "Your order number is",
|
"yourOrderNumber": "Your order number is",
|
||||||
"articleNumber": "Article number",
|
"articleNumber": "Article number",
|
||||||
"noRatingsYet": "No ratings yet"
|
"noRatingsYet": "No ratings yet",
|
||||||
|
"accounts": "Accounts",
|
||||||
|
"statistics": "Statistics"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import './App.css';
|
|||||||
import { BasketProvider } from './helper/BasketProvider';
|
import { BasketProvider } from './helper/BasketProvider';
|
||||||
import NavBar from './helper/navbar/NavBar';
|
import NavBar from './helper/navbar/NavBar';
|
||||||
import Account from './pages/Account';
|
import Account from './pages/Account';
|
||||||
import Category from './pages/Category';
|
|
||||||
import Contact from './pages/Contact';
|
import Contact from './pages/Contact';
|
||||||
import Home from './pages/Home';
|
import Home from './pages/Home';
|
||||||
import NoPage from './pages/NoPage';
|
import NoPage from './pages/NoPage';
|
||||||
@@ -13,6 +12,8 @@ import Orders from './pages/Orders';
|
|||||||
import Payment from './pages/Payment';
|
import Payment from './pages/Payment';
|
||||||
import Product from './pages/Product';
|
import Product from './pages/Product';
|
||||||
import { CustomThemeProvider } from './theme/ThemeContext';
|
import { CustomThemeProvider } from './theme/ThemeContext';
|
||||||
|
import FSModel from './pages/FSModel';
|
||||||
|
import AdminPanel from './pages/AdminPanel';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|
||||||
@@ -31,10 +32,11 @@ export default function App() {
|
|||||||
<Route path="*" element={<NoPage />} />
|
<Route path="*" element={<NoPage />} />
|
||||||
<Route path="/product/:id" element={<Product />} />
|
<Route path="/product/:id" element={<Product />} />
|
||||||
<Route path="/checkout" element={<Payment />} />
|
<Route path="/checkout" element={<Payment />} />
|
||||||
<Route path="/categories" element={<Category />} />
|
<Route path="/fsmodel" element={<FSModel />} />
|
||||||
<Route path="/contact" element={<Contact />} />
|
<Route path="/contact" element={<Contact />} />
|
||||||
<Route path='/account' element={<Account />} />
|
<Route path='/account' element={<Account />} />
|
||||||
<Route path='/orders' element={<Orders />} />
|
<Route path='/orders' element={<Orders />} />
|
||||||
|
<Route path='/admin' element={<AdminPanel />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</BasketProvider>
|
</BasketProvider>
|
||||||
|
|||||||
8
01-frontend/src/components/Account.tsx
Normal file
8
01-frontend/src/components/Account.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
type AccountType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
status: "active" | "inactive";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountType;
|
||||||
10
01-frontend/src/components/Order.tsx
Normal file
10
01-frontend/src/components/Order.tsx
Normal file
@@ -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;
|
||||||
70
01-frontend/src/helper/NavBar.css
Normal file
70
01-frontend/src/helper/NavBar.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
216
01-frontend/src/helper/NavBar.tsx
Normal file
216
01-frontend/src/helper/NavBar.tsx
Normal file
@@ -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 | HTMLElement>(null);
|
||||||
|
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
|
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorElNav(event.currentTarget);
|
||||||
|
};
|
||||||
|
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorElUser(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseNavMenu = (link: string) => {
|
||||||
|
setAnchorElNav(null);
|
||||||
|
navigate(`/${link.toLowerCase()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseUserMenu = () => {
|
||||||
|
setAnchorElUser(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar>
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Toolbar disableGutters>
|
||||||
|
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
noWrap
|
||||||
|
component="a"
|
||||||
|
href="/"
|
||||||
|
sx={{
|
||||||
|
mr: 2,
|
||||||
|
display: { xs: 'none', md: 'flex' },
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '.3rem',
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Digitaler Produktionsshop
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Search>
|
||||||
|
<SearchIconWrapper>
|
||||||
|
<SearchIcon />
|
||||||
|
</SearchIconWrapper>
|
||||||
|
<StyledInputBase
|
||||||
|
placeholder="Search…"
|
||||||
|
inputProps={{ 'aria-label': 'search' }}
|
||||||
|
/>
|
||||||
|
</Search>
|
||||||
|
|
||||||
|
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||||
|
<IconButton
|
||||||
|
size="large"
|
||||||
|
aria-label="account of current user"
|
||||||
|
aria-controls="menu-appbar"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleOpenNavMenu}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElNav}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={Boolean(anchorElNav)}
|
||||||
|
onClose={handleCloseNavMenu}
|
||||||
|
sx={{ display: { xs: 'block', md: 'none' } }}
|
||||||
|
>
|
||||||
|
{pages.map((page) => (
|
||||||
|
<MenuItem key={page} onClick={() => handleCloseNavMenu(page)}>
|
||||||
|
<Typography sx={{ textAlign: 'center' }}>{page}</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
noWrap
|
||||||
|
component="a"
|
||||||
|
href="/"
|
||||||
|
sx={{
|
||||||
|
mr: 2,
|
||||||
|
display: { xs: 'flex', md: 'none' },
|
||||||
|
flexGrow: 1,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '.3rem',
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
DPS
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||||
|
{pages.map((page) => (
|
||||||
|
<Button
|
||||||
|
key={page}
|
||||||
|
onClick={() => handleCloseNavMenu(page)}
|
||||||
|
sx={{ my: 2, color: 'white', display: 'block' }}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ flexGrow: 0 }}>
|
||||||
|
<Tooltip title="Open settings">
|
||||||
|
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||||
|
<Avatar alt="Florian Speicher" src="/static/images/avatar/2.jpg" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Menu
|
||||||
|
sx={{ mt: '45px' }}
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElUser}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
open={Boolean(anchorElUser)}
|
||||||
|
onClose={handleCloseUserMenu}
|
||||||
|
>
|
||||||
|
{settings.map((setting) => (
|
||||||
|
<MenuItem key={setting} onClick={handleCloseUserMenu}>
|
||||||
|
<Typography sx={{ textAlign: 'center' }}>{setting}</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</Container>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
156
01-frontend/src/helper/adminpanel/AccountsInfo.tsx
Normal file
156
01-frontend/src/helper/adminpanel/AccountsInfo.tsx
Normal file
@@ -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<AccountType[]>(mockAccounts);
|
||||||
|
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||||
|
const [editMode, setEditMode] = useState<{ [key: string]: boolean }>({});
|
||||||
|
|
||||||
|
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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 (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h4" gutterBottom align="center">
|
||||||
|
Accounts Management
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
label="Search Accounts"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>ID</TableCell>
|
||||||
|
<TableCell>Name</TableCell>
|
||||||
|
<TableCell>Email</TableCell>
|
||||||
|
<TableCell>Status</TableCell>
|
||||||
|
<TableCell align="center">Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{filteredAccounts.map((account) => (
|
||||||
|
<TableRow key={account.id}>
|
||||||
|
<TableCell>{account.id}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{editMode[account.id] ? (
|
||||||
|
<TextField
|
||||||
|
value={account.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleEdit(account.id, "name", e.target.value)
|
||||||
|
}
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
account.name
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{editMode[account.id] ? (
|
||||||
|
<TextField
|
||||||
|
value={account.email}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleEdit(account.id, "email", e.target.value)
|
||||||
|
}
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
account.email
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{account.status}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
<IconButton
|
||||||
|
color="error"
|
||||||
|
onClick={() => handleDelete(account.id)}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color={
|
||||||
|
account.status === "active" ? "warning" : "success"
|
||||||
|
}
|
||||||
|
onClick={() => handleToggleStatus(account.id)}
|
||||||
|
>
|
||||||
|
{account.status === "active"
|
||||||
|
? "Set Inactive"
|
||||||
|
: "Set Active"}
|
||||||
|
</Button>
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
onClick={() => toggleEditMode(account.id)}
|
||||||
|
>
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
01-frontend/src/helper/adminpanel/DroppableContainer.tsx
Normal file
27
01-frontend/src/helper/adminpanel/DroppableContainer.tsx
Normal file
@@ -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 (
|
||||||
|
<Box
|
||||||
|
ref={setNodeRef}
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 400,
|
||||||
|
bgcolor: "background.default",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: 2,
|
||||||
|
p: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
171
01-frontend/src/helper/adminpanel/OrdersInfo.tsx
Normal file
171
01-frontend/src/helper/adminpanel/OrdersInfo.tsx
Normal file
@@ -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<OrderType[]>(mockOrders);
|
||||||
|
const [selectedOrder, setSelectedOrder] = useState<OrderType | null>(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 (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: 300,
|
||||||
|
p: 2,
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
|
{t(status)}
|
||||||
|
</Typography>
|
||||||
|
{filteredOrders.map((order) => (
|
||||||
|
<SortableItem
|
||||||
|
key={order.id}
|
||||||
|
id={order.id}
|
||||||
|
order={order}
|
||||||
|
onClick={() => setSelectedOrder(order)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h4" align="center" gutterBottom>
|
||||||
|
{t("Orders Management")}
|
||||||
|
</Typography>
|
||||||
|
<DndContext
|
||||||
|
collisionDetection={closestCenter}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
modifiers={[restrictToParentElement]}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
|
{statusOrder.map((status) => (
|
||||||
|
<DroppableContainer key={status} id={status}>
|
||||||
|
{renderOrders(status)}
|
||||||
|
</DroppableContainer>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</DndContext>
|
||||||
|
|
||||||
|
{/* Dialog für Bestelldetails */}
|
||||||
|
<Dialog open={!!selectedOrder} onClose={() => setSelectedOrder(null)}>
|
||||||
|
<DialogTitle>Order Details</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{selectedOrder && (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>Order ID:</strong> {selectedOrder.id}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>Date:</strong> {selectedOrder.date}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<strong>Address:</strong> {selectedOrder.address}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ mt: 2 }}>
|
||||||
|
<strong>Items:</strong>
|
||||||
|
</Typography>
|
||||||
|
{selectedOrder.items.map((item, index) => (
|
||||||
|
<Typography key={index} variant="body2">
|
||||||
|
{item.quantity}x {item.name} - {item.price.toFixed(2)} €
|
||||||
|
</Typography>
|
||||||
|
))}
|
||||||
|
<Typography variant="body1" sx={{ mt: 2 }}>
|
||||||
|
<strong>Total:</strong> {selectedOrder.total.toFixed(2)} €
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
onClick={() => {
|
||||||
|
handleNextStatus(selectedOrder);
|
||||||
|
setSelectedOrder(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Move to Next Status
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
01-frontend/src/helper/adminpanel/SortableItem.tsx
Normal file
43
01-frontend/src/helper/adminpanel/SortableItem.tsx
Normal file
@@ -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 (
|
||||||
|
<Paper
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
mb: 2,
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.04)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Typography variant="body1">Order ID: {order.id}</Typography>
|
||||||
|
<Typography variant="body2">Total: {order.total.toFixed(2)} €</Typography>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
101
01-frontend/src/helper/adminpanel/StatisticsInfo.tsx
Normal file
101
01-frontend/src/helper/adminpanel/StatisticsInfo.tsx
Normal file
@@ -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 (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h4" align="center" gutterBottom sx={{ color: "text.primary" }}>
|
||||||
|
Sales Statistics
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Gesamtverkaufszahlen pro Woche */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
|
Weekly Sales
|
||||||
|
</Typography>
|
||||||
|
<Bar data={weeklySalesData} options={{ responsive: true, plugins: { legend: { position: "top" } } }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Verkaufsanzahl der einzelnen Items */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
|
Item Sales Distribution
|
||||||
|
</Typography>
|
||||||
|
<Pie data={itemSalesData} options={{ responsive: true, plugins: { legend: { position: "top" } } }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Verkäufe pro Benutzer */}
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
|
Sales by User
|
||||||
|
</Typography>
|
||||||
|
<Line data={userSalesData} options={{ responsive: true, plugins: { legend: { position: "top" } } }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { AddShoppingCart } from "@mui/icons-material";
|
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 { useNavigate } from "react-router-dom";
|
||||||
import Item from "../../components/Item";
|
import Item from "../../components/Item";
|
||||||
import { useBasket } from "../BasketProvider";
|
import { useBasket } from "../BasketProvider";
|
||||||
import "../helper.css";
|
import "../helper.css";
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
|
|
||||||
export default function ItemCard({ item }: { item: Item }) {
|
export default function ItemCard({ item }: { item: Item }) {
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function NavBar() {
|
|||||||
|
|
||||||
const [itemNames, setItemNames] = React.useState<string[]>([]); // Für Autocomplete
|
const [itemNames, setItemNames] = React.useState<string[]>([]); // Für Autocomplete
|
||||||
|
|
||||||
const pageKeys = ['categories', 'checkout', 'contact'];
|
const pageKeys = ['fsmodel', 'admin', 'checkout', 'contact'];
|
||||||
const settingKeys = ['account', 'orders', 'logout'];
|
const settingKeys = ['account', 'orders', 'logout'];
|
||||||
|
|
||||||
const pages = pageKeys.map(key => ({ key, label: t(key) }));
|
const pages = pageKeys.map(key => ({ key, label: t(key) }));
|
||||||
|
|||||||
82
01-frontend/src/pages/AdminPanel.tsx
Normal file
82
01-frontend/src/pages/AdminPanel.tsx
Normal file
@@ -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<string>("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 <StatisticsInfo />;
|
||||||
|
case "orders":
|
||||||
|
return <OrdersInfo />;
|
||||||
|
case "accounts":
|
||||||
|
return <AccountsInfo />;
|
||||||
|
default:
|
||||||
|
return <StatisticsInfo />; // Fallback, falls kein gültiger Status
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container">
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h3" align="center" gutterBottom sx={{ color: 'text.primary' }}>
|
||||||
|
Admin Panel
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" align="center">
|
||||||
|
Manage your application settings and content here.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", gap: 4 }}>
|
||||||
|
{/* Sidebar */}
|
||||||
|
<Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper', border: '1px solid red' }}>
|
||||||
|
<nav aria-label="main mailbox folders">
|
||||||
|
<List>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => handleInfoStatus("statistics")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<QueryStats />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={t("statistics")} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => handleInfoStatus("orders")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ReceiptLong />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={t("orders")} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => handleInfoStatus("accounts")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<AccountCircle />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={t("accounts")} />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</nav>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
|
{renderContent()}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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: <SpaIcon sx={{ fontSize: 48, color: "#388e3c" }} />,
|
|
||||||
filter: "Seeds",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t('gardenSupplies'),
|
|
||||||
icon: <ScienceIcon sx={{ fontSize: 48, color: "#fbc02d" }} />,
|
|
||||||
filter: "GardenSupplies"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t('technicalComponents'),
|
|
||||||
icon: <PrecisionManufacturingIcon sx={{ fontSize: 48, color: "#1976d2" }} />,
|
|
||||||
filter: "TechnicalComponents"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t('other'),
|
|
||||||
icon: <CategoryIcon sx={{ fontSize: 48, color: "#757575" }} />,
|
|
||||||
filter: "Other"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleCategoryClick = (filter: string) => {
|
|
||||||
// Navigiere zur Home-Seite und übergebe die gewählte Kategorie als Query-Parameter
|
|
||||||
navigate(`/?category=${encodeURIComponent(filter)}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box className="page-background" sx={{ minHeight: "100vh", pt: 4 }}>
|
|
||||||
<Typography
|
|
||||||
variant="h3"
|
|
||||||
align="center"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ color: 'text.primary' }}
|
|
||||||
>
|
|
||||||
{t('categories')}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="subtitle1" align="center" sx={{ mb: 4 }}>
|
|
||||||
...
|
|
||||||
</Typography>
|
|
||||||
<Grid container spacing={4} justifyContent="center">
|
|
||||||
{categories.map((cat) => (
|
|
||||||
<Grid item xs={12} sm={6} md={3} key={cat.name}>
|
|
||||||
<Card elevation={4} sx={{ borderRadius: 3 }}>
|
|
||||||
<CardActionArea onClick={() => handleCategoryClick(cat.filter)}>
|
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", py: 4 }}>
|
|
||||||
{cat.icon}
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="h6" align="center">
|
|
||||||
{cat.name}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Box>
|
|
||||||
</CardActionArea>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
24
01-frontend/src/pages/FSModel.tsx
Normal file
24
01-frontend/src/pages/FSModel.tsx
Normal file
@@ -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 (
|
||||||
|
<Box className="page-background" sx={{ minHeight: "100vh", pt: 4 }}>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
align="center"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ color: 'text.primary' }}
|
||||||
|
>
|
||||||
|
{t('categories')}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" align="center" sx={{ mb: 4 }}>
|
||||||
|
...
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,17 +2,9 @@ import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import "./pages.css";
|
import "./pages.css";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
import OrderType from "../components/Order";
|
||||||
|
|
||||||
type Order = {
|
const mockOrders: OrderType[] = [
|
||||||
id: string;
|
|
||||||
date: string;
|
|
||||||
status: "laufend" | "inactive";
|
|
||||||
items: { name: string; quantity: number; price: number }[];
|
|
||||||
total: number;
|
|
||||||
address: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockOrders: Order[] = [
|
|
||||||
{
|
{
|
||||||
id: "1001",
|
id: "1001",
|
||||||
date: "2025-05-20",
|
date: "2025-05-20",
|
||||||
@@ -41,7 +33,7 @@ export default function Orders() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [tab, setTab] = useState(0);
|
const [tab, setTab] = useState(0);
|
||||||
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
|
const [selectedOrder, setSelectedOrder] = useState<OrderType | null>(null);
|
||||||
|
|
||||||
const activeOrders = mockOrders.filter(o => o.status === "active");
|
const activeOrders = mockOrders.filter(o => o.status === "active");
|
||||||
const inactiveOrders = mockOrders.filter(o => o.status === "inactive");
|
const inactiveOrders = mockOrders.filter(o => o.status === "inactive");
|
||||||
|
|||||||
Reference in New Issue
Block a user