diff --git a/admin_dashboard/client/package-lock.json b/admin_dashboard/client/package-lock.json index 551f029..da47131 100644 --- a/admin_dashboard/client/package-lock.json +++ b/admin_dashboard/client/package-lock.json @@ -16,6 +16,7 @@ "@nivo/bar": "^0.85.1", "@nivo/core": "^0.85.1", "@nivo/geo": "^0.85.1", + "@nivo/line": "^0.85.1", "@nivo/pie": "^0.85.1", "@reduxjs/toolkit": "^2.2.3", "@testing-library/jest-dom": "^5.17.0", @@ -3900,6 +3901,27 @@ "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": { "version": "0.85.1", "resolved": "https://registry.npmjs.org/@nivo/pie/-/pie-0.85.1.tgz", @@ -3962,6 +3984,21 @@ "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": { "version": "2.1.5", "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", "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": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz", @@ -7714,6 +7756,14 @@ "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": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", @@ -8015,6 +8065,11 @@ "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", diff --git a/admin_dashboard/client/package.json b/admin_dashboard/client/package.json index 00030f6..4d8cc1c 100644 --- a/admin_dashboard/client/package.json +++ b/admin_dashboard/client/package.json @@ -11,6 +11,7 @@ "@nivo/bar": "^0.85.1", "@nivo/core": "^0.85.1", "@nivo/geo": "^0.85.1", + "@nivo/line": "^0.85.1", "@nivo/pie": "^0.85.1", "@reduxjs/toolkit": "^2.2.3", "@testing-library/jest-dom": "^5.17.0", diff --git a/admin_dashboard/client/src/App.js b/admin_dashboard/client/src/App.js index 4d002a6..37a96b3 100644 --- a/admin_dashboard/client/src/App.js +++ b/admin_dashboard/client/src/App.js @@ -10,6 +10,8 @@ import Products from "pages/products/Products"; import Customers from "pages/customers/Customers"; import Transactions from "pages/transactions/Transactions"; import Geography from "pages/geography/Geography"; +import Overview from "pages/overview/Overview"; +import Daily from "pages/dailyStat/Daily"; function App() { @@ -31,6 +33,8 @@ function App() { } /> } /> } /> + } /> + }/> diff --git a/admin_dashboard/client/src/components/OverviewChart.jsx b/admin_dashboard/client/src/components/OverviewChart.jsx new file mode 100644 index 0000000..7f35a5a --- /dev/null +++ b/admin_dashboard/client/src/components/OverviewChart.jsx @@ -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 ( + { + 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 \ No newline at end of file diff --git a/admin_dashboard/client/src/pages/dailyStat/Daily.jsx b/admin_dashboard/client/src/pages/dailyStat/Daily.jsx new file mode 100644 index 0000000..03fb5fb --- /dev/null +++ b/admin_dashboard/client/src/pages/dailyStat/Daily.jsx @@ -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 ( + +
+ + + + setStartDate(date)} selectsStart startDate={startDate} endDate={endDate} /> + + + setEndDate(date)} selectsEnd startDate={startDate} endDate={endDate} minDate={startDate} /> + + + {data ? ( + + ) : ( + <>Loading... + )} + + + ) +} + +export default Daily \ No newline at end of file diff --git a/admin_dashboard/client/src/pages/geography/Geography.jsx b/admin_dashboard/client/src/pages/geography/Geography.jsx index e16edc7..63f87bc 100644 --- a/admin_dashboard/client/src/pages/geography/Geography.jsx +++ b/admin_dashboard/client/src/pages/geography/Geography.jsx @@ -17,38 +17,38 @@ const Geography = () => { {data? ( { + const [view, setView] = useState("units"); + + + return ( + +
+ + + View + + + + + + ) +} + +export default Overview \ No newline at end of file diff --git a/admin_dashboard/client/src/state/api.js b/admin_dashboard/client/src/state/api.js index 9b43fc8..0f9fd12 100644 --- a/admin_dashboard/client/src/state/api.js +++ b/admin_dashboard/client/src/state/api.js @@ -3,7 +3,7 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react" export const api = createApi({ baseQuery: fetchBaseQuery({baseUrl: process.env.REACT_APP_BASE_URL}), reducerPath: "adminApi", - tagTypes: ["User", "Products", "Customers", "Transactions", "Geography" ], + tagTypes: ["User", "Products", "Customers", "Transactions", "Geography", "Sales" ], endpoints: (build) => ({ getUser: build.query({ query: (id) => `general/user/${id}`, @@ -28,8 +28,12 @@ export const api = createApi({ getGeography: build.query({ query: () => "client/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; diff --git a/admin_dashboard/server/controllers/sales.js b/admin_dashboard/server/controllers/sales.js index e69de29..f453fc0 100644 --- a/admin_dashboard/server/controllers/sales.js +++ b/admin_dashboard/server/controllers/sales.js @@ -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}) + } +} diff --git a/admin_dashboard/server/index.js b/admin_dashboard/server/index.js index 07d9ea7..b864637 100644 --- a/admin_dashboard/server/index.js +++ b/admin_dashboard/server/index.js @@ -10,11 +10,11 @@ import generalRoutes from "./routes/general.js" import managmentRoutes from "./routes/management.js" import saleRoutes from "./routes/sales.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 ProductStat from "./models/ProductStat.js" import Transaction from "./models/Transaction.js" - +import Stat from "./models/Stat.js" //configuration setup dotenv.config(); @@ -48,6 +48,8 @@ mongoose.connect(process.env.MONGO_URL, { //ProductStat.insertMany(dataProductStat); //User.insertMany(dataUser); //Transaction.insertMany(dataTransaction); + //Stat.insertMany(dataOverallStat); + }).catch((error) => console.log(`${error} didn't connect`)) diff --git a/admin_dashboard/server/models/Stat.js b/admin_dashboard/server/models/Stat.js new file mode 100644 index 0000000..781c2d2 --- /dev/null +++ b/admin_dashboard/server/models/Stat.js @@ -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; \ No newline at end of file diff --git a/admin_dashboard/server/routes/sales.js b/admin_dashboard/server/routes/sales.js index 4596761..d87b012 100644 --- a/admin_dashboard/server/routes/sales.js +++ b/admin_dashboard/server/routes/sales.js @@ -1,5 +1,10 @@ import express from "express" +import {getSales} from "../controllers/sales.js" + + const router = express.Router(); +router.get("/sales", getSales) + export default router; \ No newline at end of file