├── src ├── locales │ ├── messages.json │ └── index.js ├── assets │ └── styles │ │ ├── scss │ │ ├── theme-styles.scss │ │ └── parts │ │ │ ├── main.scss │ │ │ ├── variables.scss │ │ │ ├── texts.scss │ │ │ └── backgrounds.scss │ │ └── css │ │ └── index.css ├── views │ ├── HomeView.vue │ └── DashboardView.vue ├── utils │ ├── index.js │ ├── time.js │ └── request.js ├── plugins │ ├── router.js │ ├── i18.js │ ├── index.js │ └── pinia.js ├── layouts │ ├── AppLayout.vue │ └── Default.vue ├── stores │ ├── all.js │ ├── index.js │ ├── message.js │ ├── user.js │ └── sales.js ├── App.vue ├── main.js ├── components │ ├── utils │ │ ├── Chart.vue │ │ ├── GetErrorSuccess.vue │ │ ├── Skeletons │ │ │ ├── ChartSkeleton.vue │ │ │ └── TableSkeleton.vue │ │ └── VueTable.vue │ ├── home │ │ └── Login.vue │ └── dashboard │ │ └── ComparisonTable.vue ├── router │ ├── index.js │ └── middleware │ │ └── loadLayoutMiddleware.js └── CONSTANTS.js ├── .env ├── .vscode └── extensions.json ├── .gitattributes ├── .firebaserc ├── postcss.config.js ├── public ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png └── site.webmanifest ├── jsconfig.json ├── tailwind.config.js ├── firebase.json ├── index.html ├── .gitignore ├── .prettierrc.json ├── .github └── workflows │ ├── firebase-hosting-merge.yml │ ├── firebase-hosting-pull-request.yml │ └── sonarcloud.yml ├── package.json ├── vite.config.js ├── README.md ├── .eslintrc.js └── .eslintrc-auto-import.json /src/locales/messages.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VITE_OPENAI_API_BASEURL=https://iapitest.eva.guru/ -------------------------------------------------------------------------------- /src/assets/styles/scss/theme-styles.scss: -------------------------------------------------------------------------------- 1 | @import './parts/main.scss'; 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "vue3-highcharts-tailwind" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | import messages from './messages.json'; 2 | 3 | export default messages; 4 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { request } from './request' 2 | export { dateWithDay } from './time' 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/plugins/router.js: -------------------------------------------------------------------------------- 1 | import router from '@/router' 2 | 3 | export const initRouter = () => { 4 | return router 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/styles/scss/parts/main.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './texts.scss'; 3 | @import './backgrounds.scss'; 4 | -------------------------------------------------------------------------------- /src/layouts/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/stores/all.js: -------------------------------------------------------------------------------- 1 | export { useMessageStore } from './message' 2 | export { useSalesStore } from './sales' 3 | export { useUserStore } from './user' 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | }, 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/styles/scss/parts/variables.scss: -------------------------------------------------------------------------------- 1 | /* Colors */ 2 | $primary: #0064ff; 3 | $secondary: #100045; 4 | $lightergrey: #f5f5f5; 5 | $lightgrey: #e8e8e8; 6 | $green: #05ad48; 7 | $orange: #f48c11; 8 | $red: #ff005c; 9 | -------------------------------------------------------------------------------- /src/layouts/Default.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 3 | theme: { 4 | extend: {}, 5 | }, 6 | variants: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 5 | "rewrites": [ 6 | { 7 | "source": "**", 8 | "destination": "/index.html" 9 | } 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /src/plugins/i18.js: -------------------------------------------------------------------------------- 1 | import messages from '@/locales' 2 | import { createI18n } from 'vue-i18n' 3 | 4 | const DEFAULT_LOCALE = 'en' 5 | 6 | export const initI18n = () => { 7 | return createI18n({ 8 | legacy: false, 9 | locale: DEFAULT_LOCALE, 10 | messages, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/stores/index.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | export * from "./all"; // it will be an easy way to use stores like => import { useConfig, useUser } from '@/stores' 3 | 4 | export const useMainStore = defineStore("main", { 5 | state: () => reactive({}), 6 | getters: {}, 7 | actions: {}, 8 | }); 9 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import '@/assets/styles/css/index.css' 2 | 3 | import { initPlugins } from './plugins' 4 | import { createApp } from 'vue/dist/vue.esm-bundler' // https://github.com/fengyuanchen/vue-feather/issues/8 5 | 6 | import App from './App.vue' 7 | 8 | const app = createApp(App) 9 | 10 | initPlugins(app) 11 | 12 | app.mount('#app') 13 | -------------------------------------------------------------------------------- /src/components/utils/Chart.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/utils/time.js: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns' 2 | 3 | export const dateWithDay = dateString => { 4 | let response = '' 5 | 6 | if (dateString) { 7 | try { 8 | const date = new Date(dateString) 9 | response = format(date, 'EEEE, MM-dd-yyyy') 10 | } catch (error) { 11 | console.log(error) 12 | } 13 | } 14 | 15 | return response 16 | } 17 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import { initI18n } from "./i18"; 2 | import { initPinia } from "./pinia"; 3 | import { initRouter } from "./router"; 4 | 5 | export const initPlugins = (app) => { 6 | const i18n = initI18n(app); 7 | const router = initRouter(app); 8 | const pinia = initPinia(app, router, i18n); 9 | 10 | app.use(i18n); 11 | app.use(pinia); 12 | app.use(router); 13 | }; 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Highcharts Tailwind Boilerplate 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/styles/scss/parts/texts.scss: -------------------------------------------------------------------------------- 1 | .text-primary { 2 | color: $primary; 3 | } 4 | 5 | .text-secondary { 6 | color: $secondary; 7 | } 8 | 9 | .text-success { 10 | color: $green; 11 | } 12 | 13 | .text-warninge { 14 | color: $orange; 15 | } 16 | 17 | .text-danger { 18 | color: $red; 19 | } 20 | 21 | .text-lightergrey { 22 | color: $lightergrey; 23 | } 24 | 25 | .text-lightgrey { 26 | color: $lightgrey; 27 | } 28 | -------------------------------------------------------------------------------- /src/assets/styles/scss/parts/backgrounds.scss: -------------------------------------------------------------------------------- 1 | .bg-primary { 2 | background-color: $primary; 3 | } 4 | 5 | .bg-secondary { 6 | background-color: $secondary; 7 | } 8 | 9 | .bg-success { 10 | background-color: $green; 11 | } 12 | 13 | .bg-warning { 14 | background-color: $orange; 15 | } 16 | 17 | .bg-danger { 18 | background-color: $red; 19 | } 20 | 21 | .bg-lightergrey { 22 | background-color: $lightergrey; 23 | } 24 | 25 | .bg-lightgrey { 26 | background-color: $lightgrey; 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | .npmrc 21 | .env 22 | 23 | # Editor directories and files 24 | .vscode 25 | .vscode/* 26 | !.vscode/extensions.json 27 | .idea 28 | *.suo 29 | *.ntvs* 30 | *.njsproj 31 | *.sln 32 | *.sw? 33 | 34 | *.tsbuildinfo 35 | -------------------------------------------------------------------------------- /src/plugins/pinia.js: -------------------------------------------------------------------------------- 1 | import router from '@/router' 2 | import { createPinia } from 'pinia' 3 | import { markRaw } from 'vue' 4 | 5 | const pinia = createPinia() 6 | 7 | pinia.use(({ store }) => { 8 | store.router = markRaw(router) 9 | store.i18n = markRaw(i18n) 10 | }) 11 | 12 | export const initPinia = (app, router, i18n) => { 13 | const pinia = createPinia() 14 | 15 | pinia.use(({ store }) => { 16 | store.router = markRaw(router) 17 | store.i18n = markRaw(i18n) 18 | }) 19 | 20 | return pinia 21 | } 22 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "htmlWhitespaceSensitivity": "css", 5 | "insertPragma": false, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": true, 8 | "printWidth": 120, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": true, 14 | "tabWidth": 2, 15 | "trailingComma": "all", 16 | "useTabs": false, 17 | "vueIndentScriptAndStyle": false, 18 | "endOfLine": "lf", 19 | "singleAttributePerLine": true 20 | } -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import { loadLayoutMiddleware } from '@/router/middleware/loadLayoutMiddleware' 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'home', 10 | component: () => import('../views/HomeView.vue'), 11 | }, 12 | { 13 | path: '/dashboard', 14 | name: 'dashboard', 15 | component: () => import('../views/DashboardView.vue'), 16 | }, 17 | ], 18 | }) 19 | 20 | router.beforeEach(loadLayoutMiddleware) 21 | 22 | export default router 23 | -------------------------------------------------------------------------------- /.github/workflows/firebase-hosting-merge.yml: -------------------------------------------------------------------------------- 1 | # This file was auto-generated by the Firebase CLI 2 | # https://github.com/firebase/firebase-tools 3 | 4 | name: Deploy to Firebase Hosting on merge 5 | on: 6 | push: 7 | branches: 8 | - main 9 | jobs: 10 | build_and_deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - run: yarn install && yarn build 15 | - uses: FirebaseExtended/action-hosting-deploy@v0 16 | with: 17 | repoToken: ${{ secrets.GITHUB_TOKEN }} 18 | firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_VUE3_HIGHCHARTS_TAILWIND }} 19 | channelId: live 20 | projectId: vue3-highcharts-tailwind 21 | -------------------------------------------------------------------------------- /src/assets/styles/css/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* WebKit scrollbar styles */ 6 | ::-webkit-scrollbar { 7 | width: 10px; 8 | } 9 | 10 | ::-webkit-scrollbar-thumb { 11 | background: #e1e1e1; 12 | border-radius: 5px; 13 | } 14 | 15 | ::-webkit-scrollbar-thumb:hover { 16 | background: #d1d1d1; 17 | } 18 | 19 | /* Firefox scrollbar styles */ 20 | body { 21 | scrollbar-width: thin; /* "auto" or "thin" */ 22 | scrollbar-color: #e1e1e1 #ffffff; /* thumb and track colors */ 23 | } 24 | 25 | body::-webkit-scrollbar { 26 | width: 10px; 27 | } 28 | 29 | body::-webkit-scrollbar-thumb { 30 | background: #e1e1e1; 31 | border-radius: 5px; 32 | } 33 | 34 | body::-webkit-scrollbar-thumb:hover { 35 | background: #d1d1d1; 36 | } 37 | -------------------------------------------------------------------------------- /src/router/middleware/loadLayoutMiddleware.js: -------------------------------------------------------------------------------- 1 | // this middleware is used to dynamically update the Layouts system. 2 | 3 | // as soon as the route changes, it tries to pull the layout that the users want to display. Then it loads the layout component, and assigns the loaded component to the meta in the layout Component variable. 4 | 5 | // if there is no layout defined, the default layout will be used: default 6 | 7 | export async function loadLayoutMiddleware(route) { 8 | try { 9 | const layout = route.meta.layout 10 | const layoutComponent = await import(`../../layouts/${layout}.vue`) 11 | route.meta.layoutComponent = layoutComponent.default 12 | } catch (_e) { 13 | const layoutComponent = await import(`../../layouts/Default.vue`) 14 | route.meta.layoutComponent = layoutComponent.default 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/firebase-hosting-pull-request.yml: -------------------------------------------------------------------------------- 1 | # This file was auto-generated by the Firebase CLI 2 | # https://github.com/firebase/firebase-tools 3 | 4 | name: Deploy to Firebase Hosting on PR 5 | on: pull_request 6 | permissions: 7 | checks: write 8 | contents: read 9 | pull-requests: write 10 | jobs: 11 | build_and_preview: 12 | if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: yarn install && yarn build 17 | - uses: FirebaseExtended/action-hosting-deploy@v0 18 | with: 19 | repoToken: ${{ secrets.GITHUB_TOKEN }} 20 | firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_VUE3_HIGHCHARTS_TAILWIND }} 21 | projectId: vue3-highcharts-tailwind 22 | -------------------------------------------------------------------------------- /src/components/utils/GetErrorSuccess.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /src/CONSTANTS.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | api: { 3 | login: 'oauth/token', 4 | me: 'user/user-information', 5 | sales: { 6 | dailySalesOverview: 'data/daily-sales-overview', 7 | skuData: 'data/daily-sales-sku-list', 8 | skuRefundRates: 'data/get-sku-refund-rate', 9 | }, 10 | }, 11 | dailySalesOverviewDays: [7, 14, 30, 60], 12 | defaultCurrency: '$', 13 | defaultRecordPerPage: 10, 14 | defaultRecordSize: 30, 15 | errors: { 16 | dashboard: { maxComparisonDays: 'You can not compare more than 2 days' }, 17 | noData: 'No data available', 18 | sales: { errorFetching: 'Error fetching sales' }, 19 | users: { 20 | loginError: 'Invalid Credentials', 21 | invalidToken: 'Invalid Token', 22 | userData: 'Error fetching user data', 23 | email: 'Invalid Email', 24 | get password() { 25 | return `Password should be at least ${config?.password?.minLength || 8} characters` 26 | }, 27 | }, 28 | }, 29 | maxComparisonDays: 2, 30 | password: { minLength: 8 }, 31 | routes: { dashboard: '/dashboard' }, 32 | } 33 | 34 | export default config 35 | -------------------------------------------------------------------------------- /src/components/utils/Skeletons/ChartSkeleton.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "highcharts-tailwind-boilerplate", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "sass": "sass --watch src/assets/styles/scss/theme-styles.scss:theme-style.css" 11 | }, 12 | "dependencies": { 13 | "axios": "^1.7.2", 14 | "date-fns": "^3.6.0", 15 | "highcharts": "^11.4.3", 16 | "highcharts-vue": "^2.0.1", 17 | "lodash": "^4.17.21", 18 | "pinia": "^2.1.7", 19 | "sass": "^1.77.6", 20 | "validator": "^13.12.0", 21 | "vite-plugin-commonjs": "^0.10.1", 22 | "vite-plugin-node-polyfills": "^0.19.0", 23 | "vue": "^3.4.29", 24 | "vue-i18n": "^9.13.1", 25 | "vue-router": "^4.3.3" 26 | }, 27 | "devDependencies": { 28 | "@vitejs/plugin-vue": "^5.0.5", 29 | "autoprefixer": "^10.4.19", 30 | "postcss": "^8.4.38", 31 | "tailwindcss": "^3.4.4", 32 | "unplugin-auto-import": "^0.16.7", 33 | "unplugin-vue-components": "^0.25.2", 34 | "unplugin-vue-define-options": "^1.3.18", 35 | "vite": "^5.3.1", 36 | "vite-plugin-pages": "^0.31.0", 37 | "vite-plugin-vue-devtools": "^7.3.1", 38 | "vite-plugin-vue-layouts": "^0.8.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/stores/message.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useMessageStore = defineStore('message', { 4 | state: () => ({ error: null, isSuccess: null, errorTime: 5000, successTime: 5000 }), 5 | getters: { 6 | getError() { 7 | return this.error 8 | }, 9 | getIsSuccess() { 10 | return this.isSuccess 11 | }, 12 | }, 13 | actions: { 14 | setErrorClear(payload) { 15 | this.error = payload?.error 16 | let time = payload?.time && payload?.time != undefined ? payload?.time : this.errorTime 17 | setTimeout(() => { 18 | this.error = null 19 | }, time) 20 | }, 21 | 22 | setIsSuccessClear(payload) { 23 | try { 24 | this.error = null 25 | this.isSuccess = payload?.message || null 26 | 27 | const time = payload?.time ? payload.time : this.successTime 28 | 29 | setTimeout(() => { 30 | this.isSuccess = null 31 | }, time) 32 | } catch (error) { 33 | this.setErrorClear({ error }) 34 | } 35 | }, 36 | setIsSuccess(payload) { 37 | try { 38 | this.error = null 39 | this.setIsSuccessClear(payload) 40 | } catch (error) { 41 | this.setErrorClear({ error }) 42 | } 43 | }, 44 | 45 | setError(payload) { 46 | try { 47 | this.isSuccess = null 48 | this.setErrorClear(payload) 49 | } catch (error) { 50 | this.setErrorClear({ error }) 51 | } 52 | }, 53 | clearError() { 54 | this.error = null 55 | }, 56 | }, 57 | }) 58 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue' 2 | import AutoImport from 'unplugin-auto-import/vite' 3 | import Components from 'unplugin-vue-components/vite' 4 | import DefineOptions from 'unplugin-vue-define-options/vite' 5 | import { fileURLToPath } from 'url' 6 | import { defineConfig } from 'vite' 7 | import Pages from 'vite-plugin-pages' 8 | import Layouts from 'vite-plugin-vue-layouts' 9 | import commonjs from 'vite-plugin-commonjs' 10 | import { nodePolyfills } from 'vite-plugin-node-polyfills' 11 | 12 | // https://vitejs.dev/config/ 13 | export default defineConfig({ 14 | plugins: [ 15 | vue({ 16 | template: { 17 | compilerOptions: {}, 18 | }, 19 | }), 20 | commonjs(), 21 | nodePolyfills(), 22 | Pages({}), 23 | Layouts(), 24 | Components({ 25 | /* options */ 26 | }), 27 | AutoImport({ 28 | eslintrc: { 29 | enabled: true, 30 | filepath: './.eslintrc-auto-import.json', 31 | }, 32 | imports: ['vue', 'vue-router', 'pinia'], 33 | vueTemplate: true, 34 | }), 35 | DefineOptions(), 36 | ], 37 | css: { 38 | preprocessorOptions: { 39 | scss: { 40 | additionalData: `@import "@/assets/styles/scss/parts/main.scss";`, 41 | }, 42 | }, 43 | }, 44 | define: { 'process.env': {} }, 45 | resolve: { 46 | alias: { 47 | vue$: 'vue/dist/vue.esm-bundler.js', 48 | '@': fileURLToPath(new URL('./src', import.meta.url)), 49 | }, 50 | }, 51 | build: { 52 | chunkSizeWarningLimit: 10000, 53 | rollupOptions: { 54 | output: { 55 | inlineDynamicImports: true, 56 | }, 57 | }, 58 | }, 59 | server: { 60 | port: 5018, // Change this to your desired port number 61 | }, 62 | }) 63 | -------------------------------------------------------------------------------- /.github/workflows/sonarcloud.yml: -------------------------------------------------------------------------------- 1 | name: SonarCloud analysis 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | branches: ['main'] 8 | workflow_dispatch: 9 | 10 | permissions: 11 | pull-requests: read # allows SonarCloud to decorate PRs with analysis results 12 | 13 | jobs: 14 | Analysis: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Analyze with SonarCloud 19 | 20 | # You can pin the exact commit or the version. 21 | # uses: SonarSource/sonarcloud-github-action@v2.2.0 22 | uses: SonarSource/sonarcloud-github-action@4006f663ecaf1f8093e8e4abb9227f6041f52216 23 | env: 24 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN (Settings > Secrets > Actions > add new repository secret) 25 | with: 26 | # Additional arguments for the SonarScanner CLI 27 | args: 28 | # Unique keys of your project and organization. You can find them in SonarCloud > Information (bottom-left menu) 29 | # mandatory 30 | -Dsonar.projectKey=mustafacagri_vue3-highcharts-tailwind-boilerplate 31 | -Dsonar.organization=mustafacagri 32 | # Comma-separated paths to directories containing main source files. 33 | #-Dsonar.sources= # optional, default is project base directory 34 | # Comma-separated paths to directories containing test source files. 35 | #-Dsonar.tests= # optional. For more info about Code Coverage, please refer to https://docs.sonarcloud.io/enriching/test-coverage/overview/ 36 | # Adds more detail to both client and server-side analysis logs, activating DEBUG mode for the scanner, and adding client-side environment variables and system properties to the server-side log of analysis report processing. 37 | #-Dsonar.verbose= # optional, default is false 38 | # When you need the analysis to take place in a directory other than the one from which it was launched, default is . 39 | projectBaseDir: . 40 | -------------------------------------------------------------------------------- /src/components/utils/Skeletons/TableSkeleton.vue: -------------------------------------------------------------------------------- 1 | 44 | -------------------------------------------------------------------------------- /src/components/utils/VueTable.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 56 | 57 | 94 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import CONSTANTS from '@/CONSTANTS' 3 | import { useMessageStore } from '@/stores' 4 | 5 | const request = async (type, pureUrl, params = {}, time = null) => { 6 | pureUrl ??= '' // if pureUrl is undefined, set it to an empty string 7 | 8 | const checkResponse = async data => { 9 | return data 10 | .then(res => { 11 | if (!res?.data) { 12 | useMessageStore().setErrorClear({ error: 'Server Error' }) 13 | } else { 14 | const { ApiStatusMessage: message } = res.data 15 | 16 | if (res?.data?.ApiStatus !== 200) { 17 | useMessageStore().setErrorClear({ error: message, time }) 18 | } else if (res.data) { 19 | useMessageStore().setIsSuccess({ message, time }) 20 | } else { 21 | useMessageStore().setErrorClear({ error: message, time }) 22 | } 23 | 24 | return res.data 25 | } 26 | }) 27 | .catch(error => { 28 | error = error?.response?.data?.ApiStatusMessage || 'Server Error' 29 | 30 | useMessageStore().setError({ error }) 31 | }) 32 | } 33 | 34 | const baseUrl = import.meta.env.VITE_OPENAI_API_BASEURL || '' 35 | const url = baseUrl.endsWith('/') ? baseUrl + pureUrl : baseUrl + '/' + pureUrl 36 | const token = sessionStorage.getItem('token') 37 | let contentType = 'application/json' 38 | 39 | const potentialFiles = ['image', 'image', 'file', 'files'] 40 | const foundPropertyInParams = potentialFiles.find(item => Object.prototype.hasOwnProperty.call(params, item)) 41 | 42 | if (foundPropertyInParams && !!params[foundPropertyInParams]) { 43 | contentType = 'multipart/form-data' 44 | } 45 | 46 | const headers = { 47 | Authorization: 'Bearer ' + token, 48 | 'content-type': contentType, 49 | } 50 | 51 | const withCredentials = false 52 | 53 | let options = { 54 | params, 55 | headers, 56 | withCredentials, 57 | } 58 | 59 | const response = ['get', 'delete'].includes(type) 60 | ? axios[type](url, options) 61 | : axios[type](url, params, { headers, withCredentials }) 62 | 63 | const checkedResponse = await checkResponse(response) 64 | if (checkedResponse) { 65 | // we do not return anything if the request failed since we already used useMessageStore().setError 66 | 67 | return checkedResponse 68 | } 69 | } 70 | 71 | export { request } 72 | -------------------------------------------------------------------------------- /src/components/home/Login.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 95 | -------------------------------------------------------------------------------- /src/stores/user.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { request } from '@/utils' 3 | import { useMessageStore, useSalesStore } from '@/stores' 4 | import CONSTANTS from '@/CONSTANTS' 5 | import router from '@/router' 6 | import validator from 'validator' 7 | 8 | export const useUserStore = defineStore('user', () => { 9 | const messageStore = useMessageStore() 10 | const salesStore = useSalesStore() 11 | 12 | const state = reactive({ 13 | user: {}, 14 | marketplace: null, 15 | sellerId: null, 16 | }) 17 | 18 | const login = async payload => { 19 | let response = false 20 | 21 | if (!validator.isEmail(payload?.Email)) { 22 | messageStore.setError({ error: CONSTANTS?.errors?.users?.email }) 23 | 24 | return response 25 | } else if (!validator.isLength(payload?.Password, { min: CONSTANTS?.password?.minLength })) { 26 | messageStore.setError({ error: CONSTANTS?.errors?.users?.password }) 27 | 28 | return response 29 | } 30 | 31 | const loginPayload = { 32 | ...payload, 33 | GrantType: 'password', 34 | Scope: 'amazon_data', 35 | ClientId: 'C0001', 36 | ClientSecret: 'SECRET0001', 37 | RedirectUri: 'https://api.eva.guru', 38 | } 39 | 40 | try { 41 | const res = await request('post', CONSTANTS?.api?.login, loginPayload) 42 | 43 | if (res?.Data?.AccessToken) { 44 | sessionStorage.setItem('token', res.Data.AccessToken) 45 | sessionStorage.setItem('email', payload.Email) 46 | response = true 47 | } else { 48 | messageStore.setError({ error: CONSTANTS?.errors?.users?.loginError }) 49 | } 50 | } catch (error) { 51 | messageStore.setError({ error: CONSTANTS?.errors?.users?.loginError }) 52 | } 53 | 54 | return response 55 | } 56 | 57 | const fetchMe = async () => { 58 | salesStore.dailySalesOverview = [] 59 | const email = sessionStorage.getItem('email') || '' 60 | let hasError = true 61 | 62 | if (email) { 63 | const payload = { email } 64 | 65 | try { 66 | const res = await request('post', CONSTANTS?.api?.me, payload) 67 | if (res?.Data?.user) { 68 | state.user = res.Data.user 69 | 70 | const store = res.Data.user?.store?.[0] 71 | 72 | if (store?.marketplaceName && store?.storeId) { 73 | state.marketplace = store.marketplaceName 74 | state.sellerId = store.storeId 75 | hasError = false 76 | 77 | await salesStore.fetchDailySalesOverview() 78 | } 79 | } 80 | } catch (error) {} 81 | } 82 | 83 | if (hasError) { 84 | sessionStorage.removeItem('token') 85 | sessionStorage.removeItem('email') 86 | messageStore.setError({ error: CONSTANTS?.errors?.users?.userData }) 87 | router.push({ path: '/' }) 88 | } 89 | } 90 | 91 | return { 92 | ...toRefs(state), // return all properties in state as refs 93 | fetchMe, 94 | login, 95 | } 96 | }) 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌟 Vue 3 HighCharts Tailwind Boilerplate 2 | 3 | ✨ Powered by Modern Technologies 4 | 5 | - **Vue 3**: The versatile powerhouse that makes building dynamic user interfaces a breeze. 6 | - **Tailwind**: A utility-first CSS framework for rapid UI development. 7 | - **Pinia**: Simplified and intuitive state management for Vue 3 applications. 8 | - **Vite**: Lightning-fast build tool that ensures your development experience is as smooth as silk. 9 | - **i18n**: Ready to go with translations, though not yet implemented. 10 | 11 | ## 🎥 Demo 12 | 13 | https://github.com/mustafacagri/vue3-highcharts-tailwind-boilerplate/assets/7488394/099e6d53-f6c0-4e23-b0d8-d195d8cd231b 14 | 15 | ### 🌐 Live Demo 16 | 17 | - https://vue3-highcharts-tailwind.web.app/ 18 | 19 | ### 🔑 Credentials 20 | 21 | - homework@eva.guru 22 | - Homeworkeva1\*\* 23 | 24 | ## Component Structure 25 | 26 | - **Skeletons** 27 | 28 | - **ChartSkeleton**: Placeholder for charts. 29 | - **TableSkeleton**: Placeholder for tables. 30 | 31 | - **Chart**: The component to display our charts. 32 | 33 | - **GetErrorSuccess** 34 | - **VueTable**: Component with pagination and search functionalities. 35 | 36 | ## Layout Structure 37 | 38 | The repository includes a structured layout schema that can be easily utilized. To use a new layout, simply specify its name in the router when creating a new route. All handling is managed in the `loadLayoutMiddleware.js` file. 39 | 40 | ## View Structure 41 | 42 | There are two main views in this repository: 43 | 44 | - **Home (Login screen)**: Entry point of the application. 45 | - **Dashboard**: Displays charts and related tables. 46 | 47 | ## Utility Files 48 | 49 | - **Request**: Centralized handling of HTTP requests. 50 | - **Time**: Utility functions for consistent date formatting across the application. 51 | 52 | ## CONSTANTS! 53 | 54 | The `CONSTANTS.js` file defines crucial constants for the application: 55 | 56 | ```javascript 57 | // CONSTANTS.js 58 | export default { 59 | api: { 60 | login: 'oauth/token', 61 | me: 'user/user-information', 62 | sales: { 63 | dailySalesOverview: 'data/daily-sales-overview', 64 | skuData: 'data/daily-sales-sku-list', 65 | skuRefundRates: 'data/get-sku-refund-rate', 66 | }, 67 | }, 68 | dailySalesOverviewDays: [7, 14, 30, 60], 69 | defaultCurrency: '$', 70 | defaultRecordPerPage: 10, 71 | defaultRecordSize: 30, 72 | errors: { 73 | dashboard: { maxComparisonDays: 'You can not compare more than 2 days' }, 74 | noData: 'No data available', 75 | sales: { errorFetching: 'Error fetching sales' }, 76 | users: { loginError: 'Invalid Credentials', invalidToken: 'Invalid Token' }, 77 | }, 78 | maxComparisonDays: 2, 79 | routes: { dashboard: '/dashboard' }, 80 | } 81 | ``` 82 | 83 | ## .env 84 | 85 | ``` 86 | VITE_OPENAI_API_BASEURL=https://iapitest.eva.guru/ 87 | ``` 88 | 89 | ## 🚀 Features 90 | 91 | **State Management** 92 | 93 | - **Pinia**: Simplified and intuitive state management for maintaining the application state. 94 | 95 | **Efficient Development Workflow** 96 | 97 | - **Vite**: Fast build tool for an efficient development experience. 98 | - **Modular Code Structure**: Clean and maintainable codebase with Vue 3's Composition API. 99 | 100 | ## 🛠️ Setup Instructions 101 | 102 | ### Cloning and Running Locally 103 | 104 | To get started, clone the repository and run the development server: 105 | 106 | ```bash 107 | git clone https://github.com/mustafacagri/vue3-highcharts-tailwind-boilerplate.git 108 | cd vue3-highcharts-tailwind-boilerplate 109 | yarn install 110 | yarn dev 111 | ``` 112 | 113 | The application will be running at http://localhost:5018/. You can redefine the port in the vite.config.js file. 114 | 115 | ## How can I support? 🌟 116 | 117 | - ⭐ Star my GitHub repo. 118 | - 🛠 Create pull requests, submit bugs, suggest new features or updates. 119 | -------------------------------------------------------------------------------- /src/stores/sales.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { first, isEmpty } from 'lodash' 3 | import { request } from '@/utils' 4 | import { useMessageStore, useUserStore } from '@/stores' 5 | import CONSTANTS from '@/CONSTANTS' 6 | 7 | export const useSalesStore = defineStore('sales', () => { 8 | const messageStore = useMessageStore() 9 | const userStore = useUserStore() 10 | 11 | const state = reactive({ 12 | currency: CONSTANTS?.defaultCurrency, 13 | dailySalesOverview: [], 14 | isSkuRefundRatesLoading: false, 15 | skuData: {}, 16 | skuRefundRates: [], 17 | selectedDates: [], 18 | }) 19 | 20 | const fetchDailySalesOverview = async (day = first(CONSTANTS.dailySalesOverviewDays)) => { 21 | state.dailySalesOverview = [] 22 | state.skuData = {} 23 | state.selectedDates = [] 24 | 25 | const payload = { 26 | customDateData: null, 27 | day, 28 | excludeYoYData: true, 29 | marketplace: userStore.marketplace, 30 | requestStatus: 0, 31 | sellerId: userStore.sellerId, 32 | } 33 | 34 | try { 35 | const res = await request('post', CONSTANTS?.api?.sales?.dailySalesOverview, payload) 36 | 37 | if (!isEmpty(res?.Data)) { 38 | updateCurrency(res.Data.Currency) 39 | updateDailySalesOverview(res.Data.item) 40 | } else { 41 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching }) 42 | } 43 | } catch (error) { 44 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching }) 45 | } 46 | } 47 | 48 | const updateCurrency = currency => { 49 | if (currency) { 50 | state.currency = currency 51 | } 52 | } 53 | 54 | const updateDailySalesOverview = items => { 55 | state.dailySalesOverview = items.map(item => ({ 56 | date: item.date, 57 | fbaAmount: item.fbaAmount, 58 | fbmAmount: item.fbmAmount, 59 | fbaShippingAmount: item.fbaShippingAmount, 60 | profit: item.profit, 61 | })) 62 | } 63 | 64 | const fetchSkuData = async payload => { 65 | payload.pageNumber ||= 1 66 | 67 | const { dates, pageNumber } = payload 68 | state.selectedDates = dates 69 | 70 | if (isEmpty(dates) || !Array.isArray(dates)) { 71 | state.skuRefundRates = [] 72 | return 73 | } 74 | 75 | state.isSkuRefundRatesLoading = true 76 | const data = buildRequestData(dates, pageNumber) 77 | 78 | try { 79 | const res = await request('post', CONSTANTS?.api?.sales?.skuData, data) 80 | handleSkuResponse(res, pageNumber) 81 | } catch (error) { 82 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching }) 83 | } 84 | } 85 | 86 | const buildRequestData = (dates, pageNumber) => { 87 | const data = { 88 | isDaysCompare: dates.length > 1 ? 1 : 0, 89 | marketplace: userStore.marketplace, 90 | pageNumber, 91 | pageSize: CONSTANTS.defaultRecordSize, 92 | salesDate: first(dates), 93 | sellerId: userStore.sellerId, 94 | } 95 | 96 | if (dates.length > 1) { 97 | data.salesDate2 = dates[1] 98 | } 99 | 100 | return data 101 | } 102 | 103 | const handleSkuResponse = (res, pageNumber) => { 104 | if (!isEmpty(res?.Data)) { 105 | if (pageNumber === 1) { 106 | state.skuData = res.Data 107 | } else { 108 | state.skuData.item.skuList = [...state.skuData.item.skuList, ...res.Data.item.skuList] 109 | } 110 | fetchSkuRefundRates() 111 | } else { 112 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching }) 113 | } 114 | } 115 | 116 | const fetchSkuRefundRates = async () => { 117 | const payload = { 118 | marketplace: userStore.marketplace, 119 | sellerId: userStore.sellerId, 120 | skuList: state.skuData?.item?.skuList || [], 121 | } 122 | 123 | try { 124 | const res = await request('post', CONSTANTS?.api?.sales?.skuRefundRates, payload) 125 | 126 | if (!isEmpty(res?.Data)) { 127 | state.skuRefundRates = res.Data 128 | } else { 129 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching }) 130 | } 131 | } catch (error) { 132 | messageStore.setError({ error: CONSTANTS?.errors?.sales?.errorFetching }) 133 | } 134 | 135 | state.isSkuRefundRatesLoading = false 136 | } 137 | 138 | return { 139 | ...toRefs(state), 140 | fetchDailySalesOverview, 141 | fetchSkuData, 142 | } 143 | }) 144 | -------------------------------------------------------------------------------- /src/views/DashboardView.vue: -------------------------------------------------------------------------------- 1 | 107 | 108 | 140 | 141 | 162 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | '.eslintrc-auto-import.json', 8 | 'plugin:vue/vue3-recommended', 9 | 'plugin:import/recommended', 10 | 'plugin:promise/recommended', 11 | 'plugin:sonarjs/recommended', 12 | ], 13 | parser: 'vue-eslint-parser', 14 | parserOptions: { 15 | ecmaVersion: 13, 16 | sourceType: 'module', 17 | }, 18 | plugins: ['vue'], 19 | ignorePatterns: ['src/@iconify/*.js', 'node_modules', 'dist', '*.d.ts'], 20 | rules: { 21 | 'import/extensions': never, 22 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 23 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 24 | 25 | // indentation (Already present in TypeScript) 26 | indent: ['error', 2], 27 | 28 | // Enforce trailing command (Already present in TypeScript) 29 | 'comma-dangle': ['error', 'always-multiline'], 30 | 31 | // Disable max-len 32 | 'max-len': 'off', 33 | 34 | // we don't want it 35 | semi: ['error', 'never'], 36 | 37 | // add parens ony when required in arrow function 38 | 'arrow-parens': ['error', 'as-needed'], 39 | 40 | // add new line above comment 41 | 'newline-before-return': 'error', 42 | 43 | // add new line above comment 44 | 'lines-around-comment': [ 45 | 'error', 46 | { 47 | beforeBlockComment: true, 48 | beforeLineComment: true, 49 | allowBlockStart: true, 50 | allowClassStart: true, 51 | allowObjectStart: true, 52 | allowArrayStart: true, 53 | }, 54 | ], 55 | 56 | 'vue/html-self-closing': [ 57 | 'error', 58 | { 59 | html: { 60 | void: 'always', 61 | normal: 'always', 62 | component: 'always', 63 | }, 64 | svg: 'always', 65 | math: 'always', 66 | }, 67 | ], 68 | 69 | 'array-element-newline': ['error', 'consistent'], 70 | 'array-bracket-newline': ['error', 'consistent'], 71 | 72 | 'vue/multi-word-component-names': 'off', 73 | 74 | // Plugin: eslint-plugin-import 75 | 'import/prefer-default-export': 'off', 76 | 77 | // Plugin: eslint-plugin-import 78 | // For omitting extension for ts files 79 | 'import/extensions': [ 80 | 'error', 81 | 'ignorePackages', 82 | { 83 | js: 'never', 84 | jsx: 'never', 85 | ts: 'never', 86 | tsx: 'never', 87 | }, 88 | ], 89 | 90 | // ignore virtual files 91 | 'import/no-unresolved': [ 92 | 2, 93 | { 94 | ignore: [ 95 | '~pages$', 96 | 'virtual:generated-layouts', 97 | 98 | // Ignore vite's ?raw imports 99 | '.*?raw', 100 | ], 101 | }, 102 | ], 103 | 104 | // Thanks: https://stackoverflow.com/a/63961972/10796681 105 | 'no-shadow': 'off', 106 | 107 | // Plugin: eslint-plugin-promise 108 | 'promise/always-return': 'off', 109 | 'promise/catch-or-return': 'off', 110 | 111 | // ESLint plugin vue 112 | 'vue/block-tag-newline': 'error', 113 | 'vue/component-api-style': 'error', 114 | 'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }], 115 | 'vue/custom-event-name-casing': [ 116 | 'error', 117 | 'camelCase', 118 | { 119 | ignores: ['/^(click):[a-z]+((d)|([A-Z0-9][a-z0-9]+))*([A-Z])?/'], 120 | }, 121 | ], 122 | 'vue/define-macros-order': 'error', 123 | 'vue/html-comment-content-newline': 'error', 124 | 'vue/html-comment-content-spacing': 'error', 125 | 'vue/html-comment-indent': 'error', 126 | 'vue/match-component-file-name': 'error', 127 | 'vue/no-child-content': 'error', 128 | 129 | // NOTE this rule only supported in SFC, Users of the unplugin-vue-define-options should disable that rule: https://github.com/vuejs/eslint-plugin-vue/issues/1886 130 | // 'vue/no-duplicate-attr-inheritance': 'error', 131 | 'vue/no-empty-component-block': 'error', 132 | 'vue/no-multiple-objects-in-class': 'error', 133 | 'vue/no-reserved-component-names': 'error', 134 | 'vue/no-template-target-blank': 'error', 135 | 'vue/no-useless-mustaches': 'error', 136 | 'vue/no-useless-v-bind': 'error', 137 | 'vue/padding-line-between-blocks': 'error', 138 | 'vue/prefer-separate-static-class': 'error', 139 | 'vue/prefer-true-attribute-shorthand': 'error', 140 | 'vue/v-on-function-call': 'error', 141 | 142 | // -- Extension Rules 143 | 'vue/no-irregular-whitespace': 'error', 144 | 'vue/template-curly-spacing': 'error', 145 | 146 | // -- Sonarlint 147 | 'sonarjs/no-duplicate-string': 'off', 148 | 'sonarjs/no-nested-template-literals': 'off', 149 | }, 150 | settings: { 151 | 'import/resolver': { 152 | node: { 153 | extensions: ['.ts', '.js', '.tsx', '.jsx', '.mjs', '.vue'], 154 | }, 155 | alias: { 156 | extensions: ['.ts', '.js', '.tsx', '.jsx', '.mjs', '.vue'], 157 | map: [ 158 | ['@', './src'], 159 | ['@core', './src/@core'], 160 | ['@layouts', './src/@layouts'], 161 | ['@configured-variables', './src/styles/variables/_template.scss'], 162 | ['@axios', './src/plugins/axios'], 163 | ['apexcharts', 'node_modules/apexcharts-clevision'], 164 | ], 165 | }, 166 | }, 167 | }, 168 | } 169 | -------------------------------------------------------------------------------- /.eslintrc-auto-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "EffectScope": true, 4 | "acceptHMRUpdate": true, 5 | "asyncComputed": true, 6 | "autoResetRef": true, 7 | "computed": true, 8 | "computedAsync": true, 9 | "computedEager": true, 10 | "computedInject": true, 11 | "computedWithControl": true, 12 | "controlledComputed": true, 13 | "controlledRef": true, 14 | "createApp": true, 15 | "createEventHook": true, 16 | "createGlobalState": true, 17 | "createInjectionState": true, 18 | "createPinia": true, 19 | "createReactiveFn": true, 20 | "createSharedComposable": true, 21 | "createUnrefFn": true, 22 | "customRef": true, 23 | "debouncedRef": true, 24 | "debouncedWatch": true, 25 | "defineAsyncComponent": true, 26 | "defineComponent": true, 27 | "defineStore": true, 28 | "eagerComputed": true, 29 | "effectScope": true, 30 | "extendRef": true, 31 | "getActivePinia": true, 32 | "getCurrentInstance": true, 33 | "getCurrentScope": true, 34 | "h": true, 35 | "ignorableWatch": true, 36 | "inject": true, 37 | "isDefined": true, 38 | "isProxy": true, 39 | "isReactive": true, 40 | "isReadonly": true, 41 | "isRef": true, 42 | "logicAnd": true, 43 | "logicNot": true, 44 | "logicOr": true, 45 | "makeDestructurable": true, 46 | "mapActions": true, 47 | "mapGetters": true, 48 | "mapState": true, 49 | "mapStores": true, 50 | "mapWritableState": true, 51 | "markRaw": true, 52 | "nextTick": true, 53 | "onActivated": true, 54 | "onBeforeMount": true, 55 | "onBeforeUnmount": true, 56 | "onBeforeUpdate": true, 57 | "onClickOutside": true, 58 | "onDeactivated": true, 59 | "onErrorCaptured": true, 60 | "onKeyStroke": true, 61 | "onLongPress": true, 62 | "onMounted": true, 63 | "onRenderTracked": true, 64 | "onRenderTriggered": true, 65 | "onScopeDispose": true, 66 | "onServerPrefetch": true, 67 | "onStartTyping": true, 68 | "onUnmounted": true, 69 | "onUpdated": true, 70 | "pausableWatch": true, 71 | "provide": true, 72 | "reactify": true, 73 | "reactifyObject": true, 74 | "reactive": true, 75 | "reactiveComputed": true, 76 | "reactiveOmit": true, 77 | "reactivePick": true, 78 | "readonly": true, 79 | "ref": true, 80 | "refAutoReset": true, 81 | "refDebounced": true, 82 | "refDefault": true, 83 | "refThrottled": true, 84 | "refWithControl": true, 85 | "resolveComponent": true, 86 | "resolveRef": true, 87 | "resolveUnref": true, 88 | "setActivePinia": true, 89 | "setMapStoreSuffix": true, 90 | "shallowReactive": true, 91 | "shallowReadonly": true, 92 | "shallowRef": true, 93 | "storeToRefs": true, 94 | "syncRef": true, 95 | "syncRefs": true, 96 | "templateRef": true, 97 | "throttledRef": true, 98 | "throttledWatch": true, 99 | "toRaw": true, 100 | "toReactive": true, 101 | "toRef": true, 102 | "toRefs": true, 103 | "triggerRef": true, 104 | "tryOnBeforeMount": true, 105 | "tryOnBeforeUnmount": true, 106 | "tryOnMounted": true, 107 | "tryOnScopeDispose": true, 108 | "tryOnUnmounted": true, 109 | "unref": true, 110 | "unrefElement": true, 111 | "until": true, 112 | "useActiveElement": true, 113 | "useAsyncQueue": true, 114 | "useAsyncState": true, 115 | "useAttrs": true, 116 | "useBase64": true, 117 | "useBattery": true, 118 | "useBluetooth": true, 119 | "useBreakpoints": true, 120 | "useBroadcastChannel": true, 121 | "useBrowserLocation": true, 122 | "useCached": true, 123 | "useClamp": true, 124 | "useClipboard": true, 125 | "useColorMode": true, 126 | "useConfirmDialog": true, 127 | "useCounter": true, 128 | "useCssModule": true, 129 | "useCssVar": true, 130 | "useCssVars": true, 131 | "useCurrentElement": true, 132 | "useCycleList": true, 133 | "useDark": true, 134 | "useDateFormat": true, 135 | "useDebounce": true, 136 | "useDebounceFn": true, 137 | "useDebouncedRefHistory": true, 138 | "useDeviceMotion": true, 139 | "useDeviceOrientation": true, 140 | "useDevicePixelRatio": true, 141 | "useDevicesList": true, 142 | "useDisplayMedia": true, 143 | "useDocumentVisibility": true, 144 | "useDraggable": true, 145 | "useDropZone": true, 146 | "useElementBounding": true, 147 | "useElementByPoint": true, 148 | "useElementHover": true, 149 | "useElementSize": true, 150 | "useElementVisibility": true, 151 | "useEventBus": true, 152 | "useEventListener": true, 153 | "useEventSource": true, 154 | "useEyeDropper": true, 155 | "useFavicon": true, 156 | "useFetch": true, 157 | "useFileDialog": true, 158 | "useFileSystemAccess": true, 159 | "useFocus": true, 160 | "useFocusWithin": true, 161 | "useFps": true, 162 | "useFullscreen": true, 163 | "useGamepad": true, 164 | "useGeolocation": true, 165 | "useI18n": true, 166 | "useIdle": true, 167 | "useImage": true, 168 | "useInfiniteScroll": true, 169 | "useIntersectionObserver": true, 170 | "useInterval": true, 171 | "useIntervalFn": true, 172 | "useKeyModifier": true, 173 | "useLastChanged": true, 174 | "useLocalStorage": true, 175 | "useMagicKeys": true, 176 | "useManualRefHistory": true, 177 | "useMediaControls": true, 178 | "useMediaQuery": true, 179 | "useMemoize": true, 180 | "useMemory": true, 181 | "useMounted": true, 182 | "useMouse": true, 183 | "useMouseInElement": true, 184 | "useMousePressed": true, 185 | "useMutationObserver": true, 186 | "useNavigatorLanguage": true, 187 | "useNetwork": true, 188 | "useNow": true, 189 | "useObjectUrl": true, 190 | "useOffsetPagination": true, 191 | "useOnline": true, 192 | "usePageLeave": true, 193 | "useParallax": true, 194 | "usePermission": true, 195 | "usePointer": true, 196 | "usePointerSwipe": true, 197 | "usePreferredColorScheme": true, 198 | "usePreferredDark": true, 199 | "usePreferredLanguages": true, 200 | "useRafFn": true, 201 | "useRefHistory": true, 202 | "useResizeObserver": true, 203 | "useRoute": true, 204 | "useRouter": true, 205 | "useScreenOrientation": true, 206 | "useScreenSafeArea": true, 207 | "useScriptTag": true, 208 | "useScroll": true, 209 | "useScrollLock": true, 210 | "useSessionStorage": true, 211 | "useShare": true, 212 | "useSlots": true, 213 | "useSpeechRecognition": true, 214 | "useSpeechSynthesis": true, 215 | "useStepper": true, 216 | "useStorage": true, 217 | "useStorageAsync": true, 218 | "useStyleTag": true, 219 | "useSwipe": true, 220 | "useTemplateRefsList": true, 221 | "useTextSelection": true, 222 | "useTextareaAutosize": true, 223 | "useThrottle": true, 224 | "useThrottleFn": true, 225 | "useThrottledRefHistory": true, 226 | "useTimeAgo": true, 227 | "useTimeout": true, 228 | "useTimeoutFn": true, 229 | "useTimeoutPoll": true, 230 | "useTimestamp": true, 231 | "useTitle": true, 232 | "useToggle": true, 233 | "useTransition": true, 234 | "useUrlSearchParams": true, 235 | "useUserMedia": true, 236 | "useVModel": true, 237 | "useVModels": true, 238 | "useVibrate": true, 239 | "useVirtualList": true, 240 | "useWakeLock": true, 241 | "useWebNotification": true, 242 | "useWebSocket": true, 243 | "useWebWorker": true, 244 | "useWebWorkerFn": true, 245 | "useWindowFocus": true, 246 | "useWindowScroll": true, 247 | "useWindowSize": true, 248 | "watch": true, 249 | "watchArray": true, 250 | "watchAtMost": true, 251 | "watchDebounced": true, 252 | "watchEffect": true, 253 | "watchIgnorable": true, 254 | "watchOnce": true, 255 | "watchPausable": true, 256 | "watchPostEffect": true, 257 | "watchSyncEffect": true, 258 | "watchThrottled": true, 259 | "watchTriggerable": true, 260 | "watchWithFilter": true, 261 | "whenever": true, 262 | "Component": true, 263 | "ComponentPublicInstance": true, 264 | "ComputedRef": true, 265 | "ExtractDefaultPropTypes": true, 266 | "ExtractPropTypes": true, 267 | "ExtractPublicPropTypes": true, 268 | "InjectionKey": true, 269 | "PropType": true, 270 | "Ref": true, 271 | "VNode": true, 272 | "WritableComputedRef": true, 273 | "onBeforeRouteLeave": true, 274 | "onBeforeRouteUpdate": true, 275 | "toValue": true, 276 | "useLink": true 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/components/dashboard/ComparisonTable.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 221 | 222 | 228 | --------------------------------------------------------------------------------