Added Admin Panel dummy ui.

This commit is contained in:
FlorianSpeicher
2025-06-04 23:18:35 +02:00
parent 69f6c8b818
commit 6484a611cd
20 changed files with 1036 additions and 95 deletions

View 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;
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View File

@@ -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 }) {

View File

@@ -29,7 +29,7 @@ export default function NavBar() {
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 pages = pageKeys.map(key => ({ key, label: t(key) }));