add OrderStatus statistic, stock fullfilment dummy

This commit is contained in:
Tim
2025-06-16 00:29:25 +02:00
parent 48baf47d10
commit 838498227c
8 changed files with 119 additions and 20 deletions

View File

@@ -49,5 +49,6 @@ public class ControllerPathConfig {
private static final String STATISTICS_BASE = "/statistics";
public static final String STATISTICS_VOLUME = STATISTICS_BASE + "/volume";
public static final String STATISTICS_REVENUE = STATISTICS_BASE + "/revenue";
public static final String STATISTICS_ORDERSTATUS = STATISTICS_BASE + "/orderstatus";
}

View File

@@ -1,6 +1,7 @@
package de.htwsaar.webshop.controller;
import de.htwsaar.webshop.model.CatMonthModel;
import de.htwsaar.webshop.model.OrderStatus;
import de.htwsaar.webshop.service.SessionService;
import de.htwsaar.webshop.service.StatisticsService;
import jakarta.servlet.http.HttpServletRequest;
@@ -11,10 +12,10 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.UUID;
import static de.htwsaar.webshop.config.ControllerPathConfig.STATISTICS_REVENUE;
import static de.htwsaar.webshop.config.ControllerPathConfig.STATISTICS_VOLUME;
import static de.htwsaar.webshop.config.ControllerPathConfig.*;
import static de.htwsaar.webshop.config.ParameterConfig.PARAM_EMAIL;
import static de.htwsaar.webshop.config.ParameterConfig.PARAM_SESSION;
import static de.htwsaar.webshop.util.LoggerUtil.logRequest;
@@ -55,4 +56,16 @@ public class StatisticsController {
return ResponseEntity.ok(statisticsService.getSalesRevenue());
}
@RequestMapping(value = STATISTICS_ORDERSTATUS, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<Map<OrderStatus, Integer>> getOrderStatus(HttpServletRequest request,
@RequestParam(value = PARAM_SESSION) UUID token,
@RequestParam(value = PARAM_EMAIL) String email) {
logRequest(request);
if (!sessionService.isAdmin(token, email)) {
log.warn("Invalid session requesting Admin {}", token);
return ResponseEntity.status(403).build();
}
return ResponseEntity.ok(statisticsService.getOrderStatus());
}
}

View File

@@ -1,10 +1,15 @@
package de.htwsaar.webshop.service;
import de.htwsaar.webshop.model.CatMonthModel;
import de.htwsaar.webshop.model.OrderStatus;
import java.util.Map;
public interface StatisticsService {
CatMonthModel<Integer> getSalesVolume();
CatMonthModel<Integer> getSalesRevenue();
Map<OrderStatus, Integer> getOrderStatus();
}

View File

@@ -2,6 +2,7 @@ package de.htwsaar.webshop.service.impl;
import de.htwsaar.webshop.model.ArticleCategory;
import de.htwsaar.webshop.model.CatMonthModel;
import de.htwsaar.webshop.model.OrderStatus;
import de.htwsaar.webshop.repository.entities.OrderItem;
import de.htwsaar.webshop.service.OrderService;
import de.htwsaar.webshop.service.StatisticsService;
@@ -57,4 +58,14 @@ public class StatisticsServiceImpl implements StatisticsService {
public CatMonthModel<Integer> getSalesRevenue() {
return getMonthCategoryMap(item -> item.getArticle().getPrice100(), Integer::sum, 0);
}
@Override
public Map<OrderStatus, Integer> getOrderStatus() {
Map<OrderStatus, Integer> map = new TreeMap<>();
orderService.findAll().forEach(order -> {
map.putIfAbsent(order.getStatus(), 0);
map.computeIfPresent(order.getStatus(), (k,v) -> ++v);
});
return map;
}
}

View File

@@ -5,8 +5,9 @@ import { BarSeriesType } from '@mui/x-charts'
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useQuery } from "@tanstack/react-query";
import { fetchStatisticsVolume, fetchStatisticsRevenue } from "../query/Queries.tsx";
import { fetchStatisticsVolume, fetchStatisticsRevenue, fetchOrderStatus } from "../query/Queries.tsx";
import { useAccount } from "../AccountProvider.tsx";
import { data } from "react-router-dom";
export default function StatisticsInfo() {
const theme = useTheme();
@@ -14,16 +15,17 @@ export default function StatisticsInfo() {
const [monthlyVolume, setMonthlyVolume] = useState<BarSeriesType[]>([]);
const [monthlyVolumeXaxis, setMonthlyVolumeXaxis] = useState([{ data: [] }]);
const [totalVolume, setTotalVolume] = useState([{ data: [] }]);
const [totalVolume, setTotalVolume] = useState([]);
const [monthlyRevenue, setMonthlyRevenue] = useState<BarSeriesType[]>([]);
const [monthlyRevenueXaxis, setMonthlyRevenueXaxis] = useState([{ data: [] }]);
const [totalRevenue, setTotalRevenue] = useState([{ data: [] }]);
const [totalRevenue, setTotalRevenue] = useState([]);
const [orderStatus, setOrderStatus] = useState([]);
const { user: loginData } = useAccount();
const { data: dataVolume } = useQuery({
queryKey: ["fetchStatisticsVolume", loginData],
queryFn: () => fetchStatisticsVolume(loginData ? loginData : { email: "", password: "", session: "", customerId: -1, isAdmin: false }),
@@ -38,15 +40,23 @@ export default function StatisticsInfo() {
retryDelay: 0,
});
const { data: dataOrderStatus } = useQuery({
queryKey: ["fetchOrderStatus", loginData],
queryFn: () => fetchOrderStatus(loginData ? loginData : { email: "", password: "", session: "", customerId: -1, isAdmin: false }),
retry: 0,
retryDelay: 0,
});
useEffect(() => {
if (dataVolume) {
const cmm = []
const cmmx = monthlyVolumeXaxis
const tv = [{data:[]}]
const tv = []
let i = 0
for (const cat in dataVolume.catMonthMap) {
for (const timestamp in dataVolume.catMonthMap[cat]) {
console.log(i + "." + timestamp);
const date = new Date(parseInt(timestamp))
const formattedDate = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, '0')
if (!cmmx[0].data.includes(formattedDate)) {
@@ -58,10 +68,10 @@ export default function StatisticsInfo() {
}
cmm[i].data.push(datapoint)
if(tv[0].data.length == i) {
tv[0].data.push({ id: i, value: 0, label: t(cat)})
if(tv.length == i) {
tv.push({ id: i, value: 0, label: t(cat)})
}
tv[0].data[i].value += datapoint
tv[i].value += datapoint
}
i++;
}
@@ -76,7 +86,7 @@ export default function StatisticsInfo() {
if (dataRevenue) {
const cmm = []
const cmmx = monthlyRevenueXaxis
const tv = [{data:[]}]
const tv = []
let i = 0
for (const cat in dataRevenue.catMonthMap) {
for (const timestamp in dataRevenue.catMonthMap[cat]) {
@@ -85,16 +95,16 @@ export default function StatisticsInfo() {
if (!cmmx[0].data.includes(formattedDate)) {
cmmx[0].data.push(formattedDate);
}
const datapoint = dataRevenue.catMonthMap[cat][timestamp]
const datapoint = dataRevenue.catMonthMap[cat][timestamp] / 100
if(cmm.length == i) {
cmm.push({ id: i, data: [], label: t(cat), type: "bar" })
}
cmm[i].data.push(datapoint)
if(tv[0].data.length == i) {
tv[0].data.push({ id: i, value: 0, label: t(cat)})
if(tv.length == i) {
tv.push({ id: i, value: 0, label: t(cat)})
}
tv[0].data[i].value += datapoint
tv[i].value += datapoint
}
i++;
}
@@ -105,6 +115,16 @@ export default function StatisticsInfo() {
}
}, [dataRevenue]);
useEffect(() => {
if(dataOrderStatus) {
const orderStatus = []
for(var status in dataOrderStatus) {
orderStatus.push({value: dataOrderStatus[status], label: t(status)})
}
setOrderStatus(orderStatus)
}
}, [dataOrderStatus])
return (
<Box className="" sx={{ color: theme.palette.text.primary }}>
<Typography mt={4} variant="h4" align="center" gutterBottom>
@@ -132,27 +152,64 @@ export default function StatisticsInfo() {
/>
</Box>
<Box display={"flex"} mb={9}>
<Box sx={{ m: 2 }}>
<Box className="vw20" sx={{ m: 2 }}>
<Typography variant="h6" align="center" gutterBottom>
Item Volume Distribution
</Typography>
<PieChart
series={totalVolume}
series={[{
data: totalVolume,
highlightScope: { fade: 'global', highlight: 'item' },
faded: { innerRadius: 30, additionalRadius: -30, color: 'gray' },
}]}
width={200}
height={200}
/>
</Box>
<Box sx={{ m: 2 }}>
<Box className="vw20" sx={{ m: 2 }}>
<Typography variant="h6" align="center" gutterBottom>
Item Revenue Distribution
</Typography>
<PieChart
series={totalRevenue}
series={[{
data: totalRevenue,
highlightScope: { fade: 'global', highlight: 'item' },
}]}
width={200}
height={200}
/>
</Box>
<Box className="vw20" sx={{ m: 2 }}>
<Typography variant="h6" align="center" gutterBottom>
Stock fulfillment
</Typography>
<PieChart
series={[{
data: totalRevenue,
}]}
width={200}
height={200}
/>
</Box>
<Box className="vw20" sx={{ m: 2 }}>
<Typography variant="h6" align="center" gutterBottom>
Order Status
</Typography>
<PieChart
series={[{
data: orderStatus,
innerRadius: 20,
outerRadius: 70,
cornerRadius: 5,
paddingAngle: 1,
highlightScope: { fade: 'global', highlight: 'item' },
faded: { innerRadius: 30, additionalRadius: -10, color: 'gray' },
}]}
height={200}
width={200}
/>
</Box>
</Box>
</Box>
);

View File

@@ -29,4 +29,8 @@
.rating-text-field{
background-color: whitesmoke;
}
.vw20 {
width: 20vw;
}

View File

@@ -206,4 +206,12 @@ export const orderPatch = async (order: OrderPatch) => {
throw new Error("Order patch failed");
}
return response.json();
};
export const fetchOrderStatus = async (loginData: User) => {
const response = await fetch("http://localhost:8085/statistics/orderstatus?email=" + loginData.email + "&session=" + loginData.session);
if (!response.ok) {
throw new Error("fetching satistics Revenue failed");
}
return response.json();
};