buidl
This commit is contained in:
parent
23f15dac6b
commit
c7b49df155
|
|
@ -0,0 +1,32 @@
|
||||||
|
name: Gitea Actions Demo
|
||||||
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2 # Required to mount the Github Workspace to a volume
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: https://github.com/docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: gitea.webart-tech.ru
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: https://github.com/docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: gitea.webart-tech.ru/webart/strava-frontend/platformer:${{ gitea.sha }}
|
||||||
|
# - name: Deploy
|
||||||
|
# run: |
|
||||||
|
# git clone https://gitea.webart-tech.ru/webart/kuber-deploy.git deploy
|
||||||
|
# cd ./deploy
|
||||||
|
# sed -i -E 's/platformer:.+/platformer:${{ gitea.sha }}/g' strava-frontend.yml
|
||||||
|
# git config --global user.email "deploy@deploy.deploy"
|
||||||
|
# git commit -am 'auto-deploy'
|
||||||
|
# git push https://${{ secrets.DOCKER_USERNAME }}:${{ secrets.DOCKER_PASSWORD }}@gitea.webart-tech.ru/webart/kuber-deploy
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
### STAGE 1: Build ----------------------------------------------------------------------------------------------------
|
||||||
|
### STAGE 1: Build ----------------------------------------------------------------------------------------------------
|
||||||
|
FROM node:18
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ARG BUILD
|
||||||
|
ARG TARGETENV
|
||||||
|
ARG CURLOPT_SSL_VERIFYPEER=FALSE
|
||||||
|
|
||||||
|
LABEL version=$VERSION build=$BUILD mode=$TARGETENV
|
||||||
|
EXPOSE 80
|
||||||
|
RUN apt-get update && apt-get install -y curl ca-certificates
|
||||||
|
# RUN curl -fsSLk https://deb.nodesource.com/setup_16.x | bash -
|
||||||
|
RUN apt-get install -y nginx
|
||||||
|
RUN rm -rf /usr/share/nginx/html/
|
||||||
|
RUN mkdir -p /usr/share/nginx/html
|
||||||
|
# RUN npm install forever -g
|
||||||
|
RUN mkdir /data
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ARG BUILD
|
||||||
|
ARG TARGETENV
|
||||||
|
ARG CURLOPT_SSL_VERIFYPEER=FALSE
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# служебные штуки
|
||||||
|
RUN cp -R /data/dist/* /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
RUN mkdir /usr/share/nginx/html/.well-known
|
||||||
|
RUN chmod +x /data/run.sh
|
||||||
|
|
||||||
|
CMD ["/data/run.sh"]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import globals from "globals";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import pluginVue from "eslint-plugin-vue";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{ languageOptions: { globals: globals.browser } },
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...pluginVue.configs["flat/essential"],
|
||||||
|
];
|
||||||
10
index.html
10
index.html
|
|
@ -3,8 +3,14 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet" />
|
<link
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,300..600,0,0"
|
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,300..600,0,0"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
|
@ -5,7 +5,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "npm run lint && vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"build:ci": "vite build",
|
"build:ci": "vite build",
|
||||||
"start:ci": "serve -s ./dist",
|
"start:ci": "serve -s ./dist",
|
||||||
"prelint": "npm run format",
|
"prelint": "npm run format",
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
"vuestic-ui": "^1.9.0"
|
"vuestic-ui": "^1.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.0.0",
|
||||||
"@intlify/unplugin-vue-i18n": "^1.5.0",
|
"@intlify/unplugin-vue-i18n": "^1.5.0",
|
||||||
"@storybook/addon-essentials": "^7.4.6",
|
"@storybook/addon-essentials": "^7.4.6",
|
||||||
"@storybook/addon-interactions": "^7.4.6",
|
"@storybook/addon-interactions": "^7.4.6",
|
||||||
|
|
@ -58,10 +59,12 @@
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "^8.13.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"eslint-plugin-react": "^7.34.1",
|
||||||
"eslint-plugin-storybook": "^0.6.15",
|
"eslint-plugin-storybook": "^0.6.15",
|
||||||
"eslint-plugin-vue": "^9.18.1",
|
"eslint-plugin-vue": "^9.25.0",
|
||||||
|
"globals": "^15.0.0",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.1",
|
||||||
"lint-staged": "^15.1.0",
|
"lint-staged": "^15.1.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
|
|
@ -69,6 +72,7 @@
|
||||||
"storybook": "^7.4.6",
|
"storybook": "^7.4.6",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
|
"typescript-eslint": "^7.6.0",
|
||||||
"vite": "^4.4.6",
|
"vite": "^4.4.6",
|
||||||
"vue-eslint-parser": "^9.3.2",
|
"vue-eslint-parser": "^9.3.2",
|
||||||
"vue-tsc": "^1.8.22"
|
"vue-tsc": "^1.8.22"
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ module.exports = {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1 +1,19 @@
|
||||||
{"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"}
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
nginx
|
||||||
|
#node /usr/share/nginx/html/server/main.js
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'scss/main.scss';
|
@import "scss/main.scss";
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: 'Inter', Avenir, Helvetica, Arial, sans-serif;
|
font-family: "Inter", Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<svg fill="none" height="200" viewBox="0 0 200 200" width="200" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="200"
|
||||||
|
viewBox="0 0 200 200"
|
||||||
|
width="200"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
clip-rule="evenodd"
|
clip-rule="evenodd"
|
||||||
d="M141.03 63.87c2.58-3.14 5.5-6.4 7.4-8.46l-1.4-1.3a215.73 215.73 0 0 0-7.47 8.55 90.25 90.25 0 0 0-3.51 4.5 17.33 17.33 0 0 0-2.08 3.46l1.79.67c.23-.63.87-1.68 1.83-3.02a87.85 87.85 0 0 1 3.44-4.4ZM94.82 51.4c2-2.4 4.27-4.9 5.74-6.46l-1.39-1.3a166.85 166.85 0 0 0-5.81 6.53 69.06 69.06 0 0 0-2.74 3.46 13.4 13.4 0 0 0-1.65 2.7l1.78.68c.18-.45.65-1.23 1.4-2.26.73-1 1.66-2.16 2.67-3.35Z"
|
d="M141.03 63.87c2.58-3.14 5.5-6.4 7.4-8.46l-1.4-1.3a215.73 215.73 0 0 0-7.47 8.55 90.25 90.25 0 0 0-3.51 4.5 17.33 17.33 0 0 0-2.08 3.46l1.79.67c.23-.63.87-1.68 1.83-3.02a87.85 87.85 0 0 1 3.44-4.4ZM94.82 51.4c2-2.4 4.27-4.9 5.74-6.46l-1.39-1.3a166.85 166.85 0 0 0-5.81 6.53 69.06 69.06 0 0 0-2.74 3.46 13.4 13.4 0 0 0-1.65 2.7l1.78.68c.18-.45.65-1.23 1.4-2.26.73-1 1.66-2.16 2.67-3.35Z"
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
import VuesticLogo from './VuesticLogo.vue'
|
import VuesticLogo from "./VuesticLogo.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'VuesticLogo',
|
title: "VuesticLogo",
|
||||||
component: VuesticLogo,
|
component: VuesticLogo,
|
||||||
tags: ['autodocs'],
|
tags: ["autodocs"],
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Default = () => ({
|
export const Default = () => ({
|
||||||
components: { VuesticLogo },
|
components: { VuesticLogo },
|
||||||
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" />`,
|
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" />`,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const White = () => ({
|
export const White = () => ({
|
||||||
components: { VuesticLogo },
|
components: { VuesticLogo },
|
||||||
template: `<div class="bg-primary">
|
template: `<div class="bg-primary">
|
||||||
<VuesticLogo start="#FFF"/>
|
<VuesticLogo start="#FFF"/>
|
||||||
</div>`,
|
</div>`,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Blue = () => ({
|
export const Blue = () => ({
|
||||||
components: { VuesticLogo },
|
components: { VuesticLogo },
|
||||||
template: `<VuesticLogo start="#0E41C9"/>`,
|
template: `<VuesticLogo start="#0E41C9"/>`,
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Height = () => ({
|
export const Height = () => ({
|
||||||
components: { VuesticLogo },
|
components: { VuesticLogo },
|
||||||
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" :height="48"/>`,
|
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" :height="48"/>`,
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,107 @@
|
||||||
<template>
|
<template>
|
||||||
<svg width="231" height="26" xmlns="http://www.w3.org/2000/svg">
|
<svg width="231" height="26" xmlns="http://www.w3.org/2000/svg">
|
||||||
<!-- Created with SVG Editor - http://github.com/mzalive/SVG Editor/ -->
|
<!-- Created with SVG Editor - http://github.com/mzalive/SVG Editor/ -->
|
||||||
<defs>
|
<defs>
|
||||||
<filter height="200%" width="200%" y="-50%" x="-50%" id="svg_8_blur">
|
<filter height="200%" width="200%" y="-50%" x="-50%" id="svg_8_blur">
|
||||||
<feGaussianBlur stdDeviation="0.6" in="SourceGraphic"/>
|
<feGaussianBlur stdDeviation="0.6" in="SourceGraphic" />
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
<g>
|
<g>
|
||||||
<title>background</title>
|
<title>background</title>
|
||||||
<rect fill="none" id="canvas_background" height="28" width="233" y="-1" x="-1"/>
|
<rect
|
||||||
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
|
fill="none"
|
||||||
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
|
id="canvas_background"
|
||||||
</g>
|
height="28"
|
||||||
</g>
|
width="233"
|
||||||
<g>
|
y="-1"
|
||||||
<title>Layer 1</title>
|
x="-1"
|
||||||
<path d="m280.5,281.4375c0,0 -2.054138,0.567322 -5,0c-5.287994,-1.018372 -7,-2 -8,-3c-1,-1 -1.289795,-3.042908 -1,-4c1.04483,-3.450836 4.891724,-5.19577 12,-9c7.885925,-4.220428 13.207825,-6.930664 21,-9c2.899506,-0.770004 4,-1 3,-1c-5,0 -21,0 -40,0c-7,0 -17,-2 -17,0c0,4 10.003876,0.232941 17,0c15.024963,-0.500275 22.824158,1.062897 30,-3c1.230652,-0.696777 -3,-1 -9,-1c-13,0 -21,0 -24,1l-2,1" id="svg_2" stroke-width="1.5" stroke="#fff" fill="none"/>
|
/>
|
||||||
<path d="m192.5,194.4375c1,0 2.74707,1.61702 7,3c5.784607,1.881058 9.04863,2.814651 17,5c10.067017,2.766815 21,5 27,5c8,0 12,0 19,1l5,0l2,0" id="svg_3" stroke-width="1.5" stroke="#fff" fill="none"/>
|
<g
|
||||||
<path d="m251.5,198.4375c-1,0 -1.934143,0.144287 -4,1c-5.843124,2.420303 -8.925797,2.497559 -14,3c-5.970795,0.591232 -11,0 -13,0c-4,0 -7,0 -9,0l-1,0l-2,0l0,-1" id="svg_4" stroke-width="1.5" stroke="#fff" fill="none"/>
|
display="none"
|
||||||
<text font-style="normal" font-weight="normal" filter="url(#svg_8_blur)" opacity="0.78" xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="26" id="svg_8" y="21.4375" x="27" stroke="#000ecc" fill="#ffffff">CYCLE-RIDER</text>
|
overflow="visible"
|
||||||
</g>
|
y="0"
|
||||||
</svg>
|
x="0"
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
id="canvasGrid"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
fill="url(#gridpattern)"
|
||||||
|
stroke-width="0"
|
||||||
|
y="0"
|
||||||
|
x="0"
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<path
|
||||||
|
d="m280.5,281.4375c0,0 -2.054138,0.567322 -5,0c-5.287994,-1.018372 -7,-2 -8,-3c-1,-1 -1.289795,-3.042908 -1,-4c1.04483,-3.450836 4.891724,-5.19577 12,-9c7.885925,-4.220428 13.207825,-6.930664 21,-9c2.899506,-0.770004 4,-1 3,-1c-5,0 -21,0 -40,0c-7,0 -17,-2 -17,0c0,4 10.003876,0.232941 17,0c15.024963,-0.500275 22.824158,1.062897 30,-3c1.230652,-0.696777 -3,-1 -9,-1c-13,0 -21,0 -24,1l-2,1"
|
||||||
|
id="svg_2"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="#fff"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m192.5,194.4375c1,0 2.74707,1.61702 7,3c5.784607,1.881058 9.04863,2.814651 17,5c10.067017,2.766815 21,5 27,5c8,0 12,0 19,1l5,0l2,0"
|
||||||
|
id="svg_3"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="#fff"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m251.5,198.4375c-1,0 -1.934143,0.144287 -4,1c-5.843124,2.420303 -8.925797,2.497559 -14,3c-5.970795,0.591232 -11,0 -13,0c-4,0 -7,0 -9,0l-1,0l-2,0l0,-1"
|
||||||
|
id="svg_4"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="#fff"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
font-style="normal"
|
||||||
|
font-weight="normal"
|
||||||
|
filter="url(#svg_8_blur)"
|
||||||
|
opacity="0.78"
|
||||||
|
xml:space="preserve"
|
||||||
|
text-anchor="start"
|
||||||
|
font-family="Helvetica, Arial, sans-serif"
|
||||||
|
font-size="26"
|
||||||
|
id="svg_8"
|
||||||
|
y="21.4375"
|
||||||
|
x="27"
|
||||||
|
stroke="#000ecc"
|
||||||
|
fill="#ffffff"
|
||||||
|
>
|
||||||
|
CYCLE-RIDER
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import { useColors } from 'vuestic-ui'
|
import { useColors } from "vuestic-ui";
|
||||||
|
|
||||||
const { getColor } = useColors()
|
const { getColor } = useColors();
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
height?: number
|
height?: number;
|
||||||
start?: string
|
start?: string;
|
||||||
end?: string
|
end?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
height: 18,
|
height: 18,
|
||||||
start: 'primary',
|
start: "primary",
|
||||||
end: undefined,
|
end: undefined,
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
const colorsComputed = computed(() => {
|
const colorsComputed = computed(() => {
|
||||||
return {
|
return {
|
||||||
start: getColor(props.start),
|
start: getColor(props.start),
|
||||||
end: getColor(props.end || props.start),
|
end: getColor(props.end || props.start),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<nav class="flex items-center">
|
<nav class="flex items-center">
|
||||||
<VaBreadcrumbs>
|
<VaBreadcrumbs>
|
||||||
<VaBreadcrumbsItem label="Home" :to="{ name: 'dashboard' }" />
|
<VaBreadcrumbsItem label="Начало" :to="{ name: 'dashboard' }" />
|
||||||
<VaBreadcrumbsItem
|
<VaBreadcrumbsItem
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.label"
|
:key="item.label"
|
||||||
|
|
@ -22,71 +22,71 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from "vue-i18n";
|
||||||
import { useColors } from 'vuestic-ui'
|
import { useColors } from "vuestic-ui";
|
||||||
import VaIconMenuCollapsed from '../icons/VaIconMenuCollapsed.vue'
|
import VaIconMenuCollapsed from "../icons/VaIconMenuCollapsed.vue";
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from "pinia";
|
||||||
import { useGlobalStore } from '../../stores/global-store'
|
import { useGlobalStore } from "../../stores/global-store";
|
||||||
import NavigationRoutes from '../sidebar/NavigationRoutes'
|
import NavigationRoutes from "../sidebar/NavigationRoutes";
|
||||||
|
|
||||||
const { isSidebarMinimized } = storeToRefs(useGlobalStore())
|
const { isSidebarMinimized } = storeToRefs(useGlobalStore());
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const { t } = useI18n()
|
const { t } = useI18n();
|
||||||
|
|
||||||
type BreadcrumbNavigationItem = {
|
type BreadcrumbNavigationItem = {
|
||||||
label: string
|
label: string;
|
||||||
to: string
|
to: string;
|
||||||
hasChildren: boolean
|
hasChildren: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
const findRouteName = (name: string) => {
|
const findRouteName = (name: string) => {
|
||||||
const traverse = (routers: any[]): string => {
|
const traverse = (routers: any[]): string => {
|
||||||
for (const router of routers) {
|
for (const router of routers) {
|
||||||
if (router.name === name) {
|
if (router.name === name) {
|
||||||
return router.displayName
|
return router.displayName;
|
||||||
}
|
}
|
||||||
if (router.children) {
|
if (router.children) {
|
||||||
const result = traverse(router.children)
|
const result = traverse(router.children);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ''
|
return "";
|
||||||
}
|
};
|
||||||
|
|
||||||
return traverse(NavigationRoutes.routes)
|
return traverse(NavigationRoutes.routes);
|
||||||
}
|
};
|
||||||
|
|
||||||
const items = computed(() => {
|
const items = computed(() => {
|
||||||
const result: { label: string; to: string; hasChildren: boolean }[] = []
|
const result: { label: string; to: string; hasChildren: boolean }[] = [];
|
||||||
route.matched.forEach((route) => {
|
route.matched.forEach((route) => {
|
||||||
const labelKey = findRouteName(route.name as string)
|
const labelKey = findRouteName(route.name as string);
|
||||||
if (!labelKey) {
|
if (!labelKey) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
result.push({
|
result.push({
|
||||||
label: t(labelKey),
|
label: t(labelKey),
|
||||||
to: route.path,
|
to: route.path,
|
||||||
hasChildren: route.children && route.children.length > 0,
|
hasChildren: route.children && route.children.length > 0,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
return result
|
return result;
|
||||||
})
|
});
|
||||||
|
|
||||||
const { getColor } = useColors()
|
const { getColor } = useColors();
|
||||||
|
|
||||||
const collapseIconColor = computed(() => getColor('secondary'))
|
const collapseIconColor = computed(() => getColor("secondary"));
|
||||||
|
|
||||||
const handleBreadcrumbClick = (item: BreadcrumbNavigationItem) => {
|
const handleBreadcrumbClick = (item: BreadcrumbNavigationItem) => {
|
||||||
if (!item.hasChildren) {
|
if (!item.hasChildren) {
|
||||||
router.push(item.to)
|
router.push(item.to);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-clean-code" viewBox="0 0 56.02 50.34" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-clean-code"
|
||||||
|
viewBox="0 0 56.02 50.34"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs />
|
<defs />
|
||||||
<title>overview_icon_4</title>
|
<title>overview_icon_4</title>
|
||||||
<g id="Layer_2" data-name="Layer 2">
|
<g id="Layer_2" data-name="Layer 2">
|
||||||
<g id="Layer_1-2" data-name="Layer 1">
|
<g id="Layer_1-2" data-name="Layer 1">
|
||||||
<path class="cls-1" d="M38.23,16.17a10,10,0,1,0-17.67,6.42V47.5l7.33-5,8,5V22.58A10,10,0,0,0,38.23,16.17Z" />
|
<path
|
||||||
|
class="cls-1"
|
||||||
|
d="M38.23,16.17a10,10,0,1,0-17.67,6.42V47.5l7.33-5,8,5V22.58A10,10,0,0,0,38.23,16.17Z"
|
||||||
|
/>
|
||||||
<path
|
<path
|
||||||
class="cls-2"
|
class="cls-2"
|
||||||
d="M28.23,0a13.15,13.15,0,0,0-9.17,22.6V50.34l8.87-6,9.46,5.92V22.6A13.15,13.15,0,0,0,28.23,0ZM34.4,44.79l-6.54-4.08-5.8,4V24.79a13.11,13.11,0,0,0,12.33,0ZM28.23,23.33A10.17,10.17,0,1,1,38.4,13.17,10.18,10.18,0,0,1,28.23,23.33Z"
|
d="M28.23,0a13.15,13.15,0,0,0-9.17,22.6V50.34l8.87-6,9.46,5.92V22.6A13.15,13.15,0,0,0,28.23,0ZM34.4,44.79l-6.54-4.08-5.8,4V24.79a13.11,13.11,0,0,0,12.33,0ZM28.23,23.33A10.17,10.17,0,1,1,38.4,13.17,10.18,10.18,0,0,1,28.23,23.33Z"
|
||||||
|
|
@ -13,7 +20,10 @@
|
||||||
class="cls-2"
|
class="cls-2"
|
||||||
d="M28.23,5.67a7.5,7.5,0,1,0,7.5,7.5A7.51,7.51,0,0,0,28.23,5.67Zm0,12a4.5,4.5,0,1,1,4.5-4.5A4.5,4.5,0,0,1,28.23,17.67Z"
|
d="M28.23,5.67a7.5,7.5,0,1,0,7.5,7.5A7.51,7.51,0,0,0,28.23,5.67Zm0,12a4.5,4.5,0,1,1,4.5-4.5A4.5,4.5,0,0,1,28.23,17.67Z"
|
||||||
/>
|
/>
|
||||||
<polygon class="cls-2" points="9.51 15.11 0 24.61 9.51 34.12 11.63 32 4.24 24.61 11.63 17.23 9.51 15.11" />
|
<polygon
|
||||||
|
class="cls-2"
|
||||||
|
points="9.51 15.11 0 24.61 9.51 34.12 11.63 32 4.24 24.61 11.63 17.23 9.51 15.11"
|
||||||
|
/>
|
||||||
<polygon
|
<polygon
|
||||||
class="cls-2"
|
class="cls-2"
|
||||||
points="46.52 15.11 44.39 17.23 51.78 24.61 44.39 32 46.52 34.12 56.02 24.61 46.52 15.11"
|
points="46.52 15.11 44.39 17.23 51.78 24.61 44.39 32 46.52 34.12 56.02 24.61 46.52 15.11"
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
color?: string
|
color?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
color: 'inherit',
|
color: "inherit",
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-faster" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-faster"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>62EBC3B8-A55C-4B01-95A2-52FB8EDD4150</title>
|
<title>62EBC3B8-A55C-4B01-95A2-52FB8EDD4150</title>
|
||||||
<defs />
|
<defs />
|
||||||
<g id="symbols" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
|
<g
|
||||||
|
id="symbols"
|
||||||
|
fill="none"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="none"
|
||||||
|
stroke-width="1"
|
||||||
|
>
|
||||||
<g id="icon-faster" fill="#34495E">
|
<g id="icon-faster" fill="#34495E">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-free" viewBox="0 0 44.99 51.04" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-free"
|
||||||
|
viewBox="0 0 44.99 51.04"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs />
|
<defs />
|
||||||
<title>overview_icon_2</title>
|
<title>overview_icon_2</title>
|
||||||
<g id="Layer_2" data-name="Layer 2">
|
<g id="Layer_2" data-name="Layer 2">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-fresh" viewBox="0 0 50.98 47.66" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-fresh"
|
||||||
|
viewBox="0 0 50.98 47.66"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs />
|
<defs />
|
||||||
<title>overview_icon_5</title>
|
<title>overview_icon_5</title>
|
||||||
<g id="Layer_2" data-name="Layer 2">
|
<g id="Layer_2" data-name="Layer 2">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M18.3516 2.8125H3.97663C3.53557 2.8125 3.17802 3.17005 3.17802 3.61111C3.17802 4.05217 3.53557 4.40972 3.97663 4.40972H18.3516C18.7927 4.40972 19.1502 4.05217 19.1502 3.61111C19.1502 3.17005 18.7927 2.8125 18.3516 2.8125Z"
|
d="M18.3516 2.8125H3.97663C3.53557 2.8125 3.17802 3.17005 3.17802 3.61111C3.17802 4.05217 3.53557 4.40972 3.97663 4.40972H18.3516C18.7927 4.40972 19.1502 4.05217 19.1502 3.61111C19.1502 3.17005 18.7927 2.8125 18.3516 2.8125Z"
|
||||||
fill="#767C88"
|
fill="#767C88"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-menu" height="18" viewBox="0 0 24 18" width="23" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-menu"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 18"
|
||||||
|
width="23"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<g fill="none" fill-rule="nonzero" transform="translate(1 -3)">
|
<g fill="none" fill-rule="nonzero" transform="translate(1 -3)">
|
||||||
<path d="M0 0h24v24H0z" />
|
<path d="M0 0h24v24H0z" />
|
||||||
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
|
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
|
||||||
<path :fill="color" d="M11 11h10a1 1 0 0 1 0 2H11a1 1 0 0 1 0-2zM1 11h5a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z" />
|
<path
|
||||||
|
:fill="color"
|
||||||
|
d="M11 11h10a1 1 0 0 1 0 2H11a1 1 0 0 1 0-2zM1 11h5a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z"
|
||||||
|
/>
|
||||||
<rect :fill="color" height="2" rx="1" width="20" x="2" y="19" />
|
<rect :fill="color" height="2" rx="1" width="20" x="2" y="19" />
|
||||||
<path :stroke="color" d="M4 9l-3 3 3 3" stroke-width="2" />
|
<path :stroke="color" d="M4 9l-3 3 3 3" stroke-width="2" />
|
||||||
</g>
|
</g>
|
||||||
|
|
@ -13,12 +22,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
color?: string
|
color?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
color: 'inherit',
|
color: "inherit",
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-menu-collapsed" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-menu-collapsed"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<g fill="none" fill-rule="nonzero">
|
<g fill="none" fill-rule="nonzero">
|
||||||
<path d="M0 0h24v24H0z" />
|
<path d="M0 0h24v24H0z" />
|
||||||
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
|
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
|
||||||
|
|
@ -15,12 +21,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
color?: string
|
color?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
color: 'inherit',
|
color: "inherit",
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<svg :fill="color" height="16" viewBox="0 0 20 16" width="20" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
:fill="color"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 20 16"
|
||||||
|
width="20"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M20 2c0-1.1-.9-2-2-2H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V2zm-2 0l-8 5-8-5h16zm0 12H2V4l8 5 8-5v10z"
|
d="M20 2c0-1.1-.9-2-2-2H2C.9 0 0 .9 0 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V2zm-2 0l-8 5-8-5h16zm0 12H2V4l8 5 8-5v10z"
|
||||||
fill-rule="nonzero"
|
fill-rule="nonzero"
|
||||||
|
|
@ -10,12 +16,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
color?: string
|
color?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
color: 'inherit',
|
color: "inherit",
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
color?: string
|
color?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
color: 'inherit',
|
color: "inherit",
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-responsive" viewBox="0 0 47.5 49" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-responsive"
|
||||||
|
viewBox="0 0 47.5 49"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs />
|
<defs />
|
||||||
<title>overview_icon_3</title>
|
<title>overview_icon_3</title>
|
||||||
<g id="Layer_2" data-name="Layer 2">
|
<g id="Layer_2" data-name="Layer 2">
|
||||||
<g id="Layer_1-2" data-name="Layer 1">
|
<g id="Layer_1-2" data-name="Layer 1">
|
||||||
<polygon class="cls-1" points="37 26 37 7 11 7 11 18 3 18 3 46 11 46 15 46 30 46 37 46 45 46 45 26 37 26" />
|
<polygon
|
||||||
<path class="cls-2" d="M40,19V0H8V11H0V49H47.5V19ZM3,46V14H8V46Zm34,0H11V3H37Zm7.5,0H40V22h4.5Z" />
|
class="cls-1"
|
||||||
|
points="37 26 37 7 11 7 11 18 3 18 3 46 11 46 15 46 30 46 37 46 45 46 45 26 37 26"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="cls-2"
|
||||||
|
d="M40,19V0H8V11H0V49H47.5V19ZM3,46V14H8V46Zm34,0H11V3H37Zm7.5,0H40V22h4.5Z"
|
||||||
|
/>
|
||||||
<circle class="cls-2" cx="24" cy="41" r="2.67" />
|
<circle class="cls-2" cx="24" cy="41" r="2.67" />
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-rich" viewBox="0 0 56.99 55" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-rich"
|
||||||
|
viewBox="0 0 56.99 55"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs />
|
<defs />
|
||||||
<title>overview_icon_6</title>
|
<title>overview_icon_6</title>
|
||||||
<g id="Layer_2" data-name="Layer 2">
|
<g id="Layer_2" data-name="Layer 2">
|
||||||
|
|
@ -9,7 +13,10 @@
|
||||||
class="cls-2"
|
class="cls-2"
|
||||||
d="M57,41.18l-7.85-16V24H8.81v1.11L0,41.11l2.63,1.45L8.81,31.33V55H49.15V32L54.3,42.5ZM46.15,52H11.81V27H46.15Z"
|
d="M57,41.18l-7.85-16V24H8.81v1.11L0,41.11l2.63,1.45L8.81,31.33V55H49.15V32L54.3,42.5ZM46.15,52H11.81V27H46.15Z"
|
||||||
/>
|
/>
|
||||||
<polygon class="cls-2" points="35.3 1.8 32.9 0 28.12 6.39 26.16 4.63 24.16 6.87 28.56 10.8 35.3 1.8" />
|
<polygon
|
||||||
|
class="cls-2"
|
||||||
|
points="35.3 1.8 32.9 0 28.12 6.39 26.16 4.63 24.16 6.87 28.56 10.8 35.3 1.8"
|
||||||
|
/>
|
||||||
<polygon
|
<polygon
|
||||||
class="cls-2"
|
class="cls-2"
|
||||||
points="22.3 12.46 19.9 10.67 15.12 17.05 13.16 15.3 11.16 17.54 15.56 21.47 22.3 12.46"
|
points="22.3 12.46 19.9 10.67 15.12 17.05 13.16 15.3 11.16 17.54 15.56 21.47 22.3 12.46"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-slower" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-slower"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
<!-- Generator: sketchtool 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>67046716-A590-445C-AC65-1EEF69089C00</title>
|
<title>67046716-A590-445C-AC65-1EEF69089C00</title>
|
||||||
<defs />
|
<defs />
|
||||||
<g id="symbols" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
|
<g
|
||||||
|
id="symbols"
|
||||||
|
fill="none"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="none"
|
||||||
|
stroke-width="1"
|
||||||
|
>
|
||||||
<g id="icon-slower" fill="#34495E">
|
<g id="icon-slower" fill="#34495E">
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-vue" viewBox="0 0 55.05 47.8" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-vue"
|
||||||
|
viewBox="0 0 55.05 47.8"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs />
|
<defs />
|
||||||
<title>overview_icon_1</title>
|
<title>overview_icon_1</title>
|
||||||
<g id="Layer_2" data-name="Layer 2">
|
<g id="Layer_2" data-name="Layer 2">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<svg class="va-icon-vuestic" height="31" viewBox="0 0 304 31" width="304" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
|
class="va-icon-vuestic"
|
||||||
|
height="31"
|
||||||
|
viewBox="0 0 304 31"
|
||||||
|
width="304"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient :id="'ORIGINAL'" x1="0%" y1="50%" y2="50%">
|
<linearGradient :id="'ORIGINAL'" x1="0%" y1="50%" y2="50%">
|
||||||
<stop offset="0%" stop-color="#4AE387" />
|
<stop offset="0%" stop-color="#4AE387" />
|
||||||
|
|
@ -25,17 +31,17 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'VaIconVuestic',
|
name: "VaIconVuestic",
|
||||||
inject: ['contextConfig'],
|
inject: ["contextConfig"],
|
||||||
computed: {
|
computed: {
|
||||||
themeGradientId() {
|
themeGradientId() {
|
||||||
return this.contextConfig.invertedColor ? 'CORPORATE' : 'ORIGINAL'
|
return this.contextConfig.invertedColor ? "CORPORATE" : "ORIGINAL";
|
||||||
},
|
},
|
||||||
textColor() {
|
textColor() {
|
||||||
return this.contextConfig.invertedColor ? '#6E85E8' : '#E4FF32'
|
return this.contextConfig.invertedColor ? "#6E85E8" : "#E4FF32";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from "pinia";
|
||||||
import { useGlobalStore } from '../../stores/global-store'
|
import { useGlobalStore } from "../../stores/global-store";
|
||||||
import AppNavbarActions from './components/AppNavbarActions.vue'
|
import AppNavbarActions from "./components/AppNavbarActions.vue";
|
||||||
import VuesticLogo from '../VuesticLogo.vue'
|
import VuesticLogo from "../VuesticLogo.vue";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
isMobile: { type: Boolean, default: false },
|
isMobile: { type: Boolean, default: false },
|
||||||
})
|
});
|
||||||
|
|
||||||
const GlobalStore = useGlobalStore()
|
const GlobalStore = useGlobalStore();
|
||||||
|
|
||||||
const { isSidebarMinimized } = storeToRefs(GlobalStore)
|
const { isSidebarMinimized } = storeToRefs(GlobalStore);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -22,21 +22,23 @@
|
||||||
{{ t('helpAndSupport') }}
|
{{ t('helpAndSupport') }}
|
||||||
</VaButton> -->
|
</VaButton> -->
|
||||||
<!-- <NotificationDropdown class="app-navbar-actions__item" /> -->
|
<!-- <NotificationDropdown class="app-navbar-actions__item" /> -->
|
||||||
<ProfileDropdown class="app-navbar-actions__item app-navbar-actions__item--profile mr-1" />
|
<ProfileDropdown
|
||||||
|
class="app-navbar-actions__item app-navbar-actions__item--profile mr-1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ProfileDropdown from './dropdowns/ProfileDropdown.vue'
|
import ProfileDropdown from "./dropdowns/ProfileDropdown.vue";
|
||||||
import NotificationDropdown from './dropdowns/NotificationDropdown.vue'
|
import NotificationDropdown from "./dropdowns/NotificationDropdown.vue";
|
||||||
import GithubButton from './GitHubButton.vue'
|
import GithubButton from "./GitHubButton.vue";
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
isMobile: { type: Boolean, default: false },
|
isMobile: { type: Boolean, default: false },
|
||||||
})
|
});
|
||||||
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from "vue-i18n";
|
||||||
const { t } = useI18n()
|
const { t } = useI18n();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import VaIconGitHub from '../../icons/VaIconGitHub.vue'
|
import VaIconGitHub from "../../icons/VaIconGitHub.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<VaDropdown :offset="[13, 0]" class="notification-dropdown" stick-to-edges :close-on-content-click="false">
|
<VaDropdown
|
||||||
|
:offset="[13, 0]"
|
||||||
|
class="notification-dropdown"
|
||||||
|
stick-to-edges
|
||||||
|
:close-on-content-click="false"
|
||||||
|
>
|
||||||
<template #anchor>
|
<template #anchor>
|
||||||
<VaButton preset="secondary" color="textPrimary">
|
<VaButton preset="secondary" color="textPrimary">
|
||||||
<VaBadge overlap>
|
<VaBadge overlap>
|
||||||
|
|
@ -11,7 +16,10 @@
|
||||||
<VaDropdownContent class="h-full sm:max-w-[420px] sm:h-auto">
|
<VaDropdownContent class="h-full sm:max-w-[420px] sm:h-auto">
|
||||||
<section class="sm:max-h-[320px] p-4 overflow-auto">
|
<section class="sm:max-h-[320px] p-4 overflow-auto">
|
||||||
<VaList class="space-y-1 mb-2">
|
<VaList class="space-y-1 mb-2">
|
||||||
<template v-for="(item, index) in notificationsWithRelativeTime" :key="item.id">
|
<template
|
||||||
|
v-for="(item, index) in notificationsWithRelativeTime"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
<VaListItem class="text-base">
|
<VaListItem class="text-base">
|
||||||
<VaListItemSection icon class="mx-0 p-0">
|
<VaListItemSection icon class="mx-0 p-0">
|
||||||
<VaIcon :name="item.icon" color="secondary" />
|
<VaIcon :name="item.icon" color="secondary" />
|
||||||
|
|
@ -23,12 +31,25 @@
|
||||||
{{ item.updateTimestamp }}
|
{{ item.updateTimestamp }}
|
||||||
</VaListItemSection>
|
</VaListItemSection>
|
||||||
</VaListItem>
|
</VaListItem>
|
||||||
<VaListSeparator v-if="item.separator && index !== notificationsWithRelativeTime.length - 1" class="mx-3" />
|
<VaListSeparator
|
||||||
|
v-if="
|
||||||
|
item.separator &&
|
||||||
|
index !== notificationsWithRelativeTime.length - 1
|
||||||
|
"
|
||||||
|
class="mx-3"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VaList>
|
</VaList>
|
||||||
|
|
||||||
<VaButton preset="primary" class="w-full" @click="displayAllNotifications = !displayAllNotifications"
|
<VaButton
|
||||||
>{{ displayAllNotifications ? t('notifications.less') : t('notifications.all') }}
|
preset="primary"
|
||||||
|
class="w-full"
|
||||||
|
@click="displayAllNotifications = !displayAllNotifications"
|
||||||
|
>{{
|
||||||
|
displayAllNotifications
|
||||||
|
? t("notifications.less")
|
||||||
|
: t("notifications.all")
|
||||||
|
}}
|
||||||
</VaButton>
|
</VaButton>
|
||||||
</section>
|
</section>
|
||||||
</VaDropdownContent>
|
</VaDropdownContent>
|
||||||
|
|
@ -36,87 +57,92 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from "vue";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from "vue-i18n";
|
||||||
import VaIconNotification from '../../../icons/VaIconNotification.vue'
|
import VaIconNotification from "../../../icons/VaIconNotification.vue";
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
const baseNumberOfVisibleNotifications = 4
|
const baseNumberOfVisibleNotifications = 4;
|
||||||
const rtf = new Intl.RelativeTimeFormat(locale.value, { style: 'short' })
|
const rtf = new Intl.RelativeTimeFormat(locale.value, { style: "short" });
|
||||||
const displayAllNotifications = ref(false)
|
const displayAllNotifications = ref(false);
|
||||||
|
|
||||||
interface INotification {
|
interface INotification {
|
||||||
message: string
|
message: string;
|
||||||
icon: string
|
icon: string;
|
||||||
id: number
|
id: number;
|
||||||
separator?: boolean
|
separator?: boolean;
|
||||||
updateTimestamp: Date
|
updateTimestamp: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeDateFromNow = (timeFromNow: number) => {
|
const makeDateFromNow = (timeFromNow: number) => {
|
||||||
const date = new Date()
|
const date = new Date();
|
||||||
date.setTime(date.getTime() + timeFromNow)
|
date.setTime(date.getTime() + timeFromNow);
|
||||||
return date
|
return date;
|
||||||
}
|
};
|
||||||
|
|
||||||
const notifications: INotification[] = [
|
const notifications: INotification[] = [
|
||||||
{
|
{
|
||||||
message: '4 pending requests',
|
message: "4 pending requests",
|
||||||
icon: 'favorite_outline',
|
icon: "favorite_outline",
|
||||||
id: 1,
|
id: 1,
|
||||||
separator: true,
|
separator: true,
|
||||||
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: '3 new reports',
|
message: "3 new reports",
|
||||||
icon: 'calendar_today',
|
icon: "calendar_today",
|
||||||
id: 2,
|
id: 2,
|
||||||
separator: true,
|
separator: true,
|
||||||
updateTimestamp: makeDateFromNow(-12 * 60 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-12 * 60 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'Whoops! Your trial period has expired.',
|
message: "Whoops! Your trial period has expired.",
|
||||||
icon: 'error_outline',
|
icon: "error_outline",
|
||||||
id: 3,
|
id: 3,
|
||||||
separator: true,
|
separator: true,
|
||||||
updateTimestamp: makeDateFromNow(-2 * 24 * 60 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-2 * 24 * 60 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'It looks like your timezone is set incorrectly, please change it to avoid issues with Memory.',
|
message:
|
||||||
icon: 'schedule',
|
"It looks like your timezone is set incorrectly, please change it to avoid issues with Memory.",
|
||||||
|
icon: "schedule",
|
||||||
id: 4,
|
id: 4,
|
||||||
updateTimestamp: makeDateFromNow(-2 * 7 * 24 * 60 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-2 * 7 * 24 * 60 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: '2 new team members added',
|
message: "2 new team members added",
|
||||||
icon: 'group_add',
|
icon: "group_add",
|
||||||
id: 5,
|
id: 5,
|
||||||
separator: false,
|
separator: false,
|
||||||
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'Monthly budget exceeded by 10%',
|
message: "Monthly budget exceeded by 10%",
|
||||||
icon: 'trending_up',
|
icon: "trending_up",
|
||||||
id: 6,
|
id: 6,
|
||||||
separator: true,
|
separator: true,
|
||||||
updateTimestamp: makeDateFromNow(-3 * 24 * 60 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-3 * 24 * 60 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: '7 tasks are approaching their deadlines',
|
message: "7 tasks are approaching their deadlines",
|
||||||
icon: 'alarm',
|
icon: "alarm",
|
||||||
id: 7,
|
id: 7,
|
||||||
separator: false,
|
separator: false,
|
||||||
updateTimestamp: makeDateFromNow(-5 * 60 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-5 * 60 * 60 * 1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'New software update available',
|
message: "New software update available",
|
||||||
icon: 'system_update',
|
icon: "system_update",
|
||||||
id: 8,
|
id: 8,
|
||||||
separator: true,
|
separator: true,
|
||||||
updateTimestamp: makeDateFromNow(-1 * 24 * 60 * 60 * 1000),
|
updateTimestamp: makeDateFromNow(-1 * 24 * 60 * 60 * 1000),
|
||||||
},
|
},
|
||||||
].sort((a, b) => new Date(b.updateTimestamp).getTime() - new Date(a.updateTimestamp).getTime())
|
].sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.updateTimestamp).getTime() -
|
||||||
|
new Date(a.updateTimestamp).getTime(),
|
||||||
|
);
|
||||||
|
|
||||||
const TIME_NAMES = {
|
const TIME_NAMES = {
|
||||||
second: 1000,
|
second: 1000,
|
||||||
|
|
@ -126,41 +152,51 @@ const TIME_NAMES = {
|
||||||
week: 1000 * 60 * 60 * 24 * 7,
|
week: 1000 * 60 * 60 * 24 * 7,
|
||||||
month: 1000 * 60 * 60 * 24 * 30,
|
month: 1000 * 60 * 60 * 24 * 30,
|
||||||
year: 1000 * 60 * 60 * 24 * 365,
|
year: 1000 * 60 * 60 * 24 * 365,
|
||||||
}
|
};
|
||||||
|
|
||||||
const getTimeName = (differenceTime: number) => {
|
const getTimeName = (differenceTime: number) => {
|
||||||
return Object.keys(TIME_NAMES).reduce(
|
return Object.keys(TIME_NAMES).reduce(
|
||||||
(acc, key) => (TIME_NAMES[key as keyof typeof TIME_NAMES] < differenceTime ? key : acc),
|
(acc, key) =>
|
||||||
'month',
|
TIME_NAMES[key as keyof typeof TIME_NAMES] < differenceTime ? key : acc,
|
||||||
) as keyof typeof TIME_NAMES
|
"month",
|
||||||
}
|
) as keyof typeof TIME_NAMES;
|
||||||
|
};
|
||||||
|
|
||||||
const notificationsWithRelativeTime = computed(() => {
|
const notificationsWithRelativeTime = computed(() => {
|
||||||
const list = displayAllNotifications.value ? notifications : notifications.slice(0, baseNumberOfVisibleNotifications)
|
const list = displayAllNotifications.value
|
||||||
|
? notifications
|
||||||
|
: notifications.slice(0, baseNumberOfVisibleNotifications);
|
||||||
|
|
||||||
return list.map((item, index) => {
|
return list.map((item, index) => {
|
||||||
const timeDifference = Math.round(new Date().getTime() - new Date(item.updateTimestamp).getTime())
|
const timeDifference = Math.round(
|
||||||
const timeName = getTimeName(timeDifference)
|
new Date().getTime() - new Date(item.updateTimestamp).getTime(),
|
||||||
|
);
|
||||||
|
const timeName = getTimeName(timeDifference);
|
||||||
|
|
||||||
let separator = false
|
let separator = false;
|
||||||
|
|
||||||
const nextItem = list[index + 1]
|
const nextItem = list[index + 1];
|
||||||
if (nextItem) {
|
if (nextItem) {
|
||||||
const nextItemDifference = Math.round(new Date().getTime() - new Date(nextItem.updateTimestamp).getTime())
|
const nextItemDifference = Math.round(
|
||||||
const nextItemTimeName = getTimeName(nextItemDifference)
|
new Date().getTime() - new Date(nextItem.updateTimestamp).getTime(),
|
||||||
|
);
|
||||||
|
const nextItemTimeName = getTimeName(nextItemDifference);
|
||||||
|
|
||||||
if (timeName !== nextItemTimeName) {
|
if (timeName !== nextItemTimeName) {
|
||||||
separator = true
|
separator = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
updateTimestamp: rtf.format(-1 * Math.round(timeDifference / TIME_NAMES[timeName]), timeName),
|
updateTimestamp: rtf.format(
|
||||||
|
-1 * Math.round(timeDifference / TIME_NAMES[timeName]),
|
||||||
|
timeName,
|
||||||
|
),
|
||||||
separator,
|
separator,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="profile-dropdown-wrapper">
|
<div class="profile-dropdown-wrapper">
|
||||||
<VaDropdown v-model="isShown" :offset="[9, 0]" class="profile-dropdown" stick-to-edges>
|
<VaDropdown
|
||||||
|
v-model="isShown"
|
||||||
|
:offset="[9, 0]"
|
||||||
|
class="profile-dropdown"
|
||||||
|
stick-to-edges
|
||||||
|
>
|
||||||
<template #anchor>
|
<template #anchor>
|
||||||
<VaButton preset="secondary" color="textPrimary">
|
<VaButton preset="secondary" color="textPrimary">
|
||||||
<span class="profile-dropdown__anchor min-w-max">
|
<span class="profile-dropdown__anchor min-w-max">
|
||||||
|
|
@ -14,7 +19,10 @@
|
||||||
:style="{ '--hover-color': hoverColor }"
|
:style="{ '--hover-color': hoverColor }"
|
||||||
>
|
>
|
||||||
<VaList v-for="group in options" :key="group.name">
|
<VaList v-for="group in options" :key="group.name">
|
||||||
<header v-if="group.name" class="uppercase text-[var(--va-secondary)] opacity-80 font-bold text-xs px-4">
|
<header
|
||||||
|
v-if="group.name"
|
||||||
|
class="uppercase text-[var(--va-secondary)] opacity-80 font-bold text-xs px-4"
|
||||||
|
>
|
||||||
{{ t(`user.${group.name}`) }}
|
{{ t(`user.${group.name}`) }}
|
||||||
</header>
|
</header>
|
||||||
<VaListItem
|
<VaListItem
|
||||||
|
|
@ -34,86 +42,90 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from "vue";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from "vue-i18n";
|
||||||
import { useColors } from 'vuestic-ui'
|
import { useColors } from "vuestic-ui";
|
||||||
|
|
||||||
const { colors, setHSLAColor } = useColors()
|
const { colors, setHSLAColor } = useColors();
|
||||||
const hoverColor = computed(() => setHSLAColor(colors.focus, { a: 0.1 }))
|
const hoverColor = computed(() => setHSLAColor(colors.focus, { a: 0.1 }));
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n();
|
||||||
|
|
||||||
type ProfileListItem = {
|
type ProfileListItem = {
|
||||||
name: string
|
name: string;
|
||||||
to?: string
|
to?: string;
|
||||||
href?: string
|
href?: string;
|
||||||
icon: string
|
icon: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type ProfileOptions = {
|
type ProfileOptions = {
|
||||||
name: string
|
name: string;
|
||||||
separator: boolean
|
separator: boolean;
|
||||||
list: ProfileListItem[]
|
list: ProfileListItem[];
|
||||||
}
|
};
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
options?: ProfileOptions[]
|
options?: ProfileOptions[];
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
options: () => [
|
options: () => [
|
||||||
{
|
{
|
||||||
name: 'account',
|
name: "account",
|
||||||
separator: true,
|
separator: true,
|
||||||
list: [
|
list: [
|
||||||
{
|
{
|
||||||
name: 'profile',
|
name: "profile",
|
||||||
to: 'preferences',
|
to: "preferences",
|
||||||
icon: 'mso-account_circle',
|
icon: "mso-account_circle",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'settings',
|
name: "settings",
|
||||||
to: 'settings',
|
to: "settings",
|
||||||
icon: 'mso-settings',
|
icon: "mso-settings",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'explore',
|
name: "explore",
|
||||||
separator: true,
|
separator: true,
|
||||||
list: [
|
list: [
|
||||||
{
|
{
|
||||||
name: 'faq',
|
name: "faq",
|
||||||
to: 'faq',
|
to: "faq",
|
||||||
icon: 'mso-quiz',
|
icon: "mso-quiz",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'helpAndSupport',
|
name: "helpAndSupport",
|
||||||
href: 'https://discord.gg/u7fQdqQt8c',
|
href: "https://discord.gg/u7fQdqQt8c",
|
||||||
icon: 'mso-error',
|
icon: "mso-error",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '',
|
name: "",
|
||||||
separator: false,
|
separator: false,
|
||||||
list: [
|
list: [
|
||||||
{
|
{
|
||||||
name: 'logout',
|
name: "logout",
|
||||||
to: 'login',
|
to: "login",
|
||||||
icon: 'mso-logout',
|
icon: "mso-logout",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
const isShown = ref(false)
|
const isShown = ref(false);
|
||||||
|
|
||||||
const resolveLinkAttribute = (item: ProfileListItem) => {
|
const resolveLinkAttribute = (item: ProfileListItem) => {
|
||||||
return item.to ? { to: { name: item.to } } : item.href ? { href: item.href, target: '_blank' } : {}
|
return item.to
|
||||||
}
|
? { to: { name: item.to } }
|
||||||
|
: item.href
|
||||||
|
? { href: item.href, target: "_blank" }
|
||||||
|
: {};
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<VaSidebar v-model="writableVisible" :width="sidebarWidth" :color="color" minimized-width="0">
|
<VaSidebar
|
||||||
|
v-model="writableVisible"
|
||||||
|
:width="sidebarWidth"
|
||||||
|
:color="color"
|
||||||
|
minimized-width="0"
|
||||||
|
>
|
||||||
<VaAccordion v-model="value" multiple>
|
<VaAccordion v-model="value" multiple>
|
||||||
<VaCollapse v-for="(route, index) in navigationRoutes.routes" :key="index">
|
<VaCollapse
|
||||||
|
v-for="(route, index) in navigationRoutes.routes"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
<template #header="{ value: isCollapsed }">
|
<template #header="{ value: isCollapsed }">
|
||||||
<VaSidebarItem
|
<VaSidebarItem
|
||||||
:to="route.children ? undefined : { name: route.name }"
|
:to="route.children ? undefined : { name: route.name }"
|
||||||
:active="routeHasActiveChild(route)"
|
:active="routeHasActiveChild(route)"
|
||||||
:active-color="activeColor"
|
:active-color="activeColor"
|
||||||
:text-color="textColor(route)"
|
:text-color="textColor(route)"
|
||||||
:aria-label="`${route.children ? 'Open category ' : 'Visit'} ${t(route.displayName)}`"
|
:aria-label="`${route.children ? 'Open category ' : 'Visit'} ${t(
|
||||||
|
route.displayName,
|
||||||
|
)}`"
|
||||||
role="button"
|
role="button"
|
||||||
hover-opacity="0.10"
|
hover-opacity="0.10"
|
||||||
>
|
>
|
||||||
|
|
@ -20,9 +30,15 @@
|
||||||
size="20px"
|
size="20px"
|
||||||
:color="iconColor(route)"
|
:color="iconColor(route)"
|
||||||
/>
|
/>
|
||||||
<VaSidebarItemTitle class="flex justify-between items-center leading-5 font-semibold">
|
<VaSidebarItemTitle
|
||||||
|
class="flex justify-between items-center leading-5 font-semibold"
|
||||||
|
>
|
||||||
{{ t(route.displayName) }}
|
{{ t(route.displayName) }}
|
||||||
<VaIcon v-if="route.children" :name="arrowDirection(isCollapsed)" size="20px" />
|
<VaIcon
|
||||||
|
v-if="route.children"
|
||||||
|
:name="arrowDirection(isCollapsed)"
|
||||||
|
size="20px"
|
||||||
|
/>
|
||||||
</VaSidebarItemTitle>
|
</VaSidebarItemTitle>
|
||||||
</VaSidebarItemContent>
|
</VaSidebarItemContent>
|
||||||
</VaSidebarItem>
|
</VaSidebarItem>
|
||||||
|
|
@ -50,56 +66,64 @@
|
||||||
</VaSidebar>
|
</VaSidebar>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, watch, ref, computed } from 'vue'
|
import { defineComponent, watch, ref, computed } from "vue";
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from "vue-i18n";
|
||||||
import { useColors } from 'vuestic-ui'
|
import { useColors } from "vuestic-ui";
|
||||||
|
|
||||||
import navigationRoutes, { type INavigationRoute } from './NavigationRoutes'
|
import navigationRoutes, { type INavigationRoute } from "./NavigationRoutes";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Sidebar',
|
name: "Sidebar",
|
||||||
props: {
|
props: {
|
||||||
visible: { type: Boolean, default: true },
|
visible: { type: Boolean, default: true },
|
||||||
mobile: { type: Boolean, default: false },
|
mobile: { type: Boolean, default: false },
|
||||||
},
|
},
|
||||||
emits: ['update:visible'],
|
emits: ["update:visible"],
|
||||||
|
|
||||||
setup: (props, { emit }) => {
|
setup: (props, { emit }) => {
|
||||||
const { getColor, colorToRgba } = useColors()
|
const { getColor, colorToRgba } = useColors();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const { t } = useI18n()
|
const { t } = useI18n();
|
||||||
|
|
||||||
const value = ref<boolean[]>([])
|
const value = ref<boolean[]>([]);
|
||||||
|
|
||||||
const writableVisible = computed({
|
const writableVisible = computed({
|
||||||
get: () => props.visible,
|
get: () => props.visible,
|
||||||
set: (v: boolean) => emit('update:visible', v),
|
set: (v: boolean) => emit("update:visible", v),
|
||||||
})
|
});
|
||||||
|
|
||||||
const isActiveChildRoute = (child: INavigationRoute) => route.name === child.name
|
const isActiveChildRoute = (child: INavigationRoute) =>
|
||||||
|
route.name === child.name;
|
||||||
|
|
||||||
const routeHasActiveChild = (section: INavigationRoute) => {
|
const routeHasActiveChild = (section: INavigationRoute) => {
|
||||||
if (!section.children) {
|
if (!section.children) {
|
||||||
return route.path.endsWith(`${section.name}`)
|
return route.path.endsWith(`${section.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return section.children.some(({ name }) => route.path.endsWith(`${name}`))
|
return section.children.some(({ name }) =>
|
||||||
}
|
route.path.endsWith(`${name}`),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const setActiveExpand = () =>
|
const setActiveExpand = () =>
|
||||||
(value.value = navigationRoutes.routes.map((route: INavigationRoute) => routeHasActiveChild(route)))
|
(value.value = navigationRoutes.routes.map((route: INavigationRoute) =>
|
||||||
|
routeHasActiveChild(route),
|
||||||
|
));
|
||||||
|
|
||||||
const sidebarWidth = computed(() => (props.mobile ? '100vw' : '280px'))
|
const sidebarWidth = computed(() => (props.mobile ? "100vw" : "280px"));
|
||||||
const color = computed(() => getColor('background-secondary'))
|
const color = computed(() => getColor("background-secondary"));
|
||||||
const activeColor = computed(() => colorToRgba(getColor('focus'), 0.1))
|
const activeColor = computed(() => colorToRgba(getColor("focus"), 0.1));
|
||||||
|
|
||||||
const iconColor = (route: INavigationRoute) => (routeHasActiveChild(route) ? 'primary' : 'secondary')
|
const iconColor = (route: INavigationRoute) =>
|
||||||
const textColor = (route: INavigationRoute) => (routeHasActiveChild(route) ? 'primary' : 'textPrimary')
|
routeHasActiveChild(route) ? "primary" : "secondary";
|
||||||
const arrowDirection = (state: boolean) => (state ? 'va-arrow-up' : 'va-arrow-down')
|
const textColor = (route: INavigationRoute) =>
|
||||||
|
routeHasActiveChild(route) ? "primary" : "textPrimary";
|
||||||
|
const arrowDirection = (state: boolean) =>
|
||||||
|
state ? "va-arrow-up" : "va-arrow-down";
|
||||||
|
|
||||||
watch(() => route.fullPath, setActiveExpand, { immediate: true })
|
watch(() => route.fullPath, setActiveExpand, { immediate: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
writableVisible,
|
writableVisible,
|
||||||
|
|
@ -114,7 +138,7 @@ export default defineComponent({
|
||||||
iconColor,
|
iconColor,
|
||||||
textColor,
|
textColor,
|
||||||
arrowDirection,
|
arrowDirection,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
export interface INavigationRoute {
|
export interface INavigationRoute {
|
||||||
name: string
|
name: string;
|
||||||
displayName: string
|
displayName: string;
|
||||||
meta: { icon: string }
|
meta: { icon: string };
|
||||||
children?: INavigationRoute[]
|
children?: INavigationRoute[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
root: {
|
root: {
|
||||||
name: '/',
|
name: "/",
|
||||||
displayName: 'navigationRoutes.home',
|
displayName: "navigationRoutes.home",
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
name: 'dashboard',
|
name: "dashboard",
|
||||||
displayName: 'menu.dashboard',
|
displayName: "menu.dashboard",
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'vuestic-iconset-dashboard',
|
icon: "vuestic-iconset-dashboard",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
|
@ -104,4 +104,4 @@ export default {
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
] as INavigationRoute[],
|
] as INavigationRoute[],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import Typography from './Typography.vue'
|
import Typography from "./Typography.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Typography',
|
title: "Typography",
|
||||||
component: Typography,
|
component: Typography,
|
||||||
tags: ['autodocs'],
|
tags: ["autodocs"],
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Default = () => ({
|
export const Default = () => ({
|
||||||
components: { Typography },
|
components: { Typography },
|
||||||
template: `
|
template: `
|
||||||
<Typography/>
|
<Typography/>
|
||||||
`,
|
`,
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,48 +7,57 @@
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1>Display 1 Heading</h1>
|
<h1>Display 1 Heading</h1>
|
||||||
<p>
|
<p>
|
||||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
Of all of the celestial bodies that capture our attention and
|
||||||
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about it.
|
fascination as astronomers, none has a greater influence on life
|
||||||
|
on planet Earth than it’s own satellite, the moon. When you think
|
||||||
|
about it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h2>Display 2 Heading</h2>
|
<h2>Display 2 Heading</h2>
|
||||||
<p>
|
<p>
|
||||||
None has a greater influence on life on planet Earth than it’s own satellite, the moon. When you think
|
None has a greater influence on life on planet Earth than it’s own
|
||||||
about it.
|
satellite, the moon. When you think about it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h3>Display 3 Heading</h3>
|
<h3>Display 3 Heading</h3>
|
||||||
<p>
|
<p>
|
||||||
Let’s talk about meat fondue recipes and what you need to know first. Meat fondue also known as oil fondue
|
Let’s talk about meat fondue recipes and what you need to know
|
||||||
is a method of cooking all kinds of meats, poultry, and seafood in a pot of heated oil.
|
first. Meat fondue also known as oil fondue is a method of cooking
|
||||||
|
all kinds of meats, poultry, and seafood in a pot of heated oil.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h4>Display 4 Heading</h4>
|
<h4>Display 4 Heading</h4>
|
||||||
<p>
|
<p>
|
||||||
There is something about parenthood that gives us a sense of history and a deeply rooted desire to send on
|
There is something about parenthood that gives us a sense of
|
||||||
into the next generation the great things we have discovered about life.
|
history and a deeply rooted desire to send on into the next
|
||||||
|
generation the great things we have discovered about life.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h5>Display 5 Heading</h5>
|
<h5>Display 5 Heading</h5>
|
||||||
<p>
|
<p>
|
||||||
There is a moment in the life of any aspiring astronomer that it is time to buy that first telescope. It’s
|
There is a moment in the life of any aspiring astronomer that it
|
||||||
exciting to think about setting up your own viewing station.
|
is time to buy that first telescope. It’s exciting to think about
|
||||||
|
setting up your own viewing station.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<p>
|
<p>
|
||||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
Of all of the celestial bodies that capture our attention and
|
||||||
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about it.
|
fascination as astronomers, none has a greater influence on life
|
||||||
|
on planet Earth than it’s own satellite, the moon. When you think
|
||||||
|
about it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="text--secondary">
|
<div class="text--secondary">
|
||||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
Of all of the celestial bodies that capture our attention and
|
||||||
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about it.
|
fascination as astronomers, none has a greater influence on life
|
||||||
|
on planet Earth than it’s own satellite, the moon. When you think
|
||||||
|
about it.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
|
|
@ -59,9 +68,11 @@
|
||||||
</p></pre
|
</p></pre
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Of all of the celestial bodies that capture our attention and fascination as astronomers,
|
Of all of the celestial bodies that capture our attention and
|
||||||
<span class="text--code">currentColor</span> none has a greater influence on life on planet Earth than
|
fascination as astronomers,
|
||||||
it’s own satellite, the moon.
|
<span class="text--code">currentColor</span> none has a greater
|
||||||
|
influence on life on planet Earth than it’s own satellite, the
|
||||||
|
moon.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
|
|
@ -73,10 +84,12 @@
|
||||||
<p class="va-h3">Lists</p>
|
<p class="va-h3">Lists</p>
|
||||||
<ol class="va-ordered">
|
<ol class="va-ordered">
|
||||||
<li>
|
<li>
|
||||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
Of all of the celestial bodies that capture our attention and
|
||||||
greater influence.
|
fascination as astronomers, none has a greater influence.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Earth than it’s own satellite, the moon. When you think about it.
|
||||||
</li>
|
</li>
|
||||||
<li>Earth than it’s own satellite, the moon. When you think about it.</li>
|
|
||||||
<li>Attention and fascination as.</li>
|
<li>Attention and fascination as.</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ol class="va-ordered">
|
<ol class="va-ordered">
|
||||||
|
|
@ -104,10 +117,12 @@
|
||||||
</ol>
|
</ol>
|
||||||
<ul class="va-unordered">
|
<ul class="va-unordered">
|
||||||
<li>
|
<li>
|
||||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
Of all of the celestial bodies that capture our attention and
|
||||||
greater influence.
|
fascination as astronomers, none has a greater influence.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Earth than it’s own satellite, the moon. When you think about it.
|
||||||
</li>
|
</li>
|
||||||
<li>Earth than it’s own satellite, the moon. When you think about it.</li>
|
|
||||||
<li>Attention and fascination as .</li>
|
<li>Attention and fascination as .</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="va-unordered">
|
<ul class="va-unordered">
|
||||||
|
|
@ -135,22 +150,28 @@
|
||||||
</ul>
|
</ul>
|
||||||
<p class="va-h3">Links</p>
|
<p class="va-h3">Links</p>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<a class="link mr-8" href="/default" @click.prevent> Default Link </a>
|
<a class="link mr-8" href="/default" @click.prevent>
|
||||||
<a class="link-secondary" href="/secondary" @click.prevent> Secondary Link </a>
|
Default Link
|
||||||
|
</a>
|
||||||
|
<a class="link-secondary" href="/secondary" @click.prevent>
|
||||||
|
Secondary Link
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<p class="va-h3">Other Elements</p>
|
<p class="va-h3">Other Elements</p>
|
||||||
<p>
|
<p>
|
||||||
None has a greater influence on
|
None has a greater influence on
|
||||||
<span class="text--highlighted">highlighted text</span>
|
<span class="text--highlighted">highlighted text</span>
|
||||||
life on planet Earth than it’s own satellite, the selected chunk of text. When you think about it.
|
life on planet Earth than it’s own satellite, the selected chunk
|
||||||
|
of text. When you think about it.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<blockquote class="va-blockquote border-primary">
|
<blockquote class="va-blockquote border-primary">
|
||||||
<p>
|
<p>
|
||||||
BQ: Let’s talk about meat fondue recipes and what you need to know first. Meat fondue also known as oil
|
BQ: Let’s talk about meat fondue recipes and what you need to
|
||||||
fondue is a method of cooking all kinds.
|
know first. Meat fondue also known as oil fondue is a method of
|
||||||
|
cooking all kinds.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<i>— Mister Lebowski</i>
|
<i>— Mister Lebowski</i>
|
||||||
|
|
@ -161,9 +182,10 @@
|
||||||
<div class="text-block">
|
<div class="text-block">
|
||||||
<p class="va-h3">va-h3 Heading</p>
|
<p class="va-h3">va-h3 Heading</p>
|
||||||
<span
|
<span
|
||||||
>Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
>Of all of the celestial bodies that capture our attention and
|
||||||
greater influence on life on planet Earth than it’s own satellite, the moon. When you think about
|
fascination as astronomers, none has a greater influence on life
|
||||||
it.</span
|
on planet Earth than it’s own satellite, the moon. When you
|
||||||
|
think about it.</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -171,11 +193,16 @@
|
||||||
<table class="va-table">
|
<table class="va-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="(data, index) in tableData[0]" :key="index">{{ data }}</th>
|
<th v-for="(data, index) in tableData[0]" :key="index">
|
||||||
|
{{ data }}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(rowData, rowIndex) in tableData.slice(1)" :key="rowIndex">
|
<tr
|
||||||
|
v-for="(rowData, rowIndex) in tableData.slice(1)"
|
||||||
|
:key="rowIndex"
|
||||||
|
>
|
||||||
<td v-for="(itemData, colIndex) in rowData" :key="colIndex">
|
<td v-for="(itemData, colIndex) in rowData" :key="colIndex">
|
||||||
{{ itemData }}
|
{{ itemData }}
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -190,17 +217,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
// import { useI18n } from 'vue-i18n'
|
// import { useI18n } from 'vue-i18n'
|
||||||
//
|
//
|
||||||
// const { t } = useI18n()
|
// const { t } = useI18n()
|
||||||
|
|
||||||
const tableData = computed(() => [
|
const tableData = computed(() => [
|
||||||
['Id', 'FooBar type', 'Actions'],
|
["Id", "FooBar type", "Actions"],
|
||||||
['1', 'Zebra', 'Delete'],
|
["1", "Zebra", "Delete"],
|
||||||
['2', 'Not Zebra', 'Remove'],
|
["2", "Not Zebra", "Remove"],
|
||||||
['3', 'Very Zebra', 'Eradicate'],
|
["3", "Very Zebra", "Eradicate"],
|
||||||
])
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<component :is="chartComponent" :chart-data="data" :data="data" :options="chartOptions" class="va-chart" />
|
<component
|
||||||
|
:is="chartComponent"
|
||||||
|
:chart-data="data"
|
||||||
|
:data="data"
|
||||||
|
:options="chartOptions"
|
||||||
|
class="va-chart"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="T extends 'line' | 'bar' | 'bubble' | 'doughnut' | 'pie'">
|
<script
|
||||||
import { computed } from 'vue'
|
lang="ts"
|
||||||
import type { ChartOptions, ChartData } from 'chart.js'
|
setup
|
||||||
import { defaultConfig, chartTypesMap } from './vaChartConfigs'
|
generic="T extends 'line' | 'bar' | 'bubble' | 'doughnut' | 'pie'"
|
||||||
|
>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import type { ChartOptions, ChartData } from "chart.js";
|
||||||
|
import { defaultConfig, chartTypesMap } from "./vaChartConfigs";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VaChart',
|
name: "VaChart",
|
||||||
})
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: ChartData<T>
|
data: ChartData<T>;
|
||||||
options?: ChartOptions<T>
|
options?: ChartOptions<T>;
|
||||||
type: T
|
type: T;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const chartComponent = chartTypesMap[props.type]
|
const chartComponent = chartTypesMap[props.type];
|
||||||
|
|
||||||
const chartOptions = computed<ChartOptions<T>>(() => ({
|
const chartOptions = computed<ChartOptions<T>>(() => ({
|
||||||
...(defaultConfig as any),
|
...(defaultConfig as any),
|
||||||
...props.options,
|
...props.options,
|
||||||
}))
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,29 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Bar } from 'vue-chartjs'
|
import { Bar } from "vue-chartjs";
|
||||||
import type { ChartOptions } from 'chart.js'
|
import type { ChartOptions } from "chart.js";
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
|
import {
|
||||||
|
Chart as ChartJS,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
BarElement,
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
} from "chart.js";
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
|
ChartJS.register(
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
BarElement,
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
);
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
data: any
|
data: any;
|
||||||
options?: ChartOptions<'bar'>
|
options?: ChartOptions<"bar">;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Bubble } from 'vue-chartjs'
|
import { Bubble } from "vue-chartjs";
|
||||||
import type { ChartOptions } from 'chart.js'
|
import type { ChartOptions } from "chart.js";
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, PointElement, LinearScale } from 'chart.js'
|
import {
|
||||||
import { TBubbleChartData } from '../../../data/types'
|
Chart as ChartJS,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
PointElement,
|
||||||
|
LinearScale,
|
||||||
|
} from "chart.js";
|
||||||
|
import { TBubbleChartData } from "../../../data/types";
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale)
|
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: TBubbleChartData
|
data: TBubbleChartData;
|
||||||
options?: ChartOptions<'bubble'>
|
options?: ChartOptions<"bubble">;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Doughnut } from 'vue-chartjs'
|
import { Doughnut } from "vue-chartjs";
|
||||||
import type { ChartOptions } from 'chart.js'
|
import type { ChartOptions } from "chart.js";
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
|
import {
|
||||||
import { TDoughnutChartData } from '../../../data/types'
|
Chart as ChartJS,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ArcElement,
|
||||||
|
CategoryScale,
|
||||||
|
} from "chart.js";
|
||||||
|
import { TDoughnutChartData } from "../../../data/types";
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
|
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: TDoughnutChartData
|
data: TDoughnutChartData;
|
||||||
options?: ChartOptions<'doughnut'>
|
options?: ChartOptions<"doughnut">;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,39 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Bar } from 'vue-chartjs'
|
import { Bar } from "vue-chartjs";
|
||||||
import type { ChartOptions } from 'chart.js'
|
import type { ChartOptions } from "chart.js";
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
|
import {
|
||||||
import { TBarChartData } from '../../../data/types'
|
Chart as ChartJS,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
BarElement,
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
} from "chart.js";
|
||||||
|
import { TBarChartData } from "../../../data/types";
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
|
ChartJS.register(
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
BarElement,
|
||||||
|
LinearScale,
|
||||||
|
CategoryScale,
|
||||||
|
);
|
||||||
|
|
||||||
const horizontalBarOptions = {
|
const horizontalBarOptions = {
|
||||||
indexAxis: 'y' as 'x' | 'y',
|
indexAxis: "y" as "x" | "y",
|
||||||
elements: {
|
elements: {
|
||||||
bar: {
|
bar: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: TBarChartData
|
data: TBarChartData;
|
||||||
options?: ChartOptions<'bar'>
|
options?: ChartOptions<"bar">;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
import { Line } from 'vue-chartjs'
|
import { Line } from "vue-chartjs";
|
||||||
import type { ChartOptions } from 'chart.js'
|
import type { ChartOptions } from "chart.js";
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
Title,
|
Title,
|
||||||
|
|
@ -16,46 +16,55 @@ import {
|
||||||
PointElement,
|
PointElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
Filler,
|
Filler,
|
||||||
} from 'chart.js'
|
} from "chart.js";
|
||||||
import { TLineChartData } from '../../../data/types'
|
import { TLineChartData } from "../../../data/types";
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import { useColors } from 'vuestic-ui/web-components'
|
import { useColors } from "vuestic-ui/web-components";
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale, Filler)
|
ChartJS.register(
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
LineElement,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
CategoryScale,
|
||||||
|
Filler,
|
||||||
|
);
|
||||||
|
|
||||||
const chart = ref<typeof Line>()
|
const chart = ref<typeof Line>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: TLineChartData
|
data: TLineChartData;
|
||||||
options?: ChartOptions<'line'>
|
options?: ChartOptions<"line">;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const ctx = computed(() => {
|
const ctx = computed(() => {
|
||||||
if (!chart.value) {
|
if (!chart.value) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chart.value.chart?.ctx ?? null
|
return chart.value.chart?.ctx ?? null;
|
||||||
})
|
});
|
||||||
|
|
||||||
const { setHSLAColor, getColor } = useColors()
|
const { setHSLAColor, getColor } = useColors();
|
||||||
|
|
||||||
const colors = ['primary', 'success', 'danger', 'warning']
|
const colors = ["primary", "success", "danger", "warning"];
|
||||||
|
|
||||||
const computedChartData = computed<TLineChartData>(() => {
|
const computedChartData = computed<TLineChartData>(() => {
|
||||||
if (!ctx.value) {
|
if (!ctx.value) {
|
||||||
return props.data
|
return props.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeGradient = (bg: string) => {
|
const makeGradient = (bg: string) => {
|
||||||
const gradient = ctx.value!.createLinearGradient(0, 0, 0, 90)
|
const gradient = ctx.value!.createLinearGradient(0, 0, 0, 90);
|
||||||
gradient.addColorStop(0, setHSLAColor(bg, { a: 0.4 }))
|
gradient.addColorStop(0, setHSLAColor(bg, { a: 0.4 }));
|
||||||
gradient.addColorStop(1, setHSLAColor(bg, { a: 0.0 }))
|
gradient.addColorStop(1, setHSLAColor(bg, { a: 0.0 }));
|
||||||
return gradient
|
return gradient;
|
||||||
}
|
};
|
||||||
|
|
||||||
const datasets = props.data.datasets.map((dataset, index) => {
|
const datasets = props.data.datasets.map((dataset, index) => {
|
||||||
const color = getColor(colors[index % colors.length])
|
const color = getColor(colors[index % colors.length]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...dataset,
|
...dataset,
|
||||||
|
|
@ -64,9 +73,9 @@ const computedChartData = computed<TLineChartData>(() => {
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
return { ...props.data, datasets }
|
return { ...props.data, datasets };
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,24 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale, ChartOptions } from 'chart.js'
|
import {
|
||||||
import { ChoroplethController, ProjectionScale, ColorScale, GeoFeature } from 'chartjs-chart-geo'
|
Chart as ChartJS,
|
||||||
import { watchEffect } from 'vue'
|
Title,
|
||||||
import { ChartData } from 'chart.js'
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ArcElement,
|
||||||
|
CategoryScale,
|
||||||
|
ChartOptions,
|
||||||
|
} from "chart.js";
|
||||||
|
import {
|
||||||
|
ChoroplethController,
|
||||||
|
ProjectionScale,
|
||||||
|
ColorScale,
|
||||||
|
GeoFeature,
|
||||||
|
} from "chartjs-chart-geo";
|
||||||
|
import { watchEffect } from "vue";
|
||||||
|
import { ChartData } from "chart.js";
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
Title,
|
Title,
|
||||||
|
|
@ -19,26 +32,26 @@ ChartJS.register(
|
||||||
ProjectionScale,
|
ProjectionScale,
|
||||||
ColorScale,
|
ColorScale,
|
||||||
GeoFeature,
|
GeoFeature,
|
||||||
)
|
);
|
||||||
|
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
function getColor(revenue: number) {
|
function getColor(revenue: number) {
|
||||||
return revenue >= 0.9 ? '#63A6F8' : revenue > 0.4 ? '#8FC0FA' : '#EDF0F1'
|
return revenue >= 0.9 ? "#63A6F8" : revenue > 0.4 ? "#8FC0FA" : "#EDF0F1";
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
options?: ChartOptions<'choropleth'>
|
options?: ChartOptions<"choropleth">;
|
||||||
data: ChartData<'choropleth', { feature: any; value: number }[], string>
|
data: ChartData<"choropleth", { feature: any; value: number }[], string>;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (canvas.value === null) {
|
if (canvas.value === null) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new ChartJS(canvas.value.getContext('2d')!, {
|
new ChartJS(canvas.value.getContext("2d")!, {
|
||||||
type: 'choropleth',
|
type: "choropleth",
|
||||||
data: props.data,
|
data: props.data,
|
||||||
options: {
|
options: {
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
@ -48,12 +61,12 @@ watchEffect(() => {
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
projection: {
|
projection: {
|
||||||
axis: 'x',
|
axis: "x",
|
||||||
projection: 'mercator',
|
projection: "mercator",
|
||||||
projectionScale: 1.6,
|
projectionScale: 1.6,
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
axis: 'x',
|
axis: "x",
|
||||||
quantize: 5,
|
quantize: 5,
|
||||||
display: false,
|
display: false,
|
||||||
interpolate: getColor,
|
interpolate: getColor,
|
||||||
|
|
@ -61,6 +74,6 @@ watchEffect(() => {
|
||||||
},
|
},
|
||||||
animation: false,
|
animation: false,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Pie } from 'vue-chartjs'
|
import { Pie } from "vue-chartjs";
|
||||||
import type { ChartOptions } from 'chart.js'
|
import type { ChartOptions } from "chart.js";
|
||||||
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
|
import {
|
||||||
import { TPieChartData } from '../../../data/types'
|
Chart as ChartJS,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ArcElement,
|
||||||
|
CategoryScale,
|
||||||
|
} from "chart.js";
|
||||||
|
import { TPieChartData } from "../../../data/types";
|
||||||
|
|
||||||
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
|
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: TPieChartData
|
data: TPieChartData;
|
||||||
options?: ChartOptions<'pie'>
|
options?: ChartOptions<"pie">;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,121 @@
|
||||||
import { Chart, TooltipModel } from 'chart.js'
|
import { Chart, TooltipModel } from "chart.js";
|
||||||
import { computePosition, flip, shift } from '@floating-ui/dom'
|
import { computePosition, flip, shift } from "@floating-ui/dom";
|
||||||
|
|
||||||
const getOrCreateTooltip = (chart: Chart) => {
|
const getOrCreateTooltip = (chart: Chart) => {
|
||||||
let tooltipEl = chart.canvas.parentNode?.querySelector('div')
|
let tooltipEl = chart.canvas.parentNode?.querySelector("div");
|
||||||
|
|
||||||
if (!tooltipEl) {
|
if (!tooltipEl) {
|
||||||
tooltipEl = document.createElement('div')
|
tooltipEl = document.createElement("div");
|
||||||
tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)'
|
tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
|
||||||
tooltipEl.style.borderRadius = '3px'
|
tooltipEl.style.borderRadius = "3px";
|
||||||
tooltipEl.style.color = 'white'
|
tooltipEl.style.color = "white";
|
||||||
tooltipEl.style.opacity = '1'
|
tooltipEl.style.opacity = "1";
|
||||||
tooltipEl.style.pointerEvents = 'none'
|
tooltipEl.style.pointerEvents = "none";
|
||||||
tooltipEl.style.position = 'absolute'
|
tooltipEl.style.position = "absolute";
|
||||||
// tooltipEl.style.transform = 'translate(-50%, 0)'
|
// tooltipEl.style.transform = 'translate(-50%, 0)'
|
||||||
tooltipEl.style.left = '0'
|
tooltipEl.style.left = "0";
|
||||||
tooltipEl.style.top = '0'
|
tooltipEl.style.top = "0";
|
||||||
tooltipEl.style.transition = 'all .1s ease'
|
tooltipEl.style.transition = "all .1s ease";
|
||||||
tooltipEl.style.height = 'min-content'
|
tooltipEl.style.height = "min-content";
|
||||||
tooltipEl.style.maxWidth = '200px'
|
tooltipEl.style.maxWidth = "200px";
|
||||||
tooltipEl.style.zIndex = '9999'
|
tooltipEl.style.zIndex = "9999";
|
||||||
|
|
||||||
const table = document.createElement('table')
|
const table = document.createElement("table");
|
||||||
table.style.margin = '0px'
|
table.style.margin = "0px";
|
||||||
|
|
||||||
tooltipEl.appendChild(table)
|
tooltipEl.appendChild(table);
|
||||||
chart.canvas.parentNode?.appendChild(tooltipEl)
|
chart.canvas.parentNode?.appendChild(tooltipEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tooltipEl
|
return tooltipEl;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const externalTooltipHandler = (context: { chart: Chart; tooltip: TooltipModel<any> }) => {
|
export const externalTooltipHandler = (context: {
|
||||||
|
chart: Chart;
|
||||||
|
tooltip: TooltipModel<any>;
|
||||||
|
}) => {
|
||||||
// Tooltip Element
|
// Tooltip Element
|
||||||
const { chart, tooltip } = context
|
const { chart, tooltip } = context;
|
||||||
const tooltipEl = getOrCreateTooltip(chart)
|
const tooltipEl = getOrCreateTooltip(chart);
|
||||||
|
|
||||||
// Hide if no tooltip
|
// Hide if no tooltip
|
||||||
if (tooltip.opacity === 0) {
|
if (tooltip.opacity === 0) {
|
||||||
tooltipEl.style.opacity = '0'
|
tooltipEl.style.opacity = "0";
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Text
|
// Set Text
|
||||||
if (tooltip.body) {
|
if (tooltip.body) {
|
||||||
const titleLines = tooltip.title || []
|
const titleLines = tooltip.title || [];
|
||||||
const bodyLines = tooltip.body.map((b) => b.lines)
|
const bodyLines = tooltip.body.map((b) => b.lines);
|
||||||
|
|
||||||
const tableHead = document.createElement('thead')
|
const tableHead = document.createElement("thead");
|
||||||
|
|
||||||
titleLines.forEach((title) => {
|
titleLines.forEach((title) => {
|
||||||
const tr = document.createElement('tr')
|
const tr = document.createElement("tr");
|
||||||
tr.style.borderWidth = '0'
|
tr.style.borderWidth = "0";
|
||||||
|
|
||||||
const th = document.createElement('th')
|
const th = document.createElement("th");
|
||||||
th.style.borderWidth = '0'
|
th.style.borderWidth = "0";
|
||||||
const text = document.createTextNode(title)
|
const text = document.createTextNode(title);
|
||||||
|
|
||||||
th.appendChild(text)
|
th.appendChild(text);
|
||||||
tr.appendChild(th)
|
tr.appendChild(th);
|
||||||
tableHead.appendChild(tr)
|
tableHead.appendChild(tr);
|
||||||
})
|
});
|
||||||
|
|
||||||
const tableBody = document.createElement('tbody')
|
const tableBody = document.createElement("tbody");
|
||||||
bodyLines.forEach((body, i) => {
|
bodyLines.forEach((body, i) => {
|
||||||
const colors = tooltip.labelColors[i]
|
const colors = tooltip.labelColors[i];
|
||||||
|
|
||||||
const span = document.createElement('span')
|
const span = document.createElement("span");
|
||||||
span.style.background = String(colors.backgroundColor)
|
span.style.background = String(colors.backgroundColor);
|
||||||
span.style.borderColor = String(colors.borderColor)
|
span.style.borderColor = String(colors.borderColor);
|
||||||
span.style.borderWidth = '2px'
|
span.style.borderWidth = "2px";
|
||||||
span.style.marginRight = '10px'
|
span.style.marginRight = "10px";
|
||||||
span.style.height = '10px'
|
span.style.height = "10px";
|
||||||
span.style.width = '10px'
|
span.style.width = "10px";
|
||||||
span.style.display = 'inline-block'
|
span.style.display = "inline-block";
|
||||||
|
|
||||||
const tr = document.createElement('tr')
|
const tr = document.createElement("tr");
|
||||||
tr.style.backgroundColor = 'inherit'
|
tr.style.backgroundColor = "inherit";
|
||||||
tr.style.borderWidth = '0'
|
tr.style.borderWidth = "0";
|
||||||
|
|
||||||
const td = document.createElement('td')
|
const td = document.createElement("td");
|
||||||
td.style.borderWidth = '0'
|
td.style.borderWidth = "0";
|
||||||
|
|
||||||
const text = document.createTextNode(body as any)
|
const text = document.createTextNode(body as any);
|
||||||
|
|
||||||
td.appendChild(span)
|
td.appendChild(span);
|
||||||
td.appendChild(text)
|
td.appendChild(text);
|
||||||
tr.appendChild(td)
|
tr.appendChild(td);
|
||||||
tableBody.appendChild(tr)
|
tableBody.appendChild(tr);
|
||||||
})
|
});
|
||||||
|
|
||||||
const tableRoot = tooltipEl.querySelector('table')
|
const tableRoot = tooltipEl.querySelector("table");
|
||||||
|
|
||||||
// Remove old children
|
// Remove old children
|
||||||
while (tableRoot?.firstChild) {
|
while (tableRoot?.firstChild) {
|
||||||
tableRoot.firstChild.remove()
|
tableRoot.firstChild.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new children
|
// Add new children
|
||||||
tableRoot?.appendChild(tableHead)
|
tableRoot?.appendChild(tableHead);
|
||||||
tableRoot?.appendChild(tableBody)
|
tableRoot?.appendChild(tableBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display, position, and set styles for font
|
// Display, position, and set styles for font
|
||||||
tooltipEl.style.opacity = '1'
|
tooltipEl.style.opacity = "1";
|
||||||
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px'
|
tooltipEl.style.padding =
|
||||||
|
tooltip.options.padding + "px " + tooltip.options.padding + "px";
|
||||||
|
|
||||||
computePosition(chart.canvas.parentNode! as HTMLElement, tooltipEl!, {
|
computePosition(chart.canvas.parentNode! as HTMLElement, tooltipEl!, {
|
||||||
placement: 'top',
|
placement: "top",
|
||||||
middleware: [flip(), shift()],
|
middleware: [flip(), shift()],
|
||||||
}).then(({ x, y }) => {
|
}).then(({ x, y }) => {
|
||||||
Object.assign(tooltipEl!.style, {
|
Object.assign(tooltipEl!.style, {
|
||||||
left: `${x}px`,
|
left: `${x}px`,
|
||||||
top: `${y}px`,
|
top: `${y}px`,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineAsyncComponent, markRaw } from 'vue'
|
import { defineAsyncComponent, markRaw } from "vue";
|
||||||
|
|
||||||
const DEFAULT_FONT_FAMILY = "'Inter', sans-serif"
|
const DEFAULT_FONT_FAMILY = "'Inter', sans-serif";
|
||||||
|
|
||||||
export const defaultConfig = {
|
export const defaultConfig = {
|
||||||
scales: {
|
scales: {
|
||||||
|
|
@ -21,10 +21,10 @@ export const defaultConfig = {
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'bottom',
|
position: "bottom",
|
||||||
labels: {
|
labels: {
|
||||||
font: {
|
font: {
|
||||||
color: '#34495e',
|
color: "#34495e",
|
||||||
family: DEFAULT_FONT_FAMILY,
|
family: DEFAULT_FONT_FAMILY,
|
||||||
size: 14,
|
size: 14,
|
||||||
},
|
},
|
||||||
|
|
@ -41,23 +41,23 @@ export const defaultConfig = {
|
||||||
},
|
},
|
||||||
datasets: {
|
datasets: {
|
||||||
line: {
|
line: {
|
||||||
fill: 'origin',
|
fill: "origin",
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
bubble: {
|
bubble: {
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
bar: {
|
bar: {
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
animation: true,
|
animation: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
export const doughnutConfig = {
|
export const doughnutConfig = {
|
||||||
cutout: '80%',
|
cutout: "80%",
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
display: false,
|
display: false,
|
||||||
|
|
@ -82,26 +82,38 @@ export const doughnutConfig = {
|
||||||
},
|
},
|
||||||
datasets: {
|
datasets: {
|
||||||
line: {
|
line: {
|
||||||
fill: 'origin',
|
fill: "origin",
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
bubble: {
|
bubble: {
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
bar: {
|
bar: {
|
||||||
borderColor: 'transparent',
|
borderColor: "transparent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
animation: true,
|
animation: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
export const chartTypesMap = {
|
export const chartTypesMap = {
|
||||||
pie: markRaw(defineAsyncComponent(() => import('./chart-types/PieChart.vue'))),
|
pie: markRaw(
|
||||||
doughnut: markRaw(defineAsyncComponent(() => import('./chart-types/DoughnutChart.vue'))),
|
defineAsyncComponent(() => import("./chart-types/PieChart.vue")),
|
||||||
bubble: markRaw(defineAsyncComponent(() => import('./chart-types/BubbleChart.vue'))),
|
),
|
||||||
line: markRaw(defineAsyncComponent(() => import('./chart-types/LineChart.vue'))),
|
doughnut: markRaw(
|
||||||
bar: markRaw(defineAsyncComponent(() => import('./chart-types/BarChart.vue'))),
|
defineAsyncComponent(() => import("./chart-types/DoughnutChart.vue")),
|
||||||
'horizontal-bar': markRaw(defineAsyncComponent(() => import('./chart-types/HorizontalBarChart.vue'))),
|
),
|
||||||
}
|
bubble: markRaw(
|
||||||
|
defineAsyncComponent(() => import("./chart-types/BubbleChart.vue")),
|
||||||
|
),
|
||||||
|
line: markRaw(
|
||||||
|
defineAsyncComponent(() => import("./chart-types/LineChart.vue")),
|
||||||
|
),
|
||||||
|
bar: markRaw(
|
||||||
|
defineAsyncComponent(() => import("./chart-types/BarChart.vue")),
|
||||||
|
),
|
||||||
|
"horizontal-bar": markRaw(
|
||||||
|
defineAsyncComponent(() => import("./chart-types/HorizontalBarChart.vue")),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,56 +5,56 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, Ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, Ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
import MediumEditor from 'medium-editor'
|
import MediumEditor from "medium-editor";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
editorOptions?: {
|
editorOptions?: {
|
||||||
buttonLabels: string
|
buttonLabels: string;
|
||||||
autoLink: boolean
|
autoLink: boolean;
|
||||||
toolbar: {
|
toolbar: {
|
||||||
buttons: string[]
|
buttons: string[];
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
editorOptions: () => ({
|
editorOptions: () => ({
|
||||||
buttonLabels: 'fontawesome',
|
buttonLabels: "fontawesome",
|
||||||
autoLink: true,
|
autoLink: true,
|
||||||
toolbar: {
|
toolbar: {
|
||||||
buttons: ['bold', 'italic', 'underline', 'anchor', 'h1', 'h2', 'h3'],
|
buttons: ["bold", "italic", "underline", "anchor", "h1", "h2", "h3"],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'initialized', editor: typeof MediumEditor): void
|
(e: "initialized", editor: typeof MediumEditor): void;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const editorElement: Ref<null | HTMLElement> = ref(null)
|
const editorElement: Ref<null | HTMLElement> = ref(null);
|
||||||
let editor: typeof MediumEditor | null = null
|
let editor: typeof MediumEditor | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!editorElement.value) {
|
if (!editorElement.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor = new MediumEditor(editorElement.value, props.editorOptions)
|
editor = new MediumEditor(editorElement.value, props.editorOptions);
|
||||||
emit('initialized', editor)
|
emit("initialized", editor);
|
||||||
})
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.destroy()
|
editor.destroy();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import 'medium-editor/src/sass/medium-editor';
|
@import "medium-editor/src/sass/medium-editor";
|
||||||
@import 'variables';
|
@import "variables";
|
||||||
|
|
||||||
$medium-editor-shadow: var(--va-box-shadow);
|
$medium-editor-shadow: var(--va-box-shadow);
|
||||||
$medium-editor-background-color: var(--va-divider);
|
$medium-editor-background-color: var(--va-divider);
|
||||||
|
|
@ -163,7 +163,8 @@ $medium-editor-active-text-color: var(--va-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.medium-toolbar-arrow-under::after {
|
.medium-toolbar-arrow-under::after {
|
||||||
border-color: $medium-editor-background-color transparent transparent transparent;
|
border-color: $medium-editor-background-color transparent transparent
|
||||||
|
transparent;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@
|
||||||
defineProps({
|
defineProps({
|
||||||
date: {
|
date: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
@ -47,7 +47,7 @@ defineProps({
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: "";
|
||||||
width: 2px;
|
width: 2px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--va-background-border);
|
background: var(--va-background-border);
|
||||||
|
|
|
||||||
|
|
@ -1,243 +1,243 @@
|
||||||
export default [
|
export default [
|
||||||
'Afghanistan',
|
"Afghanistan",
|
||||||
'Albania',
|
"Albania",
|
||||||
'Algeria',
|
"Algeria",
|
||||||
'American Samoa',
|
"American Samoa",
|
||||||
'Andorra',
|
"Andorra",
|
||||||
'Angola',
|
"Angola",
|
||||||
'Anguilla',
|
"Anguilla",
|
||||||
'Antarctica',
|
"Antarctica",
|
||||||
'Antigua and Barbuda',
|
"Antigua and Barbuda",
|
||||||
'Argentina',
|
"Argentina",
|
||||||
'Armenia',
|
"Armenia",
|
||||||
'Aruba',
|
"Aruba",
|
||||||
'Australia',
|
"Australia",
|
||||||
'Austria',
|
"Austria",
|
||||||
'Azerbaijan',
|
"Azerbaijan",
|
||||||
'Bahamas',
|
"Bahamas",
|
||||||
'Bahrain',
|
"Bahrain",
|
||||||
'Bangladesh',
|
"Bangladesh",
|
||||||
'Barbados',
|
"Barbados",
|
||||||
'Belarus',
|
"Belarus",
|
||||||
'Belgium',
|
"Belgium",
|
||||||
'Belize',
|
"Belize",
|
||||||
'Benin',
|
"Benin",
|
||||||
'Bermuda',
|
"Bermuda",
|
||||||
'Bhutan',
|
"Bhutan",
|
||||||
'Bolivia',
|
"Bolivia",
|
||||||
'Bosnia and Herzegowina',
|
"Bosnia and Herzegowina",
|
||||||
'Botswana',
|
"Botswana",
|
||||||
'Bouvet Island',
|
"Bouvet Island",
|
||||||
'Brazil',
|
"Brazil",
|
||||||
'British Indian Ocean Territory',
|
"British Indian Ocean Territory",
|
||||||
'Brunei Darussalam',
|
"Brunei Darussalam",
|
||||||
'Bulgaria',
|
"Bulgaria",
|
||||||
'Burkina Faso',
|
"Burkina Faso",
|
||||||
'Burundi',
|
"Burundi",
|
||||||
'Cambodia',
|
"Cambodia",
|
||||||
'Cameroon',
|
"Cameroon",
|
||||||
'Canada',
|
"Canada",
|
||||||
'Cape Verde',
|
"Cape Verde",
|
||||||
'Cayman Islands',
|
"Cayman Islands",
|
||||||
'Central African Republic',
|
"Central African Republic",
|
||||||
'Chad',
|
"Chad",
|
||||||
'Chile',
|
"Chile",
|
||||||
'China',
|
"China",
|
||||||
'Christmas Island',
|
"Christmas Island",
|
||||||
'Cocos (Keeling) Islands',
|
"Cocos (Keeling) Islands",
|
||||||
'Colombia',
|
"Colombia",
|
||||||
'Comoros',
|
"Comoros",
|
||||||
'Congo',
|
"Congo",
|
||||||
'Congo, the Democratic Republic of the',
|
"Congo, the Democratic Republic of the",
|
||||||
'Cook Islands',
|
"Cook Islands",
|
||||||
'Costa Rica',
|
"Costa Rica",
|
||||||
"Cote d'Ivoire",
|
"Cote d'Ivoire",
|
||||||
'Croatia (Hrvatska)',
|
"Croatia (Hrvatska)",
|
||||||
'Cuba',
|
"Cuba",
|
||||||
'Cyprus',
|
"Cyprus",
|
||||||
'Czech Republic',
|
"Czech Republic",
|
||||||
'Denmark',
|
"Denmark",
|
||||||
'Djibouti',
|
"Djibouti",
|
||||||
'Dominica',
|
"Dominica",
|
||||||
'Dominican Republic',
|
"Dominican Republic",
|
||||||
'East Timor',
|
"East Timor",
|
||||||
'Ecuador',
|
"Ecuador",
|
||||||
'Egypt',
|
"Egypt",
|
||||||
'El Salvador',
|
"El Salvador",
|
||||||
'Equatorial Guinea',
|
"Equatorial Guinea",
|
||||||
'Eritrea',
|
"Eritrea",
|
||||||
'Estonia',
|
"Estonia",
|
||||||
'Ethiopia',
|
"Ethiopia",
|
||||||
'Falkland Islands (Malvinas)',
|
"Falkland Islands (Malvinas)",
|
||||||
'Faroe Islands',
|
"Faroe Islands",
|
||||||
'Fiji',
|
"Fiji",
|
||||||
'Finland',
|
"Finland",
|
||||||
'France',
|
"France",
|
||||||
'France Metropolitan',
|
"France Metropolitan",
|
||||||
'French Guiana',
|
"French Guiana",
|
||||||
'French Polynesia',
|
"French Polynesia",
|
||||||
'French Southern Territories',
|
"French Southern Territories",
|
||||||
'Gabon',
|
"Gabon",
|
||||||
'Gambia',
|
"Gambia",
|
||||||
'Georgia',
|
"Georgia",
|
||||||
'Germany',
|
"Germany",
|
||||||
'Ghana',
|
"Ghana",
|
||||||
'Gibraltar',
|
"Gibraltar",
|
||||||
'Greece',
|
"Greece",
|
||||||
'Greenland',
|
"Greenland",
|
||||||
'Grenada',
|
"Grenada",
|
||||||
'Guadeloupe',
|
"Guadeloupe",
|
||||||
'Guam',
|
"Guam",
|
||||||
'Guatemala',
|
"Guatemala",
|
||||||
'Guinea',
|
"Guinea",
|
||||||
'Guinea-Bissau',
|
"Guinea-Bissau",
|
||||||
'Guyana',
|
"Guyana",
|
||||||
'Haiti',
|
"Haiti",
|
||||||
'Heard and Mc Donald Islands',
|
"Heard and Mc Donald Islands",
|
||||||
'Holy See (Vatican City State)',
|
"Holy See (Vatican City State)",
|
||||||
'Honduras',
|
"Honduras",
|
||||||
'Hong Kong',
|
"Hong Kong",
|
||||||
'Hungary',
|
"Hungary",
|
||||||
'Iceland',
|
"Iceland",
|
||||||
'India',
|
"India",
|
||||||
'Indonesia',
|
"Indonesia",
|
||||||
'Iran (Islamic Republic of)',
|
"Iran (Islamic Republic of)",
|
||||||
'Iraq',
|
"Iraq",
|
||||||
'Ireland',
|
"Ireland",
|
||||||
'Israel',
|
"Israel",
|
||||||
'Italy',
|
"Italy",
|
||||||
'Jamaica',
|
"Jamaica",
|
||||||
'Japan',
|
"Japan",
|
||||||
'Jordan',
|
"Jordan",
|
||||||
'Kazakhstan',
|
"Kazakhstan",
|
||||||
'Kenya',
|
"Kenya",
|
||||||
'Kiribati',
|
"Kiribati",
|
||||||
"Korea, Democratic People's Republic of",
|
"Korea, Democratic People's Republic of",
|
||||||
'Korea, Republic of',
|
"Korea, Republic of",
|
||||||
'Kuwait',
|
"Kuwait",
|
||||||
'Kyrgyzstan',
|
"Kyrgyzstan",
|
||||||
"Lao, People's Democratic Republic",
|
"Lao, People's Democratic Republic",
|
||||||
'Latvia',
|
"Latvia",
|
||||||
'Lebanon',
|
"Lebanon",
|
||||||
'Lesotho',
|
"Lesotho",
|
||||||
'Liberia',
|
"Liberia",
|
||||||
'Libyan Arab Jamahiriya',
|
"Libyan Arab Jamahiriya",
|
||||||
'Liechtenstein',
|
"Liechtenstein",
|
||||||
'Lithuania',
|
"Lithuania",
|
||||||
'Luxembourg',
|
"Luxembourg",
|
||||||
'Macau',
|
"Macau",
|
||||||
'Macedonia, The Former Yugoslav Republic of',
|
"Macedonia, The Former Yugoslav Republic of",
|
||||||
'Madagascar',
|
"Madagascar",
|
||||||
'Malawi',
|
"Malawi",
|
||||||
'Malaysia',
|
"Malaysia",
|
||||||
'Maldives',
|
"Maldives",
|
||||||
'Mali',
|
"Mali",
|
||||||
'Malta',
|
"Malta",
|
||||||
'Marshall Islands',
|
"Marshall Islands",
|
||||||
'Martinique',
|
"Martinique",
|
||||||
'Mauritania',
|
"Mauritania",
|
||||||
'Mauritius',
|
"Mauritius",
|
||||||
'Mayotte',
|
"Mayotte",
|
||||||
'Mexico',
|
"Mexico",
|
||||||
'Micronesia, Federated States of',
|
"Micronesia, Federated States of",
|
||||||
'Moldova, Republic of',
|
"Moldova, Republic of",
|
||||||
'Monaco',
|
"Monaco",
|
||||||
'Mongolia',
|
"Mongolia",
|
||||||
'Montserrat',
|
"Montserrat",
|
||||||
'Morocco',
|
"Morocco",
|
||||||
'Mozambique',
|
"Mozambique",
|
||||||
'Myanmar',
|
"Myanmar",
|
||||||
'Namibia',
|
"Namibia",
|
||||||
'Nauru',
|
"Nauru",
|
||||||
'Nepal',
|
"Nepal",
|
||||||
'Netherlands',
|
"Netherlands",
|
||||||
'Netherlands Antilles',
|
"Netherlands Antilles",
|
||||||
'New Caledonia',
|
"New Caledonia",
|
||||||
'New Zealand',
|
"New Zealand",
|
||||||
'Nicaragua',
|
"Nicaragua",
|
||||||
'Niger',
|
"Niger",
|
||||||
'Nigeria',
|
"Nigeria",
|
||||||
'Niue',
|
"Niue",
|
||||||
'Norfolk Island',
|
"Norfolk Island",
|
||||||
'Northern Mariana Islands',
|
"Northern Mariana Islands",
|
||||||
'Norway',
|
"Norway",
|
||||||
'Oman',
|
"Oman",
|
||||||
'Pakistan',
|
"Pakistan",
|
||||||
'Palau',
|
"Palau",
|
||||||
'Panama',
|
"Panama",
|
||||||
'Papua New Guinea',
|
"Papua New Guinea",
|
||||||
'Paraguay',
|
"Paraguay",
|
||||||
'Peru',
|
"Peru",
|
||||||
'Philippines',
|
"Philippines",
|
||||||
'Pitcairn',
|
"Pitcairn",
|
||||||
'Poland',
|
"Poland",
|
||||||
'Portugal',
|
"Portugal",
|
||||||
'Puerto Rico',
|
"Puerto Rico",
|
||||||
'Qatar',
|
"Qatar",
|
||||||
'Reunion',
|
"Reunion",
|
||||||
'Romania',
|
"Romania",
|
||||||
'Russian Federation',
|
"Russian Federation",
|
||||||
'Rwanda',
|
"Rwanda",
|
||||||
'Saint Kitts and Nevis',
|
"Saint Kitts and Nevis",
|
||||||
'Saint Lucia',
|
"Saint Lucia",
|
||||||
'Saint Vincent and the Grenadines',
|
"Saint Vincent and the Grenadines",
|
||||||
'Samoa',
|
"Samoa",
|
||||||
'San Marino',
|
"San Marino",
|
||||||
'Sao Tome and Principe',
|
"Sao Tome and Principe",
|
||||||
'Saudi Arabia',
|
"Saudi Arabia",
|
||||||
'Senegal',
|
"Senegal",
|
||||||
'Serbia',
|
"Serbia",
|
||||||
'Seychelles',
|
"Seychelles",
|
||||||
'Sierra Leone',
|
"Sierra Leone",
|
||||||
'Singapore',
|
"Singapore",
|
||||||
'Slovakia (Slovak Republic)',
|
"Slovakia (Slovak Republic)",
|
||||||
'Slovenia',
|
"Slovenia",
|
||||||
'Solomon Islands',
|
"Solomon Islands",
|
||||||
'Somalia',
|
"Somalia",
|
||||||
'South Africa',
|
"South Africa",
|
||||||
'South Georgia and the South Sandwich Islands',
|
"South Georgia and the South Sandwich Islands",
|
||||||
'Spain',
|
"Spain",
|
||||||
'Sri Lanka',
|
"Sri Lanka",
|
||||||
'St. Helena',
|
"St. Helena",
|
||||||
'St. Pierre and Miquelon',
|
"St. Pierre and Miquelon",
|
||||||
'Sudan',
|
"Sudan",
|
||||||
'Suriname',
|
"Suriname",
|
||||||
'Svalbard and Jan Mayen Islands',
|
"Svalbard and Jan Mayen Islands",
|
||||||
'Swaziland',
|
"Swaziland",
|
||||||
'Sweden',
|
"Sweden",
|
||||||
'Switzerland',
|
"Switzerland",
|
||||||
'Syrian Arab Republic',
|
"Syrian Arab Republic",
|
||||||
'Taiwan, Province of China',
|
"Taiwan, Province of China",
|
||||||
'Tajikistan',
|
"Tajikistan",
|
||||||
'Tanzania, United Republic of',
|
"Tanzania, United Republic of",
|
||||||
'United States of America',
|
"United States of America",
|
||||||
'Thailand',
|
"Thailand",
|
||||||
'Togo',
|
"Togo",
|
||||||
'Tokelau',
|
"Tokelau",
|
||||||
'Tonga',
|
"Tonga",
|
||||||
'Trinidad and Tobago',
|
"Trinidad and Tobago",
|
||||||
'Tunisia',
|
"Tunisia",
|
||||||
'Turkey',
|
"Turkey",
|
||||||
'Turkmenistan',
|
"Turkmenistan",
|
||||||
'Turks and Caicos Islands',
|
"Turks and Caicos Islands",
|
||||||
'Tuvalu',
|
"Tuvalu",
|
||||||
'Uganda',
|
"Uganda",
|
||||||
'Ukraine',
|
"Ukraine",
|
||||||
'United Arab Emirates',
|
"United Arab Emirates",
|
||||||
'United Kingdom',
|
"United Kingdom",
|
||||||
'United States',
|
"United States",
|
||||||
'United States Minor Outlying Islands',
|
"United States Minor Outlying Islands",
|
||||||
'Uruguay',
|
"Uruguay",
|
||||||
'Uzbekistan',
|
"Uzbekistan",
|
||||||
'Vanuatu',
|
"Vanuatu",
|
||||||
'Venezuela',
|
"Venezuela",
|
||||||
'Vietnam',
|
"Vietnam",
|
||||||
'Virgin Islands (British)',
|
"Virgin Islands (British)",
|
||||||
'Virgin Islands (U.S.)',
|
"Virgin Islands (U.S.)",
|
||||||
'Wallis and Futuna Islands',
|
"Wallis and Futuna Islands",
|
||||||
'Western Sahara',
|
"Western Sahara",
|
||||||
'Yemen',
|
"Yemen",
|
||||||
'Yugoslavia',
|
"Yugoslavia",
|
||||||
'Zambia',
|
"Zambia",
|
||||||
'Zimbabwe',
|
"Zimbabwe",
|
||||||
]
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
import { TBarChartData } from '../types'
|
import { TBarChartData } from "../types";
|
||||||
|
|
||||||
export const barChartData: TBarChartData = {
|
export const barChartData: TBarChartData = {
|
||||||
labels: [
|
labels: [
|
||||||
'January',
|
"January",
|
||||||
'February',
|
"February",
|
||||||
'March',
|
"March",
|
||||||
'April',
|
"April",
|
||||||
'May',
|
"May",
|
||||||
'June',
|
"June",
|
||||||
'July',
|
"July",
|
||||||
'August',
|
"August",
|
||||||
'September',
|
"September",
|
||||||
'October',
|
"October",
|
||||||
'November',
|
"November",
|
||||||
'December',
|
"December",
|
||||||
],
|
],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Last year',
|
label: "Last year",
|
||||||
backgroundColor: 'primary',
|
backgroundColor: "primary",
|
||||||
data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
|
data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Current year',
|
label: "Current year",
|
||||||
backgroundColor: 'info',
|
backgroundColor: "info",
|
||||||
data: [50, 10, 22, 39, 15, 20, 85, 32, 60, 50, 20, 30],
|
data: [50, 10, 22, 39, 15, 20, 85, 32, 60, 50, 20, 30],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { TBubbleChartData } from '../types'
|
import { TBubbleChartData } from "../types";
|
||||||
|
|
||||||
export const bubbleChartData: TBubbleChartData = {
|
export const bubbleChartData: TBubbleChartData = {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'USA',
|
label: "USA",
|
||||||
backgroundColor: 'danger',
|
backgroundColor: "danger",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
x: 23,
|
x: 23,
|
||||||
|
|
@ -49,8 +49,8 @@ export const bubbleChartData: TBubbleChartData = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Russia',
|
label: "Russia",
|
||||||
backgroundColor: 'primary',
|
backgroundColor: "primary",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|
@ -95,8 +95,8 @@ export const bubbleChartData: TBubbleChartData = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Canada',
|
label: "Canada",
|
||||||
backgroundColor: 'warning',
|
backgroundColor: "warning",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
x: 10,
|
x: 10,
|
||||||
|
|
@ -136,8 +136,8 @@ export const bubbleChartData: TBubbleChartData = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Belarus',
|
label: "Belarus",
|
||||||
backgroundColor: 'info',
|
backgroundColor: "info",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
x: 35,
|
x: 35,
|
||||||
|
|
@ -182,8 +182,8 @@ export const bubbleChartData: TBubbleChartData = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Ukraine',
|
label: "Ukraine",
|
||||||
backgroundColor: 'success',
|
backgroundColor: "success",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
x: 25,
|
x: 25,
|
||||||
|
|
@ -228,4 +228,4 @@ export const bubbleChartData: TBubbleChartData = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,36 @@
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from "vue";
|
||||||
import { useColors, useGlobalConfig } from 'vuestic-ui'
|
import { useColors, useGlobalConfig } from "vuestic-ui";
|
||||||
|
|
||||||
type chartColors = string | string[]
|
type chartColors = string | string[];
|
||||||
|
|
||||||
export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
|
export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
|
||||||
const { getGlobalConfig } = useGlobalConfig()
|
const { getGlobalConfig } = useGlobalConfig();
|
||||||
const { setHSLAColor, getColor } = useColors()
|
const { setHSLAColor, getColor } = useColors();
|
||||||
|
|
||||||
const generateHSLAColors = (colors: chartColors) =>
|
const generateHSLAColors = (colors: chartColors) =>
|
||||||
typeof colors === 'string'
|
typeof colors === "string"
|
||||||
? setHSLAColor(getColor(colors), { a: alfa })
|
? setHSLAColor(getColor(colors), { a: alfa })
|
||||||
: colors.map((color) => setHSLAColor(getColor(color), { a: alfa }))
|
: colors.map((color) => setHSLAColor(getColor(color), { a: alfa }));
|
||||||
|
|
||||||
const generateColors = (colors: chartColors) =>
|
const generateColors = (colors: chartColors) =>
|
||||||
typeof colors === 'string' ? getColor(colors) : colors.map((color) => getColor(color))
|
typeof colors === "string"
|
||||||
|
? getColor(colors)
|
||||||
|
: colors.map((color) => getColor(color));
|
||||||
|
|
||||||
const generatedHSLAColors = ref(generateHSLAColors(chartColors))
|
const generatedHSLAColors = ref(generateHSLAColors(chartColors));
|
||||||
const generatedColors = ref(generateColors(chartColors))
|
const generatedColors = ref(generateColors(chartColors));
|
||||||
|
|
||||||
const theme = computed(() => getGlobalConfig().colors!)
|
const theme = computed(() => getGlobalConfig().colors!);
|
||||||
|
|
||||||
watch(theme, () => {
|
watch(theme, () => {
|
||||||
generatedHSLAColors.value = generateHSLAColors(chartColors)
|
generatedHSLAColors.value = generateHSLAColors(chartColors);
|
||||||
generatedColors.value = generateColors(chartColors)
|
generatedColors.value = generateColors(chartColors);
|
||||||
})
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generateHSLAColors,
|
generateHSLAColors,
|
||||||
generateColors,
|
generateColors,
|
||||||
generatedColors,
|
generatedColors,
|
||||||
generatedHSLAColors,
|
generatedHSLAColors,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
import { computed, ComputedRef } from 'vue'
|
import { computed, ComputedRef } from "vue";
|
||||||
import { useChartColors } from './useChartColors'
|
import { useChartColors } from "./useChartColors";
|
||||||
import { TChartData } from '../../types'
|
import { TChartData } from "../../types";
|
||||||
|
|
||||||
export function useChartData<T extends TChartData>(data: T, alfa?: number): ComputedRef<T> {
|
export function useChartData<T extends TChartData>(
|
||||||
const datasetsColors = data.datasets.map((dataset) => dataset.backgroundColor as string)
|
data: T,
|
||||||
|
alfa?: number,
|
||||||
|
): ComputedRef<T> {
|
||||||
|
const datasetsColors = data.datasets.map(
|
||||||
|
(dataset) => dataset.backgroundColor as string,
|
||||||
|
);
|
||||||
|
|
||||||
const datasetsThemesColors = datasetsColors.map(
|
const datasetsThemesColors = datasetsColors.map(
|
||||||
(colors) => useChartColors(colors, alfa)[alfa ? 'generatedHSLAColors' : 'generatedColors'],
|
(colors) =>
|
||||||
)
|
useChartColors(colors, alfa)[
|
||||||
|
alfa ? "generatedHSLAColors" : "generatedColors"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
const datasets = data.datasets.map((dataset, idx) => ({
|
const datasets = data.datasets.map((dataset, idx) => ({
|
||||||
...dataset,
|
...dataset,
|
||||||
backgroundColor: datasetsThemesColors[idx].value,
|
backgroundColor: datasetsThemesColors[idx].value,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
return { ...data, datasets } as T
|
return { ...data, datasets } as T;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { TDoughnutChartData } from '../types'
|
import { TDoughnutChartData } from "../types";
|
||||||
|
|
||||||
export const profitBackground = '#154EC1'
|
export const profitBackground = "#154EC1";
|
||||||
export const expensesBackground = '#fff'
|
export const expensesBackground = "#fff";
|
||||||
export const earningsBackground = '#ECF0F1'
|
export const earningsBackground = "#ECF0F1";
|
||||||
|
|
||||||
export const doughnutChartData: TDoughnutChartData = {
|
export const doughnutChartData: TDoughnutChartData = {
|
||||||
labels: ['Profit', 'Expenses'],
|
labels: ["Profit", "Expenses"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Yearly Breakdown',
|
label: "Yearly Breakdown",
|
||||||
backgroundColor: [profitBackground, earningsBackground],
|
backgroundColor: [profitBackground, earningsBackground],
|
||||||
data: [432, 167],
|
data: [432, 167],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,30 @@
|
||||||
import { TBarChartData } from '../types'
|
import { TBarChartData } from "../types";
|
||||||
|
|
||||||
export const horizontalBarChartData: TBarChartData = {
|
export const horizontalBarChartData: TBarChartData = {
|
||||||
labels: [
|
labels: [
|
||||||
'January',
|
"January",
|
||||||
'February',
|
"February",
|
||||||
'March',
|
"March",
|
||||||
'April',
|
"April",
|
||||||
'May',
|
"May",
|
||||||
'June',
|
"June",
|
||||||
'July',
|
"July",
|
||||||
'August',
|
"August",
|
||||||
'September',
|
"September",
|
||||||
'October',
|
"October",
|
||||||
'November',
|
"November",
|
||||||
'December',
|
"December",
|
||||||
],
|
],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Vuestic Satisfaction Score',
|
label: "Vuestic Satisfaction Score",
|
||||||
backgroundColor: 'primary',
|
backgroundColor: "primary",
|
||||||
data: [80, 90, 50, 70, 60, 90, 50, 90, 80, 40, 72, 93],
|
data: [80, 90, 50, 70, 60, 90, 50, 90, 80, 40, 72, 93],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Bulma Satisfaction Score',
|
label: "Bulma Satisfaction Score",
|
||||||
backgroundColor: 'danger',
|
backgroundColor: "danger",
|
||||||
data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53],
|
data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
export { bubbleChartData } from './bubbleChartData'
|
export { bubbleChartData } from "./bubbleChartData";
|
||||||
export { doughnutChartData } from './doughnutChartData'
|
export { doughnutChartData } from "./doughnutChartData";
|
||||||
export { barChartData } from './barChartData'
|
export { barChartData } from "./barChartData";
|
||||||
export { horizontalBarChartData } from './horizontalBarChartData'
|
export { horizontalBarChartData } from "./horizontalBarChartData";
|
||||||
export { lineChartData } from './lineChartData'
|
export { lineChartData } from "./lineChartData";
|
||||||
export { pieChartData } from './pieChartData'
|
export { pieChartData } from "./pieChartData";
|
||||||
|
|
||||||
// TODO: clean up charts data, after dashboard rework
|
// TODO: clean up charts data, after dashboard rework
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import { TLineChartData } from '../types'
|
import { TLineChartData } from "../types";
|
||||||
|
|
||||||
export const lineChartData: TLineChartData = {
|
export const lineChartData: TLineChartData = {
|
||||||
labels: [
|
labels: [
|
||||||
'January',
|
"January",
|
||||||
'February',
|
"February",
|
||||||
'March',
|
"March",
|
||||||
'April',
|
"April",
|
||||||
'May',
|
"May",
|
||||||
'June',
|
"June",
|
||||||
'July',
|
"July",
|
||||||
'August',
|
"August",
|
||||||
'September',
|
"September",
|
||||||
'October',
|
"October",
|
||||||
'November',
|
"November",
|
||||||
'December',
|
"December",
|
||||||
],
|
],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Monthly Earnings',
|
label: "Monthly Earnings",
|
||||||
backgroundColor: 'rgba(75,192,192,0.4)',
|
backgroundColor: "rgba(75,192,192,0.4)",
|
||||||
data: [10, 35, 14, 17, 12, 40, 75, 55, 30, 51, 25, 7], // Random values
|
data: [10, 35, 14, 17, 12, 40, 75, 55, 30, 51, 25, 7], // Random values
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { TLineChartData } from '../types'
|
import { TLineChartData } from "../types";
|
||||||
|
|
||||||
export const pieChartData: TLineChartData = {
|
export const pieChartData: TLineChartData = {
|
||||||
labels: ['Africa', 'Asia', 'Europe'],
|
labels: ["Africa", "Asia", "Europe"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Population (millions)',
|
label: "Population (millions)",
|
||||||
backgroundColor: ['primary', 'warning', 'danger'],
|
backgroundColor: ["primary", "warning", "danger"],
|
||||||
data: [2478, 5267, 734],
|
data: [2478, 5267, 734],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,49 @@
|
||||||
export const earningsColor = '#49A8FF'
|
export const earningsColor = "#49A8FF";
|
||||||
export const expensesColor = '#154EC1'
|
export const expensesColor = "#154EC1";
|
||||||
|
|
||||||
export const months: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
export const months: string[] = [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
];
|
||||||
|
|
||||||
export type Revenues = {
|
export type Revenues = {
|
||||||
month: string
|
month: string;
|
||||||
earning: number
|
earning: number;
|
||||||
expenses: number
|
expenses: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const generateRevenues = (months: string[]): Revenues[] => {
|
export const generateRevenues = (months: string[]): Revenues[] => {
|
||||||
return months.map((month: string) => {
|
return months.map((month: string) => {
|
||||||
const earning = Math.floor(Math.random() * 100000 + 10000)
|
const earning = Math.floor(Math.random() * 100000 + 10000);
|
||||||
return {
|
return {
|
||||||
month,
|
month,
|
||||||
earning,
|
earning,
|
||||||
expenses: Math.floor(earning * Math.random()),
|
expenses: Math.floor(earning * Math.random()),
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getRevenuePerMonth = (month: string, revenues: Revenues[]): Revenues => {
|
export const getRevenuePerMonth = (
|
||||||
const revenue = revenues.find((revenue) => revenue.month === month)
|
month: string,
|
||||||
return revenue || { month, earning: 0, expenses: 0 }
|
revenues: Revenues[],
|
||||||
}
|
): Revenues => {
|
||||||
|
const revenue = revenues.find((revenue) => revenue.month === month);
|
||||||
|
return revenue || { month, earning: 0, expenses: 0 };
|
||||||
|
};
|
||||||
|
|
||||||
export const formatMoney = (amount: number, currency = 'USD'): string => {
|
export const formatMoney = (amount: number, currency = "USD"): string => {
|
||||||
return new Intl.NumberFormat('en-US', {
|
return new Intl.NumberFormat("en-US", {
|
||||||
style: 'currency',
|
style: "currency",
|
||||||
currency,
|
currency,
|
||||||
}).format(amount)
|
}).format(amount);
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,66 @@
|
||||||
import { sleep } from '../../services/utils'
|
import { sleep } from "../../services/utils";
|
||||||
import projectsDb from './projects-db.json'
|
import projectsDb from "./projects-db.json";
|
||||||
import usersDb from './users-db.json'
|
import usersDb from "./users-db.json";
|
||||||
|
|
||||||
// Simulate API calls
|
// Simulate API calls
|
||||||
export type Pagination = {
|
export type Pagination = {
|
||||||
page: number
|
page: number;
|
||||||
perPage: number
|
perPage: number;
|
||||||
total: number
|
total: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Sorting = {
|
export type Sorting = {
|
||||||
sortBy: keyof (typeof projectsDb)[number] | undefined
|
sortBy: keyof (typeof projectsDb)[number] | undefined;
|
||||||
sortingOrder: 'asc' | 'desc' | null
|
sortingOrder: "asc" | "desc" | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const getSortItem = (obj: any, sortBy: keyof (typeof projectsDb)[number]) => {
|
const getSortItem = (obj: any, sortBy: keyof (typeof projectsDb)[number]) => {
|
||||||
if (sortBy === 'project_owner') {
|
if (sortBy === "project_owner") {
|
||||||
return obj.project_owner.fullname
|
return obj.project_owner.fullname;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy === 'team') {
|
if (sortBy === "team") {
|
||||||
return obj.team.map((user: any) => user.fullname).join(', ')
|
return obj.team.map((user: any) => user.fullname).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy === 'creation_date') {
|
if (sortBy === "creation_date") {
|
||||||
return new Date(obj[sortBy])
|
return new Date(obj[sortBy]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj[sortBy]
|
return obj[sortBy];
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getProjects = async (options: Sorting & Pagination) => {
|
export const getProjects = async (options: Sorting & Pagination) => {
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
|
|
||||||
const projects = projectsDb.map((project) => ({
|
const projects = projectsDb.map((project) => ({
|
||||||
...project,
|
...project,
|
||||||
project_owner: usersDb.find((user) => user.id === project.project_owner)! as (typeof usersDb)[number],
|
project_owner: usersDb.find(
|
||||||
team: usersDb.filter((user) => project.team.includes(user.id)) as (typeof usersDb)[number][],
|
(user) => user.id === project.project_owner,
|
||||||
}))
|
)! as (typeof usersDb)[number],
|
||||||
|
team: usersDb.filter((user) =>
|
||||||
|
project.team.includes(user.id),
|
||||||
|
) as (typeof usersDb)[number][],
|
||||||
|
}));
|
||||||
|
|
||||||
if (options.sortBy && options.sortingOrder) {
|
if (options.sortBy && options.sortingOrder) {
|
||||||
projects.sort((a, b) => {
|
projects.sort((a, b) => {
|
||||||
a = getSortItem(a, options.sortBy!)
|
a = getSortItem(a, options.sortBy!);
|
||||||
b = getSortItem(b, options.sortBy!)
|
b = getSortItem(b, options.sortBy!);
|
||||||
if (a < b) {
|
if (a < b) {
|
||||||
return options.sortingOrder === 'asc' ? -1 : 1
|
return options.sortingOrder === "asc" ? -1 : 1;
|
||||||
}
|
}
|
||||||
if (a > b) {
|
if (a > b) {
|
||||||
return options.sortingOrder === 'asc' ? 1 : -1
|
return options.sortingOrder === "asc" ? 1 : -1;
|
||||||
}
|
}
|
||||||
return 0
|
return 0;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedProjects = projects.slice((options.page - 1) * options.perPage, options.page * options.perPage)
|
const normalizedProjects = projects.slice(
|
||||||
|
(options.page - 1) * options.perPage,
|
||||||
|
options.page * options.perPage,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: normalizedProjects,
|
data: normalizedProjects,
|
||||||
|
|
@ -62,41 +69,51 @@ export const getProjects = async (options: Sorting & Pagination) => {
|
||||||
perPage: options.perPage,
|
perPage: options.perPage,
|
||||||
total: projectsDb.length,
|
total: projectsDb.length,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const addProject = async (project: Omit<(typeof projectsDb)[number], 'id' | 'creation_date'>) => {
|
export const addProject = async (
|
||||||
await sleep(1000)
|
project: Omit<(typeof projectsDb)[number], "id" | "creation_date">,
|
||||||
|
) => {
|
||||||
|
await sleep(1000);
|
||||||
|
|
||||||
const newProject = {
|
const newProject = {
|
||||||
...project,
|
...project,
|
||||||
id: projectsDb.length + 1,
|
id: projectsDb.length + 1,
|
||||||
creation_date: new Date().toLocaleDateString('gb', { day: 'numeric', month: 'short', year: 'numeric' }),
|
creation_date: new Date().toLocaleDateString("gb", {
|
||||||
}
|
day: "numeric",
|
||||||
|
month: "short",
|
||||||
|
year: "numeric",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
projectsDb.push(newProject)
|
projectsDb.push(newProject);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...newProject,
|
...newProject,
|
||||||
project_owner: usersDb.find((user) => user.id === project.project_owner)! as (typeof usersDb)[number],
|
project_owner: usersDb.find(
|
||||||
team: usersDb.filter((user) => project.team.includes(user.id)) as (typeof usersDb)[number][],
|
(user) => user.id === project.project_owner,
|
||||||
}
|
)! as (typeof usersDb)[number],
|
||||||
}
|
team: usersDb.filter((user) =>
|
||||||
|
project.team.includes(user.id),
|
||||||
|
) as (typeof usersDb)[number][],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const updateProject = async (project: (typeof projectsDb)[number]) => {
|
export const updateProject = async (project: (typeof projectsDb)[number]) => {
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
|
|
||||||
const index = projectsDb.findIndex((p) => p.id === project.id)
|
const index = projectsDb.findIndex((p) => p.id === project.id);
|
||||||
projectsDb[index] = project
|
projectsDb[index] = project;
|
||||||
|
|
||||||
return project
|
return project;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const removeProject = async (project: (typeof projectsDb)[number]) => {
|
export const removeProject = async (project: (typeof projectsDb)[number]) => {
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
|
|
||||||
const index = projectsDb.findIndex((p) => p.id === project.id)
|
const index = projectsDb.findIndex((p) => p.id === project.id);
|
||||||
projectsDb.splice(index, 1)
|
projectsDb.splice(index, 1);
|
||||||
|
|
||||||
return project
|
return project;
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { sleep } from '../../services/utils'
|
import { sleep } from "../../services/utils";
|
||||||
import { User } from './../../pages/users/types'
|
import { User } from "./../../pages/users/types";
|
||||||
import usersDb from './users-db.json'
|
import usersDb from "./users-db.json";
|
||||||
import projectsDb from './projects-db.json'
|
import projectsDb from "./projects-db.json";
|
||||||
import { Project } from '../../pages/projects/types'
|
import { Project } from "../../pages/projects/types";
|
||||||
|
|
||||||
export const users = usersDb as User[]
|
export const users = usersDb as User[];
|
||||||
|
|
||||||
const getUserProjects = (userId: number | string) => {
|
const getUserProjects = (userId: number | string) => {
|
||||||
return projectsDb
|
return projectsDb
|
||||||
|
|
@ -12,65 +12,74 @@ const getUserProjects = (userId: number | string) => {
|
||||||
.map((project) => ({
|
.map((project) => ({
|
||||||
...project,
|
...project,
|
||||||
project_owner: users.find((user) => user.id === project.project_owner)!,
|
project_owner: users.find((user) => user.id === project.project_owner)!,
|
||||||
team: project.team.map((userId) => users.find((user) => user.id === userId)!),
|
team: project.team.map(
|
||||||
status: project.status as Project['status'],
|
(userId) => users.find((user) => user.id === userId)!,
|
||||||
}))
|
),
|
||||||
}
|
status: project.status as Project["status"],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
// Simulate API calls
|
// Simulate API calls
|
||||||
|
|
||||||
export type Pagination = {
|
export type Pagination = {
|
||||||
page: number
|
page: number;
|
||||||
perPage: number
|
perPage: number;
|
||||||
total: number
|
total: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Sorting = {
|
export type Sorting = {
|
||||||
sortBy: keyof User | undefined
|
sortBy: keyof User | undefined;
|
||||||
sortingOrder: 'asc' | 'desc' | null
|
sortingOrder: "asc" | "desc" | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Filters = {
|
export type Filters = {
|
||||||
isActive: boolean
|
isActive: boolean;
|
||||||
search: string
|
search: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const getSortItem = (obj: any, sortBy: string) => {
|
const getSortItem = (obj: any, sortBy: string) => {
|
||||||
if (sortBy === 'projects') {
|
if (sortBy === "projects") {
|
||||||
return obj.projects.map((project: any) => project.project_name).join(', ')
|
return obj.projects.map((project: any) => project.project_name).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj[sortBy]
|
return obj[sortBy];
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getUsers = async (filters: Partial<Filters & Pagination & Sorting>) => {
|
export const getUsers = async (
|
||||||
await sleep(1000)
|
filters: Partial<Filters & Pagination & Sorting>,
|
||||||
const { isActive, search, sortBy, sortingOrder } = filters
|
) => {
|
||||||
let filteredUsers = users
|
await sleep(1000);
|
||||||
|
const { isActive, search, sortBy, sortingOrder } = filters;
|
||||||
|
let filteredUsers = users;
|
||||||
|
|
||||||
filteredUsers = filteredUsers.filter((user) => user.active === isActive)
|
filteredUsers = filteredUsers.filter((user) => user.active === isActive);
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
filteredUsers = filteredUsers.filter((user) => user.fullname.toLowerCase().includes(search.toLowerCase()))
|
filteredUsers = filteredUsers.filter((user) =>
|
||||||
|
user.fullname.toLowerCase().includes(search.toLowerCase()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredUsers = filteredUsers.map((user) => ({ ...user, projects: getUserProjects(user.id) }))
|
filteredUsers = filteredUsers.map((user) => ({
|
||||||
|
...user,
|
||||||
|
projects: getUserProjects(user.id),
|
||||||
|
}));
|
||||||
|
|
||||||
if (sortBy && sortingOrder) {
|
if (sortBy && sortingOrder) {
|
||||||
filteredUsers = filteredUsers.sort((a, b) => {
|
filteredUsers = filteredUsers.sort((a, b) => {
|
||||||
const first = getSortItem(a, sortBy)
|
const first = getSortItem(a, sortBy);
|
||||||
const second = getSortItem(b, sortBy)
|
const second = getSortItem(b, sortBy);
|
||||||
if (first > second) {
|
if (first > second) {
|
||||||
return sortingOrder === 'asc' ? 1 : -1
|
return sortingOrder === "asc" ? 1 : -1;
|
||||||
}
|
}
|
||||||
if (first < second) {
|
if (first < second) {
|
||||||
return sortingOrder === 'asc' ? -1 : 1
|
return sortingOrder === "asc" ? -1 : 1;
|
||||||
}
|
}
|
||||||
return 0
|
return 0;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { page = 1, perPage = 10 } = filters || {}
|
const { page = 1, perPage = 10 } = filters || {};
|
||||||
return {
|
return {
|
||||||
data: filteredUsers.slice((page - 1) * perPage, page * perPage),
|
data: filteredUsers.slice((page - 1) * perPage, page * perPage),
|
||||||
pagination: {
|
pagination: {
|
||||||
|
|
@ -78,24 +87,24 @@ export const getUsers = async (filters: Partial<Filters & Pagination & Sorting>)
|
||||||
perPage,
|
perPage,
|
||||||
total: filteredUsers.length,
|
total: filteredUsers.length,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const addUser = async (user: User) => {
|
export const addUser = async (user: User) => {
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
users.unshift(user)
|
users.unshift(user);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const updateUser = async (user: User) => {
|
export const updateUser = async (user: User) => {
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
const index = users.findIndex((u) => u.id === user.id)
|
const index = users.findIndex((u) => u.id === user.id);
|
||||||
users[index] = user
|
users[index] = user;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const removeUser = async (user: User) => {
|
export const removeUser = async (user: User) => {
|
||||||
await sleep(1000)
|
await sleep(1000);
|
||||||
users.splice(
|
users.splice(
|
||||||
users.findIndex((u) => u.id === user.id),
|
users.findIndex((u) => u.id === user.id),
|
||||||
1,
|
1,
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
import type { ChartData } from 'chart.js'
|
import type { ChartData } from "chart.js";
|
||||||
|
|
||||||
export type ColorThemes = {
|
export type ColorThemes = {
|
||||||
[key: string]: string
|
[key: string]: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type TLineChartData = ChartData<'line', any, any>
|
export type TLineChartData = ChartData<"line", any, any>;
|
||||||
export type TBarChartData = ChartData<'bar', any, any>
|
export type TBarChartData = ChartData<"bar", any, any>;
|
||||||
export type TBubbleChartData = ChartData<'bubble', any, any>
|
export type TBubbleChartData = ChartData<"bubble", any, any>;
|
||||||
export type TDoughnutChartData = ChartData<'doughnut', any, any>
|
export type TDoughnutChartData = ChartData<"doughnut", any, any>;
|
||||||
export type TPieChartData = ChartData<'pie', any, any>
|
export type TPieChartData = ChartData<"pie", any, any>;
|
||||||
|
|
||||||
export type TChartData = TLineChartData | TBarChartData | TBubbleChartData | TDoughnutChartData | TPieChartData
|
export type TChartData =
|
||||||
|
| TLineChartData
|
||||||
|
| TBarChartData
|
||||||
|
| TBubbleChartData
|
||||||
|
| TDoughnutChartData
|
||||||
|
| TPieChartData;
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,28 @@
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from "vue-i18n";
|
||||||
|
|
||||||
const fileNameToLocaleModuleDict = import.meta.glob<{ default: Record<string, string> }>('./locales/*.json', {
|
const fileNameToLocaleModuleDict = import.meta.glob<{
|
||||||
|
default: Record<string, string>;
|
||||||
|
}>("./locales/*.json", {
|
||||||
eager: true,
|
eager: true,
|
||||||
})
|
});
|
||||||
|
|
||||||
const messages: { [P: string]: Record<string, string> } = {}
|
const messages: { [P: string]: Record<string, string> } = {};
|
||||||
Object.entries(fileNameToLocaleModuleDict)
|
Object.entries(fileNameToLocaleModuleDict)
|
||||||
.map(([fileName, localeModule]) => {
|
.map(([fileName, localeModule]) => {
|
||||||
const fileNameParts = fileName.split('/')
|
const fileNameParts = fileName.split("/");
|
||||||
const fileNameWithoutPath = fileNameParts[fileNameParts.length - 1]
|
const fileNameWithoutPath = fileNameParts[fileNameParts.length - 1];
|
||||||
const localeName = fileNameWithoutPath.split('.json')[0]
|
const localeName = fileNameWithoutPath.split(".json")[0];
|
||||||
|
|
||||||
return [localeName, localeModule.default] as const
|
return [localeName, localeModule.default] as const;
|
||||||
})
|
})
|
||||||
.forEach((localeNameLocaleMessagesTuple) => {
|
.forEach((localeNameLocaleMessagesTuple) => {
|
||||||
messages[localeNameLocaleMessagesTuple[0]] = localeNameLocaleMessagesTuple[1]
|
messages[localeNameLocaleMessagesTuple[0]] =
|
||||||
})
|
localeNameLocaleMessagesTuple[1];
|
||||||
|
});
|
||||||
|
|
||||||
export default createI18n({
|
export default createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: 'ru',
|
locale: "ru",
|
||||||
fallbackLocale: 'ru',
|
fallbackLocale: "ru",
|
||||||
messages,
|
messages,
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,178 +1,177 @@
|
||||||
{
|
{
|
||||||
"auth": {
|
"auth": {
|
||||||
"agree": "Я согласен",
|
"agree": "Я согласен",
|
||||||
"createAccount": "Создать аккаунт",
|
"createAccount": "Создать аккаунт",
|
||||||
"createNewAccount": "Создать новый аккаунт",
|
"createNewAccount": "Создать новый аккаунт",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"login": "Логин",
|
"login": "Логин",
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
"recover_password": "Восстановить пароль",
|
"recover_password": "Восстановить пароль",
|
||||||
"sign_up": "Регистрация",
|
"sign_up": "Регистрация",
|
||||||
"keep_logged_in": "Запомнить",
|
"keep_logged_in": "Запомнить",
|
||||||
"termsOfUse": "Terms of Use.",
|
"termsOfUse": "Terms of Use.",
|
||||||
"reset_password": "Сбросить пароль"
|
"reset_password": "Сбросить пароль"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"title": "Эта страница последняя.",
|
||||||
|
"text": "Если вы счиате, что это ошибка, напишите нам ",
|
||||||
|
"back_button": "На главную"
|
||||||
|
},
|
||||||
|
"typography": {
|
||||||
|
"primary": "Primary text styles",
|
||||||
|
"secondary": "Secondary text styles"
|
||||||
|
},
|
||||||
|
"dashboard": {
|
||||||
|
"versions": "Versions",
|
||||||
|
"setupRemoteConnections": "Setup Remote Connections",
|
||||||
|
"currentVisitors": "Current Visitors",
|
||||||
|
"navigationLayout": "navigation layout",
|
||||||
|
"topBarButton": "Top Bar",
|
||||||
|
"sideBarButton": "Side Bar"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"brazilian_portuguese": "Português",
|
||||||
|
"english": "English",
|
||||||
|
"spanish": "Spanish",
|
||||||
|
"simplified_chinese": "Simplified Chinese",
|
||||||
|
"persian": "Persian"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"auth": "Auth",
|
||||||
|
"buttons": "Buttons",
|
||||||
|
"timelines": "Timelines",
|
||||||
|
"dashboard": "Статистика",
|
||||||
|
"billing": "Billing",
|
||||||
|
"login": "Login",
|
||||||
|
"preferences": "Настройки Аккаунта",
|
||||||
|
"payments": "Payments",
|
||||||
|
"settings": "Application settings",
|
||||||
|
"pricing-plans": "Pricing plans",
|
||||||
|
"payment-methods": "Payment methods",
|
||||||
|
"signup": "Signup",
|
||||||
|
"recover-password": "Recover password",
|
||||||
|
"recover-password-email": "Recover password email",
|
||||||
|
"404": "404",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"users": "Users",
|
||||||
|
"projects": "Projects"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"all": "See all messages",
|
||||||
|
"new": "New messages from {name}",
|
||||||
|
"mark_as_read": "Mark As Read"
|
||||||
|
},
|
||||||
|
"navbar": {
|
||||||
|
"messageUs": "Web development inquiries:",
|
||||||
|
"repository": "GitHub Repo"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"all": "See all notifications",
|
||||||
|
"less": "See less notifications",
|
||||||
|
"mark_as_read": "Mark as read",
|
||||||
|
"sentMessage": "sent you a message",
|
||||||
|
"uploadedZip": "uploaded a new Zip file with {type}",
|
||||||
|
"startedTopic": "started a new topic"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"language": "Change language",
|
||||||
|
"logout": "Выход",
|
||||||
|
"profile": "Профиль",
|
||||||
|
"settings": "Настройки",
|
||||||
|
"billing": "Платежы",
|
||||||
|
"faq": "FAQ",
|
||||||
|
"helpAndSupport": "Помощь",
|
||||||
|
"projects": "Projects",
|
||||||
|
"account": "Аккаунт",
|
||||||
|
"explore": "Explore"
|
||||||
|
},
|
||||||
|
"treeView": {
|
||||||
|
"basic": "Basic",
|
||||||
|
"icons": "Icons",
|
||||||
|
"selectable": "Selectable",
|
||||||
|
"editable": "Editable",
|
||||||
|
"advanced": "Advanced"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "Chat",
|
||||||
|
"sendButton": "Send"
|
||||||
|
},
|
||||||
|
"spacingPlayground": {
|
||||||
|
"value": "Value",
|
||||||
|
"margin": "Margin",
|
||||||
|
"padding": "Padding"
|
||||||
|
},
|
||||||
|
"spacing": {
|
||||||
|
"title": "Spacing"
|
||||||
|
},
|
||||||
|
"cards": {
|
||||||
|
"cards": "Cards",
|
||||||
|
"fixed": "Fixed",
|
||||||
|
"floating": "Floating",
|
||||||
|
"contentText": "The unique stripes of zebras make them one of the animals most familiar to people.",
|
||||||
|
"contentTextLong": "The unique stripes of zebras make them one of the animals most familiar to people. They occur in a variety of habitats, such as grasslands, savannas, woodlands, thorny scrublands, mountains, and coastal hills. Various anthropogenic factors have had a severe impact on zebra populations, in particular hunting for skins and habitat destruction. Grévy's zebra and the mountain zebra are endangered. While plains zebras are much more plentiful, one subspecies, the quagga.",
|
||||||
|
"rowHeight": "Row height",
|
||||||
|
"title": {
|
||||||
|
"default": "Default",
|
||||||
|
"withControls": "With controls",
|
||||||
|
"customHeader": "Custom header",
|
||||||
|
"withoutHeader": "Without header",
|
||||||
|
"withImage": "With Image",
|
||||||
|
"withTitleOnImage": "With title on image",
|
||||||
|
"withCustomTitleOnImage": "With custom title on image",
|
||||||
|
"withStripe": "With stripe",
|
||||||
|
"withBackground": "With background"
|
||||||
},
|
},
|
||||||
"404": {
|
"button": {
|
||||||
"title": "Эта страница последняя.",
|
"main": "Main",
|
||||||
"text": "Если вы счиате, что это ошибка, напишите нам ",
|
"cancel": "Cancel",
|
||||||
"back_button": "На главную"
|
"showMore": "Show More",
|
||||||
|
"readMore": "Show More"
|
||||||
},
|
},
|
||||||
"typography": {
|
"link": {
|
||||||
"primary": "Primary text styles",
|
"edit": "Edit",
|
||||||
"secondary": "Secondary text styles"
|
"setAsDefault": "Set as default",
|
||||||
},
|
"delete": "Delete",
|
||||||
"dashboard": {
|
"traveling": "Traveling",
|
||||||
"versions": "Versions",
|
"france": "France",
|
||||||
"setupRemoteConnections": "Setup Remote Connections",
|
"review": "Review",
|
||||||
"currentVisitors": "Current Visitors",
|
"feedback": "Leave feedback",
|
||||||
"navigationLayout": "navigation layout",
|
"readFull": "Read full article",
|
||||||
"topBarButton": "Top Bar",
|
"secondaryAction": "Secondary action",
|
||||||
"sideBarButton": "Side Bar"
|
"action1": "Action 1",
|
||||||
},
|
"action2": "Action 2"
|
||||||
"language": {
|
|
||||||
"brazilian_portuguese": "Português",
|
|
||||||
"english": "English",
|
|
||||||
"spanish": "Spanish",
|
|
||||||
"simplified_chinese": "Simplified Chinese",
|
|
||||||
"persian": "Persian"
|
|
||||||
},
|
|
||||||
"menu": {
|
|
||||||
"auth": "Auth",
|
|
||||||
"buttons": "Buttons",
|
|
||||||
"timelines": "Timelines",
|
|
||||||
"dashboard": "Dashboard",
|
|
||||||
"billing": "Billing",
|
|
||||||
"login": "Login",
|
|
||||||
"preferences": "Account preferences",
|
|
||||||
"payments": "Payments",
|
|
||||||
"settings": "Application settings",
|
|
||||||
"pricing-plans": "Pricing plans",
|
|
||||||
"payment-methods": "Payment methods",
|
|
||||||
"signup": "Signup",
|
|
||||||
"recover-password": "Recover password",
|
|
||||||
"recover-password-email": "Recover password email",
|
|
||||||
"404": "404",
|
|
||||||
"faq": "FAQ",
|
|
||||||
"users": "Users",
|
|
||||||
"projects": "Projects"
|
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"all": "See all messages",
|
|
||||||
"new": "New messages from {name}",
|
|
||||||
"mark_as_read": "Mark As Read"
|
|
||||||
},
|
|
||||||
"navbar": {
|
|
||||||
"messageUs": "Web development inquiries:",
|
|
||||||
"repository": "GitHub Repo"
|
|
||||||
},
|
|
||||||
"notifications": {
|
|
||||||
"all": "See all notifications",
|
|
||||||
"less": "See less notifications",
|
|
||||||
"mark_as_read": "Mark as read",
|
|
||||||
"sentMessage": "sent you a message",
|
|
||||||
"uploadedZip": "uploaded a new Zip file with {type}",
|
|
||||||
"startedTopic": "started a new topic"
|
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"language": "Change language",
|
|
||||||
"logout": "Logout",
|
|
||||||
"profile": "Profile",
|
|
||||||
"settings": "Settings",
|
|
||||||
"billing": "Billing",
|
|
||||||
"faq": "FAQ",
|
|
||||||
"helpAndSupport": "Help & support",
|
|
||||||
"projects": "Projects",
|
|
||||||
"account": "Account",
|
|
||||||
"explore": "Explore"
|
|
||||||
},
|
|
||||||
"treeView": {
|
|
||||||
"basic": "Basic",
|
|
||||||
"icons": "Icons",
|
|
||||||
"selectable": "Selectable",
|
|
||||||
"editable": "Editable",
|
|
||||||
"advanced": "Advanced"
|
|
||||||
},
|
|
||||||
"chat": {
|
|
||||||
"title": "Chat",
|
|
||||||
"sendButton": "Send"
|
|
||||||
},
|
|
||||||
"spacingPlayground": {
|
|
||||||
"value": "Value",
|
|
||||||
"margin": "Margin",
|
|
||||||
"padding": "Padding"
|
|
||||||
},
|
|
||||||
"spacing": {
|
|
||||||
"title": "Spacing"
|
|
||||||
},
|
|
||||||
"cards": {
|
|
||||||
"cards": "Cards",
|
|
||||||
"fixed": "Fixed",
|
|
||||||
"floating": "Floating",
|
|
||||||
"contentText": "The unique stripes of zebras make them one of the animals most familiar to people.",
|
|
||||||
"contentTextLong": "The unique stripes of zebras make them one of the animals most familiar to people. They occur in a variety of habitats, such as grasslands, savannas, woodlands, thorny scrublands, mountains, and coastal hills. Various anthropogenic factors have had a severe impact on zebra populations, in particular hunting for skins and habitat destruction. Grévy's zebra and the mountain zebra are endangered. While plains zebras are much more plentiful, one subspecies, the quagga.",
|
|
||||||
"rowHeight": "Row height",
|
|
||||||
"title": {
|
|
||||||
"default": "Default",
|
|
||||||
"withControls": "With controls",
|
|
||||||
"customHeader": "Custom header",
|
|
||||||
"withoutHeader": "Without header",
|
|
||||||
"withImage": "With Image",
|
|
||||||
"withTitleOnImage": "With title on image",
|
|
||||||
"withCustomTitleOnImage": "With custom title on image",
|
|
||||||
"withStripe": "With stripe",
|
|
||||||
"withBackground": "With background"
|
|
||||||
},
|
|
||||||
"button": {
|
|
||||||
"main": "Main",
|
|
||||||
"cancel": "Cancel",
|
|
||||||
"showMore": "Show More",
|
|
||||||
"readMore": "Show More"
|
|
||||||
},
|
|
||||||
"link": {
|
|
||||||
"edit": "Edit",
|
|
||||||
"setAsDefault": "Set as default",
|
|
||||||
"delete": "Delete",
|
|
||||||
"traveling": "Traveling",
|
|
||||||
"france": "France",
|
|
||||||
"review": "Review",
|
|
||||||
"feedback": "Leave feedback",
|
|
||||||
"readFull": "Read full article",
|
|
||||||
"secondaryAction": "Secondary action",
|
|
||||||
"action1": "Action 1",
|
|
||||||
"action2": "Action 2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"colors": {
|
|
||||||
"themeColors": "Theme Colors",
|
|
||||||
"extraColors": "Extra Colors",
|
|
||||||
"gradients": {
|
|
||||||
"basic": {
|
|
||||||
"title": "Button Gradients"
|
|
||||||
},
|
|
||||||
"hovered": {
|
|
||||||
"title": "Hovered Button Gradients",
|
|
||||||
"text": "Lighten 15% applied to an original style (gradient or flat color) for hover state."
|
|
||||||
},
|
|
||||||
"pressed": {
|
|
||||||
"title": "Pressed Button Gradients",
|
|
||||||
"text": "Darken 15% applied to an original style (gradient or flat color) for pressed state."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tabs": {
|
|
||||||
"alignment": "Tabs alignment",
|
|
||||||
"overflow": "Tabs overflow",
|
|
||||||
"hidden": "Tabs with hidden slider",
|
|
||||||
"grow": "Tabs grow"
|
|
||||||
},
|
|
||||||
"helpAndSupport": "Help & support",
|
|
||||||
"aboutVuesticAdmin": "About Vuestic Admin",
|
|
||||||
"search": {
|
|
||||||
"placeholder": "Search..."
|
|
||||||
},
|
|
||||||
"buttonSelect": {
|
|
||||||
"dark": "Dark",
|
|
||||||
"light": "Light"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"themeColors": "Theme Colors",
|
||||||
|
"extraColors": "Extra Colors",
|
||||||
|
"gradients": {
|
||||||
|
"basic": {
|
||||||
|
"title": "Button Gradients"
|
||||||
|
},
|
||||||
|
"hovered": {
|
||||||
|
"title": "Hovered Button Gradients",
|
||||||
|
"text": "Lighten 15% applied to an original style (gradient or flat color) for hover state."
|
||||||
|
},
|
||||||
|
"pressed": {
|
||||||
|
"title": "Pressed Button Gradients",
|
||||||
|
"text": "Darken 15% applied to an original style (gradient or flat color) for pressed state."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tabs": {
|
||||||
|
"alignment": "Tabs alignment",
|
||||||
|
"overflow": "Tabs overflow",
|
||||||
|
"hidden": "Tabs with hidden slider",
|
||||||
|
"grow": "Tabs grow"
|
||||||
|
},
|
||||||
|
"helpAndSupport": "Help & support",
|
||||||
|
"aboutVuesticAdmin": "About Vuestic Admin",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Search..."
|
||||||
|
},
|
||||||
|
"buttonSelect": {
|
||||||
|
"dark": "Dark",
|
||||||
|
"light": "Light"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<VaLayout
|
<VaLayout
|
||||||
:top="{ fixed: true, order: 2 }"
|
:top="{ fixed: true, order: 2 }"
|
||||||
:left="{ fixed: true, absolute: breakpoints.mdDown, order: 1, overlay: breakpoints.mdDown && !isSidebarMinimized }"
|
:left="{
|
||||||
|
fixed: true,
|
||||||
|
absolute: breakpoints.mdDown,
|
||||||
|
order: 1,
|
||||||
|
overlay: breakpoints.mdDown && !isSidebarMinimized,
|
||||||
|
}"
|
||||||
@leftOverlayClick="isSidebarMinimized = true"
|
@leftOverlayClick="isSidebarMinimized = true"
|
||||||
>
|
>
|
||||||
<template #top>
|
<template #top>
|
||||||
|
|
@ -9,13 +14,25 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #left>
|
<template #left>
|
||||||
<AppSidebar :minimized="isSidebarMinimized" :animated="!isMobile" :mobile="isMobile" />
|
<AppSidebar
|
||||||
|
:minimized="isSidebarMinimized"
|
||||||
|
:animated="!isMobile"
|
||||||
|
:mobile="isMobile"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #content>
|
<template #content>
|
||||||
<div :class="{ minimized: isSidebarMinimized }" class="app-layout__sidebar-wrapper">
|
<div
|
||||||
|
:class="{ minimized: isSidebarMinimized }"
|
||||||
|
class="app-layout__sidebar-wrapper"
|
||||||
|
>
|
||||||
<div v-if="isFullScreenSidebar" class="flex justify-end">
|
<div v-if="isFullScreenSidebar" class="flex justify-end">
|
||||||
<VaButton class="px-4 py-4" icon="md_close" preset="plain" @click="onCloseSidebarButtonClick" />
|
<VaButton
|
||||||
|
class="px-4 py-4"
|
||||||
|
icon="md_close"
|
||||||
|
preset="plain"
|
||||||
|
@click="onCloseSidebarButtonClick"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AppLayoutNavigation v-if="!isMobile" class="p-4" />
|
<AppLayoutNavigation v-if="!isMobile" class="p-4" />
|
||||||
|
|
@ -29,57 +46,59 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeUnmount, onMounted, ref, computed } from 'vue'
|
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from "pinia";
|
||||||
import { onBeforeRouteUpdate } from 'vue-router'
|
import { onBeforeRouteUpdate } from "vue-router";
|
||||||
import { useBreakpoint } from 'vuestic-ui'
|
import { useBreakpoint } from "vuestic-ui";
|
||||||
|
|
||||||
import { useGlobalStore } from '../stores/global-store'
|
import { useGlobalStore } from "../stores/global-store";
|
||||||
|
|
||||||
import AppLayoutNavigation from '../components/app-layout-navigation/AppLayoutNavigation.vue'
|
import AppLayoutNavigation from "../components/app-layout-navigation/AppLayoutNavigation.vue";
|
||||||
import AppNavbar from '../components/navbar/AppNavbar.vue'
|
import AppNavbar from "../components/navbar/AppNavbar.vue";
|
||||||
import AppSidebar from '../components/sidebar/AppSidebar.vue'
|
import AppSidebar from "../components/sidebar/AppSidebar.vue";
|
||||||
|
|
||||||
const GlobalStore = useGlobalStore()
|
const GlobalStore = useGlobalStore();
|
||||||
|
|
||||||
const breakpoints = useBreakpoint()
|
const breakpoints = useBreakpoint();
|
||||||
|
|
||||||
const sidebarWidth = ref('16rem')
|
const sidebarWidth = ref("16rem");
|
||||||
const sidebarMinimizedWidth = ref(undefined)
|
const sidebarMinimizedWidth = ref(undefined);
|
||||||
|
|
||||||
const isMobile = ref(false)
|
const isMobile = ref(false);
|
||||||
const isTablet = ref(false)
|
const isTablet = ref(false);
|
||||||
const { isSidebarMinimized } = storeToRefs(GlobalStore)
|
const { isSidebarMinimized } = storeToRefs(GlobalStore);
|
||||||
|
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
isSidebarMinimized.value = breakpoints.mdDown
|
isSidebarMinimized.value = breakpoints.mdDown;
|
||||||
isMobile.value = breakpoints.smDown
|
isMobile.value = breakpoints.smDown;
|
||||||
isTablet.value = breakpoints.mdDown
|
isTablet.value = breakpoints.mdDown;
|
||||||
sidebarMinimizedWidth.value = isMobile.value ? '0' : '4.5rem'
|
sidebarMinimizedWidth.value = isMobile.value ? "0" : "4.5rem";
|
||||||
sidebarWidth.value = isTablet.value ? '100%' : '16rem'
|
sidebarWidth.value = isTablet.value ? "100%" : "16rem";
|
||||||
}
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', onResize)
|
window.addEventListener("resize", onResize);
|
||||||
onResize()
|
onResize();
|
||||||
})
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('resize', onResize)
|
window.removeEventListener("resize", onResize);
|
||||||
})
|
});
|
||||||
|
|
||||||
onBeforeRouteUpdate(() => {
|
onBeforeRouteUpdate(() => {
|
||||||
if (breakpoints.mdDown) {
|
if (breakpoints.mdDown) {
|
||||||
// Collapse sidebar after route change for Mobile
|
// Collapse sidebar after route change for Mobile
|
||||||
isSidebarMinimized.value = true
|
isSidebarMinimized.value = true;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const isFullScreenSidebar = computed(() => isTablet.value && !isSidebarMinimized.value)
|
const isFullScreenSidebar = computed(
|
||||||
|
() => isTablet.value && !isSidebarMinimized.value,
|
||||||
|
);
|
||||||
|
|
||||||
const onCloseSidebarButtonClick = () => {
|
const onCloseSidebarButtonClick = () => {
|
||||||
isSidebarMinimized.value = true
|
isSidebarMinimized.value = true;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<VaLayout v-if="breakpoint.lgUp" class="h-screen bg-[var(--va-background-secondary)]">
|
<VaLayout
|
||||||
|
v-if="breakpoint.lgUp"
|
||||||
|
class="h-screen bg-[var(--va-background-secondary)]"
|
||||||
|
>
|
||||||
<template #left>
|
<template #left>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
class="bg-primary h-full flex items-center justify-center"
|
class="bg-primary h-full flex items-center justify-center"
|
||||||
|
|
@ -11,7 +14,9 @@
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<main class="h-full flex items-center justify-center mx-auto max-w-[420px]">
|
<main
|
||||||
|
class="h-full flex items-center justify-center mx-auto max-w-[420px]"
|
||||||
|
>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -20,7 +25,9 @@
|
||||||
<VaLayout v-else class="h-screen bg-[var(--va-background-secondary)]">
|
<VaLayout v-else class="h-screen bg-[var(--va-background-secondary)]">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<main class="h-full flex flex-row items-center justify-start mx-auto max-w-[420px]">
|
<main
|
||||||
|
class="h-full flex flex-row items-center justify-start mx-auto max-w-[420px]"
|
||||||
|
>
|
||||||
<div class="flex flex-col items-start">
|
<div class="flex flex-col items-start">
|
||||||
<RouterLink class="py-4" to="/" aria-label="Visit homepage">
|
<RouterLink class="py-4" to="/" aria-label="Visit homepage">
|
||||||
<VuesticLogo class="mb-2" start="#0E41C9" />
|
<VuesticLogo class="mb-2" start="#0E41C9" />
|
||||||
|
|
@ -34,8 +41,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useBreakpoint } from 'vuestic-ui'
|
import { useBreakpoint } from "vuestic-ui";
|
||||||
import VuesticLogo from '../components/VuesticLogo.vue'
|
import VuesticLogo from "../components/VuesticLogo.vue";
|
||||||
|
|
||||||
const breakpoint = useBreakpoint()
|
const breakpoint = useBreakpoint();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
30
src/main.ts
30
src/main.ts
|
|
@ -1,19 +1,19 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
import i18n from './i18n'
|
import i18n from "./i18n";
|
||||||
import { createVuestic } from 'vuestic-ui'
|
import { createVuestic } from "vuestic-ui";
|
||||||
import { createGtm } from '@gtm-support/vue-gtm'
|
import { createGtm } from "@gtm-support/vue-gtm";
|
||||||
|
|
||||||
import stores from './stores'
|
import stores from "./stores";
|
||||||
import router from './router'
|
import router from "./router";
|
||||||
import vuesticGlobalConfig from './services/vuestic-ui/global-config'
|
import vuesticGlobalConfig from "./services/vuestic-ui/global-config";
|
||||||
import App from './App.vue'
|
import App from "./App.vue";
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(stores)
|
app.use(stores);
|
||||||
app.use(router)
|
app.use(router);
|
||||||
app.use(i18n)
|
app.use(i18n);
|
||||||
app.use(createVuestic({ config: vuesticGlobalConfig }))
|
app.use(createVuestic({ config: vuesticGlobalConfig }));
|
||||||
|
|
||||||
if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
||||||
app.use(
|
app.use(
|
||||||
|
|
@ -22,7 +22,7 @@ if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
||||||
debug: false,
|
debug: false,
|
||||||
vueRouter: router,
|
vueRouter: router,
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount("#app");
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import VuesticLogo from '../components/VuesticLogo.vue'
|
import VuesticLogo from "../components/VuesticLogo.vue";
|
||||||
import NotFoundImage from '../components/NotFoundImage.vue'
|
import NotFoundImage from "../components/NotFoundImage.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-between h-screen items-center bg-[var(--va-background-secondary)]">
|
<div
|
||||||
|
class="flex flex-col justify-between h-screen items-center bg-[var(--va-background-secondary)]"
|
||||||
|
>
|
||||||
<RouterLink to="/">
|
<RouterLink to="/">
|
||||||
<VuesticLogo :gradient="false" class="my-8 h-5" />
|
<VuesticLogo :gradient="false" class="my-8 h-5" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
@ -14,12 +16,16 @@ import NotFoundImage from '../components/NotFoundImage.vue'
|
||||||
<h1 class="va-h1 text-center sm:text-5xl text-4xl">Page not found</h1>
|
<h1 class="va-h1 text-center sm:text-5xl text-4xl">Page not found</h1>
|
||||||
|
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
The page you are looking for might have been removed had its name changed or is temporarily unavailable.
|
The page you are looking for might have been removed had its name
|
||||||
|
changed or is temporarily unavailable.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row gap-4">
|
<div class="flex flex-col sm:flex-row gap-4">
|
||||||
<VaButton to="/">Go to homepage</VaButton>
|
<VaButton to="/">Go to homepage</VaButton>
|
||||||
<VaButton href="https://github.com/epicmaxco/vuestic-admin/issues/new" preset="secondary" target="_blank"
|
<VaButton
|
||||||
|
href="https://github.com/epicmaxco/vuestic-admin/issues/new"
|
||||||
|
preset="secondary"
|
||||||
|
target="_blank"
|
||||||
>Create a GitHub issue
|
>Create a GitHub issue
|
||||||
</VaButton>
|
</VaButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,32 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import RevenueUpdates from './cards/RevenueReport.vue'
|
import RevenueUpdates from "./cards/RevenueReport.vue";
|
||||||
import ProjectTable from './cards/ProjectTable.vue'
|
import ProjectTable from "./cards/ProjectTable.vue";
|
||||||
import RevenueByLocationMap from './cards/RevenueByLocationMap.vue'
|
import RevenueByLocationMap from "./cards/RevenueByLocationMap.vue";
|
||||||
import DataSection from './DataSection.vue'
|
import DataSection from "./DataSection.vue";
|
||||||
import YearlyBreakup from './cards/YearlyBreakup.vue'
|
import YearlyBreakup from "./cards/YearlyBreakup.vue";
|
||||||
import MonthlyEarnings from './cards/MonthlyEarnings.vue'
|
import MonthlyEarnings from "./cards/MonthlyEarnings.vue";
|
||||||
import RegionRevenue from './cards/RegionRevenue.vue'
|
import RegionRevenue from "./cards/RegionRevenue.vue";
|
||||||
import Timeline from './cards/Timeline.vue'
|
import Timeline from "./cards/Timeline.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<h1 class="page-title font-bold">Dashboard</h1>
|
<h1 class="page-title font-bold">Статистика</h1>
|
||||||
<section class="flex flex-col gap-4">
|
<section class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col sm:flex-row gap-4">
|
<!-- <div class="flex flex-col sm:flex-row gap-4">
|
||||||
<RevenueUpdates class="w-full sm:w-[70%]" />
|
<RevenueUpdates class="w-full sm:w-[70%]" />
|
||||||
<div class="flex flex-col gap-4 w-full sm:w-[30%]">
|
<div class="flex flex-col gap-4 w-full sm:w-[30%]">
|
||||||
<YearlyBreakup class="h-full" />
|
<YearlyBreakup class="h-full" />
|
||||||
<MonthlyEarnings />
|
<MonthlyEarnings />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
<DataSection />
|
<!-- <DataSection /> -->
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<!-- <div class="flex flex-col md:flex-row gap-4">
|
||||||
<RevenueByLocationMap class="w-full md:w-4/6" />
|
<RevenueByLocationMap class="w-full md:w-4/6" />
|
||||||
<RegionRevenue class="w-full md:w-2/6" />
|
<RegionRevenue class="w-full md:w-2/6" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<ProjectTable class="w-full md:w-1/2" />
|
<ProjectTable class="w-full md:w-1/2" />
|
||||||
<Timeline class="w-full md:w-1/2" />
|
<Timeline class="w-full md:w-1/2" />
|
||||||
</div>
|
</div> -->
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -18,63 +18,63 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import { useColors } from 'vuestic-ui'
|
import { useColors } from "vuestic-ui";
|
||||||
import DataSectionItem from './DataSectionItem.vue'
|
import DataSectionItem from "./DataSectionItem.vue";
|
||||||
|
|
||||||
interface DashboardMetric {
|
interface DashboardMetric {
|
||||||
id: string
|
id: string;
|
||||||
title: string
|
title: string;
|
||||||
value: string
|
value: string;
|
||||||
icon: string
|
icon: string;
|
||||||
changeText: string
|
changeText: string;
|
||||||
changeDirection: 'up' | 'down'
|
changeDirection: "up" | "down";
|
||||||
iconBackground: string
|
iconBackground: string;
|
||||||
iconColor: string
|
iconColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { getColor } = useColors()
|
const { getColor } = useColors();
|
||||||
|
|
||||||
const dashboardMetrics = computed<DashboardMetric[]>(() => [
|
const dashboardMetrics = computed<DashboardMetric[]>(() => [
|
||||||
{
|
{
|
||||||
id: 'openInvoices',
|
id: "openInvoices",
|
||||||
title: 'Open invoices',
|
title: "Open invoices",
|
||||||
value: '$35,548',
|
value: "$35,548",
|
||||||
icon: 'mso-attach_money',
|
icon: "mso-attach_money",
|
||||||
changeText: '$1, 450',
|
changeText: "$1, 450",
|
||||||
changeDirection: 'down',
|
changeDirection: "down",
|
||||||
iconBackground: getColor('success'),
|
iconBackground: getColor("success"),
|
||||||
iconColor: getColor('on-success'),
|
iconColor: getColor("on-success"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ongoingProjects',
|
id: "ongoingProjects",
|
||||||
title: 'Ongoing project',
|
title: "Ongoing project",
|
||||||
value: '15',
|
value: "15",
|
||||||
icon: 'mso-folder_open',
|
icon: "mso-folder_open",
|
||||||
changeText: '25.36%',
|
changeText: "25.36%",
|
||||||
changeDirection: 'up',
|
changeDirection: "up",
|
||||||
iconBackground: getColor('info'),
|
iconBackground: getColor("info"),
|
||||||
iconColor: getColor('on-info'),
|
iconColor: getColor("on-info"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'employees',
|
id: "employees",
|
||||||
title: 'Employees',
|
title: "Employees",
|
||||||
value: '25',
|
value: "25",
|
||||||
icon: 'mso-account_circle',
|
icon: "mso-account_circle",
|
||||||
changeText: '2.5%',
|
changeText: "2.5%",
|
||||||
changeDirection: 'up',
|
changeDirection: "up",
|
||||||
iconBackground: getColor('danger'),
|
iconBackground: getColor("danger"),
|
||||||
iconColor: getColor('on-danger'),
|
iconColor: getColor("on-danger"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'newProfit',
|
id: "newProfit",
|
||||||
title: 'New profit',
|
title: "New profit",
|
||||||
value: '27%',
|
value: "27%",
|
||||||
icon: 'mso-grade',
|
icon: "mso-grade",
|
||||||
changeText: '4%',
|
changeText: "4%",
|
||||||
changeDirection: 'up',
|
changeDirection: "up",
|
||||||
iconBackground: getColor('warning'),
|
iconBackground: getColor("warning"),
|
||||||
iconColor: getColor('on-warning'),
|
iconColor: getColor("on-warning"),
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -31,20 +31,20 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import { VaCard } from 'vuestic-ui'
|
import { VaCard } from "vuestic-ui";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string
|
title: string;
|
||||||
value: string | number
|
value: string | number;
|
||||||
changeText: string
|
changeText: string;
|
||||||
up: boolean
|
up: boolean;
|
||||||
iconBackground: string
|
iconBackground: string;
|
||||||
iconColor: string
|
iconColor: string;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const changeClass = computed(() => ({
|
const changeClass = computed(() => ({
|
||||||
'text-success': props.up,
|
"text-success": props.up,
|
||||||
'text-red-600': !props.up,
|
"text-red-600": !props.up,
|
||||||
}))
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<VaCard>
|
<VaCard>
|
||||||
<VaCardTitle>
|
<VaCardTitle>
|
||||||
<h1 class="card-title text-tag text-secondary font-bold uppercase">Monthly Earnings</h1>
|
<h1 class="card-title text-tag text-secondary font-bold uppercase">
|
||||||
|
Monthly Earnings
|
||||||
|
</h1>
|
||||||
</VaCardTitle>
|
</VaCardTitle>
|
||||||
<VaCardContent>
|
<VaCardContent>
|
||||||
<div class="p-1 bg-black rounded absolute right-4 top-4">
|
<div class="p-1 bg-black rounded absolute right-4 top-4">
|
||||||
|
|
@ -16,22 +18,27 @@
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<div class="w-full flex items-center">
|
<div class="w-full flex items-center">
|
||||||
<VaChart :data="chartData" class="h-24" type="line" :options="options" />
|
<VaChart
|
||||||
|
:data="chartData"
|
||||||
|
class="h-24"
|
||||||
|
type="line"
|
||||||
|
:options="options"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
</VaCard>
|
</VaCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VaCard } from 'vuestic-ui'
|
import { VaCard } from "vuestic-ui";
|
||||||
import VaChart from '../../../../components/va-charts/VaChart.vue'
|
import VaChart from "../../../../components/va-charts/VaChart.vue";
|
||||||
import { useChartData } from '../../../../data/charts/composables/useChartData'
|
import { useChartData } from "../../../../data/charts/composables/useChartData";
|
||||||
import { lineChartData } from '../../../../data/charts/lineChartData'
|
import { lineChartData } from "../../../../data/charts/lineChartData";
|
||||||
import { ChartOptions } from 'chart.js'
|
import { ChartOptions } from "chart.js";
|
||||||
|
|
||||||
const chartData = useChartData(lineChartData)
|
const chartData = useChartData(lineChartData);
|
||||||
|
|
||||||
const options: ChartOptions<'line'> = {
|
const options: ChartOptions<"line"> = {
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
display: false,
|
display: false,
|
||||||
|
|
@ -51,7 +58,7 @@ const options: ChartOptions<'line'> = {
|
||||||
},
|
},
|
||||||
interaction: {
|
interaction: {
|
||||||
intersect: false,
|
intersect: false,
|
||||||
mode: 'index',
|
mode: "index",
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
|
|
@ -61,5 +68,5 @@ const options: ChartOptions<'line'> = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,37 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineVaDataTableColumns } from 'vuestic-ui'
|
import { defineVaDataTableColumns } from "vuestic-ui";
|
||||||
import { Project } from '../../../projects/types'
|
import { Project } from "../../../projects/types";
|
||||||
import UserAvatar from '../../../users/widgets/UserAvatar.vue'
|
import UserAvatar from "../../../users/widgets/UserAvatar.vue";
|
||||||
import ProjectStatusBadge from '../../../projects/components/ProjectStatusBadge.vue'
|
import ProjectStatusBadge from "../../../projects/components/ProjectStatusBadge.vue";
|
||||||
import { useProjects } from '../../../projects/composables/useProjects'
|
import { useProjects } from "../../../projects/composables/useProjects";
|
||||||
import { Pagination } from '../../../../data/pages/projects'
|
import { Pagination } from "../../../../data/pages/projects";
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
|
|
||||||
const columns = defineVaDataTableColumns([
|
const columns = defineVaDataTableColumns([
|
||||||
{ label: 'Name', key: 'project_name', sortable: true },
|
{ label: "Name", key: "project_name", sortable: true },
|
||||||
{ label: 'Status', key: 'status', sortable: true },
|
{ label: "Status", key: "status", sortable: true },
|
||||||
{ label: 'Team', key: 'team', sortable: true },
|
{ label: "Team", key: "team", sortable: true },
|
||||||
])
|
]);
|
||||||
|
|
||||||
const pagination = ref<Pagination>({ page: 1, perPage: 5, total: 0 })
|
const pagination = ref<Pagination>({ page: 1, perPage: 5, total: 0 });
|
||||||
const { projects, isLoading, sorting } = useProjects({
|
const { projects, isLoading, sorting } = useProjects({
|
||||||
pagination,
|
pagination,
|
||||||
})
|
});
|
||||||
|
|
||||||
const avatarColor = (userName: string) => {
|
const avatarColor = (userName: string) => {
|
||||||
const colors = ['primary', '#FFD43A', '#ADFF00', '#262824', 'danger']
|
const colors = ["primary", "#FFD43A", "#ADFF00", "#262824", "danger"];
|
||||||
const index = userName.charCodeAt(0) % colors.length
|
const index = userName.charCodeAt(0) % colors.length;
|
||||||
return colors[index]
|
return colors[index];
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VaCard>
|
<VaCard>
|
||||||
<VaCardTitle class="flex items-start justify-between">
|
<VaCardTitle class="flex items-start justify-between">
|
||||||
<h1 class="card-title text-secondary font-bold uppercase">Projects</h1>
|
<h1 class="card-title text-secondary font-bold uppercase">Projects</h1>
|
||||||
<VaButton preset="primary" size="small" to="/projects">View all projects</VaButton>
|
<VaButton preset="primary" size="small" to="/projects"
|
||||||
|
>View all projects</VaButton
|
||||||
|
>
|
||||||
</VaCardTitle>
|
</VaCardTitle>
|
||||||
<VaCardContent>
|
<VaCardContent>
|
||||||
<div v-if="projects.length > 0">
|
<div v-if="projects.length > 0">
|
||||||
|
|
@ -70,7 +72,12 @@ const avatarColor = (userName: string) => {
|
||||||
</template>
|
</template>
|
||||||
</VaDataTable>
|
</VaDataTable>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="p-4 flex justify-center items-center text-[var(--va-secondary)]">No projects</div>
|
<div
|
||||||
|
v-else
|
||||||
|
class="p-4 flex justify-center items-center text-[var(--va-secondary)]"
|
||||||
|
>
|
||||||
|
No projects
|
||||||
|
</div>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
</VaCard>
|
</VaCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<VaCard>
|
<VaCard>
|
||||||
<VaCardTitle class="flex justify-between">
|
<VaCardTitle class="flex justify-between">
|
||||||
<h1 class="card-title text-secondary font-bold uppercase">Revenue by Top Regions</h1>
|
<h1 class="card-title text-secondary font-bold uppercase">
|
||||||
|
Revenue by Top Regions
|
||||||
|
</h1>
|
||||||
</VaCardTitle>
|
</VaCardTitle>
|
||||||
<VaCardContent class="flex flex-col gap-1">
|
<VaCardContent class="flex flex-col gap-1">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<VaButtonToggle v-model="selectedPeriod" :options="periods" color="background-element" size="small" />
|
<VaButtonToggle
|
||||||
|
v-model="selectedPeriod"
|
||||||
|
:options="periods"
|
||||||
|
color="background-element"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
|
||||||
<VaButton preset="primary" size="small" @click="exportAsCSV"> Export </VaButton>
|
<VaButton preset="primary" size="small" @click="exportAsCSV">
|
||||||
|
Export
|
||||||
|
</VaButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VaDataTable
|
<VaDataTable
|
||||||
|
|
@ -18,67 +27,72 @@
|
||||||
]"
|
]"
|
||||||
:items="data"
|
:items="data"
|
||||||
>
|
>
|
||||||
<template #cell(revenue)="{ rowData }"> ${{ rowData[`revenue${selectedPeriod}`] }} </template>
|
<template #cell(revenue)="{ rowData }">
|
||||||
|
${{ rowData[`revenue${selectedPeriod}`] }}
|
||||||
|
</template>
|
||||||
</VaDataTable>
|
</VaDataTable>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
</VaCard>
|
</VaCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
import { downloadAsCSV } from '../../../../services/toCSV'
|
import { downloadAsCSV } from "../../../../services/toCSV";
|
||||||
|
|
||||||
const selectedPeriod = ref('Today')
|
const selectedPeriod = ref("Today");
|
||||||
const periods = ['Today', 'Week', 'Month'].map((period) => ({ label: period, value: period }))
|
const periods = ["Today", "Week", "Month"].map((period) => ({
|
||||||
|
label: period,
|
||||||
|
value: period,
|
||||||
|
}));
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
name: 'Japan',
|
name: "Japan",
|
||||||
revenueToday: '4,748,454',
|
revenueToday: "4,748,454",
|
||||||
revenueWeek: '30,000,000',
|
revenueWeek: "30,000,000",
|
||||||
revenueMonth: '120,000,000',
|
revenueMonth: "120,000,000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'United Kingdom',
|
name: "United Kingdom",
|
||||||
revenueToday: '405,748',
|
revenueToday: "405,748",
|
||||||
revenueWeek: '2,500,000',
|
revenueWeek: "2,500,000",
|
||||||
revenueMonth: '10,000,000',
|
revenueMonth: "10,000,000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'United States',
|
name: "United States",
|
||||||
revenueToday: '308,536',
|
revenueToday: "308,536",
|
||||||
revenueWeek: '1,800,000',
|
revenueWeek: "1,800,000",
|
||||||
revenueMonth: '8,000,000',
|
revenueMonth: "8,000,000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'China',
|
name: "China",
|
||||||
revenueToday: '250,963',
|
revenueToday: "250,963",
|
||||||
revenueWeek: '1,600,000',
|
revenueWeek: "1,600,000",
|
||||||
revenueMonth: '7,000,000',
|
revenueMonth: "7,000,000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Canada',
|
name: "Canada",
|
||||||
revenueToday: '29,415',
|
revenueToday: "29,415",
|
||||||
revenueWeek: '180,000',
|
revenueWeek: "180,000",
|
||||||
revenueMonth: '800,000',
|
revenueMonth: "800,000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Australia',
|
name: "Australia",
|
||||||
revenueToday: '15,000',
|
revenueToday: "15,000",
|
||||||
revenueWeek: '100,000',
|
revenueWeek: "100,000",
|
||||||
revenueMonth: '500,000',
|
revenueMonth: "500,000",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'India',
|
name: "India",
|
||||||
revenueToday: '10,000',
|
revenueToday: "10,000",
|
||||||
revenueWeek: '50,000',
|
revenueWeek: "50,000",
|
||||||
revenueMonth: '200,000',
|
revenueMonth: "200,000",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
const exportAsCSV = () => {
|
const exportAsCSV = () => {
|
||||||
downloadAsCSV(data, 'region-revenue')
|
downloadAsCSV(data, "region-revenue");
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,81 @@
|
||||||
<template>
|
<template>
|
||||||
<VaCard class="flex flex-col">
|
<VaCard class="flex flex-col">
|
||||||
<VaCardTitle class="flex items-center justify-between">
|
<VaCardTitle class="flex items-center justify-between">
|
||||||
<h1 class="card-title text-secondary font-bold uppercase">Revenue by location</h1>
|
<h1 class="card-title text-secondary font-bold uppercase">
|
||||||
|
Revenue by location
|
||||||
|
</h1>
|
||||||
</VaCardTitle>
|
</VaCardTitle>
|
||||||
<VaCardContent class="flex-1 flex overflow-hidden">
|
<VaCardContent class="flex-1 flex overflow-hidden">
|
||||||
<VaAspectRatio class="w-full md:min-h-72 overflow-hidden relative flex items-center">
|
<VaAspectRatio
|
||||||
|
class="w-full md:min-h-72 overflow-hidden relative flex items-center"
|
||||||
|
>
|
||||||
<Map v-if="geoJson" :data="data" class="dashboard-map flex-1 h-full" />
|
<Map v-if="geoJson" :data="data" class="dashboard-map flex-1 h-full" />
|
||||||
<VaProgressCircle v-else indeterminate class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />
|
<VaProgressCircle
|
||||||
|
v-else
|
||||||
|
indeterminate
|
||||||
|
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
|
||||||
|
/>
|
||||||
</VaAspectRatio>
|
</VaAspectRatio>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
</VaCard>
|
</VaCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { VaCard } from 'vuestic-ui'
|
import { VaCard } from "vuestic-ui";
|
||||||
import type countriesGeoJSON from '../../../../data/geo.json'
|
import type countriesGeoJSON from "../../../../data/geo.json";
|
||||||
import Map from '../../../../components/va-charts/chart-types/Map.vue'
|
import Map from "../../../../components/va-charts/chart-types/Map.vue";
|
||||||
import type { ChartData } from 'chart.js'
|
import type { ChartData } from "chart.js";
|
||||||
|
|
||||||
const getRevenue = (countryName: string) => {
|
const getRevenue = (countryName: string) => {
|
||||||
if (['United States of America', 'Canada', 'United Kingdom', 'China', 'Japan'].includes(countryName)) {
|
if (
|
||||||
return 10
|
[
|
||||||
|
"United States of America",
|
||||||
|
"Canada",
|
||||||
|
"United Kingdom",
|
||||||
|
"China",
|
||||||
|
"Japan",
|
||||||
|
].includes(countryName)
|
||||||
|
) {
|
||||||
|
return 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['Antarctica', 'Greenland'].includes(countryName)) {
|
if (["Antarctica", "Greenland"].includes(countryName)) {
|
||||||
return 0
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.random() * 10
|
return Math.random() * 10;
|
||||||
}
|
};
|
||||||
|
|
||||||
const geoJson = ref<typeof countriesGeoJSON | null>(null)
|
const geoJson = ref<typeof countriesGeoJSON | null>(null);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
geoJson.value = (await import('../../../../data/geo.json')).default
|
geoJson.value = (await import("../../../../data/geo.json")).default;
|
||||||
})
|
});
|
||||||
|
|
||||||
const data = computed<ChartData<'choropleth', { feature: any; value: number }[], string>>(() => {
|
const data = computed<
|
||||||
|
ChartData<"choropleth", { feature: any; value: number }[], string>
|
||||||
|
>(() => {
|
||||||
if (!geoJson.value) {
|
if (!geoJson.value) {
|
||||||
return {
|
return {
|
||||||
labels: [],
|
labels: [],
|
||||||
datasets: [],
|
datasets: [],
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels: geoJson.value.features.map((d) => d.properties.name),
|
labels: geoJson.value.features.map((d) => d.properties.name),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Countries',
|
label: "Countries",
|
||||||
data: geoJson.value.features.map((d) => ({ feature: d, value: getRevenue(d.properties.name) })),
|
data: geoJson.value.features.map((d) => ({
|
||||||
|
feature: d,
|
||||||
|
value: getRevenue(d.properties.name),
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<VaCard class="flex flex-col">
|
<VaCard class="flex flex-col">
|
||||||
<VaCardTitle class="flex items-start justify-between">
|
<VaCardTitle class="flex items-start justify-between">
|
||||||
<h1 class="card-title text-secondary font-bold uppercase">Revenue Report</h1>
|
<h1 class="card-title text-secondary font-bold uppercase">
|
||||||
|
Revenue Report
|
||||||
|
</h1>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<VaSelect v-model="selectedMonth" preset="small" :options="monthsWithCurrentYear" class="w-24" />
|
<VaSelect
|
||||||
<VaButton class="h-2" size="small" preset="primary" @click="exportAsCSV">Export</VaButton>
|
v-model="selectedMonth"
|
||||||
|
preset="small"
|
||||||
|
:options="monthsWithCurrentYear"
|
||||||
|
class="w-24"
|
||||||
|
/>
|
||||||
|
<VaButton class="h-2" size="small" preset="primary" @click="exportAsCSV"
|
||||||
|
>Export</VaButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</VaCardTitle>
|
</VaCardTitle>
|
||||||
<VaCardContent class="flex flex-col-reverse md:flex-row md:items-center justify-between gap-5 h-full">
|
<VaCardContent
|
||||||
<section class="flex flex-col items-start w-full sm:w-1/3 md:w-2/5 lg:w-1/4 gap-2 md:gap-8 pl-4">
|
class="flex flex-col-reverse md:flex-row md:items-center justify-between gap-5 h-full"
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
class="flex flex-col items-start w-full sm:w-1/3 md:w-2/5 lg:w-1/4 gap-2 md:gap-8 pl-4"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xl font-semibold">{{ formatMoney(totalEarnings) }}</p>
|
<p class="text-xl font-semibold">{{ formatMoney(totalEarnings) }}</p>
|
||||||
<p class="whitespace-nowrap mt-2">Total earnings</p>
|
<p class="whitespace-nowrap mt-2">Total earnings</p>
|
||||||
|
|
@ -16,17 +29,27 @@
|
||||||
<div class="flex flex-col sm:flex-col gap-2 md:gap-8 w-full">
|
<div class="flex flex-col sm:flex-col gap-2 md:gap-8 w-full">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-block w-2 h-2 mr-2 -ml-4" :style="{ backgroundColor: earningsColor }"></span>
|
<span
|
||||||
|
class="inline-block w-2 h-2 mr-2 -ml-4"
|
||||||
|
:style="{ backgroundColor: earningsColor }"
|
||||||
|
></span>
|
||||||
<span class="text-secondary">Earnings this month</span>
|
<span class="text-secondary">Earnings this month</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-xl font-semibold">{{ formatMoney(earningsForSelectedMonth.earning) }}</div>
|
<div class="mt-2 text-xl font-semibold">
|
||||||
|
{{ formatMoney(earningsForSelectedMonth.earning) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-block w-2 h-2 mr-2 -ml-4" :style="{ backgroundColor: expensesColor }"></span>
|
<span
|
||||||
|
class="inline-block w-2 h-2 mr-2 -ml-4"
|
||||||
|
:style="{ backgroundColor: expensesColor }"
|
||||||
|
></span>
|
||||||
<span class="text-secondary">Expense this month</span>
|
<span class="text-secondary">Expense this month</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-xl font-semibold">{{ formatMoney(earningsForSelectedMonth.expenses) }}</div>
|
<div class="mt-2 text-xl font-semibold">
|
||||||
|
{{ formatMoney(earningsForSelectedMonth.expenses) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -40,10 +63,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from "vue";
|
||||||
import { VaCard } from 'vuestic-ui'
|
import { VaCard } from "vuestic-ui";
|
||||||
import RevenueReportChart from './RevenueReportChart.vue'
|
import RevenueReportChart from "./RevenueReportChart.vue";
|
||||||
import { downloadAsCSV } from '../../../../services/toCSV'
|
import { downloadAsCSV } from "../../../../services/toCSV";
|
||||||
import {
|
import {
|
||||||
earningsColor,
|
earningsColor,
|
||||||
expensesColor,
|
expensesColor,
|
||||||
|
|
@ -51,21 +74,26 @@ import {
|
||||||
generateRevenues,
|
generateRevenues,
|
||||||
getRevenuePerMonth,
|
getRevenuePerMonth,
|
||||||
formatMoney,
|
formatMoney,
|
||||||
} from '../../../../data/charts/revenueChartData'
|
} from "../../../../data/charts/revenueChartData";
|
||||||
|
|
||||||
const revenues = generateRevenues(months)
|
const revenues = generateRevenues(months);
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear();
|
||||||
const monthsWithCurrentYear = months.map((month) => `${month} ${currentYear}`)
|
const monthsWithCurrentYear = months.map((month) => `${month} ${currentYear}`);
|
||||||
|
|
||||||
const selectedMonth = ref(monthsWithCurrentYear[0])
|
const selectedMonth = ref(monthsWithCurrentYear[0]);
|
||||||
|
|
||||||
const earningsForSelectedMonth = computed(() => getRevenuePerMonth(selectedMonth.value.split(' ')[0], revenues))
|
const earningsForSelectedMonth = computed(() =>
|
||||||
|
getRevenuePerMonth(selectedMonth.value.split(" ")[0], revenues),
|
||||||
|
);
|
||||||
const totalEarnings = computed(() => {
|
const totalEarnings = computed(() => {
|
||||||
return earningsForSelectedMonth.value.earning + earningsForSelectedMonth.value.expenses
|
return (
|
||||||
})
|
earningsForSelectedMonth.value.earning +
|
||||||
|
earningsForSelectedMonth.value.expenses
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const exportAsCSV = () => {
|
const exportAsCSV = () => {
|
||||||
downloadAsCSV(revenues, 'revenue-report')
|
downloadAsCSV(revenues, "revenue-report");
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -5,60 +5,71 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, nextTick } from 'vue'
|
import { ref, onMounted, nextTick } from "vue";
|
||||||
import { Chart, registerables } from 'chart.js'
|
import { Chart, registerables } from "chart.js";
|
||||||
|
|
||||||
import type { Revenues } from '../../../../data/charts/revenueChartData'
|
import type { Revenues } from "../../../../data/charts/revenueChartData";
|
||||||
import { earningsColor, expensesColor, formatMoney } from '../../../../data/charts/revenueChartData'
|
import {
|
||||||
|
earningsColor,
|
||||||
|
expensesColor,
|
||||||
|
formatMoney,
|
||||||
|
} from "../../../../data/charts/revenueChartData";
|
||||||
|
|
||||||
const { revenues, months } = defineProps<{
|
const { revenues, months } = defineProps<{
|
||||||
months: string[]
|
months: string[];
|
||||||
revenues: Revenues[]
|
revenues: Revenues[];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
Chart.register(...registerables)
|
Chart.register(...registerables);
|
||||||
|
|
||||||
const BR_THICKNESS = 4
|
const BR_THICKNESS = 4;
|
||||||
|
|
||||||
Chart.register([
|
Chart.register([
|
||||||
{
|
{
|
||||||
id: 'background-color',
|
id: "background-color",
|
||||||
beforeDatasetDraw: function (chart) {
|
beforeDatasetDraw: function (chart) {
|
||||||
const ctx = chart.ctx
|
const ctx = chart.ctx;
|
||||||
const config = chart.config
|
const config = chart.config;
|
||||||
|
|
||||||
config.data.datasets.forEach(function (dataset, datasetIndex) {
|
config.data.datasets.forEach(function (dataset, datasetIndex) {
|
||||||
const meta = chart.getDatasetMeta(datasetIndex)
|
const meta = chart.getDatasetMeta(datasetIndex);
|
||||||
if (meta.type === 'bar') {
|
if (meta.type === "bar") {
|
||||||
const bgColor = earningsColor
|
const bgColor = earningsColor;
|
||||||
|
|
||||||
// Loop through each bar in the dataset
|
// Loop through each bar in the dataset
|
||||||
meta.data.forEach(function (bar) {
|
meta.data.forEach(function (bar) {
|
||||||
ctx.fillStyle = bgColor
|
ctx.fillStyle = bgColor;
|
||||||
ctx.fillRect(bar.x - BR_THICKNESS / 2, 0, BR_THICKNESS, chart.chartArea.bottom)
|
ctx.fillRect(
|
||||||
})
|
bar.x - BR_THICKNESS / 2,
|
||||||
|
0,
|
||||||
|
BR_THICKNESS,
|
||||||
|
chart.chartArea.bottom,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
|
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
const doShowChart = ref(false)
|
const doShowChart = ref(false);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (canvas.value) {
|
if (canvas.value) {
|
||||||
const ctx = canvas.value.getContext('2d')
|
const ctx = canvas.value.getContext("2d");
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
new Chart(ctx, {
|
new Chart(ctx, {
|
||||||
type: 'bar',
|
type: "bar",
|
||||||
data: {
|
data: {
|
||||||
labels: months,
|
labels: months,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
// Show relative expenses ratio
|
// Show relative expenses ratio
|
||||||
data: revenues.map(({ earning, expenses }) => (expenses / earning) * 100),
|
data: revenues.map(
|
||||||
|
({ earning, expenses }) => (expenses / earning) * 100,
|
||||||
|
),
|
||||||
backgroundColor: expensesColor,
|
backgroundColor: expensesColor,
|
||||||
barThickness: BR_THICKNESS,
|
barThickness: BR_THICKNESS,
|
||||||
},
|
},
|
||||||
|
|
@ -86,20 +97,20 @@ onMounted(() => {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function (value) {
|
callback: function (value) {
|
||||||
return formatMoney(Number(value))
|
return formatMoney(Number(value));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
doShowChart.value = true
|
doShowChart.value = true;
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import VaTimelineItem from '../../../../components/va-timeline-item.vue'
|
import VaTimelineItem from "../../../../components/va-timeline-item.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -11,30 +11,55 @@ import VaTimelineItem from '../../../../components/va-timeline-item.vue'
|
||||||
<table class="mt-4">
|
<table class="mt-4">
|
||||||
<tbody>
|
<tbody>
|
||||||
<VaTimelineItem date="25m ago">
|
<VaTimelineItem date="25m ago">
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Donald</RouterLink> updated the status of
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Refund #1234</RouterLink> to awaiting customer
|
>Donald</RouterLink
|
||||||
response
|
>
|
||||||
|
updated the status of
|
||||||
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
|
>Refund #1234</RouterLink
|
||||||
|
>
|
||||||
|
to awaiting customer response
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
<VaTimelineItem date="1h ago">
|
<VaTimelineItem date="1h ago">
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Lycy Peterson</RouterLink> was added to the group,
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
group name is Overtake
|
>Lycy Peterson</RouterLink
|
||||||
|
>
|
||||||
|
was added to the group, group name is Overtake
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
<VaTimelineItem date="2h ago">
|
<VaTimelineItem date="2h ago">
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Joseph Rust</RouterLink> opened new showcase
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Mannat #112233</RouterLink> with theme market
|
>Joseph Rust</RouterLink
|
||||||
|
>
|
||||||
|
opened new showcase
|
||||||
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
|
>Mannat #112233</RouterLink
|
||||||
|
>
|
||||||
|
with theme market
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
<VaTimelineItem date="3d ago">
|
<VaTimelineItem date="3d ago">
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Donald</RouterLink> updated the status to awaiting
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
customer response
|
>Donald</RouterLink
|
||||||
|
>
|
||||||
|
updated the status to awaiting customer response
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
<VaTimelineItem date="Nov 14, 2023">
|
<VaTimelineItem date="Nov 14, 2023">
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Lycy Peterson</RouterLink> was added to the group
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
|
>Lycy Peterson</RouterLink
|
||||||
|
>
|
||||||
|
was added to the group
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
<VaTimelineItem date="Nov 14, 2023">
|
<VaTimelineItem date="Nov 14, 2023">
|
||||||
<RouterLink class="va-link font-semibold" to="/users">Dan Rya</RouterLink> was added to the group
|
<RouterLink class="va-link font-semibold" to="/users"
|
||||||
|
>Dan Rya</RouterLink
|
||||||
|
>
|
||||||
|
was added to the group
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
<VaTimelineItem date="Nov 15, 2023">
|
<VaTimelineItem date="Nov 15, 2023">
|
||||||
Project <RouterLink class="va-link font-semibold" to="/projects">Vuestic 2023</RouterLink> was created
|
Project
|
||||||
|
<RouterLink class="va-link font-semibold" to="/projects"
|
||||||
|
>Vuestic 2023</RouterLink
|
||||||
|
>
|
||||||
|
was created
|
||||||
</VaTimelineItem>
|
</VaTimelineItem>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<VaCard>
|
<VaCard>
|
||||||
<VaCardTitle class="pb-0!">
|
<VaCardTitle class="pb-0!">
|
||||||
<h1 class="card-title text-secondary font-bold uppercase">Yearly Breakup</h1>
|
<h1 class="card-title text-secondary font-bold uppercase">
|
||||||
|
Yearly Breakup
|
||||||
|
</h1>
|
||||||
</VaCardTitle>
|
</VaCardTitle>
|
||||||
<VaCardContent class="flex flex-row gap-1">
|
<VaCardContent class="flex flex-row gap-1">
|
||||||
<section class="w-1/2">
|
<section class="w-1/2">
|
||||||
|
|
@ -13,11 +15,17 @@
|
||||||
</p>
|
</p>
|
||||||
<div class="my-4 gap-2 flex flex-col">
|
<div class="my-4 gap-2 flex flex-col">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-block w-2 h-2 mr-2" :style="{ backgroundColor: earningsBackground }"></span>
|
<span
|
||||||
|
class="inline-block w-2 h-2 mr-2"
|
||||||
|
:style="{ backgroundColor: earningsBackground }"
|
||||||
|
></span>
|
||||||
<span class="text-secondary">Earnings</span>
|
<span class="text-secondary">Earnings</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="inline-block w-2 h-2 mr-2" :style="{ backgroundColor: profitBackground }"></span>
|
<span
|
||||||
|
class="inline-block w-2 h-2 mr-2"
|
||||||
|
:style="{ backgroundColor: profitBackground }"
|
||||||
|
></span>
|
||||||
<span class="text-secondary">Profit</span>
|
<span class="text-secondary">Profit</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -36,27 +44,37 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VaCard } from 'vuestic-ui'
|
import { VaCard } from "vuestic-ui";
|
||||||
import VaChart from '../../../../components/va-charts/VaChart.vue'
|
import VaChart from "../../../../components/va-charts/VaChart.vue";
|
||||||
import { useChartData } from '../../../../data/charts/composables/useChartData'
|
import { useChartData } from "../../../../data/charts/composables/useChartData";
|
||||||
import { doughnutChartData, profitBackground, earningsBackground } from '../../../../data/charts/doughnutChartData'
|
import {
|
||||||
import { doughnutConfig } from '../../../../components/va-charts/vaChartConfigs'
|
doughnutChartData,
|
||||||
import { ChartOptions } from 'chart.js'
|
profitBackground,
|
||||||
import { externalTooltipHandler } from '../../../../components/va-charts/external-tooltip'
|
earningsBackground,
|
||||||
|
} from "../../../../data/charts/doughnutChartData";
|
||||||
|
import { doughnutConfig } from "../../../../components/va-charts/vaChartConfigs";
|
||||||
|
import { ChartOptions } from "chart.js";
|
||||||
|
import { externalTooltipHandler } from "../../../../components/va-charts/external-tooltip";
|
||||||
|
|
||||||
const chartData = useChartData(doughnutChartData)
|
const chartData = useChartData(doughnutChartData);
|
||||||
|
|
||||||
const options: ChartOptions<'doughnut'> = {
|
const options: ChartOptions<"doughnut"> = {
|
||||||
...doughnutConfig,
|
...doughnutConfig,
|
||||||
plugins: {
|
plugins: {
|
||||||
...doughnutConfig.plugins,
|
...doughnutConfig.plugins,
|
||||||
tooltip: {
|
tooltip: {
|
||||||
// Chart to small to show tooltips
|
// Chart to small to show tooltips
|
||||||
enabled: false,
|
enabled: false,
|
||||||
position: 'nearest',
|
position: "nearest",
|
||||||
external: externalTooltipHandler,
|
external: externalTooltipHandler,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
circumference: 360 * (chartData.value.datasets[0].data.reduce((acc: number, d: number) => acc + d, 0) / 800),
|
circumference:
|
||||||
}
|
360 *
|
||||||
|
(chartData.value.datasets[0].data.reduce(
|
||||||
|
(acc: number, d: number) => acc + d,
|
||||||
|
0,
|
||||||
|
) /
|
||||||
|
800),
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
<div class="not-found-pages__button-container pt-4 mb-0">
|
<div class="not-found-pages__button-container pt-4 mb-0">
|
||||||
<VaButton :to="{ name: item.buttonTo }">
|
<VaButton :to="{ name: item.buttonTo }">
|
||||||
{{ 'View Example' }}
|
{{ "View Example" }}
|
||||||
</VaButton>
|
</VaButton>
|
||||||
</div>
|
</div>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
|
|
@ -19,28 +19,28 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
|
|
||||||
const items = ref([
|
const items = ref([
|
||||||
{
|
{
|
||||||
imageUrl: 'https://i.imgur.com/GzUR0Wz.png',
|
imageUrl: "https://i.imgur.com/GzUR0Wz.png",
|
||||||
label: 'Advanced layouts',
|
label: "Advanced layouts",
|
||||||
buttonTo: 'not-found-advanced',
|
buttonTo: "not-found-advanced",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
imageUrl: 'https://i.imgur.com/HttcXPi.png',
|
imageUrl: "https://i.imgur.com/HttcXPi.png",
|
||||||
label: 'Simple',
|
label: "Simple",
|
||||||
buttonTo: 'not-found-simple',
|
buttonTo: "not-found-simple",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
imageUrl: 'https://i.imgur.com/dlcZMiG.png',
|
imageUrl: "https://i.imgur.com/dlcZMiG.png",
|
||||||
label: 'Custom image',
|
label: "Custom image",
|
||||||
buttonTo: 'not-found-custom',
|
buttonTo: "not-found-custom",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
imageUrl: 'https://i.imgur.com/qcOlDz7.png',
|
imageUrl: "https://i.imgur.com/qcOlDz7.png",
|
||||||
label: 'Large text heading',
|
label: "Large text heading",
|
||||||
buttonTo: 'not-found-large-text',
|
buttonTo: "not-found-large-text",
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
<div>
|
<div>
|
||||||
<h1 class="font-semibold text-4xl mb-4">Check the email</h1>
|
<h1 class="font-semibold text-4xl mb-4">Check the email</h1>
|
||||||
<p class="text-base mb-4 leading-5">
|
<p class="text-base mb-4 leading-5">
|
||||||
Password reset instructions have been sent to your email. Check your inbox, including the spam folder if needed.
|
Password reset instructions have been sent to your email. Check your
|
||||||
For assistance, <span class="va-link">contact support</span>.
|
inbox, including the spam folder if needed. For assistance,
|
||||||
|
<span class="va-link">contact support</span>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex justify-center mt-4">
|
<div class="flex justify-center mt-4">
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,52 @@
|
||||||
<h1 class="font-semibold text-4xl mb-4">Войти</h1>
|
<h1 class="font-semibold text-4xl mb-4">Войти</h1>
|
||||||
<p class="text-base mb-4 leading-5">
|
<p class="text-base mb-4 leading-5">
|
||||||
Впервые здесь?
|
Впервые здесь?
|
||||||
<RouterLink :to="{ name: 'signup' }" class="font-semibold text-primary">Регистрация</RouterLink>
|
<RouterLink :to="{ name: 'signup' }" class="font-semibold text-primary"
|
||||||
|
>Регистрация</RouterLink
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
<VaInput v-model="formData.email" :rules="[validators.required, validators.email]" class="mb-4" label="Email"
|
<VaInput
|
||||||
type="email" />
|
v-model="formData.email"
|
||||||
|
:rules="[validators.required, validators.email]"
|
||||||
|
class="mb-4"
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
||||||
<VaInput v-model="formData.password" :rules="[validators.required]"
|
<VaInput
|
||||||
:type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Пароль"
|
v-model="formData.password"
|
||||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
:rules="[validators.required]"
|
||||||
|
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||||
|
class="mb-4"
|
||||||
|
label="Пароль"
|
||||||
|
@clickAppendInner.stop="
|
||||||
|
isPasswordVisible.value = !isPasswordVisible.value
|
||||||
|
"
|
||||||
|
>
|
||||||
<template #appendInner>
|
<template #appendInner>
|
||||||
<VaIcon :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'" class="cursor-pointer"
|
<VaIcon
|
||||||
color="secondary" />
|
:name="
|
||||||
|
isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'
|
||||||
|
"
|
||||||
|
class="cursor-pointer"
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VaInput>
|
</VaInput>
|
||||||
</VaValue>
|
</VaValue>
|
||||||
|
|
||||||
<div class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between">
|
<div
|
||||||
<VaCheckbox v-model="formData.keepLoggedIn" class="mb-2 sm:mb-0" label="Запомнить меня" />
|
class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||||
<RouterLink :to="{ name: 'recover-password' }" class="mt-2 sm:mt-0 sm:ml-1 font-semibold text-primary">
|
>
|
||||||
|
<VaCheckbox
|
||||||
|
v-model="formData.keepLoggedIn"
|
||||||
|
class="mb-2 sm:mb-0"
|
||||||
|
label="Запомнить меня"
|
||||||
|
/>
|
||||||
|
<RouterLink
|
||||||
|
:to="{ name: 'recover-password' }"
|
||||||
|
class="mt-2 sm:mt-0 sm:ml-1 font-semibold text-primary"
|
||||||
|
>
|
||||||
Забыли пароль?
|
Забыли пароль?
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -32,31 +60,35 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
import { reactive } from 'vue'
|
import { reactive } from "vue";
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router";
|
||||||
import { useForm, useToast } from 'vuestic-ui'
|
import { useForm, useToast } from "vuestic-ui";
|
||||||
import { validators } from '../../services/utils'
|
import { validators } from "../../services/utils";
|
||||||
|
|
||||||
const { validate } = useForm('form')
|
const { validate } = useForm("form");
|
||||||
const { push } = useRouter()
|
const { push } = useRouter();
|
||||||
const { init } = useToast()
|
const { init } = useToast();
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
email: '',
|
email: "",
|
||||||
password: '',
|
password: "",
|
||||||
keepLoggedIn: false,
|
keepLoggedIn: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
axios.post('http://localhost:3000/api/v0/signin', {"login": formData.email, "password":formData.password })
|
axios
|
||||||
.then(response => {
|
.post("http://localhost:3000/api/v0/signin", {
|
||||||
// TODO: save token
|
login: formData.email,
|
||||||
init({ message: "Вы успешно вошли!", color: 'success' })
|
password: formData.password,
|
||||||
push({ name: 'dashboard' })
|
|
||||||
})
|
})
|
||||||
.catch(error => { });
|
.then((response) => {
|
||||||
|
// TODO: save token
|
||||||
|
init({ message: "Вы успешно вошли!", color: "success" });
|
||||||
|
push({ name: "dashboard" });
|
||||||
|
})
|
||||||
|
.catch((error) => {});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
<VaForm ref="passwordForm" @submit.prevent="submit">
|
<VaForm ref="passwordForm" @submit.prevent="submit">
|
||||||
<h1 class="font-semibold text-4xl mb-4">Забыли свой пароль?</h1>
|
<h1 class="font-semibold text-4xl mb-4">Забыли свой пароль?</h1>
|
||||||
<p class="text-base mb-4 leading-5">
|
<p class="text-base mb-4 leading-5">
|
||||||
Если вы забыли свой пароль, не волнуйтесь. Просто введите свой адрес электронной почты ниже, и мы отправим вам электронное письмо
|
Если вы забыли свой пароль, не волнуйтесь. Просто введите свой адрес
|
||||||
с временным паролем.
|
электронной почты ниже, и мы отправим вам электронное письмо с временным
|
||||||
|
паролем.
|
||||||
</p>
|
</p>
|
||||||
<VaInput
|
<VaInput
|
||||||
v-model="email"
|
v-model="email"
|
||||||
|
|
@ -13,22 +14,28 @@
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
<VaButton class="w-full mb-2" @click="submit">Отправить пароль</VaButton>
|
<VaButton class="w-full mb-2" @click="submit">Отправить пароль</VaButton>
|
||||||
<VaButton :to="{ name: 'login' }" class="w-full" preset="secondary" @click="submit">Вернуться</VaButton>
|
<VaButton
|
||||||
|
:to="{ name: 'login' }"
|
||||||
|
class="w-full"
|
||||||
|
preset="secondary"
|
||||||
|
@click="submit"
|
||||||
|
>Вернуться</VaButton
|
||||||
|
>
|
||||||
</VaForm>
|
</VaForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
import { useForm } from 'vuestic-ui'
|
import { useForm } from "vuestic-ui";
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const email = ref('')
|
const email = ref("");
|
||||||
const form = useForm('passwordForm')
|
const form = useForm("passwordForm");
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (form.validate()) {
|
if (form.validate()) {
|
||||||
router.push({ name: 'recover-password-email' })
|
router.push({ name: "recover-password-email" });
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,65 @@
|
||||||
<h1 class="font-semibold text-4xl mb-4">Регистрация</h1>
|
<h1 class="font-semibold text-4xl mb-4">Регистрация</h1>
|
||||||
<p class="text-base mb-4 leading-5">
|
<p class="text-base mb-4 leading-5">
|
||||||
Уже есть аккаунт?
|
Уже есть аккаунт?
|
||||||
<RouterLink :to="{ name: 'login' }" class="font-semibold text-primary">Логин</RouterLink>
|
<RouterLink :to="{ name: 'login' }" class="font-semibold text-primary"
|
||||||
|
>Логин</RouterLink
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
<VaInput v-model="formData.email"
|
<VaInput
|
||||||
:rules="[(v) => !!v || 'Email обязателен', (v) => /.+@.+\..+/.test(v) || 'Email должен быть валидным']"
|
v-model="formData.email"
|
||||||
class="mb-4" label="Email" type="email" />
|
:rules="[
|
||||||
|
(v) => !!v || 'Email обязателен',
|
||||||
|
(v) => /.+@.+\..+/.test(v) || 'Email должен быть валидным',
|
||||||
|
]"
|
||||||
|
class="mb-4"
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
/>
|
||||||
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
||||||
<VaInput ref="password1" v-model="formData.password" :rules="passwordRules"
|
<VaInput
|
||||||
:type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Пароль"
|
ref="password1"
|
||||||
|
v-model="formData.password"
|
||||||
|
:rules="passwordRules"
|
||||||
|
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||||
|
class="mb-4"
|
||||||
|
label="Пароль"
|
||||||
messages="Пароль должен быть длинее 4-х символов."
|
messages="Пароль должен быть длинее 4-х символов."
|
||||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
@clickAppendInner.stop="
|
||||||
|
isPasswordVisible.value = !isPasswordVisible.value
|
||||||
|
"
|
||||||
|
>
|
||||||
<template #appendInner>
|
<template #appendInner>
|
||||||
<VaIcon :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'" class="cursor-pointer"
|
<VaIcon
|
||||||
color="secondary" />
|
:name="
|
||||||
|
isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'
|
||||||
|
"
|
||||||
|
class="cursor-pointer"
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VaInput>
|
</VaInput>
|
||||||
<VaInput ref="password2" v-model="formData.repeatPassword" :rules="[
|
<VaInput
|
||||||
(v) => !!v || 'Поле повторите пароль обязательное',
|
ref="password2"
|
||||||
(v) => v === formData.password || 'Пароли не совпадают',
|
v-model="formData.repeatPassword"
|
||||||
]" :type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Повторите пароль"
|
:rules="[
|
||||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
(v) => !!v || 'Поле повторите пароль обязательное',
|
||||||
|
(v) => v === formData.password || 'Пароли не совпадают',
|
||||||
|
]"
|
||||||
|
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||||
|
class="mb-4"
|
||||||
|
label="Повторите пароль"
|
||||||
|
@clickAppendInner.stop="
|
||||||
|
isPasswordVisible.value = !isPasswordVisible.value
|
||||||
|
"
|
||||||
|
>
|
||||||
<template #appendInner>
|
<template #appendInner>
|
||||||
<VaIcon :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'" class="cursor-pointer"
|
<VaIcon
|
||||||
color="secondary" />
|
:name="
|
||||||
|
isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'
|
||||||
|
"
|
||||||
|
class="cursor-pointer"
|
||||||
|
color="secondary"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VaInput>
|
</VaInput>
|
||||||
</VaValue>
|
</VaValue>
|
||||||
|
|
@ -37,38 +73,41 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
import { reactive } from 'vue'
|
import { reactive } from "vue";
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router";
|
||||||
import { useForm, useToast } from 'vuestic-ui'
|
import { useForm, useToast } from "vuestic-ui";
|
||||||
|
|
||||||
const { validate } = useForm('form')
|
const { validate } = useForm("form");
|
||||||
const { push } = useRouter()
|
const { push } = useRouter();
|
||||||
const { init } = useToast()
|
const { init } = useToast();
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
email: '',
|
email: "",
|
||||||
password: '',
|
password: "",
|
||||||
repeatPassword: '',
|
repeatPassword: "",
|
||||||
})
|
});
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
axios.post('http://localhost:3000/api/v0/signin', { "email": formData.email, "password": formData.password })
|
axios
|
||||||
.then(response => {
|
.post("http://localhost:3000/api/v0/signin", {
|
||||||
|
email: formData.email,
|
||||||
|
password: formData.password,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
// TODO: save token
|
// TODO: save token
|
||||||
init({
|
init({
|
||||||
message: "Вы успешно вошли",
|
message: "Вы успешно вошли",
|
||||||
color: 'success',
|
color: "success",
|
||||||
})
|
});
|
||||||
push({ name: 'dashboard' })
|
push({ name: "dashboard" }).catch((error) => {});
|
||||||
.catch(error => { });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const passwordRules: ((v: string) => boolean | string)[] = [
|
const passwordRules: ((v: string) => boolean | string)[] = [
|
||||||
(v) => !!v || 'Поле пароль обязательное',
|
(v) => !!v || "Поле пароль обязательное",
|
||||||
(v) => (v && v.length >= 5) || 'Пароль должен быть длинее 5-ти символов',
|
(v) => (v && v.length >= 5) || "Пароль должен быть длинее 5-ти символов",
|
||||||
]
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MembeshipTier from './MembeshipTier.vue'
|
import MembeshipTier from "./MembeshipTier.vue";
|
||||||
import PaymentInfo from './PaymentInfo.vue'
|
import PaymentInfo from "./PaymentInfo.vue";
|
||||||
import { usePaymentCardsStore } from '../../stores/payment-cards'
|
import { usePaymentCardsStore } from "../../stores/payment-cards";
|
||||||
import Invoices from './Invoices.vue'
|
import Invoices from "./Invoices.vue";
|
||||||
|
|
||||||
const cardStore = usePaymentCardsStore()
|
const cardStore = usePaymentCardsStore();
|
||||||
cardStore.load()
|
cardStore.load();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -19,76 +19,93 @@
|
||||||
</template>
|
</template>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
<VaCardActions vertical class="flex flex-wrap content-center mt-4">
|
<VaCardActions vertical class="flex flex-wrap content-center mt-4">
|
||||||
<VaButton v-if="numberOfInvoicesInVIew < maxNumberOfInvoices" preset="primary" @click="increaseNumberOfInvoices()"
|
<VaButton
|
||||||
|
v-if="numberOfInvoicesInVIew < maxNumberOfInvoices"
|
||||||
|
preset="primary"
|
||||||
|
@click="increaseNumberOfInvoices()"
|
||||||
>Show more
|
>Show more
|
||||||
</VaButton>
|
</VaButton>
|
||||||
<VaButton v-else preset="primary" @click="numberOfInvoicesInVIew = minNumberOfInvoices">Show less </VaButton>
|
<VaButton
|
||||||
|
v-else
|
||||||
|
preset="primary"
|
||||||
|
@click="numberOfInvoicesInVIew = minNumberOfInvoices"
|
||||||
|
>Show less
|
||||||
|
</VaButton>
|
||||||
</VaCardActions>
|
</VaCardActions>
|
||||||
</VaCard>
|
</VaCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from "vue";
|
||||||
import { useToast } from 'vuestic-ui'
|
import { useToast } from "vuestic-ui";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const { init } = useToast()
|
const { init } = useToast();
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n();
|
||||||
|
|
||||||
const minNumberOfInvoices = 7
|
const minNumberOfInvoices = 7;
|
||||||
const maxNumberOfInvoices = 20
|
const maxNumberOfInvoices = 20;
|
||||||
|
|
||||||
const numberOfInvoicesInVIew = ref(minNumberOfInvoices)
|
const numberOfInvoicesInVIew = ref(minNumberOfInvoices);
|
||||||
|
|
||||||
const increaseNumberOfInvoices = (step = 10) => {
|
const increaseNumberOfInvoices = (step = 10) => {
|
||||||
numberOfInvoicesInVIew.value = Math.min(numberOfInvoicesInVIew.value + step, maxNumberOfInvoices)
|
numberOfInvoicesInVIew.value = Math.min(
|
||||||
}
|
numberOfInvoicesInVIew.value + step,
|
||||||
|
maxNumberOfInvoices,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function getRandomDateInBetween(start: string, end: string): Date {
|
function getRandomDateInBetween(start: string, end: string): Date {
|
||||||
const startDate = Date.parse(start)
|
const startDate = Date.parse(start);
|
||||||
const endDate = Date.parse(end)
|
const endDate = Date.parse(end);
|
||||||
|
|
||||||
return new Date(Math.floor(Math.random() * (endDate - startDate + 1) + startDate))
|
return new Date(
|
||||||
|
Math.floor(Math.random() * (endDate - startDate + 1) + startDate),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLanguageCode(): string {
|
function getLanguageCode(): string {
|
||||||
const countryCodeToLanguageCodeMapping: Record<any, string> = {
|
const countryCodeToLanguageCodeMapping: Record<any, string> = {
|
||||||
br: 'pt',
|
br: "pt",
|
||||||
cn: 'zh-CN',
|
cn: "zh-CN",
|
||||||
gb: 'en-GB',
|
gb: "en-GB",
|
||||||
ir: 'fa',
|
ir: "fa",
|
||||||
}
|
};
|
||||||
|
|
||||||
return countryCodeToLanguageCodeMapping[locale.value] || 'en-GB'
|
return countryCodeToLanguageCodeMapping[locale.value] || "en-GB";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomDateString(): string {
|
function getRandomDateString(): string {
|
||||||
const startDate = '2020-01-01'
|
const startDate = "2020-01-01";
|
||||||
const endDate = '2023-12-01'
|
const endDate = "2023-12-01";
|
||||||
|
|
||||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||||
year: 'numeric',
|
year: "numeric",
|
||||||
month: 'long',
|
month: "long",
|
||||||
day: 'numeric',
|
day: "numeric",
|
||||||
}
|
};
|
||||||
|
|
||||||
return getRandomDateInBetween(startDate, endDate).toLocaleDateString(getLanguageCode(), dateFormatOptions)
|
return getRandomDateInBetween(startDate, endDate).toLocaleDateString(
|
||||||
|
getLanguageCode(),
|
||||||
|
dateFormatOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allItems = Array.from({ length: maxNumberOfInvoices }, (_, i) => ({
|
const allItems = Array.from({ length: maxNumberOfInvoices }, (_, i) => ({
|
||||||
id: i,
|
id: i,
|
||||||
date: getRandomDateString(),
|
date: getRandomDateString(),
|
||||||
amount: `$${(Math.random() * 100).toFixed(2)}`,
|
amount: `$${(Math.random() * 100).toFixed(2)}`,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const itemsInView = computed(() => {
|
const itemsInView = computed(() => {
|
||||||
return allItems.slice(0, numberOfInvoicesInVIew.value)
|
return allItems.slice(0, numberOfInvoicesInVIew.value);
|
||||||
})
|
});
|
||||||
|
|
||||||
const download = () => {
|
const download = () => {
|
||||||
init({
|
init({
|
||||||
message: "Request received. We'll email your invoice once we've completed data collection.",
|
message:
|
||||||
color: 'success',
|
"Request received. We'll email your invoice once we've completed data collection.",
|
||||||
})
|
color: "success",
|
||||||
}
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,12 @@
|
||||||
>
|
>
|
||||||
<div class="flex items-center md:w-48">
|
<div class="flex items-center md:w-48">
|
||||||
<div class="font-bold">{{ plan.name }}</div>
|
<div class="font-bold">{{ plan.name }}</div>
|
||||||
<VaBadge v-if="plan.type === 'current'" class="ml-2" color="success" text="Selected" />
|
<VaBadge
|
||||||
|
v-if="plan.type === 'current'"
|
||||||
|
class="ml-2"
|
||||||
|
color="success"
|
||||||
|
text="Selected"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:w-48">
|
<div class="md:w-48">
|
||||||
<p class="mb-1">{{ plan.padletsTotal }} padlets</p>
|
<p class="mb-1">{{ plan.padletsTotal }} padlets</p>
|
||||||
|
|
@ -24,9 +29,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:w-48 flex justify-end">
|
<div class="md:w-48 flex justify-end">
|
||||||
<div v-if="plan.type === 'current'" class="font-bold">{{ plan.padletsUsed }} padlets used</div>
|
<div v-if="plan.type === 'current'" class="font-bold">
|
||||||
<VaButton v-else-if="plan.type === 'upgrade'" @click="switchPlan(plan.id)">Upgrade</VaButton>
|
{{ plan.padletsUsed }} padlets used
|
||||||
<VaButton v-else preset="primary" @click="switchPlan(plan.id)">Downgrade</VaButton>
|
</div>
|
||||||
|
<VaButton
|
||||||
|
v-else-if="plan.type === 'upgrade'"
|
||||||
|
@click="switchPlan(plan.id)"
|
||||||
|
>Upgrade</VaButton
|
||||||
|
>
|
||||||
|
<VaButton v-else preset="primary" @click="switchPlan(plan.id)"
|
||||||
|
>Downgrade</VaButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -37,69 +50,69 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useToast } from 'vuestic-ui'
|
import { useToast } from "vuestic-ui";
|
||||||
import { reactive } from 'vue'
|
import { reactive } from "vue";
|
||||||
|
|
||||||
const { init } = useToast()
|
const { init } = useToast();
|
||||||
|
|
||||||
type MembershipTier = {
|
type MembershipTier = {
|
||||||
id: string
|
id: string;
|
||||||
name: string
|
name: string;
|
||||||
type: 'upgrade' | 'downgrade' | 'current'
|
type: "upgrade" | "downgrade" | "current";
|
||||||
padletsUsed: number
|
padletsUsed: number;
|
||||||
padletsTotal: string
|
padletsTotal: string;
|
||||||
priceMonth?: string
|
priceMonth?: string;
|
||||||
priceYear?: string
|
priceYear?: string;
|
||||||
uploadLimit: string
|
uploadLimit: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const plans = reactive<MembershipTier[]>([
|
const plans = reactive<MembershipTier[]>([
|
||||||
{
|
{
|
||||||
id: '1',
|
id: "1",
|
||||||
name: 'Platinum',
|
name: "Platinum",
|
||||||
type: 'upgrade',
|
type: "upgrade",
|
||||||
padletsUsed: 0,
|
padletsUsed: 0,
|
||||||
padletsTotal: 'Unlimited',
|
padletsTotal: "Unlimited",
|
||||||
priceMonth: '$9.99',
|
priceMonth: "$9.99",
|
||||||
priceYear: '$99.99',
|
priceYear: "$99.99",
|
||||||
uploadLimit: '500MB',
|
uploadLimit: "500MB",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: "2",
|
||||||
name: 'Gold',
|
name: "Gold",
|
||||||
type: 'current',
|
type: "current",
|
||||||
padletsUsed: 19,
|
padletsUsed: 19,
|
||||||
padletsTotal: '20',
|
padletsTotal: "20",
|
||||||
priceMonth: '$6.99',
|
priceMonth: "$6.99",
|
||||||
priceYear: '$69.99',
|
priceYear: "$69.99",
|
||||||
uploadLimit: '100MB',
|
uploadLimit: "100MB",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: "3",
|
||||||
name: 'Neon',
|
name: "Neon",
|
||||||
type: 'downgrade',
|
type: "downgrade",
|
||||||
padletsUsed: 0,
|
padletsUsed: 0,
|
||||||
padletsTotal: '3',
|
padletsTotal: "3",
|
||||||
priceMonth: undefined,
|
priceMonth: undefined,
|
||||||
priceYear: undefined,
|
priceYear: undefined,
|
||||||
uploadLimit: '20MB',
|
uploadLimit: "20MB",
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
|
|
||||||
const switchPlan = (planId: string) => {
|
const switchPlan = (planId: string) => {
|
||||||
plans.forEach((item, index) => {
|
plans.forEach((item, index) => {
|
||||||
if (item.id === planId) {
|
if (item.id === planId) {
|
||||||
// Set the selected plan to 'current'
|
// Set the selected plan to 'current'
|
||||||
item.type = 'current'
|
item.type = "current";
|
||||||
} else {
|
} else {
|
||||||
// Determine if other plans are an 'upgrade' or 'downgrade'
|
// Determine if other plans are an 'upgrade' or 'downgrade'
|
||||||
const selectedIndex = plans.findIndex((plan) => plan.id === planId)
|
const selectedIndex = plans.findIndex((plan) => plan.id === planId);
|
||||||
item.type = index < selectedIndex ? 'upgrade' : 'downgrade'
|
item.type = index < selectedIndex ? "upgrade" : "downgrade";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
init({
|
init({
|
||||||
message: "You've successfully changed the membership tier",
|
message: "You've successfully changed the membership tier",
|
||||||
color: 'success',
|
color: "success",
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,18 @@
|
||||||
<div class="md:w-48">
|
<div class="md:w-48">
|
||||||
<p class="mb-1">Payment plan</p>
|
<p class="mb-1">Payment plan</p>
|
||||||
<p class="font-bold">
|
<p class="font-bold">
|
||||||
{{ paymentPlan.isYearly ? paymentPlan.priceYear : paymentPlan.priceMonth }} /{{
|
{{
|
||||||
paymentPlan.isYearly ? 'yearly' : 'monthly'
|
paymentPlan.isYearly
|
||||||
}}
|
? paymentPlan.priceYear
|
||||||
|
: paymentPlan.priceMonth
|
||||||
|
}} /{{ paymentPlan.isYearly ? "yearly" : "monthly" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:w-48 flex flex-col justify-end items-end">
|
<div class="md:w-48 flex flex-col justify-end items-end">
|
||||||
<VaButton preset="primary" @click="togglePaymentPlanModal">
|
<VaButton preset="primary" @click="togglePaymentPlanModal">
|
||||||
Switch to {{ paymentPlan.isYearly ? 'monthly' : 'annual' }}
|
Switch to {{ paymentPlan.isYearly ? "monthly" : "annual" }}
|
||||||
</VaButton>
|
</VaButton>
|
||||||
|
|
||||||
<div v-if="!paymentPlan.isYearly" class="mt-2 text-regularSmall">
|
<div v-if="!paymentPlan.isYearly" class="mt-2 text-regularSmall">
|
||||||
|
|
@ -38,11 +40,16 @@
|
||||||
>
|
>
|
||||||
<div class="md:w-48">
|
<div class="md:w-48">
|
||||||
<p class="mb-1">Payment method</p>
|
<p class="mb-1">Payment method</p>
|
||||||
<p class="font-bold capitalize">{{ paymentCard.paymentSystem }} {{ paymentCard.cardNumberMasked }}</p>
|
<p class="font-bold capitalize">
|
||||||
|
{{ paymentCard.paymentSystem }}
|
||||||
|
{{ paymentCard.cardNumberMasked }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="md:w-48 flex justify-end">
|
<div class="md:w-48 flex justify-end">
|
||||||
<VaButton :to="{ name: 'payment-methods' }" preset="primary">Payment preferences</VaButton>
|
<VaButton :to="{ name: 'payment-methods' }" preset="primary"
|
||||||
|
>Payment preferences</VaButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -58,35 +65,36 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from "vue";
|
||||||
import { usePaymentCardsStore } from '../../stores/payment-cards'
|
import { usePaymentCardsStore } from "../../stores/payment-cards";
|
||||||
|
|
||||||
import ChangeYourPaymentPlan from './modals/ChangeYourPaymentPlan.vue'
|
import ChangeYourPaymentPlan from "./modals/ChangeYourPaymentPlan.vue";
|
||||||
|
|
||||||
const paymentPlan = ref({
|
const paymentPlan = ref({
|
||||||
id: '1',
|
id: "1",
|
||||||
name: 'Gold',
|
name: "Gold",
|
||||||
isYearly: false,
|
isYearly: false,
|
||||||
type: 'current',
|
type: "current",
|
||||||
padletsUsed: 19,
|
padletsUsed: 19,
|
||||||
padletsTotal: '20',
|
padletsTotal: "20",
|
||||||
priceMonth: '$6.99',
|
priceMonth: "$6.99",
|
||||||
priceYear: '$69.99',
|
priceYear: "$69.99",
|
||||||
switchToYearlySave: '16%',
|
switchToYearlySave: "16%",
|
||||||
uploadLimit: '100MB',
|
uploadLimit: "100MB",
|
||||||
})
|
});
|
||||||
|
|
||||||
const cardStore = usePaymentCardsStore()
|
const cardStore = usePaymentCardsStore();
|
||||||
|
|
||||||
const isChangeYourPaymentPlanModalOpen = ref(false)
|
const isChangeYourPaymentPlanModalOpen = ref(false);
|
||||||
|
|
||||||
const paymentCard = computed(() => cardStore.currentPaymentCard)
|
const paymentCard = computed(() => cardStore.currentPaymentCard);
|
||||||
const togglePaymentPlanModal = () => {
|
const togglePaymentPlanModal = () => {
|
||||||
isChangeYourPaymentPlanModalOpen.value = !isChangeYourPaymentPlanModalOpen.value
|
isChangeYourPaymentPlanModalOpen.value =
|
||||||
}
|
!isChangeYourPaymentPlanModalOpen.value;
|
||||||
|
};
|
||||||
|
|
||||||
const updatePaymentPlan = () => {
|
const updatePaymentPlan = () => {
|
||||||
paymentPlan.value.isYearly = !paymentPlan.value.isYearly
|
paymentPlan.value.isYearly = !paymentPlan.value.isYearly;
|
||||||
isChangeYourPaymentPlanModalOpen.value = false
|
isChangeYourPaymentPlanModalOpen.value = false;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -11,34 +11,45 @@
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<h3>
|
<h3>
|
||||||
Are you sure you want to switch to the
|
Are you sure you want to switch to the
|
||||||
<span class="font-bold text-primary">{{ yearlyPlan ? 'monthly' : 'annual' }}</span>
|
<span class="font-bold text-primary">{{
|
||||||
|
yearlyPlan ? "monthly" : "annual"
|
||||||
|
}}</span>
|
||||||
plan?
|
plan?
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex flex-col-reverse md:justify-end md:flex-row md:space-x-4">
|
<div
|
||||||
<VaButton preset="secondary" color="secondary" @click="emits('cancel')"> Cancel</VaButton>
|
class="flex flex-col-reverse md:justify-end md:flex-row md:space-x-4"
|
||||||
<VaButton class="mb-4 md:mb-0" @click="confirm()"> Update Plan</VaButton>
|
>
|
||||||
|
<VaButton preset="secondary" color="secondary" @click="emits('cancel')">
|
||||||
|
Cancel</VaButton
|
||||||
|
>
|
||||||
|
<VaButton class="mb-4 md:mb-0" @click="confirm()">
|
||||||
|
Update Plan</VaButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</VaModal>
|
</VaModal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useToast } from 'vuestic-ui'
|
import { useToast } from "vuestic-ui";
|
||||||
|
|
||||||
const { init } = useToast()
|
const { init } = useToast();
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
yearlyPlan: {
|
yearlyPlan: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const emits = defineEmits(['cancel', 'confirm'])
|
const emits = defineEmits(["cancel", "confirm"]);
|
||||||
|
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
init({ message: "You've successfully changed your payment plan", color: 'success' })
|
init({
|
||||||
emits('confirm')
|
message: "You've successfully changed your payment plan",
|
||||||
}
|
color: "success",
|
||||||
|
});
|
||||||
|
emits("confirm");
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Categories from './widgets/Categories.vue'
|
import Categories from "./widgets/Categories.vue";
|
||||||
import Questions from './widgets/Questions.vue'
|
import Questions from "./widgets/Questions.vue";
|
||||||
import RequestDemo from './widgets/RequestDemo.vue'
|
import RequestDemo from "./widgets/RequestDemo.vue";
|
||||||
import Navigation from './widgets/Navigation.vue'
|
import Navigation from "./widgets/Navigation.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -4,35 +4,51 @@
|
||||||
<VaIcon color="secondary" name="mso-search" />
|
<VaIcon color="secondary" name="mso-search" />
|
||||||
</template>
|
</template>
|
||||||
</VaInput>
|
</VaInput>
|
||||||
<section v-if="filteredCategories.length" class="grid grid-cols-3 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-5">
|
<section
|
||||||
|
v-if="filteredCategories.length"
|
||||||
|
class="grid grid-cols-3 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-5"
|
||||||
|
>
|
||||||
<template v-for="category in filteredCategories" :key="category.id">
|
<template v-for="category in filteredCategories" :key="category.id">
|
||||||
<VaCard class="col-span-3 md:col-span-1 min-h-[146px]" href="#">
|
<VaCard class="col-span-3 md:col-span-1 min-h-[146px]" href="#">
|
||||||
<VaCardContent class="leading-5 text-sm">
|
<VaCardContent class="leading-5 text-sm">
|
||||||
<VaIcon :name="`mso-${category.icon}`" class="font-light mb-2" color="primary" size="2rem" />
|
<VaIcon
|
||||||
<h2 class="text-primary mb-2 text-primary text-lg leading-7 font-bold">{{ category.name }}</h2>
|
:name="`mso-${category.icon}`"
|
||||||
|
class="font-light mb-2"
|
||||||
|
color="primary"
|
||||||
|
size="2rem"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
class="text-primary mb-2 text-primary text-lg leading-7 font-bold"
|
||||||
|
>
|
||||||
|
{{ category.name }}
|
||||||
|
</h2>
|
||||||
<p>{{ category.intro }}</p>
|
<p>{{ category.intro }}</p>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
</VaCard>
|
</VaCard>
|
||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
<VaAlert v-else class="mb-4 leading-5" color="info" outline>
|
<VaAlert v-else class="mb-4 leading-5" color="info" outline>
|
||||||
No matches found. Try refining your search or browse through the categories to find the help you need.
|
No matches found. Try refining your search or browse through the categories
|
||||||
|
to find the help you need.
|
||||||
</VaAlert>
|
</VaAlert>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import categories from '../data/popularCategories.json'
|
import categories from "../data/popularCategories.json";
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from "vue";
|
||||||
|
|
||||||
const searchValue = ref('')
|
const searchValue = ref("");
|
||||||
|
|
||||||
const filteredCategories = computed(() => {
|
const filteredCategories = computed(() => {
|
||||||
const value = searchValue.value.trim().toLowerCase()
|
const value = searchValue.value.trim().toLowerCase();
|
||||||
if (value.length === 0) {
|
if (value.length === 0) {
|
||||||
return categories
|
return categories;
|
||||||
}
|
}
|
||||||
return categories.filter((category) => {
|
return categories.filter((category) => {
|
||||||
return category.intro.toLowerCase().includes(value) || category.name.toLowerCase().includes(value)
|
return (
|
||||||
})
|
category.intro.toLowerCase().includes(value) ||
|
||||||
})
|
category.name.toLowerCase().includes(value)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@
|
||||||
<div v-for="section in navSections" :key="section" class="mb-6 md:mb-0">
|
<div v-for="section in navSections" :key="section" class="mb-6 md:mb-0">
|
||||||
<h3 class="h5 mb-4">{{ section }}</h3>
|
<h3 class="h5 mb-4">{{ section }}</h3>
|
||||||
<ul class="leading-5">
|
<ul class="leading-5">
|
||||||
<li v-for="item in navigation[section]" :key="`${section}-${item.name}`" class="mb-4">
|
<li
|
||||||
|
v-for="item in navigation[section]"
|
||||||
|
:key="`${section}-${item.name}`"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
<a class="va-link" href="#">{{ item.name }}</a>
|
<a class="va-link" href="#">{{ item.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -14,10 +18,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import navigation from '../data/navigationLinks.json'
|
import navigation from "../data/navigationLinks.json";
|
||||||
|
|
||||||
const navSections = computed(() => {
|
const navSections = computed(() => {
|
||||||
return Object.keys(navigation)
|
return Object.keys(navigation);
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,31 @@
|
||||||
<VaCard class="mb-4">
|
<VaCard class="mb-4">
|
||||||
<VaCardContent>
|
<VaCardContent>
|
||||||
<h2 class="va-h5">Popular questions</h2>
|
<h2 class="va-h5">Popular questions</h2>
|
||||||
<VaAccordion v-model="accordionState" :style="{ '--va-collapse-padding': '1rem 0' }" class="mb-1">
|
<VaAccordion
|
||||||
|
v-model="accordionState"
|
||||||
|
:style="{ '--va-collapse-padding': '1rem 0' }"
|
||||||
|
class="mb-1"
|
||||||
|
>
|
||||||
<VaCollapse header="How do I reload a page?">
|
<VaCollapse header="How do I reload a page?">
|
||||||
<article class="max-w-3xl leading-5">
|
<article class="max-w-3xl leading-5">
|
||||||
<p class="mb-2">
|
<p class="mb-2">
|
||||||
Get ready for some page-refreshing wisdom! We're about to dive into the magical world of reloading web
|
Get ready for some page-refreshing wisdom! We're about to dive
|
||||||
pages. Here are some techniques:
|
into the magical world of reloading web pages. Here are some
|
||||||
|
techniques:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc list-inside leading-5 ml-4">
|
<ul class="list-disc list-inside leading-5 ml-4">
|
||||||
<li>
|
<li>
|
||||||
Press the F5 key or Ctrl + R (Windows/Linux) or Command + R (Mac) to manually reload the current page.
|
Press the F5 key or Ctrl + R (Windows/Linux) or Command + R
|
||||||
|
(Mac) to manually reload the current page.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Click the reload button in your browser. It usually looks like a circular arrow and is typically located
|
Click the reload button in your browser. It usually looks like a
|
||||||
in the address bar.
|
circular arrow and is typically located in the address bar.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Use JavaScript to reload the page programmatically:
|
||||||
|
location.reload();
|
||||||
</li>
|
</li>
|
||||||
<li>Use JavaScript to reload the page programmatically: location.reload();</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
</VaCollapse>
|
</VaCollapse>
|
||||||
|
|
@ -25,16 +34,19 @@
|
||||||
<VaCollapse header="What is a Secure Key?">
|
<VaCollapse header="What is a Secure Key?">
|
||||||
<article class="max-w-3xl text-sm">
|
<article class="max-w-3xl text-sm">
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
A Secure Key is an extra layer of security that helps look after you and your money by generating a
|
A Secure Key is an extra layer of security that helps look after
|
||||||
unique, single use security code every time you log on or authorise a payment. There are two types of
|
you and your money by generating a unique, single use security
|
||||||
Secure Key - a Digital version that works as part of the Mobile Banking App - perfect if you have a
|
code every time you log on or authorise a payment. There are two
|
||||||
smartphone or tablet. Or, you can use a Physical version that is a little key ring that looks like a mini
|
types of Secure Key - a Digital version that works as part of the
|
||||||
calculator.
|
Mobile Banking App - perfect if you have a smartphone or tablet.
|
||||||
|
Or, you can use a Physical version that is a little key ring that
|
||||||
|
looks like a mini calculator.
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
We recommend setting up a Digital Secure Key. It’s built into the first direct Banking App, and if you
|
We recommend setting up a Digital Secure Key. It’s built into the
|
||||||
have the right type of smartphone you can use your fingerprint or face recognition to seamlessly log on or
|
first direct Banking App, and if you have the right type of
|
||||||
authorise payments.
|
smartphone you can use your fingerprint or face recognition to
|
||||||
|
seamlessly log on or authorise payments.
|
||||||
</p>
|
</p>
|
||||||
<a class="va-link font-semibold" href="#"
|
<a class="va-link font-semibold" href="#"
|
||||||
>Find out more about Secure Keys
|
>Find out more about Secure Keys
|
||||||
|
|
@ -46,68 +58,87 @@
|
||||||
<VaCollapse header="How do I report fraud?">
|
<VaCollapse header="How do I report fraud?">
|
||||||
<article class="max-w-3xl text-sm">
|
<article class="max-w-3xl text-sm">
|
||||||
<p class="mb-3">
|
<p class="mb-3">
|
||||||
Reporting fraud is a serious matter, and it's important to take appropriate steps to address and prevent
|
Reporting fraud is a serious matter, and it's important to take
|
||||||
fraudulent activities. The specific process for reporting fraud may vary based on your location and the
|
appropriate steps to address and prevent fraudulent activities.
|
||||||
nature of the fraud, but here's a general guide:
|
The specific process for reporting fraud may vary based on your
|
||||||
|
location and the nature of the fraud, but here's a general guide:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc list-inside leading-6 ml-4 mb-4">
|
<ul class="list-disc list-inside leading-6 ml-4 mb-4">
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Report to Relevant Authorities:</span> Depending on the nature of the fraud,
|
<span class="font-semibold"
|
||||||
you may need to report to other relevant authorities, such as the Securities and Exchange Commission
|
>Report to Relevant Authorities:</span
|
||||||
(SEC) for investment-related fraud or the Better Business Bureau (BBB) for scams and deceptive
|
>
|
||||||
practices.
|
Depending on the nature of the fraud, you may need to report to
|
||||||
|
other relevant authorities, such as the Securities and Exchange
|
||||||
|
Commission (SEC) for investment-related fraud or the Better
|
||||||
|
Business Bureau (BBB) for scams and deceptive practices.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Document All Details:</span> Make sure to document all the relevant details
|
<span class="font-semibold">Document All Details:</span> Make
|
||||||
about the fraud, including dates, times, names of individuals involved (if known), financial
|
sure to document all the relevant details about the fraud,
|
||||||
transactions, and any communication related to the fraud.
|
including dates, times, names of individuals involved (if
|
||||||
|
known), financial transactions, and any communication related to
|
||||||
|
the fraud.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Report to Internet Crime Organizations:</span> If the fraud involves online
|
<span class="font-semibold"
|
||||||
activities, consider reporting to organizations that deal with internet crimes, such as the Internet
|
>Report to Internet Crime Organizations:</span
|
||||||
Crime Complaint Center (IC3). IC3 is a partnership between the FBI and the National White Collar Crime
|
>
|
||||||
Center.
|
If the fraud involves online activities, consider reporting to
|
||||||
|
organizations that deal with internet crimes, such as the
|
||||||
|
Internet Crime Complaint Center (IC3). IC3 is a partnership
|
||||||
|
between the FBI and the National White Collar Crime Center.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
Remember to act promptly and follow the specific reporting procedures relevant to your location and the
|
Remember to act promptly and follow the specific reporting
|
||||||
type of fraud you've encountered. Taking swift action can help mitigate potential damage and prevent
|
procedures relevant to your location and the type of fraud you've
|
||||||
further fraudulent activities.
|
encountered. Taking swift action can help mitigate potential
|
||||||
|
damage and prevent further fraudulent activities.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</VaCollapse>
|
</VaCollapse>
|
||||||
|
|
||||||
<VaCollapse :style="{ '--va-background-border': 'transparent' }" header="How to download statements?">
|
<VaCollapse
|
||||||
|
:style="{ '--va-background-border': 'transparent' }"
|
||||||
|
header="How to download statements?"
|
||||||
|
>
|
||||||
<article class="max-w-3xl text-sm">
|
<article class="max-w-3xl text-sm">
|
||||||
<p class="mb-3">
|
<p class="mb-3">
|
||||||
Downloading statements can vary depending on the type of statements you're referring to (e.g., bank
|
Downloading statements can vary depending on the type of
|
||||||
statements, credit card statements, utility statements). Below are general steps to download statements
|
statements you're referring to (e.g., bank statements, credit card
|
||||||
from various platforms:
|
statements, utility statements). Below are general steps to
|
||||||
|
download statements from various platforms:
|
||||||
</p>
|
</p>
|
||||||
<ul class="list-disc list-inside leading-6 ml-4 mb-4">
|
<ul class="list-disc list-inside leading-6 ml-4 mb-4">
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Ensure Secure Connection:</span> Always access and download statements from
|
<span class="font-semibold">Ensure Secure Connection:</span>
|
||||||
a secure and trusted network to protect your sensitive financial information.
|
Always access and download statements from a secure and trusted
|
||||||
|
network to protect your sensitive financial information.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Regularly Check Statements:</span> Review your statements regularly to
|
<span class="font-semibold">Regularly Check Statements:</span>
|
||||||
verify transactions, detect errors, or identify any suspicious activity promptly.
|
Review your statements regularly to verify transactions, detect
|
||||||
|
errors, or identify any suspicious activity promptly.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Verify Account Details:</span> Ensure that the statements you download
|
<span class="font-semibold">Verify Account Details:</span>
|
||||||
correspond to the correct account, including account numbers and account names.
|
Ensure that the statements you download correspond to the
|
||||||
|
correct account, including account numbers and account names.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="font-semibold">Use Official Channels:</span> Download statements only from official and
|
<span class="font-semibold">Use Official Channels:</span>
|
||||||
authorized platforms, such as your bank's official website or the secure online portals of service
|
Download statements only from official and authorized platforms,
|
||||||
providers.
|
such as your bank's official website or the secure online
|
||||||
|
portals of service providers.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
Always ensure that you follow the specific steps and instructions provided by the respective service
|
Always ensure that you follow the specific steps and instructions
|
||||||
provider to download statements securely and accurately. If you encounter any difficulties or have
|
provided by the respective service provider to download statements
|
||||||
specific questions, consider reaching out to the customer support of the respective institution or
|
securely and accurately. If you encounter any difficulties or have
|
||||||
service.
|
specific questions, consider reaching out to the customer support
|
||||||
|
of the respective institution or service.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</VaCollapse>
|
</VaCollapse>
|
||||||
|
|
@ -117,7 +148,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive } from 'vue'
|
import { reactive } from "vue";
|
||||||
|
|
||||||
const accordionState = reactive([false, true, false, false])
|
const accordionState = reactive([false, true, false, false]);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
<div>
|
<div>
|
||||||
<VaCardContent>
|
<VaCardContent>
|
||||||
<h2 class="va-h5">Got questions?</h2>
|
<h2 class="va-h5">Got questions?</h2>
|
||||||
<p class="text-base leading-5">Request a free demo to have all your questions answered by an expert.</p>
|
<p class="text-base leading-5">
|
||||||
|
Request a free demo to have all your questions answered by an expert.
|
||||||
|
</p>
|
||||||
</VaCardContent>
|
</VaCardContent>
|
||||||
<VaCardActions align="left">
|
<VaCardActions align="left">
|
||||||
<VaButton @click="showModal = !showModal">Request a demo</VaButton>
|
<VaButton @click="showModal = !showModal">Request a demo</VaButton>
|
||||||
|
|
@ -11,15 +13,25 @@
|
||||||
</div>
|
</div>
|
||||||
<img alt="Send a message" src="../request-demo.svg" />
|
<img alt="Send a message" src="../request-demo.svg" />
|
||||||
</VaCard>
|
</VaCard>
|
||||||
<VaModal v-model="showModal" :before-ok="submit" close-button ok-text="Request demo" size="small">
|
<VaModal
|
||||||
|
v-model="showModal"
|
||||||
|
:before-ok="submit"
|
||||||
|
close-button
|
||||||
|
ok-text="Request demo"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
<VaForm ref="form" @submit.prevent="submit">
|
<VaForm ref="form" @submit.prevent="submit">
|
||||||
<h3 class="va-h3">Request free demo</h3>
|
<h3 class="va-h3">Request free demo</h3>
|
||||||
<p class="text-base mb-4 leading-5">
|
<p class="text-base mb-4 leading-5">
|
||||||
Claim your spot now and ignite innovation with our exceptional software solution! 🔥
|
Claim your spot now and ignite innovation with our exceptional software
|
||||||
|
solution! 🔥
|
||||||
</p>
|
</p>
|
||||||
<VaInput
|
<VaInput
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:rules="[(v) => !!v || 'Email field is required', (v) => /.+@.+\..+/.test(v) || 'Email should be valid']"
|
:rules="[
|
||||||
|
(v) => !!v || 'Email field is required',
|
||||||
|
(v) => /.+@.+\..+/.test(v) || 'Email should be valid',
|
||||||
|
]"
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
label="Email"
|
label="Email"
|
||||||
type="email"
|
type="email"
|
||||||
|
|
@ -29,24 +41,24 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
|
|
||||||
import { useForm, useToast } from 'vuestic-ui'
|
import { useForm, useToast } from "vuestic-ui";
|
||||||
|
|
||||||
const showModal = ref(false)
|
const showModal = ref(false);
|
||||||
const email = ref('')
|
const email = ref("");
|
||||||
const { validate } = useForm('form')
|
const { validate } = useForm("form");
|
||||||
const { init } = useToast()
|
const { init } = useToast();
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!validate()) {
|
if (!validate()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
init({
|
init({
|
||||||
title: 'Demo Request Submitted!',
|
title: "Demo Request Submitted!",
|
||||||
message: 'An expert will get in touch soon',
|
message: "An expert will get in touch soon",
|
||||||
color: 'success',
|
color: "success",
|
||||||
})
|
});
|
||||||
showModal.value = false
|
showModal.value = false;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import PaymentCardList from './widgets/my-cards/PaymentCardList.vue'
|
import PaymentCardList from "./widgets/my-cards/PaymentCardList.vue";
|
||||||
import BillingAddressList from './widgets/billing-address/BillingAddressList.vue'
|
import BillingAddressList from "./widgets/billing-address/BillingAddressList.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import PaymentSystem from './PaymentSystem.vue'
|
import PaymentSystem from "./PaymentSystem.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'PaymentSystem',
|
title: "PaymentSystem",
|
||||||
component: PaymentSystem,
|
component: PaymentSystem,
|
||||||
tags: ['autodocs'],
|
tags: ["autodocs"],
|
||||||
}
|
};
|
||||||
|
|
||||||
export const Default = () => ({
|
export const Default = () => ({
|
||||||
components: { PaymentSystem },
|
components: { PaymentSystem },
|
||||||
|
|
@ -13,4 +13,4 @@ export const Default = () => ({
|
||||||
<br>
|
<br>
|
||||||
<PaymentSystem type="mastercard"/>
|
<PaymentSystem type="mastercard"/>
|
||||||
`,
|
`,
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-20 h-12 bg-backgroundElement rounded flex justify-center items-center align-bottom">
|
<div
|
||||||
<img v-if="props.type === 'mastercard'" alt="Mastercard Logo" src="./mastercard.png" />
|
class="w-20 h-12 bg-backgroundElement rounded flex justify-center items-center align-bottom"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="props.type === 'mastercard'"
|
||||||
|
alt="Mastercard Logo"
|
||||||
|
src="./mastercard.png"
|
||||||
|
/>
|
||||||
<img v-if="props.type === 'visa'" alt="Visa Logo" src="./visa.png" />
|
<img v-if="props.type === 'visa'" alt="Visa Logo" src="./visa.png" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PaymentSystemType } from '../types'
|
import { PaymentSystemType } from "../types";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
type: PaymentSystemType
|
type: PaymentSystemType;
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
export enum PaymentSystemType {
|
export enum PaymentSystemType {
|
||||||
Visa = 'visa',
|
Visa = "visa",
|
||||||
MasterCard = 'mastercard',
|
MasterCard = "mastercard",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const paymentSystemTypeOptions = Object.values(PaymentSystemType)
|
export const paymentSystemTypeOptions = Object.values(PaymentSystemType);
|
||||||
|
|
||||||
export interface PaymentCard {
|
export interface PaymentCard {
|
||||||
id: string
|
id: string;
|
||||||
name: string
|
name: string;
|
||||||
isPrimary: boolean // show Primary badge
|
isPrimary: boolean; // show Primary badge
|
||||||
paymentSystem: PaymentSystemType // Enum or union type for various payment systems
|
paymentSystem: PaymentSystemType; // Enum or union type for various payment systems
|
||||||
cardNumberMasked: string // ****1679
|
cardNumberMasked: string; // ****1679
|
||||||
expirationDate: string // 09/24
|
expirationDate: string; // 09/24
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BillingAddress {
|
export interface BillingAddress {
|
||||||
id: string
|
id: string;
|
||||||
name: string
|
name: string;
|
||||||
isPrimary: boolean // show Primary badge
|
isPrimary: boolean; // show Primary badge
|
||||||
street: string
|
street: string;
|
||||||
city: string
|
city: string;
|
||||||
state: string
|
state: string;
|
||||||
postalCode: string
|
postalCode: string;
|
||||||
country: string
|
country: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue