16 | )
17 | }
18 |
19 | export default PrivateRouter
--------------------------------------------------------------------------------
/src/service/useAxios.js:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux"
2 | import axios from "axios";
3 |
4 | const useAxios = () => {
5 | const {token} = useSelector((state)=> state.auth)
6 | const axiosWithToken = axios.create({
7 | baseURL:`${process.env.REACT_APP_BASE_URL}`,
8 | headers: { Authorization: `Token ${token}` }
9 | })
10 | const axiosPublic = axios.create({
11 | baseURL: `${process.env.REACT_APP_BASE_URL}`,
12 | })
13 | return {axiosWithToken, axiosPublic}
14 | }
15 |
16 | export default useAxios
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | const list = ["About Us","License","Contribute","Contact Us"]
3 | return (
4 |
19 | );
20 | }
21 | export default Footer
--------------------------------------------------------------------------------
/src/helper/ToastNotify.js:
--------------------------------------------------------------------------------
1 | import { toast } from "react-toastify";
2 | import "react-toastify/dist/ReactToastify.css";
3 |
4 | export const toastWarnNotify = (msg) => {
5 | toast.warn(msg, {
6 | autoClose: 1000,
7 | hideProgressBar: false,
8 | closeOnClick: true,
9 | pauseOnHover: true,
10 | draggable: true,
11 | progress: undefined,
12 | });
13 | };
14 |
15 | export const toastSuccessNotify = (msg) => {
16 | toast.success(msg, {
17 | autoClose: 1500,
18 | hideProgressBar: false,
19 | closeOnClick: true,
20 | pauseOnHover: true,
21 | draggable: true,
22 | progress: undefined,
23 | });
24 | };
25 |
26 | export const toastErrorNotify = (msg) => {
27 | toast.error(msg, {
28 | autoClose: 2000,
29 | hideProgressBar: false,
30 | closeOnClick: true,
31 | pauseOnHover: true,
32 | draggable: true,
33 | progress: undefined,
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Provider } from "react-redux";
2 | import AppRouter from "./router/AppRouter";
3 | import { ToastContainer } from "react-toastify";
4 | import store from "./app/store";
5 | import { PersistGate } from "redux-persist/integration/react";
6 | import { persistor } from "./app/store";
7 | import { grey, blueGrey } from "@mui/material/colors";
8 | import { createTheme, ThemeProvider } from "@mui/material/styles";
9 | function App() {
10 | const theme = createTheme({
11 | palette: {
12 | primary: {
13 | main: grey["900"],
14 | },
15 | secondary: {
16 | main: blueGrey["900"],
17 | },
18 | },
19 | });
20 | return (
21 | <>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | >
31 | );
32 | }
33 |
34 | export default App;
35 |
--------------------------------------------------------------------------------
/src/app/store.jsx:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authReducer from "../features/authSlice";
3 | import stockReducer from "../features/stockSlice"
4 | import {
5 | persistStore,
6 | persistReducer,
7 | FLUSH,
8 | REHYDRATE,
9 | PAUSE,
10 | PERSIST,
11 | PURGE,
12 | REGISTER,
13 | } from "redux-persist";
14 | import storage from "redux-persist/lib/storage";
15 | const persistConfig = {
16 | key: "root",
17 | storage,
18 | };
19 | const persistedReducer = persistReducer(persistConfig, authReducer);
20 | const store = configureStore({
21 | reducer: {
22 | auth: persistedReducer,
23 | stock: stockReducer,
24 | },
25 | devTools: process.env.Node_ENV !== "production",
26 | middleware: (getDefaultMiddleware) =>
27 | getDefaultMiddleware({
28 | serializableCheck: {
29 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
30 | },
31 | }),
32 | });
33 | export const persistor = persistStore(store);
34 | export default store;
35 |
--------------------------------------------------------------------------------
/src/features/authSlice.jsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | const initialState = {
3 | user: "",
4 | loading: false,
5 | error: false,
6 | token: "",
7 | };
8 | const authSlice = createSlice({
9 | name: "auth",
10 | initialState,
11 | reducers: {
12 | fetchStart: (state) => {
13 | state.loading = true;
14 | state.error = false
15 | },
16 | loginSuccess: (state, { payload }) => {
17 | state.loading = false;
18 | state.user = payload.user.username;
19 | state.token = payload.token;
20 | },
21 | registerSuccess: (state, { payload }) => {
22 | state.loading = false;
23 | state.user = payload.data.username;
24 | state.token = payload.token;
25 | },
26 | logoutSuccess: (state) => {
27 | state.loading = false;
28 | state.user = "";
29 | state.token = "";
30 | },
31 | fetchFail: (state) => {
32 | state.loading = false;
33 | state.error = true;
34 | },
35 | },
36 | });
37 | export const {
38 | fetchStart,
39 | loginSuccess,
40 | fetchFail,
41 | registerSuccess,
42 | logoutSuccess,
43 | } = authSlice.actions;
44 | export default authSlice.reducer;
45 |
--------------------------------------------------------------------------------
/src/features/stockSlice.jsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | const initialState = {
3 | products: [],
4 | purchases: [],
5 | brands: [],
6 | sales: [],
7 | categories: [],
8 | loading: false,
9 | error: false,
10 | firms: [],
11 | };
12 | const stockSlice = createSlice({
13 | name: "stock",
14 | initialState,
15 | reducers: {
16 | fetchStart: (state) => {
17 | state.loading = true;
18 | state.error = false;
19 | },
20 | getStockSuccess: (state, action) => {
21 | state[action.payload.url] = action.payload.apiData;
22 | state.loading = false;
23 | state.error = false;
24 | },
25 | getPromiseSuccess: (state, { payload }) => {
26 | state.loading = false;
27 | const dataKeys = payload?.endpoints;
28 | dataKeys.forEach((key, index) => {
29 | state[key] = payload.data[index];
30 | });
31 | state.error = false;
32 | },
33 | fetchFail: (state) => {
34 | state.loading = false;
35 | state.error = true;
36 | },
37 | },
38 | });
39 | export const { fetchStart, fetchFail, getStockSuccess, getPromiseSuccess } =
40 | stockSlice.actions;
41 | export default stockSlice.reducer;
42 |
--------------------------------------------------------------------------------
/src/components/Switch.jsx:
--------------------------------------------------------------------------------
1 | import { MoonIcon, SunIcon } from "@heroicons/react/24/outline";
2 | import React, { useState } from "react";
3 |
4 | const Switch = () => {
5 | const [darkMode, setDarkMode] = useState(
6 | localStorage.getItem("darkMode") || false
7 | );
8 |
9 | if (darkMode) {
10 | document.documentElement.classList.add("dark");
11 | localStorage.setItem("darkMode", true);
12 | } else {
13 | document.documentElement.classList.remove("dark");
14 | localStorage.removeItem("darkMode");
15 | }
16 |
17 | return (
18 |
19 | setDarkMode(!darkMode)}
23 | className="flex items-center p-2 mr-2 text-xs font-medium text-gray-700 bg-white rounded-lg border border-gray-200 toggle-dark-state-example hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-gray-300 dark:focus:ring-gray-500 dark:bg-gray-800 focus:outline-none dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
24 | >
25 | {darkMode ? (
26 |
27 | ) : (
28 |
29 | )}
30 |
31 |
32 | );
33 | };
34 |
35 | export default Switch;
36 |
--------------------------------------------------------------------------------
/src/router/AppRouter.jsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from "react-router-dom";
2 | import Login from "../pages/Login";
3 | import Register from "../pages/Register";
4 | import PrivateRouter from "./PrivateRouter";
5 | import Dashboard from "../pages/Dashboard";
6 | import Purchases from "../pages/Purchases"
7 | import Sales from "../pages/Sales"
8 | import Brands from "../pages/Brands"
9 | import Firms from "../pages/Firms"
10 | import Products from "../pages/Products"
11 | import ScrollToTop from "../pages/ScroolToTop";
12 | import { NotFound } from "../components/DataMessage";
13 |
14 | const AppRouter = () => {
15 | return (
16 | <>
17 |
18 |
19 | } />
20 | } />
21 | }>
22 | } />
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 |
29 | } />
30 |
31 | >
32 | );
33 | };
34 |
35 | export default AppRouter;
36 |
--------------------------------------------------------------------------------
/src/pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import RecentReport from "../components/RecentReport";
2 | import Stats from "../components/Stats";
3 | import LineChart from "../components/LineChart";
4 | import BarChart from "../components/BarChart";
5 | import useStockCalls from "../service/useStockCalls";
6 | import { useEffect } from "react";
7 | import { useSelector } from "react-redux";
8 | import { LoadingMsg, NotFound } from "../components/DataMessage";
9 | import { Helmet } from "react-helmet";
10 |
11 | const Dashboard = () => {
12 | const { getStocks, getPromise } = useStockCalls();
13 | useEffect(() => {
14 | getPromise(["sales", "purchases"]);
15 | }, []);
16 |
17 | const { loading, error } = useSelector((state) => state.stock);
18 |
19 | return (
20 | <>
21 |
22 | Dashboard | Stock Management System
23 |
24 |
25 |
26 |
27 | {error &&
}
28 | {loading &&
}
29 | {!error && !loading && (
30 | <>
31 |
32 |
33 |
34 |
35 |
36 |
37 | >
38 | )}
39 |
40 | >
41 | );
42 | };
43 |
44 | export default Dashboard;
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "first-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.11.3",
7 | "@emotion/styled": "^11.11.0",
8 | "@headlessui/react": "^1.7.17",
9 | "@heroicons/react": "^2.1.1",
10 | "@material-tailwind/react": "^2.1.8",
11 | "@mui/icons-material": "^5.15.4",
12 | "@mui/material": "^5.15.4",
13 | "@mui/x-data-grid": "^6.18.7",
14 | "@reduxjs/toolkit": "^2.0.1",
15 | "apexcharts": "^3.45.1",
16 | "axios": "^1.6.5",
17 | "formik": "^2.4.5",
18 | "open-graph": "^0.2.6",
19 | "react": "^18.2.0",
20 | "react-apexcharts": "^1.4.1",
21 | "react-dom": "^18.2.0",
22 | "react-helmet": "^6.1.0",
23 | "react-redux": "^9.0.4",
24 | "react-router-dom": "^6.21.1",
25 | "react-scripts": "5.0.1",
26 | "react-toastify": "^9.1.3",
27 | "redux-persist": "^6.0.0",
28 | "yup": "^1.3.3"
29 | },
30 | "scripts": {
31 | "start": "react-scripts start",
32 | "build": "react-scripts build",
33 | "test": "react-scripts test",
34 | "eject": "react-scripts eject"
35 | },
36 | "eslintConfig": {
37 | "extends": [
38 | "react-app",
39 | "react-app/jest"
40 | ]
41 | },
42 | "browserslist": {
43 | "production": [
44 | ">0.2%",
45 | "not dead",
46 | "not op_mini all"
47 | ],
48 | "development": [
49 | "last 1 chrome version",
50 | "last 1 firefox version",
51 | "last 1 safari version"
52 | ]
53 | },
54 | "devDependencies": {
55 | "tailwindcss": "^3.4.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/Stats.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import {
3 | CheckIcon,
4 | DolarIcon,
5 | PurchasesIcon,
6 | UsersIcon,
7 | } from "../helper/icons";
8 | const Stats = () => {
9 | const { sales, purchases } = useSelector((state) => state.stock);
10 | const totalSales = sales?.reduce((acc, item) => acc + item.amount, 0);
11 | const totalPurchases = purchases?.reduce((acc, item) => acc + item.amount, 0);
12 | const section = [
13 | { title: "Total Sales", amount: `$${totalSales}`, icon: },
14 | { title: "Purchases", amount: `$${totalPurchases}`, icon: },
15 | { title: "New Members", amount: "1256", icon: },
16 | { title: "Total CheckLists", amount: "1506", icon: },
17 | ];
18 | return (
19 |
20 |
21 |
22 | {section.map(({ amount, title, icon }) => (
23 |
27 |
28 |
{amount}
29 |
30 | {title}
31 |
32 |
33 |
{icon}
34 |
35 | ))}
36 |
37 |
38 |
39 | );
40 | };
41 | export default Stats;
42 |
--------------------------------------------------------------------------------
/src/components/BrandCard.jsx:
--------------------------------------------------------------------------------
1 | import { deleteIcon, editIcon } from "../helper/icons";
2 | import useStockCalls from "../service/useStockCalls";
3 | const BrandCard = ({setData,brand,handleOpen }) => {
4 | const {deleteStocks} = useStockCalls()
5 | const {name, image,_id}=brand
6 | return (
7 |
8 |
13 |
14 |
15 |
16 |
Firm Name
17 | {name}
18 |
19 |
20 |
21 |
22 | {handleOpen(); setData(brand)}}
26 | >
27 | {editIcon}
28 |
29 | deleteStocks("brands",_id)}
33 | >
34 | {deleteIcon}
35 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default BrandCard;
45 |
--------------------------------------------------------------------------------
/src/components/BrandForm.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Dialog,
3 | } from "@material-tailwind/react";
4 | import useStockCalls from "../service/useStockCalls";
5 | import { Input } from "@material-tailwind/react";
6 | const BrandForm = ({open,handleClose, data, setData}) => {
7 | const inputData = [
8 | { type: "text", placeholder: "Brand Name", name:"name" },
9 | { type: "url", placeholder: "Image Url", name:"image" },
10 | ];
11 |
12 | const handleChange = (e) => {
13 | setData({ ...data, [e.target.name]: e.target.value });
14 | };
15 | const { postStock, putStock } = useStockCalls();
16 | const handleSubmit = (e) => {
17 | e.preventDefault();
18 | if(data._id){
19 | putStock("brands",data)
20 | }else{
21 | postStock("brands", data);
22 | }
23 | handleClose();
24 | };
25 |
26 | return (
27 |
32 |
52 |
53 | );
54 | };
55 |
56 | export default BrandForm;
57 |
--------------------------------------------------------------------------------
/src/service/useAuthCalls.js:
--------------------------------------------------------------------------------
1 | import { useDispatch} from "react-redux";
2 | import { useNavigate } from "react-router-dom";
3 | import {
4 | fetchFail,
5 | fetchStart,
6 | loginSuccess,
7 | registerSuccess,
8 | logoutSuccess,
9 | } from "../features/authSlice";
10 | import { toastErrorNotify, toastSuccessNotify } from "../helper/ToastNotify";
11 | import useAxios from "./useAxios";
12 | const useAuthCalls = () => {
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | const {axiosWithToken, axiosPublic} = useAxios()
16 | const login = async (userInfo) => {
17 | dispatch(fetchStart());
18 | try {
19 | const {data } = await axiosPublic.post("/auth/login/",userInfo);
20 | dispatch(loginSuccess(data));
21 | toastSuccessNotify("Login successful.");
22 | navigate("/stock");
23 | } catch (error) {
24 | dispatch(fetchFail());
25 | toastErrorNotify("Login attempt failed.");
26 | }
27 | };
28 | const register = async (userInfo) => {
29 | dispatch(fetchStart());
30 | try {
31 | const { data } = await axiosPublic.post("/users/",userInfo);
32 | dispatch(registerSuccess(data));
33 | toastSuccessNotify("Registration successful.");
34 | navigate("/stock");
35 | } catch (error) {
36 | dispatch(fetchFail());
37 | toastErrorNotify("Registration unsuccessful.");
38 | }
39 | };
40 | const logout = async () => {
41 | dispatch(fetchStart());
42 | try {
43 | await axiosWithToken.get("/auth/logout/")
44 | toastSuccessNotify("Logout successful.");
45 | dispatch(logoutSuccess());
46 | } catch (error) {
47 | dispatch(fetchFail());
48 | toastErrorNotify("Logout attempt failed.");
49 | }
50 | };
51 | return { login, register, logout };
52 | };
53 |
54 | export default useAuthCalls;
55 |
--------------------------------------------------------------------------------
/src/components/FirmForm.jsx:
--------------------------------------------------------------------------------
1 | import { Dialog } from "@material-tailwind/react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import { Input } from "@material-tailwind/react";
4 | const FirmForm = ({ open,handleClose, data, setData }) => {
5 | const inputData = [
6 | { type: "text", placeholder: "Firm Name", name: "name" },
7 | { type: "text", placeholder: "Phone", name: "phone" },
8 | { type: "text", placeholder: "Adress", name: "address" },
9 | { type: "url", placeholder: "Image Url", name: "image" },
10 | ];
11 | const handleChange = (e) => {
12 | setData({ ...data, [e.target.name]: e.target.value });
13 | };
14 | const { postStock, putStock } = useStockCalls();
15 | const handleSubmit = (e) => {
16 | e.preventDefault();
17 | if (data._id) {
18 | putStock("firms", data);
19 | } else {
20 | postStock("firms", data);
21 | }
22 | handleClose();
23 | };
24 | return (
25 |
30 |
51 |
52 | );
53 | };
54 |
55 | export default FirmForm;
56 |
--------------------------------------------------------------------------------
/src/pages/Login.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import logo from "../assets/logo.png";
3 | import useAuthCalls from "../service/useAuthCalls";
4 | import { Formik } from "formik";
5 | import { loginSchema } from "../components/LoginForm";
6 | import LoginForm from "../components/LoginForm";
7 | import { Helmet } from "react-helmet";
8 |
9 | const Login = () => {
10 | const { login } = useAuthCalls();
11 |
12 | return (
13 | <>
14 |
15 | Login | Stock Management System
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 | Login to your Account
32 |
33 |
34 |
{
38 | login(values);
39 | actions.resetForm();
40 | actions.setSubmitting(false);
41 | }}
42 | component={(props) => }
43 | >
44 |
45 |
46 |
47 | >
48 | );
49 | };
50 |
51 | export default Login;
52 |
--------------------------------------------------------------------------------
/src/pages/Register.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import logo from "../assets/logo.png";
3 | import { Formik } from "formik";
4 | import RegisterForm, { registerSchema } from "../components/RegisterForm";
5 | import useAuthCalls from "../service/useAuthCalls";
6 | import { Helmet } from "react-helmet";
7 |
8 | const Register = () => {
9 | const { register } = useAuthCalls();
10 | return (
11 | <>
12 |
13 | Register | Stock Management System
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 | Create your Account
30 |
31 |
32 |
{
42 | register(values);
43 | actions.resetForm();
44 | actions.setSubmitting(false);
45 | }}
46 | component={(props) => }
47 | >
48 |
49 |
50 |
51 | >
52 | );
53 | };
54 |
55 | export default Register;
56 |
--------------------------------------------------------------------------------
/src/components/ProductTable.jsx:
--------------------------------------------------------------------------------
1 | import { DataGrid, GridActionsCellItem, GridToolbar } from "@mui/x-data-grid";
2 | import { useSelector } from "react-redux";
3 | import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
4 | import useStockCalls from "../service/useStockCalls";
5 | const ProductTable = () => {
6 | const { products } = useSelector((state) => state.stock);
7 | const { deleteStocks } = useStockCalls();
8 | const columns = [
9 | {
10 | field: "_id",
11 | headerName: "#",
12 | flex: 1.4,
13 | minWidth: "150px",
14 |
15 | sortable: false,
16 | },
17 | {
18 | field: "categoryId",
19 | headerName: "Category",
20 | flex: 1,
21 |
22 | valueGetter: (props) => {
23 | return props.row?.categoryId?.name;
24 | },
25 | },
26 | {
27 | field: "brandId",
28 | headerName: "Brand",
29 | flex: 1,
30 | valueGetter: (props) => props.row?.brandId?.name,
31 | },
32 | {
33 | field: "name",
34 | headerName: "Name",
35 | flex: 1,
36 | },
37 | {
38 | field: "quantity",
39 | headerName: "Stock",
40 | type: "number",
41 | flex: 1,
42 | headerAlign: "left",
43 | align: "left",
44 | },
45 | {
46 | field: "actions",
47 | type: "actions",
48 | headerName: "Actions",
49 | getActions: (props) => [
50 | }
52 | onClick={() => deleteStocks("products", props.id)}
53 | label="Delete"
54 | />,
55 | ],
56 | },
57 | ];
58 | const getRowId = (row) => row._id;
59 | return (
60 |
61 |
78 |
79 | );
80 | };
81 |
82 | export default ProductTable;
83 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Stock Management System
35 |
36 |
37 | You need to enable JavaScript to run this app.
38 |
39 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/components/RecentReport.jsx:
--------------------------------------------------------------------------------
1 | import { Card, Typography } from "@material-tailwind/react";
2 | import { Progress } from "@material-tailwind/react";
3 | const TABLE_HEAD = ["Member", "Report ID", "Progress"];
4 |
5 | const TABLE_ROWS = [
6 | {
7 | name: "John Michael",
8 | id: 569889,
9 | value: 65,
10 | },
11 | {
12 | name: "Alexa Liras",
13 | id: 59678,
14 |
15 | value: 75,
16 | },
17 | {
18 | name: "Laurent Perrier",
19 | id: 326549,
20 | value: 15,
21 | },
22 | {
23 | name: "Michael Levi",
24 | id: 159678,
25 | value: 95,
26 | },
27 | {
28 | name: "Richard Gran",
29 | id: 256987,
30 | value: 55,
31 | },
32 | ];
33 | const RecentReport = () => {
34 | return (
35 |
36 |
37 |
38 |
39 | {TABLE_HEAD.map((head) => (
40 |
44 |
49 | {head}
50 |
51 |
52 | ))}
53 |
54 |
55 |
56 | {TABLE_ROWS.map(({ name, id, value }) => (
57 |
58 |
59 |
64 | {name}
65 |
66 |
67 |
68 |
73 | {id}
74 |
75 |
76 |
77 |
78 |
79 |
80 | ))}
81 |
82 |
83 |
84 | );
85 | };
86 |
87 | export default RecentReport;
88 |
--------------------------------------------------------------------------------
/src/components/ProductForm.jsx:
--------------------------------------------------------------------------------
1 | import { Dialog } from "@material-tailwind/react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import { useSelector } from "react-redux";
4 | import { Select, Option, Input } from "@material-tailwind/react";
5 | const ProductForm = ({ open, handleClose, data, setData }) => {
6 | const handleChange = (e, fieldName, fieldType) => {
7 | if (fieldType === "select") {
8 | setData({ ...data, [fieldName]: e });
9 | } else {
10 | setData({ ...data, [fieldName]: e.target.value });
11 | }
12 | };
13 | const { postStock } = useStockCalls();
14 | const { categories, brands } = useSelector((state) => state.stock);
15 | const handleSubmit = (e) => {
16 | e.preventDefault();
17 | postStock("products", data);
18 | handleClose();
19 | };
20 | return (
21 |
26 |
70 |
71 | );
72 | };
73 |
74 | export default ProductForm;
75 |
--------------------------------------------------------------------------------
/src/components/FirmCard.jsx:
--------------------------------------------------------------------------------
1 | import { addressIcon, deleteIcon, editIcon, phoneIcon } from "../helper/icons";
2 | import useStockCalls from "../service/useStockCalls";
3 |
4 | const FirmCard = ({firm, handleOpen, setData }) => {
5 | const {deleteStocks} = useStockCalls()
6 | const { name, address, phone, _id, image} = firm
7 | return (
8 |
9 |
14 |
15 |
16 |
17 |
Firm Name
18 | {name}
19 |
20 |
21 |
22 |
23 |
24 | {phoneIcon}
25 |
26 |
27 |
Phone
28 |
{phone}
29 |
30 |
31 |
32 |
33 | {addressIcon}
34 |
35 |
36 |
Adress
37 |
{address}
38 |
39 |
40 |
41 |
42 |
43 | {
47 | handleOpen()
48 | setData(firm)
49 | }}
50 | >
51 | {editIcon}
52 |
53 | deleteStocks("firms",_id)}
57 | >
58 | {deleteIcon}
59 |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default FirmCard;
68 |
--------------------------------------------------------------------------------
/src/components/SaleTable.jsx:
--------------------------------------------------------------------------------
1 | import { DataGrid, GridActionsCellItem, GridToolbar } from "@mui/x-data-grid";
2 | import { useSelector } from "react-redux";
3 | import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
4 | import useStockCalls from "../service/useStockCalls";
5 | import EditIcon from "@mui/icons-material/Edit";
6 | const SaleTable = ({handleOpen, setData}) => {
7 | const { sales } = useSelector((state) => state.stock);
8 | const { deleteStocks } = useStockCalls();
9 | const columns = [
10 | {
11 | field: "createdAt",
12 | headerName: "Date",
13 | flex: 1,
14 | renderCell: ({ row }) => row.createdAtStr.slice(0,22),
15 | },
16 | {
17 | field: "brandId",
18 | headerName: "Brand",
19 | flex: 1,
20 | renderCell: ({ row }) => row?.brandId?.name,
21 | },
22 | {
23 | field: "productId",
24 | headerName: "Product",
25 | flex: 1,
26 | renderCell: ({ row }) => row?.productId?.name,
27 | },
28 | {
29 | field: "quantity",
30 | headerName: "Quantity",
31 | flex: 1,
32 | },
33 | {
34 | field: "price",
35 | headerName: "Price",
36 | flex: 1,
37 | },
38 | {
39 | field: "amount",
40 | headerName: "Amount",
41 | flex: 1,
42 | },
43 | {
44 | field: "actions",
45 | type: "actions",
46 | headerName: "Actions",
47 | flex: 1,
48 | renderCell: ({ row: { brandId, price, quantity, productId, _id } }) => {
49 | return [
50 | }
53 | label="Edit"
54 | onClick={() => {
55 | handleOpen()
56 | setData({ brandId, price, quantity, productId, _id })
57 | }}
58 | />,
59 | }
62 | label="Delete"
63 | onClick={() => deleteStocks("sales", _id)}
64 | />,
65 | ]
66 | },
67 | },
68 | ];
69 |
70 | const getRowId = (row) => row._id;
71 | return (
72 |
73 |
90 |
91 | );
92 | };
93 |
94 | export default SaleTable;
95 |
--------------------------------------------------------------------------------
/src/components/LoginForm.jsx:
--------------------------------------------------------------------------------
1 | import { Form } from "formik";
2 | import { object, string } from "yup";
3 | import { NavLink } from "react-router-dom";
4 | import { EmailIcon, PasswordIcon } from "../helper/icons";
5 |
6 | export const loginSchema = object({
7 | email: string()
8 | .email("Please enter a valid email.")
9 | .required("Email is required."),
10 | password: string()
11 | .required("Password is required.")
12 | .min(8, "Password must contain at least 8 characters.")
13 | .matches(/\d+/, "Password must contain at least one digit.")
14 | .matches(/[a-z]/, "Password must contain at least one lowercase letter.")
15 | .matches(/[A-Z]/, "Password must contain at least one uppercase letter.")
16 | .matches(
17 | /[@$!%*?&]+/,
18 | "Password must contain at least one special character (@$!%*?&)."
19 | ),
20 | });
21 |
22 | const inputData = [
23 | {
24 | label: "email",
25 | name: "email",
26 | type: "email",
27 | placeholder: "Enter email",
28 | },
29 | {
30 | label: "password",
31 | name: "password",
32 | type: "password",
33 | placeholder: "Enter password",
34 | },
35 | ];
36 | const LoginForm = ({ handleChange, values, touched, errors, handleBlur }) => {
37 | return (
38 |
80 | );
81 | };
82 |
83 | export default LoginForm;
84 |
--------------------------------------------------------------------------------
/src/service/useStockCalls.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import useAxios from "./useAxios";
3 | import { fetchFail, fetchStart } from "../features/stockSlice";
4 | import { getStockSuccess ,getPromiseSuccess} from "../features/stockSlice";
5 | import { toastErrorNotify, toastSuccessNotify } from "../helper/ToastNotify";
6 | const useStockCalls = () => {
7 | const dispatch = useDispatch();
8 | const { axiosWithToken } = useAxios();
9 | const getStocks = async (url = "firms") => {
10 | dispatch(fetchStart());
11 | try {
12 | const { data } = await axiosWithToken(`${url}`);
13 | const apiData = data.data;
14 | dispatch(getStockSuccess({ apiData, url }));
15 | } catch (error) {
16 | dispatch(fetchFail());
17 | }
18 | };
19 | const deleteStocks = async (url = "firms", id) => {
20 | dispatch(fetchStart());
21 | try {
22 | await axiosWithToken.delete(`/${url}/${id}/`);
23 | toastSuccessNotify("Data has been deleted.");
24 | getStocks(url);
25 | } catch (error) {
26 | dispatch(fetchFail());
27 | toastErrorNotify("Data could not be deleted.");
28 | }
29 | };
30 | const postStock = async (url = "firms", info) => {
31 | dispatch(fetchStart());
32 | try {
33 | await axiosWithToken.post(`${url}`, info);
34 | getStocks(url);
35 | toastSuccessNotify("Data has been added.");
36 | } catch (error) {
37 | dispatch(fetchFail());
38 | toastErrorNotify("Data could not be added.");
39 | }
40 | };
41 | const putStock = async (url = "firms", info) => {
42 | dispatch(fetchStart());
43 | try {
44 | await axiosWithToken.put(`/${url}/${info._id}/`, info);
45 | toastSuccessNotify("Data has been updated.");
46 | getStocks(url);
47 | } catch (error) {
48 | dispatch(fetchFail());
49 | toastErrorNotify("Data could not be updated.");
50 | }
51 | };
52 | const searchStock = async (url = "firms", value) => {
53 | dispatch(fetchStart());
54 | try {
55 | const lowercaseValue = value.toLowerCase();
56 | const { data } = await axiosWithToken.get(
57 | `/${url}/?search[name]=${lowercaseValue}`
58 | );
59 | const apiData = data.data;
60 | dispatch(getStockSuccess({ apiData, url }));
61 | } catch (error) {
62 | dispatch(fetchFail());
63 | }
64 | };
65 | const getPromise = async(endpoints)=>{
66 | dispatch(fetchStart());
67 | try {
68 | const responses = await Promise.all(
69 | endpoints.map((endpoint) => axiosWithToken(endpoint))
70 | );
71 | const data = responses.map((response) => response?.data?.data);
72 | dispatch(getPromiseSuccess({data,endpoints}));
73 | } catch (error) {
74 | dispatch(fetchFail());
75 | }
76 |
77 | }
78 |
79 | return {
80 | getStocks,
81 | deleteStocks,
82 | postStock,
83 | putStock,
84 | searchStock,
85 | getPromise,
86 | };
87 | };
88 |
89 | export default useStockCalls;
90 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/node
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
3 |
4 | ### Node ###
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 | .pnpm-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | node_modules/
47 | jspm_packages/
48 |
49 | # Snowpack dependency directory (https://snowpack.dev/)
50 | web_modules/
51 |
52 | # TypeScript cache
53 | *.tsbuildinfo
54 |
55 | # Optional npm cache directory
56 | .npm
57 |
58 | # Optional eslint cache
59 | .eslintcache
60 |
61 | # Optional stylelint cache
62 | .stylelintcache
63 |
64 | # Microbundle cache
65 | .rpt2_cache/
66 | .rts2_cache_cjs/
67 | .rts2_cache_es/
68 | .rts2_cache_umd/
69 |
70 | # Optional REPL history
71 | .node_repl_history
72 |
73 | # Output of 'npm pack'
74 | *.tgz
75 |
76 | # Yarn Integrity file
77 | .yarn-integrity
78 |
79 | # dotenv environment variable files
80 | .env
81 | .env.development.local
82 | .env.test.local
83 | .env.production.local
84 | .env.local
85 |
86 | # parcel-bundler cache (https://parceljs.org/)
87 | .cache
88 | .parcel-cache
89 |
90 | # Next.js build output
91 | .next
92 | out
93 |
94 | # Nuxt.js build / generate output
95 | .nuxt
96 | dist
97 |
98 | # Gatsby files
99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 |
104 | # vuepress build output
105 | .vuepress/dist
106 |
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 |
110 | # Docusaurus cache and generated files
111 | .docusaurus
112 |
113 | # Serverless directories
114 | .serverless/
115 |
116 | # FuseBox cache
117 | .fusebox/
118 |
119 | # DynamoDB Local files
120 | .dynamodb/
121 |
122 | # TernJS port file
123 | .tern-port
124 |
125 | # Stores VSCode versions used for testing VSCode extensions
126 | .vscode-test
127 |
128 | # yarn v2
129 | .yarn/cache
130 | .yarn/unplugged
131 | .yarn/build-state.yml
132 | .yarn/install-state.gz
133 | .pnp.*
134 |
135 | ### Node Patch ###
136 | # Serverless Webpack directories
137 | .webpack/
138 |
139 | # Optional stylelint cache
140 |
141 | # SvelteKit build / generate output
142 | .svelte-kit
143 |
144 | # End of https://www.toptal.com/developers/gitignore/api/node
--------------------------------------------------------------------------------
/src/components/PurchaseTable.jsx:
--------------------------------------------------------------------------------
1 | import { DataGrid, GridActionsCellItem, GridToolbar } from "@mui/x-data-grid";
2 | import { useSelector } from "react-redux";
3 | import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
4 | import useStockCalls from "../service/useStockCalls";
5 | import EditIcon from "@mui/icons-material/Edit";
6 | const PurchaseTable = ({handleOpen, setData}) => {
7 | const { purchases } = useSelector((state) => state.stock)
8 | const { deleteStocks } = useStockCalls();
9 | const columns = [
10 | {
11 | field: "createdAt",
12 | headerName: "Date",
13 | flex: 1,
14 | renderCell: ({ row }) => row.createdAtStr.slice(0,22),
15 | },
16 | {
17 | field: "firmId",
18 | headerName: "Firm",
19 | flex: 1,
20 | renderCell: ({ row }) => row?.firmId?.name,
21 | },
22 | {
23 | field: "brandId",
24 | headerName: "Brand",
25 | flex: 1,
26 | renderCell: ({ row }) => row?.brandId?.name,
27 | },
28 | {
29 | field: "productId",
30 | headerName: "Product",
31 | flex: 1,
32 | renderCell: ({ row }) => row?.productId?.name,
33 | },
34 | {
35 | field: "quantity",
36 | headerName: "Quantity",
37 | flex: 1,
38 | },
39 | {
40 | field: "price",
41 | headerName: "Price",
42 | flex: 1,
43 | },
44 | {
45 | field: "amount",
46 | headerName: "Amount",
47 | flex: 1,
48 | },
49 | {
50 | field: "actions",
51 | type: "actions",
52 | headerName: "Actions",
53 | flex: 1,
54 | renderCell: ({ row: { brandId, productId, quantity, price, firmId, _id } }) => {
55 | return [
56 | }
59 | label="Edit"
60 | onClick={() => {
61 | handleOpen()
62 | setData({ brandId, productId, quantity, price, firmId, _id })
63 | }}
64 | />,
65 | }
68 | label="Delete"
69 | onClick={() => deleteStocks("purchases", _id)}
70 | />,
71 | ]
72 | },
73 | },
74 | ];
75 |
76 | const getRowId = (row) => row._id;
77 | return (
78 |
79 |
96 |
97 | );
98 | };
99 |
100 | export default PurchaseTable;
101 |
102 |
--------------------------------------------------------------------------------
/src/pages/Products.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import ProductTable from "../components/ProductTable";
4 | import { useSelector } from "react-redux";
5 | import ProductForm from "../components/ProductForm";
6 | import { LoadingMsg, NotFound } from "../components/DataMessage";
7 | import { NoData } from "../components/DataMessage";
8 | import { Helmet } from "react-helmet";
9 |
10 | const Products = () => {
11 | const [open, setOpen] = useState(false);
12 | const handleOpen = () => setOpen(true);
13 | const handleClose = () => {
14 | setData({ categoryId: "", brandId: "", name: "" });
15 | setOpen(false);
16 | };
17 | const [data, setData] = useState({
18 | categoryId: "",
19 | brandId: "",
20 | name: "",
21 | });
22 | const { getStocks, getPromise } = useStockCalls();
23 | const { products, error, loading } = useSelector((state) => state.stock);
24 | useEffect(() => {
25 | getPromise(["products", "categories", "brands"]);
26 | }, []);
27 |
28 | return (
29 | <>
30 |
31 | Products | Stock Management System
32 |
33 |
34 |
35 |
36 | {error &&
}
37 | {loading &&
}
38 | {!error && !loading && (
39 | <>
40 |
41 |
42 |
Products
43 |
44 | See information about all products
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 | Add Product
56 |
57 |
58 |
65 |
66 |
67 |
68 | {products.length === 0 ?
:
}
69 | >
70 | )}
71 |
72 | >
73 | );
74 | };
75 |
76 | export default Products;
77 |
--------------------------------------------------------------------------------
/src/pages/Sales.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import SaleTable from "../components/SaleTable";
4 | import { useSelector } from "react-redux";
5 | import SaleForm from "../components/SaleForm";
6 | import { LoadingMsg, NotFound } from "../components/DataMessage";
7 | import { NoData } from "../components/DataMessage";
8 | import { Helmet } from "react-helmet";
9 |
10 | const Sales = () => {
11 | const [open, setOpen] = useState(false);
12 | const handleOpen = () => setOpen(true);
13 | const handleClose = () => {
14 | setData({ brandId: "", productId: "", quantity: "", price: "" });
15 | setOpen(false);
16 | };
17 |
18 | const [data, setData] = useState({
19 | brandId: "",
20 | productId: "",
21 | quantity: "",
22 | price: "",
23 | });
24 |
25 | const { getPromise } = useStockCalls();
26 | const { sales, error, loading } = useSelector((state) => state.stock);
27 | useEffect(() => {
28 | getPromise(["products", "sales", "brands"]);
29 | }, []);
30 |
31 | return (
32 | <>
33 |
34 | Sales | Stock Management System
35 |
36 |
37 |
38 | {error &&
}
39 | {loading &&
}
40 | {!error && !loading && (
41 | <>
42 |
43 |
44 |
Sales
45 |
46 | See information about all sales
47 |
48 |
49 |
50 |
51 |
55 |
56 |
57 | Add Sale
58 |
59 |
60 |
67 |
68 |
69 |
70 | {sales.length === 0 ? (
71 |
72 | ) : (
73 |
74 | )}
75 | >
76 | )}
77 |
78 | >
79 | );
80 | };
81 | export default Sales;
82 |
--------------------------------------------------------------------------------
/src/pages/Purchases.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import PurchaseTable from "../components/PurchaseTable";
4 | import { useSelector } from "react-redux";
5 | import { Helmet } from "react-helmet";
6 | import { LoadingMsg, NotFound, TableSkeleton } from "../components/DataMessage";
7 | import { NoData } from "../components/DataMessage";
8 | import PurchaseForm from "../components/PurchaseForm";
9 |
10 | const Purchases = () => {
11 | const [open, setOpen] = useState(false);
12 | const handleOpen = () => setOpen(true);
13 |
14 | const handleClose = () => {
15 | setData({
16 | brandId: "",
17 | firmId: "",
18 | productId: "",
19 | quantity: "",
20 | price: "",
21 | });
22 | setOpen(false);
23 | };
24 |
25 | const [data, setData] = useState({
26 | brandId: "",
27 | firmId: "",
28 | productId: "",
29 | quantity: "",
30 | price: "",
31 | });
32 |
33 | const { getStocks, getPromise } = useStockCalls();
34 | const { purchases, error, loading } = useSelector((state) => state.stock);
35 |
36 | useEffect(() => {
37 | getPromise(["products", "purchases", "brands", "firms"]);
38 | }, []);
39 |
40 | return (
41 | <>
42 |
43 | Purchases | Stock Management System
44 |
45 |
46 |
47 |
48 | {error &&
}
49 | {loading &&
}
50 | {!error && !loading && (
51 | <>
52 |
53 |
54 |
Purchases
55 |
56 | See information about all purchase
57 |
58 |
59 |
60 |
61 |
65 |
66 |
67 | Add Purchase
68 |
69 |
70 |
77 |
78 |
79 |
80 | {purchases.length === 0 ? (
81 |
82 | ) : (
83 |
84 | )}
85 | >
86 | )}
87 |
88 | >
89 | );
90 | };
91 |
92 | export default Purchases;
93 |
--------------------------------------------------------------------------------
/src/components/SaleForm.jsx:
--------------------------------------------------------------------------------
1 | import { Dialog, Option, Select } from "@material-tailwind/react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import { useSelector } from "react-redux";
4 | import { useNavigate } from "react-router-dom";
5 | import { Input } from "@material-tailwind/react";
6 | import React from "react";
7 | const SaleForm = ({ open, handleClose, data, setData }) => {
8 | const navigate = useNavigate();
9 | const handleChange = (e, fieldName, fieldType) => {
10 | if (fieldType === "select") {
11 | setData({ ...data, [fieldName]: e });
12 | } else {
13 | setData({ ...data, [fieldName]: e.target.value });
14 | }
15 | };
16 | const { postStock, putStock } = useStockCalls();
17 | const { brands, products } = useSelector((state) => state.stock);
18 | const handleSubmit = (e) => {
19 | e.preventDefault();
20 | if (data._id) {
21 | putStock("sales", data);
22 | } else {
23 | postStock("sales", data);
24 | console.log(data)
25 | }
26 | handleClose();
27 | };
28 |
29 | const myBrand = [
30 | {
31 | _id: "",
32 | name: "Add New Brand",
33 | onClick: () => navigate("/stock/brands/"),
34 | },
35 | ...brands,
36 | ];
37 | const myProduct = [
38 | {
39 | _id: "",
40 | name: "Add New Product",
41 | onClick: () => navigate("/stock/products"),
42 | },
43 | ...products,
44 | ];
45 | return (
46 |
51 |
103 |
104 | );
105 | };
106 |
107 | export default SaleForm;
108 |
--------------------------------------------------------------------------------
/src/components/RegisterForm.jsx:
--------------------------------------------------------------------------------
1 | import { Form } from "formik"
2 | import { object, string } from "yup"
3 | import { NavLink } from "react-router-dom";
4 | import { EmailIcon, PasswordIcon } from "../helper/icons";
5 | export const registerSchema = object({
6 | username: string()
7 | .max(10, "The username must be less than 10 characters.")
8 | .required(
9 | "The username is required."),
10 | firstName: string()
11 | .max(10, "The first name must be less than 10 characters.")
12 | .required(
13 | "The first name is required."),
14 | lastName: string()
15 | .max(10,
16 | "The last name must be less than 10 characters.")
17 | .required("The last name is required."),
18 |
19 | email: string()
20 | .email(
21 | "Please enter a valid email.")
22 | .required(
23 | "Email is required."),
24 | password: string()
25 | .required(
26 | "Password is required.")
27 | .min(8, "The password must be at least 8 characters.")
28 | .matches(/\d+/,
29 | "The password must contain at least one number.")
30 | .matches(/[a-z]/, "Password must contain at least one lowercase letter.")
31 | .matches(/[A-Z]/, "Password must contain at least one uppercase letter.")
32 | .matches(/[!/[@$!%*?&]+/, "Password must contain at least one special character (@$!%*?&)."),
33 | })
34 | const inputData= [
35 | {label:"username", name:"username", type:"text", placeholder:"User Name", icon:"" },
36 | {label:"firstName", name:"firstName", type:"text", placeholder:"First Name", icon:"" },
37 | {label:"lastName", name:"lastName", type:"text", placeholder:"Last Name", icon:"" },
38 | {label:"email", name:"email", type:"email", placeholder:"Enter email", icon: },
39 | {label:"password", name:"password", type:"password", placeholder:"Enter password", icon: }
40 | ]
41 | const RegisterForm = ({ values,
42 | handleChange,
43 | errors,
44 | touched,
45 | handleBlur,}) => {
46 | return (
47 |
90 | )
91 | }
92 |
93 | export default RegisterForm
--------------------------------------------------------------------------------
/src/components/LineChart.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | CardBody,
4 | CardHeader,
5 | Typography,
6 | } from "@material-tailwind/react";
7 | import Chart from "react-apexcharts";
8 | import { Square3Stack3DIcon } from "@heroicons/react/24/outline";
9 | import { useSelector } from "react-redux";
10 | const LineChart = () => {
11 | const { sales } = useSelector((state) => state.stock)
12 | const totalSales = sales?.map((item)=>(item.amount)) || []
13 | const date = sales?.map((item)=>new Date(item.createdAt).toLocaleDateString("tr-TR"))
14 | const chartConfig1 = {
15 | type: "line",
16 | height: 240,
17 | series: [
18 | {
19 | name: "Sales",
20 | data: totalSales,
21 | },
22 | ],
23 | options: {
24 | chart: {
25 | toolbar: {
26 | show: false,
27 | },
28 | },
29 | title: {
30 | show: "",
31 | },
32 | dataLabels: {
33 | enabled: false,
34 | },
35 | colors: ["#ABFB60"],
36 | stroke: {
37 | lineCap: "round",
38 | curve: "smooth",
39 | },
40 | markers: {
41 | size: 0,
42 | },
43 | xaxis: {
44 | axisTicks: {
45 | show: false,
46 | },
47 | axisBorder: {
48 | show: false,
49 | },
50 | labels: {
51 | style: {
52 | colors: "#616161",
53 | fontSize: "12px",
54 | fontFamily: "inherit",
55 | fontWeight: 400,
56 | },
57 | },
58 | categories: date,
59 | },
60 | yaxis: {
61 | labels: {
62 | style: {
63 | colors: "#616161",
64 | fontSize: "12px",
65 | fontFamily: "inherit",
66 | fontWeight: 400,
67 | },
68 | },
69 | },
70 | grid: {
71 | show: true,
72 | borderColor: "#dddddd",
73 | strokeDashArray: 5,
74 | xaxis: {
75 | lines: {
76 | show: true,
77 | },
78 | },
79 | padding: {
80 | top: 5,
81 | right: 20,
82 | },
83 | },
84 | fill: {
85 | opacity: 0.8,
86 | },
87 | tooltip: {
88 | theme: "dark",
89 | },
90 | },
91 | };
92 | return (
93 |
94 |
100 |
101 |
102 |
103 |
104 |
105 | Total Sales
106 |
107 |
112 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Quasi, ratione et tempora ipsa repellat est.
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | )
121 | }
122 |
123 | export default LineChart
--------------------------------------------------------------------------------
/src/components/BarChart.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | CardBody,
4 | CardHeader,
5 | Typography,
6 | } from "@material-tailwind/react";
7 | import Chart from "react-apexcharts";
8 | import { Square3Stack3DIcon } from "@heroicons/react/24/outline";
9 | import { useSelector } from "react-redux";
10 | const BarChart = () => {
11 | const {purchases } = useSelector((state) => state.stock)
12 | const totalPurchases = purchases?.map((item)=>(item.amount)) || []
13 | const date = purchases?.map((item)=>new Date(item.createdAt).toLocaleDateString("tr-TR"))
14 | const chartConfig = {
15 | type: "bar",
16 | height: 240,
17 | series: [
18 | {
19 | name: "Sales",
20 | data: totalPurchases,
21 | },
22 | ],
23 | options: {
24 | chart: {
25 | toolbar: {
26 | show: false,
27 | },
28 | },
29 | title: {
30 | show: "",
31 | },
32 | dataLabels: {
33 | enabled: false,
34 | },
35 | colors: ["#ABFB60"],
36 | plotOptions: {
37 | bar: {
38 | columnWidth: "40%",
39 | borderRadius: 2,
40 | },
41 | },
42 | xaxis: {
43 | axisTicks: {
44 | show: false,
45 | },
46 | axisBorder: {
47 | show: false,
48 | },
49 | labels: {
50 | style: {
51 | colors: "#616161",
52 | fontSize: "12px",
53 | fontFamily: "inherit",
54 | fontWeight: 400,
55 | },
56 | },
57 | categories: date,
58 | },
59 | yaxis: {
60 | labels: {
61 | style: {
62 | colors: "#616161",
63 | fontSize: "12px",
64 | fontFamily: "inherit",
65 | fontWeight: 400,
66 | },
67 | },
68 | },
69 | grid: {
70 | show: true,
71 | borderColor: "#dddddd",
72 | strokeDashArray: 5,
73 | xaxis: {
74 | lines: {
75 | show: true,
76 | },
77 | },
78 | padding: {
79 | top: 5,
80 | right: 20,
81 | },
82 | },
83 | fill: {
84 | opacity: 0.8,
85 | },
86 | tooltip: {
87 | theme: "dark",
88 | },
89 | },
90 | };
91 | return (
92 |
93 |
99 |
100 |
101 |
102 |
103 |
104 | Total Purchases
105 |
106 |
111 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex dolorem libero voluptates, quis accusamus iste.
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | )
120 | }
121 |
122 | export default BarChart
--------------------------------------------------------------------------------
/src/components/PurchaseForm.jsx:
--------------------------------------------------------------------------------
1 | import { Dialog, Option, Select } from "@material-tailwind/react";
2 | import useStockCalls from "../service/useStockCalls";
3 | import { useSelector } from "react-redux";
4 | import { useNavigate } from "react-router-dom";
5 | import { Input } from "@material-tailwind/react";
6 | import React from "react";
7 | const PurchaseForm = ({ open, handleClose, data, setData }) => {
8 | const navigate = useNavigate();
9 | const handleChange = (e, fieldName, fieldType) => {
10 | if (fieldType === "select") {
11 | setData({ ...data, [fieldName]: e });
12 | } else {
13 | setData({ ...data, [fieldName]: e.target.value });
14 | }
15 | };
16 | const { postStock, putStock } = useStockCalls();
17 | const { firms, products, brands } = useSelector((state) => state.stock);
18 | const handleSubmit = (e) => {
19 | e.preventDefault();
20 | if (data._id) {
21 | putStock("purchases", data);
22 | } else {
23 | postStock("purchases", data);
24 | }
25 | handleClose();
26 | };
27 | const myFirm = [
28 | {
29 | _id: "",
30 | name: "Add New Firm",
31 | onClick: () => navigate("/stock/firms"),
32 | },
33 | ...firms,
34 | ];
35 | const myProduct = [ {
36 | _id: "",
37 | name: "Add New Product",
38 | onClick: () => navigate("/stock/products"),
39 | },
40 | ...products,]
41 | const myBrand = [
42 | {
43 | _id: "",
44 | name: "Add New Brand",
45 | onClick: () => navigate("/stock/brands/"),
46 | },
47 | ...brands,
48 | ];
49 | return (
50 |
55 |
120 |
121 | );
122 | };
123 |
124 | export default PurchaseForm;
125 |
--------------------------------------------------------------------------------
/src/pages/Brands.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import BrandCard from "../components/BrandCard";
3 | import BrandForm from "../components/BrandForm";
4 | import { useSelector } from "react-redux";
5 | import useStockCalls from "../service/useStockCalls";
6 | import { searchIcon } from "../helper/icons";
7 | import { CardSkeloton, NoData, NotFound } from "../components/DataMessage";
8 | import { Helmet } from "react-helmet";
9 |
10 | const Brands = () => {
11 | const [open, setOpen] = useState(false);
12 | const handleOpen = () => setOpen(true);
13 | const handleClose = () => {
14 | setOpen(false);
15 | setData({ name: "", image: "" });
16 | };
17 |
18 | const [data, setData] = useState({
19 | name: "",
20 | image: "",
21 | });
22 |
23 | const { brands, loading, error } = useSelector((state) => state.stock);
24 | const { searchStock } = useStockCalls();
25 | const [value, setValue] = useState("");
26 |
27 | useEffect(() => {
28 | searchStock("brands", value);
29 | }, [value]);
30 |
31 | return (
32 | <>
33 |
34 | Brands | Stock Management System
35 |
36 |
37 |
38 |
39 | {error &&
}
40 | {!error && (
41 | <>
42 |
43 |
44 |
Brand List
45 |
46 | See information about all brands
47 |
48 |
49 |
50 |
51 | setValue(e.target.value)}
58 | autoComplete="off"
59 | />
60 |
61 |
65 | Search
66 | {searchIcon}
67 |
68 |
69 |
70 |
71 |
75 |
76 |
77 | Add Brand
78 |
79 |
80 |
86 |
87 |
88 |
89 | {loading ? (
90 |
91 | ) : (
92 |
93 | {brands?.map((brand) => (
94 |
100 | ))}
101 |
102 | )}
103 | >
104 | )}
105 | {!error && !loading && !brands.length &&
}
106 |
107 | >
108 | );
109 | };
110 |
111 | export default Brands;
112 |
--------------------------------------------------------------------------------
/src/pages/Firms.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import FirmCard from "../components/FirmCard";
3 | import FirmForm from "../components/FirmForm";
4 | import { useSelector } from "react-redux";
5 | import useStockCalls from "../service/useStockCalls";
6 | import { searchIcon } from "../helper/icons";
7 | import { CardSkeloton, NoData, NotFound } from "../components/DataMessage";
8 | import { Helmet } from "react-helmet";
9 |
10 | const Firms = () => {
11 | const [open, setOpen] = useState(false);
12 | const handleOpen = () => setOpen(true);
13 | const handleClose = () => {
14 | setData({ name: "", phone: "", address: "", image: "" });
15 | setOpen(false);
16 | };
17 |
18 | const { firms, loading, error } = useSelector((state) => state.stock);
19 | const { searchStock } = useStockCalls();
20 | const [data, setData] = useState({
21 | name: "",
22 | phone: "",
23 | address: "",
24 | image: "",
25 | });
26 |
27 | const [value, setValue] = useState("");
28 |
29 | useEffect(() => {
30 | searchStock("firms", value);
31 | }, [value]);
32 |
33 | return (
34 | <>
35 |
36 | Firms | Stock Management System
37 |
38 |
39 |
40 |
41 | {error &&
}
42 | {!error && (
43 | <>
44 |
45 |
46 |
Firm List
47 |
48 | See information about all firm
49 |
50 |
51 |
52 |
53 | setValue(e.target.value)}
60 | autoComplete="off"
61 | />
62 |
63 |
67 | Search
68 | {searchIcon}
69 |
70 |
71 |
72 |
73 |
77 |
78 |
79 | Add Firm
80 |
81 |
82 |
89 |
90 |
91 |
92 | {loading ? (
93 |
94 | ) : (
95 |
96 | {firms?.map((firm) => (
97 |
103 | ))}
104 |
105 | )}
106 | {!error && !loading && !firms.length &&
}
107 | >
108 | )}
109 |
110 | >
111 | );
112 | };
113 |
114 | export default Firms;
115 |
--------------------------------------------------------------------------------
/src/components/DataMessage.jsx:
--------------------------------------------------------------------------------
1 | import { loadingIcon } from "../helper/icons";
2 |
3 | export const NotFound = () => {
4 | return (
5 |
6 |
7 | 404 | Not Found
8 |
9 |
10 | );
11 | };
12 | export const NoData = () => {
13 | return (
14 |
15 |
31 |
No Product!
32 |
33 | );
34 | };
35 | export const CardSkeloton = () => {
36 | const arr = [1, 2, 3, 4, 5, 6, 7];
37 | return (
38 |
39 | {arr.map((item, index) => (
40 |
41 |
42 | {loadingIcon}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 | ))}
73 |
74 | );
75 | };
76 | export const LoadingMsg = () => {
77 | return (
78 |
79 |
80 | Loading...
81 |
82 | Please wait...
83 |
84 | );
85 | };
86 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { Fragment, useState } from "react";
2 | import { Dialog, Disclosure, Popover, Transition } from "@headlessui/react";
3 | import {
4 | ArrowPathIcon,
5 | Bars3Icon,
6 | ChartPieIcon,
7 | CursorArrowRaysIcon,
8 | FingerPrintIcon,
9 | SquaresPlusIcon,
10 | XMarkIcon,
11 | } from "@heroicons/react/24/outline";
12 | import {
13 | ChevronDownIcon,
14 | PhoneIcon,
15 | PlayCircleIcon,
16 | } from "@heroicons/react/20/solid";
17 | import Switch from "./Switch";
18 | import logo from "../assets/logo.png";
19 | import { useSelector } from "react-redux";
20 | import useAuthCalls from "../service/useAuthCalls";
21 | import { NavLink } from "react-router-dom";
22 | const products = [
23 | {
24 | name: "Analytics",
25 | description: "Get a better understanding of your traffic",
26 | href: "#",
27 | icon: ChartPieIcon,
28 | },
29 | {
30 | name: "Engagement",
31 | description: "Speak directly to your customers",
32 | href: "#",
33 | icon: CursorArrowRaysIcon,
34 | },
35 | {
36 | name: "Security",
37 | description: "Your customers’ data will be safe and secure",
38 | href: "#",
39 | icon: FingerPrintIcon,
40 | },
41 | {
42 | name: "Integrations",
43 | description: "Connect with third-party tools",
44 | href: "#",
45 | icon: SquaresPlusIcon,
46 | },
47 | {
48 | name: "Automations",
49 | description: "Build strategic funnels that will convert",
50 | href: "#",
51 | icon: ArrowPathIcon,
52 | },
53 | ];
54 | const callsToAction = [
55 | { name: "Watch demo", href: "#", icon: PlayCircleIcon },
56 | { name: "Contact sales", href: "#", icon: PhoneIcon },
57 | ];
58 | const menu = [
59 | {
60 | title: "Dashboard",
61 | url: "/stock/",
62 | },
63 | {
64 | title: "Purchases",
65 | url: "/stock/purchases/",
66 | },
67 | {
68 | title: "Sales",
69 | url: "/stock/sales/",
70 | },
71 | {
72 | title: "Firms",
73 | url: "/stock/firms/",
74 | },
75 | {
76 | title: "Brands",
77 | url: "/stock/brands/",
78 | },
79 | {
80 | title: "Products",
81 | url: "/stock/products/",
82 | },
83 | ]
84 | function classNames(...classes) {
85 | return classes.filter(Boolean).join(" ");
86 | }
87 |
88 | export default function Navbar() {
89 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
90 | const {user} = useSelector((state)=> state.auth)
91 | const {logout} = useAuthCalls()
92 | return (
93 |
178 | );
179 | }
180 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Stock Management System
2 | This project is a stock management application developed using **Node.js** on the backend and **React** on the frontend. For user interaction, the application offers login functionality for existing users and a straightforward account creation process for newcomers. Users can effortlessly add new information, perform edits or deletions, and manage product-related data such as firm, brand, and stock updates. The system supports sorting and filtering operations on tabular data, providing a streamlined experience. Additionally, users can visualize data through graphical representations, enhancing the overall accessibility and usability of the application.
3 | **Redux Toolkit** is chosen for state management in the project. Additionally, the **redux-persist** library is utilized for persisting the Redux state, ensuring that the state is preserved when the application is restarted. **CRUD** operations are performed using API requests, and **Formik** along with **Yup** is preferred for form validation. The UI/UX design is crafted using libraries such as **Material-UI, Tailwind CSS, Material Tailwind, ApexCharts**, and more.
4 | ## Screenshots
5 | 
6 |
7 | [Stock Management System Live Page](https://stock-management-system-esma.netlify.app/)
8 | If you prefer to see Redoc or JSON instead of Swagger, simply replace 'swagger' in the endpoint with 'redoc' or 'json'.
9 | [Swagger Documents](https://stock-management-system-backend.onrender.com/documents/swagger/)
10 |
11 | ## Project Skeleton
12 |
13 | ```
14 | Stock Management System (folder)
15 | |
16 | |
17 | ├── public
18 | │ └── index.html
19 | ├── src
20 | │ ├── app
21 | │ │ └── store.jsx
22 | │ ├── assets
23 | │ │ └── logo.png
24 | │ ├── components
25 | │ │ ├──BarChart.jsx
26 | │ │ ├──BrandCard.jsx
27 | │ │ ├──BrandForm.jsx
28 | │ │ ├──DataMessage.jsx
29 | │ │ ├──FirmCard.jsx
30 | │ │ ├──FirmForm.jsx
31 | │ │ ├──Footer.jsx
32 | │ │ ├──LineChart.jsx
33 | │ │ ├──LoginForm.jsx
34 | │ │ ├──Navbar.jsx
35 | │ │ ├──ProductForm.jsx
36 | │ │ ├──ProductTable.jsx
37 | │ │ ├──RecentReport.jsx
38 | │ │ ├──RegisterForm.jsx
39 | │ │ ├──SaleForm.jsx
40 | │ │ ├──SaleTable.jsx
41 | │ │ ├──Stats.jsx
42 | │ │ └──Switch.jsx
43 | │ ├── features
44 | │ │ ├──authSlice.js
45 | │ │ └──stockSlice.js
46 | │ ├── helpers
47 | │ │ ├── icon.js
48 | │ │ └──ToastNotify.js
49 | │ ├── pages
50 | │ │ ├──Brands.jsx
51 | │ │ ├──Dashboard.jsx
52 | │ │ ├──Firms.jsx
53 | │ │ ├──Login.jsx
54 | │ │ ├──Products.jsx
55 | │ │ ├──Purchases.jsx
56 | │ │ ├──Register.jsx
57 | │ │ ├──Sales.jsx
58 | │ │ └──ScroolToTop.jsx
59 | │ ├── router
60 | │ │ ├── PrivateRouter.jsx
61 | │ │ └── AppRouter.jsx
62 | │ ├── service
63 | │ │ ├── useAuthCalls.js
64 | │ │ ├── useAxios.js
65 | │ │ └── useStockCalls.js
66 | │ ├── App.js
67 | │ ├── index.js
68 | │ ├── index.css
69 | ├── .env
70 | ├── package-lock-json
71 | ├── package.json
72 | ├── tailwind.config.js
73 | └── README.md
74 | ```
75 |
76 | ## Packages Utilized in the Development:
77 |
78 | - **React**: A JavaScript library for building user interfaces, providing a declarative and component-based approach to UI development.
79 | - **React-Redux**: Official Redux bindings for React, enabling seamless integration of Redux state management with React components.
80 | - **Redux Toolkit**: An opinionated, batteries-included Redux library for efficient and simplified Redux usage, including Redux logic and state management utilities.
81 | - **Redux-persist**: A library for persisting Redux state to local storage or other storage mechanisms, ensuring data persistence between sessions.
82 | - **Yup**: A schema validation library for JavaScript, enabling easy and efficient validation of data schemas, particularly useful in form validation.
83 | - **Formik**: A React library for building forms with easy form state management, form validation, and form submission handling.
84 | - **Material-UI**: A popular React UI framework providing a set of pre-designed and customizable UI components based on Google's Material Design guidelines.
85 | - **Tailwind CSS**: A utility-first CSS framework for creating custom and responsive user interfaces with minimal CSS code, focusing on utility classes for styling.
86 | - **Material Tailwind**: A UI kit that combines the utility-first approach of Tailwind CSS with the components and design principles of Material Design.
87 | - **ApexCharts**: A modern JavaScript charting library that offers a wide range of interactive and visually appealing charts for data visualization in web applications.
88 | - **MUI Data Grid**: A feature-rich data grid component for React applications, offering advanced features like sorting, filtering, pagination, and customization options.
89 | - **Axios**: A promise-based HTTP client for making asynchronous HTTP requests in JavaScript environments, commonly used for consuming APIs.
90 | - **React-toastify**: A React library for displaying toast notifications in web applications, providing customizable and user-friendly notifications for various actions.
91 | - **React Router DOM**: A routing library for React applications, allowing for declarative routing and navigation between different views or pages within the application.
92 | - **React Helmet**: A React library for managing document head meta-information, enabling the dynamic manipulation of HTML head elements, including title, meta tags, and more.
93 | - **React Open Graph**: A React library for generating Open Graph meta tags, facilitating the customization and inclusion of Open Graph metadata in web pages for improved sharing on social media platforms.
94 |
95 | ## Tools Utilized in the Development:
96 | - **Postman**: A popular API testing and development tool used for testing backend API endpoints and debugging HTTP requests.
97 | - **Redux DevTools**: A browser extension and development tool that integrates with Redux, facilitating debugging and monitoring of Redux state changes during development.
98 |
99 |
100 |
101 | ## How to use?
102 | - If you have an account, you can log in; otherwise, you can create a new account.
103 | - You can delete or edit existing information at any time.
104 | - Easily add new information through the application.
105 | - You can enter product, firm, brand, etc., update stocks, and sell products currently in stock
106 | - Perform sorting or filtering operations on the displayed data in tabular form.
107 | - Access graphical representation of the data.
108 |
109 | ## Getting Started
110 |
111 | This is an example of how you may give instructions on setting up your project locally.
112 | To get a local copy up and running follow these simple example steps.
113 |
114 | ### Prerequisites
115 |
116 | This is an example of how to list things you need to use the software and how to install them.
117 |
118 | - npm
119 | ```sh
120 | npm install npm@latest -g
121 |
122 | ### Installation
123 |
124 | _Below is an example of how you can instruct your audience on installing and setting up your app. This template doesn't rely on any external dependencies or services._
125 |
126 | 1. Clone the repo
127 | ```sh
128 | git clone https://github.com/esmaaksoy/Stock-Management-System-Frontend
129 | ```
130 | 2. Install NPM packages
131 | ```sh
132 | npm install
133 | ```
134 | 3. Create .env file in home directory.
135 | ```sh
136 | REACT_APP_BASE_URL=https://stock-management-system-backend.onrender.com/
137 | ```
138 | 5. The project is ready, you can start using it now.
139 | You can run:
140 |
141 | `npm start`
142 |
143 | Runs the app in the development mode.\
144 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
145 |
146 | ## API Reference
147 | https://stock-management-system-backend.onrender.com/
148 |
149 | ## İMPORTANT NOTES !
150 |
151 | To use the application, you can log in with the following email and password
152 | email: admin@site.com
153 | password: aA?123456
154 |
155 |
--------------------------------------------------------------------------------
/src/helper/icons.js:
--------------------------------------------------------------------------------
1 | export const loadingIcon = (
2 |
10 |
15 |
16 | );
17 | export const searchIcon = (
18 |
26 |
31 |
32 | );
33 | export const editIcon = (
34 |
42 |
47 |
48 | );
49 | export const deleteIcon = (
50 |
58 |
63 |
64 | );
65 | export const addressIcon = (
66 |
74 |
79 |
84 |
85 | );
86 | export const phoneIcon = (
87 |
95 |
100 |
101 | );
102 | export const EmailIcon = () => (
103 |
110 |
116 |
117 | );
118 | export const PasswordIcon = () => (
119 |
126 |
132 |
138 |
139 | );
140 | export const DolarIcon = () => (
141 |
149 |
154 |
155 | );
156 | export const UsersIcon = () => (
157 |
165 |
170 |
171 | );
172 | export const PurchasesIcon = () => (
173 |
181 |
186 |
187 | );
188 | export const CheckIcon = () => (
189 |
197 |
202 |
203 | );
204 |
--------------------------------------------------------------------------------