diff --git a/package-lock.json b/package-lock.json index 3af8300c..84689ae1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "react-router-dom": "^6.24.0", "react-spinners": "^0.14.1", "react-toastify": "^10.0.5", + "recharts": "^2.12.7", "save-dev": "0.0.1-security", "socket.io-client": "^4.7.5", "yup": "^1.4.0" @@ -7575,6 +7576,69 @@ "@types/node": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/detect-port": { "version": "1.3.5", "dev": true, @@ -11510,6 +11574,127 @@ "url": "https://github.com/imagemin/cwebp-bin?sponsor=1" } }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "dev": true, @@ -11642,6 +11827,12 @@ "dev": true, "license": "MIT" }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decode-uri-component": { "version": "0.2.2", "dev": true, @@ -14073,7 +14264,6 @@ }, "node_modules/eventemitter3": { "version": "4.0.7", - "dev": true, "license": "MIT" }, "node_modules/events": { @@ -14386,6 +14576,15 @@ "version": "3.1.3", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "dev": true, @@ -16582,6 +16781,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "3.1.1", "dev": true, @@ -22880,6 +23088,21 @@ "react": "^16.3.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-spinners": { "version": "0.14.1", "license": "MIT", @@ -23094,6 +23317,38 @@ "node": ">= 4" } }, + "node_modules/recharts": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/rechoir": { "version": "0.8.0", "dev": true, @@ -25108,7 +25363,6 @@ }, "node_modules/tiny-invariant": { "version": "1.3.3", - "dev": true, "license": "MIT" }, "node_modules/tiny-warning": { @@ -25987,6 +26241,28 @@ "dev": true, "license": "MIT" }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "5.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz", diff --git a/package.json b/package.json index ba875b81..296ec452 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "dev": "webpack serve --config webpack.dev.config.ts", "test": "jest --watchAll=false --coverage", - "build": "webpack --config webpack.prod.config.ts", + "build": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config webpack.prod.config.ts", "lint": "eslint src --ext ts,tsx --ignore-pattern 'node_modules'", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" @@ -36,6 +36,7 @@ "react-router-dom": "^6.24.0", "react-spinners": "^0.14.1", "react-toastify": "^10.0.5", + "recharts": "^2.12.7", "save-dev": "0.0.1-security", "socket.io-client": "^4.7.5", "yup": "^1.4.0" diff --git a/public/assets/images/greenLines.png b/public/assets/images/greenLines.png new file mode 100644 index 00000000..df48563e Binary files /dev/null and b/public/assets/images/greenLines.png differ diff --git a/public/assets/images/redLines.png b/public/assets/images/redLines.png new file mode 100644 index 00000000..ad6b92fa Binary files /dev/null and b/public/assets/images/redLines.png differ diff --git a/src/App.scss b/src/App.scss index 55c86793..b109170a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -33,3 +33,5 @@ @import "./assets/styles/liveChat.scss"; @import "./assets//styles/UserProfile.scss"; @import "./assets/styles/SellerSideProduct.scss"; +@import "./assets/styles/tables.scss"; +@import "./assets/styles/cards.scss"; diff --git a/src/assets/styles/SellerLayout.scss b/src/assets/styles/SellerLayout.scss index a4af7997..477a4cc4 100644 --- a/src/assets/styles/SellerLayout.scss +++ b/src/assets/styles/SellerLayout.scss @@ -1,6 +1,7 @@ .seller__wrapper { height: 100vh; display: flex; + .left__side { width: 26rem; @@ -56,7 +57,7 @@ height: 100%; width: 19.5rem; margin-left: .1rem; - + .dashboard__side { display: flex; flex-direction: column; @@ -110,6 +111,7 @@ flex-direction: column; flex: 1; max-height: 100vh; + } .main__seller__dashboard { @@ -120,19 +122,18 @@ max-height: calc(100vh - 120px); background-color: $container-color; padding: 20px; - + .outlet { - background-color: white; flex: 0 0 60%; border-radius: 20px; min-height: 95%; max-height: 95%; min-width: 70%; max-width: 70%; - box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1); padding: 2rem; overflow-y: scroll; scroll-behavior: smooth; + background-color: white; &::-webkit-scrollbar { display: none; @@ -198,7 +199,7 @@ margin-top: 1rem; color: $text-color; .order-progress { - color: $primary-color; + color: #57ce57; margin-left: 0.5rem; margin-right: 0.5rem; } @@ -257,10 +258,88 @@ .seller-dashboard-container { display: flex; + flex-direction: column; justify-content: center; align-items: center; width: 100%; - height: 100vh; + gap: 30px; + + .statisticCards { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + + .card { + transition: margin-top 0.3s ease; + } + + .card:hover { + margin-top: -20px; + } + } + .chart-container { + width: 95%; + height: auto; + border-radius: 30px; + background-color: #fff; + overflow: hidden; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3); + + + .chart-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + background-color: #fff; + border-bottom: 1px solid #e0e0e0; + .sub-header { + display: flex; + flex-direction: column; + gap: 10px; + h1 { + margin-left: 2%; + } + .legend { + display: flex; + align-items: center; + flex-direction: row; + gap: 20px; + + .dot { + height: 10px; + width: 10px; + border-radius: 50%; + + &.completed { + background-color: #ff7300; + } + + &.cancelled { + background-color: #bfbfbf; + } + } + } + } + + .monthDropDown { + .dropdown-button { + background-color: #ff7300; + color: #fff; + border: none; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + + .arrow-down { + margin-left: 5px; + } + } + } + } + } } @media (max-width: 1024px) { diff --git a/src/assets/styles/adminDashboard.scss b/src/assets/styles/adminDashboard.scss index 4df73b24..dc90dee6 100644 --- a/src/assets/styles/adminDashboard.scss +++ b/src/assets/styles/adminDashboard.scss @@ -1,88 +1,207 @@ .admin__wrapper { - height: 100vh; - display: flex; + height: 100vh; + display: flex; + + .left__side { + width: 26rem; + + &::before { + position: absolute; + content: ""; + width: 6.5rem; + height: 100%; + background-color: $primary-color; + } - .left__side { - width: 26rem; + .icons { + position: relative; + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + .dashboard, + .users, + .logout { + display: flex; + align-items: center; + padding-left: 1rem; + gap: 2.5rem; - &::before { - position: absolute; - content: ""; - width: 6.5rem; - height: 100%; - background-color: $primary-color; + .icon { + font-size: 1.6rem; + color: $white-color; } - .icons { - position: relative; + .icons__title__link { + .text_content { + color: $text-color; + font-size: 1.8rem; + transition: all 0.5s ease-in-out; + text-decoration: none; + cursor: pointer; + } + + .active { display: flex; - flex-direction: column; - height: 100%; - width: 100%; - .dashboard, - .users, - .logout { - display: flex; - align-items: center; - padding-left: 1rem; - gap: 2.5rem; - - .icon { - font-size: 1.6rem; - color: $white-color; - } - - .icons__title__link { - .text_content { - color: $text-color; - font-size: 1.8rem; - transition: all 0.5s ease-in-out; - text-decoration: none; - cursor: pointer; - - } - - .active { - display: flex; - background-color: $menu-hover; - width: 15rem; - padding: 1rem 1rem; - transition: all 0.5s ease-in-out; - align-items: center; - border-radius: .5rem; - } - } - } - .upper{ - display: flex; - flex-direction: column; - position: absolute; - top: 10rem; - gap: 2rem; - } - .logout{ - position: absolute; - bottom: 2rem; - } + background-color: $menu-hover; + width: 15rem; + padding: 1rem 1rem; + transition: all 0.5s ease-in-out; + align-items: center; + border-radius: 0.5rem; + } } + } + .upper { + display: flex; + flex-direction: column; + position: absolute; + top: 10rem; + gap: 2rem; + } + .logout { + position: absolute; + bottom: 2rem; + } } + } + .main__content__dashboard { + display: flex; + flex-direction: column; + flex: 1; + } - .main__content__dashboard { - display: flex; - flex-direction: column; - flex: 1; + .main__dashboard { + display: flex; + justify-content: center; + align-items: center; + flex: 1; + width: 100%; + background: #f0f0f0; + } +} + +.adminOverview { + display: flex; + flex-direction: column; + width: 100%; + gap: 10px; + + .statisticCards { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + + .card { + transition: margin-top 0.3s ease; } + + .card:hover { + margin-top: -30px; + } + } + .chart-container { + width: 90%; + height: auto; + border-radius: 30px; + background-color: #fff; + overflow: hidden; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + margin-left: 5%; - .main__dashboard { + .chart-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + background-color: #fff; + border-bottom: 1px solid #e0e0e0; + .sub-header { display: flex; - justify-content: center; - align-items: center; - flex: 1; - width: 100%; - background-color: $container-color; + flex-direction: column; + gap: 10px; + h1 { + margin-left: 2%; + } + .legend { + display: flex; + align-items: center; + flex-direction: row; + gap: 20px; + + .dot { + height: 10px; + width: 10px; + border-radius: 50%; + + &.completed { + background-color: #ff7300; + } + + &.cancelled { + background-color: #bfbfbf; + } + } + } + } + + .monthDropDown { + .dropdown-button { + background-color: #ff7300; + color: #fff; + border: none; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + + .arrow-down { + margin-left: 5px; + } + } + } } + } +} +.monthDropDown { + position: relative; + display: inline-block; +} + +.dropdown-button { + background-color: #ffffff; + border: 1px solid #cccccc; + padding: 10px; + cursor: pointer; +} + +.arrow-down { + margin-left: 5px; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + background-color: #ffffff; + // border: 1px solid #cccccc; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + z-index: 1; + list-style: none; + padding: 0; + margin: 0; + width: 100%; +} +.dropdown-item { + padding: 10px; + cursor: pointer; } -@media only screen and (min-width: 76.8rem) {} \ No newline at end of file +.dropdown-item:hover { + background-color: #f1f1f1; +} +@media only screen and (min-width: 76.8rem) { +} diff --git a/src/assets/styles/cards.scss b/src/assets/styles/cards.scss new file mode 100644 index 00000000..6ac1d619 --- /dev/null +++ b/src/assets/styles/cards.scss @@ -0,0 +1,46 @@ +.card { + background: #fff; + border-radius: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + margin: 10px; + width: 180px; + + .card-content { + h3 { + margin: 0; + font-size: 16px; + color: #555; + } + + h1 { + margin: 10px 0; + font-size: 32px; + color: #333; + } + + p { + margin: 0; + font-size: 14px; + + &.positive { + color: #57ce57; + } + + &.negative { + color: red; + } + } + } + + .card-graph { + img { + width: 60px; + height: 40px; + } + } + } + \ No newline at end of file diff --git a/src/components/cards/cards.tsx b/src/components/cards/cards.tsx new file mode 100644 index 00000000..c509f70c --- /dev/null +++ b/src/components/cards/cards.tsx @@ -0,0 +1,35 @@ +/* eslint-disable */ +import React from 'react'; +import PropTypes from 'prop-types'; +import greenLines from "../../../public/assets/images/greenLines.png" +import redLines from "../../../public/assets/images/redLines.png" +const Card = ({ title, value, percentage, isPositive, onClick }) => { + return ( +
+
+

{title}

+

{value}

+

+ {isPositive ? '+' : '-'}{percentage}% +

+
+
+ graph +
+
+ ); + }; + + Card.propTypes = { + title: PropTypes.string.isRequired, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + percentage: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + isPositive: PropTypes.bool.isRequired, + onClick: PropTypes.func + }; + + Card.defaultProps = { + onClick: undefined + }; + + export default Card; diff --git a/src/components/layout/SellerLayout.tsx b/src/components/layout/SellerLayout.tsx index df0a9bff..4e72c5a9 100644 --- a/src/components/layout/SellerLayout.tsx +++ b/src/components/layout/SellerLayout.tsx @@ -56,7 +56,7 @@ export const SellerLayout = () => { .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, 5); - const completionPercentage = 12; + const completionPercentage = 80; const getCompletionColor = (percentage) => { if (percentage >= 80) return "#00FF00"; @@ -185,4 +185,4 @@ export const SellerLayout = () => { ); -}; +}; \ No newline at end of file diff --git a/src/pages/admin/Dashboard.tsx b/src/pages/admin/Dashboard.tsx index 895d384a..8f4fbf85 100644 --- a/src/pages/admin/Dashboard.tsx +++ b/src/pages/admin/Dashboard.tsx @@ -11,6 +11,7 @@ import { toast } from "react-toastify"; import { Backdrop, Box, CircularProgress, Typography } from "@mui/material"; import { Meta } from "../../components/Meta"; + export const AdminDashboard = () => { const dispatch = useAppDispatch(); const { isLoading, message, isError } = useAppSelector( @@ -44,7 +45,7 @@ export const AdminDashboard = () => { return; } }, [isError, message, navigate]); - + return ( <> @@ -111,7 +112,8 @@ export const AdminDashboard = () => {
- + +
diff --git a/src/pages/admin/OverView.tsx b/src/pages/admin/OverView.tsx index 90358824..8613639a 100644 --- a/src/pages/admin/OverView.tsx +++ b/src/pages/admin/OverView.tsx @@ -1,12 +1,241 @@ /* eslint-disable */ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Meta } from "../../components/Meta"; - +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; +import Card from "../../components/cards/cards"; +import { + getAllShops, + getAllUsers, + getOrderHistory, +} from "../../store/features/admin/adminSlice"; +import { useAppDispatch, useAppSelector } from "../../store/store"; +import { useNavigate } from 'react-router-dom'; export const OverViewDashboard = () => { + const getMonthName = (dateString) => { + const date = new Date(dateString); + return date.toLocaleString("default", { month: "short" }); + }; + + const predefinedMonths = [ + { name: "Jan", Completed: 0, Cancelled: 0 }, + { name: "Feb", Completed: 0, Cancelled: 0 }, + { name: "Mar", Completed: 0, Cancelled: 0 }, + { name: "Apr", Completed: 0, Cancelled: 0 }, + { name: "May", Completed: 0, Cancelled: 0 }, + { name: "Jun", Completed: 0, Cancelled: 0 }, + { name: "Jul", Completed: 0, Cancelled: 0 }, + { name: "Aug", Completed: 0, Cancelled: 0 }, + { name: "Sep", Completed: 0, Cancelled: 0 }, + { name: "Oct", Completed: 0, Cancelled: 0 }, + { name: "Nov", Completed: 0, Cancelled: 0 }, + { name: "Dec", Completed: 0, Cancelled: 0 }, + ]; + + const [orderStats, setOrderStats] = useState([]); + const [numberOfBuyers, setNumberOfBuyers] = useState(null); + const [numberOfSellers, setNumberOfSellers] = useState(null); + const [numberOfShops, setNumberOfShops] = useState(null); + const [selectedMonth, setSelectedMonth] = useState("All"); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + + useEffect(() => { + try { + const countUsers = async () => { + const usersResponse = await dispatch(getAllUsers()); + const shopResponse = await dispatch(getAllShops()); + const shopNumber = shopResponse.payload.data.shops.length; + const users = usersResponse.payload.data.user; + let buyerCount = 0; + let sellerCount = 0; + + users.forEach((user) => { + if (user.role === "buyer") { + buyerCount++; + } else if (user.role === "seller") { + sellerCount++; + } + }); + setNumberOfBuyers(buyerCount); + setNumberOfSellers(sellerCount); + setNumberOfShops(shopNumber); + }; + countUsers(); + } catch (error) { + console.error("Failed to fetch Users:", error); + } + }, [dispatch, getAllUsers]); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await dispatch(getOrderHistory()).unwrap(); + const aggregatedData = response.data.OrderHistory.reduce( + (acc, order) => { + const monthName = getMonthName(order.orderDate); + const month = acc.find((m) => m.name === monthName); + if (month) { + if (order.status === "completed") { + month.Completed += 1; + } else if (order.status === "canceled") { + month.Cancelled += 1; + } + } + return acc; + }, + [...predefinedMonths] + ); + setOrderStats(aggregatedData); + } catch (error) { + console.error("Failed to fetch order history:", error); + } + }; + fetchData(); + }, [dispatch]); + + const MonthDropDown = () => { + const [isOpen, setIsOpen] = useState(false); + const months = [ + "All", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + const handleMonthSelect = (month) => { + setSelectedMonth(month); + setIsOpen(false); + }; + + return ( +
+ + {isOpen && ( + + )} +
+ ); + }; + + const filteredOrderStats = + selectedMonth === "All" + ? orderStats + : orderStats.filter( + (stat) => stat.name === selectedMonth.substring(0, 3) + ); + return ( <> - -
OverViewDashboard
+
+ + +
+ navigate("/admin/dashboard/users")} + /> + + navigate("/admin/dashboard/users")} + /> + +
+ +
+
+
+

Overview sales

+
+ Completed + Cancelled +
+
+ + +
+ + + + + + + + + + + +
+
); }; diff --git a/src/pages/seller/SellerDashboard.tsx b/src/pages/seller/SellerDashboard.tsx index 5ddd2535..a92aa074 100644 --- a/src/pages/seller/SellerDashboard.tsx +++ b/src/pages/seller/SellerDashboard.tsx @@ -1,14 +1,208 @@ /* eslint-disable */ -import React from "react"; -import LiveChat from "../../components/live-chat/LiveChat"; +import React, { useEffect, useState } from "react"; +import Card from "../../components/cards/cards"; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, +} from "recharts"; +import { useAppDispatch, useAppSelector } from "../../store/store"; +import { + fetchSellerCollectionProduct, + sellerGetAllProducts, + sellerGetOrderHistory, +} from "../../store/features/product/sellerCollectionProductsSlice"; +import { useNavigate } from 'react-router-dom'; +import productSlice from "../../store/features/product/productSlice"; + + const SellerDashboard = () => { + + + const predefinedMonths = [ + { name: "Jan", Completed: 0, Cancelled: 0 }, + { name: "Feb", Completed: 0, Cancelled: 0 }, + { name: "Mar", Completed: 0, Cancelled: 0 }, + { name: "Apr", Completed: 0, Cancelled: 0 }, + { name: "May", Completed: 0, Cancelled: 0 }, + { name: "Jun", Completed: 0, Cancelled: 0 }, + { name: "Jul", Completed: 0, Cancelled: 0 }, + { name: "Aug", Completed: 0, Cancelled: 0 }, + { name: "Sep", Completed: 0, Cancelled: 0 }, + { name: "Oct", Completed: 0, Cancelled: 0 }, + { name: "Nov", Completed: 0, Cancelled: 0 }, + { name: "Dec", Completed: 0, Cancelled: 0 }, + ]; + + const getMonthName = (dateString) => { + const date = new Date(dateString); + return date.toLocaleString("default", { month: "short" }); + }; + + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const [numberOfProducts, setNumberOfProducts] = useState(null); + const [orderStats, setOrderStats] = useState([]); + const [numberOfOrders, setNumberOfOrders] = useState(null); + const [selectedMonth, setSelectedMonth] = useState("All"); + + + useEffect(() => { + try { + const countData = async () => { + const orderHistoryResponse = await dispatch(sellerGetOrderHistory()); + const orderNumber = orderHistoryResponse.payload.data.order.length; + setNumberOfOrders(orderNumber); + const porductResponse = await dispatch(fetchSellerCollectionProduct()); + const data:any = porductResponse.payload; + const numberOfProduct = data.data.products.length; + setNumberOfProducts(numberOfProduct); + + const aggregatedData = orderHistoryResponse.payload.data.order.reduce( + (acc, order) => { + const monthName = getMonthName(order.orderDate); + const month = acc.find((m) => m.name === monthName); + if (month) { + if (order.status === "completed") { + month.Completed += 1; + } else if (order.status === "canceled") { + month.Cancelled += 1; + } + } + return acc; + }, + [...predefinedMonths] + ); + setOrderStats(aggregatedData); + }; + countData(); + } catch (error) { + console.error("Failed to fetch data:", error); + } + }, [dispatch]); + + const MonthDropDown = () => { + const [isOpen, setIsOpen] = useState(false); + const months = [ + "All", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + const handleMonthSelect = (month) => { + setSelectedMonth(month); + setIsOpen(false); + }; + + return ( +
+ + {isOpen && ( + + )} +
+ ); + }; + + const filteredOrderStats = + selectedMonth === "All" + ? orderStats + : orderStats.filter( + (stat) => stat.name === selectedMonth.substring(0, 3) + ); + return ( <> -
SellerDashboard
- {/* */} +
+
+ + + navigate("/seller/products")} +/> +
+
+
+
+

Overview sales

+
+ Completed + Cancelled +
+
+ +
+ + + + + + + + + + +
+
); }; -export default SellerDashboard; +export default SellerDashboard; \ No newline at end of file diff --git a/src/store/features/admin/adminService.tsx b/src/store/features/admin/adminService.tsx index 2505caff..5bf1eba0 100644 --- a/src/store/features/admin/adminService.tsx +++ b/src/store/features/admin/adminService.tsx @@ -21,12 +21,25 @@ const updateUserStatus = async (userId: string, status: string) => { const response = await axiosInstance.put(`/api/user/admin-update-user-status/${userId}`, {status}); return response.data; } +const getOrderHistory = async () => { + const response = await axiosInstance.get(`/api/cart/admin-get-order-history`); + return response.data; +} +const getAllShops = async () => { + const response = await axiosInstance.get(`/api/shop/admin-get-shops`); + return response.data; +} + + + const adminService = { getAllUsers, getUserById, updateUserRole, updateUserStatus, + getOrderHistory, + getAllShops } export default adminService; \ No newline at end of file diff --git a/src/store/features/admin/adminSlice.tsx b/src/store/features/admin/adminSlice.tsx index d59d9a2d..8f106688 100644 --- a/src/store/features/admin/adminSlice.tsx +++ b/src/store/features/admin/adminSlice.tsx @@ -48,6 +48,22 @@ export const updateUserStatus = createAsyncThunk('admin/updateUserStatus',async( return thunkApi.rejectWithValue(getErrorMessage(error)); } }); +export const getOrderHistory = createAsyncThunk('admin/getOrderHistory', async (_, thunkApi) => { + try { + const response = await adminService.getOrderHistory(); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } +}); +export const getAllShops = createAsyncThunk('admin/admin-get-shops', async (_, thunkApi) => { + try { + const response = await adminService.getAllShops(); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } +}) const adminSlice = createSlice({ name: 'admin', @@ -110,7 +126,43 @@ const adminSlice = createSlice({ state.isLoading = false; state.message = action.payload.message; toast.error(state.message); - }); + }) + .addCase(getOrderHistory.pending, (state) => { + state.isLoading = true; + state.isError = false; + state.isSuccess = false; + }) + .addCase(getOrderHistory.fulfilled, (state, action:PayloadAction) => { + state.isLoading = false; + state.isError = false; + state.isSuccess = true; + state.users = action.payload.data.user; + state.message = action.payload.message; + + }) + .addCase(getOrderHistory.rejected, (state, action:PayloadAction) => { + state.isLoading = false; + state.isError = true; + state.message = action.payload || action.payload.message; + }) + .addCase(getAllShops.pending, (state) => { + state.isLoading = true; + state.isError = false; + state.isSuccess = false; + }) + .addCase(getAllShops.fulfilled, (state, action:PayloadAction) => { + state.isLoading = false; + state.isError = false; + state.isSuccess = true; + state.users = action.payload.data.user; + state.message = action.payload.message; + + }) + .addCase(getAllShops.rejected, (state, action:PayloadAction) => { + state.isLoading = false; + state.isError = true; + state.message = action.payload || action.payload.message; + }) }, }); diff --git a/src/store/features/product/productService.tsx b/src/store/features/product/productService.tsx index 7dc4ce90..0d221064 100644 --- a/src/store/features/product/productService.tsx +++ b/src/store/features/product/productService.tsx @@ -98,6 +98,14 @@ const addSellerProduct = async (newProductData: FormData) => { throw new Error(getErrorMessage(error)) } } +const sellerGetAllProducts = async () => { + const response = await axiosInstance.get(`/api/shop/seller-get-products`); + return response.data; +} +const sellerGetOrderHistory = async () => { + const response = await axiosInstance.get(`/api/shop/seller-get-orderHistory`); + return response.data; +} const productService = { fetchProducts, @@ -109,6 +117,8 @@ const productService = { fetchSellerSingleProduct, updateSellerProduct, addSellerProduct, - updateSellerProductStatus + updateSellerProductStatus, + sellerGetAllProducts, + sellerGetOrderHistory } export default productService; \ No newline at end of file diff --git a/src/store/features/product/sellerCollectionProductsSlice.tsx b/src/store/features/product/sellerCollectionProductsSlice.tsx index f9bea0b2..6e09d893 100644 --- a/src/store/features/product/sellerCollectionProductsSlice.tsx +++ b/src/store/features/product/sellerCollectionProductsSlice.tsx @@ -26,7 +26,22 @@ export const fetchSellerCollectionProduct = createAsyncThunk { + try { + const response = await productService.sellerGetAllProducts(); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } +}) +export const sellerGetOrderHistory = createAsyncThunk('seller/seller-get-orderHisory', async (_, thunkApi) => { + try { + const response = await productService.sellerGetOrderHistory(); + return response; + } catch (error) { + return thunkApi.rejectWithValue(getErrorMessage(error)); + } +}) const sellerCollectionProductsSlice = createSlice({ name: "sellerCollectionProducts", @@ -49,6 +64,38 @@ const sellerCollectionProductsSlice = createSlice({ state.isError = false; state.isSuccess = false; state.message = action.payload.message || null + }) + .addCase(sellerGetAllProducts.pending, (state) => { + state.isLoading = true; + state.isError = null; + state.isSuccess = false; + }) + .addCase(sellerGetAllProducts.fulfilled, (state, action: PayloadAction) => { + state.isLoading = false; + state.isSuccess = true; + state.data = action.payload.data; + }) + .addCase(sellerGetAllProducts.rejected, (state, action: PayloadAction) => { + state.isLoading = false; + state.isError = false; + state.isSuccess = false; + state.message = action.payload.message || null + }) + .addCase(sellerGetOrderHistory.pending, (state) => { + state.isLoading = true; + state.isError = null; + state.isSuccess = false; + }) + .addCase(sellerGetOrderHistory.fulfilled, (state, action: PayloadAction) => { + state.isLoading = false; + state.isSuccess = true; + state.data = action.payload.data; + }) + .addCase(sellerGetOrderHistory.rejected, (state, action: PayloadAction) => { + state.isLoading = false; + state.isError = false; + state.isSuccess = false; + state.message = action.payload.message || null }); } })