From 26f1a057be1595147a663a831b519b7484c94602 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 8 Oct 2025 16:58:40 +0330 Subject: [PATCH] dashboard --- package-lock.json | 363 ++++++++++++++++++ package.json | 1 + src/pages/dashboard/Dashboard.tsx | 64 +++ .../components/DashboardOverview.tsx | 87 +++++ .../components/OrdersChartSection.tsx | 240 ++++++++++++ .../components/OrdersCountSection.tsx | 49 +++ .../components/ProductsCountSection.tsx | 35 ++ src/pages/dashboard/components/StatCard.tsx | 25 ++ .../components/UnreadCommentsSection.tsx | 30 ++ .../components/UsersCountSection.tsx | 30 ++ src/pages/dashboard/hooks/useDashboardData.ts | 16 + src/pages/dashboard/service/Service.ts | 12 + src/pages/dashboard/types/Types.ts | 95 +++++ src/router/Main.tsx | 8 + 14 files changed, 1055 insertions(+) create mode 100644 src/pages/dashboard/Dashboard.tsx create mode 100644 src/pages/dashboard/components/DashboardOverview.tsx create mode 100644 src/pages/dashboard/components/OrdersChartSection.tsx create mode 100644 src/pages/dashboard/components/OrdersCountSection.tsx create mode 100644 src/pages/dashboard/components/ProductsCountSection.tsx create mode 100644 src/pages/dashboard/components/StatCard.tsx create mode 100644 src/pages/dashboard/components/UnreadCommentsSection.tsx create mode 100644 src/pages/dashboard/components/UsersCountSection.tsx create mode 100644 src/pages/dashboard/hooks/useDashboardData.ts create mode 100644 src/pages/dashboard/service/Service.ts create mode 100644 src/pages/dashboard/types/Types.ts diff --git a/package-lock.json b/package-lock.json index 7939c16..33d1153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "react-router-dom": "^7.8.2", "react-spinners": "^0.17.0", "react-toastify": "^11.0.5", + "recharts": "^3.2.1", "swiper": "^11.2.10", "tailwind-merge": "^3.3.1", "yup": "^1.7.0", @@ -1728,6 +1729,32 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", + "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.34", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.34.tgz", @@ -1995,6 +2022,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -2364,6 +2403,69 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "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.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "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/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2418,6 +2520,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.41.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz", @@ -3102,6 +3210,127 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "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/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -3120,6 +3349,12 @@ } } }, + "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/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3239,6 +3474,16 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.39.10", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", + "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -3955,6 +4200,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz", + "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3982,6 +4237,15 @@ "node": ">=0.8.19" } }, + "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/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5003,6 +5267,29 @@ "react-dom": "^16 || ^17 || ^18 || ^19" } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -5143,6 +5430,54 @@ "react-dom": "^18 || ^19" } }, + "node_modules/recharts": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz", + "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==", + "license": "MIT", + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5393,6 +5728,12 @@ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -5655,6 +5996,28 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "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": "7.1.7", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", diff --git a/package.json b/package.json index b985702..2f7a69c 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "react-router-dom": "^7.8.2", "react-spinners": "^0.17.0", "react-toastify": "^11.0.5", + "recharts": "^3.2.1", "swiper": "^11.2.10", "tailwind-merge": "^3.3.1", "yup": "^1.7.0", diff --git a/src/pages/dashboard/Dashboard.tsx b/src/pages/dashboard/Dashboard.tsx new file mode 100644 index 0000000..1c70223 --- /dev/null +++ b/src/pages/dashboard/Dashboard.tsx @@ -0,0 +1,64 @@ +import { type FC } from 'react' +import { useGetDashboard, useGetOrderChart } from './hooks/useDashboardData' +import { useLocation } from 'react-router-dom' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import DashboardOverview from './components/DashboardOverview' +import ProductsCountSection from './components/ProductsCountSection' +import OrdersCountSection from './components/OrdersCountSection' +import UnreadCommentsSection from './components/UnreadCommentsSection' +import UsersCountSection from './components/UsersCountSection' +import OrdersChartSection from './components/OrdersChartSection' + +const Dashboard: FC = () => { + const location = useLocation() + const { data, isLoading, error } = useGetDashboard() + const { data: orderChartData } = useGetOrderChart() + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+ +
+ ) + } + + const renderDashboardContent = () => { + const path = location.pathname + + if (path.includes('products-count')) { + return + } else if (path.includes('orders-count')) { + return + } else if (path.includes('unread-comments')) { + return + } else if (path.includes('users-count')) { + return + } else if (path.includes('orders-chart')) { + return + } else { + // نمایش داشبورد کلی + return + } + } + + return ( +
+ {renderDashboardContent()} +
+ ) +} + + +export default Dashboard \ No newline at end of file diff --git a/src/pages/dashboard/components/DashboardOverview.tsx b/src/pages/dashboard/components/DashboardOverview.tsx new file mode 100644 index 0000000..6cbcfde --- /dev/null +++ b/src/pages/dashboard/components/DashboardOverview.tsx @@ -0,0 +1,87 @@ +import { type FC } from 'react' +import TitleLine from '../../../components/TitleLine' +import { type DashboardData } from '../types/Types' +import { Box, ShoppingCart, Message, User } from 'iconsax-react' +import StatCard from './StatCard' + +const DashboardOverview: FC<{ data?: DashboardData }> = ({ data }) => { + if (!data) return null + + return ( +
+ {/* دیباگ داده‌های API */} +
+

ℹ️ وضعیت اتصال به API

+
+
داده‌های واقعی API: {Array.isArray(data?.FiveDaysAgoSales) ? `✅ ${data.FiveDaysAgoSales.length} آیتم دریافت شد` : '❌ داده‌ای وجود ندارد'}
+
نمودار: {Array.isArray(data?.FiveDaysAgoSales) && data.FiveDaysAgoSales.length > 0 ? '✅ نمایش داده‌های واقعی API' : '⏳ در انتظار داده‌های API'}
+
منطق نمایش: فقط از داده‌های واقعی API استفاده می‌شود
+
+ {Array.isArray(data?.FiveDaysAgoSales) && data.FiveDaysAgoSales.length > 0 && ( +
+
نمونه داده واقعی API:
+
+                            {JSON.stringify(data.FiveDaysAgoSales[0], null, 2)}
+                        
+
+ )} +
+
🎯 نحوه عملکرد نمودار:
+
+ ۱. فقط از داده‌های واقعی API استفاده می‌شود
+ ۲. اگر داده وجود نداشته باشد، نمودار خالی نمایش داده می‌شود
+ ۳. هیچ داده نمونه‌ای استفاده نمی‌شود
+ ۴. ساختار داده‌های مورد انتظار: totalSales, totalOrders +
+
+
+ + + + {/* آمار کلی */} +
+ } + /> + } + /> + } + /> + } + /> +
+ + {/* آمار فروش روزانه */} +
+

آمار فروش روزانه

+
+
+
{data.dailySales.totalOrders}
+
تعداد سفارشات
+
+
+
{data.dailySales.totalSales.toLocaleString()} تومان
+
مجموع فروش
+
+
+
{data.dailySales.itemsSold}
+
تعداد آیتم فروش رفته
+
+
+
+
+ ) +} + +export default DashboardOverview diff --git a/src/pages/dashboard/components/OrdersChartSection.tsx b/src/pages/dashboard/components/OrdersChartSection.tsx new file mode 100644 index 0000000..2893921 --- /dev/null +++ b/src/pages/dashboard/components/OrdersChartSection.tsx @@ -0,0 +1,240 @@ +import { type FC } from 'react' +import TitleLine from '../../../components/TitleLine' +import { type DashboardData, type OrderChartData } from '../types/Types' +import { Chart } from 'iconsax-react' +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts' + +export interface OrdersChartSectionProps { + dashboardData?: DashboardData + orderChartData?: OrderChartData +} + +const OrdersChartSection: FC = ({ + dashboardData, + orderChartData +}) => { + // پردازش داده‌های Order Chart API + const processOrderChartData = (chartData?: OrderChartData) => { + if (chartData && chartData.xAxis && chartData.yAxis) { + return chartData.xAxis.map((xValue, index) => ({ + name: xValue, + value: chartData.yAxis[index] || 0, + sales: chartData.yAxis[index] || 0, + })) + } + return [] + } + + // پردازش داده‌های ۵ روز گذشته برای نمودار (fallback) + const processDashboardChartData = (rawData: unknown) => { + if (Array.isArray(rawData) && rawData.length > 0) { + return (rawData as Array>).slice(-5).map((item, index: number) => ({ + name: `${5 - index} روز پیش`, + sales: (item.totalSales as number) || (item.sales as number) || 0, + orders: (item.totalOrders as number) || (item.orders as number) || 0, + date: item.date || item.created_at || `روز ${5 - index}`, + })) + } + return [] + } + + // اولویت با داده‌های Order Chart API + const orderChartProcessed = processOrderChartData(orderChartData) + const dashboardChartProcessed = processDashboardChartData(dashboardData?.FiveDaysAgoSales) + + // استفاده از داده‌های Order Chart اگر موجود باشد، در غیر این صورت از Dashboard استفاده کن + const chartData = orderChartProcessed.length > 0 ? orderChartProcessed : dashboardChartProcessed + + return ( +
+ + + {/* نمودار فروش ۵ روز گذشته */} +
+
+ +

آمار فروش ۵ روز گذشته

+
+ +
+ {chartData.length > 0 ? ( + + + + + orderChartData ? value.toLocaleString() : `${(value / 1000000).toFixed(1)}M`} + /> + { + if (orderChartData) { + return [ + `${value.toLocaleString()} ${orderChartData.unit || 'تومان'}`, + orderChartData.title || 'مقدار' + ] + } else { + return [ + name === 'sales' ? `${value.toLocaleString()} تومان` : value, + name === 'sales' ? 'فروش' : 'سفارشات' + ] + } + }} + labelStyle={{ color: '#374151' }} + contentStyle={{ + backgroundColor: '#ffffff', + border: '1px solid #e5e7eb', + borderRadius: '8px', + boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' + }} + /> + + + + ) : ( +
+
+ +
+ داده‌ای برای نمایش نمودار وجود ندارد +
+
+ منتظر دریافت داده از API... +
+
+
+ )} +
+
+ + {/* نمودار مقایسه فروش و سفارشات */} +
+

مقایسه فروش و تعداد سفارشات

+ +
+ {chartData.length > 0 ? ( + + + + + orderChartData ? value.toLocaleString() : `${(value / 1000000).toFixed(1)}M`} + /> + + { + if (orderChartData) { + return [ + `${value.toLocaleString()} ${orderChartData.unit || 'تومان'}`, + orderChartData.title || 'مقدار' + ] + } else { + return [ + name === 'sales' ? `${value.toLocaleString()} تومان` : value, + name === 'sales' ? 'فروش' : 'سفارشات' + ] + } + }} + labelStyle={{ color: '#374151' }} + contentStyle={{ + backgroundColor: '#ffffff', + border: '1px solid #e5e7eb', + borderRadius: '8px', + boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)' + }} + /> + + + + + ) : ( +
+
+ +
+ داده‌ای برای نمایش نمودار مقایسه وجود ندارد +
+
+ منتظر دریافت داده از API... +
+
+
+ )} +
+ + {chartData.length > 0 && ( +
+
+
+ فروش (تومان) +
+
+
+ تعداد سفارشات +
+
+ )} +
+ + {/* آمار ماهانه */} +
+

آمار فروش ماهانه

+
+
+
{dashboardData?.monthlySales?.totalOrders || 0}
+
تعداد سفارشات
+
+
+
{dashboardData?.monthlySales?.totalSales?.toLocaleString() || 0} تومان
+
مجموع فروش
+
+
+
{dashboardData?.monthlySales?.itemsSold || 0}
+
تعداد آیتم فروش رفته
+
+
+
+
+ ) +} + +export default OrdersChartSection diff --git a/src/pages/dashboard/components/OrdersCountSection.tsx b/src/pages/dashboard/components/OrdersCountSection.tsx new file mode 100644 index 0000000..964ab59 --- /dev/null +++ b/src/pages/dashboard/components/OrdersCountSection.tsx @@ -0,0 +1,49 @@ +import { type FC } from 'react' +import TitleLine from '../../../components/TitleLine' +import { type DashboardData } from '../types/Types' +import { ShoppingCart } from 'iconsax-react' +import StatCard from './StatCard' + +const OrdersCountSection: FC<{ data?: DashboardData }> = ({ data }) => { + if (!data) return null + + return ( +
+ + +
+ } + /> + } + /> +
+ + {/* آمار فروش روزانه */} +
+

آمار فروش روزانه

+
+
+
{data.dailySales.totalOrders}
+
تعداد سفارشات
+
+
+
{data.dailySales.totalSales.toLocaleString()} تومان
+
مجموع فروش
+
+
+
{data.dailySales.itemsSold}
+
تعداد آیتم فروش رفته
+
+
+
+
+ ) +} + +export default OrdersCountSection diff --git a/src/pages/dashboard/components/ProductsCountSection.tsx b/src/pages/dashboard/components/ProductsCountSection.tsx new file mode 100644 index 0000000..fd4de66 --- /dev/null +++ b/src/pages/dashboard/components/ProductsCountSection.tsx @@ -0,0 +1,35 @@ +import { type FC } from 'react' +import TitleLine from '../../../components/TitleLine' +import { type DashboardData } from '../types/Types' +import { Box, Shop } from 'iconsax-react' +import StatCard from './StatCard' + +const ProductsCountSection: FC<{ data?: DashboardData }> = ({ data }) => { + if (!data) return null + + return ( +
+ + +
+ } + /> + } + /> + } + /> +
+
+ ) +} + +export default ProductsCountSection diff --git a/src/pages/dashboard/components/StatCard.tsx b/src/pages/dashboard/components/StatCard.tsx new file mode 100644 index 0000000..6249dd0 --- /dev/null +++ b/src/pages/dashboard/components/StatCard.tsx @@ -0,0 +1,25 @@ +import { type FC } from 'react' + +interface StatCardProps { + title: string + value: number + icon: React.ReactNode +} + +const StatCard: FC = ({ title, value, icon }) => { + return ( +
+
+
+

{title}

+

{value?.toLocaleString() || 0}

+
+
+ {icon} +
+
+
+ ) +} + +export default StatCard diff --git a/src/pages/dashboard/components/UnreadCommentsSection.tsx b/src/pages/dashboard/components/UnreadCommentsSection.tsx new file mode 100644 index 0000000..527db4d --- /dev/null +++ b/src/pages/dashboard/components/UnreadCommentsSection.tsx @@ -0,0 +1,30 @@ +import { type FC } from 'react' +import TitleLine from '../../../components/TitleLine' +import { type DashboardData } from '../types/Types' +import { Message } from 'iconsax-react' +import StatCard from './StatCard' + +const UnreadCommentsSection: FC<{ data?: DashboardData }> = ({ data }) => { + if (!data) return null + + return ( +
+ + +
+ } + /> + } + /> +
+
+ ) +} + +export default UnreadCommentsSection diff --git a/src/pages/dashboard/components/UsersCountSection.tsx b/src/pages/dashboard/components/UsersCountSection.tsx new file mode 100644 index 0000000..4dfd0ef --- /dev/null +++ b/src/pages/dashboard/components/UsersCountSection.tsx @@ -0,0 +1,30 @@ +import { type FC } from 'react' +import TitleLine from '../../../components/TitleLine' +import { type DashboardData } from '../types/Types' +import { User, Shop } from 'iconsax-react' +import StatCard from './StatCard' + +const UsersCountSection: FC<{ data?: DashboardData }> = ({ data }) => { + if (!data) return null + + return ( +
+ + +
+ } + /> + } + /> +
+
+ ) +} + +export default UsersCountSection diff --git a/src/pages/dashboard/hooks/useDashboardData.ts b/src/pages/dashboard/hooks/useDashboardData.ts new file mode 100644 index 0000000..3d0efee --- /dev/null +++ b/src/pages/dashboard/hooks/useDashboardData.ts @@ -0,0 +1,16 @@ +import { useQuery } from "@tanstack/react-query"; +import * as api from "../service/Service"; + +export const useGetDashboard = () => { + return useQuery({ + queryKey: ["dashboard"], + queryFn: api.getDashboard, + }); +}; + +export const useGetOrderChart = () => { + return useQuery({ + queryKey: ["order-chart"], + queryFn: api.getOrderChart, + }); +}; diff --git a/src/pages/dashboard/service/Service.ts b/src/pages/dashboard/service/Service.ts new file mode 100644 index 0000000..649ec8e --- /dev/null +++ b/src/pages/dashboard/service/Service.ts @@ -0,0 +1,12 @@ +import axios from "@/config/axios"; +import type { DashboardResponse, OrderChartResponse } from "../types/Types"; + +export const getDashboard = async (): Promise => { + const { data } = await axios.get(`/admin/reports`); + return data; +}; + +export const getOrderChart = async (): Promise => { + const { data } = await axios.get(`/admin/orders/chart`); + return data; +}; diff --git a/src/pages/dashboard/types/Types.ts b/src/pages/dashboard/types/Types.ts new file mode 100644 index 0000000..dfca5f1 --- /dev/null +++ b/src/pages/dashboard/types/Types.ts @@ -0,0 +1,95 @@ +// تایپ‌های مربوط به فروش +interface SalesStats { + _id: string | null; + totalSales: number; + totalNetSales: number; + itemsSold: number; + totalOrders: number; +} + +// تایپ‌های مربوط به فروش حمل و نقل +interface ShipmentSalesStats { + _id: string | null; + totalShipmentCost: number; + itemsSold: number; + totalOrders: number; +} + +// تایپ فروشگاه +interface Shop { + _id: string; + shopName: string; +} + +// تایپ جزئیات محصول +interface ProductDetails { + _id: number; + title_fa: string; + title_en: string; + model: string; + description: string; + shop: Shop; +} + +// تایپ محصولات برتر +interface TopProduct { + totalSold: number; + productDetails: ProductDetails; + productId: number; +} + +// تایپ فروش ۵ روز گذشته (ساختار دقیق مشخص نیست، فعلاً unknown استفاده می‌کنیم) +type FiveDaysAgoSales = unknown[]; + +// تایپ اصلی داشبورد +export interface DashboardData { + dailySales: SalesStats; + weeklySales: SalesStats; + monthlySales: SalesStats | null; + yearlySales: SalesStats; + dailyShipmentSales: ShipmentSalesStats; + weeklyShipmentSales: ShipmentSalesStats; + monthlyShipmentSales: ShipmentSalesStats; + yearlyShipmentSales: ShipmentSalesStats; + withdrawalRequests: number; + FiveDaysAgoSales: FiveDaysAgoSales; + topProducts: TopProduct[]; + proccessingOrders: number; + pendingComments: number; + pendingQuestions: number; + sellersCount: number; + usersCount: number; + wholesaleRequestCount: number; + pendingBrandsCount: number; + pendingWarranties: number; + productCount: number; + pendingProductCount: number; +} + +// تایپ داده‌های نمودار سفارش +export interface OrderChartData { + chartType: string; + title: string; + unit: string; + granularity: string; + xAxis: string[]; + yAxis: number[]; + totalSales: number; + _debug: { + aggregatedPeriods: number; + }; +} + +// تایپ پاسخ API نمودار سفارش +export interface OrderChartResponse { + status: number; + success: boolean; + results: OrderChartData; +} + +// تایپ پاسخ API +export interface DashboardResponse { + status: number; + success: boolean; + results: DashboardData; +} diff --git a/src/router/Main.tsx b/src/router/Main.tsx index f60972f..c739b8f 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -61,6 +61,7 @@ import SiteSetting from '@/pages/setting/SiteSetting' import JobsList from '@/pages/jobs/List' import Resumes from '@/pages/jobs/Resume' import ContactUsList from '@/pages/contactUs/List' +import Dashboard from '@/pages/dashboard/Dashboard' const MainRouter: FC = () => { @@ -149,6 +150,13 @@ const MainRouter: FC = () => { } /> } /> + + } /> + } /> + } /> + } /> + } /> + } />