semi working order info admin page
This commit is contained in:
@@ -41,6 +41,7 @@ public class ControllerPathConfig {
|
||||
//OrderController
|
||||
public static final String ORDER_BASE = "/order";
|
||||
public static final String ORDER_GET_ALL = ORDER_BASE + "/all";
|
||||
public static final String ORDER_GET_ALL_ADMIN = ORDER_BASE + "/all/all";
|
||||
|
||||
//ReviewController
|
||||
public static final String REVIEW_BASE = "/review";
|
||||
|
||||
@@ -16,8 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static de.htwsaar.webshop.config.ControllerPathConfig.ORDER_BASE;
|
||||
import static de.htwsaar.webshop.config.ControllerPathConfig.ORDER_GET_ALL;
|
||||
import static de.htwsaar.webshop.config.ControllerPathConfig.*;
|
||||
import static de.htwsaar.webshop.config.ParameterConfig.*;
|
||||
import static de.htwsaar.webshop.util.LoggerUtil.logRequest;
|
||||
|
||||
@@ -47,7 +46,7 @@ public class OrderController {
|
||||
return ResponseEntity.ok(orders.stream().map(Order::toModel).toList());
|
||||
}
|
||||
|
||||
@RequestMapping(path = ORDER_GET_ALL, method = RequestMethod.TRACE, produces = "application/json")
|
||||
@RequestMapping(path = ORDER_GET_ALL_ADMIN, method = RequestMethod.GET, produces = "application/json")
|
||||
public ResponseEntity<List<OrderModel>> getAll(HttpServletRequest request,
|
||||
@RequestParam(value = PARAM_EMAIL) String email,
|
||||
@RequestParam(value = PARAM_SESSION) UUID token) {
|
||||
@@ -92,10 +91,16 @@ public class OrderController {
|
||||
@RequestParam(value = PARAM_STATUS) OrderStatus status) {
|
||||
logRequest(request);
|
||||
if (orderId == null) {
|
||||
log.info("[{}] failed to update, empty orderID", request.getRequestURI());
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
if (status == null) {
|
||||
log.info("[{}] failed to update, empty status, orderID {}", request.getRequestURI(), orderId);
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
Order order = orderService.getOrderById(orderId);
|
||||
if (order == null) {
|
||||
log.info("[{}] failed to update orderID {}", request.getRequestURI(), orderId);
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
order.setStatus(status);
|
||||
|
||||
80
01-frontend/package-lock.json
generated
80
01-frontend/package-lock.json
generated
@@ -26,6 +26,8 @@
|
||||
"react": "^19.0.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-cookie": "^8.0.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-router-dom": "^7.5.3"
|
||||
@@ -1693,6 +1695,24 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-dnd/asap": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
|
||||
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-dnd/invariant": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
|
||||
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-dnd/shallowequal": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
|
||||
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.11",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
||||
@@ -2933,6 +2953,17 @@
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dnd-core": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
|
||||
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-dnd/asap": "^5.0.1",
|
||||
"@react-dnd/invariant": "^4.0.1",
|
||||
"redux": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
@@ -3204,7 +3235,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
@@ -4110,6 +4140,45 @@
|
||||
"react": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
|
||||
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-dnd/invariant": "^4.0.1",
|
||||
"@react-dnd/shallowequal": "^4.0.1",
|
||||
"dnd-core": "^16.0.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"hoist-non-react-statics": "^3.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/hoist-non-react-statics": ">= 3.3.1",
|
||||
"@types/node": ">= 12",
|
||||
"@types/react": ">= 16",
|
||||
"react": ">= 16.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/hoist-non-react-statics": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dnd-html5-backend": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
|
||||
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dnd-core": "^16.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
@@ -4218,6 +4287,15 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"react": "^19.0.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-cookie": "^8.0.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-router-dom": "^7.5.3"
|
||||
|
||||
@@ -135,5 +135,10 @@
|
||||
"itemVolumeDistribution": "Verteilung der Artikelmenge",
|
||||
"itemRevenueDistribution": "Verteilung des Artikelumsatzes",
|
||||
"stockFulfillment": "Bestandserfüllung",
|
||||
"orderStatus": "Bestellstatus"
|
||||
"orderStatus": "Bestellstatus",
|
||||
"ORDERED": "Bestellt",
|
||||
"IN_PROGRESS": "In Versand",
|
||||
"ISSUES": "Probleme",
|
||||
"DELIVERED": "Zugesendet",
|
||||
"CANCELLED": "Storniert"
|
||||
}
|
||||
@@ -135,5 +135,10 @@
|
||||
"itemVolumeDistribution": "Item Volume Distribution",
|
||||
"itemRevenueDistribution": "Item Revenue Distribution",
|
||||
"stockFulfillment": "Stock fulfillment",
|
||||
"orderStatus": "Order Status"
|
||||
"orderStatus": "Order Status",
|
||||
"ORDERED": "Ordered",
|
||||
"IN_PROGRESS": "In Progress",
|
||||
"ISSUES": "Issues",
|
||||
"DELIVERED": "Delivered",
|
||||
"CANCELLED": "Cancelled"
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
export enum OrderStatusEnum {
|
||||
CANCELLED = 'CANCELLED',
|
||||
ISSUES = 'ISSUES',
|
||||
DELIVERED = 'DELIVERED',
|
||||
ORDERED = 'ORDERED',
|
||||
IN_PROGRESS = 'IN_PROGRESS',
|
||||
ISSUES = 'ISSUES',
|
||||
DELIVERED = 'DELIVERED',
|
||||
CANCELLED = 'CANCELLED',
|
||||
};
|
||||
|
||||
type OrderType = {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import { Box, useTheme } from "@mui/material";
|
||||
import { ReactNode } from "react";
|
||||
import { OrderStatusEnum } from "../../components/Order";
|
||||
|
||||
type DroppableContainerProps = {
|
||||
id: OrderStatusEnum;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function DroppableContainer({ id, children }: DroppableContainerProps) {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={setNodeRef}
|
||||
sx={{
|
||||
flex: 1,
|
||||
minHeight: 400,
|
||||
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,65 +1,27 @@
|
||||
import { closestCenter, DndContext } from "@dnd-kit/core";
|
||||
import { restrictToParentElement } from "@dnd-kit/modifiers";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
List, ListItemText,
|
||||
Typography,
|
||||
useTheme
|
||||
useTheme,
|
||||
Card, CardContent,
|
||||
Stack,
|
||||
Divider,
|
||||
Snackbar
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import React, { useState, useEffect, PropsWithChildren } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DroppableContainer } from "./DroppableContainer";
|
||||
import SortableItem from "./SortableItem";
|
||||
import OrderType, { OrderStatusEnum } from "../../components/Order";
|
||||
|
||||
const mockOrders: OrderType[] = [
|
||||
{
|
||||
id: "1001",
|
||||
date: "2025-05-20",
|
||||
status: OrderStatusEnum.CANCELLED,
|
||||
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: OrderStatusEnum.ISSUES,
|
||||
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: OrderStatusEnum.DELIVERED,
|
||||
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: OrderStatusEnum.ORDERED,
|
||||
items: [{ name: "Blumentopf", quantity: 2, price: 5.99 }],
|
||||
total: 11.98,
|
||||
address: "Musterstraße 1, 12345 Musterstadt"
|
||||
},
|
||||
{
|
||||
id: "1004",
|
||||
date: "2025-05-18",
|
||||
status: OrderStatusEnum.IN_PROGRESS,
|
||||
items: [{ name: "TimWall", quantity: 2, price: 5.99 }],
|
||||
total: 12.99,
|
||||
address: "Musterstraße 1, 12345 Musterstadt"
|
||||
}
|
||||
];
|
||||
import { useDrag, useDrop, DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchOrdersAdmin, orderPatch } from "../query/Queries";
|
||||
import { useAccount } from "../AccountProvider";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
// The order in which the statuses are displayed
|
||||
const statusOrder: OrderStatusEnum[] = [
|
||||
@@ -70,130 +32,175 @@ const statusOrder: OrderStatusEnum[] = [
|
||||
OrderStatusEnum.DELIVERED
|
||||
];
|
||||
|
||||
// Main component for managing orders
|
||||
export default function OrdersInfo() {
|
||||
|
||||
const OrderCard: React.FC<{ order: OrderType; onClick: () => void }> = ({ order, onClick }) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [orders, setOrders] = useState<OrderType[]>(mockOrders);
|
||||
const [selectedOrder, setSelectedOrder] = useState<OrderType | null>(null);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleDragEnd = (event: any) => {
|
||||
const { active, over } = event;
|
||||
if (!over || active.id === over.id) return;
|
||||
|
||||
const newStatus = over.id;
|
||||
setOrders((prev) =>
|
||||
prev.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((prev) =>
|
||||
prev.map((o) =>
|
||||
o.id === order.id
|
||||
? { ...o, status: statusOrder[currentIndex + 1] as OrderType["status"] }
|
||||
: o
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderOrders = (status: OrderStatusEnum) => {
|
||||
const filtered = orders.filter((o) => o.status === status);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: 300,
|
||||
p: 2,
|
||||
bgcolor: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: 2
|
||||
}}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
align="center"
|
||||
gutterBottom
|
||||
sx={{ color: theme.palette.text.primary }}>
|
||||
{t(status.toString())}
|
||||
</Typography>
|
||||
{filtered.map((order) => (
|
||||
<SortableItem
|
||||
key={order.id}
|
||||
id={order.id}
|
||||
order={order}
|
||||
onClick={() => setSelectedOrder(order)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const [{ isDragging }, drag] = useDrag(() => ({
|
||||
type: 'order',
|
||||
item: { id: order.id },
|
||||
collect: (monitor) => ({
|
||||
isDragging: !!monitor.isDragging(),
|
||||
}),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4" align="center" gutterBottom>
|
||||
{t("Orders Management")} {/* Bestellverwaltung */}
|
||||
</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 open={!!selectedOrder} onClose={() => setSelectedOrder(null)}>
|
||||
<DialogTitle>{t("orderDetails")}</DialogTitle> {/* Bestelldetails */}
|
||||
<DialogContent sx={{ bgcolor: theme.palette.background.paper }}>
|
||||
{selectedOrder && (
|
||||
<Box sx={{ color: theme.palette.text.primary }}>
|
||||
<Typography variant="body1">
|
||||
<strong>{t("orderId")}:</strong> {selectedOrder.id}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>{t("date")}:</strong> {selectedOrder.date}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
<strong>{t("address")}:</strong> {selectedOrder.address}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mt: 2 }}>
|
||||
<strong>{t("items")}:</strong>
|
||||
</Typography>
|
||||
{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 }}>
|
||||
<strong>{t("total")}:</strong> {selectedOrder.total.toFixed(2)} €
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ mt: 2 }}
|
||||
onClick={() => {
|
||||
handleNextStatus(selectedOrder);
|
||||
setSelectedOrder(null);
|
||||
}}>
|
||||
{t("moveToNextStatus")} {/* Zum nächsten Status bewegen */}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Box>
|
||||
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1, marginBottom: 8 }}>
|
||||
<Card elevation={4}>
|
||||
<CardContent onClick={onClick}>
|
||||
<Typography gutterBottom variant="h5" component="div">
|
||||
Order: {order.id}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{t('date') + ": " + new Date(order.time).toUTCString()}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{t('total') + ": " + order.total}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Column: React.FC<PropsWithChildren<{ status: OrderStatusEnum; onDrop: (id: number, status: OrderStatusEnum) => void }>> = ({ status, onDrop, children }) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
const [{ isOver }, drop] = useDrop(() => ({
|
||||
accept: 'order',
|
||||
drop: (item: { id: number }) => onDrop(item.id, status),
|
||||
collect: (monitor) => ({
|
||||
isOver: !!monitor.isOver(),
|
||||
}),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div ref={drop} style={{ flex: 1, backgroundColor: isOver ? theme.palette.background.paper : 'transparent', padding: 3, minHeight: 200 }}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6">{t(status)}</Typography>
|
||||
{children}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const EditOrder: React.FC<{ open: boolean; order: OrderType | null; onClose: () => void}> = ({ open, order, onClose }) => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
if(order === null)
|
||||
return "";
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>
|
||||
{`${t(order.status)} #${order?.id}`}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{order && (
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="subtitle1">{`${t('orderDate')}: ${new Date(order.time).toDateString()}`}</Typography>
|
||||
<Divider />
|
||||
<Typography variant="subtitle2">{t('orderedItems')}:</Typography>
|
||||
<List dense>
|
||||
{order.orderItems.map((item, idx) => (
|
||||
<ListItemText
|
||||
key={idx}
|
||||
primary={`${item.article} x${item.amount}`}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
<Typography variant="h6">{`${t('sum')}: ${(order.total/100).toFixed(2)} €`}</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>{t('close')}</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
// Main component for managing orders
|
||||
export default function OrdersInfo() {
|
||||
const [orders, setOrders] = useState<OrderType[]>([]);
|
||||
const [editOrder, setEditOrder] = useState<OrderType | null>(null);
|
||||
const [openSnackbar, setOpenSnackbar] = useState(false);
|
||||
|
||||
const {user: loginData} = useAccount();
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ["fetchOrdersAdmin", loginData],
|
||||
queryFn: () => fetchOrdersAdmin(loginData? loginData : {email: "", password: "", session: "", customerId: -1, isAdmin: false}),
|
||||
retry: 3,
|
||||
retryDelay: 1000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log("data", data);
|
||||
if (data) {
|
||||
setOrders(data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
|
||||
const handleDrop = async (id: number, status: OrderStatusEnum) => {
|
||||
if(orders.length < 1) {
|
||||
const resp = await refetch();
|
||||
setOrders(resp.data); // i dont know
|
||||
}
|
||||
const obj = orders.find((o) => {
|
||||
return o.id === id
|
||||
})
|
||||
if(!obj) {
|
||||
setOpenSnackbar(true);
|
||||
return;
|
||||
}
|
||||
obj.status = status
|
||||
const resp = await queryClient.fetchQuery({
|
||||
queryKey: ["orderPatch", {id: obj.id, status: obj.status}],
|
||||
queryFn: () => orderPatch({id: obj.id, status: obj.status}),
|
||||
retry: 0,
|
||||
retryDelay: 1000,
|
||||
})
|
||||
refetch();
|
||||
setOrders(orders.map((o) => (o.id === id ? obj : o )));
|
||||
};
|
||||
|
||||
const handleEdit = (order: OrderType) => setEditOrder(order);
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
{statusOrder.map((status) => (
|
||||
<Column key={status} status={status} onDrop={handleDrop}>
|
||||
{orders
|
||||
.filter((o) => o.status === status)
|
||||
.map((o) => (
|
||||
<OrderCard key={o.id} order={o} onClick={() => handleEdit(o)} />
|
||||
))}
|
||||
</Column>
|
||||
))}
|
||||
</div>
|
||||
<EditOrder
|
||||
open={!!editOrder}
|
||||
order={editOrder}
|
||||
onClose={() => setEditOrder(null)}
|
||||
/>
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
autoHideDuration={4000}
|
||||
onClose={() => setOpenSnackbar(false)}
|
||||
message="Failed changing Orderstatus"
|
||||
/>
|
||||
</DndProvider>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Paper, Typography, useTheme } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
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 theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
cursor: "grab",
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
onClick={onClick}
|
||||
sx={{
|
||||
p: 2,
|
||||
mb: 2,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
theme.palette.mode === "dark"
|
||||
? "rgba(255, 255, 255, 0.05)"
|
||||
: "rgba(0, 0, 0, 0.04)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
{t("orderId")}: {order.id}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{t("total")}: {order.total.toFixed(2)} €
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -136,7 +136,6 @@ export default function StatisticsInfo() {
|
||||
|
||||
useEffect(() => {
|
||||
if(dataStockPercent) {
|
||||
console.log(dataStockPercent);
|
||||
const stockPercent = []
|
||||
let i = 0
|
||||
for(let x = 0; x < 10; x++) {
|
||||
|
||||
@@ -232,3 +232,12 @@ export const fetchStockPercent = async (loginData: User) => {
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
|
||||
export const fetchOrdersAdmin = async (loginData: User) => {
|
||||
const response = await fetch("http://localhost:8085/order/all/all?email=" + loginData.email + "&session=" + loginData.session);
|
||||
if (!response.ok) {
|
||||
throw new Error("fetching admin orders failed");
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@@ -83,7 +83,7 @@ export default function Orders() {
|
||||
<ListItemButton key={order.id} onClick={() => setSelectedOrder(order)}>
|
||||
<ListItemText
|
||||
primary={`${t('order')} #${order.id} • ${order.status} • ${order.time}`}
|
||||
secondary={`${t('sum')}: ${order.total.toFixed(2)} € • ${order.orderItems.length} ${t('items')}`}
|
||||
secondary={`${t('sum')}: ${(order.total/100).toFixed(2)} € • ${order.orderItems.length} ${t('items')}`}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user