semi working order info admin page
This commit is contained in:
@@ -41,6 +41,7 @@ public class ControllerPathConfig {
|
|||||||
//OrderController
|
//OrderController
|
||||||
public static final String ORDER_BASE = "/order";
|
public static final String ORDER_BASE = "/order";
|
||||||
public static final String ORDER_GET_ALL = ORDER_BASE + "/all";
|
public static final String ORDER_GET_ALL = ORDER_BASE + "/all";
|
||||||
|
public static final String ORDER_GET_ALL_ADMIN = ORDER_BASE + "/all/all";
|
||||||
|
|
||||||
//ReviewController
|
//ReviewController
|
||||||
public static final String REVIEW_BASE = "/review";
|
public static final String REVIEW_BASE = "/review";
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static de.htwsaar.webshop.config.ControllerPathConfig.ORDER_BASE;
|
import static de.htwsaar.webshop.config.ControllerPathConfig.*;
|
||||||
import static de.htwsaar.webshop.config.ControllerPathConfig.ORDER_GET_ALL;
|
|
||||||
import static de.htwsaar.webshop.config.ParameterConfig.*;
|
import static de.htwsaar.webshop.config.ParameterConfig.*;
|
||||||
import static de.htwsaar.webshop.util.LoggerUtil.logRequest;
|
import static de.htwsaar.webshop.util.LoggerUtil.logRequest;
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ public class OrderController {
|
|||||||
return ResponseEntity.ok(orders.stream().map(Order::toModel).toList());
|
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,
|
public ResponseEntity<List<OrderModel>> getAll(HttpServletRequest request,
|
||||||
@RequestParam(value = PARAM_EMAIL) String email,
|
@RequestParam(value = PARAM_EMAIL) String email,
|
||||||
@RequestParam(value = PARAM_SESSION) UUID token) {
|
@RequestParam(value = PARAM_SESSION) UUID token) {
|
||||||
@@ -92,10 +91,16 @@ public class OrderController {
|
|||||||
@RequestParam(value = PARAM_STATUS) OrderStatus status) {
|
@RequestParam(value = PARAM_STATUS) OrderStatus status) {
|
||||||
logRequest(request);
|
logRequest(request);
|
||||||
if (orderId == null) {
|
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();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
Order order = orderService.getOrderById(orderId);
|
Order order = orderService.getOrderById(orderId);
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
|
log.info("[{}] failed to update orderID {}", request.getRequestURI(), orderId);
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
order.setStatus(status);
|
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": "^19.0.0",
|
||||||
"react-chartjs-2": "^5.3.0",
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-cookie": "^8.0.1",
|
"react-cookie": "^8.0.1",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
"react-router-dom": "^7.5.3"
|
"react-router-dom": "^7.5.3"
|
||||||
@@ -1693,6 +1695,24 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"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": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.11",
|
"version": "1.0.0-beta.11",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.11.tgz",
|
||||||
@@ -2933,6 +2953,17 @@
|
|||||||
"robust-predicates": "^3.0.2"
|
"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": {
|
"node_modules/dom-helpers": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||||
@@ -3204,7 +3235,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
@@ -4110,6 +4140,45 @@
|
|||||||
"react": ">= 16.3.0"
|
"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": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
@@ -4218,6 +4287,15 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"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": {
|
"node_modules/reselect": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-chartjs-2": "^5.3.0",
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-cookie": "^8.0.1",
|
"react-cookie": "^8.0.1",
|
||||||
|
"react-dnd": "^16.0.1",
|
||||||
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-i18next": "^15.5.1",
|
"react-i18next": "^15.5.1",
|
||||||
"react-router-dom": "^7.5.3"
|
"react-router-dom": "^7.5.3"
|
||||||
|
|||||||
@@ -135,5 +135,10 @@
|
|||||||
"itemVolumeDistribution": "Verteilung der Artikelmenge",
|
"itemVolumeDistribution": "Verteilung der Artikelmenge",
|
||||||
"itemRevenueDistribution": "Verteilung des Artikelumsatzes",
|
"itemRevenueDistribution": "Verteilung des Artikelumsatzes",
|
||||||
"stockFulfillment": "Bestandserfüllung",
|
"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",
|
"itemVolumeDistribution": "Item Volume Distribution",
|
||||||
"itemRevenueDistribution": "Item Revenue Distribution",
|
"itemRevenueDistribution": "Item Revenue Distribution",
|
||||||
"stockFulfillment": "Stock fulfillment",
|
"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 {
|
export enum OrderStatusEnum {
|
||||||
CANCELLED = 'CANCELLED',
|
|
||||||
ISSUES = 'ISSUES',
|
|
||||||
DELIVERED = 'DELIVERED',
|
|
||||||
ORDERED = 'ORDERED',
|
ORDERED = 'ORDERED',
|
||||||
IN_PROGRESS = 'IN_PROGRESS',
|
IN_PROGRESS = 'IN_PROGRESS',
|
||||||
|
ISSUES = 'ISSUES',
|
||||||
|
DELIVERED = 'DELIVERED',
|
||||||
|
CANCELLED = 'CANCELLED',
|
||||||
};
|
};
|
||||||
|
|
||||||
type OrderType = {
|
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 {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
DialogActions,
|
||||||
|
List, ListItemText,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme,
|
||||||
|
Card, CardContent,
|
||||||
|
Stack,
|
||||||
|
Divider,
|
||||||
|
Snackbar
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useState } from "react";
|
import React, { useState, useEffect, PropsWithChildren } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DroppableContainer } from "./DroppableContainer";
|
|
||||||
import SortableItem from "./SortableItem";
|
|
||||||
import OrderType, { OrderStatusEnum } from "../../components/Order";
|
import OrderType, { OrderStatusEnum } from "../../components/Order";
|
||||||
|
import { useDrag, useDrop, DndProvider } from 'react-dnd';
|
||||||
const mockOrders: OrderType[] = [
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
{
|
import { useQuery } from "@tanstack/react-query";
|
||||||
id: "1001",
|
import { fetchOrdersAdmin, orderPatch } from "../query/Queries";
|
||||||
date: "2025-05-20",
|
import { useAccount } from "../AccountProvider";
|
||||||
status: OrderStatusEnum.CANCELLED,
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
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"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// The order in which the statuses are displayed
|
// The order in which the statuses are displayed
|
||||||
const statusOrder: OrderStatusEnum[] = [
|
const statusOrder: OrderStatusEnum[] = [
|
||||||
@@ -70,130 +32,175 @@ const statusOrder: OrderStatusEnum[] = [
|
|||||||
OrderStatusEnum.DELIVERED
|
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 { t } = useTranslation();
|
||||||
const theme = useTheme();
|
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 [{ isDragging }, drag] = useDrag(() => ({
|
||||||
const handleDragEnd = (event: any) => {
|
type: 'order',
|
||||||
const { active, over } = event;
|
item: { id: order.id },
|
||||||
if (!over || active.id === over.id) return;
|
collect: (monitor) => ({
|
||||||
|
isDragging: !!monitor.isDragging(),
|
||||||
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 (
|
return (
|
||||||
<Box
|
<div ref={drag} style={{ opacity: isDragging ? 0.5 : 1, marginBottom: 8 }}>
|
||||||
sx={{
|
<Card elevation={4}>
|
||||||
minHeight: 300,
|
<CardContent onClick={onClick}>
|
||||||
p: 2,
|
<Typography gutterBottom variant="h5" component="div">
|
||||||
bgcolor: theme.palette.background.paper,
|
Order: {order.id}
|
||||||
border: `1px solid ${theme.palette.divider}`,
|
|
||||||
borderRadius: 2
|
|
||||||
}}>
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
align="center"
|
|
||||||
gutterBottom
|
|
||||||
sx={{ color: theme.palette.text.primary }}>
|
|
||||||
{t(status.toString())}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
{filtered.map((order) => (
|
<Typography>
|
||||||
<SortableItem
|
{t('date') + ": " + new Date(order.time).toUTCString()}
|
||||||
key={order.id}
|
</Typography>
|
||||||
id={order.id}
|
<Typography>
|
||||||
order={order}
|
{t('total') + ": " + order.total}
|
||||||
onClick={() => setSelectedOrder(order)}
|
</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}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</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 (
|
return (
|
||||||
<Box>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Typography variant="h4" align="center" gutterBottom>
|
<div style={{ display: 'flex', gap: 16 }}>
|
||||||
{t("Orders Management")} {/* Bestellverwaltung */}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<DndContext
|
|
||||||
collisionDetection={closestCenter}
|
|
||||||
onDragEnd={handleDragEnd}
|
|
||||||
modifiers={[restrictToParentElement]}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", gap: 2 }}>
|
|
||||||
{statusOrder.map((status) => (
|
{statusOrder.map((status) => (
|
||||||
<DroppableContainer key={status} id={status}>
|
<Column key={status} status={status} onDrop={handleDrop}>
|
||||||
{renderOrders(status)}
|
{orders
|
||||||
</DroppableContainer>
|
.filter((o) => o.status === status)
|
||||||
|
.map((o) => (
|
||||||
|
<OrderCard key={o.id} order={o} onClick={() => handleEdit(o)} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Column>
|
||||||
</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 }}>
|
</div>
|
||||||
<strong>{t("total")}:</strong> {selectedOrder.total.toFixed(2)} €
|
<EditOrder
|
||||||
</Typography>
|
open={!!editOrder}
|
||||||
<Button
|
order={editOrder}
|
||||||
variant="contained"
|
onClose={() => setEditOrder(null)}
|
||||||
color="primary"
|
/>
|
||||||
sx={{ mt: 2 }}
|
<Snackbar
|
||||||
onClick={() => {
|
open={openSnackbar}
|
||||||
handleNextStatus(selectedOrder);
|
autoHideDuration={4000}
|
||||||
setSelectedOrder(null);
|
onClose={() => setOpenSnackbar(false)}
|
||||||
}}>
|
message="Failed changing Orderstatus"
|
||||||
{t("moveToNextStatus")} {/* Zum nächsten Status bewegen */}
|
/>
|
||||||
</Button>
|
</DndProvider>
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(() => {
|
useEffect(() => {
|
||||||
if(dataStockPercent) {
|
if(dataStockPercent) {
|
||||||
console.log(dataStockPercent);
|
|
||||||
const stockPercent = []
|
const stockPercent = []
|
||||||
let i = 0
|
let i = 0
|
||||||
for(let x = 0; x < 10; x++) {
|
for(let x = 0; x < 10; x++) {
|
||||||
|
|||||||
@@ -232,3 +232,12 @@ export const fetchStockPercent = async (loginData: User) => {
|
|||||||
}
|
}
|
||||||
return response.json();
|
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)}>
|
<ListItemButton key={order.id} onClick={() => setSelectedOrder(order)}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={`${t('order')} #${order.id} • ${order.status} • ${order.time}`}
|
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>
|
</ListItemButton>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user