├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── .stylelintrc
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── deploy.rb
├── dev.env.js
├── index.js
└── prod.env.js.example
├── docs
├── absencereport.gif
├── dashboard.gif
├── estimationsanalysis.gif
├── settings.gif
├── timelogs.gif
├── timetowork.gif
└── weeklyreports.gif
├── favicon.ico
├── index.html
├── package.json
├── src
├── App.vue
├── api
│ ├── actionEntityTable.js
│ ├── charts.js
│ ├── login.js
│ └── queryConst.js
├── assets
│ ├── 401_images
│ │ └── 401.gif
│ ├── 404_images
│ │ ├── 404.png
│ │ └── 404_cloud.png
│ ├── custom-theme
│ │ └── index.css
│ └── icons
│ │ ├── analysis.svg
│ │ ├── deadline.svg
│ │ ├── down-arrow.svg
│ │ ├── holiday.svg
│ │ ├── lg_blue.svg
│ │ └── task.svg
├── components
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── Hamburger
│ │ └── index.vue
│ ├── Pagination
│ │ └── index.vue
│ ├── ScrollPane
│ │ └── index.vue
│ ├── SvgIcon
│ │ └── index.vue
│ └── TreeTable
│ │ ├── closeFunc.js
│ │ ├── customEval.js
│ │ ├── eval.js
│ │ ├── index.vue
│ │ └── timeEntries.js
├── directive
│ ├── permission
│ │ ├── index.js
│ │ └── permission.js
│ └── waves
│ │ ├── index.js
│ │ ├── waves.css
│ │ └── waves.js
├── filters
│ └── index.js
├── icons
│ ├── index.js
│ ├── svg
│ │ ├── 404.svg
│ │ ├── admins.svg
│ │ ├── analysis.svg
│ │ ├── analysis_fon.svg
│ │ ├── border-plus.svg
│ │ ├── bug.svg
│ │ ├── calendar-date.svg
│ │ ├── calendar.svg
│ │ ├── chart.svg
│ │ ├── clipboard.svg
│ │ ├── component.svg
│ │ ├── dashboard.svg
│ │ ├── deadline.svg
│ │ ├── display.svg
│ │ ├── documentation.svg
│ │ ├── down-arrow-new-blue.svg
│ │ ├── down-arrow-new.svg
│ │ ├── down-arrow.svg
│ │ ├── drag.svg
│ │ ├── edit.svg
│ │ ├── email.svg
│ │ ├── example.svg
│ │ ├── excel.svg
│ │ ├── eye.svg
│ │ ├── form.svg
│ │ ├── guide 2.svg
│ │ ├── guide.svg
│ │ ├── hide.svg
│ │ ├── holiday-fon.svg
│ │ ├── holiday.svg
│ │ ├── icon.svg
│ │ ├── international.svg
│ │ ├── language.svg
│ │ ├── lg_blue.svg
│ │ ├── link.svg
│ │ ├── list.svg
│ │ ├── lock.svg
│ │ ├── message.svg
│ │ ├── money.svg
│ │ ├── nested.svg
│ │ ├── password.svg
│ │ ├── people.svg
│ │ ├── peoples.svg
│ │ ├── projects.svg
│ │ ├── qq.svg
│ │ ├── rocket.svg
│ │ ├── shopping.svg
│ │ ├── size.svg
│ │ ├── sofa.svg
│ │ ├── star.svg
│ │ ├── tab.svg
│ │ ├── table.svg
│ │ ├── task.svg
│ │ ├── teams.svg
│ │ ├── theme.svg
│ │ ├── time.svg
│ │ ├── tree.svg
│ │ ├── user.svg
│ │ ├── wechat.svg
│ │ ├── work.svg
│ │ ├── worked.svg
│ │ └── zip.svg
│ └── svgo.yml
├── lang
│ ├── en.js
│ └── index.js
├── main.js
├── mixins
│ ├── date.js
│ ├── dialog.js
│ ├── entities.js
│ ├── includes.js
│ ├── index.js
│ ├── pagination.js
│ ├── query.js
│ └── validationRules.js
├── permission.js
├── router
│ └── index.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── actionEntityTable.js
│ │ ├── app.js
│ │ ├── charts.js
│ │ ├── modals.js
│ │ ├── pagination.js
│ │ ├── permission.js
│ │ ├── reportsTable.js
│ │ ├── tagsView.js
│ │ └── user.js
├── styles
│ ├── btn.scss
│ ├── element-ui.scss
│ ├── fonts.scss
│ ├── index.scss
│ ├── mixin.scss
│ ├── sidebar.scss
│ ├── transition.scss
│ └── variables.scss
├── utils
│ ├── auth.js
│ ├── i18n.js
│ ├── index.js
│ ├── permission.js
│ ├── request.js
│ └── validate.js
└── views
│ ├── absence-report
│ ├── components
│ │ └── modals
│ │ │ ├── edit
│ │ │ └── index.vue
│ │ │ └── view
│ │ │ └── index.vue
│ └── index.vue
│ ├── dashboard
│ ├── admin
│ │ ├── components
│ │ │ ├── BarChart.vue
│ │ │ └── PieChart.vue
│ │ └── index.vue
│ └── index.vue
│ ├── errorPage
│ ├── 401.vue
│ └── 404.vue
│ ├── estimations-analysis
│ └── index.vue
│ ├── layout
│ ├── Layout.vue
│ ├── components
│ │ ├── AppMain.vue
│ │ ├── Navbar.vue
│ │ ├── Sidebar
│ │ │ ├── Item.vue
│ │ │ ├── SidebarItem.vue
│ │ │ └── index.vue
│ │ ├── TagsView.vue
│ │ └── index.js
│ └── mixin
│ │ └── ResizeHandler.js
│ ├── login
│ ├── authredirect.vue
│ ├── index.vue
│ └── socialsignin.vue
│ ├── redirect
│ └── index.vue
│ ├── settings
│ ├── admins
│ │ ├── adminsTable.vue
│ │ ├── components
│ │ │ └── modals
│ │ │ │ ├── edit
│ │ │ │ └── index.vue
│ │ │ │ └── view
│ │ │ │ └── index.vue
│ │ └── index.vue
│ ├── holidays
│ │ ├── components
│ │ │ └── modals
│ │ │ │ ├── edit
│ │ │ │ └── index.vue
│ │ │ │ └── view
│ │ │ │ └── index.vue
│ │ ├── filters.vue
│ │ ├── holidaysTable.vue
│ │ └── index.vue
│ ├── projects
│ │ ├── components
│ │ │ └── modals
│ │ │ │ ├── edit
│ │ │ │ └── index.vue
│ │ │ │ └── view
│ │ │ │ └── index.vue
│ │ ├── filters.vue
│ │ ├── index.vue
│ │ └── projectsTable.vue
│ ├── teams
│ │ ├── components
│ │ │ └── modals
│ │ │ │ ├── edit
│ │ │ │ └── index.vue
│ │ │ │ └── view
│ │ │ │ └── index.vue
│ │ ├── filters.vue
│ │ ├── index.vue
│ │ └── teamsTable.vue
│ └── users
│ │ ├── components
│ │ └── modals
│ │ │ ├── edit
│ │ │ └── index.vue
│ │ │ └── view
│ │ │ └── index.vue
│ │ ├── filters.vue
│ │ ├── index.vue
│ │ └── usersTable.vue
│ ├── time-logs
│ ├── components
│ │ └── modals
│ │ │ ├── edit
│ │ │ └── index.vue
│ │ │ └── view
│ │ │ └── index.vue
│ ├── filters.vue
│ ├── index.vue
│ └── time-entriesTable.vue
│ ├── time-reports
│ └── index.vue
│ └── user-reports
│ └── index.vue
├── static
├── fonts
│ ├── GILGON__.ttf
│ ├── calibre
│ │ ├── Calibre-Light.otf
│ │ ├── Calibre-Medium.otf
│ │ └── Calibre-Regular.otf
│ ├── element-icons.ttf
│ ├── element-icons.woff
│ └── poppins
│ │ ├── Poppins-Medium.woff
│ │ ├── Poppins-Medium.woff2
│ │ ├── Poppins-Regular.woff
│ │ ├── Poppins-Regular.woff2
│ │ ├── Poppins-SemiBold.woff
│ │ ├── Poppins-SemiBold.woff2
│ │ ├── Poppins-Thin.woff
│ │ ├── Poppins-Thin.woff2
│ │ ├── poppins-bold.woff
│ │ └── poppins-bold.woff2
├── icons
│ ├── border-plus.svg
│ ├── calendar-date.svg
│ ├── close.svg
│ ├── ic-calendar.svg
│ ├── ic-create.svg
│ ├── ic-delete.svg
│ ├── ic-edit.svg
│ ├── ic-time-hover.svg
│ └── ic-view.svg
└── tinymce4.7.5
│ ├── plugins
│ ├── codesample
│ │ └── css
│ │ │ └── prism.css
│ └── visualblocks
│ │ └── css
│ │ └── visualblocks.css
│ ├── skins
│ └── lightgray
│ │ ├── content.inline.min.css
│ │ ├── content.min.css
│ │ ├── fonts
│ │ ├── tinymce-mobile.woff
│ │ ├── tinymce-small.eot
│ │ ├── tinymce-small.svg
│ │ ├── tinymce-small.ttf
│ │ ├── tinymce-small.woff
│ │ ├── tinymce.eot
│ │ ├── tinymce.svg
│ │ ├── tinymce.ttf
│ │ └── tinymce.woff
│ │ ├── img
│ │ ├── anchor.gif
│ │ ├── loader.gif
│ │ ├── object.gif
│ │ └── trans.gif
│ │ ├── skin.min.css
│ │ └── skin.min.css.map
│ └── tinymce.min.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"],
12 | "env": {
13 | "development":{
14 | "plugins": ["dynamic-import-node"]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | end_of_line = lf
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | config/*.js
3 | src/assets
4 | /build/
5 | /config/
6 | /dist/
7 | /*.js
8 | /test/unit/coverage/
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | **/*.log
8 | config/prod.env.js
9 |
10 | test/unit/coverage
11 | test/e2e/reports
12 | selenium-debug.log
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 |
22 | package-lock.json
23 | .ruby-gemset
24 | .ruby-version
25 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'mina'
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | mina (1.2.3)
5 | open4 (~> 1.3.4)
6 | rake
7 | open4 (1.3.4)
8 | rake (12.3.1)
9 |
10 | PLATFORMS
11 | ruby
12 |
13 | DEPENDENCIES
14 | mina
15 |
16 | BUNDLED WITH
17 | 1.16.6
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-present PanJiaChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | const ora = require('ora')
5 | const rm = require('rimraf')
6 | const path = require('path')
7 | const chalk = require('chalk')
8 | const webpack = require('webpack')
9 | const config = require('../config')
10 | const webpackConfig = require('./webpack.prod.conf')
11 | var connect = require('connect')
12 | var serveStatic = require('serve-static')
13 |
14 | const spinner = ora(
15 | 'building for ' + process.env.env_config + ' environment...'
16 | )
17 | spinner.start()
18 |
19 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
20 | if (err) throw err
21 | webpack(webpackConfig, (err, stats) => {
22 | spinner.stop()
23 | if (err) throw err
24 | process.stdout.write(
25 | stats.toString({
26 | colors: true,
27 | modules: false,
28 | children: false,
29 | chunks: false,
30 | chunkModules: false
31 | }) + '\n\n'
32 | )
33 |
34 | if (stats.hasErrors()) {
35 | console.log(chalk.red(' Build failed with errors.\n'))
36 | process.exit(1)
37 | }
38 |
39 | console.log(chalk.cyan(' Build complete.\n'))
40 | console.log(
41 | chalk.yellow(
42 | ' Tip: built files are meant to be served over an HTTP server.\n' +
43 | " Opening index.html over file:// won't work.\n"
44 | )
45 | )
46 |
47 | if (process.env.npm_config_preview) {
48 | const port = 9526
49 | const host = 'http://localhost:' + port
50 | const basePath = config.build.assetsPublicPath
51 | const app = connect()
52 |
53 | app.use(
54 | basePath,
55 | serveStatic('./dist', {
56 | index: ['index.html', '/']
57 | })
58 | )
59 |
60 | app.listen(port, function() {
61 | console.log(
62 | chalk.green(`> Listening at http://localhost:${port}${basePath}`)
63 | )
64 | })
65 | }
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec(cmd) {
8 | return require('child_process')
9 | .execSync(cmd)
10 | .toString()
11 | .trim()
12 | }
13 |
14 | const versionRequirements = [
15 | {
16 | name: 'node',
17 | currentVersion: semver.clean(process.version),
18 | versionRequirement: packageConfig.engines.node
19 | }
20 | ]
21 |
22 | if (shell.which('npm')) {
23 | versionRequirements.push({
24 | name: 'npm',
25 | currentVersion: exec('npm --version'),
26 | versionRequirement: packageConfig.engines.npm
27 | })
28 | }
29 |
30 | module.exports = function() {
31 | const warnings = []
32 |
33 | for (let i = 0; i < versionRequirements.length; i++) {
34 | const mod = versionRequirements[i]
35 |
36 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
37 | warnings.push(
38 | mod.name +
39 | ': ' +
40 | chalk.red(mod.currentVersion) +
41 | ' should be ' +
42 | chalk.green(mod.versionRequirement)
43 | )
44 | }
45 | }
46 |
47 | if (warnings.length) {
48 | console.log('')
49 | console.log(
50 | chalk.yellow(
51 | 'To use this template, you must update following to modules:'
52 | )
53 | )
54 | console.log()
55 |
56 | for (let i = 0; i < warnings.length; i++) {
57 | const warning = warnings[i]
58 | console.log(' ' + warning)
59 | }
60 |
61 | console.log()
62 | process.exit(1)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function(_path) {
8 | const assetsSubDirectory =
9 | process.env.NODE_ENV === 'production'
10 | ? config.build.assetsSubDirectory
11 | : config.dev.assetsSubDirectory
12 |
13 | return path.posix.join(assetsSubDirectory, _path)
14 | }
15 |
16 | exports.cssLoaders = function(options) {
17 | options = options || {}
18 |
19 | const cssLoader = {
20 | loader: 'css-loader',
21 | options: {
22 | sourceMap: options.sourceMap
23 | }
24 | }
25 |
26 | const postcssLoader = {
27 | loader: 'postcss-loader',
28 | options: {
29 | sourceMap: options.sourceMap
30 | }
31 | }
32 |
33 | // generate loader string to be used with extract text plugin
34 | function generateLoaders(loader, loaderOptions) {
35 | const loaders = []
36 |
37 | // Extract CSS when that option is specified
38 | // (which is the case during production build)
39 | if (options.extract) {
40 | loaders.push(MiniCssExtractPlugin.loader)
41 | } else {
42 | loaders.push('vue-style-loader')
43 | }
44 |
45 | loaders.push(cssLoader)
46 |
47 | if (options.usePostCSS) {
48 | loaders.push(postcssLoader)
49 | }
50 |
51 | if (loader) {
52 | loaders.push({
53 | loader: loader + '-loader',
54 | options: Object.assign({}, loaderOptions, {
55 | sourceMap: options.sourceMap
56 | })
57 | })
58 | }
59 |
60 | return loaders
61 | }
62 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
63 | return {
64 | css: generateLoaders(),
65 | postcss: generateLoaders(),
66 | less: generateLoaders('less'),
67 | sass: generateLoaders('sass', {
68 | indentedSyntax: true
69 | }),
70 | scss: generateLoaders('sass'),
71 | stylus: generateLoaders('stylus'),
72 | styl: generateLoaders('stylus')
73 | }
74 | }
75 |
76 | // Generate loaders for standalone style files (outside of .vue)
77 | exports.styleLoaders = function(options) {
78 | const output = []
79 | const loaders = exports.cssLoaders(options)
80 |
81 | for (const extension in loaders) {
82 | const loader = loaders[extension]
83 | output.push({
84 | test: new RegExp('\\.' + extension + '$'),
85 | use: loader
86 | })
87 | }
88 |
89 | return output
90 | }
91 |
92 | exports.createNotifierCallback = () => {
93 | const notifier = require('node-notifier')
94 |
95 | return (severity, errors) => {
96 | if (severity !== 'error') return
97 |
98 | const error = errors[0]
99 | const filename = error.file && error.file.split('!').pop()
100 |
101 | notifier.notify({
102 | title: packageConfig.name,
103 | message: severity + ': ' + error.name,
104 | subtitle: filename || '',
105 | icon: path.join(__dirname, 'logo.png')
106 | })
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = {
4 | //You can set the vue-loader configuration by yourself.
5 | }
6 |
--------------------------------------------------------------------------------
/config/deploy.rb:
--------------------------------------------------------------------------------
1 | require 'mina/rails'
2 | require 'mina/git'
3 |
4 | set :application_name, 'vue-timebot'
5 | set :domain, ''
6 | set :user, 'dev'
7 | set :deploy_to, "/home/dev/www/#{fetch(:application_name)}"
8 | set :repository, ''
9 | set :branch, 'master'
10 |
11 | set :shared_files, fetch(:shared_files, []).push('config/prod.env.js')
12 |
13 | # Put any custom commands you need to run at setup
14 | # All paths in `shared_dirs` and `shared_paths` will be created on their own.
15 | task :setup do
16 | # command %{rbenv install 2.3.0 --skip-existing}
17 | end
18 |
19 | desc "Deploys the current version to the server."
20 | task :deploy do
21 | deploy do
22 | invoke :'git:clone'
23 | invoke :'deploy:link_shared_paths'
24 | invoke :'deploy:cleanup'
25 |
26 | on :launch do
27 | in_path(fetch(:current_path)) do
28 | command %{yarn install}
29 | command %{yarn build}
30 | end
31 | end
32 | end
33 | end
34 |
35 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"development"',
3 | ENV_CONFIG: '"dev"',
4 | BASE_API: '"http://localhost:3000"'
5 | }
6 |
--------------------------------------------------------------------------------
/config/prod.env.js.example:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"',
3 | ENV_CONFIG: '"prod"',
4 | BASE_API: '""'
5 | }
6 |
--------------------------------------------------------------------------------
/docs/absencereport.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/absencereport.gif
--------------------------------------------------------------------------------
/docs/dashboard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/dashboard.gif
--------------------------------------------------------------------------------
/docs/estimationsanalysis.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/estimationsanalysis.gif
--------------------------------------------------------------------------------
/docs/settings.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/settings.gif
--------------------------------------------------------------------------------
/docs/timelogs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/timelogs.gif
--------------------------------------------------------------------------------
/docs/timetowork.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/timetowork.gif
--------------------------------------------------------------------------------
/docs/weeklyreports.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/docs/weeklyreports.gif
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/favicon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Timebot
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 | div#app
3 | router-view
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/src/api/actionEntityTable.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import qs from 'qs'
3 |
4 | export function fetchList(query, payload) {
5 | return request({
6 | url: query,
7 | method: 'get',
8 | params: payload,
9 | paramsSerializer: params => qs.stringify(payload, { arrayFormat: 'brackets' })
10 | })
11 | }
12 | export function fetchEntity(query, id) {
13 | return request({
14 | url: `${query}/${id}`,
15 | method: 'get',
16 | params: ''
17 | })
18 | }
19 | export function fetchEntityByName(query, payload) {
20 | return request({
21 | url: `${query}/search`,
22 | method: 'get',
23 | params: payload,
24 | paramsSerializer: params => qs.stringify(payload, { arrayFormat: 'brackets' })
25 | })
26 | }
27 | export function createEntity(row, query) {
28 | return request({
29 | url: query,
30 | method: 'post',
31 | params: row,
32 | paramsSerializer: params => qs.stringify(row, { arrayFormat: 'brackets' })
33 | })
34 | }
35 | export function deleteEntity(row, query) {
36 | return request({
37 | url: `${query}/${row.id}`,
38 | method: 'delete',
39 | params: row,
40 | paramsSerializer: params => qs.stringify(row, { arrayFormat: 'brackets' })
41 | })
42 | }
43 | export function batchAction(row, query) {
44 | return request({
45 | url: query,
46 | method: 'delete',
47 | params: row,
48 | paramsSerializer: params => qs.stringify(row, { arrayFormat: 'brackets' })
49 | })
50 | }
51 |
52 | export function updateEntity(row, query) {
53 | return request({
54 | url: `${query}/${row.id}`,
55 | method: 'put',
56 | params: row,
57 | paramsSerializer: params => qs.stringify(row, { arrayFormat: 'brackets' })
58 | })
59 | }
60 |
61 | export function syncUsers(query) {
62 | return request({
63 | url: query,
64 | method: 'post',
65 | params: {}
66 | })
67 | }
68 |
--------------------------------------------------------------------------------
/src/api/charts.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import qs from 'qs'
3 |
4 | export function fetchChartByDate(query, params) {
5 | return request({
6 | url: query,
7 | method: 'get',
8 | params: params,
9 | paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' })
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/api/login.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | export function loginByUsername(query, email, password) {
4 | const data = {
5 | email,
6 | password
7 | }
8 | return request({
9 | url: query,
10 | method: 'post',
11 | data
12 | })
13 | }
14 |
15 | export function getUserInfo(query, token) {
16 | return request({
17 | url: query,
18 | method: 'get',
19 | params: { token }
20 | })
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/api/queryConst.js:
--------------------------------------------------------------------------------
1 | export const setQuery = (payload) => {
2 | switch (payload) {
3 | case 'projects': return '/api/v1/projects'
4 | case 'time-entries': return '/api/v1/time_entries'
5 | case 'dashboardChart': return '/api/v1/dashboard'
6 | case 'users': return '/api/v1/users'
7 | case 'teams': return '/api/v1/teams'
8 | case 'holidays': return '/api/v1/holidays'
9 | case 'absences': return '/api/v1/absences'
10 | case 'estimationReports': return '/api/v1/reports/estimation_reports'
11 | case 'timeReports': return '/api/v1/reports/time_reports'
12 | case 'login': return '/api/v1/auth/sessions'
13 | case 'admins': return '/api/v1/admins'
14 | case 'userReports': return '/api/v1/reports/user_reports'
15 | case 'worked_time': return '/api/v1/reports/user_reports/worked_time'
16 | case 'absenceReports': return '/api/v1/reports/user_reports/absence'
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/assets/401_images/401.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/src/assets/401_images/401.gif
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/src/assets/icons/down-arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ item.meta.title }}
7 |
8 | {{ item.meta.title }}
9 |
10 |
11 |
12 |
13 |
14 |
44 |
45 |
58 |
--------------------------------------------------------------------------------
/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
28 | timebot
29 |
30 |
31 |
32 |
33 |
34 |
49 |
50 |
71 |
--------------------------------------------------------------------------------
/src/components/Pagination/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div.pagination-container
3 | el-pagination(v-show="pagination.total > 0"
4 | :current-page="pagination.page"
5 | :page-sizes="[10,20,30,50]"
6 | :page-size="pagination.limit"
7 | :total="pagination.total"
8 | background layout="total, sizes, prev, pager, next"
9 | @size-change="handleSizeChange" @current-change="handleCurrentChange")
10 |
11 |
12 |
61 |
62 |
64 |
--------------------------------------------------------------------------------
/src/components/ScrollPane/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
40 |
41 |
57 |
--------------------------------------------------------------------------------
/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
34 |
35 |
44 |
--------------------------------------------------------------------------------
/src/components/TreeTable/closeFunc.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import Vue from 'vue'
3 | export default function treeToArray(data, expandAll, parent, level, item) {
4 | const marLTemp = []
5 | let tmp = []
6 | Array.from(data).forEach(function(record) {
7 | if (record._expanded === undefined) {
8 | Vue.set(record, '_expanded', expandAll)
9 | }
10 | let _level = 1
11 | if (level !== undefined && level !== null) {
12 | _level = level + 1
13 | }
14 | Vue.set(record, '_level', _level)
15 | if (parent) {
16 | Vue.set(record, 'parent', parent)
17 | if (!marLTemp[_level]) {
18 | marLTemp[_level] = 0
19 | }
20 | Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft)
21 | Vue.set(record, '_width', record[item] / parent[item] * parent._width)
22 | marLTemp[_level] += record._width
23 | } else {
24 | marLTemp[record.id] = []
25 | marLTemp[record.id][_level] = 0
26 | Vue.set(record, '_marginLeft', 0)
27 | Vue.set(record, '_width', 1)
28 | }
29 | Vue.set(record, '_expanded', false)
30 | tmp.push(record)
31 | if (record.time_entries && record.time_entries.length > 0) {
32 | const children = treeToArray(record.time_entries, expandAll, record, _level, item)
33 | tmp = tmp.concat(children)
34 | }
35 | })
36 | return tmp
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/TreeTable/customEval.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import Vue from 'vue'
3 | export default function treeToArray(data, expandAll, parent, level, item) {
4 | const marLTemp = []
5 | let tmp = []
6 | Array.from(data).forEach(function(record) {
7 | if (record._expanded === undefined) {
8 | Vue.set(record, '_expanded', expandAll)
9 | }
10 | let _level = 1
11 | if (level !== undefined && level !== null) {
12 | _level = level + 1
13 | }
14 | Vue.set(record, '_level', _level)
15 | if (parent) {
16 | Vue.set(record, 'parent', parent)
17 | if (!marLTemp[_level]) {
18 | marLTemp[_level] = 0
19 | }
20 | Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft)
21 | Vue.set(record, '_width', record[item] / parent[item] * parent._width)
22 | marLTemp[_level] += record._width
23 | } else {
24 | marLTemp[record.id] = []
25 | marLTemp[record.id][_level] = 0
26 | Vue.set(record, '_marginLeft', 0)
27 | Vue.set(record, '_width', 1)
28 | }
29 | Vue.set(record, '_expanded', true)
30 | tmp.push(record)
31 | if (record.children && record.children.length > 0) {
32 | const children = treeToArray(record.children, expandAll, record, _level, item)
33 | tmp = tmp.concat(children)
34 | }
35 | })
36 | return tmp
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/TreeTable/eval.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import Vue from 'vue'
3 | export default function treeToArray(data, expandAll, parent = null, level = null) {
4 | let tmp = []
5 | Array.from(data).forEach(function(record) {
6 | if (record._expanded === undefined) {
7 | Vue.set(record, '_expanded', expandAll)
8 | }
9 | let _level = 1
10 | if (level !== undefined && level !== null) {
11 | _level = level + 1
12 | }
13 | Vue.set(record, '_level', _level)
14 | if (parent) {
15 | Vue.set(record, 'parent', parent)
16 | }
17 | tmp.push(record)
18 | if (record.children && record.children.length > 0) {
19 | const children = treeToArray(record.children, expandAll, record, _level)
20 | tmp = tmp.concat(children)
21 | }
22 | })
23 | return tmp
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/TreeTable/timeEntries.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import Vue from 'vue'
3 | export default function treeToArray(data, expandAll, parent, level, item) {
4 | const marLTemp = []
5 | let tmp = []
6 | Array.from(data).forEach(function(record) {
7 | if (record._expanded === undefined) {
8 | Vue.set(record, '_expanded', expandAll)
9 | }
10 | let _level = 1
11 | if (level !== undefined && level !== null) {
12 | _level = level + 1
13 | }
14 | Vue.set(record, '_level', _level)
15 | if (parent) {
16 | Vue.set(record, 'parent', parent)
17 | if (!marLTemp[_level]) {
18 | marLTemp[_level] = 0
19 | }
20 | Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft)
21 | Vue.set(record, '_width', record[item] / parent[item] * parent._width)
22 | marLTemp[_level] += record._width
23 | } else {
24 | marLTemp[record.id] = []
25 | marLTemp[record.id][_level] = 0
26 | Vue.set(record, '_marginLeft', 0)
27 | Vue.set(record, '_width', 1)
28 | }
29 | Vue.set(record, '_expanded', true)
30 | tmp.push(record)
31 | if (record.time_entries && record.time_entries.length > 0) {
32 | const children = treeToArray(record.time_entries, expandAll, record, _level, item)
33 | tmp = tmp.concat(children)
34 | }
35 | })
36 | return tmp
37 | }
38 |
--------------------------------------------------------------------------------
/src/directive/permission/index.js:
--------------------------------------------------------------------------------
1 | import permission from './permission'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('permission', permission)
5 | }
6 |
7 | if (window.Vue) {
8 | window['permission'] = permission
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | permission.install = install
13 | export default permission
14 |
--------------------------------------------------------------------------------
/src/directive/permission/permission.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | export default{
4 | inserted(el, binding, vnode) {
5 | const { value } = binding
6 | const roles = store.getters && store.getters.roles
7 |
8 | if (value && value instanceof Array && value.length > 0) {
9 | const permissionRoles = value
10 |
11 | const hasPermission = roles.some(role => {
12 | return permissionRoles.includes(role)
13 | })
14 |
15 | if (!hasPermission) {
16 | el.parentNode && el.parentNode.removeChild(el)
17 | }
18 | } else {
19 | throw new Error(`need roles! Like v-permission="['admin','editor']"`)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/directive/waves/index.js:
--------------------------------------------------------------------------------
1 | import waves from './waves'
2 |
3 | const install = function(Vue) {
4 | Vue.directive('waves', waves)
5 | }
6 |
7 | if (window.Vue) {
8 | window.waves = waves
9 | Vue.use(install); // eslint-disable-line
10 | }
11 |
12 | waves.install = install
13 | export default waves
14 |
--------------------------------------------------------------------------------
/src/directive/waves/waves.css:
--------------------------------------------------------------------------------
1 | .waves-ripple {
2 | position: absolute;
3 | border-radius: 100%;
4 | background-color: rgba(0, 0, 0, 0.15);
5 | background-clip: padding-box;
6 | pointer-events: none;
7 | -webkit-user-select: none;
8 | -moz-user-select: none;
9 | -ms-user-select: none;
10 | user-select: none;
11 | -webkit-transform: scale(0);
12 | -ms-transform: scale(0);
13 | transform: scale(0);
14 | opacity: 1;
15 | }
16 |
17 | .waves-ripple.z-active {
18 | opacity: 0;
19 | -webkit-transform: scale(2);
20 | -ms-transform: scale(2);
21 | transform: scale(2);
22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out;
25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
26 | }
--------------------------------------------------------------------------------
/src/directive/waves/waves.js:
--------------------------------------------------------------------------------
1 | import './waves.css'
2 |
3 | export default{
4 | bind(el, binding) {
5 | el.addEventListener('click', e => {
6 | const customOpts = Object.assign({}, binding.value)
7 | const opts = Object.assign({
8 | ele: el,
9 | type: 'hit',
10 | color: 'rgba(0, 0, 0, 0.15)'
11 | }, customOpts)
12 | const target = opts.ele
13 | if (target) {
14 | target.style.position = 'relative'
15 | target.style.overflow = 'hidden'
16 | const rect = target.getBoundingClientRect()
17 | let ripple = target.querySelector('.waves-ripple')
18 | if (!ripple) {
19 | ripple = document.createElement('span')
20 | ripple.className = 'waves-ripple'
21 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
22 | target.appendChild(ripple)
23 | } else {
24 | ripple.className = 'waves-ripple'
25 | }
26 | switch (opts.type) {
27 | case 'center':
28 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
29 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
30 | break
31 | default:
32 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || document.body.scrollTop) + 'px'
33 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || document.body.scrollLeft) + 'px'
34 | }
35 | ripple.style.backgroundColor = opts.color
36 | ripple.className = 'waves-ripple z-active'
37 | return false
38 | }
39 | }, false)
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/src/filters/index.js:
--------------------------------------------------------------------------------
1 | // set function parseTime,formatTime to filter
2 | export { parseTime, formatTime } from '@/utils'
3 |
4 | function pluralize(time, label) {
5 | if (time === 1) {
6 | return time + label
7 | }
8 | return time + label + 's'
9 | }
10 |
11 | export function timeAgo(time) {
12 | const between = Date.now() / 1000 - Number(time)
13 | if (between < 3600) {
14 | return pluralize(~~(between / 60), ' minute')
15 | } else if (between < 86400) {
16 | return pluralize(~~(between / 3600), ' hour')
17 | } else {
18 | return pluralize(~~(between / 86400), ' day')
19 | }
20 | }
21 |
22 | export function numberFormatter(num, digits) {
23 | const si = [
24 | { value: 1E18, symbol: 'E' },
25 | { value: 1E15, symbol: 'P' },
26 | { value: 1E12, symbol: 'T' },
27 | { value: 1E9, symbol: 'G' },
28 | { value: 1E6, symbol: 'M' },
29 | { value: 1E3, symbol: 'k' }
30 | ]
31 | for (let i = 0; i < si.length; i++) {
32 | if (num >= si[i].value) {
33 | return (num / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
34 | }
35 | }
36 | return num.toString()
37 | }
38 |
39 | export function toThousandFilter(num) {
40 | return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
41 | }
42 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = requireContext => requireContext.keys().map(requireContext)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/src/icons/svg/border-plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icons/svg/bug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/calendar.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/src/icons/svg/chart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/clipboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/component.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/dashboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/src/icons/svg/display.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/src/icons/svg/documentation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/down-arrow-new-blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icons/svg/down-arrow-new.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icons/svg/down-arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/src/icons/svg/drag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/email.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/excel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/guide 2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/guide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/hide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/international.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/language.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/lock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/message.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/money.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/people.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/peoples.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/shopping.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/size.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/sofa.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/src/icons/svg/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/teams.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/src/icons/svg/theme.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/time.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/zip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/src/lang/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 | import Cookies from 'js-cookie'
4 | import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang
5 | import enLocale from './en'
6 |
7 | Vue.use(VueI18n)
8 |
9 | const messages = {
10 | en: {
11 | ...enLocale,
12 | ...elementEnLocale
13 | }
14 | }
15 |
16 | const i18n = new VueI18n({
17 | // set locale
18 | // options: en or zh
19 | locale: Cookies.get('language') || 'en',
20 | // set locale messages
21 | messages
22 | })
23 |
24 | export default i18n
25 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import 'normalize.css/normalize.css'
3 | import Element from 'element-ui'
4 | import 'element-ui/lib/theme-chalk/index.css'
5 | import '@/styles/index.scss' // global css
6 | import App from './App'
7 | import router from './router'
8 | import store from './store'
9 | import i18n from './lang'
10 | import './icons'
11 | import './permission'
12 | import * as filters from './filters'
13 | const VueScrollTo = require('vue-scrollto')
14 | import HighchartsVue from 'highcharts-vue'
15 | import Highcharts from 'highcharts'
16 | import variablePie from 'highcharts/modules/variable-pie'
17 | import JsonExcel from 'vue-json-excel'
18 | import { VueMaskDirective } from 'v-mask'
19 | import VueLodash from 'vue-lodash'
20 |
21 | const options = { name: 'lodash' }
22 |
23 | Vue.use(VueLodash, options)
24 | Vue.directive('mask', VueMaskDirective)
25 |
26 | Vue.use(HighchartsVue)
27 | variablePie(Highcharts)
28 | Vue.component('downloadExcel', JsonExcel)
29 |
30 | Vue.use(VueScrollTo)
31 | Vue.use(Element, {
32 | i18n: (key, value) => i18n.t(key, value)
33 | })
34 |
35 | Object.keys(filters).forEach(key => {
36 | Vue.filter(key, filters[key])
37 | })
38 |
39 | Vue.config.productionTip = false
40 |
41 | new Vue({
42 | el: '#app',
43 | router,
44 | store,
45 | i18n,
46 | render: h => h(App)
47 | })
48 |
--------------------------------------------------------------------------------
/src/mixins/dialog.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data() {
3 | return {
4 | dialogFormLoading: false,
5 | loading: false,
6 | textMap: {
7 | update: 'Edit',
8 | create: 'Create',
9 | view: 'View'
10 | }
11 | }
12 | },
13 | methods: {
14 | handleUpdate(row) {
15 | let temp = {}
16 | temp = Object.assign({}, JSON.parse(JSON.stringify(row)))
17 | if (!row.team) {
18 | Object.assign(temp, { team: { id: '', type: 'teams' }})
19 | }
20 | this.$store.dispatch('modals/toggleModal', { visible: { edit: true }, status: 'update', temp: temp })
21 | },
22 | handleCreate() {
23 | this.$store.dispatch('modals/toggleModal', { visible: { edit: true }, status: 'create', temp: {}})
24 | },
25 | handleView(row) {
26 | let temp = {}
27 | temp = Object.assign({}, JSON.parse(JSON.stringify(row)))
28 | if (!row.team) {
29 | Object.assign(temp, { team: { id: '', type: 'teams' }})
30 | }
31 | this.$store.dispatch('modals/toggleModal', { visible: { view: true }, status: 'view', temp: temp })
32 | }
33 | },
34 | watch: {
35 | temp() {
36 | if (this.$refs['dataForm']) this.$refs['dataForm'].clearValidate()
37 | }
38 | },
39 | beforeDestroy() {
40 | this.$store.dispatch('actionEntityTable/clearFilters')
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/mixins/entities.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | createEntities(response) {
4 | const entities = []
5 | const included = []
6 | const data = {}
7 | response.data.data.forEach(dt => {
8 | const entity = {}
9 | for (const key in dt.attributes) {
10 | if (dt.attributes.hasOwnProperty(key)) {
11 | entity[key] = dt.attributes[key]
12 | }
13 | }
14 | for (const key in dt.relationships) {
15 | if (dt.relationships.hasOwnProperty(key)) {
16 | entity[key] = dt.relationships[key].data
17 | }
18 | }
19 | entity.id = dt.id
20 | entities.push(entity)
21 | })
22 | if (response.data.included) {
23 | response.data.included.forEach(ic => {
24 | const entity = {}
25 | for (const key in ic.attributes) {
26 | if (ic.attributes.hasOwnProperty(key)) {
27 | entity[key] = ic.attributes[key]
28 | }
29 | }
30 | entity.id = ic.id
31 | included.push(entity)
32 | })
33 | }
34 | data.data = entities
35 | data.included = included
36 | data.meta = response.data.meta
37 | return data
38 | },
39 | createEntity(response) {
40 | const included = []
41 | const data = {}
42 | const entity = {}
43 | for (const key in response.data.data.attributes) {
44 | if (response.data.data.attributes.hasOwnProperty(key)) {
45 | entity[key] = response.data.data.attributes[key]
46 | }
47 | }
48 | for (const key in response.data.data.relationships) {
49 | if (response.data.data.relationships.hasOwnProperty(key)) {
50 | entity[key] = response.data.data.relationships[key].data
51 | }
52 | }
53 | if (response.data.included) {
54 | response.data.included.forEach(ic => {
55 | const entity = {}
56 | for (const key in ic.attributes) {
57 | if (ic.attributes.hasOwnProperty(key)) {
58 | entity[key] = ic.attributes[key]
59 | }
60 | }
61 | entity.id = ic.id
62 | included.push(entity)
63 | })
64 | }
65 | entity.id = response.data.data.id
66 | data.data = entity
67 | data.included = included
68 | return data
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/mixins/includes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | methods: {
3 | getIncluded(id) {
4 | if (this.included(this.type)) {
5 | const findInclude = this.included(this.type).find(pj => {
6 | if (pj.id === id) return pj
7 | })
8 | if (findInclude) {
9 | return findInclude.name
10 | }
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/mixins/index.js:
--------------------------------------------------------------------------------
1 | import date from './date.js'
2 | import validationRules from './validationRules.js'
3 | import dialog from './dialog.js'
4 | import pagination from './pagination.js'
5 | import query from './query.js'
6 | import includes from './includes.js'
7 | import entities from './entities.js'
8 |
9 | export const mixDate = date
10 | export const mixValidationRules = validationRules
11 | export const mixDialog = dialog
12 | export const mixPagination = pagination
13 | export const mixQuery = query
14 | export const mixIncludes = includes
15 | export const mixEntities = entities
16 |
--------------------------------------------------------------------------------
/src/mixins/pagination.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data: () => ({
3 | }),
4 | methods: {
5 | handleSizeChange(val) {
6 | this.$store.dispatch('setPagination', { type: 'limit', value: val }, { root: true })
7 | .then(() => {
8 | this.getList()
9 | })
10 | },
11 | handleCurrentChange(val) {
12 | this.$store.dispatch('setPagination', { type: 'page', value: val }, { root: true })
13 | .then(() => {
14 | this.getList()
15 | })
16 | }
17 | },
18 | beforeDestroy() {
19 | this.$store.dispatch('setDefault')
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/mixins/validationRules.js:
--------------------------------------------------------------------------------
1 | export default {
2 | data() {
3 | const checkName = (rule, value, callback) => {
4 | if (!value) {
5 | callback(new Error())
6 | } else {
7 | callback()
8 | }
9 | }
10 | const checkProject = (rule, value, callback) => {
11 | if (!value.id) {
12 | callback(new Error())
13 | } else {
14 | callback()
15 | }
16 | }
17 | const checkUser = (rule, value, callback) => {
18 | if (!value.id) {
19 | callback(new Error())
20 | } else {
21 | callback()
22 | }
23 | }
24 | const checkDate = (rule, value, callback) => {
25 | if (!value) {
26 | callback(new Error())
27 | } else {
28 | callback()
29 | }
30 | }
31 | const checkRole = (rule, value, callback) => {
32 | if (!value) {
33 | callback(new Error())
34 | } else {
35 | callback()
36 | }
37 | }
38 | return {
39 | rules: {
40 | date: [{ validator: checkDate, type: 'date', required: true, message: 'Date is required', trigger: 'blur' }],
41 | time: [{ validator: checkDate, type: 'datetime', required: true, message: 'Time is required', trigger: 'blur' }],
42 | project: [{ validator: checkProject, required: true, message: 'Project is required', trigger: 'change' }],
43 | user: [{ validator: checkUser, required: true, message: 'User is required', trigger: 'change' }],
44 | name: [{ validator: checkName, required: true, message: 'Name is required', trigger: 'change' }],
45 | role: [{ validator: checkRole, required: true, message: 'Role is required', trigger: 'change' }],
46 | reason: [{ validator: checkRole, required: true, message: 'Reason is required', trigger: 'change' }],
47 | team: [{ required: false, message: 'Team is required', trigger: 'change' }],
48 | description: [{ validator: checkName, required: true, message: 'Description is required', trigger: 'change' }],
49 | password: [{ validator: checkName, required: true, message: 'Password is required', trigger: 'change' }],
50 | email: [{ required: true, message: 'Please input email address', trigger: 'blur' },
51 | { type: 'email', message: 'Please input correct email address', trigger: ['blur', 'change'] }]
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import { Message } from 'element-ui'
4 | import NProgress from 'nprogress' // progress bar
5 | import 'nprogress/nprogress.css'// progress bar style
6 | import { getToken } from '@/utils/auth' // getToken from cookie
7 |
8 | NProgress.configure({ showSpinner: false })// NProgress Configuration
9 |
10 | // permission judge function
11 | function hasPermission(roles, permissionRoles) {
12 | if (roles.indexOf('admin') >= 0) return true // admin permission passed directly
13 | if (!permissionRoles) return true
14 | return roles.some(role => permissionRoles.indexOf(role) >= 0)
15 | }
16 |
17 | const whiteList = ['/login', '/auth-redirect']// no redirect whitelist
18 |
19 | router.beforeEach((to, from, next) => {
20 | NProgress.start()
21 | if (getToken()) {
22 | if (to.path === '/login') {
23 | next({ path: '/' })
24 | NProgress.done()
25 | } else {
26 | if (store.getters.roles.length === 0) {
27 | store.dispatch('GetUserInfo').then(res => {
28 | const roles = res.data.roles
29 | store.dispatch('GenerateRoutes', { roles }).then(() => {
30 | router.addRoutes(store.getters.addRouters)
31 | next({ ...to, replace: true })
32 | })
33 | }).catch((err) => {
34 | store.dispatch('FedLogOut').then(() => {
35 | Message.error(err || 'Verification failed, please login again')
36 | next({ path: '/' })
37 | })
38 | })
39 | } else {
40 | if (hasPermission(store.getters.roles, to.meta.roles)) {
41 | next()
42 | } else {
43 | next({ path: '/401', replace: true, query: { noGoBack: true }})
44 | }
45 | }
46 | }
47 | } else {
48 | /* has no token*/
49 | if (whiteList.indexOf(to.path) !== -1) {
50 | next()
51 | } else {
52 | next(`/login?redirect=${to.path}`)
53 | NProgress.done()
54 | }
55 | }
56 | })
57 |
58 | router.afterEach(() => {
59 | NProgress.done() // finish progress bar
60 | })
61 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | language: state => state.app.language,
4 | size: state => state.app.size,
5 | device: state => state.app.device,
6 | visitedViews: state => state.tagsView.visitedViews,
7 | cachedViews: state => state.tagsView.cachedViews,
8 | token: state => state.user.token,
9 | avatar: state => state.user.avatar,
10 | name: state => state.user.name,
11 | introduction: state => state.user.introduction,
12 | status: state => state.user.status,
13 | roles: state => state.user.roles,
14 | setting: state => state.user.setting,
15 | permission_routers: state => state.permission.routers,
16 | addRouters: state => state.permission.addRouters,
17 | loader: state => state.app.loader
18 | }
19 | export default getters
20 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import app from './modules/app'
4 | import permission from './modules/permission'
5 | import tagsView from './modules/tagsView'
6 | import charts from './modules/charts'
7 | import user from './modules/user'
8 | import actionEntityTable from './modules/actionEntityTable'
9 | import reportsTable from './modules/reportsTable'
10 | import modals from './modules/modals'
11 | import pagination from './modules/pagination'
12 | import getters from './getters'
13 |
14 | Vue.use(Vuex)
15 |
16 | const store = new Vuex.Store({
17 | modules: {
18 | app,
19 | permission,
20 | tagsView,
21 | charts,
22 | user,
23 | modals,
24 | actionEntityTable,
25 | reportsTable,
26 | pagination
27 | },
28 | getters
29 | })
30 |
31 | export default store
32 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const app = {
4 | state: {
5 | sidebar: {
6 | opened: !+Cookies.get('sidebarStatus'),
7 | withoutAnimation: false
8 | },
9 | device: 'desktop',
10 | language: Cookies.get('language') || 'en',
11 | loader: false
12 | },
13 | mutations: {
14 | TOGGLE_SIDEBAR: state => {
15 | if (state.sidebar.opened) {
16 | Cookies.set('sidebarStatus', 1)
17 | } else {
18 | Cookies.set('sidebarStatus', 0)
19 | }
20 | state.sidebar.opened = !state.sidebar.opened
21 | state.sidebar.withoutAnimation = false
22 | },
23 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
24 | Cookies.set('sidebarStatus', 1)
25 | state.sidebar.opened = false
26 | state.sidebar.withoutAnimation = withoutAnimation
27 | },
28 | TOGGLE_DEVICE: (state, device) => {
29 | state.device = device
30 | },
31 | SET_LANGUAGE: (state, language) => {
32 | state.language = language
33 | Cookies.set('language', language)
34 | },
35 | SET_LOADER: (state, status) => {
36 | state.loader = status
37 | }
38 | },
39 | actions: {
40 | toggleSideBar({ state, dispatch, commit }) {
41 | commit('TOGGLE_SIDEBAR')
42 | },
43 | closeSideBar({ commit }, { withoutAnimation }) {
44 | commit('CLOSE_SIDEBAR', withoutAnimation)
45 | },
46 | toggleDevice({ commit }, device) {
47 | commit('TOGGLE_DEVICE', device)
48 | },
49 | setLanguage({ commit }, language) {
50 | commit('SET_LANGUAGE', language)
51 | },
52 | setLoader({ commit }, status) {
53 | commit('SET_LOADER', status)
54 | }
55 | }
56 | }
57 |
58 | export default app
59 |
--------------------------------------------------------------------------------
/src/store/modules/charts.js:
--------------------------------------------------------------------------------
1 | import * as Api from '@/api/charts'
2 | import { setQuery } from '@/api/queryConst'
3 | const charts = {
4 | state: {
5 | absent: [],
6 | hours_worked: '',
7 | hours_to_work: '',
8 | holidays: [],
9 | departments: {
10 | title: '',
11 | data: []
12 | },
13 | projects: {
14 | title: '',
15 | data: [
16 | ]
17 | },
18 | tickets: [
19 | {
20 | title: { text: 'Development' },
21 | qty: 50,
22 | color: [],
23 | data: [
24 | { name: '50', value: 100 }
25 | ]
26 | },
27 | {
28 | title: { text: 'Staging' },
29 | qty: 45,
30 | color: ['#333'],
31 | data: [
32 | { name: '45', value: 200 }
33 | ]
34 | },
35 | {
36 | title: { text: 'Production' },
37 | qty: 250,
38 | color: ['#666'],
39 | data: [
40 | { name: '250', value: 300 }
41 | ]
42 | }
43 | ],
44 | rangeDate: [],
45 | series: [],
46 | xAxisData: []
47 | },
48 | getters: {
49 | chartData: (state) => path => {
50 | return state[path]
51 | },
52 | staticData: (state) => path => state[path],
53 | rangeDate: (state) => state.rangeDate,
54 | tickets: (state) => state.tickets
55 | },
56 | mutations: {
57 | FETCH_CHART_BY_DATE(state, payload) {
58 | state.departments = payload.data.users_chart
59 | state.projects = payload.data.projects_chart
60 | state.absent = payload.data.absent
61 | state.holidays = payload.data.holidays
62 | state.hours_to_work = payload.data.hours_to_work
63 | state.hours_worked = payload.data.hours_worked
64 | state.rangeDate = [payload.data.start_of_week, payload.data.end_of_week]
65 | state.series = payload.data.series
66 | state.xAxisData = payload.data.xAxisData
67 | state.colors = payload.data.colors
68 | },
69 | SET_RANGE_DATE(state, payload) {
70 | state.rangeDate = payload
71 | }
72 | },
73 | actions: {
74 | fetchChartByDate({ state, commit }, payload) {
75 | return new Promise((resolve, reject) => {
76 | Api.fetchChartByDate(setQuery(payload.type), payload.params)
77 | .then((res) => {
78 | commit('FETCH_CHART_BY_DATE', res)
79 | resolve()
80 | })
81 | })
82 | },
83 | setRangeDate({ state, commit }, payload) {
84 | commit('SET_RANGE_DATE', payload)
85 | }
86 | }
87 | }
88 |
89 | export default charts
90 |
--------------------------------------------------------------------------------
/src/store/modules/modals.js:
--------------------------------------------------------------------------------
1 | const temp = {
2 | id: undefined,
3 | type: '',
4 | date: '',
5 | details: '',
6 | name: '',
7 | description: '',
8 | 'estimated-time': '',
9 | time: '',
10 | 'trello-labels': [],
11 | project: {},
12 | user: {},
13 | team: {},
14 | 'is-active': true
15 | }
16 | const modals = {
17 | namespaced: true,
18 | state: {
19 | visible: {
20 | view: false,
21 | edit: false
22 | },
23 | loading: false,
24 | status: '',
25 | temp: JSON.parse(JSON.stringify(temp))
26 | },
27 | getters: {
28 | visible: (state) => type => state.visible[type],
29 | status: (state) => state.status,
30 | temp: (state) => state.temp,
31 | loading: (state) => state.loading
32 | },
33 | actions: {
34 | toggleModal({ commit }, payload) {
35 | commit('TOGGLE_MODAL', payload)
36 | },
37 | setLoading({ commit }, payload) {
38 | commit('SET_LOADING', payload)
39 | }
40 | },
41 | mutations: {
42 | TOGGLE_MODAL(state, payload) {
43 | state.temp = Object.assign(JSON.parse(JSON.stringify(temp)), payload.temp)
44 | Object.assign(state, state.visible, { visible: payload.visible }, { status: payload.status })
45 | },
46 | SET_LOADING(state, payload) {
47 | state.loading = payload.loading
48 | }
49 | }
50 | }
51 |
52 | export default modals
53 |
--------------------------------------------------------------------------------
/src/store/modules/pagination.js:
--------------------------------------------------------------------------------
1 | const pagination = {
2 | state: {
3 | pagination: {
4 | page: 1,
5 | limit: 30,
6 | total: 10,
7 | sort: '+id'
8 | }
9 | },
10 | getters: {
11 | pagination: (state) => state.pagination
12 | },
13 | actions: {
14 | setPagination: {
15 | root: true,
16 | handler({ state, commit }, payload) {
17 | return new Promise((resolve, reject) => {
18 | commit('SET_PAGINATION', payload)
19 | resolve()
20 | })
21 | }
22 | },
23 | setDefault({ state, commit }) {
24 | commit('SET_DEFAULT')
25 | }
26 | },
27 | mutations: {
28 | SET_PAGINATION(state, payload) {
29 | Object.assign(state.pagination, payload)
30 | },
31 | SET_DEFAULT(state) {
32 | state.pagination = {
33 | page: 1,
34 | limit: 30,
35 | total: 10,
36 | sort: '+id'
37 | }
38 | }
39 | }
40 | }
41 |
42 | export default pagination
43 |
--------------------------------------------------------------------------------
/src/store/modules/permission.js:
--------------------------------------------------------------------------------
1 | import { asyncRouterMap, constantRouterMap } from '@/router'
2 | function hasPermission(roles, route) {
3 | if (route.meta && route.meta.roles) {
4 | return roles.some(role => route.meta.roles.indexOf(role) >= 0)
5 | } else {
6 | return true
7 | }
8 | }
9 | function filterAsyncRouter(asyncRouterMap, roles) {
10 | const accessedRouters = asyncRouterMap.filter(route => {
11 | if (hasPermission(roles, route)) {
12 | if (route.children && route.children.length) {
13 | route.children = filterAsyncRouter(route.children, roles)
14 | }
15 | return true
16 | }
17 | return false
18 | })
19 | return accessedRouters
20 | }
21 |
22 | const permission = {
23 | state: {
24 | routers: constantRouterMap,
25 | addRouters: []
26 | },
27 | mutations: {
28 | SET_ROUTERS: (state, routers) => {
29 | state.addRouters = routers
30 | state.routers = constantRouterMap.concat(routers)
31 | }
32 | },
33 | actions: {
34 | GenerateRoutes({ commit }, data) {
35 | return new Promise(resolve => {
36 | const { roles } = data
37 | let accessedRouters
38 | if (roles.indexOf('admin') >= 0) {
39 | accessedRouters = asyncRouterMap
40 | } else {
41 | accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
42 | }
43 | commit('SET_ROUTERS', accessedRouters)
44 | resolve()
45 | })
46 | }
47 | }
48 | }
49 |
50 | export default permission
51 |
--------------------------------------------------------------------------------
/src/store/modules/reportsTable.js:
--------------------------------------------------------------------------------
1 | import * as Api from '@/api/actionEntityTable'
2 | import { setQuery } from '@/api/queryConst'
3 |
4 | const reportsTable = {
5 | namespaced: true,
6 | state: {
7 | estimationReports: {
8 | list: []
9 | },
10 | timeReports: {
11 | list: []
12 | },
13 | userReports: {
14 | list: []
15 | },
16 | absenceReports: {
17 | list: [],
18 | filterable: []
19 | },
20 | filters: {
21 | },
22 | loader: false
23 | },
24 | getters: {
25 | list: (state) => type => state[type].list,
26 | filterable: (state) => type => state[type].filterable,
27 | included: (state) => type => state[type].included,
28 | loader: (state) => state.loader,
29 | filters: (state) => state.filters
30 | },
31 | actions: {
32 | fetchList({ state, commit, dispatch, rootState }, payload) {
33 | return new Promise((resolve, reject) => {
34 | Api.fetchList(setQuery(payload), { page: rootState.pagination.pagination.page, 'per_page': rootState.pagination.pagination.limit, ...state.filters })
35 | .then((response) => {
36 | commit('FETCH_LIST', { data: response.data, type: payload })
37 | if (response.data.meta) {
38 | dispatch('setPagination', { total: response.data.meta['total-count'] || response.data.meta['total_count'] }, { root: true })
39 | }
40 | resolve()
41 | })
42 | .catch(() => {
43 | reject()
44 | })
45 | })
46 | },
47 | setFilter({ state, commit }, payload) {
48 | return new Promise((resolve, reject) => {
49 | commit('SET_FILTER', { data: payload })
50 | resolve()
51 | })
52 | },
53 | setLoader({ state, commit }, payload) {
54 | commit('SET_LOADER', payload)
55 | }
56 | },
57 | mutations: {
58 | FETCH_LIST(state, payload) {
59 | state[payload.type].list = payload.data.data
60 | if (payload.data.included) {
61 | state[payload.type].included = payload.data.included
62 | }
63 | },
64 | SET_FILTER(state, payload) {
65 | state.filters = payload.data
66 | },
67 | SET_LOADER(state, payload) {
68 | state.loader = payload
69 | },
70 | SET_PAGINATION(state, payload) {
71 | if (payload.limit) {
72 | state.pagination.limit = payload.limit
73 | } else {
74 | state.pagination.page = payload.page
75 | }
76 | }
77 | }
78 | }
79 |
80 | export default reportsTable
81 |
--------------------------------------------------------------------------------
/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { loginByUsername } from '@/api/login'
2 | import { getToken, setToken, removeToken } from '@/utils/auth'
3 | import { setQuery } from '@/api/queryConst'
4 |
5 | const user = {
6 | state: {
7 | user: '',
8 | status: '',
9 | code: '',
10 | token: getToken(),
11 | name: '',
12 | avatar: '',
13 | introduction: '',
14 | roles: [],
15 | setting: {
16 | articlePlatform: []
17 | }
18 | },
19 |
20 | mutations: {
21 | SET_CODE: (state, code) => {
22 | state.code = code
23 | },
24 | SET_TOKEN: (state, token) => {
25 | state.token = token
26 | },
27 | SET_INTRODUCTION: (state, introduction) => {
28 | state.introduction = introduction
29 | },
30 | SET_SETTING: (state, setting) => {
31 | state.setting = setting
32 | },
33 | SET_STATUS: (state, status) => {
34 | state.status = status
35 | },
36 | SET_NAME: (state, name) => {
37 | state.name = name
38 | },
39 | SET_AVATAR: (state, avatar) => {
40 | state.avatar = avatar
41 | },
42 | SET_ROLES: (state, roles) => {
43 | state.roles = ['admin']
44 | }
45 | },
46 |
47 | actions: {
48 | LoginByUsername({ commit }, userInfo) {
49 | const email = userInfo.payload.email.trim()
50 | return new Promise((resolve, reject) => {
51 | loginByUsername(setQuery(userInfo.type), email, userInfo.payload.password).then(response => {
52 | const data = response.data
53 | commit('SET_TOKEN', data.token)
54 | setToken(data.token, data.exp)
55 | resolve()
56 | }).catch(error => {
57 | reject(error)
58 | })
59 | })
60 | },
61 | GetUserInfo({ commit, state }) {
62 | return new Promise((resolve, reject) => {
63 | commit('SET_ROLES')
64 | resolve({ data: { roles: ['admin'] }})
65 | // getUserInfo(state.token).then(response => {
66 | // if (!response.data) {
67 | // reject('error')
68 | // }
69 | // const data = response.data
70 | //
71 | // if (data.roles && data.roles.length > 0) {
72 | // commit('SET_ROLES', data.roles)
73 | // } else {
74 | // reject('getInfo: roles must be a non-null array !')
75 | // }
76 | //
77 | // commit('SET_NAME', data.name)
78 | // commit('SET_AVATAR', data.avatar)
79 | // commit('SET_INTRODUCTION', data.introduction)
80 | // resolve(response)
81 | // }).catch(error => {
82 | // reject(error)
83 | // })
84 | })
85 | },
86 | LogOut({ commit, state }) {
87 | return new Promise((resolve, reject) => {
88 | commit('SET_TOKEN', '')
89 | commit('SET_ROLES', [])
90 | removeToken()
91 | resolve()
92 | })
93 | },
94 | FedLogOut({ commit }) {
95 | return new Promise(resolve => {
96 | commit('SET_TOKEN', '')
97 | removeToken()
98 | resolve()
99 | })
100 | }
101 | }
102 | }
103 |
104 | export default user
105 |
--------------------------------------------------------------------------------
/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | .el-breadcrumb__inner,
2 | .el-breadcrumb__inner a {
3 | font-family: $main-ff-medium;
4 | font-size: 16px;
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-breadcrumb__inner a:hover,
9 | .el-breadcrumb__inner.is-link:hover {
10 | color: #305ac7;
11 | }
12 |
13 | .el-upload {
14 | input[type="file"] {
15 | display: none !important;
16 | }
17 | }
18 |
19 | .el-upload__input {
20 | display: none;
21 | }
22 |
23 | .cell {
24 | .el-tag {
25 | margin-right: 0;
26 | }
27 | }
28 |
29 | .small-padding {
30 | .cell {
31 | padding-left: 5px;
32 | padding-right: 5px;
33 | }
34 | }
35 |
36 | .fixed-width {
37 | .el-button--mini {
38 | padding: 9px 14px 5px;
39 | }
40 | }
41 |
42 | .status-col {
43 | .cell {
44 | padding: 0 10px;
45 | text-align: center;
46 |
47 | .el-tag {
48 | margin-right: 0;
49 | }
50 | }
51 | }
52 |
53 | .el-dialog {
54 | left: 0;
55 | margin: 0 auto;
56 | position: relative;
57 | transform: none;
58 | }
59 |
60 | .article-textarea {
61 | textarea {
62 | border: 0;
63 | border-bottom: 1px solid #bfcbd9;
64 | border-radius: 0;
65 | padding-right: 40px;
66 | resize: none;
67 | }
68 | }
69 |
70 | .upload-container {
71 | .el-upload {
72 | width: 100%;
73 |
74 | .el-upload-dragger {
75 | height: 200px;
76 | width: 100%;
77 | }
78 | }
79 | }
80 |
81 | .el-dropdown-menu {
82 | a {
83 | display: block
84 | }
85 | }
86 |
87 | .el-popper[x-placement^=bottom] {
88 | margin-top: 0;
89 | }
90 |
91 | .el-dropdown-menu__item--divided {
92 | border-top: 0;
93 | margin-top: 0 !important;
94 | }
95 |
96 | .el-dropdown-menu--medium .el-dropdown-menu__item.el-dropdown-menu__item--divided:before {
97 | height: 0;
98 | margin: 0 -17px;
99 | }
100 |
101 | .el-dropdown-menu__item:focus,
102 | .el-dropdown-menu__item:not(.is-disabled):hover {
103 | background-color: #fff;
104 | color: #305ac7;
105 | }
106 |
--------------------------------------------------------------------------------
/src/styles/fonts.scss:
--------------------------------------------------------------------------------
1 | //Gilgon
2 | @font-face {
3 | font-family: Gilgon;
4 | src: url('/static/fonts/GILGON__.ttf');
5 | }
6 |
7 | //Calibre
8 | @font-face {
9 | font-family: 'Calibre-Light';
10 | src: url('/static/fonts/calibre/Calibre-Light.otf') format('opentype');
11 | font-style: normal;
12 | font-weight: normal;
13 | }
14 |
15 | @font-face {
16 | font-family: 'Calibre-Regular';
17 | src: url('/static/fonts/calibre/Calibre-Regular.otf') format('opentype');
18 | font-style: normal;
19 | font-weight: normal;
20 | }
21 |
22 | @font-face {
23 | font-family: 'Calibre-Medium';
24 | src: url('/static/fonts/calibre/Calibre-Medium.otf') format('opentype');
25 | font-style: normal;
26 | font-weight: normal;
27 | }
28 |
29 | //Poppins
30 | @font-face {
31 | font-family: 'Poppins-Thin';
32 | src: url('/static/fonts/poppins/Poppins-Thin.woff2') format('woff2'),
33 | url('/static/fonts/poppins/Poppins-Thin.woff') format('woff');
34 | font-style: normal;
35 | font-weight: normal;
36 | }
37 |
38 | @font-face {
39 | font-family: 'Poppins-Regular';
40 | src: url('/static/fonts/poppins/Poppins-Regular.woff2') format('woff2'),
41 | url('/static/fonts/poppins/Poppins-Regular.woff') format('woff');
42 | font-style: normal;
43 | font-weight: normal;
44 | }
45 |
46 | @font-face {
47 | font-family: 'Poppins-Medium';
48 | src: url('/static/fonts/poppins/Poppins-Medium.woff2') format('woff2'),
49 | url('/static/fonts/poppins/Poppins-Medium.woff') format('woff');
50 | font-style: normal;
51 | font-weight: normal;
52 | }
53 |
54 | @font-face {
55 | font-family: 'Poppins-Semi-Bold';
56 | src: url('/static/fonts/poppins/Poppins-SemiBold.woff2') format('woff2'),
57 | url('/static/fonts/poppins/Poppins-SemiBold.woff') format('woff');
58 | font-style: normal;
59 | font-weight: normal;
60 | }
61 |
62 | @font-face {
63 | font-family: 'Poppins-Bold';
64 | src: url('/static/fonts/poppins/poppins-bold.woff2') format('woff2'),
65 | url('/static/fonts/poppins/poppins-bold.woff') format('woff');
66 | font-style: normal;
67 | font-weight: normal;
68 | }
69 |
--------------------------------------------------------------------------------
/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | .fade-enter-active,
2 | .fade-leave-active {
3 | transition: opacity .28s;
4 | }
5 |
6 | .fade-enter,
7 | .fade-leave-active {
8 | opacity: 0;
9 | }
10 |
11 | .fade-transform-leave-active,
12 | .fade-transform-enter-active {
13 | transition: all .5s;
14 | }
15 |
16 | .fade-transform-enter {
17 | opacity: 0;
18 | transform: translateX(-30px);
19 | }
20 |
21 | .fade-transform-leave-to {
22 | opacity: 0;
23 | transform: translateX(30px);
24 | }
25 |
26 | .breadcrumb-enter-active,
27 | .breadcrumb-leave-active {
28 | transition: all .5s;
29 | }
30 |
31 | .breadcrumb-enter,
32 | .breadcrumb-leave-active {
33 | opacity: 0;
34 | transform: translateX(20px);
35 | }
36 |
37 | .breadcrumb-move {
38 | transition: all .5s;
39 | }
40 |
41 | .breadcrumb-leave-active {
42 | position: absolute;
43 | }
44 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | $main-corp-color: #ed3e44;
2 | $secondary-corp-color: #1973cd;
3 |
4 | $color-w: #fff;
5 |
6 | $font-color: #232323;
7 | $font-color-2: #ed3e44;
8 | $font-hover: #fff;
9 | $blog-bg: #f6f6f9;
10 | $border-color: #e0e0e0;
11 |
12 | $default-font-size: 16px;
13 | $heightTopBar: 50px;
14 |
15 | //Calibre
16 | $main-ff-light: 'Calibre-Light', sans-serif;
17 | $main-ff-regular: 'Calibre-Regular', sans-serif;
18 | $main-ff-medium: 'Calibre-Medium', sans-serif;
19 |
20 | //Poppins
21 | $title-ff-thin: 'Poppins-Thin', sans-serif;
22 | $title-ff-regular: 'Poppins-Regular', sans-serif;
23 | $title-ff-medium: 'Poppins-Medium', sans-serif;
24 | $title-ff-semi-bold: 'Poppins-Semi-Bold', sans-serif;
25 | $title-ff-bold: 'Poppins-Bold', sans-serif;
26 |
27 | //font-size
28 | $font-btn: 24px;
29 | $font-s: 20px;
30 | $font-m: 25px;
31 | $font-l: 30px;
32 | $font-xl: 36px;
33 |
34 | $default-breakpoints-four-inch: 320px;
35 | $default-breakpoints-five-inch: 375px;
36 | $default-breakpoints-xs: 420px;
37 | $default-breakpoints-sm: 576px;
38 | $default-breakpoints-md: 767px;
39 | $default-breakpoints-lg: 991px;
40 | $default-breakpoints-xl: 1199px;
41 | $default-breakpoints-xxl: 1800px;
42 |
43 | $blue:#324157;
44 | $light-blue:#3A71A8;
45 | $red:#C03639;
46 | $pink: #E65D6E;
47 | $green: #30B08F;
48 | $tiffany: #4AB7BD;
49 | $yellow:#FEC171;
50 | $panGreen: #30B08F;
51 |
52 | $menuBg:#304156;
53 | $subMenuBg:#1f2d3d;
54 | $menuHover:#001528;
55 |
--------------------------------------------------------------------------------
/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const TokenKey = 'Admin-Token'
4 |
5 | export function getToken() {
6 | return Cookies.get(TokenKey)
7 | }
8 |
9 | export function setToken(token, exp) {
10 | return Cookies.set(TokenKey, token, { expires: exp / (3600 * 24) }) // Hours to day
11 | }
12 |
13 | export function removeToken() {
14 | return Cookies.remove(TokenKey)
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/i18n.js:
--------------------------------------------------------------------------------
1 | // translate router.meta.title, be used in breadcrumb sidebar tagsview
2 | export function generateTitle(title) {
3 | const hasKey = this.$te('route.' + title)
4 | const translatedTitle = this.$t('route.' + title) // $t :this method from vue-i18n, inject in @/lang/index.js
5 |
6 | if (hasKey) {
7 | return translatedTitle
8 | }
9 | return title
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/permission.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | export default function checkPermission(value) {
4 | if (value && value instanceof Array && value.length > 0) {
5 | const roles = store.getters && store.getters.roles
6 | const permissionRoles = value
7 |
8 | const hasPermission = roles.some(role => {
9 | return permissionRoles.includes(role)
10 | })
11 |
12 | if (!hasPermission) {
13 | return false
14 | }
15 | return true
16 | } else {
17 | console.error(`need roles! Like v-permission="['admin','editor']"`)
18 | return false
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { Message } from 'element-ui'
3 | import store from '@/store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | const service = axios.create({
7 | baseURL: process.env.BASE_API,
8 | headers: {
9 | 'Access-Control-Allow-Origin': '*',
10 | 'Access-Control-Allow-Methods': 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
11 | 'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Auth-Token'
12 | },
13 | timeout: 125000
14 | })
15 | service.interceptors.request.use(
16 | config => {
17 | if (store.getters.token) {
18 | config.headers['Authorization'] = `Bearer ${getToken()}`
19 | }
20 | return config
21 | },
22 | error => {
23 | console.log(error)
24 | Promise.reject(error)
25 | }
26 | )
27 | service.interceptors.response.use(
28 | response => response,
29 | error => {
30 | console.log('err' + error)
31 | Message({
32 | message: error.response ? (error.response.data.message ? error.response.data.message : error.message) : error.message,
33 | type: 'error',
34 | duration: 5 * 1000
35 | })
36 | if (store.getters.roles.length && error.response && error.response.status === 401) {
37 | store.dispatch('LogOut').then(() => { location.reload() })
38 | }
39 | return Promise.reject(error)
40 | }
41 | )
42 |
43 | export default service
44 |
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | export function isvalidUsername(str) {
2 | const valid_map = ['admin', 'editor']
3 | return valid_map.indexOf(str.trim()) >= 0
4 | }
5 |
6 | export function validateURL(textval) {
7 | const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
8 | return urlregex.test(textval)
9 | }
10 |
11 | export function validateLowerCase(str) {
12 | const reg = /^[a-z]+$/
13 | return reg.test(str)
14 | }
15 |
16 | export function validateUpperCase(str) {
17 | const reg = /^[A-Z]+$/
18 | return reg.test(str)
19 | }
20 |
21 | export function validateAlphabets(str) {
22 | const reg = /^[A-Za-z]+$/
23 | return reg.test(str)
24 | }
25 |
26 | /**
27 | * validate email
28 | * @param email
29 | * @returns {boolean}
30 | */
31 | export function isvalidateEmail(email) {
32 | const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
33 | return re.test(email)
34 | }
35 |
--------------------------------------------------------------------------------
/src/views/absence-report/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Id
6 | .el-dialog-flex-subhead {{temp.id}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head Name
9 | .el-dialog-flex-subhead {{getIncluded(temp.user.id)}}
10 | .el-dialog-flex-block
11 | .el-dialog-flex-head Date
12 | .el-dialog-flex-subhead {{temp.date}}
13 | .el-dialog-flex-block
14 | .el-dialog-flex-head Reason
15 | .el-dialog-flex-subhead {{temp.reason}}
16 | .el-dialog-flex-block
17 | .el-dialog-flex-head Comment
18 | .el-dialog-flex-subhead {{temp.comment}}
19 |
20 |
21 |
45 |
46 |
49 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/BarChart.vue:
--------------------------------------------------------------------------------
1 |
2 | highcharts(
3 | :style="{height: height}"
4 | :options="{title: {text: 'Projects'}, colors: colors, series: series,legend: legend, tooltip: tooltip, plotOptions: plotOptions, chart: chart, yAxis: { title: { text: 'Hours' }}, xAxis: { categories: xAxisData }}")
5 |
6 |
7 |
74 |
--------------------------------------------------------------------------------
/src/views/dashboard/admin/components/PieChart.vue:
--------------------------------------------------------------------------------
1 |
2 | highcharts(
3 | :style="{height: height, width: width}"
4 | :options="{title: {text: title}, colors: colors, legend: legend, series: [{name: 'Hours', innerSize: innerSize, data: payloadData}], tooltip: tooltip, plotOptions: plotOptions, chart: chart}")
5 |
6 |
70 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
28 |
--------------------------------------------------------------------------------
/src/views/errorPage/401.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Back
4 |
5 |
6 | Oops!
7 | You don't have permission to go to this page
8 | If you are dissatisfied, please contact your leader.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
43 |
44 |
83 |
--------------------------------------------------------------------------------
/src/views/layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
66 |
67 |
93 |
--------------------------------------------------------------------------------
/src/views/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
24 |
25 |
34 |
35 |
--------------------------------------------------------------------------------
/src/views/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/src/views/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
17 |
34 |
35 |
38 |
--------------------------------------------------------------------------------
/src/views/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar/index.vue'
3 | export { default as TagsView } from './TagsView'
4 | export { default as AppMain } from './AppMain'
5 |
--------------------------------------------------------------------------------
/src/views/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 1024
5 | const RATIO = 3
6 |
7 | export default {
8 | watch: {
9 | $route(route) {
10 | if (this.device === 'mobile' && this.sidebar.opened) {
11 | store.dispatch('closeSideBar', { withoutAnimation: false })
12 | }
13 | }
14 | },
15 | beforeMount() {
16 | window.addEventListener('resize', this.resizeHandler)
17 | },
18 | mounted() {
19 | const isMobile = this.isMobile()
20 | if (isMobile) {
21 | store.dispatch('toggleDevice', 'mobile')
22 | store.dispatch('closeSideBar', { withoutAnimation: true })
23 | }
24 | },
25 | methods: {
26 | isMobile() {
27 | const rect = body.getBoundingClientRect()
28 | return rect.width - RATIO < WIDTH
29 | },
30 | resizeHandler() {
31 | if (!document.hidden) {
32 | const isMobile = this.isMobile()
33 | store.dispatch('toggleDevice', isMobile ? 'mobile' : 'desktop')
34 |
35 | if (isMobile) {
36 | store.dispatch('closeSideBar', { withoutAnimation: true })
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/views/login/authredirect.vue:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/views/login/socialsignin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 微信
5 |
6 |
7 | QQ
8 |
9 |
10 |
11 |
12 |
37 |
38 |
71 |
--------------------------------------------------------------------------------
/src/views/redirect/index.vue:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/views/settings/admins/adminsTable.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | div
4 | div(class="position-create-new-category-right")
5 | el-button(@click="handleCreate",
6 | class="filter-item create-new-category",
7 | type="primary") Add new admin
8 | el-table(
9 | :key="tableKey"
10 | :data="list(type)"
11 | border
12 | fit
13 | highlight-current-row
14 | style="width: 100%;")
15 | el-table-column(:label="$t('table.id')" align="center" width="65")
16 | template(slot-scope="scope")
17 | span {{ scope.row.id }}
18 | el-table-column(label="Email/Login")
19 | template(slot-scope="scope")
20 | span {{ scope.row.email }}
21 | el-table-column(:label="$t('table.actions')" width="230" class-name="small-padding fixed-width")
22 | template(slot-scope="scope")
23 | el-button(type="info" size="mini" @click="handleView(scope.row)") View
24 | el-button(type="primary" size="mini" @click="handleUpdate(scope.row)") {{ $t('table.edit') }}
25 | el-button(v-if="scope.row.status !== 'deleted'" size="mini" type="danger" @click="removeEntity(scope.row,'deleted')") {{ $t('table.delete') }}
26 | modal-edit(ref="edit")
27 | modal-view(ref="view")
28 |
29 |
30 |
57 |
58 |
61 |
--------------------------------------------------------------------------------
/src/views/settings/admins/components/modals/edit/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-edit(:class="{'el-dialog-create': status === 'create'}" @close="closeModal" :title="textMap[status]" :visible="modalVisible('edit')")
3 | el-form(ref="dataForm"
4 | :rules="rules"
5 | :model="temp"
6 | label-position="left"
7 | label-width="70px")
8 | .el-dialog-edit-block
9 | el-form-item(label="Email" prop="email")
10 | el-input(v-model="temp.email")
11 | el-form-item(v-if="status === 'create'" label="Password" prop="password")
12 | el-input(v-model="temp.password")
13 | .el-dialog-edit-block-last
14 | div(slot="footer" class="dialog-footer")
15 | el-button(@click="closeModal") {{ $t('table.cancel') }}
16 | el-button(v-if="status === 'create'" :loading="loader" type="primary" @click="create") Create
17 | el-button(v-else type="primary" :loading="loader" @click="update") {{ $t('table.confirm') }}
18 |
19 |
20 |
63 |
64 |
67 |
--------------------------------------------------------------------------------
/src/views/settings/admins/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Id
6 | .el-dialog-flex-subhead {{temp.id}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head Email
9 | .el-dialog-flex-subhead {{temp.email}}
10 |
11 |
12 |
33 |
34 |
37 |
--------------------------------------------------------------------------------
/src/views/settings/admins/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | div(class="timebot-header") Admins
4 | adminsTable
5 |
6 |
7 |
16 |
19 |
--------------------------------------------------------------------------------
/src/views/settings/holidays/components/modals/edit/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-edit(:class="{'el-dialog-create': status === 'create'}" @close="closeModal" :title="textMap[status]" :visible="modalVisible('edit')")
3 | el-form(ref="dataForm"
4 | :rules="rules"
5 | :model="temp"
6 | label-position="left"
7 | label-width="70px"
8 | style="width: 400px; margin-left:50px;")
9 | .el-dialog-edit-block
10 | el-form-item(label="Name" prop="name")
11 | el-input(v-model="temp.name" clearable)
12 | el-form-item(label="Date" prop="date")
13 | el-date-picker(
14 | format="yyyy-MM-dd"
15 | value-format="yyyy-MM-dd"
16 | v-model="temp.date" type="date" placeholder="Please pick a date")
17 | .el-dialog-edit-block-last
18 | div(slot="footer" class="dialog-footer")
19 | el-button(@click="closeModal") {{ $t('table.cancel') }}
20 | el-button(v-if="status === 'create'" :loading="loader" type="primary" @click="create") Create
21 | el-button(v-else type="primary" :loading="loader" @click="update") {{ $t('table.confirm') }}
22 |
23 |
24 |
67 |
68 |
71 |
--------------------------------------------------------------------------------
/src/views/settings/holidays/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Name
6 | .el-dialog-flex-subhead {{temp.name}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head Date
9 | .el-dialog-flex-subhead {{temp.date}}
10 |
11 |
12 |
32 |
33 |
36 |
--------------------------------------------------------------------------------
/src/views/settings/holidays/filters.vue:
--------------------------------------------------------------------------------
1 |
2 | div(class="time-entries-filters-container")
3 | div(class="time-entries-filters")
4 | div(class="filters-label") Holiday name
5 | el-input(
6 | v-model="searchParams.holiday",
7 | @input="filter"
8 | placeholder="Please enter a holiday",
9 | clearable
10 | )
11 | div(class="time-entries-filters")
12 | div(class="filters-label") Date
13 | el-date-picker(
14 | format="yyyy-MM-dd"
15 | type="daterange",
16 | range-separator="-",
17 | value-format="yyyy-MM-dd"
18 | v-model="searchParams.date"
19 | :picker-options="pickerOptions",
20 | start-placeholder="Start date",
21 | @input="filter"
22 | end-placeholder="End date",
23 | placeholder="Please pick a date",
24 | prefix-icon="date-calendar")
25 | div(style="margin: 19px 0 0")
26 | div
27 | el-button.el-button-clear-filter(@click="clearFilter" type="info") Clear Filters
28 |
29 |
30 |
69 |
70 |
73 |
--------------------------------------------------------------------------------
/src/views/settings/holidays/holidaysTable.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | div
4 | div(class="position-create-new-category")
5 | el-button(@click="handleCreate",
6 | class="filter-item create-new-category",
7 | type="primary") Add new holiday
8 | el-table(
9 | :key="tableKey"
10 | :data="list(type)"
11 | border
12 | fit
13 | @selection-change="handleSelectionChange"
14 | highlight-current-row
15 | style="width: 100%;")
16 | el-table-column(:label="$t('table.id')" align="center" width="65")
17 | template(slot-scope="scope")
18 | span {{ scope.row.id }}
19 | el-table-column(label="Name")
20 | template(slot-scope="scope")
21 | span {{ scope.row.name }}
22 | el-table-column(label="Date")
23 | template(slot-scope="scope")
24 | span {{ scope.row.date }}
25 | el-table-column(:label="$t('table.actions')" width="230" class-name="small-padding fixed-width")
26 | template(slot-scope="scope")
27 | el-button(type="info" size="mini" @click="handleView(scope.row)") View
28 | el-button(type="primary" size="mini" @click="handleUpdate(scope.row)") {{ $t('table.edit') }}
29 | el-button(v-if="scope.row.status !== 'deleted'" size="mini" type="danger" @click="removeEntity(scope.row,'deleted')") {{ $t('table.delete') }}
30 | pagination(:type="type" v-if="list(type).length")
31 | modal-edit(ref="edit")
32 | modal-view(ref="view")
33 |
34 |
35 |
71 |
72 |
75 |
--------------------------------------------------------------------------------
/src/views/settings/holidays/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div()
3 | div(class="timebot-header") Holidays
4 | filters
5 | holidaysTable
6 |
7 |
8 |
26 |
29 |
--------------------------------------------------------------------------------
/src/views/settings/projects/components/modals/edit/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-edit(:class="{'el-dialog-create': status === 'create'}" :title="textMap[status]" @close="closeModal" :visible="modalVisible('edit')")
3 | el-form(ref="dataForm"
4 | :rules="rules"
5 | :model="temp"
6 | label-position="left"
7 | label-width="70px"
8 | style="width: 400px; margin-left:50px;")
9 | .el-dialog-edit-block
10 | el-form-item(label="Name" prop="name")
11 | el-input(placeholder="Please input" v-model="temp.name" clearable)
12 | el-form-item(label="Team" prop="team")
13 | el-select(v-if="temp.team" v-model="temp.team.id" placeholder="Select")
14 | el-option(v-for="team in list('teams')" :key="team.id" :label="team.name" :value="team.id")
15 | el-select(v-else v-model="temp.team" placeholder="Select")
16 | el-option(v-for="team in list('teams')" :key="team.id" :label="team.name" :value="team")
17 | .el-dialog-edit-block-status
18 | el-form-item(label="Status")
19 | el-checkbox(v-model="temp['active']") Is Active
20 | .el-dialog-edit-block-last
21 | div(slot="footer" class="dialog-footer")
22 | el-button(@click="closeModal") {{ $t('table.cancel') }}
23 | el-button(v-if="status === 'create'" type="primary" :loading="loader" @click="create") {{ $t('table.confirm') }}
24 | el-button(v-else type="primary" :loading="loader" @click="update") Update
25 |
26 |
27 |
79 |
80 |
83 |
--------------------------------------------------------------------------------
/src/views/settings/projects/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Id
6 | .el-dialog-flex-subhead {{temp.id}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head Name
9 | .el-dialog-flex-subhead {{temp.name}}
10 | .el-dialog-flex-block
11 | .el-dialog-flex-head Alias
12 | .el-dialog-flex-subhead {{temp.alias}}
13 | .el-dialog-flex-block
14 | .el-dialog-flex-head Status
15 | .el-dialog-flex-subhead
16 | span(v-if="temp['active']") Active
17 | span(v-else) Inactive
18 |
19 |
20 |
41 |
42 |
45 |
--------------------------------------------------------------------------------
/src/views/settings/projects/filters.vue:
--------------------------------------------------------------------------------
1 |
2 | div(class="time-entries-filters-container")
3 | div(class="time-entries-filters")
4 | div(class="filters-label") Name
5 | el-select(
6 | v-model="searchParams.name"
7 | filterable
8 | remote,
9 | @focus="remoteGetProjects"
10 | clearable,
11 | @input="filter",
12 | placeholder="Please enter a keyword"
13 | :remote-method="remoteGetProjects"
14 | )
15 | el-option(
16 | v-for="project in filterable('projects')"
17 | :value="project.name"
18 | :key="project.id",
19 | :label="project.name")
20 | div(class="time-entries-filters")
21 | div(class="filters-label") Alias
22 | el-input(v-model="searchParams.alias" @input="filter")
23 | div(class="time-entries-filters-checkbox")
24 | div(class="filters-label") Status
25 | div(class="time-entries-checkbox")
26 | el-checkbox(v-model="searchParams.status" @input="filter") Is Active
27 | div(style="margin: 19px 0 0")
28 | el-button.el-button-clear-filter(@click="clearFilter" type="info") Clear Filters
29 |
30 |
31 |
72 |
73 |
76 |
--------------------------------------------------------------------------------
/src/views/settings/projects/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div()
3 | div(class="timebot-header") Projects
4 | filters
5 | projectsTable
6 |
7 |
8 |
46 |
47 |
50 |
--------------------------------------------------------------------------------
/src/views/settings/teams/components/modals/edit/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-edit(:class="{'el-dialog-create': status === 'create'}" :title="textMap[status]" :visible="modalVisible('edit')" @close="closeModal")
3 | el-form(ref="dataForm"
4 | :rules="rules"
5 | :model="temp"
6 | label-position="left"
7 | label-width="70px")
8 | .el-dialog-edit-block
9 | el-form-item(label="Name" prop="name")
10 | el-input(v-model="temp.name" clearable)
11 | el-form-item(label="Description" prop="description")
12 | el-input(v-model="temp.description" clearable)
13 | .el-dialog-edit-block-last
14 | div(slot="footer" class="dialog-footer")
15 | el-button(@click="closeModal") {{ $t('table.cancel') }}
16 | el-button(v-if="status === 'create'" :loading="loader" type="primary" @click="create()") Create
17 | el-button(v-else type="primary" :loading="loader" @click="update") {{ $t('table.confirm') }}
18 |
19 |
20 |
75 |
76 |
79 |
--------------------------------------------------------------------------------
/src/views/settings/teams/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Id
6 | .el-dialog-flex-subhead {{temp.id}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head Name
9 | .el-dialog-flex-subhead {{temp.name}}
10 | .el-dialog-flex-block
11 | .el-dialog-flex-head Name
12 | .el-dialog-flex-subhead {{temp.description}}
13 |
14 |
15 |
35 |
36 |
39 |
--------------------------------------------------------------------------------
/src/views/settings/teams/filters.vue:
--------------------------------------------------------------------------------
1 |
2 | div(class="time-entries-filters-container")
3 | div(class="filters-header") Filters
4 | div(class="time-entries-filters")
5 | div(style="font-size: 14px;") User name
6 | el-select(
7 | v-model="searchParams.user",
8 | filterable,
9 | remote,
10 | @focus="remoteGetUsers"
11 | clearable,
12 | placeholder="Please select a user"
13 | :remote-method="remoteGetUsers"
14 | )
15 | el-option(
16 | v-for="user in filterable('users')"
17 | :key="user.id"
18 | :label="user.name"
19 | :value="user.id"
20 | )
21 | div
22 | div(style="font-size: 14px;") Project name
23 | el-select(
24 | v-model="searchParams.project"
25 | filterable
26 | remote,
27 | @focus="remoteGetProjects"
28 | clearable,
29 | placeholder="Please select a project"
30 | :remote-method="remoteGetProjects"
31 | )
32 | el-option(
33 | v-for="project in filterable('projects')"
34 | :value="project.id"
35 | :key="project.id",
36 | :label="project.name")
37 | div(style="margin: 20px 0 10px;")
38 | div
39 | el-button(@click="filter") Filter
40 | el-button(@click="clearFilter" type="info") Clear Filters
41 |
42 |
43 |
81 |
82 |
85 |
--------------------------------------------------------------------------------
/src/views/settings/teams/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div()
3 | div.timebot-header Teams
4 | teamsTable
5 |
6 |
7 |
23 |
24 |
26 |
--------------------------------------------------------------------------------
/src/views/settings/teams/teamsTable.vue:
--------------------------------------------------------------------------------
1 |
2 | div
3 | div
4 | div(class="position-create-new-category-right")
5 | el-button(@click="handleCreate",
6 | class="filter-item create-new-category",
7 | type="primary") Add new team
8 | el-table(
9 | :key="tableKey"
10 | :data="list(type)"
11 | border
12 | fit
13 | highlight-current-row
14 | style="width: 100%;")
15 | el-table-column(:label="$t('table.id')" align="center" width="65")
16 | template(slot-scope="scope")
17 | span {{ scope.row.id }}
18 | el-table-column(label="Name")
19 | template(slot-scope="scope")
20 | span {{ scope.row.name }}
21 | el-table-column(label="Description")
22 | template(slot-scope="scope")
23 | span {{ scope.row.description }}
24 | el-table-column(:label="$t('table.actions')" width="230" class-name="small-padding fixed-width")
25 | template(slot-scope="scope")
26 | el-button(type="info" size="mini" @click="handleView(scope.row)") View
27 | el-button(type="primary" size="mini" @click="handleUpdate(scope.row)") {{ $t('table.edit') }}
28 | el-button(v-if="scope.row.status !== 'deleted'" size="mini" type="danger" @click="removeEntity(scope.row,'deleted')") {{ $t('table.delete') }}
29 | pagination(:type="type" v-if="list(type).length")
30 | modal-edit(ref="edit")
31 | modal-view(ref="view")
32 |
33 |
34 |
65 |
66 |
69 |
--------------------------------------------------------------------------------
/src/views/settings/users/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Users name
6 | .el-dialog-flex-subhead {{temp.name}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head UID
9 | .el-dialog-flex-subhead {{temp.uid}}
10 | .el-dialog-flex-block
11 | .el-dialog-flex-head Team
12 | .el-dialog-flex-subhead {{setTeam(temp.team)}}
13 | .el-dialog-flex-block
14 | .el-dialog-flex-head Created at
15 | .el-dialog-flex-date
16 | .el-dialog-flex-subhead
17 | span(v-if="temp['created-at']") {{temp['created-at'].date}}
18 | .el-dialog-flex-subhead
19 | span(v-if="temp['created-at']") {{temp['created-at'].time}}
20 | .el-dialog-flex-block
21 | .el-dialog-flex-head Status
22 | .el-dialog-flex-subhead
23 | span(v-if="temp['is-active']") Active
24 | span(v-else) Inactive
25 |
26 |
27 |
58 |
59 |
62 |
--------------------------------------------------------------------------------
/src/views/settings/users/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div()
3 | div(class="timebot-header") Users
4 | filters
5 | usersTable
6 |
7 |
8 |
26 |
27 |
29 |
--------------------------------------------------------------------------------
/src/views/time-logs/components/modals/view/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-dialog.el-dialog-view(:title="textMap[status]" :visible="modalVisible('view')" @close="closeModal")
3 | .el-dialog-flex
4 | .el-dialog-flex-block
5 | .el-dialog-flex-head Id
6 | .el-dialog-flex-subhead {{temp.id}}
7 | .el-dialog-flex-block
8 | .el-dialog-flex-head User
9 | .el-dialog-flex-subhead {{getIncluded(temp.user.id)}}
10 | .el-dialog-flex-block
11 | .el-dialog-flex-head Date
12 | .el-dialog-flex-subhead {{temp.date}}
13 | .el-dialog-flex-block
14 | .el-dialog-flex-head Details
15 | .el-dialog-flex-subhead {{temp.details}}
16 | .el-dialog-flex-block
17 | .el-dialog-flex-head Time
18 | .el-dialog-flex-subhead {{temp.time}}
19 | .el-dialog-flex-block
20 | .el-dialog-flex-head Project
21 | .el-dialog-flex-subhead {{getIncluded(temp.project.id)}}
22 | .el-dialog-flex-block
23 | .el-dialog-flex-head Ticket
24 | .el-dialog-flex-subhead {{temp.details}}
25 | .el-dialog-flex-block
26 | .el-dialog-flex-head Trello labels
27 | .el-dialog-flex-subhead {{temp['trello-labels']}}
28 |
29 |
30 |
54 |
55 |
58 |
--------------------------------------------------------------------------------
/src/views/time-logs/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div()
3 | div(class="timebot-header") Time logs
4 | filters
5 | div.time-entries-filters-bottom-head(v-if="structure.length") {{structure.title}}
6 | div(v-for="entity in structure" :key="entity.id")
7 | strong {{entity.name}}
8 | div(v-for="project in entity.projects" :key="project.id")
9 | | {{project.name}}: {{setHours(project.time_spent)}} hours {{setMinutes(project.time_spent)}} minutes
10 | strong Total: {{setHours(entity.total_time_spent)}} hours {{setMinutes(entity.total_time_spent)}} minutes
11 | time-entries-table
12 |
13 |
14 |
88 |
89 |
94 |
--------------------------------------------------------------------------------
/src/views/user-reports/index.vue:
--------------------------------------------------------------------------------
1 |
2 | div()
3 | div(class="timebot-header") Time to work
4 | div(class="time-entries-filters-container")
5 | div(class="time-entries-filters")
6 | div(class="filters-label") Date
7 | el-date-picker(
8 | v-model="date",
9 | type="daterange",
10 | range-separator="-",
11 | value-format="yyyy-MM-dd",
12 | start-placeholder="Start date",
13 | :picker-options="pickerOptions",
14 | @change="getTimeReports"
15 | end-placeholder="End date"
16 | prefix-icon="date-calendar")
17 | el-table(:data="getUsers()")
18 | el-table-column(
19 | prop="name",
20 | label="Users")
21 | el-table-column(
22 | prop="hours_worked",
23 | label="Hours worked")
24 | el-table-column(
25 | prop="hours_to_work",
26 | label=" Estimated Hours Worked")
27 | el-table-column(
28 | prop="difference"
29 | label="Difference"
30 | )
31 | template(slot-scope="scope") {{scope.row.difference.toFixed(2)}}
32 |
33 |
34 |
82 |
--------------------------------------------------------------------------------
/static/fonts/GILGON__.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/GILGON__.ttf
--------------------------------------------------------------------------------
/static/fonts/calibre/Calibre-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/calibre/Calibre-Light.otf
--------------------------------------------------------------------------------
/static/fonts/calibre/Calibre-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/calibre/Calibre-Medium.otf
--------------------------------------------------------------------------------
/static/fonts/calibre/Calibre-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/calibre/Calibre-Regular.otf
--------------------------------------------------------------------------------
/static/fonts/element-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/element-icons.ttf
--------------------------------------------------------------------------------
/static/fonts/element-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/element-icons.woff
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-Medium.woff
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-Medium.woff2
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-Regular.woff
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-Regular.woff2
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-SemiBold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-SemiBold.woff
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-SemiBold.woff2
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-Thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-Thin.woff
--------------------------------------------------------------------------------
/static/fonts/poppins/Poppins-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/Poppins-Thin.woff2
--------------------------------------------------------------------------------
/static/fonts/poppins/poppins-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/poppins-bold.woff
--------------------------------------------------------------------------------
/static/fonts/poppins/poppins-bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/fonts/poppins/poppins-bold.woff2
--------------------------------------------------------------------------------
/static/icons/border-plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/icons/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/icons/ic-create.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/icons/ic-delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/icons/ic-edit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/icons/ic-time-hover.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/icons/ic-view.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/tinymce4.7.5/plugins/codesample/css/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
2 | /**
3 | * prism.js default theme for JavaScript, CSS and HTML
4 | * Based on dabblet (http://dabblet.com)
5 | * @author Lea Verou
6 | */
7 |
8 | code[class*="language-"],
9 | pre[class*="language-"] {
10 | color: black;
11 | text-shadow: 0 1px white;
12 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
13 | direction: ltr;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 | line-height: 1.5;
20 |
21 | -moz-tab-size: 4;
22 | -o-tab-size: 4;
23 | tab-size: 4;
24 |
25 | -webkit-hyphens: none;
26 | -moz-hyphens: none;
27 | -ms-hyphens: none;
28 | hyphens: none;
29 | }
30 |
31 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
32 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
33 | text-shadow: none;
34 | background: #b3d4fc;
35 | }
36 |
37 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
38 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
39 | text-shadow: none;
40 | background: #b3d4fc;
41 | }
42 |
43 | @media print {
44 | code[class*="language-"],
45 | pre[class*="language-"] {
46 | text-shadow: none;
47 | }
48 | }
49 |
50 | /* Code blocks */
51 | pre[class*="language-"] {
52 | padding: 1em;
53 | margin: .5em 0;
54 | overflow: auto;
55 | }
56 |
57 | :not(pre) > code[class*="language-"],
58 | pre[class*="language-"] {
59 | background: #f5f2f0;
60 | }
61 |
62 | /* Inline code */
63 | :not(pre) > code[class*="language-"] {
64 | padding: .1em;
65 | border-radius: .3em;
66 | }
67 |
68 | .token.comment,
69 | .token.prolog,
70 | .token.doctype,
71 | .token.cdata {
72 | color: slategray;
73 | }
74 |
75 | .token.punctuation {
76 | color: #999;
77 | }
78 |
79 | .namespace {
80 | opacity: .7;
81 | }
82 |
83 | .token.property,
84 | .token.tag,
85 | .token.boolean,
86 | .token.number,
87 | .token.constant,
88 | .token.symbol,
89 | .token.deleted {
90 | color: #905;
91 | }
92 |
93 | .token.selector,
94 | .token.attr-name,
95 | .token.string,
96 | .token.char,
97 | .token.builtin,
98 | .token.inserted {
99 | color: #690;
100 | }
101 |
102 | .token.operator,
103 | .token.entity,
104 | .token.url,
105 | .language-css .token.string,
106 | .style .token.string {
107 | color: #a67f59;
108 | background: hsla(0, 0%, 100%, .5);
109 | }
110 |
111 | .token.atrule,
112 | .token.attr-value,
113 | .token.keyword {
114 | color: #07a;
115 | }
116 |
117 | .token.function {
118 | color: #DD4A68;
119 | }
120 |
121 | .token.regex,
122 | .token.important,
123 | .token.variable {
124 | color: #e90;
125 | }
126 |
127 | .token.important,
128 | .token.bold {
129 | font-weight: bold;
130 | }
131 | .token.italic {
132 | font-style: italic;
133 | }
134 |
135 | .token.entity {
136 | cursor: help;
137 | }
138 |
139 |
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/img/anchor.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/img/anchor.gif
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/img/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/img/loader.gif
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/img/object.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/img/object.gif
--------------------------------------------------------------------------------
/static/tinymce4.7.5/skins/lightgray/img/trans.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codica2/vue-timebot/7242b83c74cd5187f27e16be3106534ad7469a0a/static/tinymce4.7.5/skins/lightgray/img/trans.gif
--------------------------------------------------------------------------------