├── .gitignore ├── README.md ├── docs └── store-analytics-logo.PNG ├── v1 ├── .babelrc.js ├── .gitignore ├── .npmignore ├── .yarnrc.yml ├── LICENSE ├── README.md ├── datasource.js ├── docs │ ├── medusa-store-analytics-1.PNG │ ├── medusa-store-analytics-2.PNG │ └── medusa-store-analytics-3.PNG ├── index.js ├── medusa-config.js ├── package copy.json ├── package.json ├── src │ ├── admin │ │ └── routes │ │ │ └── analytics │ │ │ └── page.tsx │ ├── api │ │ ├── README.md │ │ └── admin │ │ │ ├── customers-analytics │ │ │ └── [kind] │ │ │ │ └── route.ts │ │ │ ├── marketing-analytics │ │ │ └── [kind] │ │ │ │ └── route.ts │ │ │ ├── orders-analytics │ │ │ └── [kind] │ │ │ │ └── route.ts │ │ │ ├── products-analytics │ │ │ └── [kind] │ │ │ │ └── route.ts │ │ │ ├── reports-analytics │ │ │ └── [kind] │ │ │ │ └── route.ts │ │ │ └── sales-analytics │ │ │ └── [kind] │ │ │ └── route.ts │ ├── index.ts │ ├── jobs │ │ └── README.md │ ├── loaders │ │ └── README.md │ ├── migrations │ │ └── README.md │ ├── models │ │ └── README.md │ ├── services │ │ ├── customersAnalytics.ts │ │ ├── marketingAnalytics.ts │ │ ├── ordersAnalytics.ts │ │ ├── pdf-templates │ │ │ ├── common.ts │ │ │ ├── customers-template.ts │ │ │ ├── hr.ts │ │ │ ├── orders-template.ts │ │ │ ├── products-template.ts │ │ │ └── sales-template.ts │ │ ├── productsAnalytics.ts │ │ ├── reportsAnalytics.ts │ │ ├── salesAnalytics.ts │ │ └── utils │ │ │ ├── currency.ts │ │ │ ├── dateTransformations.ts │ │ │ └── types.ts │ ├── subscribers │ │ └── README.md │ └── ui-components │ │ ├── common │ │ ├── chart-components.tsx │ │ ├── icon-comparison.tsx │ │ ├── overview-components.tsx │ │ ├── percentage-comparison.tsx │ │ ├── popularity-table.tsx │ │ └── utils │ │ │ └── chartUtils.ts │ │ ├── customers │ │ ├── cumulative-history │ │ │ ├── cumulative-customers-card.tsx │ │ │ └── cumulative-customers-chart.tsx │ │ ├── customers-by-new-chart.tsx │ │ ├── customers-number-overview.tsx │ │ ├── customers-overview-card.tsx │ │ ├── repeat-customer-rate │ │ │ ├── customers-repeat-customer-rate-number.tsx │ │ │ ├── customers-repeat-customer-rate.tsx │ │ │ ├── order-frequency-distribution-chart.tsx │ │ │ └── order-frequency-distribution.tsx │ │ ├── retention-customer-rate │ │ │ ├── customers-retention-customer-rate-number.tsx │ │ │ └── customers-retention-customer-rate.tsx │ │ └── types.ts │ │ ├── index.ts │ │ ├── marketing │ │ ├── discounts-top-by-count.tsx │ │ └── discounts-top-table.tsx │ │ ├── orders │ │ ├── orders-by-new-chart.tsx │ │ ├── orders-number-overview.tsx │ │ ├── orders-overview-card.tsx │ │ ├── orders-payment-provider-card.tsx │ │ ├── orders-payment-provider-chart.tsx │ │ └── types.ts │ │ ├── products │ │ ├── out_of_the_stock_variants │ │ │ ├── helpers.tsx │ │ │ ├── out-of-the-stock-variants-all.tsx │ │ │ ├── out-of-the-stock-variants-by-count.tsx │ │ │ └── out-of-the-stock-variants-table.tsx │ │ ├── products-sold-count.tsx │ │ ├── returned_variants │ │ │ ├── returned-variants-by-count.tsx │ │ │ └── returned-variants-table.tsx │ │ ├── variants-top-by-count.tsx │ │ └── variants-top-table.tsx │ │ ├── sales │ │ ├── refunds │ │ │ ├── refunds-numbers.tsx │ │ │ └── refunds-overview-card.tsx │ │ ├── regions-popularity-card.tsx │ │ ├── sales-channel-popularity-card.tsx │ │ ├── sales-number-overview.tsx │ │ ├── sales-overview-card.tsx │ │ ├── sales-total-chart.tsx │ │ └── types.ts │ │ ├── tabs │ │ ├── customers.tsx │ │ ├── orders.tsx │ │ ├── overview.tsx │ │ ├── pro.tsx │ │ ├── products.tsx │ │ └── sales.tsx │ │ └── utils │ │ ├── helpers.ts │ │ └── types.ts ├── tsconfig.admin.json ├── tsconfig.json ├── tsconfig.server.json └── tsconfig.spec.json └── v2 ├── README.md ├── docs ├── medusa-store-analytics-1.PNG └── medusa-store-analytics-2.PNG ├── package.json ├── src ├── admin │ └── routes │ │ └── analytics │ │ └── page.tsx ├── api │ ├── README.md │ └── admin │ │ ├── customers-analytics │ │ └── [kind] │ │ │ └── route.ts │ │ ├── helpers-analytics │ │ └── pro-check │ │ │ └── route.ts │ │ ├── marketing-analytics │ │ └── [kind] │ │ │ └── route.ts │ │ ├── orders-analytics │ │ └── [kind] │ │ │ └── route.ts │ │ ├── products-analytics │ │ └── [kind] │ │ │ └── route.ts │ │ ├── reports-analytics │ │ └── [kind] │ │ │ └── route.ts │ │ └── sales-analytics │ │ └── [kind] │ │ └── route.ts ├── index.ts ├── jobs │ └── README.md ├── loaders │ └── README.md ├── migrations │ └── README.md ├── models │ └── README.md ├── modules │ └── store-analytics │ │ ├── index.ts │ │ ├── service.ts │ │ ├── services │ │ ├── customersAnalytics.ts │ │ ├── index.ts │ │ ├── marketingAnalytics.ts │ │ ├── ordersAnalytics.ts │ │ ├── pdf-templates │ │ │ ├── common.ts │ │ │ ├── customers-template.ts │ │ │ ├── hr.ts │ │ │ ├── orders-template.ts │ │ │ ├── products-template.ts │ │ │ └── sales-template.ts │ │ ├── productsAnalytics.ts │ │ ├── reportsAnalytics.ts │ │ ├── salesAnalytics.ts │ │ └── utils │ │ │ ├── currency.ts │ │ │ └── types.ts │ │ └── utils │ │ ├── currency.ts │ │ ├── dateTransformations.ts │ │ └── types.ts ├── subscribers │ └── README.md └── ui-components │ ├── common │ ├── chart-components.tsx │ ├── icon-comparison.tsx │ ├── overview-components.tsx │ ├── percentage-comparison.tsx │ ├── popularity-table.tsx │ └── utils │ │ └── chartUtils.ts │ ├── customers │ ├── cumulative-history │ │ ├── cumulative-customers-card.tsx │ │ └── cumulative-customers-chart.tsx │ ├── customers-by-new-chart.tsx │ ├── customers-number-overview.tsx │ ├── customers-overview-card.tsx │ ├── repeat-customer-rate │ │ ├── customers-repeat-customer-rate-number.tsx │ │ ├── customers-repeat-customer-rate.tsx │ │ ├── order-frequency-distribution-chart.tsx │ │ └── order-frequency-distribution.tsx │ ├── retention-customer-rate │ │ ├── customers-retention-customer-rate-number.tsx │ │ └── customers-retention-customer-rate.tsx │ └── types.ts │ ├── index.ts │ ├── marketing │ ├── discounts-top-by-count.tsx │ └── discounts-top-table.tsx │ ├── orders │ ├── orders-by-new-chart.tsx │ ├── orders-number-overview.tsx │ ├── orders-overview-card.tsx │ ├── orders-payment-provider-card.tsx │ ├── orders-payment-provider-chart.tsx │ └── types.ts │ ├── products │ ├── out_of_the_stock_variants │ │ ├── helpers.tsx │ │ ├── out-of-the-stock-variants-all.tsx │ │ ├── out-of-the-stock-variants-by-count.tsx │ │ └── out-of-the-stock-variants-table.tsx │ ├── products-sold-count.tsx │ ├── returned_variants │ │ ├── returned-variants-by-count.tsx │ │ └── returned-variants-table.tsx │ ├── variants-top-by-count.tsx │ └── variants-top-table.tsx │ ├── sales │ ├── refunds │ │ ├── refunds-numbers.tsx │ │ └── refunds-overview-card.tsx │ ├── regions-popularity-card.tsx │ ├── sales-channel-popularity-card.tsx │ ├── sales-number-overview.tsx │ ├── sales-overview-card.tsx │ ├── sales-total-chart.tsx │ └── types.ts │ ├── tabs │ ├── customers.tsx │ ├── orders.tsx │ ├── overview.tsx │ ├── pro.tsx │ ├── products.tsx │ └── sales.tsx │ └── utils │ ├── helpers.ts │ └── types.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | /v2/node_modules 7 | /v2/.medusa 8 | yarn-error.log 9 | 10 | .idea 11 | 12 | coverage 13 | 14 | !src/** 15 | 16 | ./tsconfig.tsbuildinfo 17 | package-lock.json 18 | yarn.lock 19 | medusa-db.sql 20 | build 21 | .cache 22 | 23 | .yarn/* 24 | !.yarn/patches 25 | !.yarn/plugins 26 | !.yarn/releases 27 | !.yarn/sdks 28 | !.yarn/versions 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Medusa store-analytics logo 4 | 5 |

6 | 7 |

8 | Medusa store-analytics 9 |

10 | 11 | Medusa "store-analytics" is a plugin which shows analytics data of your store, including orders, sales and other useful information. 12 | 13 | ### Why? 14 | 15 | Knowledge about your store is crucial to take proper action to increase the sales. Analytics data can show various things like what is a most popular region, sales channel or on which day people are buying the most. Every such data may help to find problem and possible solutions. 16 | 17 | ### Supported Medusa versions 18 | 19 | Choose README dependent on your Medusa version. 20 | 21 |

22 | Medusa V1 23 |

24 | 25 | [README](https://github.com/RSC-Labs/medusa-store-analytics/blob/main/v1/README.md) 26 | 27 |

28 | Medusa V2 29 |

30 | 31 | [README](https://github.com/RSC-Labs/medusa-store-analytics/blob/main/v2/README.md) 32 | 33 | ## Supported statistics 34 | 35 | ### General 36 | 37 | | Name | Status | 38 | | --- | --- | 39 | | 4 ranges of dates | :white_check_mark: | 40 | | Comparison across date ranges | :white_check_mark: | 41 | | Filtering by orders' status | :white_check_mark: | 42 | 43 | ### Orders 44 | 45 | | Name | Status | 46 | | --- | --- | 47 | | Orders by time | :white_check_mark: | 48 | | Orders chart | :white_check_mark: | 49 | | Regions popularity | :white_check_mark: | 50 | | Sales channel popularity | :white_check_mark: | 51 | | Orders frequency distribution | :white_check_mark: | 52 | | Payment provider popularity | :white_check_mark: | 53 | 54 | ### Sales 55 | 56 | | Name | Status | 57 | | --- | --- | 58 | | Sales by time | :white_check_mark: | 59 | | Sales by currency code | :white_check_mark: | 60 | | Sales chart | :white_check_mark: | 61 | | Refunds | :white_check_mark: | 62 | 63 | ### Customers 64 | 65 | 66 | | Name | Status | 67 | | --- | --- | 68 | | New customers by time | :white_check_mark: | 69 | | Repeat customer rate | :white_check_mark: | 70 | | Customers chart | :white_check_mark: | 71 | | Cumulative customers by time | :white_check_mark: | 72 | 73 | ### Products 74 | 75 | | Name | Status | 76 | | --- | --- | 77 | | Top variants | :white_check_mark: | 78 | | Top returned variants | :white_check_mark: | 79 | | Products sold count | :white_check_mark: | 80 | | Out of the stock variants | :white_check_mark: | 81 | 82 | ### Marketing 83 | 84 | | Name | Status | 85 | | --- | --- | 86 | | Top discounts | :white_check_mark: | 87 | 88 | --- 89 | 90 | © 2024 RSC https://rsoftcon.com/ 91 | -------------------------------------------------------------------------------- /docs/store-analytics-logo.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSC-Labs/medusa-store-analytics/4d04707866d80087d9b48f57a99a0efed85e0e1e/docs/store-analytics-logo.PNG -------------------------------------------------------------------------------- /v1/.babelrc.js: -------------------------------------------------------------------------------- 1 | let ignore = [`**/dist`] 2 | 3 | // Jest needs to compile this code, but generally we don't want this copied 4 | // to output folders 5 | if (process.env.NODE_ENV !== `test`) { 6 | ignore.push(`**/__tests__`) 7 | } 8 | 9 | module.exports = { 10 | presets: [["babel-preset-medusa-package"], ["@babel/preset-typescript"]], 11 | ignore, 12 | } 13 | -------------------------------------------------------------------------------- /v1/.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | .env 3 | .DS_Store 4 | /uploads 5 | /node_modules 6 | yarn-error.log 7 | 8 | .idea 9 | 10 | coverage 11 | 12 | !src/** 13 | 14 | ./tsconfig.tsbuildinfo 15 | package-lock.json 16 | yarn.lock 17 | medusa-db.sql 18 | build 19 | .cache 20 | 21 | .yarn/* 22 | !.yarn/patches 23 | !.yarn/plugins 24 | !.yarn/releases 25 | !.yarn/sdks 26 | !.yarn/versions 27 | -------------------------------------------------------------------------------- /v1/.npmignore: -------------------------------------------------------------------------------- 1 | /lib 2 | node_modules 3 | .DS_store 4 | .env* 5 | /*.js 6 | !index.js 7 | yarn.lock 8 | src 9 | .gitignore 10 | .eslintrc 11 | .babelrc 12 | .prettierrc 13 | build 14 | .cache 15 | .yarn 16 | uploads 17 | 18 | # These are files that are included in a 19 | # Medusa project and can be removed from a 20 | # plugin project 21 | medusa-config.js 22 | Dockerfile 23 | medusa-db.sql 24 | develop.sh -------------------------------------------------------------------------------- /v1/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /v1/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 RSC-Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /v1/datasource.js: -------------------------------------------------------------------------------- 1 | const { DataSource } = require("typeorm") 2 | 3 | const AppDataSource = new DataSource({ 4 | type: "postgres", 5 | port: 5432, 6 | username: "postgres", 7 | password: "postgres", 8 | database: "medusa-z0g5", 9 | entities: [ 10 | ], 11 | migrations: [ 12 | "dist/migrations/*.js", 13 | ], 14 | autoLoadEntities: true 15 | }) 16 | 17 | module.exports = { 18 | datasource: AppDataSource, 19 | } -------------------------------------------------------------------------------- /v1/docs/medusa-store-analytics-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSC-Labs/medusa-store-analytics/4d04707866d80087d9b48f57a99a0efed85e0e1e/v1/docs/medusa-store-analytics-1.PNG -------------------------------------------------------------------------------- /v1/docs/medusa-store-analytics-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSC-Labs/medusa-store-analytics/4d04707866d80087d9b48f57a99a0efed85e0e1e/v1/docs/medusa-store-analytics-2.PNG -------------------------------------------------------------------------------- /v1/docs/medusa-store-analytics-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSC-Labs/medusa-store-analytics/4d04707866d80087d9b48f57a99a0efed85e0e1e/v1/docs/medusa-store-analytics-3.PNG -------------------------------------------------------------------------------- /v1/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const { GracefulShutdownServer } = require("medusa-core-utils") 3 | 4 | const loaders = require("@medusajs/medusa/dist/loaders/index").default 5 | 6 | ;(async() => { 7 | async function start() { 8 | const app = express() 9 | const directory = process.cwd() 10 | 11 | try { 12 | const { container } = await loaders({ 13 | directory, 14 | expressApp: app 15 | }) 16 | const configModule = container.resolve("configModule") 17 | const port = process.env.PORT ?? configModule.projectConfig.port ?? 9000 18 | 19 | const server = GracefulShutdownServer.create( 20 | app.listen(port, (err) => { 21 | if (err) { 22 | return 23 | } 24 | console.log(`Server is ready on port: ${port}`) 25 | }) 26 | ) 27 | 28 | // Handle graceful shutdown 29 | const gracefulShutDown = () => { 30 | server 31 | .shutdown() 32 | .then(() => { 33 | console.info("Gracefully stopping the server.") 34 | process.exit(0) 35 | }) 36 | .catch((e) => { 37 | console.error("Error received when shutting down the server.", e) 38 | process.exit(1) 39 | }) 40 | } 41 | process.on("SIGTERM", gracefulShutDown) 42 | process.on("SIGINT", gracefulShutDown) 43 | } catch (err) { 44 | console.error("Error starting server", err) 45 | process.exit(1) 46 | } 47 | } 48 | 49 | await start() 50 | })() 51 | -------------------------------------------------------------------------------- /v1/medusa-config.js: -------------------------------------------------------------------------------- 1 | const dotenv = require("dotenv"); 2 | 3 | let ENV_FILE_NAME = ""; 4 | switch (process.env.NODE_ENV) { 5 | case "production": 6 | ENV_FILE_NAME = ".env.production"; 7 | break; 8 | case "staging": 9 | ENV_FILE_NAME = ".env.staging"; 10 | break; 11 | case "test": 12 | ENV_FILE_NAME = ".env.test"; 13 | break; 14 | case "development": 15 | default: 16 | ENV_FILE_NAME = ".env"; 17 | break; 18 | } 19 | 20 | try { 21 | dotenv.config({ path: process.cwd() + "/" + ENV_FILE_NAME }); 22 | } catch (e) {} 23 | 24 | // CORS when consuming Medusa from admin 25 | const ADMIN_CORS = 26 | process.env.ADMIN_CORS || "http://localhost:7000,http://localhost:7001"; 27 | 28 | // CORS to avoid issues when consuming Medusa from a client 29 | const STORE_CORS = process.env.STORE_CORS || "http://localhost:8000"; 30 | 31 | const DATABASE_URL = 32 | process.env.DATABASE_URL || "postgres://localhost/medusa-starter-default"; 33 | 34 | const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379"; 35 | 36 | const plugins = [ 37 | 38 | ]; 39 | 40 | const modules = { 41 | 42 | }; 43 | 44 | /** @type {import('@medusajs/medusa').ConfigModule["projectConfig"]} */ 45 | const projectConfig = { 46 | jwtSecret: process.env.JWT_SECRET, 47 | cookieSecret: process.env.COOKIE_SECRET, 48 | store_cors: STORE_CORS, 49 | database_url: DATABASE_URL, 50 | admin_cors: ADMIN_CORS, 51 | // Uncomment the following lines to enable REDIS 52 | // redis_url: REDIS_URL 53 | }; 54 | 55 | /** @type {import('@medusajs/medusa').ConfigModule} */ 56 | module.exports = { 57 | projectConfig, 58 | plugins, 59 | modules, 60 | }; 61 | -------------------------------------------------------------------------------- /v1/package copy.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rsc-labs/medusa-store-analytics", 3 | "version": "0.14.2", 4 | "description": "Get analytics data about your store", 5 | "author": "RSC Labs (https://rsoftcon.com)", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/RSC-Labs/medusa-store-analytics" 12 | }, 13 | "keywords": [ 14 | "medusa-plugin", 15 | "medusa-plugin-analytics", 16 | "analytics", 17 | "statistics", 18 | "store" 19 | ], 20 | "scripts": { 21 | "clean": "cross-env ./node_modules/.bin/rimraf dist", 22 | "build": "cross-env npm run clean && npm run build:server && npm run build:admin", 23 | "build:server": "cross-env npm run clean && tsc -p tsconfig.json", 24 | "build:admin": "cross-env medusa-admin build", 25 | "prepare": "cross-env NODE_ENV=production npm run build:server && medusa-admin bundle" 26 | }, 27 | "dependencies": { 28 | "@medusajs/admin": "7.1.17", 29 | "@medusajs/ui": "^3.0.0", 30 | "@medusajs/icons": "1.2.2", 31 | "@medusajs/utils": "1.11.11", 32 | "@tanstack/react-query": "4.22", 33 | "medusa-interfaces": "^1.3.7", 34 | "medusa-react": "9.0.18", 35 | "@mui/material": "^5.15.3", 36 | "typeorm": "^0.3.16", 37 | "react-hook-form": "^7.49.2", 38 | "@emotion/react": "^11.11.3", 39 | "@emotion/styled": "11.13.0", 40 | "recharts": "^2.10.3", 41 | "pdfkit": "^0.15.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/cli": "^7.14.3", 45 | "@babel/core": "^7.14.3", 46 | "@medusajs/medusa": "^1.20.7", 47 | "@types/express": "^4.17.13", 48 | "babel-preset-medusa-package": "^1.1.19", 49 | "cross-env": "^7.0.3", 50 | "eslint": "^6.8.0", 51 | "rimraf": "^3.0.2", 52 | "typescript": "^4.5.2" 53 | }, 54 | "peerDependencies": { 55 | "@medusajs/medusa": "^1.20.7", 56 | "react": "^18.2.0", 57 | "react-router-dom": "^6.13.0" 58 | }, 59 | "jest": { 60 | "globals": { 61 | "ts-jest": { 62 | "tsconfig": "tsconfig.spec.json" 63 | } 64 | }, 65 | "moduleFileExtensions": [ 66 | "js", 67 | "json", 68 | "ts" 69 | ], 70 | "testPathIgnorePatterns": [ 71 | "/node_modules/", 72 | "/node_modules/" 73 | ], 74 | "rootDir": "src", 75 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|js)$", 76 | "transform": { 77 | ".ts": "ts-jest" 78 | }, 79 | "collectCoverageFrom": [ 80 | "**/*.(t|j)s" 81 | ], 82 | "coverageDirectory": "./coverage", 83 | "testEnvironment": "node" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /v1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rsc-labs/medusa-store-analytics", 3 | "version": "0.15.0", 4 | "description": "Get analytics data about your store", 5 | "author": "RSC Labs (https://rsoftcon.com)", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/RSC-Labs/medusa-store-analytics" 12 | }, 13 | "keywords": [ 14 | "medusa-plugin", 15 | "medusa-plugin-analytics", 16 | "analytics", 17 | "statistics", 18 | "store" 19 | ], 20 | "scripts": { 21 | "clean": "cross-env ./node_modules/.bin/rimraf dist", 22 | "build": "cross-env npm run clean && npm run build:server && npm run build:admin", 23 | "build:server": "cross-env npm run clean && tsc -p tsconfig.json", 24 | "build:admin": "cross-env medusa-admin build", 25 | "prepare": "cross-env NODE_ENV=production npm run build:server && medusa-admin bundle" 26 | }, 27 | "dependencies": { 28 | "@medusajs/admin": "7.1.17", 29 | "@medusajs/ui": "^3.0.0", 30 | "@medusajs/icons": "1.2.2", 31 | "@medusajs/utils": "1.11.11", 32 | "@tanstack/react-query": "4.22", 33 | "medusa-interfaces": "^1.3.7", 34 | "medusa-react": "9.0.18", 35 | "@mui/material": "^5.15.3", 36 | "typeorm": "^0.3.16", 37 | "react-hook-form": "^7.49.2", 38 | "@emotion/react": "^11.11.3", 39 | "@emotion/styled": "11.13.0", 40 | "recharts": "^2.10.3", 41 | "pdfkit": "^0.15.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/cli": "^7.14.3", 45 | "@babel/core": "^7.14.3", 46 | "@medusajs/medusa": "^1.20.7", 47 | "@types/express": "^4.17.13", 48 | "babel-preset-medusa-package": "^1.1.19", 49 | "cross-env": "^7.0.3", 50 | "eslint": "^6.8.0", 51 | "rimraf": "^3.0.2", 52 | "typescript": "^4.5.2" 53 | }, 54 | "peerDependencies": { 55 | "@medusajs/medusa": "^1.20.7", 56 | "react": "^18.2.0", 57 | "react-router-dom": "^6.13.0" 58 | }, 59 | "jest": { 60 | "globals": { 61 | "ts-jest": { 62 | "tsconfig": "tsconfig.spec.json" 63 | } 64 | }, 65 | "moduleFileExtensions": [ 66 | "js", 67 | "json", 68 | "ts" 69 | ], 70 | "testPathIgnorePatterns": [ 71 | "/node_modules/", 72 | "/node_modules/" 73 | ], 74 | "rootDir": "src", 75 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|js)$", 76 | "transform": { 77 | ".ts": "ts-jest" 78 | }, 79 | "collectCoverageFrom": [ 80 | "**/*.(t|j)s" 81 | ], 82 | "coverageDirectory": "./coverage", 83 | "testEnvironment": "node" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /v1/src/api/admin/marketing-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/medusa" 17 | import { OrderStatus } from "@medusajs/medusa"; 18 | import { MedusaError, MedusaErrorTypes } from "@medusajs/utils" 19 | import MarketingAnalyticsService from "../../../../services/marketingAnalytics"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const dateRangeFrom = req.query.dateRangeFrom; 28 | const dateRangeTo = req.query.dateRangeTo; 29 | const orderStatusesFromQuery: string[] = req.query.orderStatuses as string[]; 30 | 31 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 32 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 33 | 34 | 35 | let result: any; 36 | const marketingAnalyticsService: MarketingAnalyticsService = req.scope.resolve('marketingAnalyticsService'); 37 | 38 | try { 39 | switch (kind) { 40 | case 'discounts-by-count': 41 | result = await marketingAnalyticsService.getTopDiscountsByCount( 42 | orderStatuses, 43 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 44 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 45 | ); 46 | break; 47 | } 48 | res.status(200).json({ 49 | analytics: result 50 | }); 51 | } catch (error) { 52 | throw new MedusaError( 53 | MedusaErrorTypes.DB_ERROR, 54 | error.message 55 | ) 56 | } 57 | } -------------------------------------------------------------------------------- /v1/src/api/admin/orders-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/medusa" 17 | import { OrderStatus } from "@medusajs/medusa"; 18 | import { MedusaError, MedusaErrorTypes } from "@medusajs/utils" 19 | import OrdersAnalyticsService from "../../../../services/ordersAnalytics"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const dateRangeFrom = req.query.dateRangeFrom; 28 | const dateRangeTo = req.query.dateRangeTo; 29 | const dateRangeFromCompareTo = req.query.dateRangeFromCompareTo; 30 | const dateRangeToCompareTo = req.query.dateRangeToCompareTo; 31 | const orderStatusesFromQuery: string[] = req.query.orderStatuses as string[]; 32 | 33 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 34 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 35 | 36 | 37 | let result; 38 | const ordersAnalyticsService: OrdersAnalyticsService = req.scope.resolve('ordersAnalyticsService'); 39 | 40 | try { 41 | switch (kind) { 42 | case 'history': 43 | result = await ordersAnalyticsService.getOrdersHistory( 44 | orderStatuses, 45 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 46 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 47 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 48 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 49 | ); 50 | break; 51 | case 'count': 52 | result = await ordersAnalyticsService.getOrdersCount( 53 | orderStatuses, 54 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 55 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 56 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 57 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 58 | ); 59 | break; 60 | case 'payment-provider': 61 | result = await ordersAnalyticsService.getPaymentProviderPopularity( 62 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 63 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 64 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 65 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 66 | ); 67 | break; 68 | } 69 | res.status(200).json({ 70 | analytics: result 71 | }); 72 | } catch (error) { 73 | throw new MedusaError( 74 | MedusaErrorTypes.DB_ERROR, 75 | error.message 76 | ) 77 | } 78 | } -------------------------------------------------------------------------------- /v1/src/api/admin/products-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/medusa" 17 | import { OrderStatus } from "@medusajs/medusa"; 18 | import { MedusaError, MedusaErrorTypes } from "@medusajs/utils" 19 | import ProductsAnalyticsService from "../../../../services/productsAnalytics"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const dateRangeFrom = req.query.dateRangeFrom; 28 | const dateRangeTo = req.query.dateRangeTo; 29 | const dateRangeFromCompareTo = req.query.dateRangeFromCompareTo; 30 | const dateRangeToCompareTo = req.query.dateRangeToCompareTo; 31 | const orderStatusesFromQuery: string[] = req.query.orderStatuses as string[]; 32 | 33 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 34 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 35 | 36 | let result: any; 37 | const productsAnalyticsService: ProductsAnalyticsService = req.scope.resolve('productsAnalyticsService'); 38 | 39 | try { 40 | switch (kind) { 41 | case 'popularity-by-count': 42 | result = await productsAnalyticsService.getTopVariantsByCount( 43 | orderStatuses, 44 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 45 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 46 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 47 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 48 | ); 49 | break; 50 | case 'returned-by-count': 51 | result = await productsAnalyticsService.getTopReturnedVariantsByCount( 52 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 53 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 54 | ); 55 | break; 56 | case 'sold-count': 57 | result = await productsAnalyticsService.getProductsSoldCount( 58 | orderStatuses, 59 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 60 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 61 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 62 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 63 | ); 64 | break; 65 | case 'out-of-the-stock-variants': 66 | const limit = req.query.limit as string; 67 | result = await productsAnalyticsService.getOutOfTheStockVariants(limit ? parseInt(limit) : undefined); 68 | break; 69 | } 70 | res.status(200).json({ 71 | analytics: result 72 | }); 73 | } catch (error) { 74 | throw new MedusaError( 75 | MedusaErrorTypes.DB_ERROR, 76 | error.message 77 | ) 78 | } 79 | } -------------------------------------------------------------------------------- /v1/src/api/admin/reports-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/medusa" 17 | import { OrderStatus } from "@medusajs/medusa"; 18 | import { MedusaError, MedusaErrorTypes } from "@medusajs/utils" 19 | import ReportsAnalyticsService from "../../../../services/reportsAnalytics"; 20 | 21 | export const POST = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const body: any = req.body as any; 28 | const dateRangeFrom = body.dateRangeFrom; 29 | const dateRangeTo = body.dateRangeTo; 30 | const dateRangeFromCompareTo = body.dateRangeFromCompareTo; 31 | const dateRangeToCompareTo = body.dateRangeToCompareTo; 32 | const orderStatusesFromQuery: string[] = body.orderStatuses as string[]; 33 | 34 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 35 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 36 | 37 | let result: Buffer; 38 | const reportsAnalyticsService: ReportsAnalyticsService = req.scope.resolve('reportsAnalyticsService'); 39 | 40 | try { 41 | switch (kind) { 42 | case 'general': 43 | result = await reportsAnalyticsService.generateReport( 44 | orderStatuses, 45 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 46 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 47 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 48 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 49 | ); 50 | break; 51 | } 52 | res.status(201).json({ 53 | buffer: result 54 | }); 55 | } catch (error) { 56 | throw new MedusaError( 57 | MedusaErrorTypes.DB_ERROR, 58 | error.message 59 | ) 60 | } 61 | } -------------------------------------------------------------------------------- /v1/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export * from './ui-components' -------------------------------------------------------------------------------- /v1/src/jobs/README.md: -------------------------------------------------------------------------------- 1 | # Custom scheduled jobs 2 | 3 | You may define custom scheduled jobs (cron jobs) by creating files in the `/jobs` directory. 4 | 5 | ```ts 6 | import { 7 | ProductService, 8 | ScheduledJobArgs, 9 | ScheduledJobConfig, 10 | } from "@medusajs/medusa"; 11 | 12 | export default async function myCustomJob({ container }: ScheduledJobArgs) { 13 | const productService: ProductService = container.resolve("productService"); 14 | 15 | const products = await productService.listAndCount(); 16 | 17 | // Do something with the products 18 | } 19 | 20 | export const config: ScheduledJobConfig = { 21 | name: "daily-product-report", 22 | schedule: "0 0 * * *", // Every day at midnight 23 | }; 24 | ``` 25 | 26 | A scheduled job is defined in two parts a `handler` and a `config`. The `handler` is a function which is invoked when the job is scheduled. The `config` is an object which defines the name of the job, the schedule, and an optional data object. 27 | 28 | The `handler` is a function which takes one parameter, an `object` of type `ScheduledJobArgs` with the following properties: 29 | 30 | - `container` - a `MedusaContainer` instance which can be used to resolve services. 31 | - `data` - an `object` containing data passed to the job when it was scheduled. This object is passed in the `config` object. 32 | - `pluginOptions` - an `object` containing plugin options, if the job is defined in a plugin. 33 | -------------------------------------------------------------------------------- /v1/src/loaders/README.md: -------------------------------------------------------------------------------- 1 | # Custom loader 2 | 3 | The loader allows you have access to the Medusa service container. This allows you to access the database and the services registered on the container. 4 | you can register custom registrations in the container or run custom code on startup. 5 | 6 | ```ts 7 | // src/loaders/my-loader.ts 8 | 9 | import { AwilixContainer } from 'awilix' 10 | 11 | /** 12 | * 13 | * @param container The container in which the registrations are made 14 | * @param config The options of the plugin or the entire config object 15 | */ 16 | export default (container: AwilixContainer, config: Record): void | Promise => { 17 | /* Implement your own loader. */ 18 | } 19 | ``` -------------------------------------------------------------------------------- /v1/src/migrations/README.md: -------------------------------------------------------------------------------- 1 | # Custom migrations 2 | 3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`. 4 | In that case you also need to provide a migration in order to create the table in the database. 5 | 6 | ## Example 7 | 8 | ### 1. Create the migration 9 | 10 | See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation. 11 | 12 | ```ts 13 | // src/migration/my-migration.ts 14 | 15 | import { MigrationInterface, QueryRunner } from "typeorm" 16 | 17 | export class MyMigration1617703530229 implements MigrationInterface { 18 | name = "myMigration1617703530229" 19 | 20 | public async up(queryRunner: QueryRunner): Promise { 21 | // write you migration here 22 | } 23 | 24 | public async down(queryRunner: QueryRunner): Promise { 25 | // write you migration here 26 | } 27 | } 28 | 29 | ``` -------------------------------------------------------------------------------- /v1/src/models/README.md: -------------------------------------------------------------------------------- 1 | # Custom models 2 | 3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`. 4 | 5 | ## Example 6 | 7 | ### 1. Create the Entity 8 | 9 | ```ts 10 | // src/models/post.ts 11 | 12 | import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm"; 13 | import { generateEntityId } from "@medusajs/utils"; 14 | import { BaseEntity } from "@medusajs/medusa"; 15 | 16 | @Entity() 17 | export class Post extends BaseEntity { 18 | @Column({type: 'varchar'}) 19 | title: string | null; 20 | 21 | @BeforeInsert() 22 | private beforeInsert(): void { 23 | this.id = generateEntityId(this.id, "post") 24 | } 25 | } 26 | ``` 27 | 28 | ### 2. Create the Migration 29 | 30 | You also need to create a Migration to create the new table in the database. See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation. 31 | 32 | ### 3. Create a Repository 33 | Entities data can be easily accessed and modified using [TypeORM Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` for the `Post` entity: 34 | 35 | ```ts 36 | // src/repositories/post.ts 37 | 38 | import { EntityRepository, Repository } from "typeorm" 39 | 40 | import { Post } from "../models/post" 41 | 42 | @EntityRepository(Post) 43 | export class PostRepository extends Repository { } 44 | ``` 45 | 46 | See more about defining and accesing your custom [Entities](https://docs.medusajs.com/advanced/backend/entities/overview) in the documentation. -------------------------------------------------------------------------------- /v1/src/services/pdf-templates/common.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { OrderStatus } from "@medusajs/medusa" 14 | 15 | 16 | const Y_REACHED_TO_ADD_NEW_PAGE = '650'; 17 | 18 | function addPageIfReachingEnd(doc) { 19 | if (doc.y > Y_REACHED_TO_ADD_NEW_PAGE) { 20 | doc.addPage(); 21 | } 22 | } 23 | 24 | export function moveDown(doc) { 25 | addPageIfReachingEnd(doc); 26 | doc.moveDown(); 27 | } 28 | 29 | export function generateReportHeader(doc, orderStatuses: OrderStatus[], from?: Date, to?: Date, dateRangeFromCompareTo?: Date, dateRangeToCompareTo?: Date) : void { 30 | doc 31 | .fillColor("#444444") 32 | .fontSize(20) 33 | 34 | doc 35 | .text('Store analytics report', { align: "center"}) 36 | 37 | doc 38 | .fontSize(16) 39 | .moveDown() 40 | 41 | doc 42 | .text('for', { align: "center"}) 43 | .moveDown(); 44 | 45 | if (from) { 46 | doc 47 | .text(`${from.toDateString()} - ${to ? to.toDateString() : new Date(Date.now()).toDateString()}`, { align: "center"}) 48 | .moveDown(); 49 | } else { 50 | doc 51 | .text(`All time - ${to ? to.toDateString() : new Date(Date.now()).toDateString()}`, { align: "center"}) 52 | .moveDown(); 53 | } 54 | doc 55 | .fontSize(10) 56 | .text(`Filtered by order statuses:`, { align: "center"}) 57 | 58 | orderStatuses.map(orderStatus => doc 59 | .text(orderStatus, { align: "center"}) 60 | ); 61 | 62 | doc 63 | .addPage() 64 | } -------------------------------------------------------------------------------- /v1/src/services/pdf-templates/customers-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { CustomersHistoryResult } from "../customersAnalytics"; 14 | import { moveDown } from "./common"; 15 | import { generateHr } from "./hr"; 16 | 17 | export default class PdfCustomersTemplate { 18 | private static generateTableRow( 19 | doc, 20 | date, 21 | customers 22 | ) { 23 | const startY = doc.y; 24 | doc 25 | .fontSize(10) 26 | .text(date, 70, startY, { align: "left" }) 27 | .text(customers, 320, startY, { width: 90, align: "right" }) 28 | } 29 | 30 | static generateTable(doc, customersHistoryResult: CustomersHistoryResult) : void { 31 | 32 | doc.font("Helvetica-Bold"); 33 | this.generateTableRow( 34 | doc, 35 | "Date", 36 | "New customers count", 37 | ); 38 | moveDown(doc) 39 | generateHr(doc); 40 | moveDown(doc) 41 | doc.font("Helvetica"); 42 | 43 | let totalCurrent = 0; 44 | for (const currentCustomersHistoryResult of customersHistoryResult.current) { 45 | totalCurrent += Number(currentCustomersHistoryResult.customerCount); 46 | this.generateTableRow( 47 | doc, 48 | new Date(currentCustomersHistoryResult.date).toLocaleDateString(), 49 | currentCustomersHistoryResult.customerCount 50 | ); 51 | 52 | moveDown(doc) 53 | generateHr(doc); 54 | moveDown(doc) 55 | } 56 | 57 | this.generateTableRow( 58 | doc, 59 | "Total", 60 | totalCurrent 61 | ); 62 | moveDown(doc) 63 | } 64 | 65 | static generateHeader(doc) : void { 66 | doc 67 | .fontSize(18) 68 | moveDown(doc) 69 | 70 | doc 71 | .text('New customers', 70, doc.y, { align: "left"}) 72 | 73 | doc 74 | .fontSize(12) 75 | moveDown(doc) 76 | } 77 | } -------------------------------------------------------------------------------- /v1/src/services/pdf-templates/hr.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export function generateHr(doc) { 14 | doc 15 | .strokeColor("#aaaaaa") 16 | .lineWidth(1) 17 | .moveTo(70, doc.y) 18 | .lineTo(550, doc.y) 19 | .stroke(); 20 | } 21 | -------------------------------------------------------------------------------- /v1/src/services/pdf-templates/orders-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { generateHr } from "./hr"; 14 | import { OrdersHistoryResult } from "../utils/types"; 15 | import { moveDown } from "./common"; 16 | 17 | export default class PdfOrdersTemplate { 18 | private static generateTableRow( 19 | doc, 20 | date, 21 | orders 22 | ) { 23 | const startY = doc.y; 24 | doc 25 | .fontSize(10) 26 | .text(date, 70, startY, { align: "left" }) 27 | .text(orders, 320, startY, { width: 90, align: "right" }) 28 | } 29 | 30 | static generateTable(doc, ordersHistoryResult: OrdersHistoryResult) : void { 31 | 32 | doc.font("Helvetica-Bold"); 33 | this.generateTableRow( 34 | doc, 35 | "Date", 36 | "Orders count", 37 | ); 38 | moveDown(doc) 39 | generateHr(doc); 40 | moveDown(doc) 41 | doc.font("Helvetica"); 42 | 43 | let totalCurrent = 0; 44 | for (const currentOrdersHistoryResult of ordersHistoryResult.current) { 45 | totalCurrent += Number(currentOrdersHistoryResult.orderCount); 46 | this.generateTableRow( 47 | doc, 48 | new Date(currentOrdersHistoryResult.date).toLocaleDateString(), 49 | currentOrdersHistoryResult.orderCount 50 | ); 51 | 52 | moveDown(doc) 53 | generateHr(doc); 54 | moveDown(doc) 55 | } 56 | 57 | this.generateTableRow( 58 | doc, 59 | "Total", 60 | totalCurrent 61 | ); 62 | moveDown(doc) 63 | } 64 | 65 | static generateHeader(doc) : void { 66 | doc 67 | .fontSize(18) 68 | moveDown(doc) 69 | 70 | doc 71 | .text('Orders', 70, doc.y, { align: "left"}) 72 | 73 | doc 74 | .fontSize(12) 75 | moveDown(doc) 76 | } 77 | } -------------------------------------------------------------------------------- /v1/src/services/pdf-templates/products-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { VariantsCountPopularityResult } from "../productsAnalytics"; 14 | import { moveDown } from "./common"; 15 | import { generateHr } from "./hr"; 16 | 17 | export default class PdfProductsTemplate { 18 | private static generateTableRow( 19 | doc, 20 | product, 21 | variant, 22 | sum, 23 | ) { 24 | const startY = doc.y; 25 | doc 26 | .fontSize(10) 27 | .text(product, 70, startY, { align: "left" }) 28 | .text(variant, 200, startY, { align: "left" }) 29 | .text(sum, 320, startY, { width: 90, align: "right" }) 30 | } 31 | 32 | static generateTable(doc, variantsCountPopularityResult: VariantsCountPopularityResult) : void { 33 | 34 | doc.font("Helvetica-Bold"); 35 | this.generateTableRow( 36 | doc, 37 | "Product", 38 | "Variant", 39 | "Sum", 40 | ); 41 | moveDown(doc) 42 | generateHr(doc); 43 | moveDown(doc) 44 | doc.font("Helvetica"); 45 | 46 | let totalCurrent = 0; 47 | for (const currentResults of variantsCountPopularityResult.current) { 48 | totalCurrent += Number(currentResults.sum); 49 | this.generateTableRow( 50 | doc, 51 | currentResults.productTitle, 52 | currentResults.variantTitle, 53 | currentResults.sum 54 | ); 55 | 56 | moveDown(doc) 57 | generateHr(doc); 58 | moveDown(doc) 59 | } 60 | 61 | this.generateTableRow( 62 | doc, 63 | "Total", 64 | "", 65 | totalCurrent 66 | ); 67 | moveDown(doc) 68 | } 69 | 70 | static generateHeader(doc) : void { 71 | doc 72 | .fontSize(18) 73 | moveDown(doc) 74 | 75 | doc 76 | .text('Top products', 70, doc.y, { align: "left"}) 77 | 78 | doc 79 | .fontSize(12) 80 | moveDown(doc) 81 | } 82 | } -------------------------------------------------------------------------------- /v1/src/services/pdf-templates/sales-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Region } from "@medusajs/medusa"; 14 | import { SalesHistoryResult } from "../salesAnalytics"; 15 | import { generateHr } from "./hr"; 16 | import { amountToDisplay } from "../utils/currency"; 17 | import { moveDown } from "./common"; 18 | 19 | export default class PdfSalesTemplate { 20 | private static generateTableRow( 21 | doc, 22 | date, 23 | currencyCode, 24 | sales 25 | ) { 26 | const startY = doc.y; 27 | doc 28 | .fontSize(10) 29 | .text(date, 70, startY, { align: "left" }) 30 | .text(currencyCode, 200, startY, { align: "left" }) 31 | .text(sales, 320, startY, { width: 90, align: "right" }) 32 | } 33 | 34 | static generateTableTitle(doc, region: Region) { 35 | doc 36 | .fontSize(14) 37 | moveDown(doc) 38 | 39 | doc 40 | .text(`${region.name}`, 70, doc.y, { align: "left"}) 41 | 42 | doc 43 | .fontSize(12) 44 | moveDown(doc) 45 | } 46 | 47 | static generateTable(doc, salesHistoryResult: SalesHistoryResult) : void { 48 | 49 | doc.font("Helvetica-Bold"); 50 | this.generateTableRow( 51 | doc, 52 | "Date", 53 | "Currency code", 54 | "Sales", 55 | ); 56 | moveDown(doc) 57 | generateHr(doc); 58 | moveDown(doc) 59 | doc.font("Helvetica"); 60 | 61 | let totalCurrent = 0; 62 | for (const currentSalesResult of salesHistoryResult.current) { 63 | totalCurrent += Number(currentSalesResult.total); 64 | this.generateTableRow( 65 | doc, 66 | currentSalesResult.date.toLocaleDateString(), 67 | salesHistoryResult.currencyCode.toUpperCase(), 68 | amountToDisplay(Number(currentSalesResult.total), salesHistoryResult.currencyCode) 69 | ); 70 | 71 | moveDown(doc) 72 | generateHr(doc); 73 | moveDown(doc) 74 | } 75 | 76 | this.generateTableRow( 77 | doc, 78 | "Total", 79 | "", 80 | amountToDisplay(totalCurrent, salesHistoryResult.currencyCode) 81 | ); 82 | moveDown(doc) 83 | } 84 | 85 | static generateHeader(doc) : void { 86 | doc 87 | .fontSize(18) 88 | moveDown(doc) 89 | 90 | 91 | doc 92 | .text('Sales by region', 70, doc.y, { align: "left"}) 93 | 94 | doc 95 | .fontSize(12) 96 | moveDown(doc) 97 | } 98 | } -------------------------------------------------------------------------------- /v1/src/services/utils/currency.ts: -------------------------------------------------------------------------------- 1 | import { defaultCurrencies } from "@medusajs/utils"; 2 | 3 | const DEFAULT_DECIMAL_DIGITS = 2; 4 | 5 | export function getDecimalDigits(currencyCode: string): number { 6 | try { 7 | const decimalDigits = defaultCurrencies[currencyCode.toUpperCase()] !== undefined ? defaultCurrencies[currencyCode.toUpperCase()].decimal_digits : undefined; 8 | if (decimalDigits !== undefined) { 9 | return decimalDigits; 10 | } 11 | } catch { 12 | return DEFAULT_DECIMAL_DIGITS; 13 | } 14 | return DEFAULT_DECIMAL_DIGITS; 15 | } 16 | 17 | export function amountToDisplay(amount: number, currencyCode: string) : string { 18 | const decimalDigits = getDecimalDigits(currencyCode); 19 | return `${(amount / Math.pow(10, decimalDigits)).toFixed(decimalDigits)} ${currencyCode.toUpperCase()}`; 20 | } -------------------------------------------------------------------------------- /v1/src/services/utils/dateTransformations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export enum DateResolutionType { 14 | Day = 'day', 15 | Month = 'month' 16 | } 17 | 18 | export function calculateResolution(date?: Date) : DateResolutionType | undefined { 19 | if (!date) return undefined; 20 | 21 | const weekAgoTruncated = new Date(new Date(Date.now() - 604800000).setHours(0,0,0,0)); 22 | if (date.getTime() >= weekAgoTruncated.getTime()) { 23 | return DateResolutionType.Day; 24 | } 25 | 26 | const monthAgoTruncated = new Date(new Date(new Date().setMonth(new Date().getMonth() - 1)).setHours(0,0,0,0)); 27 | if (date.getTime() >= monthAgoTruncated.getTime()) { 28 | return DateResolutionType.Day; 29 | } 30 | 31 | const yearAgoTruncated = new Date(new Date(new Date().setFullYear(new Date().getFullYear() - 1)).setHours(0,0,0,0)); 32 | if (date.getTime() > yearAgoTruncated.getTime()) { 33 | return DateResolutionType.Month; 34 | } 35 | return DateResolutionType.Month 36 | } 37 | 38 | export function getTruncateFunction(dateResolution: DateResolutionType) : (date: Date) => Date { 39 | if (dateResolution == DateResolutionType.Day) { 40 | return (date: Date) => new Date(new Date(date).setHours(0,0,0,0)); 41 | } else { 42 | return (date: Date) => new Date(new Date(new Date(date).setDate(0)).setHours(0,0,0,0)) 43 | } 44 | } -------------------------------------------------------------------------------- /v1/src/services/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | type OrdersHistory = { 14 | orderCount: string, 15 | date: string 16 | } 17 | 18 | export type OrdersHistoryResult = { 19 | dateRangeFrom?: number 20 | dateRangeTo?: number, 21 | dateRangeFromCompareTo?: number, 22 | dateRangeToCompareTo?: number, 23 | current: OrdersHistory[]; 24 | previous: OrdersHistory[]; 25 | } -------------------------------------------------------------------------------- /v1/src/subscribers/README.md: -------------------------------------------------------------------------------- 1 | # Custom subscribers 2 | 3 | You may define custom eventhandlers, `subscribers` by creating files in the `/subscribers` directory. 4 | 5 | ```ts 6 | import MyCustomService from "../services/my-custom"; 7 | import { 8 | OrderService, 9 | SubscriberArgs, 10 | SubscriberConfig, 11 | } from "@medusajs/medusa"; 12 | 13 | type OrderPlacedEvent = { 14 | id: string; 15 | no_notification: boolean; 16 | }; 17 | 18 | export default async function orderPlacedHandler({ 19 | data, 20 | eventName, 21 | container, 22 | }: SubscriberArgs) { 23 | const orderService: OrderService = container.resolve(OrderService); 24 | 25 | const order = await orderService.retrieve(data.id, { 26 | relations: ["items", "items.variant", "items.variant.product"], 27 | }); 28 | 29 | // Do something with the order 30 | } 31 | 32 | export const config: SubscriberConfig = { 33 | event: OrderService.Events.PLACED, 34 | }; 35 | ``` 36 | 37 | A subscriber is defined in two parts a `handler` and a `config`. The `handler` is a function which is invoked when an event is emitted. The `config` is an object which defines which event(s) the subscriber should subscribe to. 38 | 39 | The `handler` is a function which takes one parameter, an `object` of type `SubscriberArgs` with the following properties: 40 | 41 | - `data` - an `object` of type `T` containing information about the event. 42 | - `eventName` - a `string` containing the name of the event. 43 | - `container` - a `MedusaContainer` instance which can be used to resolve services. 44 | - `pluginOptions` - an `object` containing plugin options, if the subscriber is defined in a plugin. 45 | -------------------------------------------------------------------------------- /v1/src/ui-components/common/icon-comparison.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { ArrowUpMini, ArrowDownMini, MinusMini } from "@medusajs/icons" 14 | 15 | export const IconComparison = ({current, previous, switchArrow} : {current: number, previous?: number, switchArrow?: boolean}) => { 16 | if (current == previous) { 17 | return 18 | } 19 | if (current > previous) { 20 | return 21 | } 22 | if (current < previous) { 23 | return 24 | } 25 | } -------------------------------------------------------------------------------- /v1/src/ui-components/common/percentage-comparison.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Tooltip } from "@medusajs/ui"; 14 | import { calculatePercentage } from "../utils/helpers"; 15 | 16 | export const PercentageComparison = ({current, label, previous, headingLevel = "h2" } : {current: string, label: string, previous: string, headingLevel?: any}) => { 17 | const percentage: number | undefined = calculatePercentage(parseInt(current), parseInt(previous)); 18 | return ( 19 | 20 | 21 | 22 | {percentage !== undefined ? `${percentage}%` : `N/A`} 23 | 24 | 25 | 26 | ) 27 | } -------------------------------------------------------------------------------- /v1/src/ui-components/common/utils/chartUtils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export enum ChartResolutionType { 14 | DayMonth, 15 | Month 16 | } 17 | 18 | export function calculateResolution(fromDate?: Date, toDate?: Date) : ChartResolutionType { 19 | if (!fromDate) return undefined; 20 | 21 | const calculateToDate = toDate ? new Date(toDate) : new Date(Date.now()); 22 | const diffTime = calculateToDate.getTime() - fromDate.getTime(); 23 | 24 | const weekTime = 604800000; 25 | const monthTime = weekTime * 4; 26 | const twoMonthsTime = monthTime * 2; 27 | if (diffTime <= twoMonthsTime) { 28 | return ChartResolutionType.DayMonth; 29 | } 30 | 31 | const yearTime = monthTime * 12; 32 | if (diffTime < yearTime) { 33 | return ChartResolutionType.Month; 34 | } 35 | return ChartResolutionType.Month 36 | } 37 | 38 | export const compareDatesBasedOnResolutionType = (date1: Date, date2: Date, resolutionType: ChartResolutionType): boolean => { 39 | switch (resolutionType) { 40 | case ChartResolutionType.DayMonth: 41 | return new Date(new Date(date1).setHours(0,0,0,0)).getTime() == new Date(new Date(date2).setHours(0,0,0,0)).getTime(); 42 | case ChartResolutionType.Month: 43 | return new Date(new Date(new Date(date1).setDate(0)).setHours(0,0,0,0)).getTime() == new Date(new Date(new Date(date2).setDate(0)).setHours(0,0,0,0)).getTime(); 44 | default: 45 | return new Date(new Date(date1).setHours(0,0,0,0)).getTime() == new Date(new Date(date2).setHours(0,0,0,0)).getTime(); 46 | } 47 | } 48 | 49 | export const getChartDateName = (date: Date, resolutionType: ChartResolutionType, startDate: Date, endDate: Date): string => { 50 | 51 | switch (resolutionType) { 52 | case ChartResolutionType.DayMonth: 53 | if (compareDatesBasedOnResolutionType(date, startDate, resolutionType) || compareDatesBasedOnResolutionType(date, endDate, resolutionType)) { 54 | return `${date.getDate().toString()} ${getShortMonthName(date)}`; 55 | } 56 | return date.getDate().toString(); 57 | case ChartResolutionType.Month: 58 | if (compareDatesBasedOnResolutionType(date, startDate, resolutionType) || compareDatesBasedOnResolutionType(date, endDate, resolutionType)) { 59 | return `${getShortMonthName(date)} ${date.getFullYear().toString()}`; 60 | } 61 | return getShortMonthName(date); 62 | default: 63 | return date.getFullYear().toString() 64 | } 65 | } 66 | 67 | export const getChartTooltipDate = (date: Date, resolutionType: ChartResolutionType): string => { 68 | switch (resolutionType) { 69 | case ChartResolutionType.DayMonth: 70 | return `${date.getDate().toString()}-${getShortMonthName(date)}`; 71 | case ChartResolutionType.Month: 72 | return `${getShortMonthName(date)}-${date.getFullYear()}`; 73 | default: 74 | return date.getFullYear().toString() 75 | } 76 | } 77 | 78 | export const getLegendName = (current: boolean): string => { 79 | return current ? `Current` : `Preceding`; 80 | } 81 | 82 | const getShortMonthName = (date: Date) => { 83 | let days = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 84 | return days[date.getMonth()]; 85 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/cumulative-history/cumulative-customers-card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Users } from "@medusajs/icons"; 15 | import { Grid } from "@mui/material"; 16 | import type { DateRange } from "../../utils/types"; 17 | import { CumulativeCustomersChart } from "./cumulative-customers-chart"; 18 | 19 | export const CumulativeCustomersCard = ({dateRange, dateRangeCompareTo, compareEnabled} : 20 | {dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Cumulative customers 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/customers-number-overview.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Alert } from "@medusajs/ui"; 14 | import { CircularProgress, Grid } from "@mui/material"; 15 | import { useAdminCustomQuery } from "medusa-react" 16 | import type { DateRange } from "../utils/types"; 17 | import { PercentageComparison } from "../common/percentage-comparison"; 18 | import { IconComparison } from "../common/icon-comparison"; 19 | 20 | type AdminCustomersStatisticsQuery = { 21 | dateRangeFrom: number 22 | dateRangeTo: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | } 26 | 27 | export type CustomersCountResponse = { 28 | analytics: { 29 | dateRangeFrom: number 30 | dateRangeTo: number, 31 | dateRangeFromCompareTo?: number, 32 | dateRangeToCompareTo?: number, 33 | current: string, 34 | previous: string 35 | } 36 | } 37 | 38 | export const CustomersNumber = ({dateRange, dateRangeCompareTo, compareEnabled} : {dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled?: boolean}) => { 39 | const { data, isLoading, isError, error } = useAdminCustomQuery< 40 | AdminCustomersStatisticsQuery, 41 | CustomersCountResponse 42 | >( 43 | `/customers-analytics/count`, 44 | [dateRange, dateRangeCompareTo], 45 | { 46 | dateRangeFrom: dateRange ? dateRange.from.getTime() : undefined, 47 | dateRangeTo: dateRange ? dateRange.to.getTime() : undefined, 48 | dateRangeFromCompareTo: dateRangeCompareTo ? dateRangeCompareTo.from.getTime() : undefined, 49 | dateRangeToCompareTo: dateRangeCompareTo ? dateRangeCompareTo.to.getTime() : undefined 50 | } 51 | ) 52 | 53 | if (isLoading) { 54 | return 55 | } 56 | 57 | if (isError) { 58 | const trueError = error as any; 59 | const errorText = `Error when loading data. It shouldn't have happened - please raise an issue. For developer: ${trueError?.response?.data?.message}` 60 | return {errorText} 61 | } 62 | 63 | if (data.analytics == undefined) { 64 | return Cannot get customers 65 | } 66 | return ( 67 | 68 | 69 | 70 | {data.analytics.current} 71 | 72 | 73 | {compareEnabled && dateRangeCompareTo && 74 | 75 | 76 | 77 | 78 | 79 | {data.analytics.previous !== undefined && 80 | 81 | } 82 | 83 | 84 | } 85 | 86 | ); 87 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/customers-overview-card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Users } from "@medusajs/icons"; 15 | import { Grid } from "@mui/material"; 16 | import type { DateRange } from "../utils/types"; 17 | import { CustomersNumber } from "./customers-number-overview"; 18 | import { CustomersByNewChart } from "./customers-by-new-chart"; 19 | 20 | export const CustomersOverviewCard = ({dateRange, dateRangeCompareTo, compareEnabled} : 21 | {dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | New customers 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/repeat-customer-rate/customers-repeat-customer-rate-number.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../../common/percentage-comparison"; 16 | import { IconComparison } from "../../common/icon-comparison"; 17 | import { CustomersRepeatCustomerRateResponse } from "../types" 18 | 19 | export const RepeatCustomerRateNummber = ({repeatCustomerRateResponse, compareEnabled} : {repeatCustomerRateResponse: CustomersRepeatCustomerRateResponse, compareEnabled?: boolean}) => { 20 | const currentPercentage: number | undefined = 21 | repeatCustomerRateResponse.analytics.current !== undefined && repeatCustomerRateResponse.analytics.current.returnCustomerRate !== undefined ? 22 | parseInt(repeatCustomerRateResponse.analytics.current.returnCustomerRate) : undefined; 23 | const previousPercentage: number | undefined = 24 | repeatCustomerRateResponse.analytics.previous !== undefined && repeatCustomerRateResponse.analytics.previous.returnCustomerRate !== undefined ? 25 | parseInt(repeatCustomerRateResponse.analytics.previous.returnCustomerRate) : undefined; 26 | 27 | return ( 28 | 29 | 30 | {currentPercentage !== undefined ? 31 | 32 | {`${currentPercentage}%`} 33 | : 34 | 35 | {`No orders or customers`} 36 | 37 | } 38 | 39 | {compareEnabled && repeatCustomerRateResponse.analytics.dateRangeFromCompareTo && currentPercentage !== undefined && 40 | 41 | 42 | 43 | 44 | 45 | {previousPercentage !== undefined && 46 | 47 | } 48 | 49 | 50 | } 51 | 52 | ); 53 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/repeat-customer-rate/order-frequency-distribution-chart.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { calculateResolution, getLegendName } from "../../common/utils/chartUtils"; 14 | import { CustomersRepeatCustomerRateResponse, Distributions } from "../types" 15 | import { Legend, Pie, PieChart, Tooltip } from "recharts"; 16 | 17 | const ONE_TIME_LABEL_NAME = 'One-time purchase'; 18 | const REPEAT_LABEL_NAME = 'Repeat purchase'; 19 | 20 | function convertToChartData(distributions: Distributions) { 21 | if (distributions) { 22 | if (distributions.orderOneTimeFrequency || distributions.orderRepeatFrequency) { 23 | const oneTimeValue = distributions.orderOneTimeFrequency ? parseInt(distributions.orderOneTimeFrequency) : 0; 24 | const repeatValue = distributions.orderRepeatFrequency ? parseInt(distributions.orderRepeatFrequency) : 0; 25 | return [ 26 | { 27 | name: ONE_TIME_LABEL_NAME, 28 | value: oneTimeValue, 29 | displayValue: ONE_TIME_LABEL_NAME 30 | }, 31 | { 32 | name: REPEAT_LABEL_NAME, 33 | value: repeatValue, 34 | displayValue: REPEAT_LABEL_NAME 35 | } 36 | ] 37 | } 38 | } 39 | return undefined; 40 | } 41 | 42 | export const OrderFrequencyDistributionPieChart = ({repeatCustomerRateResponse, compareEnabled} : {repeatCustomerRateResponse: CustomersRepeatCustomerRateResponse, compareEnabled?: boolean}) => { 43 | 44 | const currentData = convertToChartData(repeatCustomerRateResponse.analytics.current); 45 | const previousData = convertToChartData(repeatCustomerRateResponse.analytics.previous); 46 | 47 | const renderLabel = function(entry) { 48 | return entry.displayValue; 49 | } 50 | 51 | return ( 52 | 53 | 54 | {compareEnabled && repeatCustomerRateResponse.analytics.dateRangeFromCompareTo && currentData !== undefined && 55 | 56 | } 57 | {(compareEnabled && repeatCustomerRateResponse.analytics.dateRangeFromCompareTo) && } 67 | `${value}%`}/> 68 | 69 | ); 70 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/repeat-customer-rate/order-frequency-distribution.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { CustomersRepeatCustomerRateResponse } from "../types" 16 | import { OrderFrequencyDistributionPieChart } from "./order-frequency-distribution-chart"; 17 | 18 | export const OrderFrequencyDistribution = ({repeatCustomerRateResponse, compareEnabled} : {repeatCustomerRateResponse: CustomersRepeatCustomerRateResponse, compareEnabled?: boolean}) => { 19 | return ( 20 | 21 | 22 | 23 | How orders were distributed? 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/retention-customer-rate/customers-retention-customer-rate-number.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../../common/percentage-comparison"; 16 | import { IconComparison } from "../../common/icon-comparison"; 17 | import { CustomersRetentionCustomerRateResponse } from "../types" 18 | 19 | export const RetentionCustomerRateNumber = ({retentionCustomerRateResponse, compareEnabled} : {retentionCustomerRateResponse: CustomersRetentionCustomerRateResponse, compareEnabled?: boolean}) => { 20 | const currentPercentage: number | undefined = 21 | retentionCustomerRateResponse.analytics.current !== undefined ? 22 | parseInt(retentionCustomerRateResponse.analytics.current) : undefined; 23 | const previousPercentage: number | undefined = 24 | retentionCustomerRateResponse.analytics.previous !== undefined ? 25 | parseInt(retentionCustomerRateResponse.analytics.previous) : undefined; 26 | 27 | return ( 28 | 29 | 30 | {currentPercentage !== undefined ? 31 | 32 | {`${currentPercentage}%`} 33 | : 34 | 35 | {`No orders or customers`} 36 | 37 | } 38 | 39 | {compareEnabled && retentionCustomerRateResponse.analytics.dateRangeFromCompareTo && currentPercentage !== undefined && 40 | 41 | 42 | 43 | 44 | 45 | {previousPercentage !== undefined && 46 | 47 | } 48 | 49 | 50 | } 51 | 52 | ); 53 | } -------------------------------------------------------------------------------- /v1/src/ui-components/customers/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export type Distributions = { 14 | returnCustomerRate: string, 15 | orderOneTimeFrequency?: string, 16 | orderRepeatFrequency?: string 17 | } 18 | 19 | export type CustomersRepeatCustomerRateResponse = { 20 | analytics: { 21 | dateRangeFrom: number 22 | dateRangeTo: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | current: Distributions, 26 | previous: Distributions 27 | } 28 | } 29 | 30 | export type CustomersRetentionCustomerRateResponse = { 31 | analytics: { 32 | dateRangeFrom: number 33 | dateRangeTo: number, 34 | dateRangeFromCompareTo?: number, 35 | dateRangeToCompareTo?: number, 36 | current?: string, 37 | previous?: string 38 | } 39 | } -------------------------------------------------------------------------------- /v1/src/ui-components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export { ComparedDate, SwitchComparison, DropdownOrderStatus, SelectDateLasts, GenerateReportButton } from './common/overview-components'; 14 | 15 | export { OrdersOverviewCard } from './orders/orders-overview-card' 16 | export { OrdersPaymentProviderCard } from './orders/orders-payment-provider-card' 17 | 18 | export { SalesOverviewCard } from './sales/sales-overview-card' 19 | export { SalesChannelPopularityCard } from './sales/sales-channel-popularity-card' 20 | export { RefundsOverviewCard } from './sales/refunds/refunds-overview-card' 21 | export { RegionsPopularityCard } from './sales/regions-popularity-card' 22 | 23 | export { CustomersOverviewCard } from './customers/customers-overview-card' 24 | export { CustomersRepeatCustomerRate } from './customers/repeat-customer-rate/customers-repeat-customer-rate'; 25 | export { CustomersRetentionCustomerRate } from './customers/retention-customer-rate/customers-retention-customer-rate'; 26 | export { CumulativeCustomersCard } from './customers/cumulative-history/cumulative-customers-card'; 27 | 28 | export { VariantsTopByCountCard } from './products/variants-top-by-count'; 29 | export { ReturnedVariantsByCountCard } from './products/returned_variants/returned-variants-by-count'; 30 | export { ProductsSoldCountCard } from './products/products-sold-count'; 31 | export { OutOfTheStockVariantsCard } from './products/out_of_the_stock_variants/out-of-the-stock-variants-by-count'; 32 | 33 | export { DiscountsTopCard } from './marketing/discounts-top-by-count'; 34 | 35 | export { DateLasts, OrderStatus } from './utils/types' 36 | export type { DateRange } from './utils/types' 37 | export { convertDateLastsToComparedDateRange, convertDateLastsToDateRange, amountToDisplay, calculatePercentage } from './utils/helpers' -------------------------------------------------------------------------------- /v1/src/ui-components/marketing/discounts-top-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Divider, Grid } from "@mui/material"; 15 | 16 | export type DiscountsTopTableRow = { 17 | sum: string, 18 | discountCode: string 19 | } 20 | 21 | export const DiscountsTopTable = ({tableRows} : {tableRows: DiscountsTopTableRow[]}) => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Discount 32 | 33 | 34 | 35 | 36 | Count 37 | 38 | 39 | 40 | 41 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 42 | 43 | 44 | 45 | 46 | {tableRow.discountCode} 47 | 48 | 49 | 50 | 51 | {tableRow.sum} 52 | 53 | 54 | 55 | 56 | )) : 57 | 58 | 59 | 60 | 61 | None 62 | 63 | 64 | 65 | 66 | None 67 | 68 | 69 | 70 | 71 | } 72 | 73 | ) 74 | } -------------------------------------------------------------------------------- /v1/src/ui-components/orders/orders-overview-card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { ShoppingCart } from "@medusajs/icons"; 15 | import { Grid } from "@mui/material"; 16 | import { OrdersByNewChart } from "./orders-by-new-chart"; 17 | import type { DateRange } from "../utils/types"; 18 | import { OrdersNumber } from "./orders-number-overview"; 19 | import { OrderStatus } from "../utils/types"; 20 | 21 | export const OrdersOverviewCard = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 22 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Orders 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } -------------------------------------------------------------------------------- /v1/src/ui-components/orders/orders-payment-provider-chart.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { calculateResolution, getLegendName } from "../common/utils/chartUtils"; 14 | import { Legend, Pie, PieChart, Tooltip } from "recharts"; 15 | import { OrdersPaymentProvider, OrdersPaymentProviderResponse } from "./types"; 16 | import { Text, Container } from "@medusajs/ui"; 17 | 18 | function convertToChartData(ordersPaymentProviders: OrdersPaymentProvider[]) { 19 | if (ordersPaymentProviders.length) { 20 | return ordersPaymentProviders.map(ordersPaymentProvider => { 21 | return { 22 | name: ordersPaymentProvider.paymentProviderId, 23 | value: parseFloat(ordersPaymentProvider.percentage), 24 | displayValue: ordersPaymentProvider.paymentProviderId, 25 | orderCount: ordersPaymentProvider.orderCount, 26 | } 27 | }) 28 | } 29 | return undefined; 30 | } 31 | const ChartCustomTooltip = ({ active, payload, label }) => { 32 | if (active && payload && payload.length) { 33 | return ( 34 | 35 | {`${payload[0].payload.value}%`} 36 | {`Provider: ${payload[0].payload.name}`} 37 | {`Order count: ${payload[0].payload.orderCount}`} 38 | 39 | ) 40 | } 41 | return null; 42 | }; 43 | 44 | export const OrdersPaymentProviderPieChart = ({ordersPaymentProviderResponse, compareEnabled} : {ordersPaymentProviderResponse: OrdersPaymentProviderResponse, compareEnabled?: boolean}) => { 45 | 46 | const currentData = convertToChartData(ordersPaymentProviderResponse.analytics.current); 47 | const previousData = convertToChartData(ordersPaymentProviderResponse.analytics.previous); 48 | 49 | const renderLabel = function(entry) { 50 | return entry.displayValue; 51 | } 52 | 53 | return ( 54 | 55 | 56 | {compareEnabled && ordersPaymentProviderResponse.analytics.dateRangeFromCompareTo && 57 | 58 | } 59 | {(compareEnabled && ordersPaymentProviderResponse.analytics.dateRangeFromCompareTo) && } 69 | } /> 70 | 71 | ); 72 | } -------------------------------------------------------------------------------- /v1/src/ui-components/orders/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | 14 | export type OrdersPaymentProvider = { 15 | orderCount: string, 16 | percentage: string, 17 | paymentProviderId: string 18 | } 19 | 20 | export type OrdersPaymentProviderPopularityResult = { 21 | dateRangeFrom?: number 22 | dateRangeTo?: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | current: OrdersPaymentProvider[] 26 | previous: OrdersPaymentProvider[] 27 | } 28 | 29 | export type OrdersPaymentProviderResponse = { 30 | analytics: OrdersPaymentProviderPopularityResult 31 | } -------------------------------------------------------------------------------- /v1/src/ui-components/products/out_of_the_stock_variants/helpers.tsx: -------------------------------------------------------------------------------- 1 | export type AdminOutOfTheStockVariantsStatisticsQuery = {} 2 | 3 | export type OutOfTheStockVariantsCount = { 4 | productId: string, 5 | variantId: string, 6 | productTitle: string, 7 | variantTitle: string, 8 | thumbnail: string, 9 | } 10 | 11 | export type OutOfTheStockVariantsCountResult = { 12 | dateRangeFrom?: number 13 | dateRangeTo?: number, 14 | dateRangeFromCompareTo?: number, 15 | dateRangeToCompareTo?: number, 16 | current: OutOfTheStockVariantsCount[], 17 | } 18 | 19 | export type OutOfTheStockVariantsCountResponse = { 20 | analytics: OutOfTheStockVariantsCountResult 21 | } 22 | export type OutOfTheStockVariantsTableRow = { 23 | variantId: string, 24 | productId: string, 25 | productTitle: string, 26 | variantTitle: string, 27 | thumbnail: string, 28 | } 29 | 30 | export function transformToVariantTopTable(result: OutOfTheStockVariantsCountResult): OutOfTheStockVariantsTableRow[] { 31 | const currentMap = new Map(); 32 | 33 | result.current.forEach(currentItem => { 34 | currentMap.set(currentItem.variantId, { 35 | variantId: currentItem.variantId, 36 | productId: currentItem.productId, 37 | productTitle: currentItem.productTitle, 38 | variantTitle: currentItem.variantTitle, 39 | thumbnail: currentItem.thumbnail, 40 | }); 41 | }); 42 | 43 | return Array.from(currentMap.values()); 44 | } -------------------------------------------------------------------------------- /v1/src/ui-components/products/out_of_the_stock_variants/out-of-the-stock-variants-by-count.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Alert, Tooltip, Badge } from "@medusajs/ui"; 14 | import { ArrowRightOnRectangle, InformationCircle } from "@medusajs/icons"; 15 | import { CircularProgress, Grid } from "@mui/material"; 16 | import { useAdminCustomQuery } from "medusa-react" 17 | import { OutOfTheStockVariantsTable } from "./out-of-the-stock-variants-table"; 18 | import { OutOfTheStockVariantsModal } from "./out-of-the-stock-variants-all"; 19 | import { AdminOutOfTheStockVariantsStatisticsQuery, OutOfTheStockVariantsCountResponse, transformToVariantTopTable } from "./helpers"; 20 | 21 | const OutOfTheStockVariants = () => { 22 | const { data, isError, isLoading, error } = useAdminCustomQuery< 23 | AdminOutOfTheStockVariantsStatisticsQuery, 24 | OutOfTheStockVariantsCountResponse 25 | >( 26 | `/products-analytics/out-of-the-stock-variants`, 27 | [], 28 | { 29 | limit: 5 30 | } 31 | ) 32 | 33 | if (isLoading) { 34 | return 35 | } 36 | 37 | if (isError) { 38 | const trueError = error as any; 39 | const errorText = `Error when loading data. It shouldn't have happened - please raise an issue. For developer: ${trueError?.response?.data?.message}` 40 | return {errorText} 41 | } 42 | 43 | if (data.analytics == undefined) { 44 | return Cannot get variants 45 | } 46 | 47 | return 48 | } 49 | 50 | export const OutOfTheStockVariantsCard = () => { 51 | return ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Out of the stock variants 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Last 5 variants 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ) 87 | } -------------------------------------------------------------------------------- /v1/src/ui-components/products/out_of_the_stock_variants/out-of-the-stock-variants-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Box, Divider, Grid } from "@mui/material"; 15 | import { Link } from "react-router-dom" 16 | import { OutOfTheStockVariantsTableRow } from "./helpers"; 17 | 18 | export const OutOfTheStockVariantsTable = ({tableRows} : {tableRows: OutOfTheStockVariantsTableRow[]}) => { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Variant 29 | 30 | 31 | 32 | 33 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 34 | 35 | 36 | 37 | 38 | 39 | {tableRow.thumbnail && 40 | 49 | } 50 | 51 | {tableRow.productTitle} - {tableRow.variantTitle} 52 | 53 | 54 | 55 | 56 | 57 | 58 | )) : 59 | 60 | 61 | 62 | 63 | None 64 | 65 | 66 | 67 | 68 | } 69 | 70 | ) 71 | } -------------------------------------------------------------------------------- /v1/src/ui-components/products/returned_variants/returned-variants-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Box, Divider, Grid } from "@mui/material"; 15 | import { Link } from "react-router-dom" 16 | 17 | export type VariantsTopTableRow = { 18 | sum: string, 19 | productId: string, 20 | productTitle: string, 21 | variantTitle: string, 22 | thumbnail: string, 23 | } 24 | 25 | export const ReturnedVariantsTable = ({tableRows} : {tableRows: VariantsTopTableRow[]}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Variant 36 | 37 | 38 | 39 | 40 | Count 41 | 42 | 43 | 44 | 45 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 46 | 47 | 48 | 49 | 50 | 51 | {tableRow.thumbnail && 52 | 61 | } 62 | 63 | {tableRow.productTitle} - {tableRow.variantTitle} 64 | 65 | 66 | 67 | 68 | 69 | 70 | {tableRow.sum} 71 | 72 | 73 | 74 | 75 | )) : 76 | 77 | 78 | 79 | 80 | None 81 | 82 | 83 | 84 | 85 | None 86 | 87 | 88 | 89 | 90 | } 91 | 92 | ) 93 | } -------------------------------------------------------------------------------- /v1/src/ui-components/products/variants-top-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Box, Divider, Grid } from "@mui/material"; 15 | import { Link } from "react-router-dom" 16 | 17 | export type VariantsTopTableRow = { 18 | sum: string, 19 | productId: string, 20 | productTitle: string, 21 | variantTitle: string, 22 | thumbnail: string, 23 | } 24 | 25 | export const VariantsTopTable = ({tableRows} : {tableRows: VariantsTopTableRow[]}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Variant 36 | 37 | 38 | 39 | 40 | Count 41 | 42 | 43 | 44 | 45 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 46 | 47 | 48 | 49 | 50 | 51 | {tableRow.thumbnail && 52 | 61 | } 62 | 63 | {tableRow.productTitle} - {tableRow.variantTitle} 64 | 65 | 66 | 67 | 68 | 69 | 70 | {tableRow.sum} 71 | 72 | 73 | 74 | 75 | )) : 76 | 77 | 78 | 79 | 80 | None 81 | 82 | 83 | 84 | 85 | None 86 | 87 | 88 | 89 | 90 | } 91 | 92 | ) 93 | } -------------------------------------------------------------------------------- /v1/src/ui-components/sales/refunds/refunds-numbers.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../../common/percentage-comparison"; 16 | import { IconComparison } from "../../common/icon-comparison"; 17 | import { RefundsResponse } from "../types"; 18 | import { amountToDisplay } from "../../utils/helpers"; 19 | 20 | export const RefundsNumber = ({refundsResponse, compareEnabled} : {refundsResponse: RefundsResponse, compareEnabled?: boolean}) => { 21 | const overallCurrentSum: number = parseInt(refundsResponse.analytics.current); 22 | const overallPreviousSum: number | undefined = refundsResponse.analytics.previous !== undefined ? 23 | parseInt(refundsResponse.analytics.previous) : undefined; 24 | 25 | return ( 26 | 27 | 28 | 29 | {amountToDisplay(overallCurrentSum, refundsResponse.analytics.currencyDecimalDigits)} {refundsResponse.analytics.currencyCode.toUpperCase()} 30 | 31 | 32 | {compareEnabled && refundsResponse.analytics.dateRangeFromCompareTo && 33 | 34 | 35 | 36 | 37 | 38 | {overallPreviousSum !== undefined && 39 | 40 | } 41 | 42 | 43 | } 44 | 45 | ); 46 | } -------------------------------------------------------------------------------- /v1/src/ui-components/sales/sales-number-overview.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../common/percentage-comparison"; 16 | import { IconComparison } from "../common/icon-comparison"; 17 | import { SalesHistoryResponse } from "./types"; 18 | import { amountToDisplay } from "../utils/helpers"; 19 | 20 | export const SalesNumber = ({salesHistoryResponse, compareEnabled} : {salesHistoryResponse: SalesHistoryResponse, compareEnabled?: boolean}) => { 21 | const overallCurrentSum: number = salesHistoryResponse.analytics.current.reduce((sum, order) => sum + parseInt(order.total), 0); 22 | const overallPreviousSum: number | undefined = salesHistoryResponse.analytics.previous.length > 0 ? 23 | salesHistoryResponse.analytics.previous.reduce((sum, order) => sum + parseInt(order.total), 0) : 24 | undefined; 25 | 26 | return ( 27 | 28 | 29 | 30 | {amountToDisplay(overallCurrentSum, salesHistoryResponse.analytics.currencyDecimalDigits)} {salesHistoryResponse.analytics.currencyCode.toUpperCase()} 31 | 32 | 33 | {compareEnabled && salesHistoryResponse.analytics.dateRangeFromCompareTo && 34 | 35 | 36 | 37 | 38 | 39 | {overallPreviousSum !== undefined && 40 | 41 | } 42 | 43 | 44 | } 45 | 46 | ); 47 | } -------------------------------------------------------------------------------- /v1/src/ui-components/sales/sales-total-chart.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { ChartCurrentPrevious } from "../common/chart-components"; 15 | import { SalesHistoryResponse } from "./types"; 16 | import { amountToDisplay } from "../utils/helpers"; 17 | 18 | export const SalesByNewChart = ({salesHistoryResponse, compareEnabled} : {salesHistoryResponse: SalesHistoryResponse, compareEnabled?: boolean}) => { 19 | const rawChartData = { 20 | current: salesHistoryResponse.analytics.current.map(currentData => { 21 | return { 22 | date: new Date(currentData.date), 23 | value: amountToDisplay(parseInt(currentData.total), salesHistoryResponse.analytics.currencyDecimalDigits) 24 | }; 25 | }), 26 | previous: salesHistoryResponse.analytics.previous.map(previousData => { 27 | return { 28 | date: new Date(previousData.date), 29 | value: amountToDisplay(parseInt(previousData.total), salesHistoryResponse.analytics.currencyDecimalDigits) 30 | }; 31 | }), 32 | }; 33 | return ( 34 | <> 35 | Sales by time 36 | 44 | 45 | ) 46 | } -------------------------------------------------------------------------------- /v1/src/ui-components/sales/types.ts: -------------------------------------------------------------------------------- 1 | export type SalesHistory = { 2 | total: string, 3 | date: string 4 | } 5 | 6 | export type SalesHistoryResponse = { 7 | analytics: { 8 | dateRangeFrom?: number 9 | dateRangeTo?: number, 10 | dateRangeFromCompareTo?: number, 11 | dateRangeToCompareTo?: number, 12 | currencyCode: string, 13 | currencyDecimalDigits: number, 14 | current: SalesHistory[]; 15 | previous: SalesHistory[]; 16 | } 17 | } 18 | 19 | export type RefundsResponse = { 20 | analytics: { 21 | dateRangeFrom?: number 22 | dateRangeTo?: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | currencyCode: string 26 | currencyDecimalDigits: number, 27 | current: string; 28 | previous: string; 29 | } 30 | } -------------------------------------------------------------------------------- /v1/src/ui-components/tabs/customers.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | CustomersOverviewCard, 16 | CustomersRepeatCustomerRate, 17 | CumulativeCustomersCard, 18 | CustomersRetentionCustomerRate, 19 | OrderStatus, 20 | } from '..'; 21 | import type { DateRange } from '..'; 22 | import { Grid } from "@mui/material"; 23 | 24 | const CustomersTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 25 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default CustomersTab -------------------------------------------------------------------------------- /v1/src/ui-components/tabs/orders.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | OrdersOverviewCard, 16 | OrderStatus, 17 | } from '..'; 18 | import type { DateRange } from '..'; 19 | import { Grid } from "@mui/material"; 20 | import { OrdersPaymentProviderCard } from "../orders/orders-payment-provider-card"; 21 | 22 | const OrdersTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 23 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default OrdersTab -------------------------------------------------------------------------------- /v1/src/ui-components/tabs/products.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | ProductsSoldCountCard, 16 | VariantsTopByCountCard, 17 | ReturnedVariantsByCountCard, 18 | OrderStatus, 19 | } from '..'; 20 | import type { DateRange } from '..'; 21 | import { Grid } from "@mui/material"; 22 | import { OutOfTheStockVariantsCard } from "../products/out_of_the_stock_variants/out-of-the-stock-variants-by-count"; 23 | 24 | const ProductsTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 25 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default ProductsTab -------------------------------------------------------------------------------- /v1/src/ui-components/tabs/sales.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | DiscountsTopCard, 16 | SalesChannelPopularityCard, 17 | OrderStatus, 18 | SalesOverviewCard, 19 | RefundsOverviewCard 20 | } from '..'; 21 | import type { DateRange } from '..'; 22 | import { Grid } from "@mui/material"; 23 | 24 | const SalesTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 25 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default SalesTab -------------------------------------------------------------------------------- /v1/src/ui-components/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { DateLasts } from "./types"; 14 | import type { DateRange } from "./types" 15 | 16 | export function amountToDisplay(amount: number, decimalDigits: number) : string { 17 | return (amount / Math.pow(10, decimalDigits)).toFixed(decimalDigits); 18 | } 19 | 20 | export function calculatePercentage(current: number, previous: number) : number | undefined { 21 | if (current == previous) { 22 | return 0; 23 | } 24 | if (current == 0) { 25 | return 100; 26 | } 27 | 28 | if (previous == 0) { 29 | return undefined; 30 | } 31 | 32 | const percentage: number = Number((((current) - previous) / previous).toFixed(2)) * 100; 33 | if (percentage > 0) { 34 | return Math.round(percentage * 100) / 100; 35 | } 36 | return Math.round((percentage - percentage - percentage) * 100) / 100; 37 | } 38 | 39 | export function convertDateLastsToDateRange(dateLasts: DateLasts): DateRange | undefined { 40 | let result: DateRange | undefined; 41 | switch (dateLasts) { 42 | case DateLasts.LastMonth: 43 | result = { 44 | // 86400000 - alignment for taking last 29 days, as the current day is 30 45 | from: new Date(new Date(new Date().setDate(new Date().getDate() - 29)).setHours(0,0,0,0)), 46 | to: new Date(Date.now()) 47 | } 48 | break; 49 | case DateLasts.LastWeek: 50 | result = { 51 | // 86400000 - alignment for taking last 6 days, as the current day is 7th 52 | from: new Date(new Date(new Date(Date.now() - 604800000 + 86400000)).setHours(0,0,0,0)), 53 | to: new Date(Date.now()) 54 | } 55 | break; 56 | case DateLasts.LastYear: 57 | const lastYearAgo = new Date(new Date().setFullYear(new Date().getFullYear() - 1)); 58 | result = { 59 | // + 1 - alignment for taking last 11 months, as the current month is 12th 60 | from: new Date(new Date(new Date().setDate(new Date().getDate() - 364)).setHours(0,0,0,0)), 61 | to: new Date(Date.now()) 62 | } 63 | break; 64 | } 65 | return result; 66 | } 67 | 68 | export function convertDateLastsToComparedDateRange(dateLasts: DateLasts): DateRange | undefined { 69 | let result: DateRange | undefined; 70 | switch (dateLasts) { 71 | case DateLasts.LastMonth: 72 | result = { 73 | from: new Date(new Date(new Date().setDate(new Date().getDate() - 59)).setHours(0,0,0,0)), 74 | to: new Date(new Date(new Date().setDate(new Date().getDate() - 29)).setHours(0,0,0,0)), 75 | } 76 | break; 77 | case DateLasts.LastWeek: 78 | result = { 79 | from: new Date(new Date(Date.now() - 604800000 * 2 + 86400000).setHours(0,0,0,0)), 80 | to: new Date(new Date(Date.now() - 604800000 + 86400000).setHours(0,0,0,0)), 81 | } 82 | break; 83 | case DateLasts.LastYear: 84 | result = { 85 | from: new Date(new Date(new Date().setDate(new Date().getDate() - 729)).setHours(0,0,0,0)), 86 | to: new Date(new Date(new Date().setDate(new Date().getDate() - 364)).setHours(0,0,0,0)), 87 | } 88 | break; 89 | } 90 | return result; 91 | } -------------------------------------------------------------------------------- /v1/src/ui-components/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export enum OrderStatus { 14 | /** 15 | * The order is pending. 16 | */ 17 | PENDING = "pending", 18 | /** 19 | * The order is completed, meaning that 20 | * the items have been fulfilled and the payment 21 | * has been captured. 22 | */ 23 | COMPLETED = "completed", 24 | /** 25 | * The order is archived. 26 | */ 27 | ARCHIVED = "archived", 28 | /** 29 | * The order is canceled. 30 | */ 31 | CANCELED = "canceled", 32 | /** 33 | * The order requires action. 34 | */ 35 | REQUIRES_ACTION = "requires_action" 36 | } 37 | 38 | export enum DateLasts { 39 | All = "All time", 40 | LastMonth = "Last 30 days", 41 | LastWeek = "Last 7 days", 42 | LastYear = "Last 365 days" 43 | } 44 | export type DateRange = { 45 | from: Date, 46 | to: Date 47 | } -------------------------------------------------------------------------------- /v1/tsconfig.admin.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext" 5 | }, 6 | "include": ["src/admin"], 7 | "exclude": ["**/*.spec.js"] 8 | } -------------------------------------------------------------------------------- /v1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "checkJs": false, 7 | "jsx": "react-jsx", 8 | "declaration": true, 9 | "outDir": "./dist", 10 | "rootDir": "./src", 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "noEmit": false, 14 | "strict": false, 15 | "moduleResolution": "node", 16 | "esModuleInterop": true, 17 | "resolveJsonModule": true, 18 | "skipLibCheck": true, 19 | "forceConsistentCasingInFileNames": true 20 | }, 21 | "include": ["src/"], 22 | "exclude": [ 23 | "dist", 24 | "build", 25 | ".cache", 26 | "tests", 27 | "**/*.spec.js", 28 | "**/*.spec.ts", 29 | "node_modules", 30 | ".eslintrc.js" 31 | ] 32 | } -------------------------------------------------------------------------------- /v1/tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | /* Emit a single file with source maps instead of having a separate file. */ 5 | "inlineSourceMap": true 6 | }, 7 | "exclude": ["src/admin", "**/*.spec.js"] 8 | } -------------------------------------------------------------------------------- /v1/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /v2/docs/medusa-store-analytics-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSC-Labs/medusa-store-analytics/4d04707866d80087d9b48f57a99a0efed85e0e1e/v2/docs/medusa-store-analytics-1.PNG -------------------------------------------------------------------------------- /v2/docs/medusa-store-analytics-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSC-Labs/medusa-store-analytics/4d04707866d80087d9b48f57a99a0efed85e0e1e/v2/docs/medusa-store-analytics-2.PNG -------------------------------------------------------------------------------- /v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rsc-labs/medusa-store-analytics-v2", 3 | "version": "0.1.4", 4 | "description": "Get analytics data about your store", 5 | "author": "RSC Labs (https://rsoftcon.com)", 6 | "license": "MIT", 7 | "files": [ 8 | ".medusa/server" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/RSC-Labs/medusa-store-analytics" 13 | }, 14 | "keywords": [ 15 | "medusa-plugin", 16 | "medusa-v2", 17 | "medusa-plugin-analytics", 18 | "analytics", 19 | "statistics", 20 | "store" 21 | ], 22 | "scripts": { 23 | "build": "medusa plugin:build", 24 | "dev": "medusa plugin:develop", 25 | "prepublishOnly": "medusa plugin:build" 26 | }, 27 | "dependencies": { 28 | "recharts": "^2.10.3", 29 | "pdfkit": "^0.15.0", 30 | "@mui/material": "^5.15.3", 31 | "react-hook-form": "^7.49.2", 32 | "@emotion/react": "^11.11.3", 33 | "@emotion/styled": "11.13.0" 34 | }, 35 | "devDependencies": { 36 | "@medusajs/admin-sdk": "^2.7.1", 37 | "@medusajs/cli": "^2.7.1", 38 | "@medusajs/framework": "^2.7.1", 39 | "@medusajs/medusa": "^2.7.1", 40 | "@medusajs/test-utils": "^2.7.1", 41 | "@medusajs/ui": "4.0.3", 42 | "@medusajs/icons": "^2.7.1", 43 | "@mikro-orm/cli": "6.4.3", 44 | "@mikro-orm/core": "6.4.3", 45 | "@mikro-orm/knex": "6.4.3", 46 | "@mikro-orm/migrations": "6.4.3", 47 | "@mikro-orm/postgresql": "6.4.3", 48 | "@swc/core": "1.5.7", 49 | "@types/node": "^20.0.0", 50 | "@types/react": "^18.3.2", 51 | "@types/react-dom": "^18.2.25", 52 | "awilix": "^8.0.1", 53 | "pg": "^8.13.0", 54 | "prop-types": "^15.8.1", 55 | "react": "^18.2.0", 56 | "react-dom": "^18.2.0", 57 | "ts-node": "^10.9.2", 58 | "typescript": "^5.6.2", 59 | "vite": "^5.2.11", 60 | "yalc": "^1.0.0-pre.53" 61 | }, 62 | "peerDependencies": { 63 | "@medusajs/admin-sdk": "^2.7.1", 64 | "@medusajs/cli": "^2.7.1", 65 | "@medusajs/framework": "^2.7.1", 66 | "@medusajs/test-utils": "^2.7.1", 67 | "@medusajs/medusa": "^2.7.1", 68 | "@medusajs/ui": "4.0.3", 69 | "@medusajs/icons": "^2.7.1", 70 | "@mikro-orm/cli": "6.4.3", 71 | "@mikro-orm/core": "6.4.3", 72 | "@mikro-orm/knex": "6.4.3", 73 | "@mikro-orm/migrations": "6.4.3", 74 | "@mikro-orm/postgresql": "6.4.3", 75 | "awilix": "^8.0.1", 76 | "pg": "^8.13.0" 77 | }, 78 | "engines": { 79 | "node": ">=20" 80 | }, 81 | "exports": { 82 | "./package.json": "./package.json", 83 | "./workflows": "./.medusa/server/src/workflows/index.js", 84 | "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js", 85 | "./modules/*": "./.medusa/server/src/modules/*/index.js", 86 | "./providers/*": "./.medusa/server/src/providers/*/index.js", 87 | "./*": "./.medusa/server/src/*.js", 88 | "./admin": { 89 | "import": "./.medusa/server/src/admin/index.mjs", 90 | "require": "./.medusa/server/src/admin/index.js", 91 | "default": "./.medusa/server/src/admin/index.js" 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /v2/src/api/admin/helpers-analytics/pro-check/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/framework/http" 17 | import { MedusaError, MedusaErrorTypes } from "@medusajs/utils" 18 | import { STORE_ANALYTICS_MODULE } from "../../../../modules/store-analytics"; 19 | import StoreAnalyticsModuleService from "../../../../modules/store-analytics/service"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const storeAnalyticsModuleService: StoreAnalyticsModuleService = req.scope.resolve(STORE_ANALYTICS_MODULE) 27 | try { 28 | res.status(200).json({ 29 | hideProTab: storeAnalyticsModuleService.getHideProSetting() 30 | }); 31 | } catch (error) { 32 | throw new MedusaError( 33 | MedusaErrorTypes.DB_ERROR, 34 | error.message 35 | ) 36 | } 37 | } -------------------------------------------------------------------------------- /v2/src/api/admin/marketing-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/framework/http" 17 | import { MedusaError, MedusaErrorTypes, OrderStatus } from "@medusajs/utils" 18 | import { STORE_ANALYTICS_MODULE } from "../../../../modules/store-analytics"; 19 | import StoreAnalyticsModuleService from "../../../../modules/store-analytics/service"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const dateRangeFrom = req.query.dateRangeFrom; 28 | const dateRangeTo = req.query.dateRangeTo; 29 | const orderStatusesFromQuery: string[] = req.query.orderStatuses as string[]; 30 | 31 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 32 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 33 | 34 | let result; 35 | const storeAnalyticsModuleService: StoreAnalyticsModuleService = req.scope.resolve(STORE_ANALYTICS_MODULE) 36 | 37 | try { 38 | switch (kind) { 39 | case 'discounts-by-count': 40 | result = await storeAnalyticsModuleService.getMarketingTopDiscounts( 41 | orderStatuses, 42 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 43 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 44 | ); 45 | break; 46 | } 47 | res.status(200).json({ 48 | analytics: result 49 | }); 50 | } catch (error) { 51 | throw new MedusaError( 52 | MedusaErrorTypes.DB_ERROR, 53 | error.message 54 | ) 55 | } 56 | } -------------------------------------------------------------------------------- /v2/src/api/admin/orders-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/framework/http" 17 | import { MedusaError, MedusaErrorTypes, OrderStatus } from "@medusajs/utils" 18 | import { STORE_ANALYTICS_MODULE } from "../../../../modules/store-analytics"; 19 | import StoreAnalyticsModuleService from "../../../../modules/store-analytics/service"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const dateRangeFrom = req.query.dateRangeFrom; 28 | const dateRangeTo = req.query.dateRangeTo; 29 | const dateRangeFromCompareTo = req.query.dateRangeFromCompareTo; 30 | const dateRangeToCompareTo = req.query.dateRangeToCompareTo; 31 | const orderStatusesFromQuery: string[] = req.query.orderStatuses as string[]; 32 | 33 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 34 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 35 | 36 | let result; 37 | const storeAnalyticsModuleService: StoreAnalyticsModuleService = req.scope.resolve(STORE_ANALYTICS_MODULE) 38 | 39 | try { 40 | switch (kind) { 41 | case 'history': 42 | result = await storeAnalyticsModuleService.getOrdersHistory( 43 | orderStatuses, 44 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 45 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 46 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 47 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 48 | ); 49 | break; 50 | case 'count': 51 | result = await storeAnalyticsModuleService.getOrdersCount( 52 | orderStatuses, 53 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 54 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 55 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 56 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 57 | ); 58 | break; 59 | case 'payment-provider': 60 | result = await storeAnalyticsModuleService.getOrdersPaymentProviderPopularity( 61 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 62 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 63 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 64 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 65 | ); 66 | break; 67 | } 68 | res.status(200).json({ 69 | analytics: result 70 | }); 71 | } catch (error) { 72 | throw new MedusaError( 73 | MedusaErrorTypes.DB_ERROR, 74 | error.message 75 | ) 76 | } 77 | } -------------------------------------------------------------------------------- /v2/src/api/admin/products-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import type { 14 | MedusaRequest, 15 | MedusaResponse, 16 | } from "@medusajs/framework/http" 17 | import { MedusaError, MedusaErrorTypes, OrderStatus } from "@medusajs/utils" 18 | import { STORE_ANALYTICS_MODULE } from "../../../../modules/store-analytics"; 19 | import StoreAnalyticsModuleService from "../../../../modules/store-analytics/service"; 20 | 21 | export const GET = async ( 22 | req: MedusaRequest, 23 | res: MedusaResponse 24 | ) => { 25 | 26 | const kind = req.params.kind; 27 | const dateRangeFrom = req.query.dateRangeFrom; 28 | const dateRangeTo = req.query.dateRangeTo; 29 | const dateRangeFromCompareTo = req.query.dateRangeFromCompareTo; 30 | const dateRangeToCompareTo = req.query.dateRangeToCompareTo; 31 | const orderStatusesFromQuery: string[] = req.query.orderStatuses as string[]; 32 | 33 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 34 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 35 | 36 | let result; 37 | const storeAnalyticsModuleService: StoreAnalyticsModuleService = req.scope.resolve(STORE_ANALYTICS_MODULE) 38 | 39 | try { 40 | switch (kind) { 41 | case 'popularity-by-count': 42 | result = await storeAnalyticsModuleService.getProductsTopVariantsByCount( 43 | orderStatuses, 44 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 45 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 46 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 47 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 48 | ); 49 | break; 50 | case 'returned-by-count': 51 | result = await storeAnalyticsModuleService.getProductsTopReturnedVariantsByCount( 52 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 53 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 54 | ); 55 | break; 56 | case 'sold-count': 57 | result = await storeAnalyticsModuleService.getProductsSoldCount( 58 | orderStatuses, 59 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 60 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 61 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 62 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 63 | ); 64 | break; 65 | case 'out-of-the-stock-variants': 66 | const limit = req.query.limit as string; 67 | result = await storeAnalyticsModuleService.getProductsOutOfTheStockVariants(limit ? parseInt(limit) : undefined); 68 | break; 69 | } 70 | res.status(200).json({ 71 | analytics: result 72 | }); 73 | } catch (error) { 74 | throw new MedusaError( 75 | MedusaErrorTypes.DB_ERROR, 76 | error.message 77 | ) 78 | } 79 | } -------------------------------------------------------------------------------- /v2/src/api/admin/reports-analytics/[kind]/route.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | 14 | import { MedusaError, MedusaErrorTypes, Modules, OrderStatus } from "@medusajs/utils" 15 | import { MedusaRequest, MedusaResponse } from "@medusajs/framework"; 16 | import StoreAnalyticsModuleService from "../../../../modules/store-analytics/service"; 17 | import { STORE_ANALYTICS_MODULE } from "../../../../modules/store-analytics"; 18 | 19 | export const POST = async ( 20 | req: MedusaRequest, 21 | res: MedusaResponse 22 | ) => { 23 | 24 | const rawRequest = req as unknown as any; 25 | 26 | const kind = req.params.kind; 27 | const body: any = rawRequest.body as any; 28 | const dateRangeFrom = body.dateRangeFrom; 29 | const dateRangeTo = body.dateRangeTo; 30 | const dateRangeFromCompareTo = body.dateRangeFromCompareTo; 31 | const dateRangeToCompareTo = body.dateRangeToCompareTo; 32 | const orderStatusesFromQuery: string[] = body.orderStatuses as string[]; 33 | 34 | const orderStatuses: OrderStatus[] = orderStatusesFromQuery !== undefined ? 35 | orderStatusesFromQuery.map(status => OrderStatus[status.toUpperCase()]).filter(orderStatus => orderStatus !== undefined): []; 36 | 37 | let result: Buffer | undefined; 38 | const storeAnalyticsModuleService: StoreAnalyticsModuleService = req.scope.resolve(STORE_ANALYTICS_MODULE) 39 | 40 | try { 41 | switch (kind) { 42 | case 'general': 43 | const regionModuleService = req.scope.resolve( 44 | Modules.REGION 45 | ) 46 | const regions = await regionModuleService.listRegions(); 47 | result = await storeAnalyticsModuleService.generateReport( 48 | regions, 49 | orderStatuses, 50 | dateRangeFrom ? new Date(Number(dateRangeFrom)) : undefined, 51 | dateRangeTo ? new Date(Number(dateRangeTo)) : undefined, 52 | dateRangeFromCompareTo ? new Date(Number(dateRangeFromCompareTo)) : undefined, 53 | dateRangeToCompareTo ? new Date(Number(dateRangeToCompareTo)) : undefined, 54 | ); 55 | break; 56 | } 57 | res.status(201).json({ 58 | buffer: result 59 | }); 60 | } catch (error) { 61 | throw new MedusaError( 62 | MedusaErrorTypes.DB_ERROR, 63 | error.message 64 | ) 65 | } 66 | } -------------------------------------------------------------------------------- /v2/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export * from './ui-components' -------------------------------------------------------------------------------- /v2/src/jobs/README.md: -------------------------------------------------------------------------------- 1 | # Custom scheduled jobs 2 | 3 | You may define custom scheduled jobs (cron jobs) by creating files in the `/jobs` directory. 4 | 5 | ```ts 6 | import { 7 | ProductService, 8 | ScheduledJobArgs, 9 | ScheduledJobConfig, 10 | } from "@medusajs/medusa"; 11 | 12 | export default async function myCustomJob({ container }: ScheduledJobArgs) { 13 | const productService: ProductService = container.resolve("productService"); 14 | 15 | const products = await productService.listAndCount(); 16 | 17 | // Do something with the products 18 | } 19 | 20 | export const config: ScheduledJobConfig = { 21 | name: "daily-product-report", 22 | schedule: "0 0 * * *", // Every day at midnight 23 | }; 24 | ``` 25 | 26 | A scheduled job is defined in two parts a `handler` and a `config`. The `handler` is a function which is invoked when the job is scheduled. The `config` is an object which defines the name of the job, the schedule, and an optional data object. 27 | 28 | The `handler` is a function which takes one parameter, an `object` of type `ScheduledJobArgs` with the following properties: 29 | 30 | - `container` - a `MedusaContainer` instance which can be used to resolve services. 31 | - `data` - an `object` containing data passed to the job when it was scheduled. This object is passed in the `config` object. 32 | - `pluginOptions` - an `object` containing plugin options, if the job is defined in a plugin. 33 | -------------------------------------------------------------------------------- /v2/src/loaders/README.md: -------------------------------------------------------------------------------- 1 | # Custom loader 2 | 3 | The loader allows you have access to the Medusa service container. This allows you to access the database and the services registered on the container. 4 | you can register custom registrations in the container or run custom code on startup. 5 | 6 | ```ts 7 | // src/loaders/my-loader.ts 8 | 9 | import { AwilixContainer } from 'awilix' 10 | 11 | /** 12 | * 13 | * @param container The container in which the registrations are made 14 | * @param config The options of the plugin or the entire config object 15 | */ 16 | export default (container: AwilixContainer, config: Record): void | Promise => { 17 | /* Implement your own loader. */ 18 | } 19 | ``` -------------------------------------------------------------------------------- /v2/src/migrations/README.md: -------------------------------------------------------------------------------- 1 | # Custom migrations 2 | 3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`. 4 | In that case you also need to provide a migration in order to create the table in the database. 5 | 6 | ## Example 7 | 8 | ### 1. Create the migration 9 | 10 | See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation. 11 | 12 | ```ts 13 | // src/migration/my-migration.ts 14 | 15 | import { MigrationInterface, QueryRunner } from "typeorm" 16 | 17 | export class MyMigration1617703530229 implements MigrationInterface { 18 | name = "myMigration1617703530229" 19 | 20 | public async up(queryRunner: QueryRunner): Promise { 21 | // write you migration here 22 | } 23 | 24 | public async down(queryRunner: QueryRunner): Promise { 25 | // write you migration here 26 | } 27 | } 28 | 29 | ``` -------------------------------------------------------------------------------- /v2/src/models/README.md: -------------------------------------------------------------------------------- 1 | # Custom models 2 | 3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`. 4 | 5 | ## Example 6 | 7 | ### 1. Create the Entity 8 | 9 | ```ts 10 | // src/models/post.ts 11 | 12 | import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm"; 13 | import { generateEntityId } from "@medusajs/utils"; 14 | import { BaseEntity } from "@medusajs/medusa"; 15 | 16 | @Entity() 17 | export class Post extends BaseEntity { 18 | @Column({type: 'varchar'}) 19 | title: string | null; 20 | 21 | @BeforeInsert() 22 | private beforeInsert(): void { 23 | this.id = generateEntityId(this.id, "post") 24 | } 25 | } 26 | ``` 27 | 28 | ### 2. Create the Migration 29 | 30 | You also need to create a Migration to create the new table in the database. See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation. 31 | 32 | ### 3. Create a Repository 33 | Entities data can be easily accessed and modified using [TypeORM Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` for the `Post` entity: 34 | 35 | ```ts 36 | // src/repositories/post.ts 37 | 38 | import { EntityRepository, Repository } from "typeorm" 39 | 40 | import { Post } from "../models/post" 41 | 42 | @EntityRepository(Post) 43 | export class PostRepository extends Repository { } 44 | ``` 45 | 46 | See more about defining and accesing your custom [Entities](https://docs.medusajs.com/advanced/backend/entities/overview) in the documentation. -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/index.ts: -------------------------------------------------------------------------------- 1 | import StoreAnalyticsModuleService from "./service" 2 | import { Module } from "@medusajs/framework/utils" 3 | 4 | export const STORE_ANALYTICS_MODULE = "storeAnalyticsModuleService" 5 | 6 | export default Module(STORE_ANALYTICS_MODULE, { 7 | service: StoreAnalyticsModuleService, 8 | }) -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./customersAnalytics" 2 | export * from "./marketingAnalytics" 3 | export * from "./ordersAnalytics" 4 | export * from "./productsAnalytics" 5 | export * from "./reportsAnalytics" 6 | export * from "./salesAnalytics" -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/pdf-templates/common.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { OrderStatus } from "@medusajs/framework/utils"; 14 | 15 | 16 | const Y_REACHED_TO_ADD_NEW_PAGE = '650'; 17 | 18 | function addPageIfReachingEnd(doc) { 19 | if (doc.y > Y_REACHED_TO_ADD_NEW_PAGE) { 20 | doc.addPage(); 21 | } 22 | } 23 | 24 | export function moveDown(doc) { 25 | addPageIfReachingEnd(doc); 26 | doc.moveDown(); 27 | } 28 | 29 | export function generateReportHeader(doc, orderStatuses: OrderStatus[], from?: Date, to?: Date, dateRangeFromCompareTo?: Date, dateRangeToCompareTo?: Date) : void { 30 | doc 31 | .fillColor("#444444") 32 | .fontSize(20) 33 | 34 | doc 35 | .text('Store analytics report', { align: "center"}) 36 | 37 | doc 38 | .fontSize(16) 39 | .moveDown() 40 | 41 | doc 42 | .text('for', { align: "center"}) 43 | .moveDown(); 44 | 45 | if (from) { 46 | doc 47 | .text(`${from.toDateString()} - ${to ? to.toDateString() : new Date(Date.now()).toDateString()}`, { align: "center"}) 48 | .moveDown(); 49 | } else { 50 | doc 51 | .text(`All time - ${to ? to.toDateString() : new Date(Date.now()).toDateString()}`, { align: "center"}) 52 | .moveDown(); 53 | } 54 | doc 55 | .fontSize(10) 56 | .text(`Filtered by order statuses:`, { align: "center"}) 57 | 58 | orderStatuses.map(orderStatus => doc 59 | .text(orderStatus, { align: "center"}) 60 | ); 61 | 62 | doc 63 | .addPage() 64 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/pdf-templates/customers-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { CustomersHistoryResult } from "../customersAnalytics"; 14 | import { moveDown } from "./common"; 15 | import { generateHr } from "./hr"; 16 | 17 | export default class PdfCustomersTemplate { 18 | private static generateTableRow( 19 | doc, 20 | date, 21 | customers 22 | ) { 23 | const startY = doc.y; 24 | doc 25 | .fontSize(10) 26 | .text(date, 70, startY, { align: "left" }) 27 | .text(customers, 320, startY, { width: 90, align: "right" }) 28 | } 29 | 30 | static generateTable(doc, customersHistoryResult: CustomersHistoryResult) : void { 31 | 32 | doc.font("Helvetica-Bold"); 33 | this.generateTableRow( 34 | doc, 35 | "Date", 36 | "New customers count", 37 | ); 38 | moveDown(doc) 39 | generateHr(doc); 40 | moveDown(doc) 41 | doc.font("Helvetica"); 42 | 43 | let totalCurrent = 0; 44 | for (const currentCustomersHistoryResult of customersHistoryResult.current) { 45 | totalCurrent += Number(currentCustomersHistoryResult.customerCount); 46 | this.generateTableRow( 47 | doc, 48 | new Date(currentCustomersHistoryResult.date).toLocaleDateString(), 49 | currentCustomersHistoryResult.customerCount 50 | ); 51 | 52 | moveDown(doc) 53 | generateHr(doc); 54 | moveDown(doc) 55 | } 56 | 57 | this.generateTableRow( 58 | doc, 59 | "Total", 60 | totalCurrent 61 | ); 62 | moveDown(doc) 63 | } 64 | 65 | static generateHeader(doc) : void { 66 | doc 67 | .fontSize(18) 68 | moveDown(doc) 69 | 70 | doc 71 | .text('New customers', 70, doc.y, { align: "left"}) 72 | 73 | doc 74 | .fontSize(12) 75 | moveDown(doc) 76 | } 77 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/pdf-templates/hr.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export function generateHr(doc) { 14 | doc 15 | .strokeColor("#aaaaaa") 16 | .lineWidth(1) 17 | .moveTo(70, doc.y) 18 | .lineTo(550, doc.y) 19 | .stroke(); 20 | } 21 | -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/pdf-templates/orders-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { generateHr } from "./hr"; 14 | import { OrdersHistoryResult } from "../utils/types"; 15 | import { moveDown } from "./common"; 16 | 17 | export default class PdfOrdersTemplate { 18 | private static generateTableRow( 19 | doc, 20 | date, 21 | orders 22 | ) { 23 | const startY = doc.y; 24 | doc 25 | .fontSize(10) 26 | .text(date, 70, startY, { align: "left" }) 27 | .text(orders, 320, startY, { width: 90, align: "right" }) 28 | } 29 | 30 | static generateTable(doc, ordersHistoryResult: OrdersHistoryResult) : void { 31 | 32 | doc.font("Helvetica-Bold"); 33 | this.generateTableRow( 34 | doc, 35 | "Date", 36 | "Orders count", 37 | ); 38 | moveDown(doc) 39 | generateHr(doc); 40 | moveDown(doc) 41 | doc.font("Helvetica"); 42 | 43 | let totalCurrent = 0; 44 | for (const currentOrdersHistoryResult of ordersHistoryResult.current) { 45 | totalCurrent += Number(currentOrdersHistoryResult.orderCount); 46 | this.generateTableRow( 47 | doc, 48 | new Date(currentOrdersHistoryResult.date).toLocaleDateString(), 49 | currentOrdersHistoryResult.orderCount 50 | ); 51 | 52 | moveDown(doc) 53 | generateHr(doc); 54 | moveDown(doc) 55 | } 56 | 57 | this.generateTableRow( 58 | doc, 59 | "Total", 60 | totalCurrent 61 | ); 62 | moveDown(doc) 63 | } 64 | 65 | static generateHeader(doc) : void { 66 | doc 67 | .fontSize(18) 68 | moveDown(doc) 69 | 70 | doc 71 | .text('Orders', 70, doc.y, { align: "left"}) 72 | 73 | doc 74 | .fontSize(12) 75 | moveDown(doc) 76 | } 77 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/pdf-templates/products-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { VariantsCountPopularityResult } from "./../productsAnalytics"; 14 | import { moveDown } from "./common"; 15 | import { generateHr } from "./hr"; 16 | 17 | export default class PdfProductsTemplate { 18 | private static generateTableRow( 19 | doc, 20 | product, 21 | variant, 22 | sum, 23 | ) { 24 | const startY = doc.y; 25 | doc 26 | .fontSize(10) 27 | .text(product, 70, startY, { align: "left" }) 28 | .text(variant, 200, startY, { align: "left" }) 29 | .text(sum, 320, startY, { width: 90, align: "right" }) 30 | } 31 | 32 | static generateTable(doc, variantsCountPopularityResult: VariantsCountPopularityResult) : void { 33 | 34 | doc.font("Helvetica-Bold"); 35 | this.generateTableRow( 36 | doc, 37 | "Product", 38 | "Variant", 39 | "Sum", 40 | ); 41 | moveDown(doc) 42 | generateHr(doc); 43 | moveDown(doc) 44 | doc.font("Helvetica"); 45 | 46 | let totalCurrent = 0; 47 | for (const currentResults of variantsCountPopularityResult.current) { 48 | totalCurrent += Number(currentResults.sum); 49 | this.generateTableRow( 50 | doc, 51 | currentResults.productTitle, 52 | currentResults.variantTitle, 53 | currentResults.sum 54 | ); 55 | 56 | moveDown(doc) 57 | generateHr(doc); 58 | moveDown(doc) 59 | } 60 | 61 | this.generateTableRow( 62 | doc, 63 | "Total", 64 | "", 65 | totalCurrent 66 | ); 67 | moveDown(doc) 68 | } 69 | 70 | static generateHeader(doc) : void { 71 | doc 72 | .fontSize(18) 73 | moveDown(doc) 74 | 75 | doc 76 | .text('Top products', 70, doc.y, { align: "left"}) 77 | 78 | doc 79 | .fontSize(12) 80 | moveDown(doc) 81 | } 82 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/pdf-templates/sales-template.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { SalesHistoryResult } from "./../salesAnalytics"; 14 | import { generateHr } from "./hr"; 15 | import { amountToDisplay } from "../utils/currency"; 16 | import { moveDown } from "./common"; 17 | import { RegionDTO } from "@medusajs/framework/types"; 18 | 19 | export default class PdfSalesTemplate { 20 | private static generateTableRow( 21 | doc, 22 | date, 23 | currencyCode, 24 | sales 25 | ) { 26 | const startY = doc.y; 27 | doc 28 | .fontSize(10) 29 | .text(date, 70, startY, { align: "left" }) 30 | .text(currencyCode, 200, startY, { align: "left" }) 31 | .text(sales, 320, startY, { width: 90, align: "right" }) 32 | } 33 | 34 | static generateTableTitle(doc, region: RegionDTO) { 35 | doc 36 | .fontSize(14) 37 | moveDown(doc) 38 | 39 | doc 40 | .text(`${region.name}`, 70, doc.y, { align: "left"}) 41 | 42 | doc 43 | .fontSize(12) 44 | moveDown(doc) 45 | } 46 | 47 | static generateTable(doc, salesHistoryResult: SalesHistoryResult) : void { 48 | 49 | doc.font("Helvetica-Bold"); 50 | this.generateTableRow( 51 | doc, 52 | "Date", 53 | "Currency code", 54 | "Sales", 55 | ); 56 | moveDown(doc) 57 | generateHr(doc); 58 | moveDown(doc) 59 | doc.font("Helvetica"); 60 | 61 | let totalCurrent = 0; 62 | for (const currentSalesResult of salesHistoryResult.current) { 63 | totalCurrent += Number(currentSalesResult.total); 64 | this.generateTableRow( 65 | doc, 66 | currentSalesResult.date.toLocaleDateString(), 67 | salesHistoryResult.currencyCode.toUpperCase(), 68 | amountToDisplay(Number(currentSalesResult.total), salesHistoryResult.currencyCode) 69 | ); 70 | 71 | moveDown(doc) 72 | generateHr(doc); 73 | moveDown(doc) 74 | } 75 | 76 | this.generateTableRow( 77 | doc, 78 | "Total", 79 | "", 80 | amountToDisplay(totalCurrent, salesHistoryResult.currencyCode) 81 | ); 82 | moveDown(doc) 83 | } 84 | 85 | static generateHeader(doc) : void { 86 | doc 87 | .fontSize(18) 88 | moveDown(doc) 89 | 90 | 91 | doc 92 | .text('Sales by region', 70, doc.y, { align: "left"}) 93 | 94 | doc 95 | .fontSize(12) 96 | moveDown(doc) 97 | } 98 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/reportsAnalytics.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { OrderStatus } from "@medusajs/framework/utils"; 14 | import { RegionDTO } from "@medusajs/framework/types"; 15 | import { PgConnectionType } from "../utils/types" 16 | 17 | import PDFDocument from 'pdfkit'; 18 | import PdfSalesTemplate from "./pdf-templates/sales-template" 19 | import PdfOrdersTemplate from "./pdf-templates/orders-template"; 20 | import PdfCustomersTemplate from "./pdf-templates/customers-template"; 21 | import { generateReportHeader } from "./pdf-templates/common"; 22 | import PdfProductsTemplate from "./pdf-templates/products-template"; 23 | import { OrdersHistoryResult } from "./ordersAnalytics"; 24 | import { SalesHistoryResult } from "./salesAnalytics"; 25 | import { CustomersHistoryResult } from "./customersAnalytics"; 26 | import { VariantsCountPopularityResult } from "./productsAnalytics"; 27 | 28 | type InjectedDependencies = { 29 | __pg_connection__: PgConnectionType, 30 | } 31 | 32 | type ReportInput = { 33 | ordersHistory: OrdersHistoryResult, 34 | salesHistories: SalesHistoryResult[], 35 | customersHistory: CustomersHistoryResult, 36 | variantsCountPopularityResult: VariantsCountPopularityResult, 37 | regions: RegionDTO[] 38 | } 39 | 40 | export class ReportsAnalyticsService { 41 | 42 | protected pgConnection: PgConnectionType; 43 | 44 | constructor({ __pg_connection__ }: InjectedDependencies) { 45 | this.pgConnection = __pg_connection__ 46 | } 47 | async generateReport(orderStatuses: OrderStatus[], input: ReportInput, from?: Date, to?: Date, dateRangeFromCompareTo?: Date, dateRangeToCompareTo?: Date) { 48 | var doc = new PDFDocument(); 49 | 50 | const buffers = [] 51 | doc.on("data", buffers.push.bind(buffers)) 52 | 53 | generateReportHeader(doc, orderStatuses, from, to, dateRangeFromCompareTo, dateRangeToCompareTo); 54 | 55 | // Orders 56 | PdfOrdersTemplate.generateHeader(doc); 57 | PdfOrdersTemplate.generateTable(doc, input.ordersHistory); 58 | 59 | // Sales 60 | PdfSalesTemplate.generateHeader(doc); 61 | for (const region of input.regions) { 62 | const salesHistory = input.salesHistories.find(salesHistory => salesHistory.currencyCode == region.currency_code); 63 | if (salesHistory) { 64 | PdfSalesTemplate.generateTableTitle(doc, region); 65 | PdfSalesTemplate.generateTable(doc, salesHistory); 66 | } 67 | } 68 | 69 | // Customers 70 | doc.addPage(); 71 | PdfCustomersTemplate.generateHeader(doc); 72 | PdfCustomersTemplate.generateTable(doc, input.customersHistory); 73 | 74 | // Products 75 | doc.addPage(); 76 | PdfProductsTemplate.generateHeader(doc); 77 | PdfProductsTemplate.generateTable(doc, input.variantsCountPopularityResult); 78 | 79 | doc.end(); 80 | 81 | const bufferPromise = new Promise(resolve => { 82 | doc.on("end", () => { 83 | const pdfData = Buffer.concat(buffers) 84 | resolve(pdfData) 85 | }) 86 | }) 87 | 88 | return await bufferPromise; 89 | } 90 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/utils/currency.ts: -------------------------------------------------------------------------------- 1 | import { defaultCurrencies } from "@medusajs/utils"; 2 | 3 | const DEFAULT_DECIMAL_DIGITS = 2; 4 | 5 | export function getDecimalDigits(currencyCode: string): number { 6 | try { 7 | const decimalDigits = defaultCurrencies[currencyCode.toUpperCase()] !== undefined ? defaultCurrencies[currencyCode.toUpperCase()].decimal_digits : undefined; 8 | if (decimalDigits !== undefined) { 9 | return decimalDigits; 10 | } 11 | } catch { 12 | return DEFAULT_DECIMAL_DIGITS; 13 | } 14 | return DEFAULT_DECIMAL_DIGITS; 15 | } 16 | 17 | export function amountToDisplay(amount: number, currencyCode: string) : string { 18 | const decimalDigits = getDecimalDigits(currencyCode); 19 | return `${(amount / Math.pow(10, decimalDigits)).toFixed(decimalDigits)} ${currencyCode.toUpperCase()}`; 20 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/services/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | type OrdersHistory = { 14 | orderCount: string, 15 | date: string 16 | } 17 | 18 | export type OrdersHistoryResult = { 19 | dateRangeFrom?: number 20 | dateRangeTo?: number, 21 | dateRangeFromCompareTo?: number, 22 | dateRangeToCompareTo?: number, 23 | current: OrdersHistory[]; 24 | previous: OrdersHistory[]; 25 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/utils/currency.ts: -------------------------------------------------------------------------------- 1 | import { defaultCurrencies } from "@medusajs/utils"; 2 | 3 | const DEFAULT_DECIMAL_DIGITS = 2; 4 | 5 | export function getDecimalDigits(currencyCode: string): number { 6 | try { 7 | const decimalDigits = defaultCurrencies[currencyCode.toUpperCase()] !== undefined ? defaultCurrencies[currencyCode.toUpperCase()].decimal_digits : undefined; 8 | if (decimalDigits !== undefined) { 9 | return decimalDigits; 10 | } 11 | } catch { 12 | return DEFAULT_DECIMAL_DIGITS; 13 | } 14 | return DEFAULT_DECIMAL_DIGITS; 15 | } 16 | 17 | export function amountToDisplay(amount: number, currencyCode: string) : string { 18 | const decimalDigits = getDecimalDigits(currencyCode); 19 | return `${(amount / Math.pow(10, decimalDigits)).toFixed(decimalDigits)} ${currencyCode.toUpperCase()}`; 20 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/utils/dateTransformations.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export enum DateResolutionType { 14 | Day = 'day', 15 | Month = 'month' 16 | } 17 | 18 | export function calculateResolution(date?: Date, toDate?: Date) : DateResolutionType { 19 | if (!date) return DateResolutionType.Month; 20 | 21 | const weekAgoTruncated = new Date(new Date(Date.now() - 604800000).setHours(0,0,0,0)); 22 | const monthAgoTruncated = new Date(new Date(new Date().setMonth(new Date().getMonth() - 1)).setHours(0,0,0,0)); 23 | const yearAgoTruncated = new Date(new Date(new Date().setFullYear(new Date().getFullYear() - 1)).setHours(0,0,0,0)); 24 | 25 | if (toDate) { 26 | const diffTime = toDate.getTime() - date.getTime(); 27 | 28 | const weekTime = 604800000; 29 | const monthTime = weekTime * 4; 30 | if (diffTime <= monthTime) { 31 | return DateResolutionType.Day; 32 | } 33 | const yearTime = monthTime * 12; 34 | 35 | if (diffTime < yearTime) { 36 | return DateResolutionType.Month; 37 | } 38 | } 39 | 40 | if (date.getTime() >= weekAgoTruncated.getTime()) { 41 | return DateResolutionType.Day; 42 | } 43 | 44 | if (date.getTime() >= monthAgoTruncated.getTime()) { 45 | return DateResolutionType.Day; 46 | } 47 | 48 | if (date.getTime() > yearAgoTruncated.getTime()) { 49 | return DateResolutionType.Month; 50 | } 51 | return DateResolutionType.Month 52 | } 53 | 54 | export function getTruncateFunction(dateResolution: DateResolutionType) : (date: Date) => Date { 55 | if (dateResolution == DateResolutionType.Day) { 56 | return (date: Date) => new Date(new Date(date).setHours(0,0,0,0)); 57 | } else { 58 | return (date: Date) => new Date(new Date(new Date(date).setDate(0)).setHours(0,0,0,0)) 59 | } 60 | } -------------------------------------------------------------------------------- /v2/src/modules/store-analytics/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { ModulesSdkUtils } from "@medusajs/framework/utils" 14 | 15 | type OrdersHistory = { 16 | orderCount: string, 17 | date: string 18 | } 19 | 20 | export type OrdersHistoryResult = { 21 | dateRangeFrom?: number 22 | dateRangeTo?: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | current: OrdersHistory[]; 26 | previous: OrdersHistory[]; 27 | } 28 | 29 | export type PgConnectionType = ReturnType; -------------------------------------------------------------------------------- /v2/src/subscribers/README.md: -------------------------------------------------------------------------------- 1 | # Custom subscribers 2 | 3 | You may define custom eventhandlers, `subscribers` by creating files in the `/subscribers` directory. 4 | 5 | ```ts 6 | import MyCustomService from "../services/my-custom"; 7 | import { 8 | OrderService, 9 | SubscriberArgs, 10 | SubscriberConfig, 11 | } from "@medusajs/medusa"; 12 | 13 | type OrderPlacedEvent = { 14 | id: string; 15 | no_notification: boolean; 16 | }; 17 | 18 | export default async function orderPlacedHandler({ 19 | data, 20 | eventName, 21 | container, 22 | }: SubscriberArgs) { 23 | const orderService: OrderService = container.resolve(OrderService); 24 | 25 | const order = await orderService.retrieve(data.id, { 26 | relations: ["items", "items.variant", "items.variant.product"], 27 | }); 28 | 29 | // Do something with the order 30 | } 31 | 32 | export const config: SubscriberConfig = { 33 | event: OrderService.Events.PLACED, 34 | }; 35 | ``` 36 | 37 | A subscriber is defined in two parts a `handler` and a `config`. The `handler` is a function which is invoked when an event is emitted. The `config` is an object which defines which event(s) the subscriber should subscribe to. 38 | 39 | The `handler` is a function which takes one parameter, an `object` of type `SubscriberArgs` with the following properties: 40 | 41 | - `data` - an `object` of type `T` containing information about the event. 42 | - `eventName` - a `string` containing the name of the event. 43 | - `container` - a `MedusaContainer` instance which can be used to resolve services. 44 | - `pluginOptions` - an `object` containing plugin options, if the subscriber is defined in a plugin. 45 | -------------------------------------------------------------------------------- /v2/src/ui-components/common/icon-comparison.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { ArrowUpMini, ArrowDownMini, MinusMini } from "@medusajs/icons" 14 | 15 | export const IconComparison = ({current, previous, switchArrow} : {current: number, previous?: number, switchArrow?: boolean}) => { 16 | if (current == previous) { 17 | return 18 | } 19 | if (previous && (current > previous)) { 20 | return 21 | } 22 | if (previous && (current < previous)) { 23 | return 24 | } 25 | } -------------------------------------------------------------------------------- /v2/src/ui-components/common/percentage-comparison.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Tooltip, TooltipProvider } from "@medusajs/ui"; 14 | import { calculatePercentage } from "../utils/helpers"; 15 | 16 | export const PercentageComparison = ({current, label, previous, headingLevel = "h2" } : {current: string, label: string, previous: string, headingLevel?: any}) => { 17 | const percentage: number | undefined = calculatePercentage(parseInt(current), parseInt(previous)); 18 | return ( 19 | 20 | 21 | 22 | 23 | {percentage !== undefined ? `${percentage}%` : `N/A`} 24 | 25 | 26 | 27 | 28 | ) 29 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/cumulative-history/cumulative-customers-card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Users } from "@medusajs/icons"; 15 | import { Grid } from "@mui/material"; 16 | import type { DateRange } from "../../utils/types"; 17 | import { CumulativeCustomersChart } from "./cumulative-customers-chart"; 18 | 19 | export const CumulativeCustomersCard = ({dateRange, dateRangeCompareTo, compareEnabled} : 20 | {dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Cumulative customers 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/customers-overview-card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Users } from "@medusajs/icons"; 15 | import { Grid } from "@mui/material"; 16 | import type { DateRange } from "../utils/types"; 17 | import { CustomersNumber } from "./customers-number-overview"; 18 | import { CustomersByNewChart } from "./customers-by-new-chart"; 19 | 20 | export const CustomersOverviewCard = ({dateRange, dateRangeCompareTo, compareEnabled} : 21 | {dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | New customers 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ) 44 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/repeat-customer-rate/customers-repeat-customer-rate-number.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../../common/percentage-comparison"; 16 | import { IconComparison } from "../../common/icon-comparison"; 17 | import { CustomersRepeatCustomerRateResponse } from "../types" 18 | 19 | export const RepeatCustomerRateNummber = ({repeatCustomerRateResponse, compareEnabled} : {repeatCustomerRateResponse: CustomersRepeatCustomerRateResponse, compareEnabled?: boolean}) => { 20 | const currentPercentage: number | undefined = 21 | repeatCustomerRateResponse.analytics.current !== undefined && repeatCustomerRateResponse.analytics.current.returnCustomerRate !== undefined ? 22 | parseInt(repeatCustomerRateResponse.analytics.current.returnCustomerRate) : undefined; 23 | const previousPercentage: number | undefined = 24 | repeatCustomerRateResponse.analytics.previous !== undefined && repeatCustomerRateResponse.analytics.previous.returnCustomerRate !== undefined ? 25 | parseInt(repeatCustomerRateResponse.analytics.previous.returnCustomerRate) : undefined; 26 | 27 | return ( 28 | 29 | 30 | {currentPercentage !== undefined ? 31 | 32 | {`${currentPercentage}%`} 33 | : 34 | 35 | {`No orders or customers`} 36 | 37 | } 38 | 39 | {compareEnabled && repeatCustomerRateResponse.analytics.dateRangeFromCompareTo && currentPercentage !== undefined && 40 | 41 | 42 | 43 | 44 | 45 | {previousPercentage !== undefined && 46 | 47 | } 48 | 49 | 50 | } 51 | 52 | ); 53 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/repeat-customer-rate/order-frequency-distribution-chart.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { getLegendName } from "../../common/utils/chartUtils"; 14 | import { CustomersRepeatCustomerRateResponse, Distributions } from "../types" 15 | import { Legend, Pie, PieChart, Tooltip } from "recharts"; 16 | 17 | const ONE_TIME_LABEL_NAME = 'One-time purchase'; 18 | const REPEAT_LABEL_NAME = 'Repeat purchase'; 19 | 20 | function convertToChartData(distributions: Distributions) { 21 | if (distributions) { 22 | if (distributions.orderOneTimeFrequency || distributions.orderRepeatFrequency) { 23 | const oneTimeValue = distributions.orderOneTimeFrequency ? parseInt(distributions.orderOneTimeFrequency) : 0; 24 | const repeatValue = distributions.orderRepeatFrequency ? parseInt(distributions.orderRepeatFrequency) : 0; 25 | return [ 26 | { 27 | name: ONE_TIME_LABEL_NAME, 28 | value: oneTimeValue, 29 | displayValue: ONE_TIME_LABEL_NAME 30 | }, 31 | { 32 | name: REPEAT_LABEL_NAME, 33 | value: repeatValue, 34 | displayValue: REPEAT_LABEL_NAME 35 | } 36 | ] 37 | } 38 | } 39 | return undefined; 40 | } 41 | 42 | export const OrderFrequencyDistributionPieChart = ({repeatCustomerRateResponse, compareEnabled} : {repeatCustomerRateResponse: CustomersRepeatCustomerRateResponse, compareEnabled?: boolean}) => { 43 | 44 | const currentData = convertToChartData(repeatCustomerRateResponse.analytics.current); 45 | const previousData = convertToChartData(repeatCustomerRateResponse.analytics.previous); 46 | 47 | const renderLabel = function(entry) { 48 | return entry.displayValue; 49 | } 50 | 51 | return ( 52 | 53 | 54 | {compareEnabled && repeatCustomerRateResponse.analytics.dateRangeFromCompareTo && currentData !== undefined && 55 | 56 | } 57 | {(compareEnabled && repeatCustomerRateResponse.analytics.dateRangeFromCompareTo) && } 67 | `${value}%`}/> 68 | 69 | ); 70 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/repeat-customer-rate/order-frequency-distribution.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { CustomersRepeatCustomerRateResponse } from "../types" 16 | import { OrderFrequencyDistributionPieChart } from "./order-frequency-distribution-chart"; 17 | 18 | export const OrderFrequencyDistribution = ({repeatCustomerRateResponse, compareEnabled} : {repeatCustomerRateResponse: CustomersRepeatCustomerRateResponse, compareEnabled?: boolean}) => { 19 | return ( 20 | 21 | 22 | 23 | How orders were distributed? 24 | 25 | 26 | 27 | 28 | 29 | 30 | ) 31 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/retention-customer-rate/customers-retention-customer-rate-number.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../../common/percentage-comparison"; 16 | import { IconComparison } from "../../common/icon-comparison"; 17 | import { CustomersRetentionCustomerRateResponse } from "../types" 18 | 19 | export const RetentionCustomerRateNumber = ({retentionCustomerRateResponse, compareEnabled} : {retentionCustomerRateResponse: CustomersRetentionCustomerRateResponse, compareEnabled?: boolean}) => { 20 | const currentPercentage: number | undefined = 21 | retentionCustomerRateResponse.analytics.current !== undefined ? 22 | parseInt(retentionCustomerRateResponse.analytics.current) : undefined; 23 | const previousPercentage: number | undefined = 24 | retentionCustomerRateResponse.analytics.previous !== undefined ? 25 | parseInt(retentionCustomerRateResponse.analytics.previous) : undefined; 26 | 27 | return ( 28 | 29 | 30 | {currentPercentage !== undefined ? 31 | 32 | {`${currentPercentage}%`} 33 | : 34 | 35 | {`No orders or customers`} 36 | 37 | } 38 | 39 | {compareEnabled && retentionCustomerRateResponse.analytics.dateRangeFromCompareTo && currentPercentage !== undefined && 40 | 41 | 42 | 43 | 44 | 45 | {previousPercentage !== undefined && 46 | 47 | } 48 | 49 | 50 | } 51 | 52 | ); 53 | } -------------------------------------------------------------------------------- /v2/src/ui-components/customers/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export type Distributions = { 14 | returnCustomerRate: string, 15 | orderOneTimeFrequency?: string, 16 | orderRepeatFrequency?: string 17 | } 18 | 19 | export type CustomersRepeatCustomerRateResponse = { 20 | analytics: { 21 | dateRangeFrom: number 22 | dateRangeTo: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | current: Distributions, 26 | previous: Distributions 27 | } 28 | } 29 | 30 | export type CustomersRetentionCustomerRateResponse = { 31 | analytics: { 32 | dateRangeFrom: number 33 | dateRangeTo: number, 34 | dateRangeFromCompareTo?: number, 35 | dateRangeToCompareTo?: number, 36 | current?: string, 37 | previous?: string 38 | } 39 | } -------------------------------------------------------------------------------- /v2/src/ui-components/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export { ComparedDate, SwitchComparison, DropdownOrderStatus, SelectDateLasts } from './common/overview-components'; 14 | 15 | export { OrdersOverviewCard } from './orders/orders-overview-card' 16 | export { OrdersPaymentProviderCard } from './orders/orders-payment-provider-card' 17 | 18 | export { SalesOverviewCard } from './sales/sales-overview-card' 19 | export { SalesChannelPopularityCard } from './sales/sales-channel-popularity-card' 20 | export { RefundsOverviewCard } from './sales/refunds/refunds-overview-card' 21 | export { RegionsPopularityCard } from './sales/regions-popularity-card' 22 | 23 | export { CustomersOverviewCard } from './customers/customers-overview-card' 24 | export { CustomersRepeatCustomerRate } from './customers/repeat-customer-rate/customers-repeat-customer-rate'; 25 | export { CustomersRetentionCustomerRate } from './customers/retention-customer-rate/customers-retention-customer-rate'; 26 | export { CumulativeCustomersCard } from './customers/cumulative-history/cumulative-customers-card'; 27 | 28 | export { VariantsTopByCountCard } from './products/variants-top-by-count'; 29 | export { ReturnedVariantsByCountCard } from './products/returned_variants/returned-variants-by-count'; 30 | export { ProductsSoldCountCard } from './products/products-sold-count'; 31 | export { OutOfTheStockVariantsCard } from './products/out_of_the_stock_variants/out-of-the-stock-variants-by-count'; 32 | 33 | export { DiscountsTopCard } from './marketing/discounts-top-by-count'; 34 | 35 | export { DateLasts, OrderStatus } from './utils/types' 36 | export type { DateRange } from './utils/types' 37 | export { convertDateLastsToComparedDateRange, convertDateLastsToDateRange, amountToDisplay, calculatePercentage } from './utils/helpers' -------------------------------------------------------------------------------- /v2/src/ui-components/marketing/discounts-top-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Divider, Grid } from "@mui/material"; 15 | 16 | export type DiscountsTopTableRow = { 17 | sum: string, 18 | discountCode: string 19 | } 20 | 21 | export const DiscountsTopTable = ({tableRows} : {tableRows: DiscountsTopTableRow[]}) => { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Discount 32 | 33 | 34 | 35 | 36 | Count 37 | 38 | 39 | 40 | 41 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 42 | 43 | 44 | 45 | 46 | {tableRow.discountCode} 47 | 48 | 49 | 50 | 51 | {tableRow.sum} 52 | 53 | 54 | 55 | 56 | )) : 57 | 58 | 59 | 60 | 61 | None 62 | 63 | 64 | 65 | 66 | None 67 | 68 | 69 | 70 | 71 | } 72 | 73 | ) 74 | } -------------------------------------------------------------------------------- /v2/src/ui-components/orders/orders-overview-card.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { ShoppingCart } from "@medusajs/icons"; 15 | import { Grid } from "@mui/material"; 16 | import { OrdersByNewChart } from "./orders-by-new-chart"; 17 | import type { DateRange } from "../utils/types"; 18 | import { OrdersNumber } from "./orders-number-overview"; 19 | import { OrderStatus } from "../utils/types"; 20 | 21 | export const OrdersOverviewCard = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 22 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Orders 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } -------------------------------------------------------------------------------- /v2/src/ui-components/orders/orders-payment-provider-chart.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { calculateResolution, getLegendName } from "../common/utils/chartUtils"; 14 | import { Legend, Pie, PieChart, Tooltip } from "recharts"; 15 | import { OrdersPaymentProvider, OrdersPaymentProviderResponse } from "./types"; 16 | import { Text, Container } from "@medusajs/ui"; 17 | 18 | function convertToChartData(ordersPaymentProviders: OrdersPaymentProvider[]) { 19 | if (ordersPaymentProviders.length) { 20 | return ordersPaymentProviders.map(ordersPaymentProvider => { 21 | return { 22 | name: ordersPaymentProvider.paymentProviderId, 23 | value: parseFloat(ordersPaymentProvider.percentage), 24 | displayValue: ordersPaymentProvider.paymentProviderId, 25 | orderCount: ordersPaymentProvider.orderCount, 26 | } 27 | }) 28 | } 29 | return undefined; 30 | } 31 | const ChartCustomTooltip = ({ active, payload, label }) => { 32 | if (active && payload && payload.length) { 33 | return ( 34 | 35 | {`${payload[0].payload.value}%`} 36 | {`Provider: ${payload[0].payload.name}`} 37 | {`Order count: ${payload[0].payload.orderCount}`} 38 | 39 | ) 40 | } 41 | return null; 42 | }; 43 | 44 | export const OrdersPaymentProviderPieChart = ({ordersPaymentProviderResponse, compareEnabled} : {ordersPaymentProviderResponse: OrdersPaymentProviderResponse, compareEnabled?: boolean}) => { 45 | 46 | const currentData = convertToChartData(ordersPaymentProviderResponse.analytics.current); 47 | const previousData = convertToChartData(ordersPaymentProviderResponse.analytics.previous); 48 | 49 | const renderLabel = function(entry) { 50 | return entry.displayValue; 51 | } 52 | 53 | return ( 54 | 55 | 56 | {compareEnabled && ordersPaymentProviderResponse.analytics.dateRangeFromCompareTo && 57 | 58 | } 59 | {(compareEnabled && ordersPaymentProviderResponse.analytics.dateRangeFromCompareTo) && } 69 | } /> 70 | 71 | ); 72 | } -------------------------------------------------------------------------------- /v2/src/ui-components/orders/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | 14 | export type OrdersPaymentProvider = { 15 | orderCount: string, 16 | percentage: string, 17 | paymentProviderId: string 18 | } 19 | 20 | export type OrdersPaymentProviderPopularityResult = { 21 | dateRangeFrom?: number 22 | dateRangeTo?: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | current: OrdersPaymentProvider[] 26 | previous: OrdersPaymentProvider[] 27 | } 28 | 29 | export type OrdersPaymentProviderResponse = { 30 | analytics: OrdersPaymentProviderPopularityResult 31 | } -------------------------------------------------------------------------------- /v2/src/ui-components/products/out_of_the_stock_variants/helpers.tsx: -------------------------------------------------------------------------------- 1 | export type AdminOutOfTheStockVariantsStatisticsQuery = {} 2 | 3 | export type OutOfTheStockVariantsCount = { 4 | productId: string, 5 | variantId: string, 6 | productTitle: string, 7 | variantTitle: string, 8 | thumbnail: string, 9 | } 10 | 11 | export type OutOfTheStockVariantsCountResult = { 12 | dateRangeFrom?: number 13 | dateRangeTo?: number, 14 | dateRangeFromCompareTo?: number, 15 | dateRangeToCompareTo?: number, 16 | current: OutOfTheStockVariantsCount[], 17 | } 18 | 19 | export type OutOfTheStockVariantsCountResponse = { 20 | analytics: OutOfTheStockVariantsCountResult 21 | } 22 | export type OutOfTheStockVariantsTableRow = { 23 | variantId: string, 24 | productId: string, 25 | productTitle: string, 26 | variantTitle: string, 27 | thumbnail: string, 28 | } 29 | 30 | export function transformToVariantTopTable(result: OutOfTheStockVariantsCountResult): OutOfTheStockVariantsTableRow[] { 31 | const currentMap = new Map(); 32 | 33 | result.current.forEach(currentItem => { 34 | currentMap.set(currentItem.variantId, { 35 | variantId: currentItem.variantId, 36 | productId: currentItem.productId, 37 | productTitle: currentItem.productTitle, 38 | variantTitle: currentItem.variantTitle, 39 | thumbnail: currentItem.thumbnail, 40 | }); 41 | }); 42 | 43 | return Array.from(currentMap.values()); 44 | } -------------------------------------------------------------------------------- /v2/src/ui-components/products/out_of_the_stock_variants/out-of-the-stock-variants-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Box, Divider, Grid } from "@mui/material"; 15 | import { Link } from "react-router-dom" 16 | import { OutOfTheStockVariantsTableRow } from "./helpers"; 17 | 18 | export const OutOfTheStockVariantsTable = ({tableRows} : {tableRows: OutOfTheStockVariantsTableRow[]}) => { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Variant 29 | 30 | 31 | 32 | 33 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 34 | 35 | 36 | 37 | 38 | 39 | {tableRow.thumbnail && 40 | 49 | } 50 | 51 | {tableRow.productTitle} - {tableRow.variantTitle} 52 | 53 | 54 | 55 | 56 | 57 | 58 | )) : 59 | 60 | 61 | 62 | 63 | None 64 | 65 | 66 | 67 | 68 | } 69 | 70 | ) 71 | } -------------------------------------------------------------------------------- /v2/src/ui-components/products/returned_variants/returned-variants-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Box, Divider, Grid } from "@mui/material"; 15 | import { Link } from "react-router-dom" 16 | 17 | export type VariantsTopTableRow = { 18 | sum: string, 19 | productId: string, 20 | productTitle: string, 21 | variantTitle: string, 22 | thumbnail: string, 23 | } 24 | 25 | export const ReturnedVariantsTable = ({tableRows} : {tableRows: VariantsTopTableRow[]}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Variant 36 | 37 | 38 | 39 | 40 | Count 41 | 42 | 43 | 44 | 45 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 46 | 47 | 48 | 49 | 50 | 51 | {tableRow.thumbnail && 52 | 61 | } 62 | 63 | {tableRow.productTitle} - {tableRow.variantTitle} 64 | 65 | 66 | 67 | 68 | 69 | 70 | {tableRow.sum} 71 | 72 | 73 | 74 | 75 | )) : 76 | 77 | 78 | 79 | 80 | None 81 | 82 | 83 | 84 | 85 | None 86 | 87 | 88 | 89 | 90 | } 91 | 92 | ) 93 | } -------------------------------------------------------------------------------- /v2/src/ui-components/products/variants-top-table.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading, Text } from "@medusajs/ui"; 14 | import { Box, Divider, Grid } from "@mui/material"; 15 | import { Link } from "react-router-dom" 16 | 17 | export type VariantsTopTableRow = { 18 | sum: string, 19 | productId: string, 20 | productTitle: string, 21 | variantTitle: string, 22 | thumbnail: string, 23 | } 24 | 25 | export const VariantsTopTable = ({tableRows} : {tableRows: VariantsTopTableRow[]}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Variant 36 | 37 | 38 | 39 | 40 | Count 41 | 42 | 43 | 44 | 45 | {tableRows.length > 0 ? tableRows.map(tableRow => ( 46 | 47 | 48 | 49 | 50 | 51 | {tableRow.thumbnail && 52 | 61 | } 62 | 63 | {tableRow.productTitle} - {tableRow.variantTitle} 64 | 65 | 66 | 67 | 68 | 69 | 70 | {tableRow.sum} 71 | 72 | 73 | 74 | 75 | )) : 76 | 77 | 78 | 79 | 80 | None 81 | 82 | 83 | 84 | 85 | None 86 | 87 | 88 | 89 | 90 | } 91 | 92 | ) 93 | } -------------------------------------------------------------------------------- /v2/src/ui-components/sales/refunds/refunds-numbers.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../../common/percentage-comparison"; 16 | import { IconComparison } from "../../common/icon-comparison"; 17 | import { RefundsResponse } from "../types"; 18 | import { amountToDisplay } from "../../utils/helpers"; 19 | 20 | export const RefundsNumber = ({refundsResponse, compareEnabled} : {refundsResponse: RefundsResponse, compareEnabled?: boolean}) => { 21 | const overallCurrentSum: number = parseInt(refundsResponse.analytics.current); 22 | const overallPreviousSum: number | undefined = refundsResponse.analytics.previous !== undefined ? 23 | parseInt(refundsResponse.analytics.previous) : undefined; 24 | 25 | return ( 26 | 27 | 28 | 29 | {overallCurrentSum.toFixed(refundsResponse.analytics.currencyDecimalDigits)} {refundsResponse.analytics.currencyCode.toUpperCase()} 30 | 31 | 32 | {compareEnabled && refundsResponse.analytics.dateRangeFromCompareTo && 33 | 34 | 35 | 36 | 37 | 38 | {overallPreviousSum !== undefined && 39 | 40 | } 41 | 42 | 43 | } 44 | 45 | ); 46 | } -------------------------------------------------------------------------------- /v2/src/ui-components/sales/sales-number-overview.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { Grid } from "@mui/material"; 15 | import { PercentageComparison } from "../common/percentage-comparison"; 16 | import { IconComparison } from "../common/icon-comparison"; 17 | import { SalesHistoryResponse } from "./types"; 18 | import { amountToDisplay } from "../utils/helpers"; 19 | 20 | export const SalesNumber = ({salesHistoryResponse, compareEnabled} : {salesHistoryResponse: SalesHistoryResponse, compareEnabled?: boolean}) => { 21 | const overallCurrentSum: number = salesHistoryResponse.analytics.current.reduce((sum, order) => sum + parseInt(order.total), 0); 22 | const overallPreviousSum: number | undefined = salesHistoryResponse.analytics.previous.length > 0 ? 23 | salesHistoryResponse.analytics.previous.reduce((sum, order) => sum + parseInt(order.total), 0) : 24 | undefined; 25 | 26 | return ( 27 | 28 | 29 | 30 | {amountToDisplay(overallCurrentSum, salesHistoryResponse.analytics.currencyDecimalDigits)} {salesHistoryResponse.analytics.currencyCode.toUpperCase()} 31 | 32 | 33 | {compareEnabled && salesHistoryResponse.analytics.dateRangeFromCompareTo && 34 | 35 | 36 | 37 | 38 | 39 | {overallPreviousSum !== undefined && 40 | 41 | } 42 | 43 | 44 | } 45 | 46 | ); 47 | } -------------------------------------------------------------------------------- /v2/src/ui-components/sales/sales-total-chart.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Heading } from "@medusajs/ui"; 14 | import { ChartCurrentPrevious } from "../common/chart-components"; 15 | import { SalesHistoryResponse } from "./types"; 16 | import { amountToDisplay } from "../utils/helpers"; 17 | 18 | export const SalesByNewChart = ({dateRangeFrom, dateRangeTo, salesHistoryResponse, compareEnabled} : 19 | {dateRangeFrom: Date, dateRangeTo: Date, salesHistoryResponse: SalesHistoryResponse, compareEnabled?: boolean}) => { 20 | const rawChartData = { 21 | current: salesHistoryResponse.analytics.current.map(currentData => { 22 | return { 23 | date: new Date(currentData.date), 24 | value: amountToDisplay(parseInt(currentData.total), salesHistoryResponse.analytics.currencyDecimalDigits) 25 | }; 26 | }), 27 | previous: salesHistoryResponse.analytics.previous.map(previousData => { 28 | return { 29 | date: new Date(previousData.date), 30 | value: amountToDisplay(parseInt(previousData.total), salesHistoryResponse.analytics.currencyDecimalDigits) 31 | }; 32 | }), 33 | }; 34 | return ( 35 | <> 36 | Sales by time 37 | 45 | 46 | ) 47 | } -------------------------------------------------------------------------------- /v2/src/ui-components/sales/types.ts: -------------------------------------------------------------------------------- 1 | export type SalesHistory = { 2 | total: string, 3 | date: string 4 | } 5 | 6 | export type SalesHistoryResponse = { 7 | analytics: { 8 | dateRangeFrom?: number 9 | dateRangeTo?: number, 10 | dateRangeFromCompareTo?: number, 11 | dateRangeToCompareTo?: number, 12 | currencyCode: string, 13 | currencyDecimalDigits: number, 14 | current: SalesHistory[]; 15 | previous: SalesHistory[]; 16 | } 17 | } 18 | 19 | export type RefundsResponse = { 20 | analytics: { 21 | dateRangeFrom?: number 22 | dateRangeTo?: number, 23 | dateRangeFromCompareTo?: number, 24 | dateRangeToCompareTo?: number, 25 | currencyCode: string 26 | currencyDecimalDigits: number, 27 | current: string; 28 | previous: string; 29 | } 30 | } -------------------------------------------------------------------------------- /v2/src/ui-components/tabs/customers.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | CustomersOverviewCard, 16 | CustomersRepeatCustomerRate, 17 | CumulativeCustomersCard, 18 | CustomersRetentionCustomerRate, 19 | OrderStatus, 20 | } from '..'; 21 | import type { DateRange } from '..'; 22 | import { Grid } from "@mui/material"; 23 | 24 | const CustomersTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 25 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default CustomersTab -------------------------------------------------------------------------------- /v2/src/ui-components/tabs/orders.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | OrdersOverviewCard, 16 | OrderStatus, 17 | } from '..'; 18 | import type { DateRange } from '..'; 19 | import { Grid } from "@mui/material"; 20 | import { OrdersPaymentProviderCard } from "../orders/orders-payment-provider-card"; 21 | 22 | const OrdersTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 23 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | export default OrdersTab -------------------------------------------------------------------------------- /v2/src/ui-components/tabs/products.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | ProductsSoldCountCard, 16 | VariantsTopByCountCard, 17 | ReturnedVariantsByCountCard, 18 | OutOfTheStockVariantsCard, 19 | OrderStatus, 20 | } from '..'; 21 | import type { DateRange } from '..'; 22 | import { Grid } from "@mui/material"; 23 | 24 | const ProductsTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 25 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default ProductsTab -------------------------------------------------------------------------------- /v2/src/ui-components/tabs/sales.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | import { Container } from "@medusajs/ui" 14 | import { 15 | DiscountsTopCard, 16 | SalesChannelPopularityCard, 17 | OrderStatus, 18 | SalesOverviewCard, 19 | RefundsOverviewCard 20 | } from '..'; 21 | import type { DateRange } from '..'; 22 | import { Grid } from "@mui/material"; 23 | 24 | const SalesTab = ({orderStatuses, dateRange, dateRangeCompareTo, compareEnabled} : 25 | {orderStatuses: OrderStatus[], dateRange?: DateRange, dateRangeCompareTo?: DateRange, compareEnabled: boolean}) => { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ) 50 | } 51 | 52 | export default SalesTab -------------------------------------------------------------------------------- /v2/src/ui-components/utils/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RSC-Labs, https://rsoftcon.com/ 3 | * 4 | * MIT License 5 | * 6 | * Unless required by applicable law or agreed to in writing, software 7 | * distributed under the License is distributed on an "AS IS" BASIS, 8 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | * See the License for the specific language governing permissions and 10 | * limitations under the License. 11 | */ 12 | 13 | export enum OrderStatus { 14 | /** 15 | * The order is pending. 16 | */ 17 | PENDING = "pending", 18 | /** 19 | * The order is completed, meaning that 20 | * the items have been fulfilled and the payment 21 | * has been captured. 22 | */ 23 | COMPLETED = "completed", 24 | /** 25 | * The order is archived. 26 | */ 27 | ARCHIVED = "archived", 28 | /** 29 | * The order is canceled. 30 | */ 31 | CANCELED = "canceled", 32 | /** 33 | * The order requires action. 34 | */ 35 | REQUIRES_ACTION = "requires_action" 36 | } 37 | 38 | export enum DateLasts { 39 | All = "All time", 40 | LastMonth = "Last 30 days", 41 | LastWeek = "Last 7 days", 42 | LastYear = "Last 365 days" 43 | } 44 | export type DateRange = { 45 | from: Date, 46 | to: Date 47 | } -------------------------------------------------------------------------------- /v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "esModuleInterop": true, 5 | "module": "Node16", 6 | "moduleResolution": "Node16", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "skipLibCheck": true, 10 | "skipDefaultLibCheck": true, 11 | "declaration": false, 12 | "sourceMap": false, 13 | "inlineSourceMap": true, 14 | "outDir": "./.medusa/server", 15 | "rootDir": "./", 16 | "baseUrl": ".", 17 | "jsx": "react-jsx", 18 | "forceConsistentCasingInFileNames": true, 19 | "resolveJsonModule": true, 20 | "checkJs": false, 21 | "strictNullChecks": true 22 | }, 23 | "ts-node": { 24 | "swc": true 25 | }, 26 | "include": [ 27 | "**/*", 28 | ".medusa/types/*" 29 | ], 30 | "exclude": [ 31 | "node_modules", 32 | ".medusa/server", 33 | ".medusa/admin", 34 | "src/admin", 35 | ".cache" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------