StatisticsInfo
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
package de.htwsaar.webshop.model;
|
||||||
|
|
||||||
|
public enum ArticleCategory {
|
||||||
|
GARDEN_SUPPLIES("gardenSupplies"),
|
||||||
|
OTHER("other"),
|
||||||
|
SEEDS("seeds"),
|
||||||
|
TECHNICAL_COMPONENTS("technicalComponents");
|
||||||
|
|
||||||
|
public final String loc;
|
||||||
|
|
||||||
|
ArticleCategory(String loc) {
|
||||||
|
this.loc = loc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,6 @@ public class OrderItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Integer getPrice() {
|
public Integer getPrice() {
|
||||||
return amount * article.getPrice100();
|
return amount * (article.getPrice100() * (100-article.getDiscount100()))/100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ public class OrderServiceImpl implements OrderService {
|
|||||||
while (months > 0) {
|
while (months > 0) {
|
||||||
upper = lower + TimeUtil.monthLength(lower);
|
upper = lower + TimeUtil.monthLength(lower);
|
||||||
List<Order> a = orderRepository.getOrdersByTimeBetween(lower, upper);
|
List<Order> a = orderRepository.getOrdersByTimeBetween(lower, upper);
|
||||||
log.info("Getting Orders from {} to {}: size {}", lower, upper, a.size());
|
|
||||||
orders.put(lower, a);
|
orders.put(lower, a);
|
||||||
lower = upper;
|
lower = upper;
|
||||||
months--;
|
months--;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.htwsaar.webshop.service.impl;
|
package de.htwsaar.webshop.service.impl;
|
||||||
|
|
||||||
|
import de.htwsaar.webshop.model.ArticleCategory;
|
||||||
import de.htwsaar.webshop.model.CatMonthModel;
|
import de.htwsaar.webshop.model.CatMonthModel;
|
||||||
import de.htwsaar.webshop.repository.entities.OrderItem;
|
import de.htwsaar.webshop.repository.entities.OrderItem;
|
||||||
import de.htwsaar.webshop.service.OrderService;
|
import de.htwsaar.webshop.service.OrderService;
|
||||||
@@ -9,8 +10,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.function.BinaryOperator;
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@@ -28,12 +29,17 @@ public class StatisticsServiceImpl implements StatisticsService {
|
|||||||
private <T extends Number> CatMonthModel<T> getMonthCategoryMap(Function<OrderItem, T> mappingFunction,
|
private <T extends Number> CatMonthModel<T> getMonthCategoryMap(Function<OrderItem, T> mappingFunction,
|
||||||
BinaryOperator<T> reduceFunction,
|
BinaryOperator<T> reduceFunction,
|
||||||
T defaultValue) {
|
T defaultValue) {
|
||||||
Map<String, Map<Long, T>> map = new HashMap<>();
|
Map<String, Map<Long, T>> map = new TreeMap<>();
|
||||||
|
for (ArticleCategory value : ArticleCategory.values()) {
|
||||||
|
map.put(value.loc, new TreeMap<>());
|
||||||
|
}
|
||||||
orderService.getTimeSortedOrders(TimeUtil.nowMonthsAgo(12), 12).forEach( (k,v) -> {
|
orderService.getTimeSortedOrders(TimeUtil.nowMonthsAgo(12), 12).forEach( (k,v) -> {
|
||||||
log.info("Month {} has {}", k, v.size());
|
for (ArticleCategory value : ArticleCategory.values()) {
|
||||||
|
map.get(value.loc).putIfAbsent(k, defaultValue);
|
||||||
|
}
|
||||||
v.forEach( (order) ->
|
v.forEach( (order) ->
|
||||||
order.getOrderItems().forEach((item) -> {
|
order.getOrderItems().forEach((item) -> {
|
||||||
map.putIfAbsent(item.getArticle().getCategory(), new HashMap<>());
|
map.putIfAbsent(item.getArticle().getCategory(), new TreeMap<>());
|
||||||
map.get(item.getArticle().getCategory()).putIfAbsent(k, defaultValue);
|
map.get(item.getArticle().getCategory()).putIfAbsent(k, defaultValue);
|
||||||
map.get(item.getArticle().getCategory()).computeIfPresent(k, (timestamp,left ) -> reduceFunction.apply(left, mappingFunction.apply(item)));
|
map.get(item.getArticle().getCategory()).computeIfPresent(k, (timestamp,left ) -> reduceFunction.apply(left, mappingFunction.apply(item)));
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,92 +1,158 @@
|
|||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
import { BarChart } from '@mui/x-charts/BarChart';
|
import { BarChart } from '@mui/x-charts/BarChart';
|
||||||
import { PieChart } from '@mui/x-charts/PieChart';
|
import { PieChart } from '@mui/x-charts/PieChart';
|
||||||
import {
|
import { BarSeriesType } from '@mui/x-charts'
|
||||||
ArcElement,
|
import { useEffect, useState } from "react";
|
||||||
BarElement,
|
|
||||||
CategoryScale,
|
|
||||||
Chart as ChartJS,
|
|
||||||
Legend,
|
|
||||||
LinearScale,
|
|
||||||
LineElement,
|
|
||||||
PointElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
} from "chart.js";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
// Chart.js registrieren
|
import { fetchStatisticsVolume, fetchStatisticsRevenue } from "../query/Queries.tsx";
|
||||||
ChartJS.register(
|
import { useAccount } from "../AccountProvider.tsx";
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
BarElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
ArcElement,
|
|
||||||
LineElement,
|
|
||||||
PointElement
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function StatisticsInfo() {
|
export default function StatisticsInfo() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const {t} = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [monthlyVolume, setMonthlyVolume] = useState<BarSeriesType[]>([]);
|
||||||
|
const [monthlyVolumeXaxis, setMonthlyVolumeXaxis] = useState([{ data: [] }]);
|
||||||
|
const [totalVolume, setTotalVolume] = useState([{ data: [] }]);
|
||||||
|
|
||||||
|
const [monthlyRevenue, setMonthlyRevenue] = useState<BarSeriesType[]>([]);
|
||||||
|
const [monthlyRevenueXaxis, setMonthlyRevenueXaxis] = useState([{ data: [] }]);
|
||||||
|
const [totalRevenue, setTotalRevenue] = useState([{ data: [] }]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { user: loginData } = useAccount();
|
||||||
|
|
||||||
|
const { data: dataVolume } = useQuery({
|
||||||
|
queryKey: ["fetchStatisticsVolume", loginData],
|
||||||
|
queryFn: () => fetchStatisticsVolume(loginData ? loginData : { email: "", password: "", session: "", customerId: -1, isAdmin: false }),
|
||||||
|
retry: 0,
|
||||||
|
retryDelay: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: dataRevenue } = useQuery({
|
||||||
|
queryKey: ["fetchStatisticsRevenue", loginData],
|
||||||
|
queryFn: () => fetchStatisticsRevenue(loginData ? loginData : { email: "", password: "", session: "", customerId: -1, isAdmin: false }),
|
||||||
|
retry: 0,
|
||||||
|
retryDelay: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataVolume) {
|
||||||
|
const cmm = []
|
||||||
|
const cmmx = monthlyVolumeXaxis
|
||||||
|
const tv = [{data:[]}]
|
||||||
|
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)) {
|
||||||
|
cmmx[0].data.push(formattedDate);
|
||||||
|
}
|
||||||
|
const datapoint = dataVolume.catMonthMap[cat][timestamp]
|
||||||
|
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)})
|
||||||
|
}
|
||||||
|
tv[0].data[i].value += datapoint
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMonthlyVolume(cmm)
|
||||||
|
setMonthlyVolumeXaxis(cmmx)
|
||||||
|
setTotalVolume(tv)
|
||||||
|
}
|
||||||
|
}, [dataVolume]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataRevenue) {
|
||||||
|
const cmm = []
|
||||||
|
const cmmx = monthlyRevenueXaxis
|
||||||
|
const tv = [{data:[]}]
|
||||||
|
let i = 0
|
||||||
|
for (const cat in dataRevenue.catMonthMap) {
|
||||||
|
for (const timestamp in dataRevenue.catMonthMap[cat]) {
|
||||||
|
const date = new Date(parseInt(timestamp))
|
||||||
|
const formattedDate = date.getFullYear() + "-" + String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
if (!cmmx[0].data.includes(formattedDate)) {
|
||||||
|
cmmx[0].data.push(formattedDate);
|
||||||
|
}
|
||||||
|
const datapoint = dataRevenue.catMonthMap[cat][timestamp]
|
||||||
|
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)})
|
||||||
|
}
|
||||||
|
tv[0].data[i].value += datapoint
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMonthlyRevenue(cmm)
|
||||||
|
setMonthlyRevenueXaxis(cmmx)
|
||||||
|
setTotalRevenue(tv)
|
||||||
|
}
|
||||||
|
}, [dataRevenue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ color: theme.palette.text.primary }}>
|
<Box className="" sx={{ color: theme.palette.text.primary }}>
|
||||||
<Typography variant="h4" align="center" gutterBottom>
|
<Typography mt={4} variant="h4" align="center" gutterBottom>
|
||||||
Sales Statistics
|
Sales Statistics
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ mb: 4 }}>
|
<Box sx={{ mb: 4 }}>
|
||||||
<Typography variant="h6" align="center" gutterBottom>
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
Monthly Sales in n
|
Monthly Sales Volume
|
||||||
</Typography>
|
</Typography>
|
||||||
<BarChart
|
<BarChart
|
||||||
series={[
|
series={monthlyVolume}
|
||||||
{ data: [35, 44, 24, 34], label: t('seeds') },
|
height={290}
|
||||||
{ data: [51, 6, 49, 30], label: t('technicalComponents') },
|
xAxis={monthlyVolumeXaxis}
|
||||||
{ data: [60, 50, 15, 25], label: t('gardenSupplies') },
|
|
||||||
{ data: [15, 25, 30, 50], label: t('other')},
|
|
||||||
]}
|
|
||||||
height={290}
|
|
||||||
xAxis={[{ data: ['2025-01', '2025-02', '2025-03', '2025-04'] }]}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ mb: 4 }}>
|
<Box sx={{ mb: 4 }}>
|
||||||
<Typography variant="h6" align="center" gutterBottom>
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
Monthly Revenue in €
|
Monthly Sales Revenue in €
|
||||||
</Typography>
|
</Typography>
|
||||||
<BarChart
|
<BarChart
|
||||||
series={[
|
series={monthlyRevenue}
|
||||||
{ data: [200, 244, 224, 234], label: t('seeds') },
|
height={290}
|
||||||
{ data: [888, 300, 600, 1200], label: t('technicalComponents') },
|
xAxis={monthlyRevenueXaxis}
|
||||||
{ data: [260, 150, 50, 0], label: t('gardenSupplies') },
|
|
||||||
{ data: [10, 20, 30, 50], label: t('other')},
|
|
||||||
]}
|
|
||||||
height={290}
|
|
||||||
xAxis={[{ data: ['2025-01', '2025-02', '2025-03', '2025-04'] }]}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box display={"flex"} mb={9}>
|
||||||
|
<Box sx={{ m: 2 }}>
|
||||||
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
|
Item Volume Distribution
|
||||||
|
</Typography>
|
||||||
|
<PieChart
|
||||||
|
series={totalVolume}
|
||||||
|
width={200}
|
||||||
|
height={200}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ mb: 4 }}>
|
<Box sx={{ m: 2 }}>
|
||||||
<Typography variant="h6" align="center" gutterBottom>
|
<Typography variant="h6" align="center" gutterBottom>
|
||||||
Item Sales Distribution
|
Item Revenue Distribution
|
||||||
</Typography>
|
</Typography>
|
||||||
<PieChart
|
<PieChart
|
||||||
series={[
|
series={totalRevenue}
|
||||||
{
|
width={200}
|
||||||
data: [
|
height={200}
|
||||||
{ id: 0, value: 10, label: t('seeds') },
|
/>
|
||||||
{ id: 1, value: 15, label: t('technicalComponents') },
|
</Box>
|
||||||
{ id: 2, value: 10, label: t('gardenSupplies') },
|
|
||||||
{ id: 2, value: 3, label: t('other') },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
width={200}
|
|
||||||
height={200}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -159,10 +159,26 @@ export const fetchAccounts = async (loginData: User) => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchItems = async (loginData: User) => {
|
export const fetchItems = async (loginData: User) => { //TODO: remove and use above
|
||||||
const response = await fetch("http://localhost:8085/article/all?email=" + loginData.email + "&session=" + loginData.session);
|
const response = await fetch("http://localhost:8085/article/all");
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Login failed");
|
throw new Error("fetching items failed");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchStatisticsVolume = async (loginData: User) => {
|
||||||
|
const response = await fetch("http://localhost:8085/statistics/volume?email=" + loginData.email + "&session=" + loginData.session);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("fetching satistics Volume failed");
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchStatisticsRevenue = async (loginData: User) => {
|
||||||
|
const response = await fetch("http://localhost:8085/statistics/revenue?email=" + loginData.email + "&session=" + loginData.session);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("fetching satistics Revenue failed");
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user