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>
|
||||
<meta charset="UTF-8" />
|
||||
<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 href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<link
|
||||
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
|
||||
rel="stylesheet"
|
||||
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": {
|
||||
"prepare": "husky install",
|
||||
"dev": "vite",
|
||||
"build": "npm run lint && vue-tsc --noEmit && vite build",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"build:ci": "vite build",
|
||||
"start:ci": "serve -s ./dist",
|
||||
"prelint": "npm run format",
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
"vuestic-ui": "^1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.0.0",
|
||||
"@intlify/unplugin-vue-i18n": "^1.5.0",
|
||||
"@storybook/addon-essentials": "^7.4.6",
|
||||
"@storybook/addon-interactions": "^7.4.6",
|
||||
|
|
@ -58,10 +59,12 @@
|
|||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-react": "^7.34.1",
|
||||
"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",
|
||||
"lint-staged": "^15.1.0",
|
||||
"postcss": "^8.4.21",
|
||||
|
|
@ -69,6 +72,7 @@
|
|||
"storybook": "^7.4.6",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^7.6.0",
|
||||
"vite": "^4.4.6",
|
||||
"vue-eslint-parser": "^9.3.2",
|
||||
"vue-tsc": "^1.8.22"
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ module.exports = {
|
|||
tailwindcss: {},
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'scss/main.scss';
|
||||
@import "scss/main.scss";
|
||||
|
||||
#app {
|
||||
font-family: 'Inter', Avenir, Helvetica, Arial, sans-serif;
|
||||
font-family: "Inter", Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<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
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import VuesticLogo from './VuesticLogo.vue'
|
||||
import VuesticLogo from "./VuesticLogo.vue";
|
||||
|
||||
export default {
|
||||
title: 'VuesticLogo',
|
||||
title: "VuesticLogo",
|
||||
component: VuesticLogo,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
components: { VuesticLogo },
|
||||
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" />`,
|
||||
})
|
||||
});
|
||||
|
||||
export const White = () => ({
|
||||
components: { VuesticLogo },
|
||||
template: `<div class="bg-primary">
|
||||
<VuesticLogo start="#FFF"/>
|
||||
</div>`,
|
||||
})
|
||||
});
|
||||
|
||||
export const Blue = () => ({
|
||||
components: { VuesticLogo },
|
||||
template: `<VuesticLogo start="#0E41C9"/>`,
|
||||
})
|
||||
});
|
||||
|
||||
export const Height = () => ({
|
||||
components: { VuesticLogo },
|
||||
template: `<VuesticLogo start="#6B7AFE" end="#083CC6" :height="48"/>`,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,51 +1,107 @@
|
|||
<template>
|
||||
<svg width="231" height="26" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Created with SVG Editor - http://github.com/mzalive/SVG Editor/ -->
|
||||
<defs>
|
||||
<filter height="200%" width="200%" y="-50%" x="-50%" id="svg_8_blur">
|
||||
<feGaussianBlur stdDeviation="0.6" in="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" 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>
|
||||
<title>Layer 1</title>
|
||||
<path d="m280.5,281.4375c0,0 -2.054138,0.567322 -5,0c-5.287994,-1.018372 -7,-2 -8,-3c-1,-1 -1.289795,-3.042908 -1,-4c1.04483,-3.450836 4.891724,-5.19577 12,-9c7.885925,-4.220428 13.207825,-6.930664 21,-9c2.899506,-0.770004 4,-1 3,-1c-5,0 -21,0 -40,0c-7,0 -17,-2 -17,0c0,4 10.003876,0.232941 17,0c15.024963,-0.500275 22.824158,1.062897 30,-3c1.230652,-0.696777 -3,-1 -9,-1c-13,0 -21,0 -24,1l-2,1" id="svg_2" stroke-width="1.5" stroke="#fff" fill="none"/>
|
||||
<path d="m192.5,194.4375c1,0 2.74707,1.61702 7,3c5.784607,1.881058 9.04863,2.814651 17,5c10.067017,2.766815 21,5 27,5c8,0 12,0 19,1l5,0l2,0" id="svg_3" stroke-width="1.5" stroke="#fff" fill="none"/>
|
||||
<path d="m251.5,198.4375c-1,0 -1.934143,0.144287 -4,1c-5.843124,2.420303 -8.925797,2.497559 -14,3c-5.970795,0.591232 -11,0 -13,0c-4,0 -7,0 -9,0l-1,0l-2,0l0,-1" id="svg_4" stroke-width="1.5" stroke="#fff" fill="none"/>
|
||||
<text font-style="normal" font-weight="normal" filter="url(#svg_8_blur)" opacity="0.78" xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="26" id="svg_8" y="21.4375" x="27" stroke="#000ecc" fill="#ffffff">CYCLE-RIDER</text>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- Created with SVG Editor - http://github.com/mzalive/SVG Editor/ -->
|
||||
<defs>
|
||||
<filter height="200%" width="200%" y="-50%" x="-50%" id="svg_8_blur">
|
||||
<feGaussianBlur stdDeviation="0.6" in="SourceGraphic" />
|
||||
</filter>
|
||||
</defs>
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect
|
||||
fill="none"
|
||||
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>
|
||||
<title>Layer 1</title>
|
||||
<path
|
||||
d="m280.5,281.4375c0,0 -2.054138,0.567322 -5,0c-5.287994,-1.018372 -7,-2 -8,-3c-1,-1 -1.289795,-3.042908 -1,-4c1.04483,-3.450836 4.891724,-5.19577 12,-9c7.885925,-4.220428 13.207825,-6.930664 21,-9c2.899506,-0.770004 4,-1 3,-1c-5,0 -21,0 -40,0c-7,0 -17,-2 -17,0c0,4 10.003876,0.232941 17,0c15.024963,-0.500275 22.824158,1.062897 30,-3c1.230652,-0.696777 -3,-1 -9,-1c-13,0 -21,0 -24,1l-2,1"
|
||||
id="svg_2"
|
||||
stroke-width="1.5"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="m192.5,194.4375c1,0 2.74707,1.61702 7,3c5.784607,1.881058 9.04863,2.814651 17,5c10.067017,2.766815 21,5 27,5c8,0 12,0 19,1l5,0l2,0"
|
||||
id="svg_3"
|
||||
stroke-width="1.5"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="m251.5,198.4375c-1,0 -1.934143,0.144287 -4,1c-5.843124,2.420303 -8.925797,2.497559 -14,3c-5.970795,0.591232 -11,0 -13,0c-4,0 -7,0 -9,0l-1,0l-2,0l0,-1"
|
||||
id="svg_4"
|
||||
stroke-width="1.5"
|
||||
stroke="#fff"
|
||||
fill="none"
|
||||
/>
|
||||
<text
|
||||
font-style="normal"
|
||||
font-weight="normal"
|
||||
filter="url(#svg_8_blur)"
|
||||
opacity="0.78"
|
||||
xml:space="preserve"
|
||||
text-anchor="start"
|
||||
font-family="Helvetica, Arial, sans-serif"
|
||||
font-size="26"
|
||||
id="svg_8"
|
||||
y="21.4375"
|
||||
x="27"
|
||||
stroke="#000ecc"
|
||||
fill="#ffffff"
|
||||
>
|
||||
CYCLE-RIDER
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
import { computed } from "vue";
|
||||
import { useColors } from "vuestic-ui";
|
||||
|
||||
const { getColor } = useColors()
|
||||
const { getColor } = useColors();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
height?: number
|
||||
start?: string
|
||||
end?: string
|
||||
height?: number;
|
||||
start?: string;
|
||||
end?: string;
|
||||
}>(),
|
||||
{
|
||||
height: 18,
|
||||
start: 'primary',
|
||||
start: "primary",
|
||||
end: undefined,
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
const colorsComputed = computed(() => {
|
||||
return {
|
||||
start: getColor(props.start),
|
||||
end: getColor(props.end || props.start),
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<nav class="flex items-center">
|
||||
<VaBreadcrumbs>
|
||||
<VaBreadcrumbsItem label="Home" :to="{ name: 'dashboard' }" />
|
||||
<VaBreadcrumbsItem label="Начало" :to="{ name: 'dashboard' }" />
|
||||
<VaBreadcrumbsItem
|
||||
v-for="item in items"
|
||||
:key="item.label"
|
||||
|
|
@ -22,71 +22,71 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
import VaIconMenuCollapsed from '../icons/VaIconMenuCollapsed.vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useGlobalStore } from '../../stores/global-store'
|
||||
import NavigationRoutes from '../sidebar/NavigationRoutes'
|
||||
import { computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useColors } from "vuestic-ui";
|
||||
import VaIconMenuCollapsed from "../icons/VaIconMenuCollapsed.vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useGlobalStore } from "../../stores/global-store";
|
||||
import NavigationRoutes from "../sidebar/NavigationRoutes";
|
||||
|
||||
const { isSidebarMinimized } = storeToRefs(useGlobalStore())
|
||||
const { isSidebarMinimized } = storeToRefs(useGlobalStore());
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
type BreadcrumbNavigationItem = {
|
||||
label: string
|
||||
to: string
|
||||
hasChildren: boolean
|
||||
}
|
||||
label: string;
|
||||
to: string;
|
||||
hasChildren: boolean;
|
||||
};
|
||||
|
||||
const findRouteName = (name: string) => {
|
||||
const traverse = (routers: any[]): string => {
|
||||
for (const router of routers) {
|
||||
if (router.name === name) {
|
||||
return router.displayName
|
||||
return router.displayName;
|
||||
}
|
||||
if (router.children) {
|
||||
const result = traverse(router.children)
|
||||
const result = traverse(router.children);
|
||||
if (result) {
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
return traverse(NavigationRoutes.routes)
|
||||
}
|
||||
return traverse(NavigationRoutes.routes);
|
||||
};
|
||||
|
||||
const items = computed(() => {
|
||||
const result: { label: string; to: string; hasChildren: boolean }[] = []
|
||||
const result: { label: string; to: string; hasChildren: boolean }[] = [];
|
||||
route.matched.forEach((route) => {
|
||||
const labelKey = findRouteName(route.name as string)
|
||||
const labelKey = findRouteName(route.name as string);
|
||||
if (!labelKey) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
result.push({
|
||||
label: t(labelKey),
|
||||
to: route.path,
|
||||
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) => {
|
||||
if (!item.hasChildren) {
|
||||
router.push(item.to)
|
||||
router.push(item.to);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
<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 />
|
||||
<title>overview_icon_4</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<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
|
||||
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"
|
||||
|
|
@ -13,7 +20,10 @@
|
|||
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"
|
||||
/>
|
||||
<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
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@
|
|||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
color?: string;
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
color: "inherit",
|
||||
},
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
<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 -->
|
||||
<title>62EBC3B8-A55C-4B01-95A2-52FB8EDD4150</title>
|
||||
<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>
|
||||
<path
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<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 />
|
||||
<title>overview_icon_2</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<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 />
|
||||
<title>overview_icon_5</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<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
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
<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)">
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<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" />
|
||||
<path :stroke="color" d="M4 9l-3 3 3 3" stroke-width="2" />
|
||||
</g>
|
||||
|
|
@ -13,12 +22,12 @@
|
|||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
color?: string;
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
color: "inherit",
|
||||
},
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<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">
|
||||
<path d="M0 0h24v24H0z" />
|
||||
<rect :fill="color" height="2" rx="1" width="20" x="2" y="3" />
|
||||
|
|
@ -15,12 +21,12 @@
|
|||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
color?: string;
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
color: "inherit",
|
||||
},
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<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
|
||||
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"
|
||||
|
|
@ -10,12 +16,12 @@
|
|||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
color?: string;
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
color: "inherit",
|
||||
},
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@
|
|||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
color?: string;
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
color: "inherit",
|
||||
},
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
<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 />
|
||||
<title>overview_icon_3</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<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" />
|
||||
<path class="cls-2" d="M40,19V0H8V11H0V49H47.5V19ZM3,46V14H8V46Zm34,0H11V3H37Zm7.5,0H40V22h4.5Z" />
|
||||
<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"
|
||||
/>
|
||||
<path
|
||||
class="cls-2"
|
||||
d="M40,19V0H8V11H0V49H47.5V19ZM3,46V14H8V46Zm34,0H11V3H37Zm7.5,0H40V22h4.5Z"
|
||||
/>
|
||||
<circle class="cls-2" cx="24" cy="41" r="2.67" />
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<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 />
|
||||
<title>overview_icon_6</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
|
|
@ -9,7 +13,10 @@
|
|||
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"
|
||||
/>
|
||||
<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
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
<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 -->
|
||||
<title>67046716-A590-445C-AC65-1EEF69089C00</title>
|
||||
<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>
|
||||
<path
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<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 />
|
||||
<title>overview_icon_1</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
<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>
|
||||
<linearGradient :id="'ORIGINAL'" x1="0%" y1="50%" y2="50%">
|
||||
<stop offset="0%" stop-color="#4AE387" />
|
||||
|
|
@ -25,17 +31,17 @@
|
|||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'VaIconVuestic',
|
||||
inject: ['contextConfig'],
|
||||
name: "VaIconVuestic",
|
||||
inject: ["contextConfig"],
|
||||
computed: {
|
||||
themeGradientId() {
|
||||
return this.contextConfig.invertedColor ? 'CORPORATE' : 'ORIGINAL'
|
||||
return this.contextConfig.invertedColor ? "CORPORATE" : "ORIGINAL";
|
||||
},
|
||||
textColor() {
|
||||
return this.contextConfig.invertedColor ? '#6E85E8' : '#E4FF32'
|
||||
return this.contextConfig.invertedColor ? "#6E85E8" : "#E4FF32";
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -23,18 +23,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useGlobalStore } from '../../stores/global-store'
|
||||
import AppNavbarActions from './components/AppNavbarActions.vue'
|
||||
import VuesticLogo from '../VuesticLogo.vue'
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useGlobalStore } from "../../stores/global-store";
|
||||
import AppNavbarActions from "./components/AppNavbarActions.vue";
|
||||
import VuesticLogo from "../VuesticLogo.vue";
|
||||
|
||||
defineProps({
|
||||
isMobile: { type: Boolean, default: false },
|
||||
})
|
||||
});
|
||||
|
||||
const GlobalStore = useGlobalStore()
|
||||
const GlobalStore = useGlobalStore();
|
||||
|
||||
const { isSidebarMinimized } = storeToRefs(GlobalStore)
|
||||
const { isSidebarMinimized } = storeToRefs(GlobalStore);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -22,21 +22,23 @@
|
|||
{{ t('helpAndSupport') }}
|
||||
</VaButton> -->
|
||||
<!-- <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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ProfileDropdown from './dropdowns/ProfileDropdown.vue'
|
||||
import NotificationDropdown from './dropdowns/NotificationDropdown.vue'
|
||||
import GithubButton from './GitHubButton.vue'
|
||||
import ProfileDropdown from "./dropdowns/ProfileDropdown.vue";
|
||||
import NotificationDropdown from "./dropdowns/NotificationDropdown.vue";
|
||||
import GithubButton from "./GitHubButton.vue";
|
||||
|
||||
defineProps({
|
||||
isMobile: { type: Boolean, default: false },
|
||||
})
|
||||
});
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
import { useI18n } from "vue-i18n";
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import VaIconGitHub from '../../icons/VaIconGitHub.vue'
|
||||
import VaIconGitHub from "../../icons/VaIconGitHub.vue";
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<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>
|
||||
<VaButton preset="secondary" color="textPrimary">
|
||||
<VaBadge overlap>
|
||||
|
|
@ -11,7 +16,10 @@
|
|||
<VaDropdownContent class="h-full sm:max-w-[420px] sm:h-auto">
|
||||
<section class="sm:max-h-[320px] p-4 overflow-auto">
|
||||
<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">
|
||||
<VaListItemSection icon class="mx-0 p-0">
|
||||
<VaIcon :name="item.icon" color="secondary" />
|
||||
|
|
@ -23,12 +31,25 @@
|
|||
{{ item.updateTimestamp }}
|
||||
</VaListItemSection>
|
||||
</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>
|
||||
</VaList>
|
||||
|
||||
<VaButton preset="primary" class="w-full" @click="displayAllNotifications = !displayAllNotifications"
|
||||
>{{ displayAllNotifications ? t('notifications.less') : t('notifications.all') }}
|
||||
<VaButton
|
||||
preset="primary"
|
||||
class="w-full"
|
||||
@click="displayAllNotifications = !displayAllNotifications"
|
||||
>{{
|
||||
displayAllNotifications
|
||||
? t("notifications.less")
|
||||
: t("notifications.all")
|
||||
}}
|
||||
</VaButton>
|
||||
</section>
|
||||
</VaDropdownContent>
|
||||
|
|
@ -36,87 +57,92 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import VaIconNotification from '../../../icons/VaIconNotification.vue'
|
||||
import { ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import VaIconNotification from "../../../icons/VaIconNotification.vue";
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const baseNumberOfVisibleNotifications = 4
|
||||
const rtf = new Intl.RelativeTimeFormat(locale.value, { style: 'short' })
|
||||
const displayAllNotifications = ref(false)
|
||||
const baseNumberOfVisibleNotifications = 4;
|
||||
const rtf = new Intl.RelativeTimeFormat(locale.value, { style: "short" });
|
||||
const displayAllNotifications = ref(false);
|
||||
|
||||
interface INotification {
|
||||
message: string
|
||||
icon: string
|
||||
id: number
|
||||
separator?: boolean
|
||||
updateTimestamp: Date
|
||||
message: string;
|
||||
icon: string;
|
||||
id: number;
|
||||
separator?: boolean;
|
||||
updateTimestamp: Date;
|
||||
}
|
||||
|
||||
const makeDateFromNow = (timeFromNow: number) => {
|
||||
const date = new Date()
|
||||
date.setTime(date.getTime() + timeFromNow)
|
||||
return date
|
||||
}
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + timeFromNow);
|
||||
return date;
|
||||
};
|
||||
|
||||
const notifications: INotification[] = [
|
||||
{
|
||||
message: '4 pending requests',
|
||||
icon: 'favorite_outline',
|
||||
message: "4 pending requests",
|
||||
icon: "favorite_outline",
|
||||
id: 1,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: '3 new reports',
|
||||
icon: 'calendar_today',
|
||||
message: "3 new reports",
|
||||
icon: "calendar_today",
|
||||
id: 2,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-12 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: 'Whoops! Your trial period has expired.',
|
||||
icon: 'error_outline',
|
||||
message: "Whoops! Your trial period has expired.",
|
||||
icon: "error_outline",
|
||||
id: 3,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-2 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: 'It looks like your timezone is set incorrectly, please change it to avoid issues with Memory.',
|
||||
icon: 'schedule',
|
||||
message:
|
||||
"It looks like your timezone is set incorrectly, please change it to avoid issues with Memory.",
|
||||
icon: "schedule",
|
||||
id: 4,
|
||||
updateTimestamp: makeDateFromNow(-2 * 7 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: '2 new team members added',
|
||||
icon: 'group_add',
|
||||
message: "2 new team members added",
|
||||
icon: "group_add",
|
||||
id: 5,
|
||||
separator: false,
|
||||
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: 'Monthly budget exceeded by 10%',
|
||||
icon: 'trending_up',
|
||||
message: "Monthly budget exceeded by 10%",
|
||||
icon: "trending_up",
|
||||
id: 6,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-3 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: '7 tasks are approaching their deadlines',
|
||||
icon: 'alarm',
|
||||
message: "7 tasks are approaching their deadlines",
|
||||
icon: "alarm",
|
||||
id: 7,
|
||||
separator: false,
|
||||
updateTimestamp: makeDateFromNow(-5 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
message: 'New software update available',
|
||||
icon: 'system_update',
|
||||
message: "New software update available",
|
||||
icon: "system_update",
|
||||
id: 8,
|
||||
separator: true,
|
||||
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 = {
|
||||
second: 1000,
|
||||
|
|
@ -126,41 +152,51 @@ const TIME_NAMES = {
|
|||
week: 1000 * 60 * 60 * 24 * 7,
|
||||
month: 1000 * 60 * 60 * 24 * 30,
|
||||
year: 1000 * 60 * 60 * 24 * 365,
|
||||
}
|
||||
};
|
||||
|
||||
const getTimeName = (differenceTime: number) => {
|
||||
return Object.keys(TIME_NAMES).reduce(
|
||||
(acc, key) => (TIME_NAMES[key as keyof typeof TIME_NAMES] < differenceTime ? key : acc),
|
||||
'month',
|
||||
) as keyof typeof TIME_NAMES
|
||||
}
|
||||
(acc, key) =>
|
||||
TIME_NAMES[key as keyof typeof TIME_NAMES] < differenceTime ? key : acc,
|
||||
"month",
|
||||
) as keyof typeof TIME_NAMES;
|
||||
};
|
||||
|
||||
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) => {
|
||||
const timeDifference = Math.round(new Date().getTime() - new Date(item.updateTimestamp).getTime())
|
||||
const timeName = getTimeName(timeDifference)
|
||||
const timeDifference = Math.round(
|
||||
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) {
|
||||
const nextItemDifference = Math.round(new Date().getTime() - new Date(nextItem.updateTimestamp).getTime())
|
||||
const nextItemTimeName = getTimeName(nextItemDifference)
|
||||
const nextItemDifference = Math.round(
|
||||
new Date().getTime() - new Date(nextItem.updateTimestamp).getTime(),
|
||||
);
|
||||
const nextItemTimeName = getTimeName(nextItemDifference);
|
||||
|
||||
if (timeName !== nextItemTimeName) {
|
||||
separator = true
|
||||
separator = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
updateTimestamp: rtf.format(-1 * Math.round(timeDifference / TIME_NAMES[timeName]), timeName),
|
||||
updateTimestamp: rtf.format(
|
||||
-1 * Math.round(timeDifference / TIME_NAMES[timeName]),
|
||||
timeName,
|
||||
),
|
||||
separator,
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<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>
|
||||
<VaButton preset="secondary" color="textPrimary">
|
||||
<span class="profile-dropdown__anchor min-w-max">
|
||||
|
|
@ -14,7 +19,10 @@
|
|||
:style="{ '--hover-color': hoverColor }"
|
||||
>
|
||||
<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}`) }}
|
||||
</header>
|
||||
<VaListItem
|
||||
|
|
@ -34,86 +42,90 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
import { ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useColors } from "vuestic-ui";
|
||||
|
||||
const { colors, setHSLAColor } = useColors()
|
||||
const hoverColor = computed(() => setHSLAColor(colors.focus, { a: 0.1 }))
|
||||
const { colors, setHSLAColor } = useColors();
|
||||
const hoverColor = computed(() => setHSLAColor(colors.focus, { a: 0.1 }));
|
||||
|
||||
const { t } = useI18n()
|
||||
const { t } = useI18n();
|
||||
|
||||
type ProfileListItem = {
|
||||
name: string
|
||||
to?: string
|
||||
href?: string
|
||||
icon: string
|
||||
}
|
||||
name: string;
|
||||
to?: string;
|
||||
href?: string;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
type ProfileOptions = {
|
||||
name: string
|
||||
separator: boolean
|
||||
list: ProfileListItem[]
|
||||
}
|
||||
name: string;
|
||||
separator: boolean;
|
||||
list: ProfileListItem[];
|
||||
};
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
options?: ProfileOptions[]
|
||||
options?: ProfileOptions[];
|
||||
}>(),
|
||||
{
|
||||
options: () => [
|
||||
{
|
||||
name: 'account',
|
||||
name: "account",
|
||||
separator: true,
|
||||
list: [
|
||||
{
|
||||
name: 'profile',
|
||||
to: 'preferences',
|
||||
icon: 'mso-account_circle',
|
||||
name: "profile",
|
||||
to: "preferences",
|
||||
icon: "mso-account_circle",
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
to: 'settings',
|
||||
icon: 'mso-settings',
|
||||
name: "settings",
|
||||
to: "settings",
|
||||
icon: "mso-settings",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'explore',
|
||||
name: "explore",
|
||||
separator: true,
|
||||
list: [
|
||||
{
|
||||
name: 'faq',
|
||||
to: 'faq',
|
||||
icon: 'mso-quiz',
|
||||
name: "faq",
|
||||
to: "faq",
|
||||
icon: "mso-quiz",
|
||||
},
|
||||
{
|
||||
name: 'helpAndSupport',
|
||||
href: 'https://discord.gg/u7fQdqQt8c',
|
||||
icon: 'mso-error',
|
||||
name: "helpAndSupport",
|
||||
href: "https://discord.gg/u7fQdqQt8c",
|
||||
icon: "mso-error",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
name: "",
|
||||
separator: false,
|
||||
list: [
|
||||
{
|
||||
name: 'logout',
|
||||
to: 'login',
|
||||
icon: 'mso-logout',
|
||||
name: "logout",
|
||||
to: "login",
|
||||
icon: "mso-logout",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
const isShown = ref(false)
|
||||
const isShown = ref(false);
|
||||
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
<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>
|
||||
<VaCollapse v-for="(route, index) in navigationRoutes.routes" :key="index">
|
||||
<VaCollapse
|
||||
v-for="(route, index) in navigationRoutes.routes"
|
||||
:key="index"
|
||||
>
|
||||
<template #header="{ value: isCollapsed }">
|
||||
<VaSidebarItem
|
||||
:to="route.children ? undefined : { name: route.name }"
|
||||
:active="routeHasActiveChild(route)"
|
||||
:active-color="activeColor"
|
||||
: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"
|
||||
hover-opacity="0.10"
|
||||
>
|
||||
|
|
@ -20,9 +30,15 @@
|
|||
size="20px"
|
||||
: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) }}
|
||||
<VaIcon v-if="route.children" :name="arrowDirection(isCollapsed)" size="20px" />
|
||||
<VaIcon
|
||||
v-if="route.children"
|
||||
:name="arrowDirection(isCollapsed)"
|
||||
size="20px"
|
||||
/>
|
||||
</VaSidebarItemTitle>
|
||||
</VaSidebarItemContent>
|
||||
</VaSidebarItem>
|
||||
|
|
@ -50,56 +66,64 @@
|
|||
</VaSidebar>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { defineComponent, watch, ref, computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useColors } from "vuestic-ui";
|
||||
|
||||
import navigationRoutes, { type INavigationRoute } from './NavigationRoutes'
|
||||
import navigationRoutes, { type INavigationRoute } from "./NavigationRoutes";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Sidebar',
|
||||
name: "Sidebar",
|
||||
props: {
|
||||
visible: { type: Boolean, default: true },
|
||||
mobile: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['update:visible'],
|
||||
emits: ["update:visible"],
|
||||
|
||||
setup: (props, { emit }) => {
|
||||
const { getColor, colorToRgba } = useColors()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
const { getColor, colorToRgba } = useColors();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const value = ref<boolean[]>([])
|
||||
const value = ref<boolean[]>([]);
|
||||
|
||||
const writableVisible = computed({
|
||||
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) => {
|
||||
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 = () =>
|
||||
(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 color = computed(() => getColor('background-secondary'))
|
||||
const activeColor = computed(() => colorToRgba(getColor('focus'), 0.1))
|
||||
const sidebarWidth = computed(() => (props.mobile ? "100vw" : "280px"));
|
||||
const color = computed(() => getColor("background-secondary"));
|
||||
const activeColor = computed(() => colorToRgba(getColor("focus"), 0.1));
|
||||
|
||||
const iconColor = (route: INavigationRoute) => (routeHasActiveChild(route) ? 'primary' : 'secondary')
|
||||
const textColor = (route: INavigationRoute) => (routeHasActiveChild(route) ? 'primary' : 'textPrimary')
|
||||
const arrowDirection = (state: boolean) => (state ? 'va-arrow-up' : 'va-arrow-down')
|
||||
const iconColor = (route: INavigationRoute) =>
|
||||
routeHasActiveChild(route) ? "primary" : "secondary";
|
||||
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 {
|
||||
writableVisible,
|
||||
|
|
@ -114,7 +138,7 @@ export default defineComponent({
|
|||
iconColor,
|
||||
textColor,
|
||||
arrowDirection,
|
||||
}
|
||||
};
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
export interface INavigationRoute {
|
||||
name: string
|
||||
displayName: string
|
||||
meta: { icon: string }
|
||||
children?: INavigationRoute[]
|
||||
name: string;
|
||||
displayName: string;
|
||||
meta: { icon: string };
|
||||
children?: INavigationRoute[];
|
||||
}
|
||||
|
||||
export default {
|
||||
root: {
|
||||
name: '/',
|
||||
displayName: 'navigationRoutes.home',
|
||||
name: "/",
|
||||
displayName: "navigationRoutes.home",
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
name: 'dashboard',
|
||||
displayName: 'menu.dashboard',
|
||||
name: "dashboard",
|
||||
displayName: "menu.dashboard",
|
||||
meta: {
|
||||
icon: 'vuestic-iconset-dashboard',
|
||||
icon: "vuestic-iconset-dashboard",
|
||||
},
|
||||
},
|
||||
// {
|
||||
|
|
@ -104,4 +104,4 @@ export default {
|
|||
// },
|
||||
// },
|
||||
] as INavigationRoute[],
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import Typography from './Typography.vue'
|
||||
import Typography from "./Typography.vue";
|
||||
|
||||
export default {
|
||||
title: 'Typography',
|
||||
title: "Typography",
|
||||
component: Typography,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
components: { Typography },
|
||||
template: `
|
||||
<Typography/>
|
||||
`,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,48 +7,57 @@
|
|||
<div class="mb-8">
|
||||
<h1>Display 1 Heading</h1>
|
||||
<p>
|
||||
Of all of the celestial bodies that capture our attention and 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.
|
||||
Of all of the celestial bodies that capture our attention and
|
||||
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>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<h2>Display 2 Heading</h2>
|
||||
<p>
|
||||
None has a greater influence on life on planet Earth than it’s own satellite, the moon. When you think
|
||||
about it.
|
||||
None has a greater influence on life on planet Earth than it’s own
|
||||
satellite, the moon. When you think about it.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<h3>Display 3 Heading</h3>
|
||||
<p>
|
||||
Let’s talk about meat fondue recipes and what you need to know 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.
|
||||
Let’s talk about meat fondue recipes and what you need to know
|
||||
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>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<h4>Display 4 Heading</h4>
|
||||
<p>
|
||||
There is something about parenthood that gives us a sense of history and a deeply rooted desire to send on
|
||||
into the next generation the great things we have discovered about life.
|
||||
There is something about parenthood that gives us a sense of
|
||||
history and a deeply rooted desire to send on into the next
|
||||
generation the great things we have discovered about life.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<h5>Display 5 Heading</h5>
|
||||
<p>
|
||||
There is a moment in the life of any aspiring astronomer that it is time to buy that first telescope. It’s
|
||||
exciting to think about setting up your own viewing station.
|
||||
There is a moment in the life of any aspiring astronomer that it
|
||||
is time to buy that first telescope. It’s exciting to think about
|
||||
setting up your own viewing station.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<p>
|
||||
Of all of the celestial bodies that capture our attention and 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.
|
||||
Of all of the celestial bodies that capture our attention and
|
||||
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>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<div class="text--secondary">
|
||||
Of all of the celestial bodies that capture our attention and 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.
|
||||
Of all of the celestial bodies that capture our attention and
|
||||
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 class="mb-8">
|
||||
|
|
@ -59,9 +68,11 @@
|
|||
</p></pre
|
||||
>
|
||||
<p>
|
||||
Of all of the celestial bodies that capture our attention and fascination as astronomers,
|
||||
<span class="text--code">currentColor</span> none has a greater influence on life on planet Earth than
|
||||
it’s own satellite, the moon.
|
||||
Of all of the celestial bodies that capture our attention and
|
||||
fascination as astronomers,
|
||||
<span class="text--code">currentColor</span> none has a greater
|
||||
influence on life on planet Earth than it’s own satellite, the
|
||||
moon.
|
||||
</p>
|
||||
</div>
|
||||
</VaCardContent>
|
||||
|
|
@ -73,10 +84,12 @@
|
|||
<p class="va-h3">Lists</p>
|
||||
<ol class="va-ordered">
|
||||
<li>
|
||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
||||
greater influence.
|
||||
Of all of the celestial bodies that capture our attention and
|
||||
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>Earth than it’s own satellite, the moon. When you think about it.</li>
|
||||
<li>Attention and fascination as.</li>
|
||||
</ol>
|
||||
<ol class="va-ordered">
|
||||
|
|
@ -104,10 +117,12 @@
|
|||
</ol>
|
||||
<ul class="va-unordered">
|
||||
<li>
|
||||
Of all of the celestial bodies that capture our attention and fascination as astronomers, none has a
|
||||
greater influence.
|
||||
Of all of the celestial bodies that capture our attention and
|
||||
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>Earth than it’s own satellite, the moon. When you think about it.</li>
|
||||
<li>Attention and fascination as .</li>
|
||||
</ul>
|
||||
<ul class="va-unordered">
|
||||
|
|
@ -135,22 +150,28 @@
|
|||
</ul>
|
||||
<p class="va-h3">Links</p>
|
||||
<div class="mb-8">
|
||||
<a class="link mr-8" href="/default" @click.prevent> Default Link </a>
|
||||
<a class="link-secondary" href="/secondary" @click.prevent> Secondary Link </a>
|
||||
<a class="link mr-8" href="/default" @click.prevent>
|
||||
Default Link
|
||||
</a>
|
||||
<a class="link-secondary" href="/secondary" @click.prevent>
|
||||
Secondary Link
|
||||
</a>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<p class="va-h3">Other Elements</p>
|
||||
<p>
|
||||
None has a greater influence on
|
||||
<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>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<blockquote class="va-blockquote border-primary">
|
||||
<p>
|
||||
BQ: Let’s talk about meat fondue recipes and what you need to know first. Meat fondue also known as oil
|
||||
fondue is a method of cooking all kinds.
|
||||
BQ: Let’s talk about meat fondue recipes and what you need to
|
||||
know first. Meat fondue also known as oil fondue is a method of
|
||||
cooking all kinds.
|
||||
</p>
|
||||
<p>
|
||||
<i>— Mister Lebowski</i>
|
||||
|
|
@ -161,9 +182,10 @@
|
|||
<div class="text-block">
|
||||
<p class="va-h3">va-h3 Heading</p>
|
||||
<span
|
||||
>Of all of the celestial bodies that capture our attention and 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.</span
|
||||
>Of all of the celestial bodies that capture our attention and
|
||||
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.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -171,11 +193,16 @@
|
|||
<table class="va-table">
|
||||
<thead>
|
||||
<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>
|
||||
</thead>
|
||||
<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">
|
||||
{{ itemData }}
|
||||
</td>
|
||||
|
|
@ -190,17 +217,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { computed } from "vue";
|
||||
// import { useI18n } from 'vue-i18n'
|
||||
//
|
||||
// const { t } = useI18n()
|
||||
|
||||
const tableData = computed(() => [
|
||||
['Id', 'FooBar type', 'Actions'],
|
||||
['1', 'Zebra', 'Delete'],
|
||||
['2', 'Not Zebra', 'Remove'],
|
||||
['3', 'Very Zebra', 'Eradicate'],
|
||||
])
|
||||
["Id", "FooBar type", "Actions"],
|
||||
["1", "Zebra", "Delete"],
|
||||
["2", "Not Zebra", "Remove"],
|
||||
["3", "Very Zebra", "Eradicate"],
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,38 @@
|
|||
<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>
|
||||
|
||||
<script lang="ts" setup generic="T extends 'line' | 'bar' | 'bubble' | 'doughnut' | 'pie'">
|
||||
import { computed } from 'vue'
|
||||
import type { ChartOptions, ChartData } from 'chart.js'
|
||||
import { defaultConfig, chartTypesMap } from './vaChartConfigs'
|
||||
<script
|
||||
lang="ts"
|
||||
setup
|
||||
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({
|
||||
name: 'VaChart',
|
||||
})
|
||||
name: "VaChart",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
data: ChartData<T>
|
||||
options?: ChartOptions<T>
|
||||
type: T
|
||||
}>()
|
||||
data: ChartData<T>;
|
||||
options?: ChartOptions<T>;
|
||||
type: T;
|
||||
}>();
|
||||
|
||||
const chartComponent = chartTypesMap[props.type]
|
||||
const chartComponent = chartTypesMap[props.type];
|
||||
|
||||
const chartOptions = computed<ChartOptions<T>>(() => ({
|
||||
...(defaultConfig as any),
|
||||
...props.options,
|
||||
}))
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -3,14 +3,29 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Bar } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
|
||||
import { Bar } from "vue-chartjs";
|
||||
import type { ChartOptions } 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<{
|
||||
data: any
|
||||
options?: ChartOptions<'bar'>
|
||||
}>()
|
||||
data: any;
|
||||
options?: ChartOptions<"bar">;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,22 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Bubble } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, PointElement, LinearScale } from 'chart.js'
|
||||
import { TBubbleChartData } from '../../../data/types'
|
||||
import { Bubble } from "vue-chartjs";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
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<{
|
||||
data: TBubbleChartData
|
||||
options?: ChartOptions<'bubble'>
|
||||
}>()
|
||||
data: TBubbleChartData;
|
||||
options?: ChartOptions<"bubble">;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,22 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Doughnut } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
|
||||
import { TDoughnutChartData } from '../../../data/types'
|
||||
import { Doughnut } from "vue-chartjs";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
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<{
|
||||
data: TDoughnutChartData
|
||||
options?: ChartOptions<'doughnut'>
|
||||
}>()
|
||||
data: TDoughnutChartData;
|
||||
options?: ChartOptions<"doughnut">;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,24 +3,39 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Bar } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'
|
||||
import { TBarChartData } from '../../../data/types'
|
||||
import { Bar } from "vue-chartjs";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
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 = {
|
||||
indexAxis: 'y' as 'x' | 'y',
|
||||
indexAxis: "y" as "x" | "y",
|
||||
elements: {
|
||||
bar: {
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
data: TBarChartData
|
||||
options?: ChartOptions<'bar'>
|
||||
}>()
|
||||
data: TBarChartData;
|
||||
options?: ChartOptions<"bar">;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import { ref } from "vue";
|
||||
import { Line } from "vue-chartjs";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
|
|
@ -16,46 +16,55 @@ import {
|
|||
PointElement,
|
||||
CategoryScale,
|
||||
Filler,
|
||||
} from 'chart.js'
|
||||
import { TLineChartData } from '../../../data/types'
|
||||
import { computed } from 'vue'
|
||||
import { useColors } from 'vuestic-ui/web-components'
|
||||
} from "chart.js";
|
||||
import { TLineChartData } from "../../../data/types";
|
||||
import { computed } from "vue";
|
||||
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<{
|
||||
data: TLineChartData
|
||||
options?: ChartOptions<'line'>
|
||||
}>()
|
||||
data: TLineChartData;
|
||||
options?: ChartOptions<"line">;
|
||||
}>();
|
||||
|
||||
const ctx = computed(() => {
|
||||
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>(() => {
|
||||
if (!ctx.value) {
|
||||
return props.data
|
||||
return props.data;
|
||||
}
|
||||
|
||||
const makeGradient = (bg: string) => {
|
||||
const gradient = ctx.value!.createLinearGradient(0, 0, 0, 90)
|
||||
gradient.addColorStop(0, setHSLAColor(bg, { a: 0.4 }))
|
||||
gradient.addColorStop(1, setHSLAColor(bg, { a: 0.0 }))
|
||||
return gradient
|
||||
}
|
||||
const gradient = ctx.value!.createLinearGradient(0, 0, 0, 90);
|
||||
gradient.addColorStop(0, setHSLAColor(bg, { a: 0.4 }));
|
||||
gradient.addColorStop(1, setHSLAColor(bg, { a: 0.0 }));
|
||||
return gradient;
|
||||
};
|
||||
|
||||
const datasets = props.data.datasets.map((dataset, index) => {
|
||||
const color = getColor(colors[index % colors.length])
|
||||
const color = getColor(colors[index % colors.length]);
|
||||
|
||||
return {
|
||||
...dataset,
|
||||
|
|
@ -64,9 +73,9 @@ const computedChartData = computed<TLineChartData>(() => {
|
|||
borderColor: color,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
return { ...props.data, datasets }
|
||||
})
|
||||
return { ...props.data, datasets };
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,24 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { Chart as ChartJS, Title, 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'
|
||||
import { ref } from "vue";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
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(
|
||||
Title,
|
||||
|
|
@ -19,26 +32,26 @@ ChartJS.register(
|
|||
ProjectionScale,
|
||||
ColorScale,
|
||||
GeoFeature,
|
||||
)
|
||||
);
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||
|
||||
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<{
|
||||
options?: ChartOptions<'choropleth'>
|
||||
data: ChartData<'choropleth', { feature: any; value: number }[], string>
|
||||
}>()
|
||||
options?: ChartOptions<"choropleth">;
|
||||
data: ChartData<"choropleth", { feature: any; value: number }[], string>;
|
||||
}>();
|
||||
|
||||
watchEffect(() => {
|
||||
if (canvas.value === null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
new ChartJS(canvas.value.getContext('2d')!, {
|
||||
type: 'choropleth',
|
||||
new ChartJS(canvas.value.getContext("2d")!, {
|
||||
type: "choropleth",
|
||||
data: props.data,
|
||||
options: {
|
||||
plugins: {
|
||||
|
|
@ -48,12 +61,12 @@ watchEffect(() => {
|
|||
},
|
||||
scales: {
|
||||
projection: {
|
||||
axis: 'x',
|
||||
projection: 'mercator',
|
||||
axis: "x",
|
||||
projection: "mercator",
|
||||
projectionScale: 1.6,
|
||||
},
|
||||
color: {
|
||||
axis: 'x',
|
||||
axis: "x",
|
||||
quantize: 5,
|
||||
display: false,
|
||||
interpolate: getColor,
|
||||
|
|
@ -61,6 +74,6 @@ watchEffect(() => {
|
|||
},
|
||||
animation: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,22 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Pie } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import { Chart as ChartJS, Title, Tooltip, Legend, ArcElement, CategoryScale } from 'chart.js'
|
||||
import { TPieChartData } from '../../../data/types'
|
||||
import { Pie } from "vue-chartjs";
|
||||
import type { ChartOptions } from "chart.js";
|
||||
import {
|
||||
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<{
|
||||
data: TPieChartData
|
||||
options?: ChartOptions<'pie'>
|
||||
}>()
|
||||
data: TPieChartData;
|
||||
options?: ChartOptions<"pie">;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,117 +1,121 @@
|
|||
import { Chart, TooltipModel } from 'chart.js'
|
||||
import { computePosition, flip, shift } from '@floating-ui/dom'
|
||||
import { Chart, TooltipModel } from "chart.js";
|
||||
import { computePosition, flip, shift } from "@floating-ui/dom";
|
||||
|
||||
const getOrCreateTooltip = (chart: Chart) => {
|
||||
let tooltipEl = chart.canvas.parentNode?.querySelector('div')
|
||||
let tooltipEl = chart.canvas.parentNode?.querySelector("div");
|
||||
|
||||
if (!tooltipEl) {
|
||||
tooltipEl = document.createElement('div')
|
||||
tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)'
|
||||
tooltipEl.style.borderRadius = '3px'
|
||||
tooltipEl.style.color = 'white'
|
||||
tooltipEl.style.opacity = '1'
|
||||
tooltipEl.style.pointerEvents = 'none'
|
||||
tooltipEl.style.position = 'absolute'
|
||||
tooltipEl = document.createElement("div");
|
||||
tooltipEl.style.background = "rgba(0, 0, 0, 0.7)";
|
||||
tooltipEl.style.borderRadius = "3px";
|
||||
tooltipEl.style.color = "white";
|
||||
tooltipEl.style.opacity = "1";
|
||||
tooltipEl.style.pointerEvents = "none";
|
||||
tooltipEl.style.position = "absolute";
|
||||
// tooltipEl.style.transform = 'translate(-50%, 0)'
|
||||
tooltipEl.style.left = '0'
|
||||
tooltipEl.style.top = '0'
|
||||
tooltipEl.style.transition = 'all .1s ease'
|
||||
tooltipEl.style.height = 'min-content'
|
||||
tooltipEl.style.maxWidth = '200px'
|
||||
tooltipEl.style.zIndex = '9999'
|
||||
tooltipEl.style.left = "0";
|
||||
tooltipEl.style.top = "0";
|
||||
tooltipEl.style.transition = "all .1s ease";
|
||||
tooltipEl.style.height = "min-content";
|
||||
tooltipEl.style.maxWidth = "200px";
|
||||
tooltipEl.style.zIndex = "9999";
|
||||
|
||||
const table = document.createElement('table')
|
||||
table.style.margin = '0px'
|
||||
const table = document.createElement("table");
|
||||
table.style.margin = "0px";
|
||||
|
||||
tooltipEl.appendChild(table)
|
||||
chart.canvas.parentNode?.appendChild(tooltipEl)
|
||||
tooltipEl.appendChild(table);
|
||||
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
|
||||
const { chart, tooltip } = context
|
||||
const tooltipEl = getOrCreateTooltip(chart)
|
||||
const { chart, tooltip } = context;
|
||||
const tooltipEl = getOrCreateTooltip(chart);
|
||||
|
||||
// Hide if no tooltip
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.opacity = '0'
|
||||
return
|
||||
tooltipEl.style.opacity = "0";
|
||||
return;
|
||||
}
|
||||
|
||||
// Set Text
|
||||
if (tooltip.body) {
|
||||
const titleLines = tooltip.title || []
|
||||
const bodyLines = tooltip.body.map((b) => b.lines)
|
||||
const titleLines = tooltip.title || [];
|
||||
const bodyLines = tooltip.body.map((b) => b.lines);
|
||||
|
||||
const tableHead = document.createElement('thead')
|
||||
const tableHead = document.createElement("thead");
|
||||
|
||||
titleLines.forEach((title) => {
|
||||
const tr = document.createElement('tr')
|
||||
tr.style.borderWidth = '0'
|
||||
const tr = document.createElement("tr");
|
||||
tr.style.borderWidth = "0";
|
||||
|
||||
const th = document.createElement('th')
|
||||
th.style.borderWidth = '0'
|
||||
const text = document.createTextNode(title)
|
||||
const th = document.createElement("th");
|
||||
th.style.borderWidth = "0";
|
||||
const text = document.createTextNode(title);
|
||||
|
||||
th.appendChild(text)
|
||||
tr.appendChild(th)
|
||||
tableHead.appendChild(tr)
|
||||
})
|
||||
th.appendChild(text);
|
||||
tr.appendChild(th);
|
||||
tableHead.appendChild(tr);
|
||||
});
|
||||
|
||||
const tableBody = document.createElement('tbody')
|
||||
const tableBody = document.createElement("tbody");
|
||||
bodyLines.forEach((body, i) => {
|
||||
const colors = tooltip.labelColors[i]
|
||||
const colors = tooltip.labelColors[i];
|
||||
|
||||
const span = document.createElement('span')
|
||||
span.style.background = String(colors.backgroundColor)
|
||||
span.style.borderColor = String(colors.borderColor)
|
||||
span.style.borderWidth = '2px'
|
||||
span.style.marginRight = '10px'
|
||||
span.style.height = '10px'
|
||||
span.style.width = '10px'
|
||||
span.style.display = 'inline-block'
|
||||
const span = document.createElement("span");
|
||||
span.style.background = String(colors.backgroundColor);
|
||||
span.style.borderColor = String(colors.borderColor);
|
||||
span.style.borderWidth = "2px";
|
||||
span.style.marginRight = "10px";
|
||||
span.style.height = "10px";
|
||||
span.style.width = "10px";
|
||||
span.style.display = "inline-block";
|
||||
|
||||
const tr = document.createElement('tr')
|
||||
tr.style.backgroundColor = 'inherit'
|
||||
tr.style.borderWidth = '0'
|
||||
const tr = document.createElement("tr");
|
||||
tr.style.backgroundColor = "inherit";
|
||||
tr.style.borderWidth = "0";
|
||||
|
||||
const td = document.createElement('td')
|
||||
td.style.borderWidth = '0'
|
||||
const td = document.createElement("td");
|
||||
td.style.borderWidth = "0";
|
||||
|
||||
const text = document.createTextNode(body as any)
|
||||
const text = document.createTextNode(body as any);
|
||||
|
||||
td.appendChild(span)
|
||||
td.appendChild(text)
|
||||
tr.appendChild(td)
|
||||
tableBody.appendChild(tr)
|
||||
})
|
||||
td.appendChild(span);
|
||||
td.appendChild(text);
|
||||
tr.appendChild(td);
|
||||
tableBody.appendChild(tr);
|
||||
});
|
||||
|
||||
const tableRoot = tooltipEl.querySelector('table')
|
||||
const tableRoot = tooltipEl.querySelector("table");
|
||||
|
||||
// Remove old children
|
||||
while (tableRoot?.firstChild) {
|
||||
tableRoot.firstChild.remove()
|
||||
tableRoot.firstChild.remove();
|
||||
}
|
||||
|
||||
// Add new children
|
||||
tableRoot?.appendChild(tableHead)
|
||||
tableRoot?.appendChild(tableBody)
|
||||
tableRoot?.appendChild(tableHead);
|
||||
tableRoot?.appendChild(tableBody);
|
||||
}
|
||||
|
||||
// Display, position, and set styles for font
|
||||
tooltipEl.style.opacity = '1'
|
||||
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px'
|
||||
tooltipEl.style.opacity = "1";
|
||||
tooltipEl.style.padding =
|
||||
tooltip.options.padding + "px " + tooltip.options.padding + "px";
|
||||
|
||||
computePosition(chart.canvas.parentNode! as HTMLElement, tooltipEl!, {
|
||||
placement: 'top',
|
||||
placement: "top",
|
||||
middleware: [flip(), shift()],
|
||||
}).then(({ x, y }) => {
|
||||
Object.assign(tooltipEl!.style, {
|
||||
left: `${x}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 = {
|
||||
scales: {
|
||||
|
|
@ -21,10 +21,10 @@ export const defaultConfig = {
|
|||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
position: "bottom",
|
||||
labels: {
|
||||
font: {
|
||||
color: '#34495e',
|
||||
color: "#34495e",
|
||||
family: DEFAULT_FONT_FAMILY,
|
||||
size: 14,
|
||||
},
|
||||
|
|
@ -41,23 +41,23 @@ export const defaultConfig = {
|
|||
},
|
||||
datasets: {
|
||||
line: {
|
||||
fill: 'origin',
|
||||
fill: "origin",
|
||||
tension: 0.3,
|
||||
borderColor: 'transparent',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
bubble: {
|
||||
borderColor: 'transparent',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
bar: {
|
||||
borderColor: 'transparent',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
}
|
||||
};
|
||||
|
||||
export const doughnutConfig = {
|
||||
cutout: '80%',
|
||||
cutout: "80%",
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
|
|
@ -82,26 +82,38 @@ export const doughnutConfig = {
|
|||
},
|
||||
datasets: {
|
||||
line: {
|
||||
fill: 'origin',
|
||||
fill: "origin",
|
||||
tension: 0.3,
|
||||
borderColor: 'transparent',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
bubble: {
|
||||
borderColor: 'transparent',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
bar: {
|
||||
borderColor: 'transparent',
|
||||
borderColor: "transparent",
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
}
|
||||
};
|
||||
|
||||
export const chartTypesMap = {
|
||||
pie: markRaw(defineAsyncComponent(() => import('./chart-types/PieChart.vue'))),
|
||||
doughnut: markRaw(defineAsyncComponent(() => import('./chart-types/DoughnutChart.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'))),
|
||||
}
|
||||
pie: markRaw(
|
||||
defineAsyncComponent(() => import("./chart-types/PieChart.vue")),
|
||||
),
|
||||
doughnut: markRaw(
|
||||
defineAsyncComponent(() => import("./chart-types/DoughnutChart.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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, Ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import MediumEditor from 'medium-editor'
|
||||
import { ref, Ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import MediumEditor from "medium-editor";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
editorOptions?: {
|
||||
buttonLabels: string
|
||||
autoLink: boolean
|
||||
buttonLabels: string;
|
||||
autoLink: boolean;
|
||||
toolbar: {
|
||||
buttons: string[]
|
||||
}
|
||||
}
|
||||
buttons: string[];
|
||||
};
|
||||
};
|
||||
}>(),
|
||||
{
|
||||
editorOptions: () => ({
|
||||
buttonLabels: 'fontawesome',
|
||||
buttonLabels: "fontawesome",
|
||||
autoLink: true,
|
||||
toolbar: {
|
||||
buttons: ['bold', 'italic', 'underline', 'anchor', 'h1', 'h2', 'h3'],
|
||||
buttons: ["bold", "italic", "underline", "anchor", "h1", "h2", "h3"],
|
||||
},
|
||||
}),
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'initialized', editor: typeof MediumEditor): void
|
||||
}>()
|
||||
(e: "initialized", editor: typeof MediumEditor): void;
|
||||
}>();
|
||||
|
||||
const editorElement: Ref<null | HTMLElement> = ref(null)
|
||||
let editor: typeof MediumEditor | null = null
|
||||
const editorElement: Ref<null | HTMLElement> = ref(null);
|
||||
let editor: typeof MediumEditor | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
if (!editorElement.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
editor = new MediumEditor(editorElement.value, props.editorOptions)
|
||||
emit('initialized', editor)
|
||||
})
|
||||
editor = new MediumEditor(editorElement.value, props.editorOptions);
|
||||
emit("initialized", editor);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (editor) {
|
||||
editor.destroy()
|
||||
editor.destroy();
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'medium-editor/src/sass/medium-editor';
|
||||
@import 'variables';
|
||||
@import "medium-editor/src/sass/medium-editor";
|
||||
@import "variables";
|
||||
|
||||
$medium-editor-shadow: var(--va-box-shadow);
|
||||
$medium-editor-background-color: var(--va-divider);
|
||||
|
|
@ -163,7 +163,8 @@ $medium-editor-active-text-color: var(--va-white);
|
|||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
defineProps({
|
||||
date: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: "",
|
||||
},
|
||||
})
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
@ -47,7 +47,7 @@ defineProps({
|
|||
height: 100%;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
content: "";
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: var(--va-background-border);
|
||||
|
|
|
|||
|
|
@ -1,243 +1,243 @@
|
|||
export default [
|
||||
'Afghanistan',
|
||||
'Albania',
|
||||
'Algeria',
|
||||
'American Samoa',
|
||||
'Andorra',
|
||||
'Angola',
|
||||
'Anguilla',
|
||||
'Antarctica',
|
||||
'Antigua and Barbuda',
|
||||
'Argentina',
|
||||
'Armenia',
|
||||
'Aruba',
|
||||
'Australia',
|
||||
'Austria',
|
||||
'Azerbaijan',
|
||||
'Bahamas',
|
||||
'Bahrain',
|
||||
'Bangladesh',
|
||||
'Barbados',
|
||||
'Belarus',
|
||||
'Belgium',
|
||||
'Belize',
|
||||
'Benin',
|
||||
'Bermuda',
|
||||
'Bhutan',
|
||||
'Bolivia',
|
||||
'Bosnia and Herzegowina',
|
||||
'Botswana',
|
||||
'Bouvet Island',
|
||||
'Brazil',
|
||||
'British Indian Ocean Territory',
|
||||
'Brunei Darussalam',
|
||||
'Bulgaria',
|
||||
'Burkina Faso',
|
||||
'Burundi',
|
||||
'Cambodia',
|
||||
'Cameroon',
|
||||
'Canada',
|
||||
'Cape Verde',
|
||||
'Cayman Islands',
|
||||
'Central African Republic',
|
||||
'Chad',
|
||||
'Chile',
|
||||
'China',
|
||||
'Christmas Island',
|
||||
'Cocos (Keeling) Islands',
|
||||
'Colombia',
|
||||
'Comoros',
|
||||
'Congo',
|
||||
'Congo, the Democratic Republic of the',
|
||||
'Cook Islands',
|
||||
'Costa Rica',
|
||||
"Afghanistan",
|
||||
"Albania",
|
||||
"Algeria",
|
||||
"American Samoa",
|
||||
"Andorra",
|
||||
"Angola",
|
||||
"Anguilla",
|
||||
"Antarctica",
|
||||
"Antigua and Barbuda",
|
||||
"Argentina",
|
||||
"Armenia",
|
||||
"Aruba",
|
||||
"Australia",
|
||||
"Austria",
|
||||
"Azerbaijan",
|
||||
"Bahamas",
|
||||
"Bahrain",
|
||||
"Bangladesh",
|
||||
"Barbados",
|
||||
"Belarus",
|
||||
"Belgium",
|
||||
"Belize",
|
||||
"Benin",
|
||||
"Bermuda",
|
||||
"Bhutan",
|
||||
"Bolivia",
|
||||
"Bosnia and Herzegowina",
|
||||
"Botswana",
|
||||
"Bouvet Island",
|
||||
"Brazil",
|
||||
"British Indian Ocean Territory",
|
||||
"Brunei Darussalam",
|
||||
"Bulgaria",
|
||||
"Burkina Faso",
|
||||
"Burundi",
|
||||
"Cambodia",
|
||||
"Cameroon",
|
||||
"Canada",
|
||||
"Cape Verde",
|
||||
"Cayman Islands",
|
||||
"Central African Republic",
|
||||
"Chad",
|
||||
"Chile",
|
||||
"China",
|
||||
"Christmas Island",
|
||||
"Cocos (Keeling) Islands",
|
||||
"Colombia",
|
||||
"Comoros",
|
||||
"Congo",
|
||||
"Congo, the Democratic Republic of the",
|
||||
"Cook Islands",
|
||||
"Costa Rica",
|
||||
"Cote d'Ivoire",
|
||||
'Croatia (Hrvatska)',
|
||||
'Cuba',
|
||||
'Cyprus',
|
||||
'Czech Republic',
|
||||
'Denmark',
|
||||
'Djibouti',
|
||||
'Dominica',
|
||||
'Dominican Republic',
|
||||
'East Timor',
|
||||
'Ecuador',
|
||||
'Egypt',
|
||||
'El Salvador',
|
||||
'Equatorial Guinea',
|
||||
'Eritrea',
|
||||
'Estonia',
|
||||
'Ethiopia',
|
||||
'Falkland Islands (Malvinas)',
|
||||
'Faroe Islands',
|
||||
'Fiji',
|
||||
'Finland',
|
||||
'France',
|
||||
'France Metropolitan',
|
||||
'French Guiana',
|
||||
'French Polynesia',
|
||||
'French Southern Territories',
|
||||
'Gabon',
|
||||
'Gambia',
|
||||
'Georgia',
|
||||
'Germany',
|
||||
'Ghana',
|
||||
'Gibraltar',
|
||||
'Greece',
|
||||
'Greenland',
|
||||
'Grenada',
|
||||
'Guadeloupe',
|
||||
'Guam',
|
||||
'Guatemala',
|
||||
'Guinea',
|
||||
'Guinea-Bissau',
|
||||
'Guyana',
|
||||
'Haiti',
|
||||
'Heard and Mc Donald Islands',
|
||||
'Holy See (Vatican City State)',
|
||||
'Honduras',
|
||||
'Hong Kong',
|
||||
'Hungary',
|
||||
'Iceland',
|
||||
'India',
|
||||
'Indonesia',
|
||||
'Iran (Islamic Republic of)',
|
||||
'Iraq',
|
||||
'Ireland',
|
||||
'Israel',
|
||||
'Italy',
|
||||
'Jamaica',
|
||||
'Japan',
|
||||
'Jordan',
|
||||
'Kazakhstan',
|
||||
'Kenya',
|
||||
'Kiribati',
|
||||
"Croatia (Hrvatska)",
|
||||
"Cuba",
|
||||
"Cyprus",
|
||||
"Czech Republic",
|
||||
"Denmark",
|
||||
"Djibouti",
|
||||
"Dominica",
|
||||
"Dominican Republic",
|
||||
"East Timor",
|
||||
"Ecuador",
|
||||
"Egypt",
|
||||
"El Salvador",
|
||||
"Equatorial Guinea",
|
||||
"Eritrea",
|
||||
"Estonia",
|
||||
"Ethiopia",
|
||||
"Falkland Islands (Malvinas)",
|
||||
"Faroe Islands",
|
||||
"Fiji",
|
||||
"Finland",
|
||||
"France",
|
||||
"France Metropolitan",
|
||||
"French Guiana",
|
||||
"French Polynesia",
|
||||
"French Southern Territories",
|
||||
"Gabon",
|
||||
"Gambia",
|
||||
"Georgia",
|
||||
"Germany",
|
||||
"Ghana",
|
||||
"Gibraltar",
|
||||
"Greece",
|
||||
"Greenland",
|
||||
"Grenada",
|
||||
"Guadeloupe",
|
||||
"Guam",
|
||||
"Guatemala",
|
||||
"Guinea",
|
||||
"Guinea-Bissau",
|
||||
"Guyana",
|
||||
"Haiti",
|
||||
"Heard and Mc Donald Islands",
|
||||
"Holy See (Vatican City State)",
|
||||
"Honduras",
|
||||
"Hong Kong",
|
||||
"Hungary",
|
||||
"Iceland",
|
||||
"India",
|
||||
"Indonesia",
|
||||
"Iran (Islamic Republic of)",
|
||||
"Iraq",
|
||||
"Ireland",
|
||||
"Israel",
|
||||
"Italy",
|
||||
"Jamaica",
|
||||
"Japan",
|
||||
"Jordan",
|
||||
"Kazakhstan",
|
||||
"Kenya",
|
||||
"Kiribati",
|
||||
"Korea, Democratic People's Republic of",
|
||||
'Korea, Republic of',
|
||||
'Kuwait',
|
||||
'Kyrgyzstan',
|
||||
"Korea, Republic of",
|
||||
"Kuwait",
|
||||
"Kyrgyzstan",
|
||||
"Lao, People's Democratic Republic",
|
||||
'Latvia',
|
||||
'Lebanon',
|
||||
'Lesotho',
|
||||
'Liberia',
|
||||
'Libyan Arab Jamahiriya',
|
||||
'Liechtenstein',
|
||||
'Lithuania',
|
||||
'Luxembourg',
|
||||
'Macau',
|
||||
'Macedonia, The Former Yugoslav Republic of',
|
||||
'Madagascar',
|
||||
'Malawi',
|
||||
'Malaysia',
|
||||
'Maldives',
|
||||
'Mali',
|
||||
'Malta',
|
||||
'Marshall Islands',
|
||||
'Martinique',
|
||||
'Mauritania',
|
||||
'Mauritius',
|
||||
'Mayotte',
|
||||
'Mexico',
|
||||
'Micronesia, Federated States of',
|
||||
'Moldova, Republic of',
|
||||
'Monaco',
|
||||
'Mongolia',
|
||||
'Montserrat',
|
||||
'Morocco',
|
||||
'Mozambique',
|
||||
'Myanmar',
|
||||
'Namibia',
|
||||
'Nauru',
|
||||
'Nepal',
|
||||
'Netherlands',
|
||||
'Netherlands Antilles',
|
||||
'New Caledonia',
|
||||
'New Zealand',
|
||||
'Nicaragua',
|
||||
'Niger',
|
||||
'Nigeria',
|
||||
'Niue',
|
||||
'Norfolk Island',
|
||||
'Northern Mariana Islands',
|
||||
'Norway',
|
||||
'Oman',
|
||||
'Pakistan',
|
||||
'Palau',
|
||||
'Panama',
|
||||
'Papua New Guinea',
|
||||
'Paraguay',
|
||||
'Peru',
|
||||
'Philippines',
|
||||
'Pitcairn',
|
||||
'Poland',
|
||||
'Portugal',
|
||||
'Puerto Rico',
|
||||
'Qatar',
|
||||
'Reunion',
|
||||
'Romania',
|
||||
'Russian Federation',
|
||||
'Rwanda',
|
||||
'Saint Kitts and Nevis',
|
||||
'Saint Lucia',
|
||||
'Saint Vincent and the Grenadines',
|
||||
'Samoa',
|
||||
'San Marino',
|
||||
'Sao Tome and Principe',
|
||||
'Saudi Arabia',
|
||||
'Senegal',
|
||||
'Serbia',
|
||||
'Seychelles',
|
||||
'Sierra Leone',
|
||||
'Singapore',
|
||||
'Slovakia (Slovak Republic)',
|
||||
'Slovenia',
|
||||
'Solomon Islands',
|
||||
'Somalia',
|
||||
'South Africa',
|
||||
'South Georgia and the South Sandwich Islands',
|
||||
'Spain',
|
||||
'Sri Lanka',
|
||||
'St. Helena',
|
||||
'St. Pierre and Miquelon',
|
||||
'Sudan',
|
||||
'Suriname',
|
||||
'Svalbard and Jan Mayen Islands',
|
||||
'Swaziland',
|
||||
'Sweden',
|
||||
'Switzerland',
|
||||
'Syrian Arab Republic',
|
||||
'Taiwan, Province of China',
|
||||
'Tajikistan',
|
||||
'Tanzania, United Republic of',
|
||||
'United States of America',
|
||||
'Thailand',
|
||||
'Togo',
|
||||
'Tokelau',
|
||||
'Tonga',
|
||||
'Trinidad and Tobago',
|
||||
'Tunisia',
|
||||
'Turkey',
|
||||
'Turkmenistan',
|
||||
'Turks and Caicos Islands',
|
||||
'Tuvalu',
|
||||
'Uganda',
|
||||
'Ukraine',
|
||||
'United Arab Emirates',
|
||||
'United Kingdom',
|
||||
'United States',
|
||||
'United States Minor Outlying Islands',
|
||||
'Uruguay',
|
||||
'Uzbekistan',
|
||||
'Vanuatu',
|
||||
'Venezuela',
|
||||
'Vietnam',
|
||||
'Virgin Islands (British)',
|
||||
'Virgin Islands (U.S.)',
|
||||
'Wallis and Futuna Islands',
|
||||
'Western Sahara',
|
||||
'Yemen',
|
||||
'Yugoslavia',
|
||||
'Zambia',
|
||||
'Zimbabwe',
|
||||
]
|
||||
"Latvia",
|
||||
"Lebanon",
|
||||
"Lesotho",
|
||||
"Liberia",
|
||||
"Libyan Arab Jamahiriya",
|
||||
"Liechtenstein",
|
||||
"Lithuania",
|
||||
"Luxembourg",
|
||||
"Macau",
|
||||
"Macedonia, The Former Yugoslav Republic of",
|
||||
"Madagascar",
|
||||
"Malawi",
|
||||
"Malaysia",
|
||||
"Maldives",
|
||||
"Mali",
|
||||
"Malta",
|
||||
"Marshall Islands",
|
||||
"Martinique",
|
||||
"Mauritania",
|
||||
"Mauritius",
|
||||
"Mayotte",
|
||||
"Mexico",
|
||||
"Micronesia, Federated States of",
|
||||
"Moldova, Republic of",
|
||||
"Monaco",
|
||||
"Mongolia",
|
||||
"Montserrat",
|
||||
"Morocco",
|
||||
"Mozambique",
|
||||
"Myanmar",
|
||||
"Namibia",
|
||||
"Nauru",
|
||||
"Nepal",
|
||||
"Netherlands",
|
||||
"Netherlands Antilles",
|
||||
"New Caledonia",
|
||||
"New Zealand",
|
||||
"Nicaragua",
|
||||
"Niger",
|
||||
"Nigeria",
|
||||
"Niue",
|
||||
"Norfolk Island",
|
||||
"Northern Mariana Islands",
|
||||
"Norway",
|
||||
"Oman",
|
||||
"Pakistan",
|
||||
"Palau",
|
||||
"Panama",
|
||||
"Papua New Guinea",
|
||||
"Paraguay",
|
||||
"Peru",
|
||||
"Philippines",
|
||||
"Pitcairn",
|
||||
"Poland",
|
||||
"Portugal",
|
||||
"Puerto Rico",
|
||||
"Qatar",
|
||||
"Reunion",
|
||||
"Romania",
|
||||
"Russian Federation",
|
||||
"Rwanda",
|
||||
"Saint Kitts and Nevis",
|
||||
"Saint Lucia",
|
||||
"Saint Vincent and the Grenadines",
|
||||
"Samoa",
|
||||
"San Marino",
|
||||
"Sao Tome and Principe",
|
||||
"Saudi Arabia",
|
||||
"Senegal",
|
||||
"Serbia",
|
||||
"Seychelles",
|
||||
"Sierra Leone",
|
||||
"Singapore",
|
||||
"Slovakia (Slovak Republic)",
|
||||
"Slovenia",
|
||||
"Solomon Islands",
|
||||
"Somalia",
|
||||
"South Africa",
|
||||
"South Georgia and the South Sandwich Islands",
|
||||
"Spain",
|
||||
"Sri Lanka",
|
||||
"St. Helena",
|
||||
"St. Pierre and Miquelon",
|
||||
"Sudan",
|
||||
"Suriname",
|
||||
"Svalbard and Jan Mayen Islands",
|
||||
"Swaziland",
|
||||
"Sweden",
|
||||
"Switzerland",
|
||||
"Syrian Arab Republic",
|
||||
"Taiwan, Province of China",
|
||||
"Tajikistan",
|
||||
"Tanzania, United Republic of",
|
||||
"United States of America",
|
||||
"Thailand",
|
||||
"Togo",
|
||||
"Tokelau",
|
||||
"Tonga",
|
||||
"Trinidad and Tobago",
|
||||
"Tunisia",
|
||||
"Turkey",
|
||||
"Turkmenistan",
|
||||
"Turks and Caicos Islands",
|
||||
"Tuvalu",
|
||||
"Uganda",
|
||||
"Ukraine",
|
||||
"United Arab Emirates",
|
||||
"United Kingdom",
|
||||
"United States",
|
||||
"United States Minor Outlying Islands",
|
||||
"Uruguay",
|
||||
"Uzbekistan",
|
||||
"Vanuatu",
|
||||
"Venezuela",
|
||||
"Vietnam",
|
||||
"Virgin Islands (British)",
|
||||
"Virgin Islands (U.S.)",
|
||||
"Wallis and Futuna Islands",
|
||||
"Western Sahara",
|
||||
"Yemen",
|
||||
"Yugoslavia",
|
||||
"Zambia",
|
||||
"Zimbabwe",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
import { TBarChartData } from '../types'
|
||||
import { TBarChartData } from "../types";
|
||||
|
||||
export const barChartData: TBarChartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Last year',
|
||||
backgroundColor: 'primary',
|
||||
label: "Last year",
|
||||
backgroundColor: "primary",
|
||||
data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
|
||||
},
|
||||
{
|
||||
label: 'Current year',
|
||||
backgroundColor: 'info',
|
||||
label: "Current year",
|
||||
backgroundColor: "info",
|
||||
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 = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'USA',
|
||||
backgroundColor: 'danger',
|
||||
label: "USA",
|
||||
backgroundColor: "danger",
|
||||
data: [
|
||||
{
|
||||
x: 23,
|
||||
|
|
@ -49,8 +49,8 @@ export const bubbleChartData: TBubbleChartData = {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: 'Russia',
|
||||
backgroundColor: 'primary',
|
||||
label: "Russia",
|
||||
backgroundColor: "primary",
|
||||
data: [
|
||||
{
|
||||
x: 0,
|
||||
|
|
@ -95,8 +95,8 @@ export const bubbleChartData: TBubbleChartData = {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: 'Canada',
|
||||
backgroundColor: 'warning',
|
||||
label: "Canada",
|
||||
backgroundColor: "warning",
|
||||
data: [
|
||||
{
|
||||
x: 10,
|
||||
|
|
@ -136,8 +136,8 @@ export const bubbleChartData: TBubbleChartData = {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: 'Belarus',
|
||||
backgroundColor: 'info',
|
||||
label: "Belarus",
|
||||
backgroundColor: "info",
|
||||
data: [
|
||||
{
|
||||
x: 35,
|
||||
|
|
@ -182,8 +182,8 @@ export const bubbleChartData: TBubbleChartData = {
|
|||
],
|
||||
},
|
||||
{
|
||||
label: 'Ukraine',
|
||||
backgroundColor: 'success',
|
||||
label: "Ukraine",
|
||||
backgroundColor: "success",
|
||||
data: [
|
||||
{
|
||||
x: 25,
|
||||
|
|
@ -228,4 +228,4 @@ export const bubbleChartData: TBubbleChartData = {
|
|||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,34 +1,36 @@
|
|||
import { computed, ref, watch } from 'vue'
|
||||
import { useColors, useGlobalConfig } from 'vuestic-ui'
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useColors, useGlobalConfig } from "vuestic-ui";
|
||||
|
||||
type chartColors = string | string[]
|
||||
type chartColors = string | string[];
|
||||
|
||||
export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
|
||||
const { getGlobalConfig } = useGlobalConfig()
|
||||
const { setHSLAColor, getColor } = useColors()
|
||||
const { getGlobalConfig } = useGlobalConfig();
|
||||
const { setHSLAColor, getColor } = useColors();
|
||||
|
||||
const generateHSLAColors = (colors: chartColors) =>
|
||||
typeof colors === 'string'
|
||||
typeof colors === "string"
|
||||
? 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) =>
|
||||
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 generatedColors = ref(generateColors(chartColors))
|
||||
const generatedHSLAColors = ref(generateHSLAColors(chartColors));
|
||||
const generatedColors = ref(generateColors(chartColors));
|
||||
|
||||
const theme = computed(() => getGlobalConfig().colors!)
|
||||
const theme = computed(() => getGlobalConfig().colors!);
|
||||
|
||||
watch(theme, () => {
|
||||
generatedHSLAColors.value = generateHSLAColors(chartColors)
|
||||
generatedColors.value = generateColors(chartColors)
|
||||
})
|
||||
generatedHSLAColors.value = generateHSLAColors(chartColors);
|
||||
generatedColors.value = generateColors(chartColors);
|
||||
});
|
||||
|
||||
return {
|
||||
generateHSLAColors,
|
||||
generateColors,
|
||||
generatedColors,
|
||||
generatedHSLAColors,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
import { computed, ComputedRef } from 'vue'
|
||||
import { useChartColors } from './useChartColors'
|
||||
import { TChartData } from '../../types'
|
||||
import { computed, ComputedRef } from "vue";
|
||||
import { useChartColors } from "./useChartColors";
|
||||
import { TChartData } from "../../types";
|
||||
|
||||
export function useChartData<T extends TChartData>(data: T, alfa?: number): ComputedRef<T> {
|
||||
const datasetsColors = data.datasets.map((dataset) => dataset.backgroundColor as string)
|
||||
export function useChartData<T extends TChartData>(
|
||||
data: T,
|
||||
alfa?: number,
|
||||
): ComputedRef<T> {
|
||||
const datasetsColors = data.datasets.map(
|
||||
(dataset) => dataset.backgroundColor as string,
|
||||
);
|
||||
|
||||
const datasetsThemesColors = datasetsColors.map(
|
||||
(colors) => useChartColors(colors, alfa)[alfa ? 'generatedHSLAColors' : 'generatedColors'],
|
||||
)
|
||||
(colors) =>
|
||||
useChartColors(colors, alfa)[
|
||||
alfa ? "generatedHSLAColors" : "generatedColors"
|
||||
],
|
||||
);
|
||||
|
||||
return computed(() => {
|
||||
const datasets = data.datasets.map((dataset, idx) => ({
|
||||
...dataset,
|
||||
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 expensesBackground = '#fff'
|
||||
export const earningsBackground = '#ECF0F1'
|
||||
export const profitBackground = "#154EC1";
|
||||
export const expensesBackground = "#fff";
|
||||
export const earningsBackground = "#ECF0F1";
|
||||
|
||||
export const doughnutChartData: TDoughnutChartData = {
|
||||
labels: ['Profit', 'Expenses'],
|
||||
labels: ["Profit", "Expenses"],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Yearly Breakdown',
|
||||
label: "Yearly Breakdown",
|
||||
backgroundColor: [profitBackground, earningsBackground],
|
||||
data: [432, 167],
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
import { TBarChartData } from '../types'
|
||||
import { TBarChartData } from "../types";
|
||||
|
||||
export const horizontalBarChartData: TBarChartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Vuestic Satisfaction Score',
|
||||
backgroundColor: 'primary',
|
||||
label: "Vuestic Satisfaction Score",
|
||||
backgroundColor: "primary",
|
||||
data: [80, 90, 50, 70, 60, 90, 50, 90, 80, 40, 72, 93],
|
||||
},
|
||||
{
|
||||
label: 'Bulma Satisfaction Score',
|
||||
backgroundColor: 'danger',
|
||||
label: "Bulma Satisfaction Score",
|
||||
backgroundColor: "danger",
|
||||
data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53],
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export { bubbleChartData } from './bubbleChartData'
|
||||
export { doughnutChartData } from './doughnutChartData'
|
||||
export { barChartData } from './barChartData'
|
||||
export { horizontalBarChartData } from './horizontalBarChartData'
|
||||
export { lineChartData } from './lineChartData'
|
||||
export { pieChartData } from './pieChartData'
|
||||
export { bubbleChartData } from "./bubbleChartData";
|
||||
export { doughnutChartData } from "./doughnutChartData";
|
||||
export { barChartData } from "./barChartData";
|
||||
export { horizontalBarChartData } from "./horizontalBarChartData";
|
||||
export { lineChartData } from "./lineChartData";
|
||||
export { pieChartData } from "./pieChartData";
|
||||
|
||||
// TODO: clean up charts data, after dashboard rework
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { TLineChartData } from '../types'
|
||||
import { TLineChartData } from "../types";
|
||||
|
||||
export const lineChartData: TLineChartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Monthly Earnings',
|
||||
backgroundColor: 'rgba(75,192,192,0.4)',
|
||||
label: "Monthly Earnings",
|
||||
backgroundColor: "rgba(75,192,192,0.4)",
|
||||
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 = {
|
||||
labels: ['Africa', 'Asia', 'Europe'],
|
||||
labels: ["Africa", "Asia", "Europe"],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Population (millions)',
|
||||
backgroundColor: ['primary', 'warning', 'danger'],
|
||||
label: "Population (millions)",
|
||||
backgroundColor: ["primary", "warning", "danger"],
|
||||
data: [2478, 5267, 734],
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,49 @@
|
|||
export const earningsColor = '#49A8FF'
|
||||
export const expensesColor = '#154EC1'
|
||||
export const earningsColor = "#49A8FF";
|
||||
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 = {
|
||||
month: string
|
||||
earning: number
|
||||
expenses: number
|
||||
}
|
||||
month: string;
|
||||
earning: number;
|
||||
expenses: number;
|
||||
};
|
||||
|
||||
export const generateRevenues = (months: string[]): Revenues[] => {
|
||||
return months.map((month: string) => {
|
||||
const earning = Math.floor(Math.random() * 100000 + 10000)
|
||||
const earning = Math.floor(Math.random() * 100000 + 10000);
|
||||
return {
|
||||
month,
|
||||
earning,
|
||||
expenses: Math.floor(earning * Math.random()),
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const getRevenuePerMonth = (month: string, revenues: Revenues[]): Revenues => {
|
||||
const revenue = revenues.find((revenue) => revenue.month === month)
|
||||
return revenue || { month, earning: 0, expenses: 0 }
|
||||
}
|
||||
export const getRevenuePerMonth = (
|
||||
month: string,
|
||||
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 => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
export const formatMoney = (amount: number, currency = "USD"): string => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency,
|
||||
}).format(amount)
|
||||
}
|
||||
}).format(amount);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,59 +1,66 @@
|
|||
import { sleep } from '../../services/utils'
|
||||
import projectsDb from './projects-db.json'
|
||||
import usersDb from './users-db.json'
|
||||
import { sleep } from "../../services/utils";
|
||||
import projectsDb from "./projects-db.json";
|
||||
import usersDb from "./users-db.json";
|
||||
|
||||
// Simulate API calls
|
||||
export type Pagination = {
|
||||
page: number
|
||||
perPage: number
|
||||
total: number
|
||||
}
|
||||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type Sorting = {
|
||||
sortBy: keyof (typeof projectsDb)[number] | undefined
|
||||
sortingOrder: 'asc' | 'desc' | null
|
||||
}
|
||||
sortBy: keyof (typeof projectsDb)[number] | undefined;
|
||||
sortingOrder: "asc" | "desc" | null;
|
||||
};
|
||||
|
||||
const getSortItem = (obj: any, sortBy: keyof (typeof projectsDb)[number]) => {
|
||||
if (sortBy === 'project_owner') {
|
||||
return obj.project_owner.fullname
|
||||
if (sortBy === "project_owner") {
|
||||
return obj.project_owner.fullname;
|
||||
}
|
||||
|
||||
if (sortBy === 'team') {
|
||||
return obj.team.map((user: any) => user.fullname).join(', ')
|
||||
if (sortBy === "team") {
|
||||
return obj.team.map((user: any) => user.fullname).join(", ");
|
||||
}
|
||||
|
||||
if (sortBy === 'creation_date') {
|
||||
return new Date(obj[sortBy])
|
||||
if (sortBy === "creation_date") {
|
||||
return new Date(obj[sortBy]);
|
||||
}
|
||||
|
||||
return obj[sortBy]
|
||||
}
|
||||
return obj[sortBy];
|
||||
};
|
||||
|
||||
export const getProjects = async (options: Sorting & Pagination) => {
|
||||
await sleep(1000)
|
||||
await sleep(1000);
|
||||
|
||||
const projects = projectsDb.map((project) => ({
|
||||
...project,
|
||||
project_owner: usersDb.find((user) => user.id === project.project_owner)! as (typeof usersDb)[number],
|
||||
team: usersDb.filter((user) => project.team.includes(user.id)) as (typeof usersDb)[number][],
|
||||
}))
|
||||
project_owner: usersDb.find(
|
||||
(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) {
|
||||
projects.sort((a, b) => {
|
||||
a = getSortItem(a, options.sortBy!)
|
||||
b = getSortItem(b, options.sortBy!)
|
||||
a = getSortItem(a, options.sortBy!);
|
||||
b = getSortItem(b, options.sortBy!);
|
||||
if (a < b) {
|
||||
return options.sortingOrder === 'asc' ? -1 : 1
|
||||
return options.sortingOrder === "asc" ? -1 : 1;
|
||||
}
|
||||
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 {
|
||||
data: normalizedProjects,
|
||||
|
|
@ -62,41 +69,51 @@ export const getProjects = async (options: Sorting & Pagination) => {
|
|||
perPage: options.perPage,
|
||||
total: projectsDb.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const addProject = async (project: Omit<(typeof projectsDb)[number], 'id' | 'creation_date'>) => {
|
||||
await sleep(1000)
|
||||
export const addProject = async (
|
||||
project: Omit<(typeof projectsDb)[number], "id" | "creation_date">,
|
||||
) => {
|
||||
await sleep(1000);
|
||||
|
||||
const newProject = {
|
||||
...project,
|
||||
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 {
|
||||
...newProject,
|
||||
project_owner: usersDb.find((user) => user.id === project.project_owner)! as (typeof usersDb)[number],
|
||||
team: usersDb.filter((user) => project.team.includes(user.id)) as (typeof usersDb)[number][],
|
||||
}
|
||||
}
|
||||
project_owner: usersDb.find(
|
||||
(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]) => {
|
||||
await sleep(1000)
|
||||
await sleep(1000);
|
||||
|
||||
const index = projectsDb.findIndex((p) => p.id === project.id)
|
||||
projectsDb[index] = project
|
||||
const index = projectsDb.findIndex((p) => p.id === project.id);
|
||||
projectsDb[index] = project;
|
||||
|
||||
return project
|
||||
}
|
||||
return project;
|
||||
};
|
||||
|
||||
export const removeProject = async (project: (typeof projectsDb)[number]) => {
|
||||
await sleep(1000)
|
||||
await sleep(1000);
|
||||
|
||||
const index = projectsDb.findIndex((p) => p.id === project.id)
|
||||
projectsDb.splice(index, 1)
|
||||
const index = projectsDb.findIndex((p) => p.id === project.id);
|
||||
projectsDb.splice(index, 1);
|
||||
|
||||
return project
|
||||
}
|
||||
return project;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { sleep } from '../../services/utils'
|
||||
import { User } from './../../pages/users/types'
|
||||
import usersDb from './users-db.json'
|
||||
import projectsDb from './projects-db.json'
|
||||
import { Project } from '../../pages/projects/types'
|
||||
import { sleep } from "../../services/utils";
|
||||
import { User } from "./../../pages/users/types";
|
||||
import usersDb from "./users-db.json";
|
||||
import projectsDb from "./projects-db.json";
|
||||
import { Project } from "../../pages/projects/types";
|
||||
|
||||
export const users = usersDb as User[]
|
||||
export const users = usersDb as User[];
|
||||
|
||||
const getUserProjects = (userId: number | string) => {
|
||||
return projectsDb
|
||||
|
|
@ -12,65 +12,74 @@ const getUserProjects = (userId: number | string) => {
|
|||
.map((project) => ({
|
||||
...project,
|
||||
project_owner: users.find((user) => user.id === project.project_owner)!,
|
||||
team: project.team.map((userId) => users.find((user) => user.id === userId)!),
|
||||
status: project.status as Project['status'],
|
||||
}))
|
||||
}
|
||||
team: project.team.map(
|
||||
(userId) => users.find((user) => user.id === userId)!,
|
||||
),
|
||||
status: project.status as Project["status"],
|
||||
}));
|
||||
};
|
||||
|
||||
// Simulate API calls
|
||||
|
||||
export type Pagination = {
|
||||
page: number
|
||||
perPage: number
|
||||
total: number
|
||||
}
|
||||
page: number;
|
||||
perPage: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type Sorting = {
|
||||
sortBy: keyof User | undefined
|
||||
sortingOrder: 'asc' | 'desc' | null
|
||||
}
|
||||
sortBy: keyof User | undefined;
|
||||
sortingOrder: "asc" | "desc" | null;
|
||||
};
|
||||
|
||||
export type Filters = {
|
||||
isActive: boolean
|
||||
search: string
|
||||
}
|
||||
isActive: boolean;
|
||||
search: string;
|
||||
};
|
||||
|
||||
const getSortItem = (obj: any, sortBy: string) => {
|
||||
if (sortBy === 'projects') {
|
||||
return obj.projects.map((project: any) => project.project_name).join(', ')
|
||||
if (sortBy === "projects") {
|
||||
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>) => {
|
||||
await sleep(1000)
|
||||
const { isActive, search, sortBy, sortingOrder } = filters
|
||||
let filteredUsers = users
|
||||
export const getUsers = async (
|
||||
filters: Partial<Filters & Pagination & Sorting>,
|
||||
) => {
|
||||
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) {
|
||||
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) {
|
||||
filteredUsers = filteredUsers.sort((a, b) => {
|
||||
const first = getSortItem(a, sortBy)
|
||||
const second = getSortItem(b, sortBy)
|
||||
const first = getSortItem(a, sortBy);
|
||||
const second = getSortItem(b, sortBy);
|
||||
if (first > second) {
|
||||
return sortingOrder === 'asc' ? 1 : -1
|
||||
return sortingOrder === "asc" ? 1 : -1;
|
||||
}
|
||||
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 {
|
||||
data: filteredUsers.slice((page - 1) * perPage, page * perPage),
|
||||
pagination: {
|
||||
|
|
@ -78,24 +87,24 @@ export const getUsers = async (filters: Partial<Filters & Pagination & Sorting>)
|
|||
perPage,
|
||||
total: filteredUsers.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const addUser = async (user: User) => {
|
||||
await sleep(1000)
|
||||
users.unshift(user)
|
||||
}
|
||||
await sleep(1000);
|
||||
users.unshift(user);
|
||||
};
|
||||
|
||||
export const updateUser = async (user: User) => {
|
||||
await sleep(1000)
|
||||
const index = users.findIndex((u) => u.id === user.id)
|
||||
users[index] = user
|
||||
}
|
||||
await sleep(1000);
|
||||
const index = users.findIndex((u) => u.id === user.id);
|
||||
users[index] = user;
|
||||
};
|
||||
|
||||
export const removeUser = async (user: User) => {
|
||||
await sleep(1000)
|
||||
await sleep(1000);
|
||||
users.splice(
|
||||
users.findIndex((u) => u.id === user.id),
|
||||
1,
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import type { ChartData } from 'chart.js'
|
||||
import type { ChartData } from "chart.js";
|
||||
|
||||
export type ColorThemes = {
|
||||
[key: string]: string
|
||||
}
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export type TLineChartData = ChartData<'line', any, any>
|
||||
export type TBarChartData = ChartData<'bar', any, any>
|
||||
export type TBubbleChartData = ChartData<'bubble', any, any>
|
||||
export type TDoughnutChartData = ChartData<'doughnut', any, any>
|
||||
export type TPieChartData = ChartData<'pie', any, any>
|
||||
export type TLineChartData = ChartData<"line", any, any>;
|
||||
export type TBarChartData = ChartData<"bar", any, any>;
|
||||
export type TBubbleChartData = ChartData<"bubble", any, any>;
|
||||
export type TDoughnutChartData = ChartData<"doughnut", 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,
|
||||
})
|
||||
});
|
||||
|
||||
const messages: { [P: string]: Record<string, string> } = {}
|
||||
const messages: { [P: string]: Record<string, string> } = {};
|
||||
Object.entries(fileNameToLocaleModuleDict)
|
||||
.map(([fileName, localeModule]) => {
|
||||
const fileNameParts = fileName.split('/')
|
||||
const fileNameWithoutPath = fileNameParts[fileNameParts.length - 1]
|
||||
const localeName = fileNameWithoutPath.split('.json')[0]
|
||||
const fileNameParts = fileName.split("/");
|
||||
const fileNameWithoutPath = fileNameParts[fileNameParts.length - 1];
|
||||
const localeName = fileNameWithoutPath.split(".json")[0];
|
||||
|
||||
return [localeName, localeModule.default] as const
|
||||
return [localeName, localeModule.default] as const;
|
||||
})
|
||||
.forEach((localeNameLocaleMessagesTuple) => {
|
||||
messages[localeNameLocaleMessagesTuple[0]] = localeNameLocaleMessagesTuple[1]
|
||||
})
|
||||
messages[localeNameLocaleMessagesTuple[0]] =
|
||||
localeNameLocaleMessagesTuple[1];
|
||||
});
|
||||
|
||||
export default createI18n({
|
||||
legacy: false,
|
||||
locale: 'ru',
|
||||
fallbackLocale: 'ru',
|
||||
locale: "ru",
|
||||
fallbackLocale: "ru",
|
||||
messages,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,178 +1,177 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "Я согласен",
|
||||
"createAccount": "Создать аккаунт",
|
||||
"createNewAccount": "Создать новый аккаунт",
|
||||
"email": "Email",
|
||||
"login": "Логин",
|
||||
"password": "Пароль",
|
||||
"recover_password": "Восстановить пароль",
|
||||
"sign_up": "Регистрация",
|
||||
"keep_logged_in": "Запомнить",
|
||||
"termsOfUse": "Terms of Use.",
|
||||
"reset_password": "Сбросить пароль"
|
||||
"auth": {
|
||||
"agree": "Я согласен",
|
||||
"createAccount": "Создать аккаунт",
|
||||
"createNewAccount": "Создать новый аккаунт",
|
||||
"email": "Email",
|
||||
"login": "Логин",
|
||||
"password": "Пароль",
|
||||
"recover_password": "Восстановить пароль",
|
||||
"sign_up": "Регистрация",
|
||||
"keep_logged_in": "Запомнить",
|
||||
"termsOfUse": "Terms of Use.",
|
||||
"reset_password": "Сбросить пароль"
|
||||
},
|
||||
"404": {
|
||||
"title": "Эта страница последняя.",
|
||||
"text": "Если вы счиате, что это ошибка, напишите нам ",
|
||||
"back_button": "На главную"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "Primary text styles",
|
||||
"secondary": "Secondary text styles"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "Versions",
|
||||
"setupRemoteConnections": "Setup Remote Connections",
|
||||
"currentVisitors": "Current Visitors",
|
||||
"navigationLayout": "navigation layout",
|
||||
"topBarButton": "Top Bar",
|
||||
"sideBarButton": "Side Bar"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "Português",
|
||||
"english": "English",
|
||||
"spanish": "Spanish",
|
||||
"simplified_chinese": "Simplified Chinese",
|
||||
"persian": "Persian"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "Auth",
|
||||
"buttons": "Buttons",
|
||||
"timelines": "Timelines",
|
||||
"dashboard": "Статистика",
|
||||
"billing": "Billing",
|
||||
"login": "Login",
|
||||
"preferences": "Настройки Аккаунта",
|
||||
"payments": "Payments",
|
||||
"settings": "Application settings",
|
||||
"pricing-plans": "Pricing plans",
|
||||
"payment-methods": "Payment methods",
|
||||
"signup": "Signup",
|
||||
"recover-password": "Recover password",
|
||||
"recover-password-email": "Recover password email",
|
||||
"404": "404",
|
||||
"faq": "FAQ",
|
||||
"users": "Users",
|
||||
"projects": "Projects"
|
||||
},
|
||||
"messages": {
|
||||
"all": "See all messages",
|
||||
"new": "New messages from {name}",
|
||||
"mark_as_read": "Mark As Read"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "Web development inquiries:",
|
||||
"repository": "GitHub Repo"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "See all notifications",
|
||||
"less": "See less notifications",
|
||||
"mark_as_read": "Mark as read",
|
||||
"sentMessage": "sent you a message",
|
||||
"uploadedZip": "uploaded a new Zip file with {type}",
|
||||
"startedTopic": "started a new topic"
|
||||
},
|
||||
"user": {
|
||||
"language": "Change language",
|
||||
"logout": "Выход",
|
||||
"profile": "Профиль",
|
||||
"settings": "Настройки",
|
||||
"billing": "Платежы",
|
||||
"faq": "FAQ",
|
||||
"helpAndSupport": "Помощь",
|
||||
"projects": "Projects",
|
||||
"account": "Аккаунт",
|
||||
"explore": "Explore"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "Basic",
|
||||
"icons": "Icons",
|
||||
"selectable": "Selectable",
|
||||
"editable": "Editable",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Chat",
|
||||
"sendButton": "Send"
|
||||
},
|
||||
"spacingPlayground": {
|
||||
"value": "Value",
|
||||
"margin": "Margin",
|
||||
"padding": "Padding"
|
||||
},
|
||||
"spacing": {
|
||||
"title": "Spacing"
|
||||
},
|
||||
"cards": {
|
||||
"cards": "Cards",
|
||||
"fixed": "Fixed",
|
||||
"floating": "Floating",
|
||||
"contentText": "The unique stripes of zebras make them one of the animals most familiar to people.",
|
||||
"contentTextLong": "The unique stripes of zebras make them one of the animals most familiar to people. They occur in a variety of habitats, such as grasslands, savannas, woodlands, thorny scrublands, mountains, and coastal hills. Various anthropogenic factors have had a severe impact on zebra populations, in particular hunting for skins and habitat destruction. Grévy's zebra and the mountain zebra are endangered. While plains zebras are much more plentiful, one subspecies, the quagga.",
|
||||
"rowHeight": "Row height",
|
||||
"title": {
|
||||
"default": "Default",
|
||||
"withControls": "With controls",
|
||||
"customHeader": "Custom header",
|
||||
"withoutHeader": "Without header",
|
||||
"withImage": "With Image",
|
||||
"withTitleOnImage": "With title on image",
|
||||
"withCustomTitleOnImage": "With custom title on image",
|
||||
"withStripe": "With stripe",
|
||||
"withBackground": "With background"
|
||||
},
|
||||
"404": {
|
||||
"title": "Эта страница последняя.",
|
||||
"text": "Если вы счиате, что это ошибка, напишите нам ",
|
||||
"back_button": "На главную"
|
||||
"button": {
|
||||
"main": "Main",
|
||||
"cancel": "Cancel",
|
||||
"showMore": "Show More",
|
||||
"readMore": "Show More"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "Primary text styles",
|
||||
"secondary": "Secondary text styles"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "Versions",
|
||||
"setupRemoteConnections": "Setup Remote Connections",
|
||||
"currentVisitors": "Current Visitors",
|
||||
"navigationLayout": "navigation layout",
|
||||
"topBarButton": "Top Bar",
|
||||
"sideBarButton": "Side Bar"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "Português",
|
||||
"english": "English",
|
||||
"spanish": "Spanish",
|
||||
"simplified_chinese": "Simplified Chinese",
|
||||
"persian": "Persian"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "Auth",
|
||||
"buttons": "Buttons",
|
||||
"timelines": "Timelines",
|
||||
"dashboard": "Dashboard",
|
||||
"billing": "Billing",
|
||||
"login": "Login",
|
||||
"preferences": "Account preferences",
|
||||
"payments": "Payments",
|
||||
"settings": "Application settings",
|
||||
"pricing-plans": "Pricing plans",
|
||||
"payment-methods": "Payment methods",
|
||||
"signup": "Signup",
|
||||
"recover-password": "Recover password",
|
||||
"recover-password-email": "Recover password email",
|
||||
"404": "404",
|
||||
"faq": "FAQ",
|
||||
"users": "Users",
|
||||
"projects": "Projects"
|
||||
},
|
||||
"messages": {
|
||||
"all": "See all messages",
|
||||
"new": "New messages from {name}",
|
||||
"mark_as_read": "Mark As Read"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "Web development inquiries:",
|
||||
"repository": "GitHub Repo"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "See all notifications",
|
||||
"less": "See less notifications",
|
||||
"mark_as_read": "Mark as read",
|
||||
"sentMessage": "sent you a message",
|
||||
"uploadedZip": "uploaded a new Zip file with {type}",
|
||||
"startedTopic": "started a new topic"
|
||||
},
|
||||
"user": {
|
||||
"language": "Change language",
|
||||
"logout": "Logout",
|
||||
"profile": "Profile",
|
||||
"settings": "Settings",
|
||||
"billing": "Billing",
|
||||
"faq": "FAQ",
|
||||
"helpAndSupport": "Help & support",
|
||||
"projects": "Projects",
|
||||
"account": "Account",
|
||||
"explore": "Explore"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "Basic",
|
||||
"icons": "Icons",
|
||||
"selectable": "Selectable",
|
||||
"editable": "Editable",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Chat",
|
||||
"sendButton": "Send"
|
||||
},
|
||||
"spacingPlayground": {
|
||||
"value": "Value",
|
||||
"margin": "Margin",
|
||||
"padding": "Padding"
|
||||
},
|
||||
"spacing": {
|
||||
"title": "Spacing"
|
||||
},
|
||||
"cards": {
|
||||
"cards": "Cards",
|
||||
"fixed": "Fixed",
|
||||
"floating": "Floating",
|
||||
"contentText": "The unique stripes of zebras make them one of the animals most familiar to people.",
|
||||
"contentTextLong": "The unique stripes of zebras make them one of the animals most familiar to people. They occur in a variety of habitats, such as grasslands, savannas, woodlands, thorny scrublands, mountains, and coastal hills. Various anthropogenic factors have had a severe impact on zebra populations, in particular hunting for skins and habitat destruction. Grévy's zebra and the mountain zebra are endangered. While plains zebras are much more plentiful, one subspecies, the quagga.",
|
||||
"rowHeight": "Row height",
|
||||
"title": {
|
||||
"default": "Default",
|
||||
"withControls": "With controls",
|
||||
"customHeader": "Custom header",
|
||||
"withoutHeader": "Without header",
|
||||
"withImage": "With Image",
|
||||
"withTitleOnImage": "With title on image",
|
||||
"withCustomTitleOnImage": "With custom title on image",
|
||||
"withStripe": "With stripe",
|
||||
"withBackground": "With background"
|
||||
},
|
||||
"button": {
|
||||
"main": "Main",
|
||||
"cancel": "Cancel",
|
||||
"showMore": "Show More",
|
||||
"readMore": "Show More"
|
||||
},
|
||||
"link": {
|
||||
"edit": "Edit",
|
||||
"setAsDefault": "Set as default",
|
||||
"delete": "Delete",
|
||||
"traveling": "Traveling",
|
||||
"france": "France",
|
||||
"review": "Review",
|
||||
"feedback": "Leave feedback",
|
||||
"readFull": "Read full article",
|
||||
"secondaryAction": "Secondary action",
|
||||
"action1": "Action 1",
|
||||
"action2": "Action 2"
|
||||
}
|
||||
},
|
||||
"colors": {
|
||||
"themeColors": "Theme Colors",
|
||||
"extraColors": "Extra Colors",
|
||||
"gradients": {
|
||||
"basic": {
|
||||
"title": "Button Gradients"
|
||||
},
|
||||
"hovered": {
|
||||
"title": "Hovered Button Gradients",
|
||||
"text": "Lighten 15% applied to an original style (gradient or flat color) for hover state."
|
||||
},
|
||||
"pressed": {
|
||||
"title": "Pressed Button Gradients",
|
||||
"text": "Darken 15% applied to an original style (gradient or flat color) for pressed state."
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"alignment": "Tabs alignment",
|
||||
"overflow": "Tabs overflow",
|
||||
"hidden": "Tabs with hidden slider",
|
||||
"grow": "Tabs grow"
|
||||
},
|
||||
"helpAndSupport": "Help & support",
|
||||
"aboutVuesticAdmin": "About Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "Search..."
|
||||
},
|
||||
"buttonSelect": {
|
||||
"dark": "Dark",
|
||||
"light": "Light"
|
||||
"link": {
|
||||
"edit": "Edit",
|
||||
"setAsDefault": "Set as default",
|
||||
"delete": "Delete",
|
||||
"traveling": "Traveling",
|
||||
"france": "France",
|
||||
"review": "Review",
|
||||
"feedback": "Leave feedback",
|
||||
"readFull": "Read full article",
|
||||
"secondaryAction": "Secondary action",
|
||||
"action1": "Action 1",
|
||||
"action2": "Action 2"
|
||||
}
|
||||
},
|
||||
"colors": {
|
||||
"themeColors": "Theme Colors",
|
||||
"extraColors": "Extra Colors",
|
||||
"gradients": {
|
||||
"basic": {
|
||||
"title": "Button Gradients"
|
||||
},
|
||||
"hovered": {
|
||||
"title": "Hovered Button Gradients",
|
||||
"text": "Lighten 15% applied to an original style (gradient or flat color) for hover state."
|
||||
},
|
||||
"pressed": {
|
||||
"title": "Pressed Button Gradients",
|
||||
"text": "Darken 15% applied to an original style (gradient or flat color) for pressed state."
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"alignment": "Tabs alignment",
|
||||
"overflow": "Tabs overflow",
|
||||
"hidden": "Tabs with hidden slider",
|
||||
"grow": "Tabs grow"
|
||||
},
|
||||
"helpAndSupport": "Help & support",
|
||||
"aboutVuesticAdmin": "About Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "Search..."
|
||||
},
|
||||
"buttonSelect": {
|
||||
"dark": "Dark",
|
||||
"light": "Light"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
<template>
|
||||
<VaLayout
|
||||
: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"
|
||||
>
|
||||
<template #top>
|
||||
|
|
@ -9,13 +14,25 @@
|
|||
</template>
|
||||
|
||||
<template #left>
|
||||
<AppSidebar :minimized="isSidebarMinimized" :animated="!isMobile" :mobile="isMobile" />
|
||||
<AppSidebar
|
||||
:minimized="isSidebarMinimized"
|
||||
:animated="!isMobile"
|
||||
:mobile="isMobile"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
<AppLayoutNavigation v-if="!isMobile" class="p-4" />
|
||||
|
|
@ -29,57 +46,59 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onBeforeUnmount, onMounted, ref, computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onBeforeRouteUpdate } from 'vue-router'
|
||||
import { useBreakpoint } from 'vuestic-ui'
|
||||
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
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 AppNavbar from '../components/navbar/AppNavbar.vue'
|
||||
import AppSidebar from '../components/sidebar/AppSidebar.vue'
|
||||
import AppLayoutNavigation from "../components/app-layout-navigation/AppLayoutNavigation.vue";
|
||||
import AppNavbar from "../components/navbar/AppNavbar.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 sidebarMinimizedWidth = ref(undefined)
|
||||
const sidebarWidth = ref("16rem");
|
||||
const sidebarMinimizedWidth = ref(undefined);
|
||||
|
||||
const isMobile = ref(false)
|
||||
const isTablet = ref(false)
|
||||
const { isSidebarMinimized } = storeToRefs(GlobalStore)
|
||||
const isMobile = ref(false);
|
||||
const isTablet = ref(false);
|
||||
const { isSidebarMinimized } = storeToRefs(GlobalStore);
|
||||
|
||||
const onResize = () => {
|
||||
isSidebarMinimized.value = breakpoints.mdDown
|
||||
isMobile.value = breakpoints.smDown
|
||||
isTablet.value = breakpoints.mdDown
|
||||
sidebarMinimizedWidth.value = isMobile.value ? '0' : '4.5rem'
|
||||
sidebarWidth.value = isTablet.value ? '100%' : '16rem'
|
||||
}
|
||||
isSidebarMinimized.value = breakpoints.mdDown;
|
||||
isMobile.value = breakpoints.smDown;
|
||||
isTablet.value = breakpoints.mdDown;
|
||||
sidebarMinimizedWidth.value = isMobile.value ? "0" : "4.5rem";
|
||||
sidebarWidth.value = isTablet.value ? "100%" : "16rem";
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', onResize)
|
||||
onResize()
|
||||
})
|
||||
window.addEventListener("resize", onResize);
|
||||
onResize();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', onResize)
|
||||
})
|
||||
window.removeEventListener("resize", onResize);
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(() => {
|
||||
if (breakpoints.mdDown) {
|
||||
// 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 = () => {
|
||||
isSidebarMinimized.value = true
|
||||
}
|
||||
isSidebarMinimized.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<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>
|
||||
<RouterLink
|
||||
class="bg-primary h-full flex items-center justify-center"
|
||||
|
|
@ -11,7 +14,9 @@
|
|||
</RouterLink>
|
||||
</template>
|
||||
<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 />
|
||||
</main>
|
||||
</template>
|
||||
|
|
@ -20,7 +25,9 @@
|
|||
<VaLayout v-else class="h-screen bg-[var(--va-background-secondary)]">
|
||||
<template #content>
|
||||
<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">
|
||||
<RouterLink class="py-4" to="/" aria-label="Visit homepage">
|
||||
<VuesticLogo class="mb-2" start="#0E41C9" />
|
||||
|
|
@ -34,8 +41,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useBreakpoint } from 'vuestic-ui'
|
||||
import VuesticLogo from '../components/VuesticLogo.vue'
|
||||
import { useBreakpoint } from "vuestic-ui";
|
||||
import VuesticLogo from "../components/VuesticLogo.vue";
|
||||
|
||||
const breakpoint = useBreakpoint()
|
||||
const breakpoint = useBreakpoint();
|
||||
</script>
|
||||
|
|
|
|||
30
src/main.ts
30
src/main.ts
|
|
@ -1,19 +1,19 @@
|
|||
import { createApp } from 'vue'
|
||||
import i18n from './i18n'
|
||||
import { createVuestic } from 'vuestic-ui'
|
||||
import { createGtm } from '@gtm-support/vue-gtm'
|
||||
import { createApp } from "vue";
|
||||
import i18n from "./i18n";
|
||||
import { createVuestic } from "vuestic-ui";
|
||||
import { createGtm } from "@gtm-support/vue-gtm";
|
||||
|
||||
import stores from './stores'
|
||||
import router from './router'
|
||||
import vuesticGlobalConfig from './services/vuestic-ui/global-config'
|
||||
import App from './App.vue'
|
||||
import stores from "./stores";
|
||||
import router from "./router";
|
||||
import vuesticGlobalConfig from "./services/vuestic-ui/global-config";
|
||||
import App from "./App.vue";
|
||||
|
||||
const app = createApp(App)
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(stores)
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.use(createVuestic({ config: vuesticGlobalConfig }))
|
||||
app.use(stores);
|
||||
app.use(router);
|
||||
app.use(i18n);
|
||||
app.use(createVuestic({ config: vuesticGlobalConfig }));
|
||||
|
||||
if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
||||
app.use(
|
||||
|
|
@ -22,7 +22,7 @@ if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
|||
debug: false,
|
||||
vueRouter: router,
|
||||
}),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
app.mount('#app')
|
||||
app.mount("#app");
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<script lang="ts" setup>
|
||||
import VuesticLogo from '../components/VuesticLogo.vue'
|
||||
import NotFoundImage from '../components/NotFoundImage.vue'
|
||||
import VuesticLogo from "../components/VuesticLogo.vue";
|
||||
import NotFoundImage from "../components/NotFoundImage.vue";
|
||||
</script>
|
||||
|
||||
<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="/">
|
||||
<VuesticLogo :gradient="false" class="my-8 h-5" />
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<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
|
||||
</VaButton>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
<script lang="ts" setup>
|
||||
import RevenueUpdates from './cards/RevenueReport.vue'
|
||||
import ProjectTable from './cards/ProjectTable.vue'
|
||||
import RevenueByLocationMap from './cards/RevenueByLocationMap.vue'
|
||||
import DataSection from './DataSection.vue'
|
||||
import YearlyBreakup from './cards/YearlyBreakup.vue'
|
||||
import MonthlyEarnings from './cards/MonthlyEarnings.vue'
|
||||
import RegionRevenue from './cards/RegionRevenue.vue'
|
||||
import Timeline from './cards/Timeline.vue'
|
||||
import RevenueUpdates from "./cards/RevenueReport.vue";
|
||||
import ProjectTable from "./cards/ProjectTable.vue";
|
||||
import RevenueByLocationMap from "./cards/RevenueByLocationMap.vue";
|
||||
import DataSection from "./DataSection.vue";
|
||||
import YearlyBreakup from "./cards/YearlyBreakup.vue";
|
||||
import MonthlyEarnings from "./cards/MonthlyEarnings.vue";
|
||||
import RegionRevenue from "./cards/RegionRevenue.vue";
|
||||
import Timeline from "./cards/Timeline.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="page-title font-bold">Dashboard</h1>
|
||||
<h1 class="page-title font-bold">Статистика</h1>
|
||||
<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%]" />
|
||||
<div class="flex flex-col gap-4 w-full sm:w-[30%]">
|
||||
<YearlyBreakup class="h-full" />
|
||||
<MonthlyEarnings />
|
||||
</div>
|
||||
</div>
|
||||
<DataSection />
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
</div> -->
|
||||
<!-- <DataSection /> -->
|
||||
<!-- <div class="flex flex-col md:flex-row gap-4">
|
||||
<RevenueByLocationMap class="w-full md:w-4/6" />
|
||||
<RegionRevenue class="w-full md:w-2/6" />
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<ProjectTable class="w-full md:w-1/2" />
|
||||
<Timeline class="w-full md:w-1/2" />
|
||||
</div>
|
||||
</div> -->
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -18,63 +18,63 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
import DataSectionItem from './DataSectionItem.vue'
|
||||
import { computed } from "vue";
|
||||
import { useColors } from "vuestic-ui";
|
||||
import DataSectionItem from "./DataSectionItem.vue";
|
||||
|
||||
interface DashboardMetric {
|
||||
id: string
|
||||
title: string
|
||||
value: string
|
||||
icon: string
|
||||
changeText: string
|
||||
changeDirection: 'up' | 'down'
|
||||
iconBackground: string
|
||||
iconColor: string
|
||||
id: string;
|
||||
title: string;
|
||||
value: string;
|
||||
icon: string;
|
||||
changeText: string;
|
||||
changeDirection: "up" | "down";
|
||||
iconBackground: string;
|
||||
iconColor: string;
|
||||
}
|
||||
|
||||
const { getColor } = useColors()
|
||||
const { getColor } = useColors();
|
||||
|
||||
const dashboardMetrics = computed<DashboardMetric[]>(() => [
|
||||
{
|
||||
id: 'openInvoices',
|
||||
title: 'Open invoices',
|
||||
value: '$35,548',
|
||||
icon: 'mso-attach_money',
|
||||
changeText: '$1, 450',
|
||||
changeDirection: 'down',
|
||||
iconBackground: getColor('success'),
|
||||
iconColor: getColor('on-success'),
|
||||
id: "openInvoices",
|
||||
title: "Open invoices",
|
||||
value: "$35,548",
|
||||
icon: "mso-attach_money",
|
||||
changeText: "$1, 450",
|
||||
changeDirection: "down",
|
||||
iconBackground: getColor("success"),
|
||||
iconColor: getColor("on-success"),
|
||||
},
|
||||
{
|
||||
id: 'ongoingProjects',
|
||||
title: 'Ongoing project',
|
||||
value: '15',
|
||||
icon: 'mso-folder_open',
|
||||
changeText: '25.36%',
|
||||
changeDirection: 'up',
|
||||
iconBackground: getColor('info'),
|
||||
iconColor: getColor('on-info'),
|
||||
id: "ongoingProjects",
|
||||
title: "Ongoing project",
|
||||
value: "15",
|
||||
icon: "mso-folder_open",
|
||||
changeText: "25.36%",
|
||||
changeDirection: "up",
|
||||
iconBackground: getColor("info"),
|
||||
iconColor: getColor("on-info"),
|
||||
},
|
||||
{
|
||||
id: 'employees',
|
||||
title: 'Employees',
|
||||
value: '25',
|
||||
icon: 'mso-account_circle',
|
||||
changeText: '2.5%',
|
||||
changeDirection: 'up',
|
||||
iconBackground: getColor('danger'),
|
||||
iconColor: getColor('on-danger'),
|
||||
id: "employees",
|
||||
title: "Employees",
|
||||
value: "25",
|
||||
icon: "mso-account_circle",
|
||||
changeText: "2.5%",
|
||||
changeDirection: "up",
|
||||
iconBackground: getColor("danger"),
|
||||
iconColor: getColor("on-danger"),
|
||||
},
|
||||
{
|
||||
id: 'newProfit',
|
||||
title: 'New profit',
|
||||
value: '27%',
|
||||
icon: 'mso-grade',
|
||||
changeText: '4%',
|
||||
changeDirection: 'up',
|
||||
iconBackground: getColor('warning'),
|
||||
iconColor: getColor('on-warning'),
|
||||
id: "newProfit",
|
||||
title: "New profit",
|
||||
value: "27%",
|
||||
icon: "mso-grade",
|
||||
changeText: "4%",
|
||||
changeDirection: "up",
|
||||
iconBackground: getColor("warning"),
|
||||
iconColor: getColor("on-warning"),
|
||||
},
|
||||
])
|
||||
]);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -31,20 +31,20 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { VaCard } from 'vuestic-ui'
|
||||
import { computed } from "vue";
|
||||
import { VaCard } from "vuestic-ui";
|
||||
|
||||
const props = defineProps<{
|
||||
title: string
|
||||
value: string | number
|
||||
changeText: string
|
||||
up: boolean
|
||||
iconBackground: string
|
||||
iconColor: string
|
||||
}>()
|
||||
title: string;
|
||||
value: string | number;
|
||||
changeText: string;
|
||||
up: boolean;
|
||||
iconBackground: string;
|
||||
iconColor: string;
|
||||
}>();
|
||||
|
||||
const changeClass = computed(() => ({
|
||||
'text-success': props.up,
|
||||
'text-red-600': !props.up,
|
||||
}))
|
||||
"text-success": props.up,
|
||||
"text-red-600": !props.up,
|
||||
}));
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<VaCard>
|
||||
<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>
|
||||
<VaCardContent>
|
||||
<div class="p-1 bg-black rounded absolute right-4 top-4">
|
||||
|
|
@ -16,22 +18,27 @@
|
|||
</p>
|
||||
</section>
|
||||
<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>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VaCard } from 'vuestic-ui'
|
||||
import VaChart from '../../../../components/va-charts/VaChart.vue'
|
||||
import { useChartData } from '../../../../data/charts/composables/useChartData'
|
||||
import { lineChartData } from '../../../../data/charts/lineChartData'
|
||||
import { ChartOptions } from 'chart.js'
|
||||
import { VaCard } from "vuestic-ui";
|
||||
import VaChart from "../../../../components/va-charts/VaChart.vue";
|
||||
import { useChartData } from "../../../../data/charts/composables/useChartData";
|
||||
import { lineChartData } from "../../../../data/charts/lineChartData";
|
||||
import { ChartOptions } from "chart.js";
|
||||
|
||||
const chartData = useChartData(lineChartData)
|
||||
const chartData = useChartData(lineChartData);
|
||||
|
||||
const options: ChartOptions<'line'> = {
|
||||
const options: ChartOptions<"line"> = {
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
|
|
@ -51,7 +58,7 @@ const options: ChartOptions<'line'> = {
|
|||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
mode: "index",
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
|
|
@ -61,5 +68,5 @@ const options: ChartOptions<'line'> = {
|
|||
enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,37 @@
|
|||
<script setup lang="ts">
|
||||
import { defineVaDataTableColumns } from 'vuestic-ui'
|
||||
import { Project } from '../../../projects/types'
|
||||
import UserAvatar from '../../../users/widgets/UserAvatar.vue'
|
||||
import ProjectStatusBadge from '../../../projects/components/ProjectStatusBadge.vue'
|
||||
import { useProjects } from '../../../projects/composables/useProjects'
|
||||
import { Pagination } from '../../../../data/pages/projects'
|
||||
import { ref } from 'vue'
|
||||
import { defineVaDataTableColumns } from "vuestic-ui";
|
||||
import { Project } from "../../../projects/types";
|
||||
import UserAvatar from "../../../users/widgets/UserAvatar.vue";
|
||||
import ProjectStatusBadge from "../../../projects/components/ProjectStatusBadge.vue";
|
||||
import { useProjects } from "../../../projects/composables/useProjects";
|
||||
import { Pagination } from "../../../../data/pages/projects";
|
||||
import { ref } from "vue";
|
||||
|
||||
const columns = defineVaDataTableColumns([
|
||||
{ label: 'Name', key: 'project_name', sortable: true },
|
||||
{ label: 'Status', key: 'status', sortable: true },
|
||||
{ label: 'Team', key: 'team', sortable: true },
|
||||
])
|
||||
{ label: "Name", key: "project_name", sortable: true },
|
||||
{ label: "Status", key: "status", 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({
|
||||
pagination,
|
||||
})
|
||||
});
|
||||
|
||||
const avatarColor = (userName: string) => {
|
||||
const colors = ['primary', '#FFD43A', '#ADFF00', '#262824', 'danger']
|
||||
const index = userName.charCodeAt(0) % colors.length
|
||||
return colors[index]
|
||||
}
|
||||
const colors = ["primary", "#FFD43A", "#ADFF00", "#262824", "danger"];
|
||||
const index = userName.charCodeAt(0) % colors.length;
|
||||
return colors[index];
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VaCard>
|
||||
<VaCardTitle class="flex items-start justify-between">
|
||||
<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>
|
||||
<VaCardContent>
|
||||
<div v-if="projects.length > 0">
|
||||
|
|
@ -70,7 +72,12 @@ const avatarColor = (userName: string) => {
|
|||
</template>
|
||||
</VaDataTable>
|
||||
</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>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
<template>
|
||||
<VaCard>
|
||||
<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>
|
||||
<VaCardContent class="flex flex-col gap-1">
|
||||
<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>
|
||||
|
||||
<VaDataTable
|
||||
|
|
@ -18,67 +27,72 @@
|
|||
]"
|
||||
:items="data"
|
||||
>
|
||||
<template #cell(revenue)="{ rowData }"> ${{ rowData[`revenue${selectedPeriod}`] }} </template>
|
||||
<template #cell(revenue)="{ rowData }">
|
||||
${{ rowData[`revenue${selectedPeriod}`] }}
|
||||
</template>
|
||||
</VaDataTable>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { downloadAsCSV } from '../../../../services/toCSV'
|
||||
import { ref } from "vue";
|
||||
import { downloadAsCSV } from "../../../../services/toCSV";
|
||||
|
||||
const selectedPeriod = ref('Today')
|
||||
const periods = ['Today', 'Week', 'Month'].map((period) => ({ label: period, value: period }))
|
||||
const selectedPeriod = ref("Today");
|
||||
const periods = ["Today", "Week", "Month"].map((period) => ({
|
||||
label: period,
|
||||
value: period,
|
||||
}));
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: 'Japan',
|
||||
revenueToday: '4,748,454',
|
||||
revenueWeek: '30,000,000',
|
||||
revenueMonth: '120,000,000',
|
||||
name: "Japan",
|
||||
revenueToday: "4,748,454",
|
||||
revenueWeek: "30,000,000",
|
||||
revenueMonth: "120,000,000",
|
||||
},
|
||||
{
|
||||
name: 'United Kingdom',
|
||||
revenueToday: '405,748',
|
||||
revenueWeek: '2,500,000',
|
||||
revenueMonth: '10,000,000',
|
||||
name: "United Kingdom",
|
||||
revenueToday: "405,748",
|
||||
revenueWeek: "2,500,000",
|
||||
revenueMonth: "10,000,000",
|
||||
},
|
||||
{
|
||||
name: 'United States',
|
||||
revenueToday: '308,536',
|
||||
revenueWeek: '1,800,000',
|
||||
revenueMonth: '8,000,000',
|
||||
name: "United States",
|
||||
revenueToday: "308,536",
|
||||
revenueWeek: "1,800,000",
|
||||
revenueMonth: "8,000,000",
|
||||
},
|
||||
{
|
||||
name: 'China',
|
||||
revenueToday: '250,963',
|
||||
revenueWeek: '1,600,000',
|
||||
revenueMonth: '7,000,000',
|
||||
name: "China",
|
||||
revenueToday: "250,963",
|
||||
revenueWeek: "1,600,000",
|
||||
revenueMonth: "7,000,000",
|
||||
},
|
||||
{
|
||||
name: 'Canada',
|
||||
revenueToday: '29,415',
|
||||
revenueWeek: '180,000',
|
||||
revenueMonth: '800,000',
|
||||
name: "Canada",
|
||||
revenueToday: "29,415",
|
||||
revenueWeek: "180,000",
|
||||
revenueMonth: "800,000",
|
||||
},
|
||||
{
|
||||
name: 'Australia',
|
||||
revenueToday: '15,000',
|
||||
revenueWeek: '100,000',
|
||||
revenueMonth: '500,000',
|
||||
name: "Australia",
|
||||
revenueToday: "15,000",
|
||||
revenueWeek: "100,000",
|
||||
revenueMonth: "500,000",
|
||||
},
|
||||
{
|
||||
name: 'India',
|
||||
revenueToday: '10,000',
|
||||
revenueWeek: '50,000',
|
||||
revenueMonth: '200,000',
|
||||
name: "India",
|
||||
revenueToday: "10,000",
|
||||
revenueWeek: "50,000",
|
||||
revenueMonth: "200,000",
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
const exportAsCSV = () => {
|
||||
downloadAsCSV(data, 'region-revenue')
|
||||
}
|
||||
downloadAsCSV(data, "region-revenue");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,60 +1,81 @@
|
|||
<template>
|
||||
<VaCard class="flex flex-col">
|
||||
<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>
|
||||
<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" />
|
||||
<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>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { VaCard } from 'vuestic-ui'
|
||||
import type countriesGeoJSON from '../../../../data/geo.json'
|
||||
import Map from '../../../../components/va-charts/chart-types/Map.vue'
|
||||
import type { ChartData } from 'chart.js'
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { VaCard } from "vuestic-ui";
|
||||
import type countriesGeoJSON from "../../../../data/geo.json";
|
||||
import Map from "../../../../components/va-charts/chart-types/Map.vue";
|
||||
import type { ChartData } from "chart.js";
|
||||
|
||||
const getRevenue = (countryName: string) => {
|
||||
if (['United States of America', 'Canada', 'United Kingdom', 'China', 'Japan'].includes(countryName)) {
|
||||
return 10
|
||||
if (
|
||||
[
|
||||
"United States of America",
|
||||
"Canada",
|
||||
"United Kingdom",
|
||||
"China",
|
||||
"Japan",
|
||||
].includes(countryName)
|
||||
) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
if (['Antarctica', 'Greenland'].includes(countryName)) {
|
||||
return 0
|
||||
if (["Antarctica", "Greenland"].includes(countryName)) {
|
||||
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 () => {
|
||||
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) {
|
||||
return {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
labels: geoJson.value.features.map((d) => d.properties.name),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Countries',
|
||||
data: geoJson.value.features.map((d) => ({ feature: d, value: getRevenue(d.properties.name) })),
|
||||
label: "Countries",
|
||||
data: geoJson.value.features.map((d) => ({
|
||||
feature: d,
|
||||
value: getRevenue(d.properties.name),
|
||||
})),
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
<template>
|
||||
<VaCard class="flex flex-col">
|
||||
<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">
|
||||
<VaSelect v-model="selectedMonth" preset="small" :options="monthsWithCurrentYear" class="w-24" />
|
||||
<VaButton class="h-2" size="small" preset="primary" @click="exportAsCSV">Export</VaButton>
|
||||
<VaSelect
|
||||
v-model="selectedMonth"
|
||||
preset="small"
|
||||
:options="monthsWithCurrentYear"
|
||||
class="w-24"
|
||||
/>
|
||||
<VaButton class="h-2" size="small" preset="primary" @click="exportAsCSV"
|
||||
>Export</VaButton
|
||||
>
|
||||
</div>
|
||||
</VaCardTitle>
|
||||
<VaCardContent 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">
|
||||
<VaCardContent
|
||||
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>
|
||||
<p class="text-xl font-semibold">{{ formatMoney(totalEarnings) }}</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>
|
||||
<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>
|
||||
</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 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>
|
||||
</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>
|
||||
</section>
|
||||
|
|
@ -40,10 +63,10 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { VaCard } from 'vuestic-ui'
|
||||
import RevenueReportChart from './RevenueReportChart.vue'
|
||||
import { downloadAsCSV } from '../../../../services/toCSV'
|
||||
import { ref, computed } from "vue";
|
||||
import { VaCard } from "vuestic-ui";
|
||||
import RevenueReportChart from "./RevenueReportChart.vue";
|
||||
import { downloadAsCSV } from "../../../../services/toCSV";
|
||||
import {
|
||||
earningsColor,
|
||||
expensesColor,
|
||||
|
|
@ -51,21 +74,26 @@ import {
|
|||
generateRevenues,
|
||||
getRevenuePerMonth,
|
||||
formatMoney,
|
||||
} from '../../../../data/charts/revenueChartData'
|
||||
} from "../../../../data/charts/revenueChartData";
|
||||
|
||||
const revenues = generateRevenues(months)
|
||||
const revenues = generateRevenues(months);
|
||||
|
||||
const currentYear = new Date().getFullYear()
|
||||
const monthsWithCurrentYear = months.map((month) => `${month} ${currentYear}`)
|
||||
const currentYear = new Date().getFullYear();
|
||||
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(() => {
|
||||
return earningsForSelectedMonth.value.earning + earningsForSelectedMonth.value.expenses
|
||||
})
|
||||
return (
|
||||
earningsForSelectedMonth.value.earning +
|
||||
earningsForSelectedMonth.value.expenses
|
||||
);
|
||||
});
|
||||
|
||||
const exportAsCSV = () => {
|
||||
downloadAsCSV(revenues, 'revenue-report')
|
||||
}
|
||||
downloadAsCSV(revenues, "revenue-report");
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -5,60 +5,71 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
import { ref, onMounted, nextTick } from "vue";
|
||||
import { Chart, registerables } from "chart.js";
|
||||
|
||||
import type { Revenues } from '../../../../data/charts/revenueChartData'
|
||||
import { earningsColor, expensesColor, formatMoney } from '../../../../data/charts/revenueChartData'
|
||||
import type { Revenues } from "../../../../data/charts/revenueChartData";
|
||||
import {
|
||||
earningsColor,
|
||||
expensesColor,
|
||||
formatMoney,
|
||||
} from "../../../../data/charts/revenueChartData";
|
||||
|
||||
const { revenues, months } = defineProps<{
|
||||
months: string[]
|
||||
revenues: Revenues[]
|
||||
}>()
|
||||
months: string[];
|
||||
revenues: Revenues[];
|
||||
}>();
|
||||
|
||||
Chart.register(...registerables)
|
||||
Chart.register(...registerables);
|
||||
|
||||
const BR_THICKNESS = 4
|
||||
const BR_THICKNESS = 4;
|
||||
|
||||
Chart.register([
|
||||
{
|
||||
id: 'background-color',
|
||||
id: "background-color",
|
||||
beforeDatasetDraw: function (chart) {
|
||||
const ctx = chart.ctx
|
||||
const config = chart.config
|
||||
const ctx = chart.ctx;
|
||||
const config = chart.config;
|
||||
|
||||
config.data.datasets.forEach(function (dataset, datasetIndex) {
|
||||
const meta = chart.getDatasetMeta(datasetIndex)
|
||||
if (meta.type === 'bar') {
|
||||
const bgColor = earningsColor
|
||||
const meta = chart.getDatasetMeta(datasetIndex);
|
||||
if (meta.type === "bar") {
|
||||
const bgColor = earningsColor;
|
||||
|
||||
// Loop through each bar in the dataset
|
||||
meta.data.forEach(function (bar) {
|
||||
ctx.fillStyle = bgColor
|
||||
ctx.fillRect(bar.x - BR_THICKNESS / 2, 0, BR_THICKNESS, chart.chartArea.bottom)
|
||||
})
|
||||
ctx.fillStyle = bgColor;
|
||||
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(() => {
|
||||
if (canvas.value) {
|
||||
const ctx = canvas.value.getContext('2d')
|
||||
const ctx = canvas.value.getContext("2d");
|
||||
if (ctx) {
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: months,
|
||||
datasets: [
|
||||
{
|
||||
// Show relative expenses ratio
|
||||
data: revenues.map(({ earning, expenses }) => (expenses / earning) * 100),
|
||||
data: revenues.map(
|
||||
({ earning, expenses }) => (expenses / earning) * 100,
|
||||
),
|
||||
backgroundColor: expensesColor,
|
||||
barThickness: BR_THICKNESS,
|
||||
},
|
||||
|
|
@ -86,20 +97,20 @@ onMounted(() => {
|
|||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function (value) {
|
||||
return formatMoney(Number(value))
|
||||
return formatMoney(Number(value));
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
doShowChart.value = true
|
||||
})
|
||||
})
|
||||
doShowChart.value = true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import VaTimelineItem from '../../../../components/va-timeline-item.vue'
|
||||
import VaTimelineItem from "../../../../components/va-timeline-item.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -11,30 +11,55 @@ import VaTimelineItem from '../../../../components/va-timeline-item.vue'
|
|||
<table class="mt-4">
|
||||
<tbody>
|
||||
<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">Refund #1234</RouterLink> to awaiting customer
|
||||
response
|
||||
<RouterLink class="va-link font-semibold" to="/users"
|
||||
>Donald</RouterLink
|
||||
>
|
||||
updated the status of
|
||||
<RouterLink class="va-link font-semibold" to="/users"
|
||||
>Refund #1234</RouterLink
|
||||
>
|
||||
to awaiting customer response
|
||||
</VaTimelineItem>
|
||||
<VaTimelineItem date="1h ago">
|
||||
<RouterLink class="va-link font-semibold" to="/users">Lycy Peterson</RouterLink> was added to the group,
|
||||
group name is Overtake
|
||||
<RouterLink class="va-link font-semibold" to="/users"
|
||||
>Lycy Peterson</RouterLink
|
||||
>
|
||||
was added to the group, group name is Overtake
|
||||
</VaTimelineItem>
|
||||
<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">Mannat #112233</RouterLink> with theme market
|
||||
<RouterLink class="va-link font-semibold" to="/users"
|
||||
>Joseph Rust</RouterLink
|
||||
>
|
||||
opened new showcase
|
||||
<RouterLink class="va-link font-semibold" to="/users"
|
||||
>Mannat #112233</RouterLink
|
||||
>
|
||||
with theme market
|
||||
</VaTimelineItem>
|
||||
<VaTimelineItem date="3d ago">
|
||||
<RouterLink class="va-link font-semibold" to="/users">Donald</RouterLink> updated the status to awaiting
|
||||
customer response
|
||||
<RouterLink class="va-link font-semibold" to="/users"
|
||||
>Donald</RouterLink
|
||||
>
|
||||
updated the status to awaiting customer response
|
||||
</VaTimelineItem>
|
||||
<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 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 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>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<VaCard>
|
||||
<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>
|
||||
<VaCardContent class="flex flex-row gap-1">
|
||||
<section class="w-1/2">
|
||||
|
|
@ -13,11 +15,17 @@
|
|||
</p>
|
||||
<div class="my-4 gap-2 flex flex-col">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -36,27 +44,37 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VaCard } from 'vuestic-ui'
|
||||
import VaChart from '../../../../components/va-charts/VaChart.vue'
|
||||
import { useChartData } from '../../../../data/charts/composables/useChartData'
|
||||
import { doughnutChartData, profitBackground, 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'
|
||||
import { VaCard } from "vuestic-ui";
|
||||
import VaChart from "../../../../components/va-charts/VaChart.vue";
|
||||
import { useChartData } from "../../../../data/charts/composables/useChartData";
|
||||
import {
|
||||
doughnutChartData,
|
||||
profitBackground,
|
||||
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,
|
||||
plugins: {
|
||||
...doughnutConfig.plugins,
|
||||
tooltip: {
|
||||
// Chart to small to show tooltips
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
position: "nearest",
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
{{ item.label }}
|
||||
<div class="not-found-pages__button-container pt-4 mb-0">
|
||||
<VaButton :to="{ name: item.buttonTo }">
|
||||
{{ 'View Example' }}
|
||||
{{ "View Example" }}
|
||||
</VaButton>
|
||||
</div>
|
||||
</VaCardContent>
|
||||
|
|
@ -19,28 +19,28 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref } from "vue";
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
imageUrl: 'https://i.imgur.com/GzUR0Wz.png',
|
||||
label: 'Advanced layouts',
|
||||
buttonTo: 'not-found-advanced',
|
||||
imageUrl: "https://i.imgur.com/GzUR0Wz.png",
|
||||
label: "Advanced layouts",
|
||||
buttonTo: "not-found-advanced",
|
||||
},
|
||||
{
|
||||
imageUrl: 'https://i.imgur.com/HttcXPi.png',
|
||||
label: 'Simple',
|
||||
buttonTo: 'not-found-simple',
|
||||
imageUrl: "https://i.imgur.com/HttcXPi.png",
|
||||
label: "Simple",
|
||||
buttonTo: "not-found-simple",
|
||||
},
|
||||
{
|
||||
imageUrl: 'https://i.imgur.com/dlcZMiG.png',
|
||||
label: 'Custom image',
|
||||
buttonTo: 'not-found-custom',
|
||||
imageUrl: "https://i.imgur.com/dlcZMiG.png",
|
||||
label: "Custom image",
|
||||
buttonTo: "not-found-custom",
|
||||
},
|
||||
{
|
||||
imageUrl: 'https://i.imgur.com/qcOlDz7.png',
|
||||
label: 'Large text heading',
|
||||
buttonTo: 'not-found-large-text',
|
||||
imageUrl: "https://i.imgur.com/qcOlDz7.png",
|
||||
label: "Large text heading",
|
||||
buttonTo: "not-found-large-text",
|
||||
},
|
||||
])
|
||||
]);
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
<div>
|
||||
<h1 class="font-semibold text-4xl mb-4">Check the email</h1>
|
||||
<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.
|
||||
For assistance, <span class="va-link">contact support</span>.
|
||||
Password reset instructions have been sent to your email. Check your
|
||||
inbox, including the spam folder if needed. For assistance,
|
||||
<span class="va-link">contact support</span>.
|
||||
</p>
|
||||
|
||||
<div class="flex justify-center mt-4">
|
||||
|
|
|
|||
|
|
@ -3,24 +3,52 @@
|
|||
<h1 class="font-semibold text-4xl mb-4">Войти</h1>
|
||||
<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>
|
||||
<VaInput v-model="formData.email" :rules="[validators.required, validators.email]" class="mb-4" label="Email"
|
||||
type="email" />
|
||||
<VaInput
|
||||
v-model="formData.email"
|
||||
:rules="[validators.required, validators.email]"
|
||||
class="mb-4"
|
||||
label="Email"
|
||||
type="email"
|
||||
/>
|
||||
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
||||
<VaInput v-model="formData.password" :rules="[validators.required]"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Пароль"
|
||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
||||
<VaInput
|
||||
v-model="formData.password"
|
||||
:rules="[validators.required]"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||
class="mb-4"
|
||||
label="Пароль"
|
||||
@clickAppendInner.stop="
|
||||
isPasswordVisible.value = !isPasswordVisible.value
|
||||
"
|
||||
>
|
||||
<template #appendInner>
|
||||
<VaIcon :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'" class="cursor-pointer"
|
||||
color="secondary" />
|
||||
<VaIcon
|
||||
:name="
|
||||
isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'
|
||||
"
|
||||
class="cursor-pointer"
|
||||
color="secondary"
|
||||
/>
|
||||
</template>
|
||||
</VaInput>
|
||||
</VaValue>
|
||||
|
||||
<div class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between">
|
||||
<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">
|
||||
<div
|
||||
class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
|
|
@ -32,31 +60,35 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import axios from 'axios';
|
||||
import { reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useForm, useToast } from 'vuestic-ui'
|
||||
import { validators } from '../../services/utils'
|
||||
import axios from "axios";
|
||||
import { reactive } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useForm, useToast } from "vuestic-ui";
|
||||
import { validators } from "../../services/utils";
|
||||
|
||||
const { validate } = useForm('form')
|
||||
const { push } = useRouter()
|
||||
const { init } = useToast()
|
||||
const { validate } = useForm("form");
|
||||
const { push } = useRouter();
|
||||
const { init } = useToast();
|
||||
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
password: '',
|
||||
email: "",
|
||||
password: "",
|
||||
keepLoggedIn: false,
|
||||
})
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
if (validate()) {
|
||||
axios.post('http://localhost:3000/api/v0/signin', {"login": formData.email, "password":formData.password })
|
||||
.then(response => {
|
||||
// TODO: save token
|
||||
init({ message: "Вы успешно вошли!", color: 'success' })
|
||||
push({ name: 'dashboard' })
|
||||
axios
|
||||
.post("http://localhost:3000/api/v0/signin", {
|
||||
login: formData.email,
|
||||
password: formData.password,
|
||||
})
|
||||
.catch(error => { });
|
||||
.then((response) => {
|
||||
// TODO: save token
|
||||
init({ message: "Вы успешно вошли!", color: "success" });
|
||||
push({ name: "dashboard" });
|
||||
})
|
||||
.catch((error) => {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
<VaForm ref="passwordForm" @submit.prevent="submit">
|
||||
<h1 class="font-semibold text-4xl mb-4">Забыли свой пароль?</h1>
|
||||
<p class="text-base mb-4 leading-5">
|
||||
Если вы забыли свой пароль, не волнуйтесь. Просто введите свой адрес электронной почты ниже, и мы отправим вам электронное письмо
|
||||
с временным паролем.
|
||||
Если вы забыли свой пароль, не волнуйтесь. Просто введите свой адрес
|
||||
электронной почты ниже, и мы отправим вам электронное письмо с временным
|
||||
паролем.
|
||||
</p>
|
||||
<VaInput
|
||||
v-model="email"
|
||||
|
|
@ -13,22 +14,28 @@
|
|||
type="email"
|
||||
/>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useForm } from 'vuestic-ui'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref } from "vue";
|
||||
import { useForm } from "vuestic-ui";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const email = ref('')
|
||||
const form = useForm('passwordForm')
|
||||
const router = useRouter()
|
||||
const email = ref("");
|
||||
const form = useForm("passwordForm");
|
||||
const router = useRouter();
|
||||
|
||||
const submit = () => {
|
||||
if (form.validate()) {
|
||||
router.push({ name: 'recover-password-email' })
|
||||
router.push({ name: "recover-password-email" });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,29 +3,65 @@
|
|||
<h1 class="font-semibold text-4xl mb-4">Регистрация</h1>
|
||||
<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>
|
||||
<VaInput v-model="formData.email"
|
||||
:rules="[(v) => !!v || 'Email обязателен', (v) => /.+@.+\..+/.test(v) || 'Email должен быть валидным']"
|
||||
class="mb-4" label="Email" type="email" />
|
||||
<VaInput
|
||||
v-model="formData.email"
|
||||
:rules="[
|
||||
(v) => !!v || 'Email обязателен',
|
||||
(v) => /.+@.+\..+/.test(v) || 'Email должен быть валидным',
|
||||
]"
|
||||
class="mb-4"
|
||||
label="Email"
|
||||
type="email"
|
||||
/>
|
||||
<VaValue v-slot="isPasswordVisible" :default-value="false">
|
||||
<VaInput ref="password1" v-model="formData.password" :rules="passwordRules"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Пароль"
|
||||
<VaInput
|
||||
ref="password1"
|
||||
v-model="formData.password"
|
||||
:rules="passwordRules"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||
class="mb-4"
|
||||
label="Пароль"
|
||||
messages="Пароль должен быть длинее 4-х символов."
|
||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
||||
@clickAppendInner.stop="
|
||||
isPasswordVisible.value = !isPasswordVisible.value
|
||||
"
|
||||
>
|
||||
<template #appendInner>
|
||||
<VaIcon :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'" class="cursor-pointer"
|
||||
color="secondary" />
|
||||
<VaIcon
|
||||
:name="
|
||||
isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'
|
||||
"
|
||||
class="cursor-pointer"
|
||||
color="secondary"
|
||||
/>
|
||||
</template>
|
||||
</VaInput>
|
||||
<VaInput ref="password2" v-model="formData.repeatPassword" :rules="[
|
||||
(v) => !!v || 'Поле повторите пароль обязательное',
|
||||
(v) => v === formData.password || 'Пароли не совпадают',
|
||||
]" :type="isPasswordVisible.value ? 'text' : 'password'" class="mb-4" label="Повторите пароль"
|
||||
@clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value">
|
||||
<VaInput
|
||||
ref="password2"
|
||||
v-model="formData.repeatPassword"
|
||||
:rules="[
|
||||
(v) => !!v || 'Поле повторите пароль обязательное',
|
||||
(v) => v === formData.password || 'Пароли не совпадают',
|
||||
]"
|
||||
:type="isPasswordVisible.value ? 'text' : 'password'"
|
||||
class="mb-4"
|
||||
label="Повторите пароль"
|
||||
@clickAppendInner.stop="
|
||||
isPasswordVisible.value = !isPasswordVisible.value
|
||||
"
|
||||
>
|
||||
<template #appendInner>
|
||||
<VaIcon :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'" class="cursor-pointer"
|
||||
color="secondary" />
|
||||
<VaIcon
|
||||
:name="
|
||||
isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'
|
||||
"
|
||||
class="cursor-pointer"
|
||||
color="secondary"
|
||||
/>
|
||||
</template>
|
||||
</VaInput>
|
||||
</VaValue>
|
||||
|
|
@ -37,38 +73,41 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import axios from 'axios';
|
||||
import { reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useForm, useToast } from 'vuestic-ui'
|
||||
import axios from "axios";
|
||||
import { reactive } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useForm, useToast } from "vuestic-ui";
|
||||
|
||||
const { validate } = useForm('form')
|
||||
const { push } = useRouter()
|
||||
const { init } = useToast()
|
||||
const { validate } = useForm("form");
|
||||
const { push } = useRouter();
|
||||
const { init } = useToast();
|
||||
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
password: '',
|
||||
repeatPassword: '',
|
||||
})
|
||||
email: "",
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
if (validate()) {
|
||||
axios.post('http://localhost:3000/api/v0/signin', { "email": formData.email, "password": formData.password })
|
||||
.then(response => {
|
||||
axios
|
||||
.post("http://localhost:3000/api/v0/signin", {
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
})
|
||||
.then((response) => {
|
||||
// TODO: save token
|
||||
init({
|
||||
message: "Вы успешно вошли",
|
||||
color: 'success',
|
||||
})
|
||||
push({ name: 'dashboard' })
|
||||
.catch(error => { });
|
||||
color: "success",
|
||||
});
|
||||
push({ name: "dashboard" }).catch((error) => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const passwordRules: ((v: string) => boolean | string)[] = [
|
||||
(v) => !!v || 'Поле пароль обязательное',
|
||||
(v) => (v && v.length >= 5) || 'Пароль должен быть длинее 5-ти символов',
|
||||
]
|
||||
(v) => !!v || "Поле пароль обязательное",
|
||||
(v) => (v && v.length >= 5) || "Пароль должен быть длинее 5-ти символов",
|
||||
];
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MembeshipTier from './MembeshipTier.vue'
|
||||
import PaymentInfo from './PaymentInfo.vue'
|
||||
import { usePaymentCardsStore } from '../../stores/payment-cards'
|
||||
import Invoices from './Invoices.vue'
|
||||
import MembeshipTier from "./MembeshipTier.vue";
|
||||
import PaymentInfo from "./PaymentInfo.vue";
|
||||
import { usePaymentCardsStore } from "../../stores/payment-cards";
|
||||
import Invoices from "./Invoices.vue";
|
||||
|
||||
const cardStore = usePaymentCardsStore()
|
||||
cardStore.load()
|
||||
const cardStore = usePaymentCardsStore();
|
||||
cardStore.load();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -19,76 +19,93 @@
|
|||
</template>
|
||||
</VaCardContent>
|
||||
<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
|
||||
</VaButton>
|
||||
<VaButton v-else preset="primary" @click="numberOfInvoicesInVIew = minNumberOfInvoices">Show less </VaButton>
|
||||
<VaButton
|
||||
v-else
|
||||
preset="primary"
|
||||
@click="numberOfInvoicesInVIew = minNumberOfInvoices"
|
||||
>Show less
|
||||
</VaButton>
|
||||
</VaCardActions>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useToast } from 'vuestic-ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, ref } from "vue";
|
||||
import { useToast } from "vuestic-ui";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { init } = useToast()
|
||||
const { locale } = useI18n()
|
||||
const { init } = useToast();
|
||||
const { locale } = useI18n();
|
||||
|
||||
const minNumberOfInvoices = 7
|
||||
const maxNumberOfInvoices = 20
|
||||
const minNumberOfInvoices = 7;
|
||||
const maxNumberOfInvoices = 20;
|
||||
|
||||
const numberOfInvoicesInVIew = ref(minNumberOfInvoices)
|
||||
const numberOfInvoicesInVIew = ref(minNumberOfInvoices);
|
||||
|
||||
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 {
|
||||
const startDate = Date.parse(start)
|
||||
const endDate = Date.parse(end)
|
||||
const startDate = Date.parse(start);
|
||||
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 {
|
||||
const countryCodeToLanguageCodeMapping: Record<any, string> = {
|
||||
br: 'pt',
|
||||
cn: 'zh-CN',
|
||||
gb: 'en-GB',
|
||||
ir: 'fa',
|
||||
}
|
||||
br: "pt",
|
||||
cn: "zh-CN",
|
||||
gb: "en-GB",
|
||||
ir: "fa",
|
||||
};
|
||||
|
||||
return countryCodeToLanguageCodeMapping[locale.value] || 'en-GB'
|
||||
return countryCodeToLanguageCodeMapping[locale.value] || "en-GB";
|
||||
}
|
||||
|
||||
function getRandomDateString(): string {
|
||||
const startDate = '2020-01-01'
|
||||
const endDate = '2023-12-01'
|
||||
const startDate = "2020-01-01";
|
||||
const endDate = "2023-12-01";
|
||||
|
||||
const dateFormatOptions: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
}
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
};
|
||||
|
||||
return getRandomDateInBetween(startDate, endDate).toLocaleDateString(getLanguageCode(), dateFormatOptions)
|
||||
return getRandomDateInBetween(startDate, endDate).toLocaleDateString(
|
||||
getLanguageCode(),
|
||||
dateFormatOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const allItems = Array.from({ length: maxNumberOfInvoices }, (_, i) => ({
|
||||
id: i,
|
||||
date: getRandomDateString(),
|
||||
amount: `$${(Math.random() * 100).toFixed(2)}`,
|
||||
}))
|
||||
}));
|
||||
|
||||
const itemsInView = computed(() => {
|
||||
return allItems.slice(0, numberOfInvoicesInVIew.value)
|
||||
})
|
||||
return allItems.slice(0, numberOfInvoicesInVIew.value);
|
||||
});
|
||||
|
||||
const download = () => {
|
||||
init({
|
||||
message: "Request received. We'll email your invoice once we've completed data collection.",
|
||||
color: 'success',
|
||||
})
|
||||
}
|
||||
message:
|
||||
"Request received. We'll email your invoice once we've completed data collection.",
|
||||
color: "success",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,12 @@
|
|||
>
|
||||
<div class="flex items-center md:w-48">
|
||||
<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 class="md:w-48">
|
||||
<p class="mb-1">{{ plan.padletsTotal }} padlets</p>
|
||||
|
|
@ -24,9 +29,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="md:w-48 flex justify-end">
|
||||
<div v-if="plan.type === 'current'" class="font-bold">{{ plan.padletsUsed }} padlets used</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 v-if="plan.type === 'current'" class="font-bold">
|
||||
{{ plan.padletsUsed }} padlets used
|
||||
</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>
|
||||
|
||||
|
|
@ -37,69 +50,69 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vuestic-ui'
|
||||
import { reactive } from 'vue'
|
||||
import { useToast } from "vuestic-ui";
|
||||
import { reactive } from "vue";
|
||||
|
||||
const { init } = useToast()
|
||||
const { init } = useToast();
|
||||
|
||||
type MembershipTier = {
|
||||
id: string
|
||||
name: string
|
||||
type: 'upgrade' | 'downgrade' | 'current'
|
||||
padletsUsed: number
|
||||
padletsTotal: string
|
||||
priceMonth?: string
|
||||
priceYear?: string
|
||||
uploadLimit: string
|
||||
}
|
||||
id: string;
|
||||
name: string;
|
||||
type: "upgrade" | "downgrade" | "current";
|
||||
padletsUsed: number;
|
||||
padletsTotal: string;
|
||||
priceMonth?: string;
|
||||
priceYear?: string;
|
||||
uploadLimit: string;
|
||||
};
|
||||
|
||||
const plans = reactive<MembershipTier[]>([
|
||||
{
|
||||
id: '1',
|
||||
name: 'Platinum',
|
||||
type: 'upgrade',
|
||||
id: "1",
|
||||
name: "Platinum",
|
||||
type: "upgrade",
|
||||
padletsUsed: 0,
|
||||
padletsTotal: 'Unlimited',
|
||||
priceMonth: '$9.99',
|
||||
priceYear: '$99.99',
|
||||
uploadLimit: '500MB',
|
||||
padletsTotal: "Unlimited",
|
||||
priceMonth: "$9.99",
|
||||
priceYear: "$99.99",
|
||||
uploadLimit: "500MB",
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Gold',
|
||||
type: 'current',
|
||||
id: "2",
|
||||
name: "Gold",
|
||||
type: "current",
|
||||
padletsUsed: 19,
|
||||
padletsTotal: '20',
|
||||
priceMonth: '$6.99',
|
||||
priceYear: '$69.99',
|
||||
uploadLimit: '100MB',
|
||||
padletsTotal: "20",
|
||||
priceMonth: "$6.99",
|
||||
priceYear: "$69.99",
|
||||
uploadLimit: "100MB",
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Neon',
|
||||
type: 'downgrade',
|
||||
id: "3",
|
||||
name: "Neon",
|
||||
type: "downgrade",
|
||||
padletsUsed: 0,
|
||||
padletsTotal: '3',
|
||||
padletsTotal: "3",
|
||||
priceMonth: undefined,
|
||||
priceYear: undefined,
|
||||
uploadLimit: '20MB',
|
||||
uploadLimit: "20MB",
|
||||
},
|
||||
])
|
||||
]);
|
||||
|
||||
const switchPlan = (planId: string) => {
|
||||
plans.forEach((item, index) => {
|
||||
if (item.id === planId) {
|
||||
// Set the selected plan to 'current'
|
||||
item.type = 'current'
|
||||
item.type = "current";
|
||||
} else {
|
||||
// Determine if other plans are an 'upgrade' or 'downgrade'
|
||||
const selectedIndex = plans.findIndex((plan) => plan.id === planId)
|
||||
item.type = index < selectedIndex ? 'upgrade' : 'downgrade'
|
||||
const selectedIndex = plans.findIndex((plan) => plan.id === planId);
|
||||
item.type = index < selectedIndex ? "upgrade" : "downgrade";
|
||||
}
|
||||
})
|
||||
});
|
||||
init({
|
||||
message: "You've successfully changed the membership tier",
|
||||
color: 'success',
|
||||
})
|
||||
}
|
||||
color: "success",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -10,16 +10,18 @@
|
|||
<div class="md:w-48">
|
||||
<p class="mb-1">Payment plan</p>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:w-48 flex flex-col justify-end items-end">
|
||||
<VaButton preset="primary" @click="togglePaymentPlanModal">
|
||||
Switch to {{ paymentPlan.isYearly ? 'monthly' : 'annual' }}
|
||||
Switch to {{ paymentPlan.isYearly ? "monthly" : "annual" }}
|
||||
</VaButton>
|
||||
|
||||
<div v-if="!paymentPlan.isYearly" class="mt-2 text-regularSmall">
|
||||
|
|
@ -38,11 +40,16 @@
|
|||
>
|
||||
<div class="md:w-48">
|
||||
<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 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>
|
||||
</template>
|
||||
|
|
@ -58,35 +65,36 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { usePaymentCardsStore } from '../../stores/payment-cards'
|
||||
import { computed, ref } from "vue";
|
||||
import { usePaymentCardsStore } from "../../stores/payment-cards";
|
||||
|
||||
import ChangeYourPaymentPlan from './modals/ChangeYourPaymentPlan.vue'
|
||||
import ChangeYourPaymentPlan from "./modals/ChangeYourPaymentPlan.vue";
|
||||
|
||||
const paymentPlan = ref({
|
||||
id: '1',
|
||||
name: 'Gold',
|
||||
id: "1",
|
||||
name: "Gold",
|
||||
isYearly: false,
|
||||
type: 'current',
|
||||
type: "current",
|
||||
padletsUsed: 19,
|
||||
padletsTotal: '20',
|
||||
priceMonth: '$6.99',
|
||||
priceYear: '$69.99',
|
||||
switchToYearlySave: '16%',
|
||||
uploadLimit: '100MB',
|
||||
})
|
||||
padletsTotal: "20",
|
||||
priceMonth: "$6.99",
|
||||
priceYear: "$69.99",
|
||||
switchToYearlySave: "16%",
|
||||
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 = () => {
|
||||
isChangeYourPaymentPlanModalOpen.value = !isChangeYourPaymentPlanModalOpen.value
|
||||
}
|
||||
isChangeYourPaymentPlanModalOpen.value =
|
||||
!isChangeYourPaymentPlanModalOpen.value;
|
||||
};
|
||||
|
||||
const updatePaymentPlan = () => {
|
||||
paymentPlan.value.isYearly = !paymentPlan.value.isYearly
|
||||
isChangeYourPaymentPlanModalOpen.value = false
|
||||
}
|
||||
paymentPlan.value.isYearly = !paymentPlan.value.isYearly;
|
||||
isChangeYourPaymentPlanModalOpen.value = false;
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -11,34 +11,45 @@
|
|||
<div class="space-y-6">
|
||||
<h3>
|
||||
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?
|
||||
</h3>
|
||||
<div class="flex flex-col-reverse md:justify-end md:flex-row md:space-x-4">
|
||||
<VaButton preset="secondary" color="secondary" @click="emits('cancel')"> Cancel</VaButton>
|
||||
<VaButton class="mb-4 md:mb-0" @click="confirm()"> Update Plan</VaButton>
|
||||
<div
|
||||
class="flex flex-col-reverse md:justify-end md:flex-row md:space-x-4"
|
||||
>
|
||||
<VaButton preset="secondary" color="secondary" @click="emits('cancel')">
|
||||
Cancel</VaButton
|
||||
>
|
||||
<VaButton class="mb-4 md:mb-0" @click="confirm()">
|
||||
Update Plan</VaButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</VaModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vuestic-ui'
|
||||
import { useToast } from "vuestic-ui";
|
||||
|
||||
const { init } = useToast()
|
||||
const { init } = useToast();
|
||||
|
||||
defineProps({
|
||||
yearlyPlan: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const emits = defineEmits(['cancel', 'confirm'])
|
||||
const emits = defineEmits(["cancel", "confirm"]);
|
||||
|
||||
const confirm = () => {
|
||||
init({ message: "You've successfully changed your payment plan", color: 'success' })
|
||||
emits('confirm')
|
||||
}
|
||||
init({
|
||||
message: "You've successfully changed your payment plan",
|
||||
color: "success",
|
||||
});
|
||||
emits("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import Categories from './widgets/Categories.vue'
|
||||
import Questions from './widgets/Questions.vue'
|
||||
import RequestDemo from './widgets/RequestDemo.vue'
|
||||
import Navigation from './widgets/Navigation.vue'
|
||||
import Categories from "./widgets/Categories.vue";
|
||||
import Questions from "./widgets/Questions.vue";
|
||||
import RequestDemo from "./widgets/RequestDemo.vue";
|
||||
import Navigation from "./widgets/Navigation.vue";
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -4,35 +4,51 @@
|
|||
<VaIcon color="secondary" name="mso-search" />
|
||||
</template>
|
||||
</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">
|
||||
<VaCard class="col-span-3 md:col-span-1 min-h-[146px]" href="#">
|
||||
<VaCardContent class="leading-5 text-sm">
|
||||
<VaIcon :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>
|
||||
<VaIcon
|
||||
: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>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</template>
|
||||
</section>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import categories from '../data/popularCategories.json'
|
||||
import { ref, computed } from 'vue'
|
||||
import categories from "../data/popularCategories.json";
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const searchValue = ref('')
|
||||
const searchValue = ref("");
|
||||
|
||||
const filteredCategories = computed(() => {
|
||||
const value = searchValue.value.trim().toLowerCase()
|
||||
const value = searchValue.value.trim().toLowerCase();
|
||||
if (value.length === 0) {
|
||||
return categories
|
||||
return categories;
|
||||
}
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@
|
|||
<div v-for="section in navSections" :key="section" class="mb-6 md:mb-0">
|
||||
<h3 class="h5 mb-4">{{ section }}</h3>
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -14,10 +18,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import navigation from '../data/navigationLinks.json'
|
||||
import { computed } from "vue";
|
||||
import navigation from "../data/navigationLinks.json";
|
||||
|
||||
const navSections = computed(() => {
|
||||
return Object.keys(navigation)
|
||||
})
|
||||
return Object.keys(navigation);
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,22 +2,31 @@
|
|||
<VaCard class="mb-4">
|
||||
<VaCardContent>
|
||||
<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?">
|
||||
<article class="max-w-3xl leading-5">
|
||||
<p class="mb-2">
|
||||
Get ready for some page-refreshing wisdom! We're about to dive into the magical world of reloading web
|
||||
pages. Here are some techniques:
|
||||
Get ready for some page-refreshing wisdom! We're about to dive
|
||||
into the magical world of reloading web pages. Here are some
|
||||
techniques:
|
||||
</p>
|
||||
<ul class="list-disc list-inside leading-5 ml-4">
|
||||
<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>
|
||||
Click the reload button in your browser. It usually looks like a circular arrow and is typically located
|
||||
in the address bar.
|
||||
Click the reload button in your browser. It usually looks like a
|
||||
circular arrow and is typically located in the address bar.
|
||||
</li>
|
||||
<li>
|
||||
Use JavaScript to reload the page programmatically:
|
||||
location.reload();
|
||||
</li>
|
||||
<li>Use JavaScript to reload the page programmatically: location.reload();</li>
|
||||
</ul>
|
||||
</article>
|
||||
</VaCollapse>
|
||||
|
|
@ -25,16 +34,19 @@
|
|||
<VaCollapse header="What is a Secure Key?">
|
||||
<article class="max-w-3xl text-sm">
|
||||
<p class="mb-4">
|
||||
A Secure Key is an extra layer of security that helps look after you and your money by generating a
|
||||
unique, single use security code every time you log on or authorise a payment. There are two types of
|
||||
Secure Key - a Digital version that works as part of the 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.
|
||||
A Secure Key is an extra layer of security that helps look after
|
||||
you and your money by generating a unique, single use security
|
||||
code every time you log on or authorise a payment. There are two
|
||||
types of Secure Key - a Digital version that works as part of the
|
||||
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 class="mb-4">
|
||||
We recommend setting up a Digital Secure Key. It’s built into the first direct Banking App, and if you
|
||||
have the right type of smartphone you can use your fingerprint or face recognition to seamlessly log on or
|
||||
authorise payments.
|
||||
We recommend setting up a Digital Secure Key. It’s built into the
|
||||
first direct Banking App, and if you have the right type of
|
||||
smartphone you can use your fingerprint or face recognition to
|
||||
seamlessly log on or authorise payments.
|
||||
</p>
|
||||
<a class="va-link font-semibold" href="#"
|
||||
>Find out more about Secure Keys
|
||||
|
|
@ -46,68 +58,87 @@
|
|||
<VaCollapse header="How do I report fraud?">
|
||||
<article class="max-w-3xl text-sm">
|
||||
<p class="mb-3">
|
||||
Reporting fraud is a serious matter, and it's important to take appropriate steps to address and prevent
|
||||
fraudulent activities. The specific process for reporting fraud may vary based on your location and the
|
||||
nature of the fraud, but here's a general guide:
|
||||
Reporting fraud is a serious matter, and it's important to take
|
||||
appropriate steps to address and prevent fraudulent activities.
|
||||
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>
|
||||
<ul class="list-disc list-inside leading-6 ml-4 mb-4">
|
||||
<li>
|
||||
<span class="font-semibold">Report to Relevant Authorities:</span> 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.
|
||||
<span class="font-semibold"
|
||||
>Report to Relevant Authorities:</span
|
||||
>
|
||||
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>
|
||||
<span class="font-semibold">Document All Details:</span> Make sure to document all the relevant details
|
||||
about the fraud, including dates, times, names of individuals involved (if known), financial
|
||||
transactions, and any communication related to the fraud.
|
||||
<span class="font-semibold">Document All Details:</span> Make
|
||||
sure to document all the relevant details about the fraud,
|
||||
including dates, times, names of individuals involved (if
|
||||
known), financial transactions, and any communication related to
|
||||
the fraud.
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-semibold">Report to Internet Crime Organizations:</span> 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.
|
||||
<span class="font-semibold"
|
||||
>Report to Internet Crime Organizations:</span
|
||||
>
|
||||
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>
|
||||
</ul>
|
||||
<p>
|
||||
Remember to act promptly and follow the specific reporting procedures relevant to your location and the
|
||||
type of fraud you've encountered. Taking swift action can help mitigate potential damage and prevent
|
||||
further fraudulent activities.
|
||||
Remember to act promptly and follow the specific reporting
|
||||
procedures relevant to your location and the type of fraud you've
|
||||
encountered. Taking swift action can help mitigate potential
|
||||
damage and prevent further fraudulent activities.
|
||||
</p>
|
||||
</article>
|
||||
</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">
|
||||
<p class="mb-3">
|
||||
Downloading statements can vary depending on the type of statements you're referring to (e.g., bank
|
||||
statements, credit card statements, utility statements). Below are general steps to download statements
|
||||
from various platforms:
|
||||
Downloading statements can vary depending on the type of
|
||||
statements you're referring to (e.g., bank statements, credit card
|
||||
statements, utility statements). Below are general steps to
|
||||
download statements from various platforms:
|
||||
</p>
|
||||
<ul class="list-disc list-inside leading-6 ml-4 mb-4">
|
||||
<li>
|
||||
<span class="font-semibold">Ensure Secure Connection:</span> Always access and download statements from
|
||||
a secure and trusted network to protect your sensitive financial information.
|
||||
<span class="font-semibold">Ensure Secure Connection:</span>
|
||||
Always access and download statements from a secure and trusted
|
||||
network to protect your sensitive financial information.
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-semibold">Regularly Check Statements:</span> Review your statements regularly to
|
||||
verify transactions, detect errors, or identify any suspicious activity promptly.
|
||||
<span class="font-semibold">Regularly Check Statements:</span>
|
||||
Review your statements regularly to verify transactions, detect
|
||||
errors, or identify any suspicious activity promptly.
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-semibold">Verify Account Details:</span> Ensure that the statements you download
|
||||
correspond to the correct account, including account numbers and account names.
|
||||
<span class="font-semibold">Verify Account Details:</span>
|
||||
Ensure that the statements you download correspond to the
|
||||
correct account, including account numbers and account names.
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-semibold">Use Official Channels:</span> Download statements only from official and
|
||||
authorized platforms, such as your bank's official website or the secure online portals of service
|
||||
providers.
|
||||
<span class="font-semibold">Use Official Channels:</span>
|
||||
Download statements only from official and authorized platforms,
|
||||
such as your bank's official website or the secure online
|
||||
portals of service providers.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Always ensure that you follow the specific steps and instructions provided by the respective service
|
||||
provider to download statements securely and accurately. If you encounter any difficulties or have
|
||||
specific questions, consider reaching out to the customer support of the respective institution or
|
||||
service.
|
||||
Always ensure that you follow the specific steps and instructions
|
||||
provided by the respective service provider to download statements
|
||||
securely and accurately. If you encounter any difficulties or have
|
||||
specific questions, consider reaching out to the customer support
|
||||
of the respective institution or service.
|
||||
</p>
|
||||
</article>
|
||||
</VaCollapse>
|
||||
|
|
@ -117,7 +148,7 @@
|
|||
</template>
|
||||
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
<div>
|
||||
<VaCardContent>
|
||||
<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>
|
||||
<VaCardActions align="left">
|
||||
<VaButton @click="showModal = !showModal">Request a demo</VaButton>
|
||||
|
|
@ -11,15 +13,25 @@
|
|||
</div>
|
||||
<img alt="Send a message" src="../request-demo.svg" />
|
||||
</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">
|
||||
<h3 class="va-h3">Request free demo</h3>
|
||||
<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>
|
||||
<VaInput
|
||||
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"
|
||||
label="Email"
|
||||
type="email"
|
||||
|
|
@ -29,24 +41,24 @@
|
|||
</template>
|
||||
|
||||
<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 email = ref('')
|
||||
const { validate } = useForm('form')
|
||||
const { init } = useToast()
|
||||
const showModal = ref(false);
|
||||
const email = ref("");
|
||||
const { validate } = useForm("form");
|
||||
const { init } = useToast();
|
||||
|
||||
const submit = async () => {
|
||||
if (!validate()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
init({
|
||||
title: 'Demo Request Submitted!',
|
||||
message: 'An expert will get in touch soon',
|
||||
color: 'success',
|
||||
})
|
||||
showModal.value = false
|
||||
}
|
||||
title: "Demo Request Submitted!",
|
||||
message: "An expert will get in touch soon",
|
||||
color: "success",
|
||||
});
|
||||
showModal.value = false;
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,6 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PaymentCardList from './widgets/my-cards/PaymentCardList.vue'
|
||||
import BillingAddressList from './widgets/billing-address/BillingAddressList.vue'
|
||||
import PaymentCardList from "./widgets/my-cards/PaymentCardList.vue";
|
||||
import BillingAddressList from "./widgets/billing-address/BillingAddressList.vue";
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import PaymentSystem from './PaymentSystem.vue'
|
||||
import PaymentSystem from "./PaymentSystem.vue";
|
||||
|
||||
export default {
|
||||
title: 'PaymentSystem',
|
||||
title: "PaymentSystem",
|
||||
component: PaymentSystem,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
components: { PaymentSystem },
|
||||
|
|
@ -13,4 +13,4 @@ export const Default = () => ({
|
|||
<br>
|
||||
<PaymentSystem type="mastercard"/>
|
||||
`,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
<template>
|
||||
<div 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" />
|
||||
<div
|
||||
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" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PaymentSystemType } from '../types'
|
||||
import { PaymentSystemType } from "../types";
|
||||
|
||||
const props = defineProps<{
|
||||
type: PaymentSystemType
|
||||
}>()
|
||||
type: PaymentSystemType;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
export enum PaymentSystemType {
|
||||
Visa = 'visa',
|
||||
MasterCard = 'mastercard',
|
||||
Visa = "visa",
|
||||
MasterCard = "mastercard",
|
||||
}
|
||||
|
||||
export const paymentSystemTypeOptions = Object.values(PaymentSystemType)
|
||||
export const paymentSystemTypeOptions = Object.values(PaymentSystemType);
|
||||
|
||||
export interface PaymentCard {
|
||||
id: string
|
||||
name: string
|
||||
isPrimary: boolean // show Primary badge
|
||||
paymentSystem: PaymentSystemType // Enum or union type for various payment systems
|
||||
cardNumberMasked: string // ****1679
|
||||
expirationDate: string // 09/24
|
||||
id: string;
|
||||
name: string;
|
||||
isPrimary: boolean; // show Primary badge
|
||||
paymentSystem: PaymentSystemType; // Enum or union type for various payment systems
|
||||
cardNumberMasked: string; // ****1679
|
||||
expirationDate: string; // 09/24
|
||||
}
|
||||
|
||||
export interface BillingAddress {
|
||||
id: string
|
||||
name: string
|
||||
isPrimary: boolean // show Primary badge
|
||||
street: string
|
||||
city: string
|
||||
state: string
|
||||
postalCode: string
|
||||
country: string
|
||||
id: string;
|
||||
name: string;
|
||||
isPrimary: boolean; // show Primary badge
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
postalCode: string;
|
||||
country: string;
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue