Darkmode verbessert
This commit is contained in:
@@ -1,20 +1,13 @@
|
||||
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 {
|
||||
AppBar, Box, Toolbar, IconButton, Typography, Menu,
|
||||
Container, Avatar, Button, Tooltip, MenuItem, InputBase, alpha, styled
|
||||
} from '@mui/material';
|
||||
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';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
const pages = ['Categories', 'Checkout', 'Contact'];
|
||||
const settings = ['Account', 'Orders', 'Logout'];
|
||||
@@ -23,18 +16,21 @@ 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),
|
||||
},
|
||||
border: '1px solid black', // ✅ immer schwarz
|
||||
boxShadow: '0 0 4px rgba(0,0,0,0.2)',
|
||||
marginLeft: 0,
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
marginLeft: theme.spacing(1),
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing(1),
|
||||
width: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const SearchIconWrapper = styled('div')(({ theme }) => ({
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
||||
borderColor: 'black', // ✅ auch beim Hover
|
||||
},
|
||||
}));
|
||||
|
||||
const SearchIconWrapper = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0, 2),
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
@@ -42,26 +38,31 @@ const Search = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}));
|
||||
|
||||
const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
||||
color: 'inherit',
|
||||
}));
|
||||
|
||||
const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
||||
color: theme.palette.text.primary,
|
||||
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',
|
||||
padding: theme.spacing(1, 1, 1, 0),
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||
transition: theme.transitions.create('width'),
|
||||
color: theme.palette.text.primary,
|
||||
'&::placeholder': {
|
||||
color: theme.palette.text.primary,
|
||||
opacity: 0.7,
|
||||
},
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: '12ch',
|
||||
'&:focus': {
|
||||
width: '20ch',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
}));
|
||||
|
||||
export default function NavBar() {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = React.useState<null | HTMLElement>(null);
|
||||
@@ -72,19 +73,16 @@ export default function NavBar() {
|
||||
const handleOpenUserMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseNavMenu = (link: string) => {
|
||||
setAnchorElNav(null);
|
||||
navigate(`/${link.toLowerCase()}`);
|
||||
};
|
||||
|
||||
const handleCloseUserMenu = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<AppBar>
|
||||
<AppBar position="static" color="primary">
|
||||
<Container maxWidth="xl">
|
||||
<Toolbar disableGutters>
|
||||
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
|
||||
@@ -108,10 +106,10 @@ export default function NavBar() {
|
||||
|
||||
<Search>
|
||||
<SearchIconWrapper>
|
||||
<SearchIcon />
|
||||
<SearchIcon sx={{ color: theme.palette.text.primary }} />
|
||||
</SearchIconWrapper>
|
||||
<StyledInputBase
|
||||
placeholder="Search…"
|
||||
placeholder="Suchen…"
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
/>
|
||||
</Search>
|
||||
@@ -130,26 +128,21 @@ export default function NavBar() {
|
||||
<Menu
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElNav}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
||||
open={Boolean(anchorElNav)}
|
||||
onClose={handleCloseNavMenu}
|
||||
onClose={() => setAnchorElNav(null)}
|
||||
sx={{ display: { xs: 'block', md: 'none' } }}
|
||||
>
|
||||
{pages.map((page) => (
|
||||
<MenuItem key={page} onClick={() => handleCloseNavMenu(page)}>
|
||||
<Typography sx={{ textAlign: 'center' }}>{page}</Typography>
|
||||
<Typography textAlign="center">{page}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
|
||||
<Typography
|
||||
variant="h5"
|
||||
@@ -169,6 +162,7 @@ export default function NavBar() {
|
||||
>
|
||||
DPS
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
{pages.map((page) => (
|
||||
<Button
|
||||
@@ -180,31 +174,26 @@ export default function NavBar() {
|
||||
</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" />
|
||||
<Avatar alt="User" src="/static/images/avatar/2.jpg" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
sx={{ mt: '45px' }}
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
{settings.map((setting) => (
|
||||
<MenuItem key={setting} onClick={handleCloseUserMenu}>
|
||||
<Typography sx={{ textAlign: 'center' }}>{setting}</Typography>
|
||||
<Typography textAlign="center">{setting}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
@@ -213,4 +202,4 @@ export default function NavBar() {
|
||||
</Container>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
TableRow,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import AccountType from "../../components/Account";
|
||||
@@ -24,6 +25,8 @@ const mockAccounts: AccountType[] = [
|
||||
];
|
||||
|
||||
export default function AccountsInfo() {
|
||||
const theme = useTheme();
|
||||
|
||||
const [accounts, setAccounts] = useState<AccountType[]>(mockAccounts);
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const [editMode, setEditMode] = useState<{ [key: string]: boolean }>({});
|
||||
@@ -33,31 +36,34 @@ export default function AccountsInfo() {
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
setAccounts((prevAccounts) => prevAccounts.filter((account) => account.id !== id));
|
||||
setAccounts((prev) => prev.filter((account) => account.id !== id));
|
||||
};
|
||||
|
||||
const handleToggleStatus = (id: string) => {
|
||||
setAccounts((prevAccounts) =>
|
||||
prevAccounts.map((account) =>
|
||||
setAccounts((prev) =>
|
||||
prev.map((account) =>
|
||||
account.id === id
|
||||
? { ...account, status: account.status === "active" ? "inactive" : "active" }
|
||||
? {
|
||||
...account,
|
||||
status: account.status === "active" ? "inactive" : "active"
|
||||
}
|
||||
: account
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const handleEdit = (id: string, field: keyof AccountType, value: string) => {
|
||||
setAccounts((prevAccounts) =>
|
||||
prevAccounts.map((account) =>
|
||||
setAccounts((prev) =>
|
||||
prev.map((account) =>
|
||||
account.id === id ? { ...account, [field]: value } : account
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const toggleEditMode = (id: string) => {
|
||||
setEditMode((prevEditMode) => ({
|
||||
...prevEditMode,
|
||||
[id]: !prevEditMode[id],
|
||||
setEditMode((prev) => ({
|
||||
...prev,
|
||||
[id]: !prev[id]
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -68,28 +74,60 @@ export default function AccountsInfo() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ color: theme.palette.text.primary }}>
|
||||
<Typography variant="h4" gutterBottom align="center">
|
||||
Accounts Management
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
label="Search Accounts"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
sx={{ mb: 2 }}
|
||||
sx={{
|
||||
mb: 2,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
'& input': {
|
||||
color: theme.palette.text.primary
|
||||
},
|
||||
'& label': {
|
||||
color: theme.palette.text.secondary
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: theme.palette.divider
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: theme.palette.text.primary
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main
|
||||
}
|
||||
}
|
||||
}}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<TableContainer component={Paper}>
|
||||
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary
|
||||
}}
|
||||
>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>ID</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Email</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell align="center">Actions</TableCell>
|
||||
{["ID", "Name", "Email", "Status", "Actions"].map((col) => (
|
||||
<TableCell
|
||||
key={col}
|
||||
sx={{ color: theme.palette.text.primary, fontWeight: "bold" }}
|
||||
>
|
||||
{col}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{filteredAccounts.map((account) => (
|
||||
<TableRow key={account.id}>
|
||||
@@ -98,10 +136,11 @@ export default function AccountsInfo() {
|
||||
{editMode[account.id] ? (
|
||||
<TextField
|
||||
value={account.name}
|
||||
onChange={(e) =>
|
||||
handleEdit(account.id, "name", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleEdit(account.id, "name", e.target.value)}
|
||||
variant="standard"
|
||||
InputProps={{
|
||||
sx: { color: theme.palette.text.primary }
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
account.name
|
||||
@@ -111,10 +150,11 @@ export default function AccountsInfo() {
|
||||
{editMode[account.id] ? (
|
||||
<TextField
|
||||
value={account.email}
|
||||
onChange={(e) =>
|
||||
handleEdit(account.id, "email", e.target.value)
|
||||
}
|
||||
onChange={(e) => handleEdit(account.id, "email", e.target.value)}
|
||||
variant="standard"
|
||||
InputProps={{
|
||||
sx: { color: theme.palette.text.primary }
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
account.email
|
||||
@@ -122,27 +162,18 @@ export default function AccountsInfo() {
|
||||
</TableCell>
|
||||
<TableCell>{account.status}</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton
|
||||
color="error"
|
||||
onClick={() => handleDelete(account.id)}
|
||||
>
|
||||
<IconButton color="error" onClick={() => handleDelete(account.id)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<Button
|
||||
variant="contained"
|
||||
color={
|
||||
account.status === "active" ? "warning" : "success"
|
||||
}
|
||||
color={account.status === "active" ? "warning" : "success"}
|
||||
onClick={() => handleToggleStatus(account.id)}
|
||||
sx={{ mx: 1 }}
|
||||
>
|
||||
{account.status === "active"
|
||||
? "Set Inactive"
|
||||
: "Set Active"}
|
||||
{account.status === "active" ? "Set Inactive" : "Set Active"}
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => toggleEditMode(account.id)}
|
||||
>
|
||||
<IconButton color="primary" onClick={() => toggleEditMode(account.id)}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
@@ -153,4 +184,4 @@ export default function AccountsInfo() {
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import { Box } from "@mui/material";
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type DroppableContainerProps = {
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function DroppableContainer({ id, children }: DroppableContainerProps) {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -15,13 +17,14 @@ export function DroppableContainer({ id, children }: DroppableContainerProps) {
|
||||
sx={{
|
||||
flex: 1,
|
||||
minHeight: 400,
|
||||
bgcolor: "background.default",
|
||||
border: "1px solid #ccc",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 2,
|
||||
p: 2,
|
||||
transition: "background-color 0.3s, border-color 0.3s",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import { Typography } from "@mui/material";
|
||||
import { Typography, Box, useTheme } from "@mui/material";
|
||||
|
||||
export default function ItemInfo() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Typography >
|
||||
Under construction...
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: 200,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.secondary,
|
||||
borderRadius: 2,
|
||||
boxShadow: 2,
|
||||
mt: 4,
|
||||
p: 4,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">
|
||||
🚧 Under construction...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { closestCenter, DndContext } from "@dnd-kit/core";
|
||||
import { restrictToParentElement } from "@dnd-kit/modifiers";
|
||||
import { Box, Button, Dialog, DialogContent, DialogTitle, Typography } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Typography,
|
||||
useTheme
|
||||
} 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 { DroppableContainer } from "./DroppableContainer";
|
||||
import SortableItem from "./SortableItem";
|
||||
import OrderType from "../../components/Order";
|
||||
|
||||
const mockOrders: OrderType[] = [
|
||||
@@ -14,10 +22,10 @@ const mockOrders: OrderType[] = [
|
||||
status: "active",
|
||||
items: [
|
||||
{ name: "Tomatensamen", quantity: 2, price: 3.99 },
|
||||
{ name: "Blumenerde", quantity: 1, price: 7.49 },
|
||||
{ name: "Blumenerde", quantity: 1, price: 7.49 }
|
||||
],
|
||||
total: 15.47,
|
||||
address: "Musterstraße 1, 12345 Musterstadt",
|
||||
address: "Musterstraße 1, 12345 Musterstadt"
|
||||
},
|
||||
{
|
||||
id: "1000",
|
||||
@@ -25,7 +33,7 @@ const mockOrders: OrderType[] = [
|
||||
status: "inactive",
|
||||
items: [{ name: "Gießkanne", quantity: 1, price: 12.99 }],
|
||||
total: 12.99,
|
||||
address: "Musterstraße 1, 12345 Musterstadt",
|
||||
address: "Musterstraße 1, 12345 Musterstadt"
|
||||
},
|
||||
{
|
||||
id: "1002",
|
||||
@@ -33,7 +41,7 @@ const mockOrders: OrderType[] = [
|
||||
status: "running",
|
||||
items: [{ name: "Pflanzendünger", quantity: 1, price: 8.99 }],
|
||||
total: 8.99,
|
||||
address: "Musterstraße 1, 12345 Musterstadt",
|
||||
address: "Musterstraße 1, 12345 Musterstadt"
|
||||
},
|
||||
{
|
||||
id: "1003",
|
||||
@@ -41,25 +49,25 @@ const mockOrders: OrderType[] = [
|
||||
status: "cancelled",
|
||||
items: [{ name: "Blumentopf", quantity: 2, price: 5.99 }],
|
||||
total: 11.98,
|
||||
address: "Musterstraße 1, 12345 Musterstadt",
|
||||
},
|
||||
address: "Musterstraße 1, 12345 Musterstadt"
|
||||
}
|
||||
];
|
||||
|
||||
const statusOrder = ["active", "running", "inactive", "cancelled"];
|
||||
|
||||
export default function OrdersInfo() {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
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) =>
|
||||
const newStatus = over.id;
|
||||
setOrders((prev) =>
|
||||
prev.map((order) =>
|
||||
order.id === active.id ? { ...order, status: newStatus } : order
|
||||
)
|
||||
);
|
||||
@@ -68,10 +76,10 @@ export default function OrdersInfo() {
|
||||
const handleNextStatus = (order: OrderType) => {
|
||||
const currentIndex = statusOrder.indexOf(order.status);
|
||||
if (currentIndex < statusOrder.length - 1) {
|
||||
setOrders((prevOrders) =>
|
||||
prevOrders.map((o) =>
|
||||
setOrders((prev) =>
|
||||
prev.map((o) =>
|
||||
o.id === order.id
|
||||
? { ...o, status: statusOrder[currentIndex + 1] as OrderType['status'] }
|
||||
? { ...o, status: statusOrder[currentIndex + 1] as OrderType["status"] }
|
||||
: o
|
||||
)
|
||||
);
|
||||
@@ -79,22 +87,27 @@ export default function OrdersInfo() {
|
||||
};
|
||||
|
||||
const renderOrders = (status: string) => {
|
||||
const filteredOrders = orders.filter((order) => order.status === status);
|
||||
const filtered = orders.filter((o) => o.status === status);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: 300,
|
||||
p: 2,
|
||||
bgcolor: "background.paper",
|
||||
border: "1px solid #ccc",
|
||||
borderRadius: 2,
|
||||
bgcolor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 2
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" align="center" gutterBottom>
|
||||
<Typography
|
||||
variant="h6"
|
||||
align="center"
|
||||
gutterBottom
|
||||
sx={{ color: theme.palette.text.primary }}
|
||||
>
|
||||
{t(status)}
|
||||
</Typography>
|
||||
{filteredOrders.map((order) => (
|
||||
{filtered.map((order) => (
|
||||
<SortableItem
|
||||
key={order.id}
|
||||
id={order.id}
|
||||
@@ -111,6 +124,7 @@ export default function OrdersInfo() {
|
||||
<Typography variant="h4" align="center" gutterBottom>
|
||||
{t("Orders Management")}
|
||||
</Typography>
|
||||
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
@@ -125,12 +139,11 @@ export default function OrdersInfo() {
|
||||
</Box>
|
||||
</DndContext>
|
||||
|
||||
{/* Dialog für Bestelldetails */}
|
||||
<Dialog open={!!selectedOrder} onClose={() => setSelectedOrder(null)}>
|
||||
<DialogTitle>Order Details</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContent sx={{ bgcolor: theme.palette.background.paper }}>
|
||||
{selectedOrder && (
|
||||
<Box>
|
||||
<Box sx={{ color: theme.palette.text.primary }}>
|
||||
<Typography variant="body1">
|
||||
<strong>Order ID:</strong> {selectedOrder.id}
|
||||
</Typography>
|
||||
@@ -143,9 +156,9 @@ export default function OrdersInfo() {
|
||||
<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)} €
|
||||
{selectedOrder.items.map((item, idx) => (
|
||||
<Typography key={idx} variant="body2">
|
||||
{item.quantity}x {item.name} – {item.price.toFixed(2)} €
|
||||
</Typography>
|
||||
))}
|
||||
<Typography variant="body1" sx={{ mt: 2 }}>
|
||||
@@ -168,4 +181,4 @@ export default function OrdersInfo() {
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Paper, Typography } from "@mui/material";
|
||||
import { Paper, Typography, useTheme } from "@mui/material";
|
||||
|
||||
type SortableItemProps = {
|
||||
id: string;
|
||||
@@ -13,6 +13,7 @@ type SortableItemProps = {
|
||||
|
||||
export default function SortableItem({ id, order, onClick }: SortableItemProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||
const theme = useTheme();
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
@@ -26,18 +27,22 @@ export default function SortableItem({ id, order, onClick }: SortableItemProps)
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
p: 2,
|
||||
mb: 2,
|
||||
cursor: "pointer",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(0, 0, 0, 0.04)",
|
||||
backgroundColor:
|
||||
theme.palette.mode === "dark"
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
@@ -26,76 +26,123 @@ ChartJS.register(
|
||||
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() {
|
||||
const theme = useTheme();
|
||||
|
||||
const weeklySalesData = {
|
||||
labels: ["Week 1", "Week 2", "Week 3", "Week 4"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Weekly Sales (€)",
|
||||
data: [1200, 2100, 800, 1600],
|
||||
backgroundColor: theme.palette.mode === "dark"
|
||||
? "rgba(0, 230, 255, 0.5)"
|
||||
: "rgba(75, 192, 192, 0.5)",
|
||||
borderColor: theme.palette.mode === "dark"
|
||||
? "rgba(0, 230, 255, 1)"
|
||||
: "rgba(75, 192, 192, 1)",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const itemSalesData = {
|
||||
labels: ["Tomatensamen", "Blumenerde", "Gießkanne", "Pflanzendünger"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Item Sales",
|
||||
data: [400, 300, 200, 100],
|
||||
backgroundColor: [
|
||||
theme.palette.warning.main,
|
||||
theme.palette.info.main,
|
||||
theme.palette.success.main,
|
||||
theme.palette.secondary.main,
|
||||
],
|
||||
hoverBackgroundColor: [
|
||||
theme.palette.warning.light,
|
||||
theme.palette.info.light,
|
||||
theme.palette.success.light,
|
||||
theme.palette.secondary.light,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const userSalesData = {
|
||||
labels: ["John Doe", "Jane Smith", "Alice Johnson", "Bob Brown"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Sales by User",
|
||||
data: [5, 8, 3, 6],
|
||||
fill: false,
|
||||
borderColor: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
tension: 0.1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const baseOptions = {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "top" as const,
|
||||
labels: {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: { color: theme.palette.text.primary },
|
||||
grid: {
|
||||
color: theme.palette.divider,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
ticks: { color: theme.palette.text.primary },
|
||||
grid: {
|
||||
color: theme.palette.divider,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4" align="center" gutterBottom sx={{ color: "text.primary" }}>
|
||||
<Box sx={{ color: theme.palette.text.primary }}>
|
||||
<Typography variant="h4" align="center" gutterBottom>
|
||||
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" } } }} />
|
||||
<Bar data={weeklySalesData} options={baseOptions} />
|
||||
</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" } } }} />
|
||||
<Pie
|
||||
data={itemSalesData}
|
||||
options={{
|
||||
...baseOptions,
|
||||
scales: undefined, // Pie braucht keine Achsen
|
||||
}}
|
||||
/>
|
||||
</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" } } }} />
|
||||
<Line data={userSalesData} options={baseOptions} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { FormControl, FormControlLabel, Radio, RadioGroup, Rating } from "@mui/material";
|
||||
import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Rating,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
type FilterItemOption = {
|
||||
@@ -14,11 +22,13 @@ type FilterItemProps = {
|
||||
};
|
||||
|
||||
export default function FilterItem({
|
||||
filterName,
|
||||
filterItems,
|
||||
value,
|
||||
onChange
|
||||
}: FilterItemProps) {
|
||||
filterName,
|
||||
filterItems,
|
||||
value,
|
||||
onChange,
|
||||
}: FilterItemProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!value && filterItems.length > 0) {
|
||||
value = filterItems[0].value;
|
||||
}
|
||||
@@ -30,8 +40,18 @@ export default function FilterItem({
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>{filterName}</h3>
|
||||
<div style={{ marginBottom: "1.5rem" }}>
|
||||
<FormLabel
|
||||
component="legend"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
{filterName}
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<RadioGroup value={value} onChange={handleChange}>
|
||||
{filterItems.map((item, idx) => (
|
||||
@@ -41,15 +61,23 @@ export default function FilterItem({
|
||||
control={<Radio />}
|
||||
label={
|
||||
/^[1-5]$/.test(item.value) ? (
|
||||
<Rating readOnly value={Number(item.value)} precision={1} size="small" />
|
||||
<Rating
|
||||
readOnly
|
||||
value={Number(item.value)}
|
||||
precision={1}
|
||||
size="small"
|
||||
/>
|
||||
) : (
|
||||
item.label
|
||||
)
|
||||
}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Slider, Typography, Box } from "@mui/material";
|
||||
import { Slider, Typography, Box, useTheme } from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -10,12 +10,13 @@ type PriceSliderProps = {
|
||||
|
||||
export default function PriceSlider({ min = 0, max = 10000, onChange }: PriceSliderProps) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const [value, setValue] = useState<[number, number]>([min, max]);
|
||||
|
||||
// Synchronisiere den Zustand nur, wenn sich die Props ändern
|
||||
useEffect(() => {
|
||||
setValue([min, max]);
|
||||
onChange?.([min, max]); // Initiale Werte an die übergeordnete Komponente übergeben
|
||||
onChange?.([min, max]);
|
||||
}, [min, max]);
|
||||
|
||||
const handleChange = (_: Event, newValue: number | number[]) => {
|
||||
@@ -26,32 +27,55 @@ export default function PriceSlider({ min = 0, max = 10000, onChange }: PriceSli
|
||||
|
||||
const handleCommitted = (_: Event, newValue: number | number[]) => {
|
||||
if (Array.isArray(newValue)) {
|
||||
onChange?.([newValue[0], newValue[1]]); // Übergebe die neuen Werte an die übergeordnete Komponente
|
||||
onChange?.([newValue[0], newValue[1]]);
|
||||
}
|
||||
};
|
||||
|
||||
const formatValueToEuro = (value: number) => {
|
||||
return `${(value / 100).toFixed(2)} €`; // Umrechnung von Cent in Euro
|
||||
};
|
||||
const formatValueToEuro = (val: number) => `${val.toFixed(2)} €`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>{t('price')}</h3>
|
||||
<Box sx={{ pl: 1, pr: 1 }}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
{t("price")}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ px: 1 }}>
|
||||
<Slider
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onChangeCommitted={handleCommitted}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={formatValueToEuro} // Formatierung der Werte in Euro
|
||||
valueLabelFormat={formatValueToEuro}
|
||||
min={min}
|
||||
max={max}
|
||||
step={1}
|
||||
sx={{
|
||||
color: "#0fd13f",
|
||||
'& .MuiSlider-valueLabel': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography>
|
||||
{formatValueToEuro(value[0])} - {formatValueToEuro(value[1])}
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
mt: 1,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: "0.9rem",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{formatValueToEuro(value[0])} – {formatValueToEuro(value[1])}
|
||||
</Typography>
|
||||
</Box>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function NavBar() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar position="static" color="primary" elevation={4}>
|
||||
<AppBar position="static" color="primary" elevation={4}>np
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
|
||||
@@ -1,25 +1,58 @@
|
||||
import { Card, CardActionArea, CardContent, Paper, Rating, Typography } from "@mui/material";
|
||||
import {
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
Paper,
|
||||
Rating,
|
||||
Typography,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import RatingType from "../../components/Rating";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function RatingCard(ratingType: RatingType) {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme(); // Zugriff auf Light/Dark-Mode
|
||||
|
||||
const handleClick = () => {
|
||||
console.log(`Clicked on rating`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper elevation={4}>
|
||||
<Card>
|
||||
<Paper
|
||||
elevation={4}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<CardActionArea onClick={handleClick}>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="h6"
|
||||
component="div"
|
||||
sx={{ color: theme.palette.text.primary }}
|
||||
>
|
||||
{t('ratingFrom')} {new Date(ratingType.timestamp).toLocaleDateString('de-DE')}
|
||||
</Typography>
|
||||
<Rating name="half-rating" readOnly defaultValue={ratingType.rating / 2} precision={0.01} />
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary' }} className="item-description">
|
||||
|
||||
<Rating
|
||||
name="half-rating"
|
||||
readOnly
|
||||
defaultValue={ratingType.rating / 2}
|
||||
precision={0.01}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.text.secondary, mt: 1 }}
|
||||
>
|
||||
{ratingType.content}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
@@ -27,4 +60,4 @@ export default function RatingCard(ratingType: RatingType) {
|
||||
</Card>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
IconButton,
|
||||
Rating,
|
||||
Snackbar,
|
||||
SnackbarCloseReason,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import { Close } from "@mui/icons-material";
|
||||
import { Box, Button, Divider, IconButton, Rating, Snackbar, SnackbarCloseReason, TextField, Typography } from "@mui/material";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -8,48 +19,46 @@ import { fetchRatingList, submitRating } from "../query/Queries";
|
||||
import RatingCard from "./RatingCard";
|
||||
import RatingSubmitType from "../../components/RatingSubmit";
|
||||
|
||||
export default function Ratings({itemId}: {itemId: string}) {
|
||||
|
||||
export default function Ratings({ itemId }: { itemId: string }) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [ratingText, setRatingText] = useState<string>("");
|
||||
const [ratingValue, setRatingValue] = useState<number | null>(2.5);
|
||||
|
||||
const handleClose = (
|
||||
event: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason,
|
||||
) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const ratingData: RatingSubmitType = {
|
||||
rating: ratingValue || 0,
|
||||
content: ratingText || "",
|
||||
articleId: itemId, // Konvertiert itemId in einen String,
|
||||
articleId: itemId,
|
||||
};
|
||||
|
||||
const { refetch } = useQuery({
|
||||
queryKey: ["submitRating", ratingData], // Query-Key mit Daten
|
||||
queryKey: ["submitRating", ratingData],
|
||||
queryFn: () => submitRating(ratingData),
|
||||
retry: 3, // Versucht es 3-mal erneut
|
||||
retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
|
||||
enabled: false, // Deaktiviert die automatische Ausführung
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const handleRatingSubmit = () => {
|
||||
setOpen(true);
|
||||
refetch(); // Manuelles Auslösen der Abfrage
|
||||
}
|
||||
void refetch(); // bewusst ausgelöst, kein await notwendig
|
||||
};
|
||||
|
||||
const handleClose = (
|
||||
_: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason
|
||||
) => {
|
||||
if (reason === "clickaway") return;
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const action = (
|
||||
<React.Fragment>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={t('close')}
|
||||
aria-label={t("close")}
|
||||
color="inherit"
|
||||
onClick={handleClose}
|
||||
>
|
||||
@@ -59,57 +68,99 @@ export default function Ratings({itemId}: {itemId: string}) {
|
||||
);
|
||||
|
||||
const { data = [] } = useQuery<RatingType[]>({
|
||||
queryKey: ['fetchRatingList', itemId],
|
||||
queryKey: ["fetchRatingList", itemId],
|
||||
queryFn: () => fetchRatingList(itemId),
|
||||
retry: 3, // Versucht es 3-mal erneut
|
||||
retryDelay: 1000, // Wartezeit zwischen den Versuchen (in ms)
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
});
|
||||
|
||||
const ratings:RatingType[] = useMemo(() => data || [], [data]);
|
||||
const ratings: RatingType[] = useMemo(() => data || [], [data]);
|
||||
|
||||
const getRatings = () => {
|
||||
if (ratings.length === 0)
|
||||
return <Typography variant="body1" className="no-ratings">{t('noRatingsYet')}</Typography>;
|
||||
if (ratings.length === 0) {
|
||||
return (
|
||||
<Typography variant="body1" sx={{ color: theme.palette.text.secondary }}>
|
||||
{t("noRatingsYet")}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return ratings.map((ratingType: RatingType) => (
|
||||
<RatingCard {...ratingType} />
|
||||
))
|
||||
<RatingCard key={ratingType.timestamp} {...ratingType} />
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='contact-divider-box'>
|
||||
<Divider className='contact-divider' />
|
||||
</div>
|
||||
<div className="rating-card-body">
|
||||
<Typography variant="h5">
|
||||
{t('rateThisProduct')}:</Typography>
|
||||
<Rating name="half-rating" value={ratingValue} onChange={(e, value) => setRatingValue(value)} precision={0.5} />
|
||||
<Divider sx={{ backgroundColor: theme.palette.divider, my: 3 }} />
|
||||
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Typography variant="h5" sx={{ color: theme.palette.text.primary, mb: 2 }}>
|
||||
{t("rateThisProduct")}:
|
||||
</Typography>
|
||||
|
||||
<Rating
|
||||
name="half-rating"
|
||||
value={ratingValue}
|
||||
onChange={(_, value) => setRatingValue(value)}
|
||||
precision={0.5}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={t('review')}
|
||||
label={t("review")}
|
||||
multiline
|
||||
minRows={4}
|
||||
maxRows={16}
|
||||
className="rating-text-field"
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 2,
|
||||
mb: 2,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
'& .MuiInputBase-input': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& label': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: theme.palette.text.primary,
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
}}
|
||||
value={ratingText}
|
||||
onChange={(e) => setRatingText(e.target.value)}
|
||||
/>
|
||||
<Button variant="contained" color="primary" className="rating-button" onClick={handleRatingSubmit}>
|
||||
{t('submit')}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleRatingSubmit}
|
||||
>
|
||||
{t("submit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='contact-divider-box'>
|
||||
<Divider className='contact-divider' />
|
||||
</div>
|
||||
<Box className="rating-card-box">
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ backgroundColor: theme.palette.divider, my: 3 }} />
|
||||
|
||||
<Box>
|
||||
{getRatings()}
|
||||
</Box>
|
||||
|
||||
<Snackbar
|
||||
open={open}
|
||||
autoHideDuration={3000}
|
||||
onClose={handleClose}
|
||||
message="Thanks for your rating!"
|
||||
message={t("thanksForRating")}
|
||||
action={action}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
import { AccountCircle, Category, QueryStats, ReceiptLong } from "@mui/icons-material";
|
||||
import { Box, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography } from "@mui/material";
|
||||
import {
|
||||
AccountCircle,
|
||||
Category,
|
||||
QueryStats,
|
||||
ReceiptLong,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AccountsInfo from "../helper/adminpanel/AccountsInfo";
|
||||
@@ -9,15 +23,14 @@ import ItemInfo from "../helper/adminpanel/ItemsInfo";
|
||||
|
||||
export default function AdminPanel() {
|
||||
const { t } = useTranslation();
|
||||
const [infoStatus, setInfoStatus] = useState<string>("statistics"); // Standardseite
|
||||
const theme = useTheme();
|
||||
const [infoStatus, setInfoStatus] = useState<string>("statistics");
|
||||
|
||||
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 />;
|
||||
@@ -28,66 +41,69 @@ export default function AdminPanel() {
|
||||
case "items":
|
||||
return <ItemInfo />;
|
||||
default:
|
||||
return <StatisticsInfo />; // Fallback, falls kein gültiger Status
|
||||
return <StatisticsInfo />;
|
||||
}
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ key: "statistics", icon: <QueryStats />, label: t("statistics") },
|
||||
{ key: "orders", icon: <ReceiptLong />, label: t("orders") },
|
||||
{ key: "accounts", icon: <AccountCircle />, label: t("accounts") },
|
||||
{ key: "items", icon: <Category />, label: t("items") },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<Box className="page-container" sx={{ color: theme.palette.text.primary }}>
|
||||
<Box>
|
||||
<Typography variant="h3" align="center" gutterBottom sx={{ color: 'text.primary' }}>
|
||||
<Typography variant="h3" align="center" gutterBottom>
|
||||
Admin Panel
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" align="center">
|
||||
Manage your application settings and content here.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 4 }}>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 4, mt: 4 }}>
|
||||
{/* Sidebar */}
|
||||
<Box sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper', border: '1px solid red' }}>
|
||||
<nav aria-label="main mailbox folders">
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: 280,
|
||||
bgcolor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 2,
|
||||
boxShadow: 1,
|
||||
}}
|
||||
>
|
||||
<nav aria-label="main admin menu">
|
||||
<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>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={() => handleInfoStatus("items")}>
|
||||
<ListItemIcon>
|
||||
<Category />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("items")} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{menuItems.map((item) => (
|
||||
<ListItem key={item.key} disablePadding>
|
||||
<ListItemButton
|
||||
selected={infoStatus === item.key}
|
||||
onClick={() => handleInfoStatus(item.key)}
|
||||
sx={{
|
||||
"&.Mui-selected": {
|
||||
bgcolor: theme.palette.action.selected,
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
"&:hover": {
|
||||
bgcolor: theme.palette.action.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ color: "inherit" }}>{item.icon}</ListItemIcon>
|
||||
<ListItemText primary={item.label} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</nav>
|
||||
</Box>
|
||||
|
||||
{/* Content */}
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
{renderContent()}
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1 }}>{renderContent()}</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,35 +9,47 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import Item from '../components/Item';
|
||||
import ProductInfo from '../helper/productpage/ProductInfo';
|
||||
import Ratings from '../helper/productpage/Ratings';
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export default function Product() {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const item = location.state?.item as Item;
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme(); // 🌗 Zugriff auf das aktive Theme
|
||||
|
||||
const handleGoHome = () => {
|
||||
navigate("/");
|
||||
};
|
||||
|
||||
// Redirect immediately if item is not found
|
||||
// Wenn kein Produkt vorhanden ist
|
||||
if (!item) {
|
||||
return (
|
||||
<Box className="no-page-container">
|
||||
<Typography variant="h1" className="no-page-title">
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
gap: '2rem',
|
||||
px: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h1">
|
||||
{t('productNotFound')}
|
||||
</Typography>
|
||||
<Typography variant="h5" className="no-page-description">
|
||||
<Typography variant="h5">
|
||||
{t('productDoesNotExist')}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
className="no-page-button"
|
||||
onClick={handleGoHome}
|
||||
>
|
||||
{t('backToHome')}
|
||||
@@ -47,21 +59,37 @@ export default function Product() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='product-page-background'>
|
||||
<Container maxWidth="lg" sx={{ py: 4 }} >
|
||||
<ProductInfo item={item}/>
|
||||
<div className='contact-divider-box'>
|
||||
<Divider className='contact-divider' />
|
||||
</div>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
color: theme.palette.text.primary,
|
||||
minHeight: '100vh',
|
||||
py: 4,
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<ProductInfo item={item} />
|
||||
|
||||
<Box sx={{ my: 4 }}>
|
||||
<Divider sx={{ backgroundColor: theme.palette.text.secondary }} />
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.text.secondary, mb: 1 }}
|
||||
>
|
||||
{t('articleNumber')}: {item.uuid}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ color: theme.palette.text.primary, mb: 3 }}
|
||||
>
|
||||
{item.description}
|
||||
</Typography>
|
||||
|
||||
<Ratings itemId={item.uuid} />
|
||||
</Container>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -48,14 +48,13 @@
|
||||
width: 90%;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
padding: 16px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-background {
|
||||
background-color: var(--background-color);
|
||||
height: calc(100vh);
|
||||
height: 100vh;
|
||||
min-height: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -130,13 +129,13 @@
|
||||
scroll-behavior: smooth;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
color: grey;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
width: fit-content;
|
||||
display: grid;
|
||||
margin: 20px 30px 20px 30px;
|
||||
margin: 20px 30px;
|
||||
place-self: start;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -152,6 +151,6 @@
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
min-width: 600px;
|
||||
background-color: grey;
|
||||
color: primary;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export const CustomThemeProvider: React.FC<CustomThemeProviderProps> = ({ childr
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
colorPrimary: {
|
||||
backgroundColor: mode === 'dark' ? '#065f24' : '#0fd13f',
|
||||
backgroundColor: '#0fd13f',
|
||||
color: '#ffffff',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user