init
|
|
@ -0,0 +1,130 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Epicmax LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<p align="center">
|
||||
<a href="https://vuestic.dev" target="_blank">
|
||||
<img alt="Vuestic UI Logo" width="220" src="./.github/assets/vuestic-admin-logo.png">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
Free and beautiful Admin Template utilizing Vue 3, Vite, Pinia, and Tailwind CSS. Designed for building efficient, responsive, and fast-loading admin interfaces.</br>
|
||||
Developed by <a href="https://epicmax.co">Epicmax</a>.</br>
|
||||
Based on <a href="https://ui.vuestic.dev">Vuestic UI</a> library.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://admin-demo.vuestic.dev"> Live Demo </a> |
|
||||
<a href="https://admin.vuestic.dev/"> About Vuestic Admin </a> |
|
||||
<a href="https://ui.vuestic.dev/">Vuestic UI documentation</a>
|
||||
</p>
|
||||
|
||||
> Vuestic Admin is built with [Vuestic UI](https://ui.vuestic.dev). See our
|
||||
> <a href="https://github.com/epicmaxco/vuestic-ui/issues">issues</a>,
|
||||
> <a href="https://ui.vuestic.dev/en/contribution/guide">contributing guide</a> and join discussions on our
|
||||
> <a href="https://discord.gg/jTKTjj2weV">Discord server</a> to help us improve Vuestic Admin & Vuestic UI experience.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://admin.vuestic.dev" target="_blank">
|
||||
<img src="./public/vuestic-admin-image.png" align="center" width="888px"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### Quick start
|
||||
|
||||
Use following command to quickly scaffold new [Vuestic Admin](https://admin-demo.vuestic.dev) or empty Vite or Nuxt project with [Vuestic UI](https://ui.vuestic.dev).
|
||||
|
||||
```bash
|
||||
npm create vuestic@latest
|
||||
```
|
||||
|
||||
After [Vuestic Admin](https://admin.vuestic.dev) is installed, run `npm install` to install dependcies, then run `npm run dev` to start local development server.
|
||||
|
||||
### Documentation
|
||||
|
||||
Documentation, guides, examples and tutorials are available on [ui.vuestic.dev](https://ui.vuestic.dev)
|
||||
|
||||
### Official Discord Server
|
||||
|
||||
Ask questions at the official community [discord server](https://discord.gg/jTKTjj2weV)
|
||||
|
||||
### Features
|
||||
|
||||
- **Vue 3, Vite, Pinia, and Tailwind CSS -** Fast and efficient development
|
||||
- **Dark Theme -** Modern and eye-catching
|
||||
- **Global Configuration -** Effortless customization
|
||||
- **Accessibility -** Inclusive and user-friendly
|
||||
- **i18n Integration -** Easy localization for global reach
|
||||
- **Educational Resource -** Ideal for learning and improving skills
|
||||
- **Responsive Design -** Adapts seamlessly to all devices
|
||||
- **Professional Support -** Reliable help from the experts
|
||||
- **Highly Customizable -** Tailor to your project’s style
|
||||
|
||||
### Contributing
|
||||
|
||||
Thanks for all your wonderful PRs, issues and ideas.
|
||||
|
||||
<a href="https://github.com/epicmaxco/vuestic-admin/graphs/contributors">
|
||||
<img src="https://opencollective.com/vuestic-admin/contributors.svg?width=890&button=false" />
|
||||
</a>
|
||||
<br>
|
||||
|
||||
You’re always welcome to join: check out
|
||||
our <a href="https://ui.vuestic.dev/en/contribution/guide">
|
||||
contribution guides</a>
|
||||
, [open issues](https://github.com/epicmaxco/vuestic-ui/issues)
|
||||
and [Discord server](https://discord.gg/jTKTjj2weV)
|
||||
|
||||
### Partners & Sponsors ❤️
|
||||
|
||||
<img src="./.github/assets/sponsors.png" loading="lazy" alt="Epicmax, vuejobs, ag-grid, flatlogic, browserstack and jetbrains" width="400px">
|
||||
|
||||
Become a partner: [hello@epicmax.co](mailto:hello@epicmax.co)
|
||||
|
||||
### Can I hire you guys?
|
||||
|
||||
[Epicmax](https://epicmax.co) is committed to Open Source from its beginning. Vuestic Admin was created and backed by Epicmax, and is supported through all the years.
|
||||
|
||||
With 6+ years of dedicated work on both commercial and open-source projects, and more than 47 clients worldwide across various fields, Epicmax has deep expertise in frontend development, especially in Vue.js. We regularly conduct code audits for our projects and now excited to offer this service not only to our existing clients but to anyone looking to understand the state of their frontend code and ensure it's secure and up-to-date!
|
||||
|
||||
You can request a consultation or order web development services by Epicmax via this [form](https://epicmax.co/contacts) 😎
|
||||
|
||||
Say hi: <a href="mailto:hello@epicmax.co">hello@epicmax.co</a>. We will be happy to work with you!
|
||||
|
||||
[Other work](https://epicmax.co) we’ve done 🤘
|
||||
|
||||
[Meet the Team](https://ui.vuestic.dev/introduction/team)
|
||||
|
||||
### Awards
|
||||
|
||||
<a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">
|
||||
<img src="https://i.imgur.com/ZeQPZ3Q.png" align="center" width="150px"/>
|
||||
</a>
|
||||
<p>
|
||||
By <a href="https://flatlogic.com/templates/vuestic-vue-free-admin" target="_blank">@flatlogic</a> marketplace
|
||||
</p>
|
||||
|
||||
### Follow us
|
||||
|
||||
Stay up to date with the latest Vuestic news! Follow us
|
||||
on [Twitter](https://twitter.com/vuestic_ui)
|
||||
or [Linkedin](https://www.linkedin.com/company/18509340)
|
||||
|
||||
### License
|
||||
|
||||
[MIT](https://github.com/epicmaxco/vuestic-admin/blob/master/LICENSE) license.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<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
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,300..600,0,0"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|
||||
<title>Vuestic Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"name": "vuestic-admin",
|
||||
"private": true,
|
||||
"version": "3.1.0",
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"dev": "vite",
|
||||
"build": "npm run lint && vue-tsc --noEmit && vite build",
|
||||
"build:ci": "vite build",
|
||||
"start:ci": "serve -s ./dist",
|
||||
"prelint": "npm run format",
|
||||
"lint": "eslint \"./src/**/*.{ts,js,vue}\" --fix",
|
||||
"format": "prettier --write .",
|
||||
"preview": "vite preview",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./src/**/*.{ts,js,vue}": [
|
||||
"npm run lint"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@gtm-support/vue-gtm": "^2.0.0",
|
||||
"@vuestic/tailwind": "^0.1.3",
|
||||
"@vueuse/core": "^10.6.1",
|
||||
"axios": "^1.6.8",
|
||||
"chart.js": "^4.4.1",
|
||||
"chartjs-chart-geo": "^4.2.8",
|
||||
"epic-spinners": "^2.0.0",
|
||||
"flag-icons": "^6.15.0",
|
||||
"ionicons": "^4.6.3",
|
||||
"medium-editor": "^5.23.3",
|
||||
"pinia": "^2.1.7",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"sass": "^1.69.5",
|
||||
"serve": "^14.2.1",
|
||||
"vue": "3.3.9",
|
||||
"vue-chartjs": "^5.3.0",
|
||||
"vue-i18n": "^9.6.2",
|
||||
"vue-router": "^4.2.5",
|
||||
"vuestic-ui": "^1.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^1.5.0",
|
||||
"@storybook/addon-essentials": "^7.4.6",
|
||||
"@storybook/addon-interactions": "^7.4.6",
|
||||
"@storybook/addon-links": "^7.4.6",
|
||||
"@storybook/blocks": "^7.4.6",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@storybook/vue3": "^7.4.6",
|
||||
"@storybook/vue3-vite": "^7.4.6",
|
||||
"@types/medium-editor": "^5.0.5",
|
||||
"@types/node": "^20.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.13.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"eslint-plugin-vue": "^9.18.1",
|
||||
"husky": "^8.0.1",
|
||||
"lint-staged": "^15.1.0",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^3.1.0",
|
||||
"storybook": "^7.4.6",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-eslint-parser": "^9.3.2",
|
||||
"vue-tsc": "^1.8.22"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 443 B |
|
After Width: | Height: | Size: 897 B |
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
After Width: | Height: | Size: 343 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'scss/main.scss';
|
||||
|
||||
#app {
|
||||
font-family: 'Inter', Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 20rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<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"
|
||||
fill="var(--va-danger)"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M140.87 63.63a82.26 82.26 0 0 1 6.96 4.97l1.2-1.48a84.18 84.18 0 0 0-7.13-5.1c-2.88-1.83-6.02-3.6-8.41-4.36l-.58 1.82c2.13.67 5.1 2.32 7.96 4.15ZM94.05 50.65c1.9 1.73 3.85 3.5 5.14 4.79l1.35-1.35a165.6 165.6 0 0 0-5.49-5.1c-1.81-1.65-3.51-3.19-4.51-4.19l-1.35 1.35c1.04 1.03 2.77 2.6 4.56 4.23l.3.27ZM116 88.33l.15.28c.65 1.2 1.19 2.47 1.72 3.78l.16.38c.48 1.18.97 2.38 1.56 3.55.12.24.25.5.4.7.14.22.33.44.58.6.59.38 1.23.28 1.85-.03a20.32 20.32 0 0 0 3.98-2.82c1.14-.96 2.22-1.86 3.45-2.52a.72.72 0 0 0-.68-1.26c-1.36.74-2.54 1.72-3.65 2.65l-.15.13c-1.18.98-2.3 1.9-3.59 2.54-.2.1-.32.12-.37.13-.04 0-.05 0-.06-.02a.72.72 0 0 1-.18-.2c-.1-.13-.19-.3-.3-.54a50.44 50.44 0 0 1-1.67-3.83 39.3 39.3 0 0 0-1.8-3.93l-.14-.25c-.37-.69-.92-1.72-1.6-2.43-.39-.42-.9-.8-1.51-.93a2.25 2.25 0 0 0-1.92.59c-.62.52-1.22 1.09-1.8 1.64l-.66.62c-.8.75-1.6 1.44-2.46 2-1.4.9-2.66.83-3.86.3-1.26-.57-2.45-1.66-3.62-2.83l-.05-.05c-.44-.44-.84-.84-1.22-1.16-.38-.32-.8-.6-1.27-.73-1.03-.29-1.96.24-2.99 1.15-.15.13-.3.32-.45.5l-.5.67-.26.38-.9 1.24c-.42.55-.81 1.02-1.14 1.32-.15.13-.25.2-.32.24A18.4 18.4 0 0 1 86.39 87l-.17-.17a6.66 6.66 0 0 0-.82-.75c-.27-.2-.7-.46-1.2-.4-.6.08-.95.53-1.14 1.02a234.7 234.7 0 0 0-1.76 5.34l-.02.07v.13a.72.72 0 0 0 1.41.11 7.87 7.87 0 0 1 .2-.65 230.75 230.75 0 0 1 1.53-4.56l.14.1a5.35 5.35 0 0 1 .76.72 19.49 19.49 0 0 0 4.9 3.57c.38.19.77.1 1.04-.01.27-.12.52-.3.74-.5.44-.4.9-.96 1.33-1.52a112.24 112.24 0 0 1 1.65-2.28c.14-.18.23-.28.27-.31.99-.88 1.4-.92 1.66-.84.18.05.4.17.74.45.32.27.68.64 1.16 1.1v.02c1.17 1.17 2.53 2.43 4.06 3.12a5.2 5.2 0 0 0 5.2-.4c.99-.63 1.86-1.4 2.67-2.15l.7-.66c.57-.55 1.13-1.07 1.7-1.56.35-.29.58-.3.73-.27.2.04.46.18.76.5.53.56.99 1.4 1.37 2.11Zm-31.53-1.27v.01ZM82 91.5Z"
|
||||
fill="var(--va-danger)"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M128.72 17.58c10.44 3.5 21.51 8.9 29.59 16.56 8.1 7.69 14.21 17.09 17.58 26.53 3.38 9.44 3.99 18.86 1.2 26.64-1.36 3.8-4.73 7.68-9.2 11.44-4.47 3.74-9.94 7.28-15.4 10.42a234.47 234.47 0 0 1-15.23 7.92c-1.98.95-3.75 1.77-5.2 2.44-1.69.8-2.94 1.38-3.55 1.7-2.26 1.22-4.55 2.82-6.13 5.04a11.17 11.17 0 0 0-1.82 8.65c.45 2.74 2.96 12.33 5.88 23.12 2.93 10.82 6.3 22.94 8.51 30.79l1.55-.44a3483.9 3483.9 0 0 1-10.24-37.22c3.95.12 12.67.68 24.49 2.3 8.19 1.12 15.55 5.5 21.35 10.1a96.82 96.82 0 0 1 9.54 8.8c-1.56 2.16-2.97 5.26-3.96 9.6l1.56.35c.92-4.05 2.19-6.86 3.52-8.78l1.12 1.13c.92.89 1.69 1.5 2.34 1.82.66.33 1.37.46 2 .1.57-.33.84-.94 1-1.43.17-.53.28-1.15.38-1.73l-1.58-.28c-.1.61-.2 1.12-.33 1.52-.13.42-.25.52-.27.53h-.06c-.07 0-.2-.03-.42-.14a8.85 8.85 0 0 1-1.94-1.54c-.33-.32-.74-.74-1.24-1.26 2.27-2.52 4.56-2.84 5.66-2.6l.34-1.57c-1.82-.4-4.57.22-7.1 3.02a98.22 98.22 0 0 0-9.57-8.8c-5.9-4.68-13.53-9.26-22.13-10.44-12.34-1.69-21.34-2.23-25.13-2.31-1.92-7.24-3.37-12.95-3.7-14.9a9.57 9.57 0 0 1 1.56-7.46c1.37-1.93 3.41-3.39 5.58-4.56.58-.3 1.75-.85 3.38-1.6v-.01c1.44-.67 3.24-1.5 5.3-2.5 4.36-2.08 9.82-4.81 15.33-7.97 5.5-3.16 11.07-6.76 15.63-10.58 4.55-3.82 8.18-7.93 9.69-12.13 2.95-8.23 2.26-18.05-1.2-27.72-3.46-9.7-9.71-19.3-18-27.15-8.3-7.88-19.61-13.39-30.17-16.92-10.55-3.52-20.44-5.11-25.98-5.1-11.24 0-18.88 8.25-20.84 10.89-.4.54-.98 1.23-1.68 2.07l-.3.35c-.83.99-1.78 2.14-2.73 3.4-1.9 2.5-3.82 5.51-4.71 8.66-.88 3.08-2.13 12.09-3.16 20.86-1.03 8.8-1.86 17.52-1.86 20.08 0 2.07-.08 5.83-.16 9.64a5654.21 5654.21 0 0 0-.16 8.35 45.88 45.88 0 0 0 .07 3.72V99c.1.98.3 2.93.67 5.07.42 2.4 1.1 5.16 2.17 7.06a7.85 7.85 0 0 0 1.49 1.7l.06.05c.5.49 1.05 1.02 1.61 1.67a13.02 13.02 0 0 1 3.1 6.26c.27 1.4.22 3.4-.06 5.69a68.08 68.08 0 0 1-1.34 7.17 74.77 74.77 0 0 1-2.99 10c-.15.3-.5 1.15-.98 2.4-.55-.32-1.2-.82-1.9-1.5a33.24 33.24 0 0 1-3.3-3.81 117.54 117.54 0 0 1-7.13-10.88 313.14 313.14 0 0 1-7.44-13.39l-.71-1.32a35.6 35.6 0 0 0-1.1-1.97c-2.35-3.53-9.53-5.24-21.71-8.09.75-.82.72-.91 1.65-1.9 2.44-2.02 6.48.36 6.96.84l.48-.95c-2.86-2.39-6.99-2.3-8.34-.96-2.14 2.15-.74 1-3.1 2.97-.7.6-1.89 1.5-2.45 1.85-.55.34-.95.5-1.21.53h-.04a9.43 9.43 0 0 1-2.81-1.94l-1.1 1c.46.46 1 .93 1.62 1.41.44.34.87.62 1.25.82.19.1.38.18.57.23.17.06.4.1.64.08a4.9 4.9 0 0 0 1.91-.76 18.02 18.02 0 0 0 2.75-2.1c10.54 2.34 19.25 4.34 21.6 7.86a36.64 36.64 0 0 1 1.3 2.37l.42.78 1.35 2.52c1.65 3.06 3.8 7 6.13 10.93 2.33 3.94 4.85 7.91 7.23 11.04a34.86 34.86 0 0 0 3.47 4c.81.78 1.63 1.42 2.43 1.84a531.4 531.4 0 0 0-4.68 12.65c-1.4 3.97-2.8 8.05-3.86 11.48a90.14 90.14 0 0 0-1.3 4.6c-.32 1.29-.54 2.38-.58 3.15l1.6.09c.04-.6.22-1.57.54-2.86.31-1.27.75-2.8 1.28-4.5 1.05-3.4 2.43-7.47 3.83-11.43 2.82-7.94 5.7-15.4 6.18-16.4.53-1.12 1.96-5.5 3.1-10.32a69.6 69.6 0 0 0 1.37-7.34c.28-2.33.36-4.53.04-6.19-1.41-4.31-1.86-5.26-5.36-8.96a6.58 6.58 0 0 1-1.08-1.2c-.93-1.64-1.56-4.16-1.98-6.55a67.1 67.1 0 0 1-.66-4.95l-.03-.35c-.04-.35-.05-1.5-.03-3.21a691.73 691.73 0 0 1 .16-8.33c.08-3.8.16-7.59.16-9.68 0-2.44.81-11.07 1.85-19.89 1.03-8.83 2.27-17.68 3.1-20.61.82-2.86 2.6-5.68 4.46-8.14a80.8 80.8 0 0 1 2.68-3.33l.3-.35a48.6 48.6 0 0 0 1.74-2.15c1.85-2.5 9.08-10.23 19.55-10.24 5.32-.01 15.03 1.53 25.47 5.02Z"
|
||||
fill="var(--va-danger)"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M24.85 101.92c.46 1.39 1.12 2.63 1.6 3.36l1.6-1.03a13.9 13.9 0 0 1-1.4-2.94 6.75 6.75 0 0 1-.38-3.48l-1.86-.43a8.59 8.59 0 0 0 .44 4.52Z"
|
||||
fill="var(--va-danger)"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import VuesticLogo from './VuesticLogo.vue'
|
||||
|
||||
export default {
|
||||
title: 'VuesticLogo',
|
||||
component: VuesticLogo,
|
||||
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"/>`,
|
||||
})
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<svg width="231" height="26" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Created with SVG Editor - http://github.com/mzalive/SVG Editor/ -->
|
||||
<defs>
|
||||
<filter height="200%" width="200%" y="-50%" x="-50%" id="svg_8_blur">
|
||||
<feGaussianBlur stdDeviation="0.6" in="SourceGraphic"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="28" width="233" y="-1" x="-1"/>
|
||||
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
|
||||
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<path d="m280.5,281.4375c0,0 -2.054138,0.567322 -5,0c-5.287994,-1.018372 -7,-2 -8,-3c-1,-1 -1.289795,-3.042908 -1,-4c1.04483,-3.450836 4.891724,-5.19577 12,-9c7.885925,-4.220428 13.207825,-6.930664 21,-9c2.899506,-0.770004 4,-1 3,-1c-5,0 -21,0 -40,0c-7,0 -17,-2 -17,0c0,4 10.003876,0.232941 17,0c15.024963,-0.500275 22.824158,1.062897 30,-3c1.230652,-0.696777 -3,-1 -9,-1c-13,0 -21,0 -24,1l-2,1" id="svg_2" stroke-width="1.5" stroke="#fff" fill="none"/>
|
||||
<path d="m192.5,194.4375c1,0 2.74707,1.61702 7,3c5.784607,1.881058 9.04863,2.814651 17,5c10.067017,2.766815 21,5 27,5c8,0 12,0 19,1l5,0l2,0" id="svg_3" stroke-width="1.5" stroke="#fff" fill="none"/>
|
||||
<path d="m251.5,198.4375c-1,0 -1.934143,0.144287 -4,1c-5.843124,2.420303 -8.925797,2.497559 -14,3c-5.970795,0.591232 -11,0 -13,0c-4,0 -7,0 -9,0l-1,0l-2,0l0,-1" id="svg_4" stroke-width="1.5" stroke="#fff" fill="none"/>
|
||||
<text font-style="normal" font-weight="normal" filter="url(#svg_8_blur)" opacity="0.78" xml:space="preserve" text-anchor="start" font-family="Helvetica, Arial, sans-serif" font-size="26" id="svg_8" y="21.4375" x="27" stroke="#000ecc" fill="#ffffff">CYCLE-RIDER</text>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
|
||||
const { getColor } = useColors()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
height?: number
|
||||
start?: string
|
||||
end?: string
|
||||
}>(),
|
||||
{
|
||||
height: 18,
|
||||
start: 'primary',
|
||||
end: undefined,
|
||||
},
|
||||
)
|
||||
|
||||
const colorsComputed = computed(() => {
|
||||
return {
|
||||
start: getColor(props.start),
|
||||
end: getColor(props.end || props.start),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<div class="flex gap-2">
|
||||
<VaIconMenuCollapsed
|
||||
class="cursor-pointer"
|
||||
:class="{ 'x-flip': !isSidebarMinimized }"
|
||||
:color="collapseIconColor"
|
||||
@click="isSidebarMinimized = !isSidebarMinimized"
|
||||
/>
|
||||
|
||||
<nav class="flex items-center">
|
||||
<VaBreadcrumbs>
|
||||
<VaBreadcrumbsItem label="Home" :to="{ name: 'dashboard' }" />
|
||||
<VaBreadcrumbsItem
|
||||
v-for="item in items"
|
||||
:key="item.label"
|
||||
:label="item.label"
|
||||
@click="handleBreadcrumbClick(item)"
|
||||
/>
|
||||
</VaBreadcrumbs>
|
||||
</nav>
|
||||
</div>
|
||||
</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'
|
||||
|
||||
const { isSidebarMinimized } = storeToRefs(useGlobalStore())
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
type BreadcrumbNavigationItem = {
|
||||
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
|
||||
}
|
||||
if (router.children) {
|
||||
const result = traverse(router.children)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
return traverse(NavigationRoutes.routes)
|
||||
}
|
||||
|
||||
const items = computed(() => {
|
||||
const result: { label: string; to: string; hasChildren: boolean }[] = []
|
||||
route.matched.forEach((route) => {
|
||||
const labelKey = findRouteName(route.name as string)
|
||||
if (!labelKey) {
|
||||
return
|
||||
}
|
||||
result.push({
|
||||
label: t(labelKey),
|
||||
to: route.path,
|
||||
hasChildren: route.children && route.children.length > 0,
|
||||
})
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const { getColor } = useColors()
|
||||
|
||||
const collapseIconColor = computed(() => getColor('secondary'))
|
||||
|
||||
const handleBreadcrumbClick = (item: BreadcrumbNavigationItem) => {
|
||||
if (!item.hasChildren) {
|
||||
router.push(item.to)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.x-flip {
|
||||
transform: scaleX(-100%);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<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-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"
|
||||
/>
|
||||
<path
|
||||
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="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"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-clean-code {
|
||||
display: inline-block;
|
||||
width: 56px;
|
||||
height: 50px;
|
||||
|
||||
.cls-1 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #34495e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<svg
|
||||
:fill="color"
|
||||
class="va-icon-color"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M12 22C6.49 22 2 17.51 2 12S6.49 2 12 2s10 4.04 10 9c0 3.31-2.69 6-6 6h-1.77c-.28 0-.5.22-.5.5 0 .12.05.23.13.33.41.47.64 1.06.64 1.67 0 1.38-1.12 2.5-2.5 2.5zm0-18c-4.41 0-8 3.59-8 8s3.59 8 8 8c.28 0 .5-.22.5-.5 0-.16-.08-.28-.14-.35-.41-.46-.63-1.05-.63-1.65 0-1.38 1.12-2.5 2.5-2.5H16c2.21 0 4-1.79 4-4 0-3.86-3.59-7-8-7z"
|
||||
/>
|
||||
<circle cx="6.5" cy="11.5" r="1.5" />
|
||||
<circle cx="9.5" cy="7.5" r="1.5" />
|
||||
<circle cx="14.5" cy="7.5" r="1.5" />
|
||||
<circle cx="17.5" cy="11.5" r="1.5" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-color {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<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="icon-faster" fill="#34495E">
|
||||
<g>
|
||||
<path
|
||||
id="A"
|
||||
d="M17.748,19 L16.956,16.3 L12.942,16.3 L12.168,19 L8.928,19 L13.302,6.13 L16.614,6.13 L20.988,19 L17.748,19 Z M14.976,9.064 L14.94,9.064 C14.94,9.064 14.652,10.468 14.418,11.278 L13.68,13.78 L16.218,13.78 L15.498,11.278 C15.264,10.468 14.976,9.064 14.976,9.064 Z"
|
||||
/>
|
||||
<rect id="Rectangle-4" height="2" rx="1" width="5" x="3" y="11" />
|
||||
<rect id="Rectangle-4-Copy" height="2" rx="1" width="6" x="4" y="7" />
|
||||
<rect id="Rectangle-4" height="2" rx="1" width="4" x="2" y="15" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-faster {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<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">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M1.08,28.21C1.08,13.62,8.38,6.29,19,6.29S37,13.69,37,28.21,29.66,50.54,19,50.54,1.08,42.8,1.08,28.21Zm23.56,0c0-11.3-2.58-16.9-5.62-16.9s-5.62,5.6-5.62,16.9S16,41.66,19,41.66,24.65,39.51,24.65,28.21Z"
|
||||
/>
|
||||
<line class="cls-2" x1="39.83" x2="39.83" y1="47.62" y2="50.96" />
|
||||
<path
|
||||
class="cls-3"
|
||||
d="M18.73,9.64c-4.9,0-6.9,4.54-6.9,15.66,0,11.29,2.06,16.1,6.9,16.1s6.9-4.81,6.9-16.1C25.63,14.17,23.63,9.64,18.73,9.64Zm0,28.76c-1.07,0-3.9,0-3.9-13.1,0-12.66,2.64-12.66,3.9-12.66s3.9,0,3.9,12.66C22.63,38.4,19.8,38.4,18.73,38.4Z"
|
||||
/>
|
||||
<path
|
||||
class="cls-3"
|
||||
d="M42.9,43.74A3.76,3.76,0,0,1,40.17,45c-1.95,0-3.24-1.57-3.24-4.4s1.53-4.35,3.29-4.35a3.67,3.67,0,0,1,2.5,1.11l2.08-2.55A6.8,6.8,0,0,0,41.33,33V31h-3v2.12a7.09,7.09,0,0,0-1.64.63,43.71,43.71,0,0,0,.77-8.41c0-15.84-7-25.3-18.73-25.3S0,9.46,0,25.3,7.18,51,18.73,51A16.4,16.4,0,0,0,33.12,43.1,6.77,6.77,0,0,0,40,48.46a6.35,6.35,0,0,0,5-2.22ZM18.73,48C8.88,48,3,39.54,3,25.3S8.73,3,18.73,3s15.73,8.13,15.73,22.3S28.58,48,18.73,48Z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-free {
|
||||
display: inline-block;
|
||||
width: 55px;
|
||||
height: 47.8px;
|
||||
|
||||
.cls-1 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: none;
|
||||
stroke: #34495e;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #34495e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<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">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M6,19C11,12.66,26.33,3,46.33,6c-3.67,17-8.67,26-8.67,26s-7,14-19.67-3.67C5.67,26.33,9,22.33,6,19Z"
|
||||
/>
|
||||
<path
|
||||
class="cls-2"
|
||||
d="M48.33.49l-.77,0c-11.22-1.88-30.21,1.46-39,9-3.38,2.89-5,6.11-4.69,9.59a11.06,11.06,0,0,0,4.77,8,11,11,0,0,0,6.24,1.82q.53,0,1.09,0A55.51,55.51,0,0,0,13.2,39.21C9.48,33.07,2.35,30.83,0,30.83v3c.12,0,12.18,1.95,12.5,13.54h0c0,.1,0,.19,0,.29h3a50.57,50.57,0,0,1,3.12-17.21c2.48,5.09,6.36,8,10.91,8.13,5.3.07,10.1-3.85,11.91-9.81.94-3.12,1.88-6.37,2.78-9.51C46.32,11.9,48.31,5,49.62,2.76L51,.49ZM10.28,24.61a8.06,8.06,0,0,1-3.45-5.73c-.19-2.47,1-4.85,3.65-7.08,5.9-5,17.25-8.12,27-8.7-8.35,4-15.7,12.31-20.23,22.51l-.38.1h0A8.82,8.82,0,0,1,10.28,24.61Zm31-6.18c-.9,3.13-1.82,6.37-2.76,9.47-1.4,4.62-4.95,7.69-8.86,7.69h-.13c-4-.07-7.45-3.43-9.33-9.09C25.83,14.27,35.67,4.94,45.92,3.65,44.6,7,43.06,12.39,41.33,18.42Z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-fresh {
|
||||
display: inline-block;
|
||||
width: 51px;
|
||||
height: 48px;
|
||||
|
||||
.cls-1 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #34495e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<template>
|
||||
<svg viewBox="0 0 98 96" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<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"
|
||||
/>
|
||||
<path
|
||||
d="M10.3655 9.20139H18.3516C18.5634 9.20139 18.7666 9.28553 18.9163 9.4353C19.0661 9.58506 19.1502 9.78819 19.1502 10C19.1502 10.2118 19.0661 10.4149 18.9163 10.5647C18.7666 10.7145 18.5634 10.7986 18.3516 10.7986H10.3655C10.1537 10.7986 9.95058 10.7145 9.80081 10.5647C9.65105 10.4149 9.56691 10.2118 9.56691 10C9.56691 9.78819 9.65105 9.58506 9.80081 9.4353C9.95058 9.28553 10.1537 9.20139 10.3655 9.20139ZM2.37941 9.20139H6.37246C6.58427 9.20139 6.7874 9.28553 6.93717 9.4353C7.08693 9.58506 7.17107 9.78819 7.17107 10C7.17107 10.2118 7.08693 10.4149 6.93717 10.5647C6.7874 10.7145 6.58427 10.7986 6.37246 10.7986H2.37941C2.1676 10.7986 1.96447 10.7145 1.8147 10.5647C1.66493 10.4149 1.5808 10.2118 1.5808 10C1.5808 9.78819 1.66493 9.58506 1.8147 9.4353C1.96447 9.28553 2.1676 9.20139 2.37941 9.20139Z"
|
||||
fill="#767C88"
|
||||
/>
|
||||
<path
|
||||
d="M18.3516 15.5903H3.97663C3.53557 15.5903 3.17802 15.9478 3.17802 16.3889C3.17802 16.8299 3.53557 17.1875 3.97663 17.1875H18.3516C18.7927 17.1875 19.1502 16.8299 19.1502 16.3889C19.1502 15.9478 18.7927 15.5903 18.3516 15.5903Z"
|
||||
fill="#767C88"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M3.64583 7.60417C3.95771 7.29229 4.46336 7.29229 4.77524 7.60417C5.08712 7.91604 5.08712 8.4217 4.77524 8.73357L3.50881 10L4.77524 11.2664C5.08712 11.5783 5.08712 12.084 4.77524 12.3958C4.46336 12.7077 3.95771 12.7077 3.64583 12.3958L1.74342 10.4934C1.47091 10.2209 1.47091 9.77909 1.74342 9.50658L3.64583 7.60417Z"
|
||||
fill="#767C88"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<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" />
|
||||
<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>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-menu {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<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" />
|
||||
<path
|
||||
:fill="color"
|
||||
d="M3 11h10a1 1 0 0 1 0 2H3a1 1 0 0 1 0-2zM20.993 11l-2.7-2.7-1.414 1.414L18.164 11H16a1 1 0 0 0 0 2h2.179l-1.3 1.3 1.414 1.414L21.007 13A1 1 0 0 0 21 11h-.007z"
|
||||
/>
|
||||
<rect :fill="color" height="2" rx="1" width="20" x="2" y="19" />
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-menu-collapsed {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<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"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-message {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
:fill="color"
|
||||
d="M10 20c1.1 0 2-.9 2-2H8c0 1.1.9 2 2 2zm6-6V9c0-3.07-1.63-5.64-4.5-6.32V2c0-.83-.67-1.5-1.5-1.5S8.5 1.17 8.5 2v.68C5.64 3.36 4 5.92 4 9v5l-2 2v1h16v-1l-2-2zm-2 1H6V9c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6zM5.58 2.08L4.15.65C1.75 2.48.17 5.3.03 8.5h2a8.445 8.445 0 0 1 3.55-6.42zM17.97 8.5h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43a8.495 8.495 0 0 1 3.54 6.42z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
color?: string
|
||||
}>(),
|
||||
{
|
||||
color: 'inherit',
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<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" />
|
||||
<circle class="cls-2" cx="24" cy="41" r="2.67" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-responsive {
|
||||
display: inline-block;
|
||||
width: 47.5px;
|
||||
height: 49px;
|
||||
|
||||
.cls-1 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #34495e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<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">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<rect class="cls-1" height="23" width="37.33" x="10.31" y="30.5" />
|
||||
<path
|
||||
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="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"
|
||||
/>
|
||||
<polygon
|
||||
class="cls-2"
|
||||
points="38.89 21.14 45.64 12.13 43.23 10.33 38.45 16.72 36.49 14.97 34.49 17.2 38.89 21.14"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-rich {
|
||||
display: inline-block;
|
||||
width: 57px;
|
||||
height: 55px;
|
||||
|
||||
.cls-1 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #34495e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<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="icon-slower" fill="#34495E">
|
||||
<g>
|
||||
<path
|
||||
id="A"
|
||||
d="M16.82,18.87 L16.028,16.17 L12.014,16.17 L11.24,18.87 L8,18.87 L12.374,6 L15.686,6 L20.06,18.87 L16.82,18.87 Z M14.048,8.934 L14.012,8.934 C14.012,8.934 13.724,10.338 13.49,11.148 L12.752,13.65 L15.29,13.65 L14.57,11.148 C14.336,10.338 14.048,8.934 14.048,8.934 Z"
|
||||
/>
|
||||
<rect id="Rectangle-4" height="2" rx="1" width="2" x="5" y="11" />
|
||||
<rect id="Rectangle-4-Copy" height="2" rx="1" width="3" x="6" y="7" />
|
||||
<rect id="Rectangle-4" height="2" rx="1" width="2" x="4" y="15" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-slower {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<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">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<polygon
|
||||
class="cls-1"
|
||||
points="27.75 21.19 18.48 6.18 4.78 6.18 27.75 42.92 50.89 6.18 36.13 6.18 27.75 21.19"
|
||||
/>
|
||||
<path
|
||||
class="cls-2"
|
||||
d="M33.08,0,27.44,9.76,21.84,0H0L27.43,47.8,55,0ZM27.43,15.77,34.81,3h4.6l-12,20.72L15.55,3h4.55ZM5.18,3h6.91L27.43,29.73,42.88,3h7L27.44,41.78Z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-vue {
|
||||
display: inline-block;
|
||||
width: 55px;
|
||||
height: 47.8px;
|
||||
|
||||
.cls-1 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #34495e;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<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" />
|
||||
<stop offset="100%" stop-color="#C8EA13" />
|
||||
</linearGradient>
|
||||
<linearGradient :id="'CORPORATE'" x1="0%" y1="50%" y2="50%">
|
||||
<stop offset="0%" stop-color="#74BBFF" />
|
||||
<stop offset="100%" stop-color="#6E85E8" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd" transform="scale(2)">
|
||||
<path
|
||||
:fill="`url(#${themeGradientId})`"
|
||||
d="M6.36,15.36 L-7.10542736e-15,0.48 L3.672,0.48 L6.576,7.416 L9.48,0.48 L13.152,0.48 L6.792,15.36 L6.36,15.36 Z M21.744,10.032 L21.744,0.48 L25.248,0.48 L25.248,9.504 C25.248,11.424 26.16,12.096 27.384,12.096 C28.728,12.096 29.568,11.424 29.568,9.504 L29.568,0.48 L33.096,0.48 L33.096,10.032 C33.096,13.44 30.552,15.36 27.384,15.36 C24.096,15.36 21.744,13.344 21.744,10.032 Z M42.6,14.88 L42.6,0.48 L52.128,0.48 L52.128,3.6 L46.128,3.6 L46.128,6.192 L51.672,6.192 L51.672,9.264 L46.128,9.264 L46.128,11.856 L52.392,11.856 L52.392,14.88 L42.6,14.88 Z M61.224,10.656 L64.656,10.656 C64.656,11.496 65.328,12.168 66.144,12.168 C67.032,12.168 67.584,11.664 67.584,10.92 C67.584,9.84 66.168,9.528 64.992,9.12 C62.568,8.256 61.224,7.128 61.224,4.656 C61.224,2.112 63.408,7.10542736e-15 66.12,7.10542736e-15 C69.312,7.10542736e-15 70.824,2.04 71.016,4.704 L67.704,4.704 C67.704,3.888 67.2,3.216 66.216,3.216 C65.448,3.216 64.704,3.672 64.704,4.608 C64.704,5.688 66.024,5.88 67.248,6.24 C69.816,7.008 71.016,8.448 71.016,10.704 C71.016,13.248 68.856,15.36 66.144,15.36 C63,15.36 61.224,13.248 61.224,10.656 Z M82.896,14.88 L82.896,3.6 L79.68,3.6 L79.68,0.48 L89.712,0.48 L89.712,3.6 L86.448,3.6 L86.448,14.88 L82.896,14.88 Z M98.544,14.88 L98.544,0.48 L102.072,0.48 L102.072,14.88 L98.544,14.88 Z M111.72,7.68 C111.72,3.384 114.96,7.10542736e-15 119.52,7.10542736e-15 C122.256,7.10542736e-15 124.152,1.032 125.688,2.64 L123.264,4.944 C122.256,3.96 120.96,3.336 119.52,3.336 C116.952,3.336 115.296,5.256 115.296,7.68 C115.296,10.104 116.952,12.024 119.52,12.024 C120.96,12.024 122.256,11.4 123.264,10.416 L125.616,12.72 C124.176,14.232 122.184,15.36 119.52,15.36 C114.96,15.36 111.72,11.976 111.72,7.68 Z"
|
||||
fill-rule="nonzero"
|
||||
/>
|
||||
<path
|
||||
:fill="textColor"
|
||||
d="M139.712,7.88 L139.712,6.152 L138.44,6.152 C138.272,6.152 138.066,6.162 137.822,6.182 C137.578,6.202 137.36,6.224 137.168,6.248 C137.424,5.984 137.682,5.704 137.942,5.408 C138.202,5.112 138.436,4.808 138.644,4.496 C138.852,4.184 139.022,3.87 139.154,3.554 C139.286,3.238 139.352,2.928 139.352,2.624 C139.352,2.248 139.288,1.906 139.16,1.598 C139.032,1.29 138.852,1.028 138.62,0.812 C138.388,0.596 138.112,0.428 137.792,0.308 C137.472,0.188 137.12,0.128 136.736,0.128 C136.456,0.128 136.198,0.152 135.962,0.2 C135.726,0.248 135.502,0.322 135.29,0.422 C135.078,0.522 134.876,0.65 134.684,0.806 C134.492,0.962 134.296,1.144 134.096,1.352 L134.096,1.352 L135.2,2.456 C135.384,2.272 135.574,2.106 135.77,1.958 C135.966,1.81 136.192,1.736 136.448,1.736 C136.728,1.736 136.954,1.818 137.126,1.982 C137.298,2.146 137.384,2.4 137.384,2.744 C137.384,3 137.302,3.274 137.138,3.566 C136.974,3.858 136.748,4.166 136.46,4.49 C136.172,4.814 135.834,5.16 135.446,5.528 C135.058,5.896 134.64,6.288 134.192,6.704 L134.192,6.704 L134.192,7.88 L139.712,7.88 Z M142.56,8.024 C142.912,8.024 143.2,7.904 143.424,7.664 C143.648,7.424 143.76,7.128 143.76,6.776 C143.76,6.424 143.648,6.128 143.424,5.888 C143.2,5.648 142.912,5.528 142.56,5.528 C142.208,5.528 141.92,5.648 141.696,5.888 C141.472,6.128 141.36,6.424 141.36,6.776 C141.36,7.128 141.472,7.424 141.696,7.664 C141.92,7.904 142.208,8.024 142.56,8.024 Z M150.736,7.88 L150.736,6.224 L149.368,6.224 L149.368,0.272 L147.856,0.272 C147.568,0.456 147.272,0.604 146.968,0.716 C146.664,0.828 146.296,0.928 145.864,1.016 L145.864,1.016 L145.864,2.288 L147.304,2.288 L147.304,6.224 L145.672,6.224 L145.672,7.88 L150.736,7.88 Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'VaIconVuestic',
|
||||
inject: ['contextConfig'],
|
||||
computed: {
|
||||
themeGradientId() {
|
||||
return this.contextConfig.invertedColor ? 'CORPORATE' : 'ORIGINAL'
|
||||
},
|
||||
textColor() {
|
||||
return this.contextConfig.invertedColor ? '#6E85E8' : '#E4FF32'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.va-icon-vuestic {
|
||||
.st0 {
|
||||
fill: #4ae387;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<VaNavbar class="app-layout-navbar py-2 px-0">
|
||||
<template #left>
|
||||
<div class="left">
|
||||
<Transition v-if="isMobile" name="icon-fade" mode="out-in">
|
||||
<VaIcon
|
||||
color="primary"
|
||||
:name="isSidebarMinimized ? 'menu' : 'close'"
|
||||
size="24px"
|
||||
style="margin-top: 3px"
|
||||
@click="isSidebarMinimized = !isSidebarMinimized"
|
||||
/>
|
||||
</Transition>
|
||||
<RouterLink to="/" aria-label="Visit home page">
|
||||
<VuesticLogo />
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<AppNavbarActions class="app-navbar__actions" :is-mobile="isMobile" />
|
||||
</template>
|
||||
</VaNavbar>
|
||||
</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'
|
||||
|
||||
defineProps({
|
||||
isMobile: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const GlobalStore = useGlobalStore()
|
||||
|
||||
const { isSidebarMinimized } = storeToRefs(GlobalStore)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.va-navbar {
|
||||
z-index: 2;
|
||||
|
||||
@media screen and (max-width: 950px) {
|
||||
.left {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-navbar__actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 1rem;
|
||||
|
||||
& > * {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
& > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-fade-enter-active,
|
||||
.icon-fade-leave-active {
|
||||
transition: transform 0.5s ease;
|
||||
}
|
||||
|
||||
.icon-fade-enter,
|
||||
.icon-fade-leave-to {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div class="app-navbar-actions">
|
||||
<!-- <GithubButton v-if="!isMobile" class="app-navbar-actions__item" />
|
||||
<VaButton
|
||||
v-if="!isMobile"
|
||||
preset="secondary"
|
||||
href="https://admin.vuestic.dev/"
|
||||
target="_blank"
|
||||
color="textPrimary"
|
||||
class="app-navbar-actions__item flex-shrink-0 mx-0"
|
||||
>
|
||||
{{ t('aboutVuesticAdmin') }}
|
||||
</VaButton> -->
|
||||
<!-- <VaButton
|
||||
v-if="!isMobile"
|
||||
preset="secondary"
|
||||
href="https://discord.gg/u7fQdqQt8c"
|
||||
target="_blank"
|
||||
color="textPrimary"
|
||||
class="app-navbar-actions__item flex-shrink-0 mx-0"
|
||||
>
|
||||
{{ t('helpAndSupport') }}
|
||||
</VaButton> -->
|
||||
<!-- <NotificationDropdown class="app-navbar-actions__item" /> -->
|
||||
<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'
|
||||
|
||||
defineProps({
|
||||
isMobile: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-navbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.va-dropdown__anchor {
|
||||
color: var(--va-primary);
|
||||
fill: var(--va-primary);
|
||||
}
|
||||
|
||||
&__item {
|
||||
padding: 0;
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
|
||||
svg {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&--profile {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.va-dropdown-content {
|
||||
background-color: var(--va-white);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fa-github {
|
||||
color: var(--va-on-background-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<VaButton
|
||||
preset="secondary"
|
||||
color="textPrimary"
|
||||
href="https://github.com/epicmaxco/vuestic-admin"
|
||||
target="_blank"
|
||||
aria-label="Visit github"
|
||||
>
|
||||
<VaIcon :component="VaIconGitHub" />
|
||||
</VaButton>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import VaIconGitHub from '../../icons/VaIconGitHub.vue'
|
||||
</script>
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
<template>
|
||||
<VaDropdown :offset="[13, 0]" class="notification-dropdown" stick-to-edges :close-on-content-click="false">
|
||||
<template #anchor>
|
||||
<VaButton preset="secondary" color="textPrimary">
|
||||
<VaBadge overlap>
|
||||
<template #text> 2+</template>
|
||||
<VaIconNotification class="notification-dropdown__icon" />
|
||||
</VaBadge>
|
||||
</VaButton>
|
||||
</template>
|
||||
<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">
|
||||
<VaListItem class="text-base">
|
||||
<VaListItemSection icon class="mx-0 p-0">
|
||||
<VaIcon :name="item.icon" color="secondary" />
|
||||
</VaListItemSection>
|
||||
<VaListItemSection>
|
||||
{{ item.message }}
|
||||
</VaListItemSection>
|
||||
<VaListItemSection icon class="mx-1">
|
||||
{{ item.updateTimestamp }}
|
||||
</VaListItemSection>
|
||||
</VaListItem>
|
||||
<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>
|
||||
</section>
|
||||
</VaDropdownContent>
|
||||
</VaDropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import VaIconNotification from '../../../icons/VaIconNotification.vue'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const makeDateFromNow = (timeFromNow: number) => {
|
||||
const date = new Date()
|
||||
date.setTime(date.getTime() + timeFromNow)
|
||||
return date
|
||||
}
|
||||
|
||||
const notifications: INotification[] = [
|
||||
{
|
||||
message: '4 pending requests',
|
||||
icon: 'favorite_outline',
|
||||
id: 1,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-3 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
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',
|
||||
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',
|
||||
id: 4,
|
||||
updateTimestamp: makeDateFromNow(-2 * 7 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
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',
|
||||
id: 6,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-3 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
{
|
||||
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',
|
||||
id: 8,
|
||||
separator: true,
|
||||
updateTimestamp: makeDateFromNow(-1 * 24 * 60 * 60 * 1000),
|
||||
},
|
||||
].sort((a, b) => new Date(b.updateTimestamp).getTime() - new Date(a.updateTimestamp).getTime())
|
||||
|
||||
const TIME_NAMES = {
|
||||
second: 1000,
|
||||
minute: 1000 * 60,
|
||||
hour: 1000 * 60 * 60,
|
||||
day: 1000 * 60 * 60 * 24,
|
||||
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
|
||||
}
|
||||
|
||||
const notificationsWithRelativeTime = computed(() => {
|
||||
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)
|
||||
|
||||
let separator = false
|
||||
|
||||
const nextItem = list[index + 1]
|
||||
if (nextItem) {
|
||||
const nextItemDifference = Math.round(new Date().getTime() - new Date(nextItem.updateTimestamp).getTime())
|
||||
const nextItemTimeName = getTimeName(nextItemDifference)
|
||||
|
||||
if (timeName !== nextItemTimeName) {
|
||||
separator = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
updateTimestamp: rtf.format(-1 * Math.round(timeDifference / TIME_NAMES[timeName]), timeName),
|
||||
separator,
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notification-dropdown {
|
||||
cursor: pointer;
|
||||
|
||||
.notification-dropdown__icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.va-dropdown__anchor {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div class="profile-dropdown-wrapper">
|
||||
<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">
|
||||
<slot />
|
||||
<VaAvatar :size="32" color="warning"> 😍 </VaAvatar>
|
||||
</span>
|
||||
</VaButton>
|
||||
</template>
|
||||
<VaDropdownContent
|
||||
class="profile-dropdown__content md:w-60 px-0 py-4 w-full"
|
||||
: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">
|
||||
{{ t(`user.${group.name}`) }}
|
||||
</header>
|
||||
<VaListItem
|
||||
v-for="item in group.list"
|
||||
:key="item.name"
|
||||
class="menu-item px-4 text-base cursor-pointer h-8"
|
||||
v-bind="resolveLinkAttribute(item)"
|
||||
>
|
||||
<VaIcon :name="item.icon" class="pr-1" color="secondary" />
|
||||
{{ t(`user.${item.name}`) }}
|
||||
</VaListItem>
|
||||
<VaListSeparator v-if="group.separator" class="mx-3 my-2" />
|
||||
</VaList>
|
||||
</VaDropdownContent>
|
||||
</VaDropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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 { t } = useI18n()
|
||||
|
||||
type ProfileListItem = {
|
||||
name: string
|
||||
to?: string
|
||||
href?: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
type ProfileOptions = {
|
||||
name: string
|
||||
separator: boolean
|
||||
list: ProfileListItem[]
|
||||
}
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
options?: ProfileOptions[]
|
||||
}>(),
|
||||
{
|
||||
options: () => [
|
||||
{
|
||||
name: 'account',
|
||||
separator: true,
|
||||
list: [
|
||||
{
|
||||
name: 'profile',
|
||||
to: 'preferences',
|
||||
icon: 'mso-account_circle',
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
to: 'settings',
|
||||
icon: 'mso-settings',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'explore',
|
||||
separator: true,
|
||||
list: [
|
||||
{
|
||||
name: 'faq',
|
||||
to: 'faq',
|
||||
icon: 'mso-quiz',
|
||||
},
|
||||
{
|
||||
name: 'helpAndSupport',
|
||||
href: 'https://discord.gg/u7fQdqQt8c',
|
||||
icon: 'mso-error',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
separator: false,
|
||||
list: [
|
||||
{
|
||||
name: 'logout',
|
||||
to: 'login',
|
||||
icon: 'mso-logout',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
const isShown = ref(false)
|
||||
|
||||
const resolveLinkAttribute = (item: ProfileListItem) => {
|
||||
return item.to ? { to: { name: item.to } } : item.href ? { href: item.href, target: '_blank' } : {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.profile-dropdown {
|
||||
cursor: pointer;
|
||||
|
||||
&__content {
|
||||
.menu-item:hover {
|
||||
background: var(--hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__anchor {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<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">
|
||||
<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)}`"
|
||||
role="button"
|
||||
hover-opacity="0.10"
|
||||
>
|
||||
<VaSidebarItemContent class="py-3 pr-2 pl-4">
|
||||
<VaIcon
|
||||
v-if="route.meta.icon"
|
||||
aria-hidden="true"
|
||||
:name="route.meta.icon"
|
||||
size="20px"
|
||||
:color="iconColor(route)"
|
||||
/>
|
||||
<VaSidebarItemTitle class="flex justify-between items-center leading-5 font-semibold">
|
||||
{{ t(route.displayName) }}
|
||||
<VaIcon v-if="route.children" :name="arrowDirection(isCollapsed)" size="20px" />
|
||||
</VaSidebarItemTitle>
|
||||
</VaSidebarItemContent>
|
||||
</VaSidebarItem>
|
||||
</template>
|
||||
<template #body>
|
||||
<div v-for="(childRoute, index2) in route.children" :key="index2">
|
||||
<VaSidebarItem
|
||||
:to="{ name: childRoute.name }"
|
||||
:active="isActiveChildRoute(childRoute)"
|
||||
:active-color="activeColor"
|
||||
:text-color="textColor(childRoute)"
|
||||
:aria-label="`Visit ${t(route.displayName)}`"
|
||||
hover-opacity="0.10"
|
||||
>
|
||||
<VaSidebarItemContent class="py-3 pr-2 pl-11">
|
||||
<VaSidebarItemTitle class="leading-5 font-semibold">
|
||||
{{ t(childRoute.displayName) }}
|
||||
</VaSidebarItemTitle>
|
||||
</VaSidebarItemContent>
|
||||
</VaSidebarItem>
|
||||
</div>
|
||||
</template>
|
||||
</VaCollapse>
|
||||
</VaAccordion>
|
||||
</VaSidebar>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useColors } from 'vuestic-ui'
|
||||
|
||||
import navigationRoutes, { type INavigationRoute } from './NavigationRoutes'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Sidebar',
|
||||
props: {
|
||||
visible: { type: Boolean, default: true },
|
||||
mobile: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['update:visible'],
|
||||
|
||||
setup: (props, { emit }) => {
|
||||
const { getColor, colorToRgba } = useColors()
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const value = ref<boolean[]>([])
|
||||
|
||||
const writableVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (v: boolean) => emit('update:visible', v),
|
||||
})
|
||||
|
||||
const isActiveChildRoute = (child: INavigationRoute) => route.name === child.name
|
||||
|
||||
const routeHasActiveChild = (section: INavigationRoute) => {
|
||||
if (!section.children) {
|
||||
return route.path.endsWith(`${section.name}`)
|
||||
}
|
||||
|
||||
return section.children.some(({ name }) => route.path.endsWith(`${name}`))
|
||||
}
|
||||
|
||||
const setActiveExpand = () =>
|
||||
(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 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 })
|
||||
|
||||
return {
|
||||
writableVisible,
|
||||
sidebarWidth,
|
||||
value,
|
||||
color,
|
||||
activeColor,
|
||||
navigationRoutes,
|
||||
routeHasActiveChild,
|
||||
isActiveChildRoute,
|
||||
t,
|
||||
iconColor,
|
||||
textColor,
|
||||
arrowDirection,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
export interface INavigationRoute {
|
||||
name: string
|
||||
displayName: string
|
||||
meta: { icon: string }
|
||||
children?: INavigationRoute[]
|
||||
}
|
||||
|
||||
export default {
|
||||
root: {
|
||||
name: '/',
|
||||
displayName: 'navigationRoutes.home',
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
name: 'dashboard',
|
||||
displayName: 'menu.dashboard',
|
||||
meta: {
|
||||
icon: 'vuestic-iconset-dashboard',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: 'users',
|
||||
// displayName: 'menu.users',
|
||||
// meta: {
|
||||
// icon: 'group',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'projects',
|
||||
// displayName: 'menu.projects',
|
||||
// meta: {
|
||||
// icon: 'folder_shared',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'payments',
|
||||
// displayName: 'menu.payments',
|
||||
// meta: {
|
||||
// icon: 'credit_card',
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// name: 'payment-methods',
|
||||
// displayName: 'menu.payment-methods',
|
||||
// },
|
||||
// {
|
||||
// name: 'pricing-plans',
|
||||
// displayName: 'menu.pricing-plans',
|
||||
// },
|
||||
// {
|
||||
// name: 'billing',
|
||||
// displayName: 'menu.billing',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// name: 'auth',
|
||||
// displayName: 'menu.auth',
|
||||
// meta: {
|
||||
// icon: 'login',
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// name: 'login',
|
||||
// displayName: 'menu.login',
|
||||
// },
|
||||
// {
|
||||
// name: 'signup',
|
||||
// displayName: 'menu.signup',
|
||||
// },
|
||||
// {
|
||||
// name: 'recover-password',
|
||||
// displayName: 'menu.recover-password',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'faq',
|
||||
// displayName: 'menu.faq',
|
||||
// meta: {
|
||||
// icon: 'quiz',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: '404',
|
||||
// displayName: 'menu.404',
|
||||
// meta: {
|
||||
// icon: 'vuestic-iconset-files',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'preferences',
|
||||
// displayName: 'menu.preferences',
|
||||
// meta: {
|
||||
// icon: 'manage_accounts',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'settings',
|
||||
// displayName: 'menu.settings',
|
||||
// meta: {
|
||||
// icon: 'settings',
|
||||
// },
|
||||
// },
|
||||
] as INavigationRoute[],
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import Typography from './Typography.vue'
|
||||
|
||||
export default {
|
||||
title: 'Typography',
|
||||
component: Typography,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
|
||||
export const Default = () => ({
|
||||
components: { Typography },
|
||||
template: `
|
||||
<Typography/>
|
||||
`,
|
||||
})
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<VaContent class="typography content">
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
<VaCard class="col-span-12">
|
||||
<VaCardTitle>Primary text styles</VaCardTitle>
|
||||
<VaCardContent>
|
||||
<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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<pre class="code-snippet">
|
||||
<p class=“code-snippet”>
|
||||
This is a wonderful example.
|
||||
<a href=“#” onClick=“”>Read more</a>
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
|
||||
<VaCard class="col-span-12">
|
||||
<VaCardTitle>Secondary text styles</VaCardTitle>
|
||||
<VaCardContent>
|
||||
<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.
|
||||
</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">
|
||||
<li>Coffee</li>
|
||||
<li>
|
||||
Tea
|
||||
<ol class="va-ordered">
|
||||
<li>
|
||||
Black tea
|
||||
<ol class="va-ordered">
|
||||
<li>Brooke Bond</li>
|
||||
<li>Lipton</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
Green tea
|
||||
<ol class="va-ordered">
|
||||
<li>Greenfield</li>
|
||||
<li>Tess</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>Milk</li>
|
||||
</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.
|
||||
</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">
|
||||
<li>Coffee</li>
|
||||
<li>
|
||||
Tea
|
||||
<ul class="va-unordered">
|
||||
<li>
|
||||
Black tea
|
||||
<ul class="va-unordered">
|
||||
<li>Brooke Bond</li>
|
||||
<li>Lipton</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Green tea
|
||||
<ul class="va-unordered">
|
||||
<li>Greenfield</li>
|
||||
<li>Tess</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Milk</li>
|
||||
</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>
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
<p>
|
||||
<i>— Mister Lebowski</i>
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<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
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<table class="va-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<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">
|
||||
<td v-for="(itemData, colIndex) in rowData" :key="colIndex">
|
||||
{{ itemData }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</div>
|
||||
</VaContent>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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'],
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text--secondary {
|
||||
color: var(--va-secondary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<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'
|
||||
|
||||
defineOptions({
|
||||
name: 'VaChart',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
data: ChartData<T>
|
||||
options?: ChartOptions<T>
|
||||
type: T
|
||||
}>()
|
||||
|
||||
const chartComponent = chartTypesMap[props.type]
|
||||
|
||||
const chartOptions = computed<ChartOptions<T>>(() => ({
|
||||
...(defaultConfig as any),
|
||||
...props.options,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.va-chart {
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> * {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<Bar :data="data" :options="options" />
|
||||
</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'
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
|
||||
|
||||
defineProps<{
|
||||
data: any
|
||||
options?: ChartOptions<'bar'>
|
||||
}>()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<Bubble :data="props.data" :options="options" />
|
||||
</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'
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, PointElement, LinearScale)
|
||||
|
||||
const props = defineProps<{
|
||||
data: TBubbleChartData
|
||||
options?: ChartOptions<'bubble'>
|
||||
}>()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<Doughnut :data="props.data" :options="options" />
|
||||
</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'
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
|
||||
|
||||
const props = defineProps<{
|
||||
data: TDoughnutChartData
|
||||
options?: ChartOptions<'doughnut'>
|
||||
}>()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<Bar :data="props.data" :options="{ ...options, ...horizontalBarOptions }" />
|
||||
</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'
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, BarElement, LinearScale, CategoryScale)
|
||||
|
||||
const horizontalBarOptions = {
|
||||
indexAxis: 'y' as 'x' | 'y',
|
||||
elements: {
|
||||
bar: {
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: TBarChartData
|
||||
options?: ChartOptions<'bar'>
|
||||
}>()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
<template>
|
||||
<Line ref="chart" :data="computedChartData" :options="options" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import type { ChartOptions } from 'chart.js'
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
LineElement,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
Filler,
|
||||
} 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)
|
||||
|
||||
const chart = ref<typeof Line>()
|
||||
|
||||
const props = defineProps<{
|
||||
data: TLineChartData
|
||||
options?: ChartOptions<'line'>
|
||||
}>()
|
||||
|
||||
const ctx = computed(() => {
|
||||
if (!chart.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return chart.value.chart?.ctx ?? null
|
||||
})
|
||||
|
||||
const { setHSLAColor, getColor } = useColors()
|
||||
|
||||
const colors = ['primary', 'success', 'danger', 'warning']
|
||||
|
||||
const computedChartData = computed<TLineChartData>(() => {
|
||||
if (!ctx.value) {
|
||||
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 datasets = props.data.datasets.map((dataset, index) => {
|
||||
const color = getColor(colors[index % colors.length])
|
||||
|
||||
return {
|
||||
...dataset,
|
||||
fill: true,
|
||||
backgroundColor: makeGradient(color),
|
||||
borderColor: color,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
}
|
||||
})
|
||||
|
||||
return { ...props.data, datasets }
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<template>
|
||||
<canvas ref="canvas" style="max-width: 100%" />
|
||||
</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'
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ArcElement,
|
||||
CategoryScale,
|
||||
ChoroplethController,
|
||||
ProjectionScale,
|
||||
ColorScale,
|
||||
GeoFeature,
|
||||
)
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
function getColor(revenue: number) {
|
||||
return revenue >= 0.9 ? '#63A6F8' : revenue > 0.4 ? '#8FC0FA' : '#EDF0F1'
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
options?: ChartOptions<'choropleth'>
|
||||
data: ChartData<'choropleth', { feature: any; value: number }[], string>
|
||||
}>()
|
||||
|
||||
watchEffect(() => {
|
||||
if (canvas.value === null) {
|
||||
return
|
||||
}
|
||||
|
||||
new ChartJS(canvas.value.getContext('2d')!, {
|
||||
type: 'choropleth',
|
||||
data: props.data,
|
||||
options: {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
projection: {
|
||||
axis: 'x',
|
||||
projection: 'mercator',
|
||||
projectionScale: 1.6,
|
||||
},
|
||||
color: {
|
||||
axis: 'x',
|
||||
quantize: 5,
|
||||
display: false,
|
||||
interpolate: getColor,
|
||||
},
|
||||
},
|
||||
animation: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<Pie :data="props.data" :options="options" />
|
||||
</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'
|
||||
|
||||
ChartJS.register(Title, Tooltip, Legend, ArcElement, CategoryScale)
|
||||
|
||||
const props = defineProps<{
|
||||
data: TPieChartData
|
||||
options?: ChartOptions<'pie'>
|
||||
}>()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
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')
|
||||
|
||||
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.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'
|
||||
|
||||
const table = document.createElement('table')
|
||||
table.style.margin = '0px'
|
||||
|
||||
tooltipEl.appendChild(table)
|
||||
chart.canvas.parentNode?.appendChild(tooltipEl)
|
||||
}
|
||||
|
||||
return tooltipEl
|
||||
}
|
||||
|
||||
export const externalTooltipHandler = (context: { chart: Chart; tooltip: TooltipModel<any> }) => {
|
||||
// Tooltip Element
|
||||
const { chart, tooltip } = context
|
||||
const tooltipEl = getOrCreateTooltip(chart)
|
||||
|
||||
// Hide if no tooltip
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.opacity = '0'
|
||||
return
|
||||
}
|
||||
|
||||
// Set Text
|
||||
if (tooltip.body) {
|
||||
const titleLines = tooltip.title || []
|
||||
const bodyLines = tooltip.body.map((b) => b.lines)
|
||||
|
||||
const tableHead = document.createElement('thead')
|
||||
|
||||
titleLines.forEach((title) => {
|
||||
const tr = document.createElement('tr')
|
||||
tr.style.borderWidth = '0'
|
||||
|
||||
const th = document.createElement('th')
|
||||
th.style.borderWidth = '0'
|
||||
const text = document.createTextNode(title)
|
||||
|
||||
th.appendChild(text)
|
||||
tr.appendChild(th)
|
||||
tableHead.appendChild(tr)
|
||||
})
|
||||
|
||||
const tableBody = document.createElement('tbody')
|
||||
bodyLines.forEach((body, 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 tr = document.createElement('tr')
|
||||
tr.style.backgroundColor = 'inherit'
|
||||
tr.style.borderWidth = '0'
|
||||
|
||||
const td = document.createElement('td')
|
||||
td.style.borderWidth = '0'
|
||||
|
||||
const text = document.createTextNode(body as any)
|
||||
|
||||
td.appendChild(span)
|
||||
td.appendChild(text)
|
||||
tr.appendChild(td)
|
||||
tableBody.appendChild(tr)
|
||||
})
|
||||
|
||||
const tableRoot = tooltipEl.querySelector('table')
|
||||
|
||||
// Remove old children
|
||||
while (tableRoot?.firstChild) {
|
||||
tableRoot.firstChild.remove()
|
||||
}
|
||||
|
||||
// Add new children
|
||||
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'
|
||||
|
||||
computePosition(chart.canvas.parentNode! as HTMLElement, tooltipEl!, {
|
||||
placement: 'top',
|
||||
middleware: [flip(), shift()],
|
||||
}).then(({ x, y }) => {
|
||||
Object.assign(tooltipEl!.style, {
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { defineAsyncComponent, markRaw } from 'vue'
|
||||
|
||||
const DEFAULT_FONT_FAMILY = "'Inter', sans-serif"
|
||||
|
||||
export const defaultConfig = {
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
font: {
|
||||
family: DEFAULT_FONT_FAMILY,
|
||||
},
|
||||
},
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
font: {
|
||||
family: DEFAULT_FONT_FAMILY,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
font: {
|
||||
color: '#34495e',
|
||||
family: DEFAULT_FONT_FAMILY,
|
||||
size: 14,
|
||||
},
|
||||
usePointStyle: true,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
bodyFont: {
|
||||
size: 14,
|
||||
family: DEFAULT_FONT_FAMILY,
|
||||
},
|
||||
boxPadding: 4,
|
||||
},
|
||||
},
|
||||
datasets: {
|
||||
line: {
|
||||
fill: 'origin',
|
||||
tension: 0.3,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
bubble: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
bar: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
animation: true,
|
||||
}
|
||||
|
||||
export const doughnutConfig = {
|
||||
cutout: '80%',
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
grid: {
|
||||
display: false, // Disable X-axis grid lines ("net")
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
grid: {
|
||||
display: false, // Disable Y-axis grid lines ("net")
|
||||
},
|
||||
ticks: {
|
||||
display: false, // Hide Y-axis values
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
datasets: {
|
||||
line: {
|
||||
fill: 'origin',
|
||||
tension: 0.3,
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
bubble: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
bar: {
|
||||
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'))),
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
<template>
|
||||
<div ref="editorElement" class="va-medium-editor content">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, Ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import MediumEditor from 'medium-editor'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
editorOptions?: {
|
||||
buttonLabels: string
|
||||
autoLink: boolean
|
||||
toolbar: {
|
||||
buttons: string[]
|
||||
}
|
||||
}
|
||||
}>(),
|
||||
{
|
||||
editorOptions: () => ({
|
||||
buttonLabels: 'fontawesome',
|
||||
autoLink: true,
|
||||
toolbar: {
|
||||
buttons: ['bold', 'italic', 'underline', 'anchor', 'h1', 'h2', 'h3'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'initialized', editor: typeof MediumEditor): void
|
||||
}>()
|
||||
|
||||
const editorElement: Ref<null | HTMLElement> = ref(null)
|
||||
let editor: typeof MediumEditor | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (!editorElement.value) {
|
||||
return
|
||||
}
|
||||
|
||||
editor = new MediumEditor(editorElement.value, props.editorOptions)
|
||||
emit('initialized', editor)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (editor) {
|
||||
editor.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'medium-editor/src/sass/medium-editor';
|
||||
@import 'variables';
|
||||
|
||||
$medium-editor-shadow: var(--va-box-shadow);
|
||||
$medium-editor-background-color: var(--va-divider);
|
||||
$medium-editor-text-color: var(--va-dark);
|
||||
$medium-editor-active-background-color: var(--va-primary);
|
||||
$medium-editor-active-text-color: var(--va-white);
|
||||
|
||||
.va-medium-editor {
|
||||
margin-bottom: var(--va-medium-editor-margin-bottom);
|
||||
min-width: var(--va-medium-editor-min-width);
|
||||
max-width: var(--va-medium-editor-max-width);
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.content {
|
||||
i {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isn't a part of the .va-medium-editor, so can't be places inside it
|
||||
.medium-editor-toolbar,
|
||||
.medium-editor-toolbar-form,
|
||||
.medium-editor-toolbar-actions,
|
||||
.medium-editor-toolbar-anchor-preview {
|
||||
box-shadow: $medium-editor-shadow;
|
||||
background-color: $medium-editor-background-color;
|
||||
border-radius: 1.5rem;
|
||||
height: 44px;
|
||||
line-height: 42px;
|
||||
}
|
||||
|
||||
.medium-editor-toolbar-anchor-preview {
|
||||
a {
|
||||
padding: 0 2rem;
|
||||
margin: 0;
|
||||
line-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.medium-editor-toolbar {
|
||||
box-shadow: $medium-editor-shadow;
|
||||
|
||||
.medium-editor-toolbar-actions {
|
||||
overflow: hidden;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.medium-editor-action {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0.375rem 1rem;
|
||||
height: 44px;
|
||||
background-color: $medium-editor-background-color;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
|
||||
i {
|
||||
color: $medium-editor-text-color;
|
||||
}
|
||||
|
||||
&.medium-editor-button-active {
|
||||
background-color: $medium-editor-active-background-color;
|
||||
color: $medium-editor-active-text-color;
|
||||
|
||||
i {
|
||||
color: $medium-editor-active-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .medium-editor-action:not(:last-child) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
& > .medium-editor-action + .medium-editor-action {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.medium-editor-toolbar-form {
|
||||
color: $medium-editor-text-color;
|
||||
overflow: hidden;
|
||||
|
||||
a {
|
||||
color: $medium-editor-text-color;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 4px !important;
|
||||
transform: translateY(-2px);
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.medium-editor-toolbar-close {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.medium-toolbar-arrow-under::after {
|
||||
border-color: $medium-editor-background-color transparent transparent transparent;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.medium-toolbar-arrow-over::before {
|
||||
border-color: transparent transparent var(--va-primary) transparent;
|
||||
}
|
||||
|
||||
.medium-editor-toolbar-anchor-preview {
|
||||
// @include va-button($btn-padding-y-nrm, $btn-padding-x-nrm, $btn-font-size-nrm, $btn-line-height-nrm, $btn-border-radius-nrm);
|
||||
|
||||
.medium-editor-toolbar-anchor-preview {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.medium-editor-anchor-preview {
|
||||
max-width: 50%;
|
||||
|
||||
a {
|
||||
color: $medium-editor-text-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
:root {
|
||||
--va-medium-editor-margin-bottom: 2.25rem;
|
||||
--va-medium-editor-min-width: 6rem;
|
||||
--va-medium-editor-max-width: 600px;
|
||||
|
||||
/* Toolbar */
|
||||
--va-medium-editor-toolbar-max-width: 90%;
|
||||
--va-medium-editor-toolbar-box-shadow: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<template>
|
||||
<tr class="va-timeline-item">
|
||||
<td class="va-timeline-item__icon-cell">
|
||||
<div class="va-timeline-item__icon">
|
||||
<VaIcon name="schedule" size="22px" color="backgroundBorder" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="va-timeline-item__content-cell">
|
||||
<div class="va-timeline-item__content">
|
||||
<slot />
|
||||
</div>
|
||||
</td>
|
||||
<td class="va-timeline-item__date-cell">
|
||||
<slot name="date">
|
||||
{{ $props.date }}
|
||||
</slot>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
date: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.va-timeline-item {
|
||||
display: table-row;
|
||||
|
||||
&__icon-cell {
|
||||
vertical-align: top;
|
||||
height: 1px;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 24px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background: var(--va-background-border);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
&__content-cell {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__date-cell {
|
||||
vertical-align: top;
|
||||
color: var(--va-secondary);
|
||||
text-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
padding-left: 0.5rem;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.va-timeline-item__icon {
|
||||
&::after {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +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',
|
||||
"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',
|
||||
"Korea, Democratic People's Republic of",
|
||||
'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',
|
||||
]
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { TBarChartData } from '../types'
|
||||
|
||||
export const barChartData: TBarChartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Last year',
|
||||
backgroundColor: 'primary',
|
||||
data: [50, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
|
||||
},
|
||||
{
|
||||
label: 'Current year',
|
||||
backgroundColor: 'info',
|
||||
data: [50, 10, 22, 39, 15, 20, 85, 32, 60, 50, 20, 30],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
import { TBubbleChartData } from '../types'
|
||||
|
||||
export const bubbleChartData: TBubbleChartData = {
|
||||
datasets: [
|
||||
{
|
||||
label: 'USA',
|
||||
backgroundColor: 'danger',
|
||||
data: [
|
||||
{
|
||||
x: 23,
|
||||
y: 25,
|
||||
r: 15,
|
||||
},
|
||||
{
|
||||
x: 40,
|
||||
y: 10,
|
||||
r: 10,
|
||||
},
|
||||
{
|
||||
x: 30,
|
||||
y: 22,
|
||||
r: 30,
|
||||
},
|
||||
{
|
||||
x: 7,
|
||||
y: 43,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 23,
|
||||
y: 27,
|
||||
r: 12,
|
||||
},
|
||||
{
|
||||
x: 20,
|
||||
y: 15,
|
||||
r: 11,
|
||||
},
|
||||
{
|
||||
x: 7,
|
||||
y: 10,
|
||||
r: 35,
|
||||
},
|
||||
{
|
||||
x: 10,
|
||||
y: 20,
|
||||
r: 40,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Russia',
|
||||
backgroundColor: 'primary',
|
||||
data: [
|
||||
{
|
||||
x: 0,
|
||||
y: 30,
|
||||
r: 15,
|
||||
},
|
||||
{
|
||||
x: 20,
|
||||
y: 20,
|
||||
r: 20,
|
||||
},
|
||||
{
|
||||
x: 15,
|
||||
y: 15,
|
||||
r: 50,
|
||||
},
|
||||
{
|
||||
x: 31,
|
||||
y: 46,
|
||||
r: 30,
|
||||
},
|
||||
{
|
||||
x: 20,
|
||||
y: 14,
|
||||
r: 25,
|
||||
},
|
||||
{
|
||||
x: 34,
|
||||
y: 17,
|
||||
r: 30,
|
||||
},
|
||||
{
|
||||
x: 44,
|
||||
y: 44,
|
||||
r: 10,
|
||||
},
|
||||
{
|
||||
x: 39,
|
||||
y: 25,
|
||||
r: 35,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Canada',
|
||||
backgroundColor: 'warning',
|
||||
data: [
|
||||
{
|
||||
x: 10,
|
||||
y: 30,
|
||||
r: 45,
|
||||
},
|
||||
{
|
||||
x: 10,
|
||||
y: 50,
|
||||
r: 20,
|
||||
},
|
||||
{
|
||||
x: 5,
|
||||
y: 5,
|
||||
r: 30,
|
||||
},
|
||||
{
|
||||
x: 40,
|
||||
y: 30,
|
||||
r: 20,
|
||||
},
|
||||
{
|
||||
x: 33,
|
||||
y: 15,
|
||||
r: 18,
|
||||
},
|
||||
{
|
||||
x: 40,
|
||||
y: 20,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 33,
|
||||
y: 33,
|
||||
r: 40,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Belarus',
|
||||
backgroundColor: 'info',
|
||||
data: [
|
||||
{
|
||||
x: 35,
|
||||
y: 30,
|
||||
r: 45,
|
||||
},
|
||||
{
|
||||
x: 25,
|
||||
y: 40,
|
||||
r: 35,
|
||||
},
|
||||
{
|
||||
x: 5,
|
||||
y: 5,
|
||||
r: 30,
|
||||
},
|
||||
{
|
||||
x: 5,
|
||||
y: 20,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 10,
|
||||
y: 40,
|
||||
r: 15,
|
||||
},
|
||||
{
|
||||
x: 3,
|
||||
y: 10,
|
||||
r: 10,
|
||||
},
|
||||
{
|
||||
x: 15,
|
||||
y: 40,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 7,
|
||||
y: 15,
|
||||
r: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Ukraine',
|
||||
backgroundColor: 'success',
|
||||
data: [
|
||||
{
|
||||
x: 25,
|
||||
y: 10,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 17,
|
||||
y: 40,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 35,
|
||||
y: 10,
|
||||
r: 20,
|
||||
},
|
||||
{
|
||||
x: 3,
|
||||
y: 40,
|
||||
r: 10,
|
||||
},
|
||||
{
|
||||
x: 40,
|
||||
y: 40,
|
||||
r: 40,
|
||||
},
|
||||
{
|
||||
x: 20,
|
||||
y: 10,
|
||||
r: 10,
|
||||
},
|
||||
{
|
||||
x: 10,
|
||||
y: 27,
|
||||
r: 35,
|
||||
},
|
||||
{
|
||||
x: 7,
|
||||
y: 26,
|
||||
r: 40,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { computed, ref, watch } from 'vue'
|
||||
import { useColors, useGlobalConfig } from 'vuestic-ui'
|
||||
|
||||
type chartColors = string | string[]
|
||||
|
||||
export function useChartColors(chartColors = [] as chartColors, alfa = 0.6) {
|
||||
const { getGlobalConfig } = useGlobalConfig()
|
||||
const { setHSLAColor, getColor } = useColors()
|
||||
|
||||
const generateHSLAColors = (colors: chartColors) =>
|
||||
typeof colors === 'string'
|
||||
? setHSLAColor(getColor(colors), { a: alfa })
|
||||
: colors.map((color) => setHSLAColor(getColor(color), { a: alfa }))
|
||||
|
||||
const generateColors = (colors: chartColors) =>
|
||||
typeof colors === 'string' ? getColor(colors) : colors.map((color) => getColor(color))
|
||||
|
||||
const generatedHSLAColors = ref(generateHSLAColors(chartColors))
|
||||
const generatedColors = ref(generateColors(chartColors))
|
||||
|
||||
const theme = computed(() => getGlobalConfig().colors!)
|
||||
|
||||
watch(theme, () => {
|
||||
generatedHSLAColors.value = generateHSLAColors(chartColors)
|
||||
generatedColors.value = generateColors(chartColors)
|
||||
})
|
||||
|
||||
return {
|
||||
generateHSLAColors,
|
||||
generateColors,
|
||||
generatedColors,
|
||||
generatedHSLAColors,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
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)
|
||||
|
||||
const datasetsThemesColors = datasetsColors.map(
|
||||
(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
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { TDoughnutChartData } from '../types'
|
||||
|
||||
export const profitBackground = '#154EC1'
|
||||
export const expensesBackground = '#fff'
|
||||
export const earningsBackground = '#ECF0F1'
|
||||
|
||||
export const doughnutChartData: TDoughnutChartData = {
|
||||
labels: ['Profit', 'Expenses'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Yearly Breakdown',
|
||||
backgroundColor: [profitBackground, earningsBackground],
|
||||
data: [432, 167],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { TBarChartData } from '../types'
|
||||
|
||||
export const horizontalBarChartData: TBarChartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
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',
|
||||
data: [20, 30, 20, 40, 50, 40, 15, 60, 30, 20, 42, 53],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +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'
|
||||
|
||||
// TODO: clean up charts data, after dashboard rework
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { TLineChartData } from '../types'
|
||||
|
||||
export const lineChartData: TLineChartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
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
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { TLineChartData } from '../types'
|
||||
|
||||
export const pieChartData: TLineChartData = {
|
||||
labels: ['Africa', 'Asia', 'Europe'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Population (millions)',
|
||||
backgroundColor: ['primary', 'warning', 'danger'],
|
||||
data: [2478, 5267, 734],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
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 type Revenues = {
|
||||
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)
|
||||
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 formatMoney = (amount: number, currency = 'USD'): string => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency,
|
||||
}).format(amount)
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
[
|
||||
{
|
||||
"id": 0,
|
||||
"project_name": "Vuestic",
|
||||
"project_owner": 13,
|
||||
"team": [13, 5, 28, 14, 17, 28, 23, 11, 16, 19, 12, 28, 11],
|
||||
"status": "in progress",
|
||||
"creation_date": "20 Nov 2023"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"project_name": "Mood board",
|
||||
"project_owner": 28,
|
||||
"team": [28, 10, 12, 28, 14, 27, 5, 4, 8, 23, 19, 18, 24, 11, 18, 12, 28],
|
||||
"status": "important",
|
||||
"creation_date": "16 Oct 2023"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"project_name": "Jenkins",
|
||||
"project_owner": 3,
|
||||
"team": [3, 21, 7, 19, 4, 4, 7, 24],
|
||||
"status": "important",
|
||||
"creation_date": "1 Oct 2023"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"project_name": "Springfield media",
|
||||
"project_owner": 17,
|
||||
"team": [17, 25, 21, 9, 18, 12, 15, 0, 7, 2, 7],
|
||||
"status": "important",
|
||||
"creation_date": "19 Sept 2023"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"project_name": "Galileo",
|
||||
"project_owner": 7,
|
||||
"team": [7, 1, 28, 19, 3],
|
||||
"status": "completed",
|
||||
"creation_date": "23 Sept 2023"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"project_name": "Website redesign",
|
||||
"project_owner": 24,
|
||||
"team": [24, 19, 1, 8, 9],
|
||||
"status": "completed",
|
||||
"creation_date": "9 Sept 2023"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"project_name": "Toolset landing",
|
||||
"project_owner": 15,
|
||||
"team": [15, 16, 8, 6, 11, 21, 3, 20],
|
||||
"status": "archived",
|
||||
"creation_date": "17 Aug 2023"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"project_name": "Complete product redesign",
|
||||
"project_owner": 25,
|
||||
"team": [25, 18, 24, 13, 5, 3, 4, 16, 25, 12, 18, 9, 22],
|
||||
"status": "completed",
|
||||
"creation_date": "11 Aug 2023"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"project_name": "Design team project",
|
||||
"project_owner": 17,
|
||||
"team": [17, 6, 21, 17, 7, 6, 14, 13, 27, 7, 20],
|
||||
"status": "archived",
|
||||
"creation_date": "9 Aug 2023"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"project_name": "Regular logistics",
|
||||
"project_owner": 3,
|
||||
"team": [3, 26, 8, 15, 21, 23, 18, 11, 22, 6, 20, 9],
|
||||
"status": "archived",
|
||||
"creation_date": "2 Aug 2023"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"project_name": "Aurora Analytics",
|
||||
"project_owner": 12,
|
||||
"team": [12, 7, 18, 24, 13, 5],
|
||||
"status": "in progress",
|
||||
"creation_date": "10 Dec 2023"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"project_name": "Quantum Leap",
|
||||
"project_owner": 14,
|
||||
"team": [14, 17, 3, 21, 11, 20],
|
||||
"status": "planning",
|
||||
"creation_date": "22 Nov 2023"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"project_name": "Deep Dive Research",
|
||||
"project_owner": 8,
|
||||
"team": [8, 15, 9, 3, 27, 6],
|
||||
"status": "important",
|
||||
"creation_date": "15 Nov 2023"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"project_name": "Sky High Architecture",
|
||||
"project_owner": 21,
|
||||
"team": [21, 2, 17, 18, 4],
|
||||
"status": "completed",
|
||||
"creation_date": "1 Nov 2023"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"project_name": "Tech Horizon",
|
||||
"project_owner": 9,
|
||||
"team": [9, 19, 24, 1, 22],
|
||||
"status": "in progress",
|
||||
"creation_date": "28 Oct 2023"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"project_name": "Edge of Innovation",
|
||||
"project_owner": 16,
|
||||
"team": [16, 11, 5, 14, 23],
|
||||
"status": "planning",
|
||||
"creation_date": "21 Oct 2023"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"project_name": "Crypto Ventures",
|
||||
"project_owner": 20,
|
||||
"team": [20, 7, 15, 26, 12],
|
||||
"status": "important",
|
||||
"creation_date": "10 Oct 2023"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"project_name": "Blockchain Basics",
|
||||
"project_owner": 4,
|
||||
"team": [4, 8, 3, 22, 27],
|
||||
"status": "archived",
|
||||
"creation_date": "5 Oct 2023"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"project_name": "Virtual Reality Exploration",
|
||||
"project_owner": 26,
|
||||
"team": [26, 2, 14, 20, 9],
|
||||
"status": "in progress",
|
||||
"creation_date": "29 Sept 2023"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"project_name": "AI in Daily Life",
|
||||
"project_owner": 2,
|
||||
"team": [2, 13, 24, 5, 11],
|
||||
"status": "completed",
|
||||
"creation_date": "22 Sept 2023"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
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
|
||||
}
|
||||
|
||||
export type Sorting = {
|
||||
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 === 'team') {
|
||||
return obj.team.map((user: any) => user.fullname).join(', ')
|
||||
}
|
||||
|
||||
if (sortBy === 'creation_date') {
|
||||
return new Date(obj[sortBy])
|
||||
}
|
||||
|
||||
return obj[sortBy]
|
||||
}
|
||||
|
||||
export const getProjects = async (options: Sorting & Pagination) => {
|
||||
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][],
|
||||
}))
|
||||
|
||||
if (options.sortBy && options.sortingOrder) {
|
||||
projects.sort((a, b) => {
|
||||
a = getSortItem(a, options.sortBy!)
|
||||
b = getSortItem(b, options.sortBy!)
|
||||
if (a < b) {
|
||||
return options.sortingOrder === 'asc' ? -1 : 1
|
||||
}
|
||||
if (a > b) {
|
||||
return options.sortingOrder === 'asc' ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
const normalizedProjects = projects.slice((options.page - 1) * options.perPage, options.page * options.perPage)
|
||||
|
||||
return {
|
||||
data: normalizedProjects,
|
||||
pagination: {
|
||||
page: options.page,
|
||||
perPage: options.perPage,
|
||||
total: projectsDb.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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' }),
|
||||
}
|
||||
|
||||
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][],
|
||||
}
|
||||
}
|
||||
|
||||
export const updateProject = async (project: (typeof projectsDb)[number]) => {
|
||||
await sleep(1000)
|
||||
|
||||
const index = projectsDb.findIndex((p) => p.id === project.id)
|
||||
projectsDb[index] = project
|
||||
|
||||
return project
|
||||
}
|
||||
|
||||
export const removeProject = async (project: (typeof projectsDb)[number]) => {
|
||||
await sleep(1000)
|
||||
|
||||
const index = projectsDb.findIndex((p) => p.id === project.id)
|
||||
projectsDb.splice(index, 1)
|
||||
|
||||
return project
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"fullname": "Patrik Radkow",
|
||||
"email": "magicpan@example.gg",
|
||||
"username": "magicpan",
|
||||
"role": "user",
|
||||
"avatar": "",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"fullname": "Martin Hoff",
|
||||
"email": "niceadmin@mail.com",
|
||||
"username": "admin",
|
||||
"role": "admin",
|
||||
"avatar": "😍",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"fullname": "Liz Macintosh",
|
||||
"email": "ebrown@gmail.com",
|
||||
"username": "ebrown",
|
||||
"role": "user",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/1.jpg",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"fullname": "M2",
|
||||
"email": "mrm@gmail.com",
|
||||
"username": "mrm",
|
||||
"role": "owner",
|
||||
"avatar": "",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"fullname": "Kevin Smith",
|
||||
"email": "kevin@gmail.com",
|
||||
"username": "kevin13",
|
||||
"role": "user",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/2.jpg",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"fullname": "Martin Hoff",
|
||||
"email": "martin@gmail.com",
|
||||
"username": "martin3",
|
||||
"role": "user",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/3.jpg",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"fullname": "John Doe",
|
||||
"email": "john@mail.com",
|
||||
"username": "john",
|
||||
"role": "user",
|
||||
"avatar": "",
|
||||
"active": true,
|
||||
"notes": ""
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"fullname": "Maksim Nedo",
|
||||
"email": "maksim@epic.com",
|
||||
"username": "maksim",
|
||||
"role": "admin",
|
||||
"avatar": "https://avatars.githubusercontent.com/u/23530004?v=4",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"fullname": "Dmitry Kuzmenko",
|
||||
"email": "dd@pp.com",
|
||||
"username": "dd",
|
||||
"role": "user",
|
||||
"avatar": "",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"fullname": "Rayan Gosling",
|
||||
"email": "rayan@u.ua",
|
||||
"username": "rayan",
|
||||
"role": "user",
|
||||
"avatar": "",
|
||||
"active": true,
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"active": true,
|
||||
"fullname": "Laura Smith",
|
||||
"email": "laura@example.gg",
|
||||
"username": "bbb",
|
||||
"role": "user",
|
||||
"avatar": "",
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"active": true,
|
||||
"fullname": "Ted Mosby",
|
||||
"email": "tedmosby@mail.com",
|
||||
"username": "gamer777",
|
||||
"role": "user",
|
||||
"avatar": "😭",
|
||||
"notes": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum."
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"active": true,
|
||||
"fullname": "Forrest Schmidt Jr.",
|
||||
"email": "Willard23@gmail.com",
|
||||
"username": "Clementine72",
|
||||
"role": "user",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/4.jpg",
|
||||
"notes": "sed asperiores sed"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"active": true,
|
||||
"fullname": "Emilio Bruen",
|
||||
"email": "Amya51@hotmail.com",
|
||||
"username": "Madalyn_Brekke55",
|
||||
"role": "user",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/5.jpg",
|
||||
"notes": "architecto amet deleniti"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"active": false,
|
||||
"fullname": "Jenny Heathcote",
|
||||
"email": "Granville_Lebsack38@yahoo.com",
|
||||
"role": "user",
|
||||
"username": "Vivienne98",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/6.jpg",
|
||||
"notes": "provident ipsam recusandae"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"active": true,
|
||||
"fullname": "Sonya Cummerata III",
|
||||
"email": "Toni2@yahoo.com",
|
||||
"role": "user",
|
||||
"username": "Norwood79",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/7.jpg",
|
||||
"notes": "aut quaerat totam"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"active": true,
|
||||
"fullname": "Ruben Mitchell",
|
||||
"email": "Lisette41@yahoo.com",
|
||||
"role": "user",
|
||||
"username": "Dariana_Schulist",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/8.jpg",
|
||||
"notes": "minima harum ut"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"active": true,
|
||||
"fullname": "Blake Hudson I",
|
||||
"email": "Israel88@hotmail.com",
|
||||
"role": "user",
|
||||
"username": "Crystal.Brakus29",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/9.jpg",
|
||||
"notes": "sint culpa voluptatem"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"active": true,
|
||||
"fullname": "Alison Mueller",
|
||||
"email": "Darien_Mayer@gmail.com",
|
||||
"role": "user",
|
||||
"username": "Cordie.Grant",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/10.jpg",
|
||||
"notes": "officia autem aliquam"
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"active": false,
|
||||
"fullname": "Miss Angelina Jenkins",
|
||||
"email": "Cristal.Sauer@yahoo.com",
|
||||
"role": "user",
|
||||
"username": "Peggie.Runolfsdottir",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/11.jpg",
|
||||
"notes": "rerum rerum rerum"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"active": true,
|
||||
"fullname": "Mack Boyle",
|
||||
"email": "Shanny30@gmail.com",
|
||||
"role": "user",
|
||||
"username": "Phoebe67",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/12.jpg",
|
||||
"notes": "voluptatibus et soluta"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"active": true,
|
||||
"fullname": "Raymond Simonis",
|
||||
"email": "Tressie.Bruen45@gmail.com",
|
||||
"role": "user",
|
||||
"username": "Percy37",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/13.jpg",
|
||||
"notes": "aut id molestiae"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"active": true,
|
||||
"fullname": "Janice Sporer",
|
||||
"email": "Anastasia85@hotmail.com",
|
||||
"role": "user",
|
||||
"username": "Kali84",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/14.jpg",
|
||||
"notes": "magnam eum aliquam"
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"active": true,
|
||||
"fullname": "Francis Schowalter",
|
||||
"email": "Tess56@gmail.com",
|
||||
"role": "user",
|
||||
"username": "Robyn.Kris",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/0.jpg",
|
||||
"notes": "similique architecto in"
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"active": true,
|
||||
"fullname": "Emilio Hoppe",
|
||||
"email": "Bruce49@yahoo.com",
|
||||
"role": "user",
|
||||
"username": "Clemmie.Kutch",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/16.jpg",
|
||||
"notes": "rerum quae dolorem"
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"active": true,
|
||||
"fullname": "Janice Harber",
|
||||
"email": "Jude38@hotmail.com",
|
||||
"role": "user",
|
||||
"username": "Neal70",
|
||||
"avatar": "https://randomuser.me/api/portraits/men/17.jpg",
|
||||
"notes": "iure dolor provident"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"fullname": "Evelyn Morar",
|
||||
"email": "Laverne.Roberts@hotmail.com",
|
||||
"role": "user",
|
||||
"username": "Neal_Thompson84",
|
||||
"active": true,
|
||||
"avatar": "https://randomuser.me/api/portraits/men/18.jpg",
|
||||
"notes": "quae eos placeat"
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"fullname": "Antoinette Schneider",
|
||||
"email": "Ambrose_Stehr25@gmail.com",
|
||||
"role": "user",
|
||||
"username": "Esta.Hickle",
|
||||
"active": true,
|
||||
"avatar": "https://randomuser.me/api/portraits/men/19.jpg",
|
||||
"notes": "qui cumque unde"
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"fullname": "Daniel Ebony",
|
||||
"email": "Nyah44@hotmail.com",
|
||||
"role": "user",
|
||||
"username": "Jade.Kuhlman90",
|
||||
"active": true,
|
||||
"avatar": "https://randomuser.me/api/portraits/men/20.jpg",
|
||||
"notes": "exercitationem velit consectetur"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
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[]
|
||||
|
||||
const getUserProjects = (userId: number | string) => {
|
||||
return projectsDb
|
||||
.filter((project) => project.team.includes(Number(userId)))
|
||||
.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'],
|
||||
}))
|
||||
}
|
||||
|
||||
// Simulate API calls
|
||||
|
||||
export type Pagination = {
|
||||
page: number
|
||||
perPage: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export type Sorting = {
|
||||
sortBy: keyof User | undefined
|
||||
sortingOrder: 'asc' | 'desc' | null
|
||||
}
|
||||
|
||||
export type Filters = {
|
||||
isActive: boolean
|
||||
search: string
|
||||
}
|
||||
|
||||
const getSortItem = (obj: any, sortBy: string) => {
|
||||
if (sortBy === 'projects') {
|
||||
return obj.projects.map((project: any) => project.project_name).join(', ')
|
||||
}
|
||||
|
||||
return obj[sortBy]
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (search) {
|
||||
filteredUsers = filteredUsers.filter((user) => user.fullname.toLowerCase().includes(search.toLowerCase()))
|
||||
}
|
||||
|
||||
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)
|
||||
if (first > second) {
|
||||
return sortingOrder === 'asc' ? 1 : -1
|
||||
}
|
||||
if (first < second) {
|
||||
return sortingOrder === 'asc' ? -1 : 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
const { page = 1, perPage = 10 } = filters || {}
|
||||
return {
|
||||
data: filteredUsers.slice((page - 1) * perPage, page * perPage),
|
||||
pagination: {
|
||||
page,
|
||||
perPage,
|
||||
total: filteredUsers.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const addUser = async (user: 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
|
||||
}
|
||||
|
||||
export const removeUser = async (user: User) => {
|
||||
await sleep(1000)
|
||||
users.splice(
|
||||
users.findIndex((u) => u.id === user.id),
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import type { ChartData } from 'chart.js'
|
||||
|
||||
export type ColorThemes = {
|
||||
[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 TChartData = TLineChartData | TBarChartData | TBubbleChartData | TDoughnutChartData | TPieChartData
|
||||
|
|
@ -0,0 +1,386 @@
|
|||
[
|
||||
{
|
||||
"id": "5d2c865e9a0bae79a6ef7cfa",
|
||||
"firstName": "Ashley",
|
||||
"lastName": "Mcdaniel",
|
||||
"fullName": "Ashley Mcdaniel",
|
||||
"email": "ashleymcdaniel@nebulean.com",
|
||||
"country": "Cayman Islands",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "warning",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865ec73341e16e5f2251",
|
||||
"firstName": "Sellers",
|
||||
"lastName": "Todd",
|
||||
"fullName": "Todd Sellers",
|
||||
"email": "sellerstodd@nebulean.com",
|
||||
"country": "Togo",
|
||||
"starred": false,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "none",
|
||||
"color": "primary",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e38800c5ce28f2f6b",
|
||||
"firstName": "Sherman",
|
||||
"lastName": "Knowles",
|
||||
"fullName": "Sherman Knowles",
|
||||
"email": "shermanknowles@nebulean.com",
|
||||
"country": "Central African Republic",
|
||||
"starred": true,
|
||||
"hasReport": true,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "none",
|
||||
"color": "warning",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e957cd150b82e17a6",
|
||||
"firstName": "Vasquez",
|
||||
"lastName": "Lawson",
|
||||
"fullName": "Vasquez Lawson",
|
||||
"email": "vasquezlawson@nebulean.com",
|
||||
"country": "Bouvet Island",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "warning",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e9194dbe2faf99227",
|
||||
"firstName": "April",
|
||||
"lastName": "Sykes",
|
||||
"fullName": "April Sykes",
|
||||
"email": "aprilsykes@nebulean.com",
|
||||
"country": "Saint Vincent and The Grenadines",
|
||||
"starred": false,
|
||||
"hasReport": true,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "primary",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e1ed74d83f6b26934",
|
||||
"firstName": "Hodges",
|
||||
"lastName": "Garrison",
|
||||
"fullName": "Hodges Garrison",
|
||||
"email": "hodgesgarrison@nebulean.com",
|
||||
"country": "Zimbabwe",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "none",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e0ef31380880c3de5",
|
||||
"firstName": "Therese",
|
||||
"lastName": "Stokes",
|
||||
"fullName": "Therese Stokes",
|
||||
"email": "theresestokes@nebulean.com",
|
||||
"country": "Mali",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "warning",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e4b5ab4727e5c8b69",
|
||||
"firstName": "Goodwin",
|
||||
"lastName": "Brewer",
|
||||
"fullName": "Goodwin Brewer",
|
||||
"email": "goodwinbrewer@nebulean.com",
|
||||
"country": "Iraq",
|
||||
"starred": true,
|
||||
"hasReport": true,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "none",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e4c4d675787cfe1c0",
|
||||
"firstName": "Gomez",
|
||||
"lastName": "Wise",
|
||||
"fullName": "Gomez Wise",
|
||||
"email": "gomezwise@nebulean.com",
|
||||
"country": "Portugal",
|
||||
"starred": true,
|
||||
"hasReport": true,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "none",
|
||||
"color": "primary",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e1017c3229017fc68",
|
||||
"firstName": "Laverne",
|
||||
"lastName": "Ayers",
|
||||
"fullName": "Laverne Ayers",
|
||||
"email": "laverneayers@nebulean.com",
|
||||
"country": "Micronesia",
|
||||
"starred": false,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "info",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865ee66676fd7464f8b9",
|
||||
"firstName": "Stewart",
|
||||
"lastName": "Leon",
|
||||
"fullName": "Stewart Leon",
|
||||
"email": "stewartleon@nebulean.com",
|
||||
"country": "Seychelles",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e644d8acbed1e0e97",
|
||||
"firstName": "Lindsey",
|
||||
"lastName": "Hopkins",
|
||||
"fullName": "Lindsey Hopkins",
|
||||
"email": "lindseyhopkins@nebulean.com",
|
||||
"country": "Costa Rica",
|
||||
"starred": false,
|
||||
"hasReport": true,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "primary",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865ef2b732c74dc3d6a2",
|
||||
"firstName": "Head",
|
||||
"lastName": "Lloyd",
|
||||
"fullName": "Head Lloyd",
|
||||
"email": "headlloyd@nebulean.com",
|
||||
"country": "Turkey",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e4ee4f09e92ead2e7",
|
||||
"firstName": "Fisher",
|
||||
"lastName": "Bradford",
|
||||
"fullName": "Fisher Bradford",
|
||||
"email": "fisherbradford@nebulean.com",
|
||||
"country": "Ethiopia",
|
||||
"starred": true,
|
||||
"hasReport": true,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "info",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e88d46a9e9049a549",
|
||||
"firstName": "Aurora",
|
||||
"lastName": "Bird",
|
||||
"fullName": "Aurora Bird",
|
||||
"email": "aurorabird@nebulean.com",
|
||||
"country": "Burkina Faso",
|
||||
"starred": false,
|
||||
"hasReport": true,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e44bf14ea96d6e752",
|
||||
"firstName": "Bonita",
|
||||
"lastName": "Shields",
|
||||
"fullName": "Bonita Shields",
|
||||
"email": "bonitashields@nebulean.com",
|
||||
"country": "Cote D'Ivoire (Ivory Coast)",
|
||||
"starred": true,
|
||||
"hasReport": true,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "primary",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e2a8be26f6ac4369c",
|
||||
"firstName": "Ethel",
|
||||
"lastName": "Underwood",
|
||||
"fullName": "Ethel Underwood",
|
||||
"email": "ethelunderwood@nebulean.com",
|
||||
"country": "Vanuatu",
|
||||
"starred": false,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e5e0aea40111c37f8",
|
||||
"firstName": "Parker",
|
||||
"lastName": "May",
|
||||
"fullName": "Parker May",
|
||||
"email": "parkermay@nebulean.com",
|
||||
"country": "Pakistan",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "warning",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e7e0c05ecc2d0c186",
|
||||
"firstName": "Hillary",
|
||||
"lastName": "Waters",
|
||||
"fullName": "Hillary Waters",
|
||||
"email": "hillarywaters@nebulean.com",
|
||||
"country": "Comoros",
|
||||
"starred": true,
|
||||
"hasReport": true,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "primary",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e80a72eeda016b169",
|
||||
"firstName": "Raquel",
|
||||
"lastName": "Ferrell",
|
||||
"fullName": "Raquel Ferrell",
|
||||
"email": "raquelferrell@nebulean.com",
|
||||
"country": "China",
|
||||
"starred": false,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "down",
|
||||
"color": "info",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865eafacadd378add679",
|
||||
"firstName": "Pickett",
|
||||
"lastName": "Page",
|
||||
"fullName": "Pickett Page",
|
||||
"email": "pickettpage@nebulean.com",
|
||||
"country": "Bermuda",
|
||||
"starred": true,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e772b1a75bb0a07b5",
|
||||
"firstName": "Alyson",
|
||||
"lastName": "Bailey",
|
||||
"fullName": "Alyson Bailey",
|
||||
"email": "alysonbailey@nebulean.com",
|
||||
"country": "United Arab Emirates",
|
||||
"starred": false,
|
||||
"hasReport": false,
|
||||
"status": "warning",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "warning",
|
||||
"graph": "M 5 20 C 10 5, 15 5, 30 30 S 20 20, 70 20",
|
||||
"graphColor": "#4ae387"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865e137c19a76b56210c",
|
||||
"firstName": "Farley",
|
||||
"lastName": "Meyers",
|
||||
"fullName": "Farley Meyers",
|
||||
"email": "farleymeyers@nebulean.com",
|
||||
"country": "Christmas Island",
|
||||
"starred": false,
|
||||
"hasReport": false,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "warning",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
},
|
||||
{
|
||||
"id": "5d2c865eb0ba37a27aa9afe0",
|
||||
"firstName": "Hinton",
|
||||
"lastName": "Avery",
|
||||
"fullName": "Hinton Avery",
|
||||
"email": "hintonavery@nebulean.com",
|
||||
"country": "Liechtenstein",
|
||||
"starred": false,
|
||||
"hasReport": true,
|
||||
"status": "info",
|
||||
"checked": false,
|
||||
"trend": "up",
|
||||
"color": "info",
|
||||
"graph": "M 5 30 C 10 5, 30 10, 40 30 S 30 30, 90 40",
|
||||
"graphColor": "#e34a4a"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
const fileNameToLocaleModuleDict = import.meta.glob<{ default: Record<string, string> }>('./locales/*.json', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
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]
|
||||
|
||||
return [localeName, localeModule.default] as const
|
||||
})
|
||||
.forEach((localeNameLocaleMessagesTuple) => {
|
||||
messages[localeNameLocaleMessagesTuple[0]] = localeNameLocaleMessagesTuple[1]
|
||||
})
|
||||
|
||||
export default createI18n({
|
||||
legacy: false,
|
||||
locale: 'ru',
|
||||
fallbackLocale: 'ru',
|
||||
messages,
|
||||
})
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "Eu aceito.",
|
||||
"createAccount": "Criar conta",
|
||||
"createNewAccount": "Criar uma nova conta",
|
||||
"email": "Email",
|
||||
"login": "Entrar",
|
||||
"password": "Senha",
|
||||
"recover_password": "Recuperar senha",
|
||||
"sign_up": "Cadastrar-se",
|
||||
"keep_logged_in": "Mantenha-me conectado",
|
||||
"termsOfUse": "Termos de uso.",
|
||||
"reset_password": "Redefinir senha"
|
||||
},
|
||||
"404": {
|
||||
"title": "Esta página está pescando.",
|
||||
"text": "Se você acha que isso não está certo, envie-nos uma mensagem em ",
|
||||
"back_button": "Voltar para o painel"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "Estilos de texto primários",
|
||||
"secondary": "Estilos de texto secundários"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "Versões",
|
||||
"setupRemoteConnections": "Configurar Conexões Remotas",
|
||||
"currentVisitors": "Visitantes Atuais",
|
||||
"charts": {
|
||||
"trendyTrends": "Tendências na moda",
|
||||
"showInMoreDetail": "Mostrar em mais detalhes",
|
||||
"showInLessDetail": "Mostrar em menos detalhes",
|
||||
"loadingSpeed": "Velocidade de carregamento",
|
||||
"topContributors": "Principais colaboradores",
|
||||
"showNextFive": "Mostrar próximos cinco",
|
||||
"commits": "Commits"
|
||||
},
|
||||
"info": {
|
||||
"componentRichTheme": "tema rico em componentes",
|
||||
"completedPullRequests": "solicitações de pull concluídas",
|
||||
"users": "usuários",
|
||||
"points": "pontos",
|
||||
"units": "unidades",
|
||||
"exploreGallery": "Explorar galeria",
|
||||
"viewLibrary": "Ver Biblioteca",
|
||||
"commits": "commits",
|
||||
"components": "componentes",
|
||||
"teamMembers": "membros da equipe"
|
||||
},
|
||||
"tabs": {
|
||||
"overview": {
|
||||
"title": "Visão Geral",
|
||||
"built": "Construído com o framework Vue.js",
|
||||
"free": "Totalmente gratuito para todos",
|
||||
"fresh": "Design fresco e nítido",
|
||||
"mobile": "Responsivo e otimizado para dispositivos móveis",
|
||||
"components": "Toneladas de componentes úteis",
|
||||
"nojQuery": "Completamente livre de jQuery"
|
||||
},
|
||||
"billingAddress": {
|
||||
"title": "Endereço de Cobrança",
|
||||
"personalInfo": "Informação Pessoal",
|
||||
"firstName": "Nome e Sobrenome",
|
||||
"email": "Email",
|
||||
"address": "Endereço",
|
||||
"companyInfo": "Informação da Empresa",
|
||||
"city": "Cidade",
|
||||
"country": "País",
|
||||
"infiniteConnections": "Conexões infinitas",
|
||||
"addConnection": "Adicionar Conexão"
|
||||
},
|
||||
"bankDetails": {
|
||||
"title": "Detalhes Bancários",
|
||||
"detailsFields": "Campos de Detalhes",
|
||||
"bankName": "Nome do Banco",
|
||||
"accountName": "Nome da Conta",
|
||||
"sortCode": "Código de Ordenação",
|
||||
"accountNumber": "Número da Conta",
|
||||
"notes": "Notas",
|
||||
"sendDetails": "Enviar Detalhes"
|
||||
}
|
||||
},
|
||||
"navigationLayout": "layout de navegação",
|
||||
"topBarButton": "Barra Superior",
|
||||
"sideBarButton": "Barra Lateral"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "Português",
|
||||
"english": "Inglês",
|
||||
"spanish": "Espanhol",
|
||||
"simplified_chinese": "Chinês Simplificado",
|
||||
"persian": "Persa"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "Autenticação",
|
||||
"buttons": "Botões",
|
||||
"timelines": "Linhas do Tempo",
|
||||
"dashboard": "Painel de Controle",
|
||||
"billing": "Cobrança",
|
||||
"login": "Entrar",
|
||||
"signUp": "Cadastrar-se",
|
||||
"preferences": "Preferências",
|
||||
"payments": "Pagamentos",
|
||||
"pricing-plans": "Planos de Preços",
|
||||
"login-singup": "Entrar/Cadastrar",
|
||||
"404": "Páginas 404",
|
||||
"faq": "FAQ"
|
||||
},
|
||||
"messages": {
|
||||
"all": "Ver todas as mensagens",
|
||||
"new": "Novas mensagens de {name}",
|
||||
"mark_as_read": "Marcar como Lido"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "Perguntas de desenvolvimento web:",
|
||||
"repository": "Repositório GitHub"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "Ver todas as notificações",
|
||||
"mark_as_read": "Marcar como lida",
|
||||
"sentMessage": "enviou uma mensagem",
|
||||
"uploadedZip": "fez upload de um novo arquivo Zip com {type}",
|
||||
"startedTopic": "iniciou um novo tópico"
|
||||
},
|
||||
"user": {
|
||||
"language": "Mudar Idioma",
|
||||
"logout": "Sair",
|
||||
"profile": "Meu Perfil",
|
||||
"settings": "Configurações",
|
||||
"billing": "Faturamento",
|
||||
"faq": "FAQ",
|
||||
"helpAndSupport": "Ajuda & Suporte",
|
||||
"projects": "Projetos",
|
||||
"account": "Conta",
|
||||
"explore": "Explorar"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "Básico",
|
||||
"icons": "Icones",
|
||||
"selectable": "Selecionável",
|
||||
"editable": "Editável",
|
||||
"advanced": "Advançado"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Gráficos"
|
||||
},
|
||||
"helpAndSupport": "Ajuda & Suporte",
|
||||
"aboutVuesticAdmin": "Sobre Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "Buscar..."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "我同意",
|
||||
"createAccount": "创建账号",
|
||||
"createNewAccount": "创建新账号",
|
||||
"email": "电子邮箱",
|
||||
"login": "登录",
|
||||
"password": "密码",
|
||||
"recover_password": "恢复密码",
|
||||
"sign_up": "注册",
|
||||
"keep_logged_in": "保持登录",
|
||||
"termsOfUse": "使用条款",
|
||||
"reset_password": "重置密码"
|
||||
},
|
||||
"404": {
|
||||
"title": "此页面已去钓鱼",
|
||||
"text": "如果您觉得这不对,请给我们发送消息",
|
||||
"back_button": "返回仪表板"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "主要文本样式",
|
||||
"secondary": "次要文本样式"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "版本",
|
||||
"setupRemoteConnections": "设置远程连接",
|
||||
"currentVisitors": "当前访问者",
|
||||
"charts": {
|
||||
"trendyTrends": "流行趋势",
|
||||
"showInMoreDetail": "显示更多细节",
|
||||
"showInLessDetail": "显示较少细节",
|
||||
"loadingSpeed": "加载速度",
|
||||
"topContributors": "主要贡献者",
|
||||
"showNextFive": "显示接下来的五个",
|
||||
"commits": "提交"
|
||||
},
|
||||
"info": {
|
||||
"componentRichTheme": "组件丰富的主题",
|
||||
"completedPullRequests": "已完成的拉取请求",
|
||||
"users": "用户",
|
||||
"points": "点数",
|
||||
"units": "单位",
|
||||
"exploreGallery": "探索画廊",
|
||||
"viewLibrary": "查看库",
|
||||
"commits": "提交",
|
||||
"components": "组件",
|
||||
"teamMembers": "团队成员"
|
||||
},
|
||||
"tabs": {
|
||||
"overview": {
|
||||
"title": "概述",
|
||||
"built": "使用 Vue.js 框架构建",
|
||||
"free": "对所有人完全免费",
|
||||
"fresh": "新鲜和清新的设计",
|
||||
"mobile": "响应式且优化移动",
|
||||
"components": "大量有用的组件",
|
||||
"nojQuery": "完全不使用 jQuery"
|
||||
},
|
||||
"billingAddress": {
|
||||
"title": "账单地址",
|
||||
"personalInfo": "个人信息",
|
||||
"firstName": "名字 & 姓氏",
|
||||
"email": "电子邮箱",
|
||||
"address": "地址",
|
||||
"companyInfo": "公司信息",
|
||||
"city": "城市",
|
||||
"country": "国家",
|
||||
"infiniteConnections": "无限连接",
|
||||
"addConnection": "添加连接"
|
||||
},
|
||||
"bankDetails": {
|
||||
"title": "银行详情",
|
||||
"detailsFields": "详情字段",
|
||||
"bankName": "银行名称",
|
||||
"accountName": "账户名称",
|
||||
"sortCode": "排序代码",
|
||||
"accountNumber": "账号",
|
||||
"notes": "备注",
|
||||
"sendDetails": "发送详情"
|
||||
}
|
||||
},
|
||||
"navigationLayout": "导航布局",
|
||||
"topBarButton": "顶部按钮",
|
||||
"sideBarButton": "侧边按钮"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "葡萄牙语",
|
||||
"english": "英语",
|
||||
"spanish": "西班牙语",
|
||||
"simplified_chinese": "简体中文",
|
||||
"persian": "波斯语"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "授权",
|
||||
"buttons": "按钮",
|
||||
"timelines": "时间线",
|
||||
"dashboard": "仪表板",
|
||||
"billing": "计费",
|
||||
"login": "登录",
|
||||
"signUp": "注册",
|
||||
"preferences": "偏好",
|
||||
"payments": "支付",
|
||||
"pricing-plans": "定价计划",
|
||||
"login-singup": "登录/注册",
|
||||
"404": "404 页面",
|
||||
"faq": "常见问题解答"
|
||||
},
|
||||
"messages": {
|
||||
"all": "查看所有消息",
|
||||
"new": "来自 {name} 的新消息",
|
||||
"mark_as_read": "标记为已读"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "需要Web开发帮助吗?请联系我们。",
|
||||
"repository": "GitHub 仓库"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "查看所有通知",
|
||||
"mark_as_read": "标为已读",
|
||||
"sentMessage": "{name} 给你发了一条消息",
|
||||
"uploadedZip": "{name} 上传了一个新的 Zip 文件 {type}",
|
||||
"startedTopic": "{name} 开始了一个新话题"
|
||||
},
|
||||
"user": {
|
||||
"language": "修改语言",
|
||||
"logout": "登出",
|
||||
"logout": "登出",
|
||||
"profile": "我的资料",
|
||||
"settings": "设置",
|
||||
"billing": "账单",
|
||||
"faq": "常见问题",
|
||||
"helpAndSupport": "帮助与支持",
|
||||
"projects": "项目",
|
||||
"account": "账户",
|
||||
"explore": "探索"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "基本型",
|
||||
"icons": "图标",
|
||||
"selectable": "可选择",
|
||||
"editable": "可编辑",
|
||||
"advanced": "高级"
|
||||
},
|
||||
"chat": {
|
||||
"title": "聊天"
|
||||
},
|
||||
"cards": {
|
||||
"cards": "卡片",
|
||||
"fixed": "固定的",
|
||||
"floating": "浮动的",
|
||||
"contentText": "独特的斑马条纹使它们成为人们最熟悉的动物之一。",
|
||||
"rowHeight": "行高",
|
||||
"title": {
|
||||
"dark": "暗色背景",
|
||||
"bright": "亮色卡片",
|
||||
"titleOnImageNoOverlay": "图像上的标题,但没有叠加",
|
||||
"normal": "标准卡",
|
||||
"overlayAndTextOnImage": "图像上有覆盖和文本的卡片",
|
||||
"stripeNoImage": "无图像条纹卡"
|
||||
},
|
||||
"button": {
|
||||
"main": "主要",
|
||||
"cancel": "取消"
|
||||
},
|
||||
"link": {
|
||||
"edit": "编辑",
|
||||
"setAsDefault": "设为默认",
|
||||
"delete": "删除",
|
||||
"traveling": "Traveling",
|
||||
"france": "法国",
|
||||
"review": "评论",
|
||||
"feedback": "反馈信息",
|
||||
"readFull": "阅读全文",
|
||||
"secondaryAction": "第二行为",
|
||||
"action1": "行为 1",
|
||||
"action2": "行为 2"
|
||||
}
|
||||
},
|
||||
"helpAndSupport": "帮助与支持",
|
||||
"aboutVuesticAdmin": "关于 Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "搜索..."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "Acepto",
|
||||
"createAccount": "Crear cuenta",
|
||||
"createNewAccount": "Crear cuenta nueva",
|
||||
"email": "Email",
|
||||
"login": "Iniciar sesión",
|
||||
"password": "Contraseña",
|
||||
"recover_password": "Recuperar contraseña",
|
||||
"sign_up": "Registrar",
|
||||
"keep_logged_in": "Mantenerme conectado",
|
||||
"termsOfUse": "Términos de uso",
|
||||
"reset_password": "Restablecer contraseña"
|
||||
},
|
||||
"404": {
|
||||
"title": "Esta página se ha ido a pescar",
|
||||
"text": "Si crees que esto no es correcto, por favor envíanos un mensaje a ",
|
||||
"back_button": "Volver al tablero"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "Estilos de texto primarios",
|
||||
"secondary": "Estilos de texto secundarios"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "Versiones",
|
||||
"setupRemoteConnections": "Configurar conexiones remotas",
|
||||
"currentVisitors": "Visitantes actuales",
|
||||
"charts": {
|
||||
"trendyTrends": "Tendencias modernas",
|
||||
"showInMoreDetail": "Mostrar en más detalle",
|
||||
"showInLessDetail": "Mostrar en menos detalle",
|
||||
"loadingSpeed": "Velocidad de carga",
|
||||
"topContributors": "Principales contribuyentes",
|
||||
"showNextFive": "Mostrar los siguientes cinco",
|
||||
"commits": "Commit"
|
||||
},
|
||||
"info": {
|
||||
"componentRichTheme": "Tema rico en componentes",
|
||||
"completedPullRequests": "Solicitudes de pull completadas",
|
||||
"users": "Usuarios",
|
||||
"points": "Puntos",
|
||||
"units": "Unidades",
|
||||
"exploreGallery": "Explorar galería",
|
||||
"viewLibrary": "Ver biblioteca",
|
||||
"commits": "Commits",
|
||||
"components": "Componentes",
|
||||
"teamMembers": "Miembros del equipo"
|
||||
},
|
||||
"tabs": {
|
||||
"overview": {
|
||||
"title": "Visión general",
|
||||
"built": "Construido con el framework Vue.js",
|
||||
"free": "Totalmente gratuito para todos",
|
||||
"fresh": "Diseño fresco y nítido",
|
||||
"mobile": "Responsivo y optimizado para móviles",
|
||||
"components": "Toneladas de componentes útiles",
|
||||
"nojQuery": "Totalmente libre de jQuery"
|
||||
},
|
||||
"billingAddress": {
|
||||
"title": "Dirección de facturación",
|
||||
"personalInfo": "Información personal",
|
||||
"firstName": "Nombre y apellido",
|
||||
"email": "Email",
|
||||
"address": "Dirección",
|
||||
"companyInfo": "Información de la empresa",
|
||||
"city": "Ciudad",
|
||||
"country": "País",
|
||||
"infiniteConnections": "Conexiones infinitas",
|
||||
"addConnection": "Añadir conexión"
|
||||
},
|
||||
"bankDetails": {
|
||||
"title": "Detalles del banco",
|
||||
"detailsFields": "Campos de detalles",
|
||||
"bankName": "Nombre del banco",
|
||||
"accountName": "Nombre de la cuenta",
|
||||
"sortCode": "Código de ordenación",
|
||||
"accountNumber": "Número de cuenta",
|
||||
"notes": "Notas",
|
||||
"sendDetails": "Enviar detalles"
|
||||
}
|
||||
},
|
||||
"navigationLayout": "Diseño de navegación",
|
||||
"topBarButton": "Barra superior",
|
||||
"sideBarButton": "Barra lateral"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "Portugués brasileño",
|
||||
"english": "Inglés",
|
||||
"spanish": "Español",
|
||||
"simplified_chinese": "Chino simplificado",
|
||||
"persian": "Persa"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "Autenticación",
|
||||
"buttons": "Botones",
|
||||
"timelines": "Líneas de tiempo",
|
||||
"dashboard": "Tablero",
|
||||
"billing": "Facturación",
|
||||
"login": "Iniciar sesión",
|
||||
"preferences": "Preferencias",
|
||||
"payments": "Pagos",
|
||||
"pricing-plans": "Planes de precios",
|
||||
"login-singup": "Iniciar sesión/Registrarse",
|
||||
"404": "Páginas 404",
|
||||
"faq": "FAQ"
|
||||
},
|
||||
"messages": {
|
||||
"all": "Ver todos los mensajes",
|
||||
"new": "Nuevos mensajes de {name}",
|
||||
"mark_as_read": "Marcar como leído"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "Preguntas sobre desarrollo web:",
|
||||
"repository": "Repositorio GitHub"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "Ver todas las notificaciones",
|
||||
"mark_as_read": "Marcar como leída",
|
||||
"sentMessage": "{name} te envió un mensaje",
|
||||
"uploadedZip": "{name} subió un archivo Zip con {type}",
|
||||
"startedTopic": "{name} inició un nuevo tema"
|
||||
},
|
||||
"user": {
|
||||
"language": "Cambiar Idioma",
|
||||
"logout": "Cerrar sesión",
|
||||
"profile": "Mi Perfil",
|
||||
"settings": "Configuración",
|
||||
"billing": "Facturación",
|
||||
"faq": "Preguntas Frecuentes",
|
||||
"helpAndSupport": "Ayuda & Soporte",
|
||||
"projects": "Proyectos",
|
||||
"account": "Cuenta",
|
||||
"explore": "Explorar"
|
||||
},
|
||||
"helpAndSupport": "Ayuda y Soporte",
|
||||
"aboutVuesticAdmin": "Acerca de Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "Buscar..."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "I agree to",
|
||||
"createAccount": "Create account",
|
||||
"createNewAccount": "Create New Account",
|
||||
"email": "Email",
|
||||
"login": "Login",
|
||||
"password": "Password",
|
||||
"recover_password": "Recover password",
|
||||
"sign_up": "Sign Up",
|
||||
"keep_logged_in": "Keep me logged in",
|
||||
"termsOfUse": "Terms of Use.",
|
||||
"reset_password": "Reset password"
|
||||
},
|
||||
"404": {
|
||||
"title": "This page’s gone fishing.",
|
||||
"text": "If you feel that it’s not right, please send us a message at ",
|
||||
"back_button": "Back to dashboard"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "Primary text styles",
|
||||
"secondary": "Secondary text styles"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "Versions",
|
||||
"setupRemoteConnections": "Setup Remote Connections",
|
||||
"currentVisitors": "Current Visitors",
|
||||
"navigationLayout": "navigation layout",
|
||||
"topBarButton": "Top Bar",
|
||||
"sideBarButton": "Side Bar"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "Português",
|
||||
"english": "English",
|
||||
"spanish": "Spanish",
|
||||
"simplified_chinese": "Simplified Chinese",
|
||||
"persian": "Persian"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "Auth",
|
||||
"buttons": "Buttons",
|
||||
"timelines": "Timelines",
|
||||
"dashboard": "Dashboard",
|
||||
"billing": "Billing",
|
||||
"login": "Login",
|
||||
"preferences": "Account preferences",
|
||||
"payments": "Payments",
|
||||
"settings": "Application settings",
|
||||
"pricing-plans": "Pricing plans",
|
||||
"payment-methods": "Payment methods",
|
||||
"signup": "Signup",
|
||||
"recover-password": "Recover password",
|
||||
"recover-password-email": "Recover password email",
|
||||
"404": "404",
|
||||
"faq": "FAQ",
|
||||
"users": "Users",
|
||||
"projects": "Projects"
|
||||
},
|
||||
"messages": {
|
||||
"all": "See all messages",
|
||||
"new": "New messages from {name}",
|
||||
"mark_as_read": "Mark As Read"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "Web development inquiries:",
|
||||
"repository": "GitHub Repo"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "See all notifications",
|
||||
"less": "See less notifications",
|
||||
"mark_as_read": "Mark as read",
|
||||
"sentMessage": "sent you a message",
|
||||
"uploadedZip": "uploaded a new Zip file with {type}",
|
||||
"startedTopic": "started a new topic"
|
||||
},
|
||||
"user": {
|
||||
"language": "Change language",
|
||||
"logout": "Logout",
|
||||
"profile": "Profile",
|
||||
"settings": "Settings",
|
||||
"billing": "Billing",
|
||||
"faq": "FAQ",
|
||||
"helpAndSupport": "Help & support",
|
||||
"projects": "Projects",
|
||||
"account": "Account",
|
||||
"explore": "Explore"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "Basic",
|
||||
"icons": "Icons",
|
||||
"selectable": "Selectable",
|
||||
"editable": "Editable",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Chat",
|
||||
"sendButton": "Send"
|
||||
},
|
||||
"spacingPlayground": {
|
||||
"value": "Value",
|
||||
"margin": "Margin",
|
||||
"padding": "Padding"
|
||||
},
|
||||
"spacing": {
|
||||
"title": "Spacing"
|
||||
},
|
||||
"cards": {
|
||||
"cards": "Cards",
|
||||
"fixed": "Fixed",
|
||||
"floating": "Floating",
|
||||
"contentText": "The unique stripes of zebras make them one of the animals most familiar to people.",
|
||||
"contentTextLong": "The unique stripes of zebras make them one of the animals most familiar to people. They occur in a variety of habitats, such as grasslands, savannas, woodlands, thorny scrublands, mountains, and coastal hills. Various anthropogenic factors have had a severe impact on zebra populations, in particular hunting for skins and habitat destruction. Grévy's zebra and the mountain zebra are endangered. While plains zebras are much more plentiful, one subspecies, the quagga.",
|
||||
"rowHeight": "Row height",
|
||||
"title": {
|
||||
"default": "Default",
|
||||
"withControls": "With controls",
|
||||
"customHeader": "Custom header",
|
||||
"withoutHeader": "Without header",
|
||||
"withImage": "With Image",
|
||||
"withTitleOnImage": "With title on image",
|
||||
"withCustomTitleOnImage": "With custom title on image",
|
||||
"withStripe": "With stripe",
|
||||
"withBackground": "With background"
|
||||
},
|
||||
"button": {
|
||||
"main": "Main",
|
||||
"cancel": "Cancel",
|
||||
"showMore": "Show More",
|
||||
"readMore": "Show More"
|
||||
},
|
||||
"link": {
|
||||
"edit": "Edit",
|
||||
"setAsDefault": "Set as default",
|
||||
"delete": "Delete",
|
||||
"traveling": "Traveling",
|
||||
"france": "France",
|
||||
"review": "Review",
|
||||
"feedback": "Leave feedback",
|
||||
"readFull": "Read full article",
|
||||
"secondaryAction": "Secondary action",
|
||||
"action1": "Action 1",
|
||||
"action2": "Action 2"
|
||||
}
|
||||
},
|
||||
"colors": {
|
||||
"themeColors": "Theme Colors",
|
||||
"extraColors": "Extra Colors",
|
||||
"gradients": {
|
||||
"basic": {
|
||||
"title": "Button Gradients"
|
||||
},
|
||||
"hovered": {
|
||||
"title": "Hovered Button Gradients",
|
||||
"text": "Lighten 15% applied to an original style (gradient or flat color) for hover state."
|
||||
},
|
||||
"pressed": {
|
||||
"title": "Pressed Button Gradients",
|
||||
"text": "Darken 15% applied to an original style (gradient or flat color) for pressed state."
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"alignment": "Tabs alignment",
|
||||
"overflow": "Tabs overflow",
|
||||
"hidden": "Tabs with hidden slider",
|
||||
"grow": "Tabs grow"
|
||||
},
|
||||
"helpAndSupport": "Help & support",
|
||||
"aboutVuesticAdmin": "About Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "Search..."
|
||||
},
|
||||
"buttonSelect": {
|
||||
"dark": "Dark",
|
||||
"light": "Light"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "با شرایط استفاده موافقم.",
|
||||
"createAccount": "ساخت حساب کاربری",
|
||||
"createNewAccount": "ساخت حساب کاربری تازه",
|
||||
"email": "رایانامه",
|
||||
"login": "ورود",
|
||||
"password": "گذرواژه",
|
||||
"recover_password": "فراموشی رمز عبور",
|
||||
"sign_up": "ساخت حساب کاربری",
|
||||
"keep_logged_in": "مرا در این مرورگر بخاطر بسپار",
|
||||
"termsOfUse": "شرایط استفاده",
|
||||
"reset_password": "بازنشانی گذرواژه"
|
||||
},
|
||||
"404": {
|
||||
"title": "این صفحه رفته گل بچینه :)",
|
||||
"text": "اگر فکر میکنید چیزی درست نیست برای ما پیام بفرستید.",
|
||||
"back_button": "بازگشت به پیشخوان"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "سبک های متن اصلی",
|
||||
"secondary": "سبکه های متن ثانوی"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "ورژن ها",
|
||||
"setupRemoteConnections": "راه اندازی اتصالات از راه دور",
|
||||
"currentVisitors": "بازکنندگان فعلی",
|
||||
"charts": {
|
||||
"trendyTrends": "ترند های روز",
|
||||
"showInMoreDetail": "نمایش جزییات بیشتر",
|
||||
"showInLessDetail": "نمایش جزییات کمتر",
|
||||
"loadingSpeed": "سرعت بارگزاری",
|
||||
"topContributors": "مشارکت کنندگان برتر",
|
||||
"showNextFive": "نمایش پنج تای بعدی",
|
||||
"commits": "کامیت ها"
|
||||
},
|
||||
"info": {
|
||||
"componentRichTheme": "تم غنی از کامپوننت",
|
||||
"completedPullRequests": "درخواست های کشیدن کامل شده",
|
||||
"users": "کاربران",
|
||||
"points": "امتیازات",
|
||||
"units": "واحد ها",
|
||||
"exploreGallery": "گشت و گذار در گالری",
|
||||
"viewLibrary": "مشاهده کتابخانه",
|
||||
"commits": "کامیت ها",
|
||||
"components": "کامپوننت ها",
|
||||
"teamMembers": "اعضای تیم"
|
||||
},
|
||||
"tabs": {
|
||||
"overview": {
|
||||
"title": "نمای کلی",
|
||||
"built": "ساخته شده با فریم ورک Vue.js",
|
||||
"free": "کاملا رایگان برای همه",
|
||||
"fresh": "طراحی تازه و باحال",
|
||||
"mobile": "واکنش گرا و بهینه سازی برای موبایل",
|
||||
"components": "پر از مولفه های باحال",
|
||||
"nojQuery": "کاملا بدون jQuery"
|
||||
},
|
||||
"billingAddress": {
|
||||
"title": "نشانی صورتحساب",
|
||||
"personalInfo": "اطلاعات شخصی",
|
||||
"firstName": "نام و نام خانوادگی",
|
||||
"email": "رایانما",
|
||||
"address": "نشانی",
|
||||
"companyInfo": "اطلاعات شرکت",
|
||||
"city": "شهر",
|
||||
"country": "کشور",
|
||||
"infiniteConnections": "اتصالات نامحدود",
|
||||
"addConnection": "افزودن اتصال"
|
||||
},
|
||||
"bankDetails": {
|
||||
"title": "اطلاعات بانکی",
|
||||
"detailsFields": "فیلد های جزئیات",
|
||||
"bankName": "نام بانک",
|
||||
"accountName": "نام حساب",
|
||||
"sortCode": "کد مرتب سازی",
|
||||
"accountNumber": "شماره حساب",
|
||||
"notes": "یادداشت ها",
|
||||
"sendDetails": "ارسال جزئیات"
|
||||
}
|
||||
},
|
||||
"navigationLayout": "چیدمان ناوبری",
|
||||
"topBarButton": "دکمه نوار بالا",
|
||||
"sideBarButton": "دکمه نوار کناری"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "پرتغالی برزیل",
|
||||
"english": "انگلیسی",
|
||||
"spanish": "اسپانیایی",
|
||||
"simplified_chinese": "چینی ساده شده",
|
||||
"persian": "فارسی"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "احراز هویت",
|
||||
"buttons": "دکمه ها",
|
||||
"timelines": "جدول زمانی",
|
||||
"dashboard": "داشبورد",
|
||||
"billing": "صورتحساب",
|
||||
"login": "ورود",
|
||||
"signUp": "ثبت نام",
|
||||
"preferences": "ترجیحات",
|
||||
"payments": "پرداخت ها",
|
||||
"pricing-plans": "طرح های قیمت گذاری",
|
||||
"login-singup": "ورود/ثبت نام",
|
||||
"404": "صفحات 404",
|
||||
"faq": "سوالات متداول"
|
||||
},
|
||||
"messages": {
|
||||
"all": "مشاهده تمام پیام ها",
|
||||
"new": "پیام های جدید از {name}",
|
||||
"mark_as_read": "علامت زدن به عنوان خوانده شده"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "پرسش های مربوط به توسعه وب:",
|
||||
"repository": "مخزن گیت هاب"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "مشاهده تمام اعلان ها",
|
||||
"mark_as_read": "علامت زدن به عنوان خوانده شده",
|
||||
"sentMessage": "پیامی برای شما فرستاد",
|
||||
"uploadedZip": "یک فایل زیپ جدید با {type} آپلود کرد",
|
||||
"startedTopic": "یک موضوع جدید را شروع کرد"
|
||||
},
|
||||
"user": {
|
||||
"language": "تغییر زبان",
|
||||
"logout": "خروج",
|
||||
"profile": "پروفایل من",
|
||||
"settings": "تنظیمات",
|
||||
"billing": "صورتحساب",
|
||||
"faq": "سؤالات متداول",
|
||||
"helpAndSupport": "کمک و پشتیبانی",
|
||||
"projects": "پروژه ها",
|
||||
"account": "حساب",
|
||||
"explore": "کاوش"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "اصلی",
|
||||
"icons": "آيکون ها",
|
||||
"selectable": "قابل انتخاب",
|
||||
"editable": "قابل ویرایش",
|
||||
"advanced": "پیشرفته"
|
||||
},
|
||||
"chat": {
|
||||
"title": "گفتگو"
|
||||
},
|
||||
"spacingPlayground": {
|
||||
"value": "مقدار",
|
||||
"margin": "حاشیه",
|
||||
"padding": "پدینگ"
|
||||
},
|
||||
"cards": {
|
||||
"cards": "کارت ها",
|
||||
"fixed": "ثابت",
|
||||
"floating": "شناور",
|
||||
"contentText": "از قیافت معلومه خیلی وقته روی صندلی نشستی. کپک نزنی! پاشو یه تکونی بده ",
|
||||
"contentTextLong": "یک برنامه نویس حتما نباید سیگار دستش باشد تا جامعه متوجه بشود که او برنامه نویس است. مشخص نیست این تصور غلط از کجا و به چه شکل به وجود آمد اما اصلا درست نیست. برنامه نویس\u200Cهای بزرگ و موفق غیرسیگاری زیاد هستند. اشخاصی مثل بیل گیتس، ریچارد استالمن یا علی شریفی نیستانی که در دنیای برنامه نویسی کاملا شناخته شده می\u200Cباشند و سیگاری نیستند. در مقابل بعضی از برنامه نویس\u200Cها هم سیگار می\u200Cکشند و این موضوع کاملا به خود شخص برمی\u200Cگردد",
|
||||
"rowHeight": "ارتفاع سطر",
|
||||
"title": {
|
||||
"default": "پیش فرض",
|
||||
"withControls": "همراه کنترل",
|
||||
"customHeader": "سربرگ سفارشی",
|
||||
"withoutHeader": "بدون سربرگ",
|
||||
"withImage": "همراه تصویر",
|
||||
"withTitleOnImage": "همراه عنوان و بدون تصویر",
|
||||
"withCustomTitleOnImage": "بدون تصویر یا عنوان",
|
||||
"withStripe": "همراه نوار",
|
||||
"withBackground": "همراه پس زمینه"
|
||||
},
|
||||
"button": {
|
||||
"main": "اصلی",
|
||||
"cancel": "لغو"
|
||||
},
|
||||
"link": {
|
||||
"edit": "ویرایش",
|
||||
"setAsDefault": "تنظیم به عنوان پیش فرض",
|
||||
"delete": "حذف",
|
||||
"traveling": "مسافرت",
|
||||
"france": "فرانسیه",
|
||||
"review": "بررسی",
|
||||
"feedback": "ترک کردن بازخورد",
|
||||
"readFull": "خواندن تمام مقالات",
|
||||
"secondaryAction": "افدام دوم",
|
||||
"action1": "اقدام یک",
|
||||
"action2": "اقدام دو"
|
||||
}
|
||||
},
|
||||
"helpAndSupport": "کمک و پشتیبانی",
|
||||
"aboutVuesticAdmin": "درباره Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "جستجو..."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
{
|
||||
"auth": {
|
||||
"agree": "Я согласен",
|
||||
"createAccount": "Создать аккаунт",
|
||||
"createNewAccount": "Создать новый аккаунт",
|
||||
"email": "Email",
|
||||
"login": "Логин",
|
||||
"password": "Пароль",
|
||||
"recover_password": "Восстановить пароль",
|
||||
"sign_up": "Регистрация",
|
||||
"keep_logged_in": "Запомнить",
|
||||
"termsOfUse": "Terms of Use.",
|
||||
"reset_password": "Сбросить пароль"
|
||||
},
|
||||
"404": {
|
||||
"title": "Эта страница последняя.",
|
||||
"text": "Если вы счиате, что это ошибка, напишите нам ",
|
||||
"back_button": "На главную"
|
||||
},
|
||||
"typography": {
|
||||
"primary": "Primary text styles",
|
||||
"secondary": "Secondary text styles"
|
||||
},
|
||||
"dashboard": {
|
||||
"versions": "Versions",
|
||||
"setupRemoteConnections": "Setup Remote Connections",
|
||||
"currentVisitors": "Current Visitors",
|
||||
"navigationLayout": "navigation layout",
|
||||
"topBarButton": "Top Bar",
|
||||
"sideBarButton": "Side Bar"
|
||||
},
|
||||
"language": {
|
||||
"brazilian_portuguese": "Português",
|
||||
"english": "English",
|
||||
"spanish": "Spanish",
|
||||
"simplified_chinese": "Simplified Chinese",
|
||||
"persian": "Persian"
|
||||
},
|
||||
"menu": {
|
||||
"auth": "Auth",
|
||||
"buttons": "Buttons",
|
||||
"timelines": "Timelines",
|
||||
"dashboard": "Dashboard",
|
||||
"billing": "Billing",
|
||||
"login": "Login",
|
||||
"preferences": "Account preferences",
|
||||
"payments": "Payments",
|
||||
"settings": "Application settings",
|
||||
"pricing-plans": "Pricing plans",
|
||||
"payment-methods": "Payment methods",
|
||||
"signup": "Signup",
|
||||
"recover-password": "Recover password",
|
||||
"recover-password-email": "Recover password email",
|
||||
"404": "404",
|
||||
"faq": "FAQ",
|
||||
"users": "Users",
|
||||
"projects": "Projects"
|
||||
},
|
||||
"messages": {
|
||||
"all": "See all messages",
|
||||
"new": "New messages from {name}",
|
||||
"mark_as_read": "Mark As Read"
|
||||
},
|
||||
"navbar": {
|
||||
"messageUs": "Web development inquiries:",
|
||||
"repository": "GitHub Repo"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "See all notifications",
|
||||
"less": "See less notifications",
|
||||
"mark_as_read": "Mark as read",
|
||||
"sentMessage": "sent you a message",
|
||||
"uploadedZip": "uploaded a new Zip file with {type}",
|
||||
"startedTopic": "started a new topic"
|
||||
},
|
||||
"user": {
|
||||
"language": "Change language",
|
||||
"logout": "Logout",
|
||||
"profile": "Profile",
|
||||
"settings": "Settings",
|
||||
"billing": "Billing",
|
||||
"faq": "FAQ",
|
||||
"helpAndSupport": "Help & support",
|
||||
"projects": "Projects",
|
||||
"account": "Account",
|
||||
"explore": "Explore"
|
||||
},
|
||||
"treeView": {
|
||||
"basic": "Basic",
|
||||
"icons": "Icons",
|
||||
"selectable": "Selectable",
|
||||
"editable": "Editable",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"chat": {
|
||||
"title": "Chat",
|
||||
"sendButton": "Send"
|
||||
},
|
||||
"spacingPlayground": {
|
||||
"value": "Value",
|
||||
"margin": "Margin",
|
||||
"padding": "Padding"
|
||||
},
|
||||
"spacing": {
|
||||
"title": "Spacing"
|
||||
},
|
||||
"cards": {
|
||||
"cards": "Cards",
|
||||
"fixed": "Fixed",
|
||||
"floating": "Floating",
|
||||
"contentText": "The unique stripes of zebras make them one of the animals most familiar to people.",
|
||||
"contentTextLong": "The unique stripes of zebras make them one of the animals most familiar to people. They occur in a variety of habitats, such as grasslands, savannas, woodlands, thorny scrublands, mountains, and coastal hills. Various anthropogenic factors have had a severe impact on zebra populations, in particular hunting for skins and habitat destruction. Grévy's zebra and the mountain zebra are endangered. While plains zebras are much more plentiful, one subspecies, the quagga.",
|
||||
"rowHeight": "Row height",
|
||||
"title": {
|
||||
"default": "Default",
|
||||
"withControls": "With controls",
|
||||
"customHeader": "Custom header",
|
||||
"withoutHeader": "Without header",
|
||||
"withImage": "With Image",
|
||||
"withTitleOnImage": "With title on image",
|
||||
"withCustomTitleOnImage": "With custom title on image",
|
||||
"withStripe": "With stripe",
|
||||
"withBackground": "With background"
|
||||
},
|
||||
"button": {
|
||||
"main": "Main",
|
||||
"cancel": "Cancel",
|
||||
"showMore": "Show More",
|
||||
"readMore": "Show More"
|
||||
},
|
||||
"link": {
|
||||
"edit": "Edit",
|
||||
"setAsDefault": "Set as default",
|
||||
"delete": "Delete",
|
||||
"traveling": "Traveling",
|
||||
"france": "France",
|
||||
"review": "Review",
|
||||
"feedback": "Leave feedback",
|
||||
"readFull": "Read full article",
|
||||
"secondaryAction": "Secondary action",
|
||||
"action1": "Action 1",
|
||||
"action2": "Action 2"
|
||||
}
|
||||
},
|
||||
"colors": {
|
||||
"themeColors": "Theme Colors",
|
||||
"extraColors": "Extra Colors",
|
||||
"gradients": {
|
||||
"basic": {
|
||||
"title": "Button Gradients"
|
||||
},
|
||||
"hovered": {
|
||||
"title": "Hovered Button Gradients",
|
||||
"text": "Lighten 15% applied to an original style (gradient or flat color) for hover state."
|
||||
},
|
||||
"pressed": {
|
||||
"title": "Pressed Button Gradients",
|
||||
"text": "Darken 15% applied to an original style (gradient or flat color) for pressed state."
|
||||
}
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"alignment": "Tabs alignment",
|
||||
"overflow": "Tabs overflow",
|
||||
"hidden": "Tabs with hidden slider",
|
||||
"grow": "Tabs grow"
|
||||
},
|
||||
"helpAndSupport": "Help & support",
|
||||
"aboutVuesticAdmin": "About Vuestic Admin",
|
||||
"search": {
|
||||
"placeholder": "Search..."
|
||||
},
|
||||
"buttonSelect": {
|
||||
"dark": "Dark",
|
||||
"light": "Light"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<VaLayout
|
||||
:top="{ fixed: true, order: 2 }"
|
||||
:left="{ fixed: true, absolute: breakpoints.mdDown, order: 1, overlay: breakpoints.mdDown && !isSidebarMinimized }"
|
||||
@leftOverlayClick="isSidebarMinimized = true"
|
||||
>
|
||||
<template #top>
|
||||
<AppNavbar :is-mobile="isMobile" />
|
||||
</template>
|
||||
|
||||
<template #left>
|
||||
<AppSidebar :minimized="isSidebarMinimized" :animated="!isMobile" :mobile="isMobile" />
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
<AppLayoutNavigation v-if="!isMobile" class="p-4" />
|
||||
<main class="p-4 pt-0">
|
||||
<article>
|
||||
<RouterView />
|
||||
</article>
|
||||
</main>
|
||||
</template>
|
||||
</VaLayout>
|
||||
</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 { 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'
|
||||
|
||||
const GlobalStore = useGlobalStore()
|
||||
|
||||
const breakpoints = useBreakpoint()
|
||||
|
||||
const sidebarWidth = ref('16rem')
|
||||
const sidebarMinimizedWidth = ref(undefined)
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', onResize)
|
||||
onResize()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', onResize)
|
||||
})
|
||||
|
||||
onBeforeRouteUpdate(() => {
|
||||
if (breakpoints.mdDown) {
|
||||
// Collapse sidebar after route change for Mobile
|
||||
isSidebarMinimized.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const isFullScreenSidebar = computed(() => isTablet.value && !isSidebarMinimized.value)
|
||||
|
||||
const onCloseSidebarButtonClick = () => {
|
||||
isSidebarMinimized.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// Prevent icon jump on animation
|
||||
.va-sidebar {
|
||||
width: unset !important;
|
||||
min-width: unset !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<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"
|
||||
style="width: 35vw"
|
||||
to="/"
|
||||
aria-label="Visit homepage"
|
||||
>
|
||||
<VuesticLogo :height="28" start="#FFF" />
|
||||
</RouterLink>
|
||||
</template>
|
||||
<template #content>
|
||||
<main class="h-full flex items-center justify-center mx-auto max-w-[420px]">
|
||||
<RouterView />
|
||||
</main>
|
||||
</template>
|
||||
</VaLayout>
|
||||
|
||||
<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]">
|
||||
<div class="flex flex-col items-start">
|
||||
<RouterLink class="py-4" to="/" aria-label="Visit homepage">
|
||||
<VuesticLogo class="mb-2" start="#0E41C9" />
|
||||
</RouterLink>
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
</VaLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useBreakpoint } from 'vuestic-ui'
|
||||
import VuesticLogo from '../components/VuesticLogo.vue'
|
||||
|
||||
const breakpoint = useBreakpoint()
|
||||
</script>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
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'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(stores)
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.use(createVuestic({ config: vuesticGlobalConfig }))
|
||||
|
||||
if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
||||
app.use(
|
||||
createGtm({
|
||||
id: import.meta.env.VITE_APP_GTM_KEY,
|
||||
debug: false,
|
||||
vueRouter: router,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
app.mount('#app')
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts" setup>
|
||||
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)]">
|
||||
<RouterLink to="/">
|
||||
<VuesticLogo :gradient="false" class="my-8 h-5" />
|
||||
</RouterLink>
|
||||
|
||||
<div class="flex flex-col items-center gap-6 px-4 my-8">
|
||||
<NotFoundImage />
|
||||
<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.
|
||||
</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"
|
||||
>Create a GitHub issue
|
||||
</VaButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +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'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="page-title font-bold">Dashboard</h1>
|
||||
<section class="flex flex-col 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">
|
||||
<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>
|
||||
</section>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<DataSectionItem
|
||||
v-for="metric in dashboardMetrics"
|
||||
:key="metric.id"
|
||||
:title="metric.title"
|
||||
:value="metric.value"
|
||||
:change-text="metric.changeText"
|
||||
:up="metric.changeDirection === 'up'"
|
||||
:icon-background="metric.iconBackground"
|
||||
:icon-color="metric.iconColor"
|
||||
>
|
||||
<template #icon>
|
||||
<VaIcon :name="metric.icon" size="large" />
|
||||
</template>
|
||||
</DataSectionItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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
|
||||
}
|
||||
|
||||
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: '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: 'newProfit',
|
||||
title: 'New profit',
|
||||
value: '27%',
|
||||
icon: 'mso-grade',
|
||||
changeText: '4%',
|
||||
changeDirection: 'up',
|
||||
iconBackground: getColor('warning'),
|
||||
iconColor: getColor('on-warning'),
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<VaCard>
|
||||
<VaCardContent>
|
||||
<section>
|
||||
<header class="flex items-center justify-between">
|
||||
<div class="text-lg font-semibold grow">{{ value }}</div>
|
||||
<div
|
||||
class="p-1 rounded"
|
||||
:style="{
|
||||
backgroundColor: iconBackground,
|
||||
color: iconColor,
|
||||
}"
|
||||
>
|
||||
<slot name="icon"></slot>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<p class="mb-2">{{ title }}</p>
|
||||
<p class="text-xs text-secondary">
|
||||
<span :class="changeClass">
|
||||
<template v-if="up">↑</template>
|
||||
<template v-else>↓</template>
|
||||
{{ changeText }}
|
||||
</span>
|
||||
since last month
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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
|
||||
}>()
|
||||
|
||||
const changeClass = computed(() => ({
|
||||
'text-success': props.up,
|
||||
'text-red-600': !props.up,
|
||||
}))
|
||||
</script>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<VaCard>
|
||||
<VaCardTitle>
|
||||
<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">
|
||||
<VaIcon name="mso-attach_money" color="#fff" size="large" />
|
||||
</div>
|
||||
<section>
|
||||
<div class="text-xl font-bold mb-2">$6,820</div>
|
||||
<p class="text-xs text-success">
|
||||
<VaIcon name="arrow_upward" />
|
||||
25.36%
|
||||
<span class="text-secondary"> last month</span>
|
||||
</p>
|
||||
</section>
|
||||
<div class="w-full flex items-center">
|
||||
<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'
|
||||
|
||||
const chartData = useChartData(lineChartData)
|
||||
|
||||
const options: ChartOptions<'line'> = {
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
grid: {
|
||||
display: false, // Disable X-axis grid lines ("net")
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
grid: {
|
||||
display: false, // Disable Y-axis grid lines ("net")
|
||||
},
|
||||
ticks: {
|
||||
display: false, // Hide Y-axis values
|
||||
},
|
||||
},
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<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'
|
||||
|
||||
const columns = defineVaDataTableColumns([
|
||||
{ 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 { 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]
|
||||
}
|
||||
</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>
|
||||
</VaCardTitle>
|
||||
<VaCardContent>
|
||||
<div v-if="projects.length > 0">
|
||||
<VaDataTable
|
||||
v-model:sort-by="sorting.sortBy"
|
||||
v-model:sorting-order="sorting.sortingOrder"
|
||||
:items="projects"
|
||||
:columns="columns"
|
||||
:loading="isLoading"
|
||||
>
|
||||
<template #cell(project_name)="{ rowData }">
|
||||
<div class="ellipsis max-w-[230px] lg:max-w-[450px]">
|
||||
{{ rowData.project_name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(project_owner)="{ rowData }">
|
||||
<div class="flex items-center gap-2 ellipsis max-w-[230px]">
|
||||
<UserAvatar :user="rowData.project_owner" size="small" />
|
||||
{{ rowData.project_owner.fullname }}
|
||||
</div>
|
||||
</template>
|
||||
<template #cell(team)="{ rowData: project }">
|
||||
<VaAvatarGroup
|
||||
size="small"
|
||||
:options="
|
||||
(project as Project).team.map((user) => ({
|
||||
label: user.fullname,
|
||||
src: user.avatar,
|
||||
fallbackText: user.fullname[0],
|
||||
color: avatarColor(user.fullname),
|
||||
}))
|
||||
"
|
||||
:max="2"
|
||||
/>
|
||||
</template>
|
||||
<template #cell(status)="{ rowData: project }">
|
||||
<ProjectStatusBadge :status="project.status" />
|
||||
</template>
|
||||
</VaDataTable>
|
||||
</div>
|
||||
<div v-else class="p-4 flex justify-center items-center text-[var(--va-secondary)]">No projects</div>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<VaCard>
|
||||
<VaCardTitle class="flex justify-between">
|
||||
<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" />
|
||||
|
||||
<VaButton preset="primary" size="small" @click="exportAsCSV"> Export </VaButton>
|
||||
</div>
|
||||
|
||||
<VaDataTable
|
||||
class="region-revenue-table"
|
||||
:columns="[
|
||||
{ key: 'name', label: 'Top Region' },
|
||||
{ key: 'revenue', label: 'Revenue', align: 'right' },
|
||||
]"
|
||||
:items="data"
|
||||
>
|
||||
<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'
|
||||
|
||||
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: '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: 'China',
|
||||
revenueToday: '250,963',
|
||||
revenueWeek: '1,600,000',
|
||||
revenueMonth: '7,000,000',
|
||||
},
|
||||
{
|
||||
name: 'Canada',
|
||||
revenueToday: '29,415',
|
||||
revenueWeek: '180,000',
|
||||
revenueMonth: '800,000',
|
||||
},
|
||||
{
|
||||
name: 'Australia',
|
||||
revenueToday: '15,000',
|
||||
revenueWeek: '100,000',
|
||||
revenueMonth: '500,000',
|
||||
},
|
||||
{
|
||||
name: 'India',
|
||||
revenueToday: '10,000',
|
||||
revenueWeek: '50,000',
|
||||
revenueMonth: '200,000',
|
||||
},
|
||||
]
|
||||
|
||||
const exportAsCSV = () => {
|
||||
downloadAsCSV(data, 'region-revenue')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.region-revenue-table {
|
||||
::v-deep(tbody) {
|
||||
tr {
|
||||
border-top: 1px solid var(--va-background-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<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>
|
||||
</VaCardTitle>
|
||||
<VaCardContent class="flex-1 flex overflow-hidden">
|
||||
<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" />
|
||||
</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'
|
||||
|
||||
const getRevenue = (countryName: string) => {
|
||||
if (['United States of America', 'Canada', 'United Kingdom', 'China', 'Japan'].includes(countryName)) {
|
||||
return 10
|
||||
}
|
||||
|
||||
if (['Antarctica', 'Greenland'].includes(countryName)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return Math.random() * 10
|
||||
}
|
||||
|
||||
const geoJson = ref<typeof countriesGeoJSON | null>(null)
|
||||
|
||||
onMounted(async () => {
|
||||
geoJson.value = (await import('../../../../data/geo.json')).default
|
||||
})
|
||||
|
||||
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) })),
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.va-card--flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<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>
|
||||
<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>
|
||||
</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">
|
||||
<div>
|
||||
<p class="text-xl font-semibold">{{ formatMoney(totalEarnings) }}</p>
|
||||
<p class="whitespace-nowrap mt-2">Total earnings</p>
|
||||
</div>
|
||||
<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="text-secondary">Earnings this month</span>
|
||||
</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="text-secondary">Expense this month</span>
|
||||
</div>
|
||||
<div class="mt-2 text-xl font-semibold">{{ formatMoney(earningsForSelectedMonth.expenses) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<RevenueReportChart
|
||||
class="w-2/3 md:w-3/5 lg:w-3/4 h-full min-h-72 sm:min-h-32 pt-4"
|
||||
:revenues="revenues"
|
||||
:months="months"
|
||||
/>
|
||||
</VaCardContent>
|
||||
</VaCard>
|
||||
</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 {
|
||||
earningsColor,
|
||||
expensesColor,
|
||||
months,
|
||||
generateRevenues,
|
||||
getRevenuePerMonth,
|
||||
formatMoney,
|
||||
} from '../../../../data/charts/revenueChartData'
|
||||
|
||||
const revenues = generateRevenues(months)
|
||||
|
||||
const currentYear = new Date().getFullYear()
|
||||
const monthsWithCurrentYear = months.map((month) => `${month} ${currentYear}`)
|
||||
|
||||
const selectedMonth = ref(monthsWithCurrentYear[0])
|
||||
|
||||
const earningsForSelectedMonth = computed(() => getRevenuePerMonth(selectedMonth.value.split(' ')[0], revenues))
|
||||
const totalEarnings = computed(() => {
|
||||
return earningsForSelectedMonth.value.earning + earningsForSelectedMonth.value.expenses
|
||||
})
|
||||
|
||||
const exportAsCSV = () => {
|
||||
downloadAsCSV(revenues, 'revenue-report')
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div class="flex justify-center w-full h-full overflow-hidden relative">
|
||||
<canvas ref="canvas" style="max-width: 100%"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
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'
|
||||
|
||||
const { revenues, months } = defineProps<{
|
||||
months: string[]
|
||||
revenues: Revenues[]
|
||||
}>()
|
||||
|
||||
Chart.register(...registerables)
|
||||
|
||||
const BR_THICKNESS = 4
|
||||
|
||||
Chart.register([
|
||||
{
|
||||
id: 'background-color',
|
||||
beforeDatasetDraw: function (chart) {
|
||||
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
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
const doShowChart = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (canvas.value) {
|
||||
const ctx = canvas.value.getContext('2d')
|
||||
if (ctx) {
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: months,
|
||||
datasets: [
|
||||
{
|
||||
// Show relative expenses ratio
|
||||
data: revenues.map(({ earning, expenses }) => (expenses / earning) * 100),
|
||||
backgroundColor: expensesColor,
|
||||
barThickness: BR_THICKNESS,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
border: {
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function (value) {
|
||||
return formatMoney(Number(value))
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
doShowChart.value = true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
canvas {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||