added overview & daily data
This commit is contained in:
parent
39b2da4a00
commit
3f2495f210
12 changed files with 505 additions and 28 deletions
55
admin_dashboard/client/package-lock.json
generated
55
admin_dashboard/client/package-lock.json
generated
|
@ -16,6 +16,7 @@
|
||||||
"@nivo/bar": "^0.85.1",
|
"@nivo/bar": "^0.85.1",
|
||||||
"@nivo/core": "^0.85.1",
|
"@nivo/core": "^0.85.1",
|
||||||
"@nivo/geo": "^0.85.1",
|
"@nivo/geo": "^0.85.1",
|
||||||
|
"@nivo/line": "^0.85.1",
|
||||||
"@nivo/pie": "^0.85.1",
|
"@nivo/pie": "^0.85.1",
|
||||||
"@reduxjs/toolkit": "^2.2.3",
|
"@reduxjs/toolkit": "^2.2.3",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
@ -3900,6 +3901,27 @@
|
||||||
"react": ">= 16.14.0 < 19.0.0"
|
"react": ">= 16.14.0 < 19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nivo/line": {
|
||||||
|
"version": "0.85.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.85.1.tgz",
|
||||||
|
"integrity": "sha512-BLswEMiBiFxpHaRoiKp7d3S4P3gzj0OYVBojFEEG+g19lmIEeTTc7aZsXz2pTz/NdzM6fwZqTD3llIhl6LfXFg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@nivo/annotations": "0.85.1",
|
||||||
|
"@nivo/axes": "0.85.1",
|
||||||
|
"@nivo/colors": "0.85.1",
|
||||||
|
"@nivo/core": "0.85.1",
|
||||||
|
"@nivo/legends": "0.85.1",
|
||||||
|
"@nivo/scales": "0.85.1",
|
||||||
|
"@nivo/tooltip": "0.85.1",
|
||||||
|
"@nivo/voronoi": "0.85.1",
|
||||||
|
"@react-spring/web": "9.4.5 || ^9.7.2",
|
||||||
|
"d3-shape": "^1.3.5",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.14.0 < 19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nivo/pie": {
|
"node_modules/@nivo/pie": {
|
||||||
"version": "0.85.1",
|
"version": "0.85.1",
|
||||||
"resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.85.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.85.1.tgz",
|
||||||
|
@ -3962,6 +3984,21 @@
|
||||||
"react": ">= 16.14.0 < 19.0.0"
|
"react": ">= 16.14.0 < 19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nivo/voronoi": {
|
||||||
|
"version": "0.85.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.85.1.tgz",
|
||||||
|
"integrity": "sha512-HJuc1Lhc7RhJyZCnn2eB1nqX6tsczUY4Z1YY3rl1Gy5HfW1vpoJZHQtWzelnvVcpj3qTrwI9QGLmDYE12HAeOQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@nivo/core": "0.85.1",
|
||||||
|
"@types/d3-delaunay": "^5.3.0",
|
||||||
|
"@types/d3-scale": "^4.0.8",
|
||||||
|
"d3-delaunay": "^5.3.0",
|
||||||
|
"d3-scale": "^4.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.14.0 < 19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
@ -4883,6 +4920,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/d3-delaunay": {
|
||||||
|
"version": "5.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-5.3.4.tgz",
|
||||||
|
"integrity": "sha512-GEQuDXVKQvHulQ+ecKyCubOmVjXrifAj7VR26rWVAER/IbWemaT/Tmo84ESiTtoDghg5ILdMZH7pYXQEt/Vu9A=="
|
||||||
|
},
|
||||||
"node_modules/@types/d3-format": {
|
"node_modules/@types/d3-format": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz",
|
||||||
|
@ -7714,6 +7756,14 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/d3-delaunay": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==",
|
||||||
|
"dependencies": {
|
||||||
|
"delaunator": "4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/d3-format": {
|
"node_modules/d3-format": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||||
|
@ -8015,6 +8065,11 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delaunator": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag=="
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"@nivo/bar": "^0.85.1",
|
"@nivo/bar": "^0.85.1",
|
||||||
"@nivo/core": "^0.85.1",
|
"@nivo/core": "^0.85.1",
|
||||||
"@nivo/geo": "^0.85.1",
|
"@nivo/geo": "^0.85.1",
|
||||||
|
"@nivo/line": "^0.85.1",
|
||||||
"@nivo/pie": "^0.85.1",
|
"@nivo/pie": "^0.85.1",
|
||||||
"@reduxjs/toolkit": "^2.2.3",
|
"@reduxjs/toolkit": "^2.2.3",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
|
|
@ -10,6 +10,8 @@ import Products from "pages/products/Products";
|
||||||
import Customers from "pages/customers/Customers";
|
import Customers from "pages/customers/Customers";
|
||||||
import Transactions from "pages/transactions/Transactions";
|
import Transactions from "pages/transactions/Transactions";
|
||||||
import Geography from "pages/geography/Geography";
|
import Geography from "pages/geography/Geography";
|
||||||
|
import Overview from "pages/overview/Overview";
|
||||||
|
import Daily from "pages/dailyStat/Daily";
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
@ -31,6 +33,8 @@ function App() {
|
||||||
<Route path="/customers" element={<Customers/>} />
|
<Route path="/customers" element={<Customers/>} />
|
||||||
<Route path="/transactions" element={<Transactions/>} />
|
<Route path="/transactions" element={<Transactions/>} />
|
||||||
<Route path="/geography" element={<Geography/>} />
|
<Route path="/geography" element={<Geography/>} />
|
||||||
|
<Route path="/overview" element={<Overview/>} />
|
||||||
|
<Route path="/daily" element={<Daily/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
157
admin_dashboard/client/src/components/OverviewChart.jsx
Normal file
157
admin_dashboard/client/src/components/OverviewChart.jsx
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import {ResponsiveLine} from "@nivo/line"
|
||||||
|
import { useTheme } from '@emotion/react'
|
||||||
|
import { useGetSalesQuery } from 'state/api';
|
||||||
|
|
||||||
|
|
||||||
|
const OverviewChart = ({ isDashboard = false, view }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const {data, isLoading} = useGetSalesQuery();
|
||||||
|
const [totalSalesLine, totalUnitsLine] = useMemo(() => {
|
||||||
|
if(!data) return [];
|
||||||
|
|
||||||
|
const { monthlyData } = data;
|
||||||
|
const totalSalesLine = {
|
||||||
|
id: "totalSales",
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const totalUnitsLine = {
|
||||||
|
id: "totalUnits",
|
||||||
|
color: theme.palette.secondary[600],
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
Object.values(monthlyData).reduce(
|
||||||
|
(acc, {month, totalSales, totalUnits}) => {
|
||||||
|
const curSales = acc.sales + totalSales;
|
||||||
|
const curUnits = acc.units + totalUnits;
|
||||||
|
|
||||||
|
totalSalesLine.data = [...totalSalesLine.data, { x: month, y: curSales },];
|
||||||
|
totalUnitsLine.data = [...totalUnitsLine.data, { x: month, y: curUnits },];
|
||||||
|
|
||||||
|
return {sales: curSales, units: curUnits};
|
||||||
|
},
|
||||||
|
{sales: 0, units: 0}
|
||||||
|
);
|
||||||
|
return [[totalSalesLine], [totalUnitsLine]];
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
if (!data || isLoading) return "Loading...";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveLine data={view === "sales" ? totalSalesLine : totalUnitsLine}
|
||||||
|
theme={{
|
||||||
|
axis: {
|
||||||
|
domain: {
|
||||||
|
line: {
|
||||||
|
stroke: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
line: {
|
||||||
|
stroke: theme.palette.secondary[200],
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legends: {
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
container: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
margin={{ top: 20, right: 50, bottom: 50, left: 70 }}
|
||||||
|
xScale={{ type: "point" }}
|
||||||
|
yScale={{
|
||||||
|
type: "linear",
|
||||||
|
min: "auto",
|
||||||
|
max: "auto",
|
||||||
|
stacked: false,
|
||||||
|
reverse: false,
|
||||||
|
}}
|
||||||
|
yFormat=" >-.2f"
|
||||||
|
curve="catmullRom"
|
||||||
|
enableArea={isDashboard}
|
||||||
|
axisTop={null}
|
||||||
|
axisRight={null}
|
||||||
|
axisBottom={{
|
||||||
|
format: (v) => {
|
||||||
|
if (isDashboard) return v.slice(0, 3);
|
||||||
|
return v;
|
||||||
|
},
|
||||||
|
orient: "bottom",
|
||||||
|
tickSize: 5,
|
||||||
|
tickPadding: 5,
|
||||||
|
tickRotation: 0,
|
||||||
|
legend: isDashboard ? "" : "Month",
|
||||||
|
legendOffset: 36,
|
||||||
|
legendPosition: "middle",
|
||||||
|
}}
|
||||||
|
axisLeft={{
|
||||||
|
orient: "left",
|
||||||
|
tickValues: 5,
|
||||||
|
tickSize: 5,
|
||||||
|
tickPadding: 5,
|
||||||
|
tickRotation: 0,
|
||||||
|
legend: isDashboard
|
||||||
|
? ""
|
||||||
|
: `Total ${view === "sales" ? "Revenue" : "Units"} for Year`,
|
||||||
|
legendOffset: -60,
|
||||||
|
legendPosition: "middle",
|
||||||
|
}}
|
||||||
|
enableGridX={true}
|
||||||
|
enableGridY={true}
|
||||||
|
pointSize={10}
|
||||||
|
pointColor={{ theme: "background" }}
|
||||||
|
pointBorderWidth={2}
|
||||||
|
pointBorderColor={{ from: "serieColor" }}
|
||||||
|
pointLabelYOffset={-12}
|
||||||
|
useMesh={true}
|
||||||
|
legends={
|
||||||
|
!isDashboard
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
anchor: "bottom-right",
|
||||||
|
direction: "column",
|
||||||
|
justify: false,
|
||||||
|
translateX: 30,
|
||||||
|
translateY: -40,
|
||||||
|
itemsSpacing: 0,
|
||||||
|
itemDirection: "left-to-right",
|
||||||
|
itemWidth: 80,
|
||||||
|
itemHeight: 20,
|
||||||
|
itemOpacity: 0.75,
|
||||||
|
symbolSize: 12,
|
||||||
|
symbolShape: "circle",
|
||||||
|
symbolBorderColor: "rgba(0, 0, 0, .5)",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
on: "hover",
|
||||||
|
style: {
|
||||||
|
itemBackground: "rgba(0, 0, 0, .03)",
|
||||||
|
itemOpacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OverviewChart
|
176
admin_dashboard/client/src/pages/dailyStat/Daily.jsx
Normal file
176
admin_dashboard/client/src/pages/dailyStat/Daily.jsx
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import { Box, useTheme } from '@mui/material'
|
||||||
|
import Header from 'components/Header'
|
||||||
|
import React, { useState, useMemo } from 'react'
|
||||||
|
import { useGetSalesQuery } from 'state/api';
|
||||||
|
import DatePicker from 'react-datepicker';
|
||||||
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
|
import { ResponsiveLine } from '@nivo/line';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const Daily = () => {
|
||||||
|
const [startDate, setStartDate] = useState(new Date("2021-02-01"));
|
||||||
|
const [endDate, setEndDate] = useState(new Date("2021-03-01"));
|
||||||
|
const { data } = useGetSalesQuery();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [formattedData] = useMemo(() => {
|
||||||
|
if (!data) return [];
|
||||||
|
|
||||||
|
const { dailyData } = data;
|
||||||
|
const totalSalesLine = {
|
||||||
|
id: "totalSales",
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const totalUnitsLine = {
|
||||||
|
id: "totalUnits",
|
||||||
|
color: theme.palette.secondary[600],
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.values(dailyData).forEach(({ date, totalSales, totalUnits }) => {
|
||||||
|
const dateFormatted = new Date(date);
|
||||||
|
if (dateFormatted >= startDate && dateFormatted <= endDate) {
|
||||||
|
const splitDate = date.substring(date.indexOf("-") + 1);
|
||||||
|
|
||||||
|
totalSalesLine.data = [
|
||||||
|
...totalSalesLine.data,
|
||||||
|
{ x: splitDate, y: totalSales },
|
||||||
|
];
|
||||||
|
totalUnitsLine.data = [
|
||||||
|
...totalUnitsLine.data,
|
||||||
|
{ x: splitDate, y: totalUnits },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedData = [totalSalesLine, totalUnitsLine];
|
||||||
|
return [formattedData];
|
||||||
|
}, [data, startDate, endDate]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box m="1.5rem 2.5rem">
|
||||||
|
<Header title="Daily sales" subtitle="Chart of daily sales"/>
|
||||||
|
<Box height="75vh">
|
||||||
|
<Box display="flex" justifyContent="flex-end">
|
||||||
|
<Box>
|
||||||
|
<DatePicker selected={startDate} onChange={(date) => setStartDate(date)} selectsStart startDate={startDate} endDate={endDate} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<DatePicker selected={endDate} onChange={(date) => setEndDate(date)} selectsEnd startDate={startDate} endDate={endDate} minDate={startDate} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{data ? (
|
||||||
|
<ResponsiveLine data={formattedData}
|
||||||
|
theme={{
|
||||||
|
axis: {
|
||||||
|
domain: {
|
||||||
|
line: {
|
||||||
|
stroke: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticks: {
|
||||||
|
line: {
|
||||||
|
stroke: theme.palette.secondary[200],
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legends: {
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
container: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
colors={{ datum: "color" }}
|
||||||
|
margin={{ top: 50, right: 50, bottom: 70, left: 60 }}
|
||||||
|
xScale={{ type: "point" }}
|
||||||
|
yScale={{
|
||||||
|
type: "linear",
|
||||||
|
min: "auto",
|
||||||
|
max: "auto",
|
||||||
|
stacked: false,
|
||||||
|
reverse: false,
|
||||||
|
}}
|
||||||
|
yFormat=" >-.2f"
|
||||||
|
curve="catmullRom"
|
||||||
|
axisTop={null}
|
||||||
|
axisRight={null}
|
||||||
|
axisBottom={{
|
||||||
|
orient: "bottom",
|
||||||
|
tickSize: 5,
|
||||||
|
tickPadding: 5,
|
||||||
|
tickRotation: 90,
|
||||||
|
legend: "Month",
|
||||||
|
legendOffset: 60,
|
||||||
|
legendPosition: "middle",
|
||||||
|
}}
|
||||||
|
axisLeft={{
|
||||||
|
orient: "left",
|
||||||
|
tickSize: 5,
|
||||||
|
tickPadding: 5,
|
||||||
|
tickRotation: 0,
|
||||||
|
legend: "Total",
|
||||||
|
legendOffset: -50,
|
||||||
|
legendPosition: "middle",
|
||||||
|
}}
|
||||||
|
enableGridX={false}
|
||||||
|
enableGridY={false}
|
||||||
|
pointSize={10}
|
||||||
|
pointColor={{ theme: "background" }}
|
||||||
|
pointBorderWidth={2}
|
||||||
|
pointBorderColor={{ from: "serieColor" }}
|
||||||
|
pointLabelYOffset={-12}
|
||||||
|
useMesh={true}
|
||||||
|
legends={[
|
||||||
|
{
|
||||||
|
anchor: "top-right",
|
||||||
|
direction: "column",
|
||||||
|
justify: false,
|
||||||
|
translateX: 50,
|
||||||
|
translateY: 0,
|
||||||
|
itemsSpacing: 0,
|
||||||
|
itemDirection: "left-to-right",
|
||||||
|
itemWidth: 80,
|
||||||
|
itemHeight: 20,
|
||||||
|
itemOpacity: 0.75,
|
||||||
|
symbolSize: 12,
|
||||||
|
symbolShape: "circle",
|
||||||
|
symbolBorderColor: "rgba(0, 0, 0, .5)",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
on: "hover",
|
||||||
|
style: {
|
||||||
|
itemBackground: "rgba(0, 0, 0, .03)",
|
||||||
|
itemOpacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>Loading...</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Daily
|
|
@ -17,38 +17,38 @@ const Geography = () => {
|
||||||
{data? (
|
{data? (
|
||||||
<ResponsiveChoropleth data={data}
|
<ResponsiveChoropleth data={data}
|
||||||
theme={{
|
theme={{
|
||||||
axis: {
|
axis: {
|
||||||
domain: {
|
domain: {
|
||||||
line: {
|
line: {
|
||||||
stroke: theme.palette.secondary[200],
|
stroke: theme.palette.secondary[200],
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
text: {
|
|
||||||
fill: theme.palette.secondary[200],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
line: {
|
|
||||||
stroke: theme.palette.secondary[200],
|
|
||||||
strokeWidth: 1,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
fill: theme.palette.secondary[200],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
legends: {
|
legend: {
|
||||||
text: {
|
text: {
|
||||||
fill: theme.palette.secondary[200],
|
fill: theme.palette.secondary[200],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tooltip: {
|
ticks: {
|
||||||
container: {
|
line: {
|
||||||
color: theme.palette.primary.main,
|
stroke: theme.palette.secondary[200],
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
|
legends: {
|
||||||
|
text: {
|
||||||
|
fill: theme.palette.secondary[200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
container: {
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
features={geographyData.features}
|
features={geographyData.features}
|
||||||
margin={{ top: 0, right: 0, bottom: 0, left: -50 }}
|
margin={{ top: 0, right: 0, bottom: 0, left: -50 }}
|
||||||
colors="YlGnBu"
|
colors="YlGnBu"
|
||||||
|
|
27
admin_dashboard/client/src/pages/overview/Overview.jsx
Normal file
27
admin_dashboard/client/src/pages/overview/Overview.jsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Box, FormControl, InputLabel, MenuItem, Select } from '@mui/material'
|
||||||
|
import Header from 'components/Header'
|
||||||
|
import OverviewChart from 'components/OverviewChart';
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
const Overview = () => {
|
||||||
|
const [view, setView] = useState("units");
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box m="1.5rem 2.5rem">
|
||||||
|
<Header title="Overview" subtitle="Overview of general revenue and profit"/>
|
||||||
|
<Box height="75vh" mt="2rem">
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel>View</InputLabel>
|
||||||
|
<Select label="View" value={view} onChange={(e) => setView(e.target.value)}>
|
||||||
|
<MenuItem value="sales">Sales</MenuItem>
|
||||||
|
<MenuItem value="units">Unit</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<OverviewChart view={view}/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Overview
|
|
@ -3,7 +3,7 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
|
||||||
export const api = createApi({
|
export const api = createApi({
|
||||||
baseQuery: fetchBaseQuery({baseUrl: process.env.REACT_APP_BASE_URL}),
|
baseQuery: fetchBaseQuery({baseUrl: process.env.REACT_APP_BASE_URL}),
|
||||||
reducerPath: "adminApi",
|
reducerPath: "adminApi",
|
||||||
tagTypes: ["User", "Products", "Customers", "Transactions", "Geography" ],
|
tagTypes: ["User", "Products", "Customers", "Transactions", "Geography", "Sales" ],
|
||||||
endpoints: (build) => ({
|
endpoints: (build) => ({
|
||||||
getUser: build.query({
|
getUser: build.query({
|
||||||
query: (id) => `general/user/${id}`,
|
query: (id) => `general/user/${id}`,
|
||||||
|
@ -28,8 +28,12 @@ export const api = createApi({
|
||||||
getGeography: build.query({
|
getGeography: build.query({
|
||||||
query: () => "client/geography",
|
query: () => "client/geography",
|
||||||
providesTags: ["Geography"],
|
providesTags: ["Geography"],
|
||||||
|
}),
|
||||||
|
getSales: build.query({
|
||||||
|
query: () => "sales/sales",
|
||||||
|
providesTags: ["Sales"]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
export const {useGetUserQuery, useGetProductsQuery, useGetCustomersQuery, useGetTransactionsQuery, useGetGeographyQuery} = api;
|
export const {useGetUserQuery, useGetProductsQuery, useGetCustomersQuery, useGetTransactionsQuery, useGetGeographyQuery, useGetSalesQuery} = api;
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Stat from "../models/Stat.js"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const getSales =async(req, res) => {
|
||||||
|
try {
|
||||||
|
const stats = await Stat.find();
|
||||||
|
res.status(200).json(stats[0]);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
res.status(404).json({message: error.message})
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ import generalRoutes from "./routes/general.js"
|
||||||
import managmentRoutes from "./routes/management.js"
|
import managmentRoutes from "./routes/management.js"
|
||||||
import saleRoutes from "./routes/sales.js"
|
import saleRoutes from "./routes/sales.js"
|
||||||
import User from "./models/User.js"
|
import User from "./models/User.js"
|
||||||
import {dataUser, dataProduct, dataProductStat, dataTransaction} from "./data/index.js"
|
import {dataUser, dataProduct, dataProductStat, dataTransaction, dataOverallStat} from "./data/index.js"
|
||||||
import Product from "./models/Product.js"
|
import Product from "./models/Product.js"
|
||||||
import ProductStat from "./models/ProductStat.js"
|
import ProductStat from "./models/ProductStat.js"
|
||||||
import Transaction from "./models/Transaction.js"
|
import Transaction from "./models/Transaction.js"
|
||||||
|
import Stat from "./models/Stat.js"
|
||||||
|
|
||||||
//configuration setup
|
//configuration setup
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
@ -48,6 +48,8 @@ mongoose.connect(process.env.MONGO_URL, {
|
||||||
//ProductStat.insertMany(dataProductStat);
|
//ProductStat.insertMany(dataProductStat);
|
||||||
//User.insertMany(dataUser);
|
//User.insertMany(dataUser);
|
||||||
//Transaction.insertMany(dataTransaction);
|
//Transaction.insertMany(dataTransaction);
|
||||||
|
//Stat.insertMany(dataOverallStat);
|
||||||
|
|
||||||
}).catch((error) => console.log(`${error} didn't connect`))
|
}).catch((error) => console.log(`${error} didn't connect`))
|
||||||
|
|
||||||
|
|
||||||
|
|
32
admin_dashboard/server/models/Stat.js
Normal file
32
admin_dashboard/server/models/Stat.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import mongoose from "mongoose"
|
||||||
|
|
||||||
|
const StatSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
totalCustomers: Number,
|
||||||
|
yaerlySalesTotal: Number,
|
||||||
|
yearlyTotalSoldUnits: Number,
|
||||||
|
yaer: Number,
|
||||||
|
monthlyData: [
|
||||||
|
{
|
||||||
|
month: String,
|
||||||
|
totalSales: Number,
|
||||||
|
totalUnits: Number,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dailyData: [
|
||||||
|
{
|
||||||
|
date: String,
|
||||||
|
totalSales: Number,
|
||||||
|
totalUnits: Number,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
salesByCategory:{
|
||||||
|
type: Map,
|
||||||
|
of: Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{timestamps: true}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Stat = mongoose.model("Stat", StatSchema);
|
||||||
|
export default Stat;
|
|
@ -1,5 +1,10 @@
|
||||||
import express from "express"
|
import express from "express"
|
||||||
|
import {getSales} from "../controllers/sales.js"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.get("/sales", getSales)
|
||||||
|
|
||||||
export default router;
|
export default router;
|
Loading…
Add table
Reference in a new issue