├── .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 | 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 | 3 | 4 | down-arrow 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 44 | 45 | 58 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 49 | 50 | 71 | -------------------------------------------------------------------------------- /src/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 61 | 62 | 64 | -------------------------------------------------------------------------------- /src/components/ScrollPane/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 57 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 3 | 4 | plus 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | weekly 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 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 | 3 | 4 | pie-chart 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/icons/svg/display.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/down-arrow-new-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | down-arrow 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/icons/svg/down-arrow-new.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | down-arrow 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/icons/svg/down-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | down-arrow 5 | Created with Sketch. 6 | 7 | 8 | 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 | 3 | 4 | absence 5 | Created with Sketch. 6 | 7 | 8 | 9 | 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 | 3 | 4 | teams 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/time.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | time 5 | Created with Sketch. 6 | 7 | 8 | 9 | 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 | 20 | 21 | 45 | 46 | 49 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/BarChart.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 74 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/components/PieChart.vue: -------------------------------------------------------------------------------- 1 | 6 | 70 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | -------------------------------------------------------------------------------- /src/views/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 43 | 44 | 83 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 66 | 67 | 93 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | 25 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 11 | 12 | 37 | 38 | 71 | -------------------------------------------------------------------------------- /src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/views/settings/admins/adminsTable.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 57 | 58 | 61 | -------------------------------------------------------------------------------- /src/views/settings/admins/components/modals/edit/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /src/views/settings/admins/components/modals/view/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /src/views/settings/admins/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 19 | -------------------------------------------------------------------------------- /src/views/settings/holidays/components/modals/edit/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 67 | 68 | 71 | -------------------------------------------------------------------------------- /src/views/settings/holidays/components/modals/view/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/views/settings/holidays/filters.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/views/settings/holidays/holidaysTable.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 71 | 72 | 75 | -------------------------------------------------------------------------------- /src/views/settings/holidays/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | 29 | -------------------------------------------------------------------------------- /src/views/settings/projects/components/modals/edit/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 79 | 80 | 83 | -------------------------------------------------------------------------------- /src/views/settings/projects/components/modals/view/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /src/views/settings/projects/filters.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /src/views/settings/projects/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /src/views/settings/teams/components/modals/edit/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 75 | 76 | 79 | -------------------------------------------------------------------------------- /src/views/settings/teams/components/modals/view/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /src/views/settings/teams/filters.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /src/views/settings/teams/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /src/views/settings/teams/teamsTable.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /src/views/settings/users/components/modals/view/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/views/settings/users/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /src/views/time-logs/components/modals/view/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 54 | 55 | 58 | -------------------------------------------------------------------------------- /src/views/time-logs/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 88 | 89 | 94 | -------------------------------------------------------------------------------- /src/views/user-reports/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 3 | 4 | plus 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /static/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | close 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /static/icons/ic-create.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic-create 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /static/icons/ic-delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic-delete 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /static/icons/ic-edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic-edit 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /static/icons/ic-time-hover.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic-time-hover 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /static/icons/ic-view.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic-view 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------