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"/>`,
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,44 +8,100 @@
|
||||||
</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"
|
||||||
|
height="28"
|
||||||
|
width="233"
|
||||||
|
y="-1"
|
||||||
|
x="-1"
|
||||||
|
/>
|
||||||
|
<g
|
||||||
|
display="none"
|
||||||
|
overflow="visible"
|
||||||
|
y="0"
|
||||||
|
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>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<title>Layer 1</title>
|
<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
|
||||||
<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"/>
|
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"
|
||||||
<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"/>
|
id="svg_2"
|
||||||
<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>
|
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>
|
</g>
|
||||||
</svg>
|
</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,
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,10 @@
|
||||||
"auth": "Auth",
|
"auth": "Auth",
|
||||||
"buttons": "Buttons",
|
"buttons": "Buttons",
|
||||||
"timelines": "Timelines",
|
"timelines": "Timelines",
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Статистика",
|
||||||
"billing": "Billing",
|
"billing": "Billing",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"preferences": "Account preferences",
|
"preferences": "Настройки Аккаунта",
|
||||||
"payments": "Payments",
|
"payments": "Payments",
|
||||||
"settings": "Application settings",
|
"settings": "Application settings",
|
||||||
"pricing-plans": "Pricing plans",
|
"pricing-plans": "Pricing plans",
|
||||||
|
|
@ -75,14 +75,14 @@
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"language": "Change language",
|
"language": "Change language",
|
||||||
"logout": "Logout",
|
"logout": "Выход",
|
||||||
"profile": "Profile",
|
"profile": "Профиль",
|
||||||
"settings": "Settings",
|
"settings": "Настройки",
|
||||||
"billing": "Billing",
|
"billing": "Платежы",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"helpAndSupport": "Help & support",
|
"helpAndSupport": "Помощь",
|
||||||
"projects": "Projects",
|
"projects": "Projects",
|
||||||
"account": "Account",
|
"account": "Аккаунт",
|
||||||
"explore": "Explore"
|
"explore": "Explore"
|
||||||
},
|
},
|
||||||
"treeView": {
|
"treeView": {
|
||||||
|
|
@ -175,4 +175,3 @@
|
||||||
"light": "Light"
|
"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
|
||||||
|
ref="password2"
|
||||||
|
v-model="formData.repeatPassword"
|
||||||
|
:rules="[
|
||||||
(v) => !!v || 'Поле повторите пароль обязательное',
|
(v) => !!v || 'Поле повторите пароль обязательное',
|
||||||
(v) => v === formData.password || 'Пароли не совпадают',
|
(v) => v === formData.password || 'Пароли не совпадают',
|
||||||
]" :type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Повторите пароль"
|
]"
|
||||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
: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