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"/>`,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,49 +3,105 @@
|
|||
<!-- 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"/>
|
||||
<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%"/>
|
||||
<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>
|
||||
<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>
|
||||
</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,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@
|
|||
"auth": "Auth",
|
||||
"buttons": "Buttons",
|
||||
"timelines": "Timelines",
|
||||
"dashboard": "Dashboard",
|
||||
"dashboard": "Статистика",
|
||||
"billing": "Billing",
|
||||
"login": "Login",
|
||||
"preferences": "Account preferences",
|
||||
"preferences": "Настройки Аккаунта",
|
||||
"payments": "Payments",
|
||||
"settings": "Application settings",
|
||||
"pricing-plans": "Pricing plans",
|
||||
|
|
@ -75,14 +75,14 @@
|
|||
},
|
||||
"user": {
|
||||
"language": "Change language",
|
||||
"logout": "Logout",
|
||||
"profile": "Profile",
|
||||
"settings": "Settings",
|
||||
"billing": "Billing",
|
||||
"logout": "Выход",
|
||||
"profile": "Профиль",
|
||||
"settings": "Настройки",
|
||||
"billing": "Платежы",
|
||||
"faq": "FAQ",
|
||||
"helpAndSupport": "Help & support",
|
||||
"helpAndSupport": "Помощь",
|
||||
"projects": "Projects",
|
||||
"account": "Account",
|
||||
"account": "Аккаунт",
|
||||
"explore": "Explore"
|
||||
},
|
||||
"treeView": {
|
||||
|
|
@ -174,5 +174,4 @@
|
|||
"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="[
|
||||
<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">
|
||||
]"
|
||||
: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