v2 first commit

This commit is contained in:
denisdulici
2019-11-16 10:21:14 +03:00
parent 5b23e9c2c4
commit 6d50fa8442
3075 changed files with 3451681 additions and 65594 deletions

View File

@ -0,0 +1,8 @@
<template>
<router-view></router-view>
</template>
<script>
export default {
}
</script>

View File

@ -1,22 +0,0 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('example', require('./components/Example.vue'));
const app = new Vue({
el: '#app'
});

View File

@ -10,7 +10,7 @@ window._ = require('lodash');
try {
window.$ = window.jQuery = require('jquery');
require('bootstrap-sass');
//require('bootstrap-sass');
} catch (e) {}
/**
@ -23,6 +23,7 @@ window.axios = require('axios');
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = window.Laravel.csrfToken;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
window.axios.defaults.headers.common['Content-Type'] = 'multipart/form-data';
/**
* Echo exposes an expressive API for subscribing to channels and listening
@ -38,3 +39,72 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// broadcaster: 'pusher',
// key: 'your-pusher-key'
// });
//(function ($) {
jQuery.fn.serializeFormJSON = function () {
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
jQuery.fn.serializeFormJSONShow = function () {
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(true);
} else {
o[this.name] = true;
}
});
return o;
};
jQuery.fn.serializeAll = function () {
var o = {};
var a = this;
$.each(this, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
//})(jQuery);
jQuery(document).ready(function () {
jQuery('input[type="radio"]').each(function () {
if (jQuery(this).parent().parent().hasClass('radio-yes-no')) {
if (jQuery(this).val() == 1) {
jQuery(this).parent().trigger('click');
}
}
});
});

View File

@ -0,0 +1,213 @@
<template>
<akaunting-modal
:title="title"
:show="display"
@cancel="onCancel"
v-if="display">
<template #modal-body>
<div class="modal-body text-left">
<div class="row">
<div class="col-md-12">
<base-input
v-model="form.name"
:label="text.name"
prepend-icon="fas fa-tag"
:placeholder="text.name"
inputGroupClasses="input-group-merge"
></base-input>
</div>
<!--
<akaunting-radio-group
:name="'enabled'"
:text="text.enabled"
:value="form.enabled"
@change="onEnabled"
:enable="text.yes"
:disable="text.no"
:col="'col-md-12'"
></akaunting-radio-group>
-->
</div>
</div>
</template>
<template #card-footer>
<div class="row">
<div class="col-md-12">
<div class="float-right">
<button type="button" class="btn btn-icon btn-outline-secondary" @click="onCancel">
<span class="btn-inner--icon"><i class="fas fa-times"></i></span>
<span class="btn-inner--text">{{ text.cancel }}</span>
</button>
<button :disabled="form.loading" type="button" class="btn btn-icon btn-success button-submit" @click="onSave">
<div v-if="form.loading" class="aka-loader-frame"><div class="aka-loader"></div></div>
<span v-if="!form.loading" class="btn-inner--icon"><i class="fas fa-save"></i></span>
<span v-if="!form.loading" class="btn-inner--text">{{ text.save }}</span>
</button>
</div>
</div>
</div>
</template>
</akaunting-modal>
</template>
<script>
import AkauntingModal from "./AkauntingModal";
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import Form from './../plugins/form';
import BulkAction from './../plugins/bulk-action';
import NProgressAxios from './../plugins/nprogress-axios';
export default {
name: 'akaunting-dashobard',
components: {
AkauntingModal,
AkauntingRadioGroup
},
props: {
show: Boolean,
title: {
type: String,
default: '',
description: "Modal header title"
},
text: {},
name: {
type: String,
default: ''
},
enabled: Number,
type: {
type: String,
default: 'create',
description: "Modal header title"
},
dashboard_id: {
type: Number,
default: 0,
description: "Modal header title"
},
},
data() {
return {
form: {
loading: false,
name: this.name,
enabled: this.enabled
},
display: this.show
};
},
methods: {
closeModal() {
this.$emit("update:show", false);
this.$emit("close");
},
onSave() {
this.form.loading = true;
let data = Object.assign({}, this.form);
delete data.loading;
var self = this;
var path = url + '/common/dashboards';
if ((self.type != 'create')) {
path = url + '/common/dashboards/' + self.dashboard_id;
data['_method'] = 'PATCH';
}
axios.post(path, data)
.then(function (response) {
self.form.loading = false;
if (response.data.redirect) {
self.form.loading = true;
window.location.href = response.data.redirect;
}
self.form.response = response.data;
})
.catch(function (error) {
this.errors.record(error.response.data.errors);
self.form.loading = false;
});
},
onCancel() {
this.display = false;
this.form.name = '';
this.form.enabled = 1;
this.$emit("cancel");
},
onEnabled(value) {
this.form.enabled = value;
}
},
watch: {
show(val) {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
} else {
documentClasses.remove("modal-open");
}
}
}
}
</script>
<style>
.loader10 {
width: 28px;
height: 28px;
border-radius: 50%;
position: relative;
animation: loader10 0.9s ease alternate infinite;
animation-delay: 0.36s;
top: 50%;
margin: -42px auto 0;
}
.loader10::after, .loader10::before {
content: '';
position: absolute;
width: 28px;
height: 28px;
border-radius: 50%;
animation: loader10 0.9s ease alternate infinite;
}
.loader10::before {
left: -40px;
animation-delay: 0.18s;
}
.loader10::after {
right: -40px;
animation-delay: 0.54s;
}
@keyframes loader10 {
0% {
box-shadow: 0 28px 0 -28px #0052ec;
}
100% {
box-shadow: 0 28px 0 #0052ec;
}
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<base-input :label="title"
:name="name"
:class="formClasses"
:error="formError"
:prependIcon="icon">
<flat-picker slot-scope="{focus, blur}"
@on-open="focus"
@on-close="blur"
:config="config"
class="form-control datepicker"
v-model="real_model"
@input="change">
</flat-picker>
</base-input>
</template>
<script>
import flatPicker from "vue-flatpickr-component";
import "flatpickr/dist/flatpickr.css";
export default {
name: 'akaunting-date',
components: {
flatPicker
},
props: {
title: {
type: String,
default: '',
description: "Modal header title"
},
placeholder: {
type: String,
default: '',
description: "Modal header title"
},
formClasses: null,
formError: null,
name: null,
value: null,
model: null,
config: null,
icon: {
type: String,
description: "Prepend icon (left)"
}
},
data() {
return {
real_model: this.model
}
},
mounted() {
this.real_model = this.value;
this.$emit('interface', this.real_model);
},
methods: {
change() {
this.$emit('interface', this.real_model);
}
}
}
</script>

View File

@ -0,0 +1,120 @@
<template>
<SlideYUpTransition :duration="animationDuration">
<div class="modal fade"
@click.self="closeModal"
:class="[{'show d-block': show}, {'d-none': !show}]"
v-show="show"
tabindex="-1"
role="dialog"
:aria-hidden="!show">
<div class="modal-dialog">
<slot name="modal-content">
<div class="modal-content">
<div class="card-header pb-2">
<slot name="card-header">
<h4 class="float-left"> {{ title }} </h4>
<button type="button" class="close" @click="onCancel" aria-hidden="true">&times;</button>
</slot>
</div>
<slot name="modal-body">
<div class="modal-body" v-html="message">
</div>
</slot>
<div class="card-footer border-top-0">
<slot name="card-footer">
<div class="float-right">
<button type="button" class="btn btn-outline-secondary" @click="onCancel"> {{ button_cancel }} </button>
<button type="button" class="btn btn-danger" @click="onConfirm"> {{ button_delete }} </button>
</div>
</slot>
</div>
</div>
</slot>
</div>
</div>
</SlideYUpTransition>
</template>
<script>
import { SlideYUpTransition } from "vue2-transitions";
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
import AkauntingRecurring from './AkauntingRecurring';
export default {
name: 'akaunting-modal',
componentName: 'akaunting-modal',
components: {
SlideYUpTransition,
AkauntingRadioGroup,
AkauntingSelect,
AkauntingDate,
AkauntingRecurring
},
props: {
show: Boolean,
title: {
type: String,
default: '',
description: "Modal header title"
},
message: {
type: String,
default: '',
description: "Modal body message"
},
button_cancel: {
type: String,
default: '',
description: "Modal footer cancel button text"
},
button_delete: {
type: String,
default: '',
description: "Modal footer delete button text"
},
animationDuration: {
type: Number,
default: 800,
description: "Modal transition duration"
}
},
methods: {
closeModal() {
this.$emit("update:show", false);
this.$emit("close");
},
onConfirm() {
this.$emit("confirm");
},
onCancel() {
this.$emit("cancel");
}
},
watch: {
show(val) {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
} else {
documentClasses.remove("modal-open");
}
}
}
}
</script>
<style>
.modal.show {
background-color: rgba(0, 0, 0, 0.3);
}
</style>

View File

@ -0,0 +1,164 @@
<template>
<div class="row col-md-6 pr-0">
<base-input :label="title"
name="recurring_frequency"
:class="frequencyClasses"
:error="frequencyError">
<el-select v-model="recurring_frequency" @input="change" filterable
:placeholder="placeholder">
<template slot="prefix">
<span class="el-input__suffix-inner el-select-icon">
<i :class="'el-input__icon el-icon-refresh'"></i>
</span>
</template>
<el-option v-for="(label, value) in frequencyOptions"
:key="label"
:label="label"
:value="value">
</el-option>
</el-select>
</base-input>
<base-input :label="''"
name="recurring_interval"
type="number"
:value="0"
class="recurring-single"
:class="invertalClasses"
:error="intervalError"
v-model="recurring_interval"
>
</base-input>
<base-input :label="''"
name="recurring_custom_frequency"
class="recurring-single"
:class="customFrequencyClasses"
:error="customFrequencyError"
>
<el-select v-model="recurring_custom_frequency" @input="change" filterable
:placeholder="placeholder">
<el-option v-for="(label, value) in customFrequencyOptions"
:key="label"
:label="label"
:value="value">
</el-option>
</el-select>
</base-input>
<base-input :label="''"
name="recurring_count"
type="number"
:value="0"
class="recurring-single"
:class="countClasses"
:error="countError"
v-model="recurring_count"
>
</base-input>
</div>
</template>
<script>
import { Select, Option } from 'element-ui'
export default {
name: 'akaunting-recurring',
components: {
[Select.name]: Select,
[Option.name]: Option,
},
props: {
title: {
type: String,
default: '',
description: "Modal header title"
},
placeholder: {
type: String,
default: '',
description: "Modal header title"
},
formClasses: null,
formError: null,
frequencyOptions: null,
frequencyValue: null,
frequencyError: null,
intervalValue: null,
intervalError: null,
customFrequencyOptions: null,
customFrequencyValue: null,
customFrequencyError: null,
countValue: null,
countError: null,
icon: {
type: String,
description: "Prepend icon (left)"
}
},
data() {
return {
recurring_frequency: this.frequencyValue,
recurring_interval: this.intervalValue,
recurring_custom_frequency: this.customFrequencyValue,
recurring_count: this.countValue,
frequencyClasses: 'col-md-12',
invertalClasses: 'col-md-2 d-none',
customFrequencyClasses: 'col-md-4 d-none',
countClasses: 'col-md-2 d-none'
}
},
mounted() {
this.recurring_frequency = this.frequencyValue;
this.frequencyChanges();
this.$emit('recurring_frequency', this.recurring_frequency);
this.$emit('recurring_interval', this.recurring_interval);
this.$emit('recurring_custom_frequency', this.recurring_custom_frequency);
this.$emit('recurring_count', this.recurring_count);
},
methods: {
change() {
this.$emit('change', this.recurring_frequency);
this.$emit('recurring_frequency', this.recurring_frequency);
this.$emit('recurring_interval', this.recurring_interval);
this.$emit('recurring_custom_frequency', this.recurring_custom_frequency);
this.$emit('recurring_count', this.recurring_count);
this.frequencyChanges();
},
frequencyChanges() {
if (this.recurring_frequency == 'custom') {
this.frequencyClasses = 'col-md-4';
this.invertalClasses = 'col-md-2';
this.customFrequencyClasses = 'col-md-4';
this.countClasses = 'col-md-2 pr-0';
} else if (this.recurring_frequency == 'no' || this.recurring_frequency == '') {
this.frequencyClasses = 'col-md-12 pr-0';
this.invertalClasses = 'col-md-2 d-none';
this.customFrequencyClasses = 'col-md-4 d-none';
this.countClasses = 'col-md-2 d-none';
} else {
this.frequencyClasses = 'col-md-10';
this.invertalClasses = 'col-md-2 d-none';
this.customFrequencyClasses = 'col-md-4 d-none';
this.countClasses = 'col-md-2 pr-0';
}
}
}
}
</script>

View File

@ -0,0 +1,114 @@
<template>
<component class="dropdown col-md-11 pl-0"
:is="tag"
:class="[{show: isOpen}, {'dropdown': direction === 'down'}, {'dropup': direction ==='up'}]"
aria-haspopup="true"
:aria-expanded="isOpen"
@click="toggleDropDown"
v-click-outside="closeDropDown">
<input
autocomplete="off"
placeholder="Type to search.."
name="search"
type="text"
class="form-control form-control-sm table-header-search"
style="width: 100%;">
<div class="dropdown-menu"
ref="menu"
:class="[{'dropdown-menu-right': position === 'right'}, {show: isOpen}, menuClasses]"
>
<a class="dropdown-item d-none" href="#about">About</a>
</div>
</component>
</template>
<script>
export default {
props: {
direction: {
type: String,
default: "down"
},
title: {
type: String,
description: "Dropdown title"
},
icon: {
type: String,
description: "Icon for dropdown title"
},
position: {
type: String,
description: "Position of dropdown menu (e.g right|left)"
},
menuClasses: {
type: [String, Object],
description: "Dropdown menu classes"
},
hideArrow: {
type: Boolean,
description: "Whether dropdown arrow should be hidden"
},
appendToBody: {
type: Boolean,
default: true,
description: "Whether dropdown should be appended to document body"
},
tag: {
type: String,
default: "div",
description: "Dropdown html tag (e.g div, li etc)"
}
},
data() {
return {
isOpen: false
}
},
methods: {
toggleDropDown() {
this.isOpen = !this.isOpen;
this.$emit("change", this.isOpen);
},
closeDropDown() {
this.isOpen = false;
this.$emit("change", this.isOpen);
}
},
directives: {
'click-outside': {
bind: function(el, binding, vNode) {
// Provided expression must evaluate to a function.
if (typeof binding.value !== 'function') {
const compName = vNode.context.name
let warn = `[Vue-click-outside:] provided expression '${binding.expression}' is not a function, but has to be`
if (compName) { warn += `Found in component '${compName}'` }
console.warn(warn)
}
// Define Handler and cache it on the element
const bubble = binding.modifiers.bubble
const handler = (e) => {
if (bubble || (!el.contains(e.target) && el !== e.target)) {
binding.value(e)
}
}
el.__vueClickOutside__ = handler
// add Event Listeners
document.addEventListener('click', handler)
},
unbind: function(el, binding) {
// Remove Event Listeners
document.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
}
}
</script>

View File

@ -0,0 +1,160 @@
<template>
<base-input :label="title"
:name="name"
:class="formClasses"
:error="formError">
<el-select v-model="real_model" @input="change" filterable v-if="!multiple"
:placeholder="placeholder">
<div v-if="addNew" class="el-select-dropdown__wrap" slot="empty">
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item hover" @click="onAddItem">
<span>{{ add_new_text }}</span>
</li>
</ul>
</div>
<template slot="prefix">
<span class="el-input__suffix-inner el-select-icon">
<i :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
</span>
</template>
<el-option v-if="!group" v-for="(label, value) in selectOptions"
:key="value"
:label="label"
:value="value">
</el-option>
<el-option-group
v-if="group"
v-for="(options, name) in selectOptions"
:key="name"
:label="name">
<el-option
v-for="(label, value) in options"
:key="value"
:label="label"
:value="value">
</el-option>
</el-option-group>
</el-select>
<el-select v-model="real_model" @input="change" filterable v-if="multiple" multiple collapse-tags
:placeholder="placeholder">
<div v-if="addNew" class="el-select-dropdown__wrap" slot="empty">
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item hover" @click="onAddItem">
<span>{{ add_new_text }}</span>
</li>
</ul>
</div>
<template slot="prefix">
<span class="el-input__suffix-inner el-select-icon">
<i :class="'select-icon-position el-input__icon fa fa-' + icon"></i>
</span>
</template>
<el-option v-if="!group" v-for="(label, value) in selectOptions"
:key="value"
:label="label"
:value="value">
</el-option>
<el-option-group
v-if="group"
v-for="(options, name) in selectOptions"
:key="name"
:label="name">
<el-option
v-for="(label, value) in options"
:key="value"
:label="label"
:value="value">
</el-option>
</el-option-group>
</el-select>
</base-input>
</template>
<script>
import { Select, Option, OptionGroup } from 'element-ui';
export default {
name: "akaunting-select",
components: {
[Select.name]: Select,
[Option.name]: Option,
[OptionGroup.name]: OptionGroup,
},
props: {
title: {
type: String,
default: '',
description: "Modal header title"
},
placeholder: {
type: String,
default: '',
description: "Modal header title"
},
formClasses: null,
formError: null,
name: null,
value: null,
options: null,
model: null,
icon: {
type: String,
description: "Prepend icon (left)"
},
addNew: false,
addNewText: null,
addNewPath: null,
group: false,
multiple:false
},
data() {
return {
add_new_text: this.addNewText,
selectOptions: this.options,
real_model: this.model,
}
},
mounted() {
this.real_model = this.value;
this.$emit('interface', this.real_model);
},
methods: {
change() {
this.$emit('change', this.real_model);
this.$emit('interface', this.real_model);
},
onAddItem() {
// Get Select Input value
var value = this.$children[0].$children[0].$children[0].$refs.input.value;
this.$emit('new_item', {
value: value,
path: this.addNewPath,
title: this.addNewText,
});
}
},
watch: {
options: function (options) {
// update options
this.selectOptions = options;
}
},
}
</script>

View File

@ -0,0 +1,365 @@
<template>
<akaunting-modal
:title="title"
:show="display"
@cancel="onCancel"
v-if="display">
<template #modal-body>
<div class="modal-body text-left">
<div class="row">
<div class="col-md-12">
<base-input
v-model="form.name"
:label="text.name"
prepend-icon="fas fa-font"
:placeholder="placeholder.name"
inputGroupClasses="input-group-merge">
</base-input>
</div>
<div class="col-md-12">
<base-input
:label="text.type">
<span class="el-input__prefix">
<span class="el-input__suffix-inner el-select-icon">
<i class="select-icon-position el-input__icon fa fa-bars"></i>
</span>
</span>
<el-select
class="select-primary"
v-model="form.widget_id" filterable
:placeholder="placeholder.type">
<el-option v-for="option in types"
class="select-primary"
:key="option.name"
:label="option.name"
:value="option.id">
</el-option>
</el-select>
</base-input>
</div>
<div class="col-md-12">
<base-input
:label="text.width">
<span class="el-input__prefix">
<span class="el-input__suffix-inner el-select-icon">
<i class="select-icon-position el-input__icon fas fa-ruler-horizontal"></i>
</span>
</span>
<el-select
class="select-primary"
v-model="form.width" filterable
:placeholder="placeholder.width">
<el-option v-for="option in widthOptions"
class="select-primary"
:key="option.label"
:label="option.label"
:value="option.value">
</el-option>
</el-select>
</base-input>
</div>
<div class="col-md-12">
<base-input
v-model="form.sort"
:label="text.sort"
prepend-icon="fas fa-sort"
:placeholder="placeholder.sort"
inputGroupClasses="input-group-merge"></base-input>
</div>
</div>
</div>
</template>
<template #card-footer>
<div class="row">
<div class="col-md-12">
<div class="float-right">
<button type="button" class="btn btn-icon btn-outline-secondary" @click="onCancel">
<span class="btn-inner--icon"><i class="fas fa-times"></i></span>
<span class="btn-inner--text">{{ text.cancel }}</span>
</button>
<button :disabled="form.loading" type="button" class="btn btn-icon btn-success button-submit" @click="onSave">
<div v-if="form.loading" class="aka-loader-frame"><div class="aka-loader"></div></div>
<span v-if="!form.loading" class="btn-inner--icon"><i class="fas fa-save"></i></span>
<span v-if="!form.loading" class="btn-inner--text">{{ text.save }}</span>
</button>
</div>
</div>
</div>
</template>
</akaunting-modal>
</template>
<style>
.form-group .el-select {
width: 100%
}
.el-input__prefix
{
left: 20px;
z-index: 999;
top: 2px;
}
.el-select .el-input .el-input__inner {
font-size: .875rem;
width: 100%;
height: calc(1.5em + 1.25rem + 2px);
-webkit-transition: all .15s ease-in-out;
transition: all .15s ease-in-out
}
@media (prefers-reduced-motion:reduce) {
.el-select .el-input .el-input__inner {
-webkit-transition: none;
transition: none
}
}
.el-select .el-input .el-input__inner:focus {
border-color: #324cdd!important;
border: 1px solid #2a44db
}
.el-select .el-input .el-input__inner::-webkit-input-placeholder {
color: #adb5bd;
opacity: 1
}
.el-select .el-input .el-input__inner::-moz-placeholder {
color: #adb5bd;
opacity: 1
}
.el-select .el-input .el-input__inner::-ms-input-placeholder {
color: #adb5bd;
opacity: 1
}
.el-select .el-input .el-input__inner::placeholder {
color: #adb5bd;
opacity: 1
}
.el-select .el-input .el-input__inner:disabled {
background-color: #e9ecef;
opacity: 1
}
.el-select .el-input.is-focus .el-input__inner {
border-color: #324cdd!important;
border: 1px solid #2a44db
}
.el-select-dropdown.el-popper .el-select-dropdown__item.selected,.el-select-dropdown.el-popper.is-multiple .el-select-dropdown__item.selected {
color: #5e72e4
}
.el-select .el-select__tags {
padding-left: 10px
}
.el-select .el-select__tags .el-tag {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: .625rem .625rem .5rem;
height: 25px;
margin: .125rem;
border-radius: .25rem;
background: #172b4d;
color: #fff;
line-height: 1.5;
cursor: pointer;
-webkit-box-shadow: 0 1px 2px rgba(68,68,68,.25);
box-shadow: 0 1px 2px rgba(68,68,68,.25);
-webkit-transition: all .15s ease;
transition: all .15s ease
}
@media (prefers-reduced-motion:reduce) {
.el-select .el-select__tags .el-tag {
-webkit-transition: none;
transition: none
}
}
.el-select .el-select__tags .el-tag .el-tag__close.el-icon-close {
background-color: transparent;
color: #fff;
font-size: 12px
}
</style>
<script>
import axios from 'axios';
import { Select, Option } from 'element-ui';
import AkauntingModal from "./AkauntingModal";
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import Form from './../plugins/form';
import NProgressAxios from './../plugins/nprogress-axios';
export default {
name: 'akaunting-widget',
components: {
AkauntingModal,
[Select.name]: Select,
[Option.name]: Option,
},
props: {
show: Boolean,
action: {
type: String,
default: 'create',
description: "Modal header title"
},
title: {
type: String,
default: '',
description: "Modal header title"
},
text: {},
placeholder: {},
name: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
type: {
type: String,
default: 'create',
description: "Modal header title"
},
sort: {
type: String,
default: 'create',
description: "Modal header title"
},
types: {},
dashboard_id: {
type: Number,
default: 0,
description: "Modal header title"
},
widget_id: {
type: Number,
default: 0,
description: "Modal header title"
},
},
data() {
return {
widthOptions: [
{
label: '25%',
value: 'col-md-3'
},
{
label: '33%',
value: 'col-md-4'
},
{
label: '50%',
value: 'col-md-6'
},
{
label: '100%',
value: 'col-md-12'
}
],
form: {
loading: false,
name: this.name,
width: this.width,
widget_id: this.type,
sort: this.sort,
dashboard_id: this.dashboard_id
},
display: this.show
};
},
methods: {
closeModal() {
this.$emit("update:show", false);
this.$emit("close");
},
onSave() {
this.form.loading = true;
let data = Object.assign({}, this.form);
delete data.loading;
var self = this;
var path = url + '/common/widgets';
if ((self.action != 'create')) {
path = url + '/common/widgets/' + self.widget_id;
data['_method'] = 'PATCH';
}
axios.post(path, data)
.then(function (response) {
self.form.loading = false;
if (response.data.redirect) {
self.form.loading = true;
window.location.href = response.data.redirect;
}
self.form.response = response.data;
})
.catch(function (error) {
this.errors.record(error.response.data.errors);
self.form.loading = false;
});
},
onCancel() {
this.display = false;
this.form.name = '';
this.form.enabled = 1;
this.$emit("cancel");
},
onEnabled(value) {
this.form.enabled = value;
}
},
watch: {
show(val) {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
} else {
documentClasses.remove("modal-open");
}
}
}
}
</script>

View File

@ -0,0 +1,53 @@
<template>
<component :is="tag"
class="badge"
:class="
[`badge-${type}`,
rounded ? `badge-pill` : '',
circle && 'badge-circle',
{[`badge-${size}`]: size},
]">
<slot>
<i v-if="icon" :class="icon"></i>
</slot>
</component>
</template>
<script>
export default {
name: "badge",
props: {
tag: {
type: String,
default: "span",
description: "Html tag to use for the badge."
},
rounded: {
type: Boolean,
default: false,
description: "Whether badge is of pill type"
},
circle: {
type: Boolean,
default: false,
description: "Whether badge is circle"
},
icon: {
type: String,
default: "",
description: "Icon name. Will be overwritten by slot if slot is used"
},
type: {
type: String,
default: "default",
description: "Badge type (primary|info|danger|default|warning|success)"
},
size: {
type: String,
description: 'Badge size (md, lg)',
default: ''
}
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,74 @@
<template>
<fade-transition>
<div
v-if="visible"
class="alert"
:class="[
`alert-${type}`,
{ 'alert-dismissible': dismissible }
]"
role="alert"
>
<slot v-if="!dismissible"></slot>
<template v-else>
<template v-if="icon || $slots.icon">
<slot name="icon">
<span class="alert-icon" data-notify="icon">
<i :class="icon"></i>
</span>
</slot>
</template>
<span class="alert-text"> <slot></slot> </span>
<slot name="dismiss-icon">
<button type="button"
class="close"
data-dismiss="alert"
aria-label="Close"
@click="dismissAlert">
<span aria-hidden="true">×</span>
</button>
</slot>
</template>
</div>
</fade-transition>
</template>
<script>
import { FadeTransition } from 'vue2-transitions';
export default {
name: 'base-alert',
components: {
FadeTransition
},
props: {
type: {
type: String,
default: 'default',
description: 'Alert type'
},
dismissible: {
type: Boolean,
default: false,
description: 'Whether alert is dismissible (closeable)'
},
icon: {
type: String,
default: '',
description: 'Alert icon to display'
}
},
data() {
return {
visible: true
};
},
methods: {
dismissAlert() {
this.visible = false;
}
}
};
</script>

View File

@ -0,0 +1,81 @@
<template>
<component
:is="tag"
:type="tag === 'button' ? nativeType : ''"
:disabled="disabled || loading"
@click="handleClick"
class="btn"
:class="[
{ 'rounded-circle': round },
{ 'btn-block': block },
{ 'btn-wd': wide },
{ 'btn-icon btn-fab': icon },
{ [`btn-${type}`]: type && !outline },
{ [`btn-${size}`]: size },
{ [`btn-outline-${type}`]: outline && type },
{ 'btn-link': link },
{ disabled: disabled && tag !== 'button' }
]"
>
<slot name="loading">
<i v-if="loading" class="fas fa-spinner fa-spin"></i>
</slot>
<slot></slot>
</component>
</template>
<script>
export default {
name: 'base-button',
props: {
tag: {
type: String,
default: 'button',
description: 'Button html tag'
},
round: Boolean,
icon: Boolean,
block: Boolean,
loading: Boolean,
wide: Boolean,
disabled: Boolean,
type: {
type: String,
default: 'default',
description: 'Button type (primary|secondary|danger etc)'
},
nativeType: {
type: String,
default: 'button',
description: 'Button native type (e.g button, input etc)'
},
size: {
type: String,
default: '',
description: 'Button size (sm|lg)'
},
outline: {
type: Boolean,
description: 'Whether button is outlined (only border has color)'
},
link: {
type: Boolean,
description: 'Whether button is a link (no borders or background)'
}
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>
<style scoped lang="scss">
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
i {
padding: 0 3px;
}
</style>

View File

@ -0,0 +1,100 @@
<template>
<component
:is="tag"
:class="[{ show: isOpen }, `drop${direction}`]"
@click="toggleDropDown"
v-click-outside="closeDropDown"
>
<slot name="title-container" :is-open="isOpen">
<component
:is="titleTag"
class="btn-rotate"
:class="[{'dropdown-toggle': hasToggle}, titleClasses]"
:aria-expanded="isOpen"
data-toggle="dropdown"
>
<slot name="title" :is-open="isOpen">
<i :class="icon"></i> {{ title }}
</slot>
</component>
</slot>
<ul
class="dropdown-menu"
:class="[
{ show: isOpen },
{ 'dropdown-menu-right': menuOnRight },
menuClasses
]"
>
<slot></slot>
</ul>
</component>
</template>
<script>
export default {
name: 'base-dropdown',
props: {
tag: {
type: String,
default: 'div',
description: 'Dropdown html tag (e.g div, ul etc)'
},
titleTag: {
type: String,
default: 'button',
description: 'Dropdown title (toggle) html tag'
},
title: {
type: String,
description: 'Dropdown title'
},
direction: {
type: String,
default: 'down', // up | down
description: 'Dropdown menu direction (up|down)'
},
icon: {
type: String,
description: 'Dropdown icon'
},
titleClasses: {
type: [String, Object, Array],
description: 'Title css classes'
},
menuClasses: {
type: [String, Object],
description: 'Menu css classes'
},
menuOnRight: {
type: Boolean,
description: 'Whether menu should appear on the right'
},
hasToggle: {
type: Boolean,
description: 'Whether dropdown has arrow icon shown',
default: true
}
},
data() {
return {
isOpen: false
};
},
methods: {
toggleDropDown() {
this.isOpen = !this.isOpen;
this.$emit('change', this.isOpen);
},
closeDropDown() {
this.isOpen = false;
this.$emit('change', false);
}
}
};
</script>
<style lang="scss" scoped>
.dropdown {
cursor: pointer;
user-select: none;
}
</style>

View File

@ -0,0 +1,23 @@
<template>
<div class="header" :class="{[`bg-${type}`]: type}">
<div class="container-fluid">
<div class="header-body">
<slot></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'base-header',
props: {
type: {
type: String,
default: 'success',
description: 'Header background type'
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,134 @@
<template>
<ul class="pagination" :class="[size && `pagination-${size}`, align && `justify-content-${align}`]">
<li class="page-item prev-page" :class="{disabled: value === 1}">
<a class="page-link" aria-label="Previous" @click="prevPage">
<span aria-hidden="true"><i class="fa fa-angle-left" aria-hidden="true"></i></span>
</a>
</li>
<li class="page-item" :class="{active: value === item}"
:key="item"
v-for="item in range(minPage, maxPage)">
<a class="page-link" @click="changePage(item)">{{item}}</a>
</li>
<li class="page-item next-page" :class="{disabled: value === totalPages}">
<a class="page-link" aria-label="Next" @click="nextPage">
<span aria-hidden="true"><i class="fa fa-angle-right" aria-hidden="true"></i></span>
</a>
</li>
</ul>
</template>
<script>
export default {
name: "base-pagination",
props: {
pageCount: {
type: Number,
default: 0,
description:
"Pagination page count. This should be specified in combination with perPage"
},
perPage: {
type: Number,
default: 10,
description:
"Pagination per page. Should be specified with total or pageCount"
},
total: {
type: Number,
default: 0,
description:
"Can be specified instead of pageCount. The page count in this case will be total/perPage"
},
value: {
type: Number,
default: 1,
description: "Pagination value"
},
size: {
type: String,
default: "",
description: "Pagination size"
},
align: {
type: String,
default: "",
description: "Pagination alignment (e.g center|start|end)"
}
},
computed: {
totalPages() {
if (this.pageCount > 0) return this.pageCount;
if (this.total > 0) {
return Math.ceil(this.total / this.perPage);
}
return 1;
},
pagesToDisplay() {
if (this.totalPages > 0 && this.totalPages < this.defaultPagesToDisplay) {
return this.totalPages;
}
return this.defaultPagesToDisplay;
},
minPage() {
if (this.value >= this.pagesToDisplay) {
const pagesToAdd = Math.floor(this.pagesToDisplay / 2);
const newMaxPage = pagesToAdd + this.value;
if (newMaxPage > this.totalPages) {
return this.totalPages - this.pagesToDisplay + 1;
}
return this.value - pagesToAdd;
} else {
return 1;
}
},
maxPage() {
if (this.value >= this.pagesToDisplay) {
const pagesToAdd = Math.floor(this.pagesToDisplay / 2);
const newMaxPage = pagesToAdd + this.value;
if (newMaxPage < this.totalPages) {
return newMaxPage;
} else {
return this.totalPages;
}
} else {
return this.pagesToDisplay;
}
}
},
data() {
return {
defaultPagesToDisplay: 5
};
},
methods: {
range(min, max) {
let arr = [];
for (let i = min; i <= max; i++) {
arr.push(i);
}
return arr;
},
changePage(item) {
this.$emit("input", item);
},
nextPage() {
if (this.value < this.totalPages) {
this.$emit("input", this.value + 1);
}
},
prevPage() {
if (this.value > 1) {
this.$emit("input", this.value - 1);
}
}
},
watch: {
perPage() {
this.$emit("input", 1);
},
total() {
this.$emit("input", 1);
}
}
};
</script>

View File

@ -0,0 +1,90 @@
<template>
<div class="wrapper">
<div :class="`progress-${type}`" v-if="showLabel">
<div class="progress-label">
<slot name="label">
<span>{{label}}</span>
</slot>
</div>
<div class="progress-percentage">
<slot>
<span>{{value}}%</span>
</slot>
</div>
</div>
<div class="progress"
:class="[{[`progress-${size}`]: size}, progressClasses]"
:style="`height: ${height}px`">
<div class="progress-bar"
:class="computedClasses"
role="progressbar"
:aria-valuenow="value"
aria-valuemin="0"
aria-valuemax="100"
:style="`width: ${value}%;`">
</div>
</div>
</div>
</template>
<script>
export default {
name: "base-progress",
props: {
striped: {
type: Boolean,
description: "Whether progress is striped"
},
animated: {
type: Boolean,
description:
"Whether progress is animated (works only with `striped` prop together)"
},
label: {
type: String,
description: "Progress label (shown on the left above progress)"
},
height: {
type: Number,
default: 3,
description: "Progress line height"
},
type: {
type: String,
default: "default",
description: "Progress type (e.g danger, primary etc)"
},
showLabel: {
type: Boolean,
default: false
},
progressClasses: {
type: [Array, String],
default: '',
description: 'Progress css classes'
},
size: {
type: String,
default: ''
},
value: {
type: Number,
default: 0,
validator: value => {
return value >= 0 && value <= 100;
},
description: "Progress value"
}
},
computed: {
computedClasses() {
return [
{ "progress-bar-striped": this.striped },
{ "progress-bar-animated": this.animated },
{ [`bg-${this.type}`]: this.type }
];
}
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,96 @@
<template>
<div class="slider" :disabled="disabled"></div>
</template>
<script>
import noUiSlider from 'nouislider';
export default {
name: 'base-slider',
props: {
value: {
type: [String, Array, Number],
description: 'slider value'
},
disabled: {
type: Boolean,
default: false,
description: 'whether the slider is disabled'
},
start: {
type: [Number, Array],
default: 0,
description:
'[noUi Slider start](https://refreshless.com/nouislider/slider-options/#section-start)'
},
connect: {
type: [Boolean, Array],
default: () => [true, false],
description:
'[noUi Slider connect](https://refreshless.com/nouislider/slider-options/#section-connect)'
},
range: {
type: Object,
default: () => {
return {
min: 0,
max: 100
};
},
description:
'[noUi Slider range](https://refreshless.com/nouislider/slider-values/#section-range)'
},
options: {
type: Object,
default: () => {
return {};
},
description:
'[noUi Slider options](https://refreshless.com/nouislider/slider-options/)'
}
},
data() {
return {
slider: null
};
},
methods: {
createSlider() {
noUiSlider.create(this.$el, {
start: this.value || this.start,
connect: Array.isArray(this.value) ? true : this.connect,
range: this.range,
...this.options
});
const slider = this.$el.noUiSlider;
slider.on('slide', () => {
let value = slider.get();
if (value !== this.value) {
this.$emit('input', value);
}
});
}
},
mounted() {
this.createSlider();
},
watch: {
value(newValue, oldValue) {
const slider = this.$el.noUiSlider;
const sliderValue = slider.get();
if (newValue !== oldValue && sliderValue !== newValue) {
if (Array.isArray(sliderValue) && Array.isArray(newValue)) {
if (
oldValue.length === newValue.length &&
oldValue.every((v, i) => v === newValue[i])
) {
slider.set(newValue);
}
} else {
slider.set(newValue);
}
}
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,49 @@
<template>
<label class="custom-toggle" :class="switchClass">
<input type="checkbox" v-model="model">
<span class="custom-toggle-slider rounded-circle"
:data-label-off="offText"
:data-label-on="onText">
</span>
</label>
</template>
<script>
export default {
name: 'base-switch',
props: {
value: [Array, Boolean],
type: String,
onText: {
type: String,
default: 'Yes'
},
offText: {
type: String,
default: 'No'
}
},
computed: {
switchClass() {
let baseClass = 'custom-toggle-';
if (this.type) {
return baseClass + this.type
}
return ''
},
model: {
get() {
return this.value;
},
set(value) {
this.$emit('input', value);
}
}
},
methods: {
triggerToggle() {
this.model = !this.model;
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,70 @@
<template>
<table class="table tablesorter" :class="tableClass">
<thead :class="theadClasses">
<tr>
<slot name="columns" :columns="columns">
<th v-for="column in columns" :key="column">{{ column }}</th>
</slot>
</tr>
</thead>
<tbody :class="tbodyClasses">
<tr v-for="(item, index) in data" :key="index">
<slot :row="item" :index="index">
<td
v-for="(column, index) in columns"
:key="index"
v-if="hasValue(item, column)"
>
{{ itemValue(item, column) }}
</td>
</slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'base-table',
props: {
columns: {
type: Array,
default: () => [],
description: 'Table columns'
},
data: {
type: Array,
default: () => [],
description: 'Table data'
},
type: {
type: String, // striped | hover
default: '',
description: 'Whether table is striped or hover type'
},
theadClasses: {
type: String,
default: '',
description: '<thead> css classes'
},
tbodyClasses: {
type: String,
default: '',
description: '<tbody> css classes'
}
},
computed: {
tableClass() {
return this.type && `table-${this.type}`;
}
},
methods: {
hasValue(item, column) {
return item[column.toLowerCase()] !== 'undefined';
},
itemValue(item, column) {
return item[column.toLowerCase()];
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,26 @@
<template>
<nav aria-label="breadcrumb">
<ol class="breadcrumb"
:class="[{[`bg-${type}`]: type}, listClasses]">
<slot></slot>
</ol>
</nav>
</template>
<script>
export default {
name: 'breadcrumb',
props: {
type: {
type: String,
default: '',
description: 'Breadcrumb background type'
},
listClasses: {
type: [String, Object],
default: '',
description: 'Breadcrumb list classes'
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,16 @@
<template>
<li class="breadcrumb-item" :class="{ active: active }"><slot></slot></li>
</template>
<script>
export default {
name: 'breadcrumb-item',
props: {
active: {
type: Boolean,
default: false,
description: 'Whether breadcrumb item is active'
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,45 @@
<template>
<bread-crumb list-classes="breadcrumb-links breadcrumb-dark">
<BreadCrumbItem>
<li class="breadcrumb-item">
<router-link to="/">
<i class="fas fa-home"></i>
</router-link>
</li>
</BreadCrumbItem>
<BreadCrumbItem
v-for="(route, index) in $route.matched.slice()"
:key="route.name"
:active="index === $route.matched.length - 1"
style="display:inline-block"
>
<router-link
:to="{ name: route.name }"
v-if="index < $route.matched.length - 1"
>
{{ route.name }}
</router-link>
<span v-else>{{ route.name }}</span>
</BreadCrumbItem>
</bread-crumb>
</template>
<script>
import BreadCrumb from './Breadcrumb';
import BreadCrumbItem from './BreadcrumbItem';
export default {
name: 'route-breadcrumb',
components: {
BreadCrumb,
BreadCrumbItem
},
methods: {
getBreadName(route) {
return route.name;
}
}
};
</script>
<style scoped></style>

View File

@ -0,0 +1,39 @@
<template>
<div class="btn-group-toggle" data-toggle="buttons">
<label class="btn" :class="[{ active: value }, buttonClasses]">
<input v-model="model" type="checkbox" checked="" autocomplete="off">
<slot></slot>
</label>
</div>
</template>
<script>
export default {
name: 'button-checkbox',
props: {
value: {
type: Boolean,
description: 'Checked value'
},
buttonClasses: {
type: [String, Object],
description: 'Inner button css classes'
}
},
model: {
prop: 'value',
event: 'change'
},
computed: {
model: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,47 @@
<template>
<div class="btn-group-toggle" data-toggle="buttons">
<label v-for="(option, index) in options"
:key="index"
class="btn"
:class="[{ active: value === option.value }, buttonClasses]">
<input :value="option.value" v-model="model" type="radio" id="option1" autocomplete="off" checked="">
{{option.label}}
</label>
</div>
</template>
<script>
export default {
name: 'button-radio-group',
props: {
options: {
type: Array,
description: 'Radio options. Should be an array of objects {value: "", label: ""}',
default: () => []
},
value: {
type: String,
description: 'Radio value'
},
buttonClasses: {
type: [String, Object],
description: 'Inner button css classes'
}
},
model: {
prop: 'value',
event: 'change'
},
computed: {
model: {
get() {
return this.value
},
set(val) {
this.$emit('change', val)
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,72 @@
<template>
<div class="card"
:class="[
{'card-lift--hover': hover},
{'shadow': shadow},
{[`shadow-${shadowSize}`]: shadowSize},
{[`bg-gradient-${gradient}`]: gradient},
{[`bg-${type}`]: type}
]">
<slot name="image"></slot>
<div class="card-header" :class="headerClasses" v-if="$slots.header">
<slot name="header">
</slot>
</div>
<div class="card-body" :class="bodyClasses" v-if="!noBody">
<slot></slot>
</div>
<slot v-if="noBody"></slot>
<div class="card-footer" :class="footerClasses" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: "card",
props: {
type: {
type: String,
description: "Card type"
},
gradient: {
type: String,
description: "Card background gradient type (warning,danger etc)"
},
hover: {
type: Boolean,
description: "Whether card should move on hover"
},
shadow: {
type: Boolean,
description: "Whether card has shadow"
},
shadowSize: {
type: String,
description: "Card shadow size"
},
noBody: {
type: Boolean,
default: false,
description: "Whether card should have wrapper body class"
},
bodyClasses: {
type: [String, Object, Array],
description: "Card body css classes"
},
headerClasses: {
type: [String, Object, Array],
description: "Card header css classes"
},
footerClasses: {
type: [String, Object, Array],
description: "Card footer css classes"
}
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,49 @@
<template>
<card class="card-stats" :show-footer-line="true">
<div class="row">
<div class="col">
<slot>
<h5 class="card-title text-uppercase text-muted mb-0" v-if="title">{{title}}</h5>
<span class="h2 font-weight-bold mb-0" v-if="subTitle">{{subTitle}}</span>
</slot>
</div>
<div class="col-auto" v-if="$slots.icon || icon">
<slot name="icon">
<div class="icon icon-shape text-white rounded-circle shadow"
:class="[`bg-${type}`, iconClasses]">
<i :class="icon"></i>
</div>
</slot>
</div>
</div>
<p class="mt-3 mb-0 text-sm">
<slot name="footer">
</slot>
</p>
</card>
</template>
<script>
import Card from './Card.vue';
export default {
name: 'stats-card',
components: {
Card
},
props: {
type: {
type: String,
default: 'primary'
},
icon: String,
title: String,
subTitle: String,
iconClasses: [String, Array]
}
};
</script>
<style></style>

View File

@ -0,0 +1,30 @@
import { Bar, mixins } from 'vue-chartjs';
import globalOptionsMixin from "@/components/Charts/globalOptionsMixin";
export default {
name: 'bar-chart',
extends: Bar,
mixins: [mixins.reactiveProp, globalOptionsMixin],
props: {
extraOptions: {
type: Object,
default: () => ({})
}
},
data() {
return {
ctx: null
};
},
mounted() {
this.$watch(
'chartData',
(newVal, oldVal) => {
if (!oldVal) {
this.renderChart(this.chartData, this.extraOptions);
}
},
{ immediate: true }
);
}
};

View File

@ -0,0 +1,30 @@
import { Doughnut, mixins } from 'vue-chartjs';
import globalOptionsMixin from "./../../components/Charts/globalOptionsMixin";
export default {
name: 'doughnut-chart',
extends: Doughnut,
mixins: [mixins.reactiveProp, globalOptionsMixin],
props: {
extraOptions: {
type: Object,
default: () => ({})
}
},
data() {
return {
ctx: null
};
},
mounted() {
this.$watch(
'chartData',
(newVal, oldVal) => {
if (!oldVal) {
this.renderChart(this.chartData, this.extraOptions);
}
},
{ immediate: true }
);
}
};

View File

@ -0,0 +1,30 @@
import { Line, mixins } from 'vue-chartjs';
import globalOptionsMixin from "./../../components/Charts/globalOptionsMixin";
export default {
name: 'line-chart',
extends: Line,
mixins: [mixins.reactiveProp, globalOptionsMixin],
props: {
extraOptions: {
type: Object,
default: () => ({})
}
},
data() {
return {
ctx: null
};
},
mounted() {
this.$watch(
'chartData',
(newVal, oldVal) => {
if (!oldVal) {
this.renderChart(this.chartData, this.extraOptions);
}
},
{ immediate: true }
);
}
};

View File

@ -0,0 +1,30 @@
import { Pie, mixins } from 'vue-chartjs';
import globalOptionsMixin from "@/components/Charts/globalOptionsMixin";
export default {
name: 'pie-chart',
extends: Pie,
mixins: [mixins.reactiveProp, globalOptionsMixin],
props: {
extraOptions: {
type: Object,
default: () => ({})
}
},
data() {
return {
ctx: null
};
},
mounted() {
this.$watch(
'chartData',
(newVal, oldVal) => {
if (!oldVal) {
this.renderChart(this.chartData, this.extraOptions);
}
},
{ immediate: true }
);
}
};

View File

@ -0,0 +1,493 @@
import { parseOptions } from "./../../components/Charts/optionHelpers";
export const Charts = {
mode: 'light',//(themeMode) ? themeMode : 'light';
fonts: {
base: 'Open Sans'
},
colors: {
gray: {
100: '#f6f9fc',
200: '#e9ecef',
300: '#dee2e6',
400: '#ced4da',
500: '#adb5bd',
600: '#8898aa',
700: '#525f7f',
800: '#32325d',
900: '#212529'
},
theme: {
'default': '#172b4d',
'primary': '#5e72e4',
'secondary': '#f4f5f7',
'info': '#11cdef',
'success': '#2dce89',
'danger': '#f5365c',
'warning': '#fb6340'
},
black: '#12263F',
white: '#FFFFFF',
transparent: 'transparent',
}
};
function chartOptions(Chart) {
let { colors, mode, fonts } = Charts;
// Options
let options = {
defaults: {
global: {
responsive: true,
maintainAspectRatio: false,
defaultColor: (mode == 'dark') ? colors.gray[700] : colors.gray[600],
defaultFontColor: (mode == 'dark') ? colors.gray[700] : colors.gray[600],
defaultFontFamily: fonts.base,
defaultFontSize: 13,
layout: {
padding: 0
},
legend: {
display: false,
position: 'bottom',
labels: {
usePointStyle: true,
padding: 16
}
},
elements: {
point: {
radius: 0,
backgroundColor: colors.theme['primary']
},
line: {
tension: .4,
borderWidth: 4,
borderColor: colors.theme['primary'],
backgroundColor: colors.transparent,
borderCapStyle: 'rounded'
},
rectangle: {
backgroundColor: colors.theme['warning']
},
arc: {
backgroundColor: colors.theme['primary'],
borderColor: (mode == 'dark') ? colors.gray[800] : colors.white,
borderWidth: 4
}
},
tooltips: {
enabled: true,
mode: 'index',
intersect: false,
}
},
doughnut: {
cutoutPercentage: 83,
legendCallback: function (chart) {
let data = chart.data;
let content = '';
data.labels.forEach(function (label, index) {
let bgColor = data.datasets[0].backgroundColor[index];
content += '<span class="chart-legend-item">';
content += '<i class="chart-legend-indicator" style="background-color: ' + bgColor + '"></i>';
content += label;
content += '</span>';
});
return content;
}
}
}
};
// yAxes
Chart.scaleService.updateScaleDefaults('linear', {
gridLines: {
borderDash: [2],
borderDashOffset: [2],
color: (mode == 'dark') ? colors.gray[900] : colors.gray[300],
drawBorder: false,
drawTicks: false,
lineWidth: 0,
zeroLineWidth: 0,
zeroLineColor: (mode == 'dark') ? colors.gray[900] : colors.gray[300],
zeroLineBorderDash: [2],
zeroLineBorderDashOffset: [2]
},
ticks: {
beginAtZero: true,
padding: 10,
callback: function (value) {
if (!(value % 10)) {
return value
}
}
}
});
// xAxes
Chart.scaleService.updateScaleDefaults('category', {
gridLines: {
drawBorder: false,
drawOnChartArea: false,
drawTicks: false
},
ticks: {
padding: 20
},
maxBarThickness: 10
});
return options;
};
export function initGlobalOptions(Chart) {
parseOptions(Chart, chartOptions(Chart));
}
export const basicOptions = {
maintainAspectRatio: false,
legend: {
display: false
},
responsive: true
};
export let blueChartOptions = {
scales: {
yAxes: [{
gridLines: {
color: Charts.colors.gray[700],
zeroLineColor: Charts.colors.gray[700]
}
}]
}
};
export let lineChartOptionsBlue = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
responsive: true,
scales: {
yAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.0)',
zeroLineColor: 'transparent'
},
ticks: {
suggestedMin: 60,
suggestedMax: 125,
padding: 20,
fontColor: '#9e9e9e'
}
}
],
xAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
padding: 20,
fontColor: '#9e9e9e'
}
}
]
}
};
export let barChartOptionsGradient = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
responsive: true,
scales: {
yAxes: [
{
gridLines: {
drawBorder: false,
color: 'rgba(253,93,147,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
suggestedMin: 60,
suggestedMax: 125,
padding: 20,
fontColor: '#9e9e9e'
}
}
],
xAxes: [
{
gridLines: {
drawBorder: false,
color: 'rgba(253,93,147,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
padding: 20,
fontColor: '#9e9e9e'
}
}
]
}
};
export let pieChartOptions = {
...basicOptions,
cutoutPercentage: 70,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
scales: {
yAxes: [
{
display: 0,
ticks: {
display: false
},
gridLines: {
drawBorder: false,
zeroLineColor: 'transparent',
color: 'rgba(255,255,255,0.05)'
}
}
],
xAxes: [
{
display: 0,
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(255,255,255,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
display: false
}
}
]
}
};
export let purpleChartOptions = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
scales: {
yAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.0)',
zeroLineColor: 'transparent'
},
ticks: {
suggestedMin: 60,
suggestedMax: 125,
padding: 20,
fontColor: '#9a9a9a'
}
}
],
xAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(225,78,202,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
padding: 20,
fontColor: '#9a9a9a'
}
}
]
}
};
export let orangeChartOptions = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
scales: {
yAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.0)',
zeroLineColor: 'transparent'
},
ticks: {
suggestedMin: 50,
suggestedMax: 110,
padding: 20,
fontColor: '#ff8a76'
}
}
],
xAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(220,53,69,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
padding: 20,
fontColor: '#ff8a76'
}
}
]
}
};
export let greenChartOptions = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
scales: {
yAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.0)',
zeroLineColor: 'transparent'
},
ticks: {
suggestedMin: 50,
suggestedMax: 125,
padding: 20,
fontColor: '#9e9e9e'
}
}
],
xAxes: [
{
barPercentage: 1.6,
gridLines: {
drawBorder: false,
color: 'rgba(0,242,195,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
padding: 20,
fontColor: '#9e9e9e'
}
}
]
}
};
export let barChartOptions = {
...basicOptions,
tooltips: {
backgroundColor: '#f5f5f5',
titleFontColor: '#333',
bodyFontColor: '#666',
bodySpacing: 4,
xPadding: 12,
mode: 'nearest',
intersect: 0,
position: 'nearest'
},
scales: {
yAxes: [
{
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
suggestedMin: 60,
suggestedMax: 120,
padding: 20,
fontColor: '#9e9e9e'
}
}
],
xAxes: [
{
gridLines: {
drawBorder: false,
color: 'rgba(29,140,248,0.1)',
zeroLineColor: 'transparent'
},
ticks: {
padding: 20,
fontColor: '#9e9e9e'
}
}
]
}
};

View File

@ -0,0 +1,7 @@
import Chart from 'chart.js';
import { initGlobalOptions } from "./../../components/Charts/config";
export default {
mounted() {
initGlobalOptions(Chart);
}
}

View File

@ -0,0 +1,10 @@
// Parse global options
export function parseOptions(parent, options) {
for (let item in options) {
if (typeof options[item] !== 'object') {
parent[item] = options[item];
} else {
parseOptions(parent[item], options[item]);
}
}
}

View File

@ -0,0 +1,35 @@
<template>
<button
type="button"
class="navbar-toggler"
data-toggle="collapse"
@click="handleClick"
:data-target="`#${target}`"
:aria-controls="target"
:aria-expanded="expanded"
aria-label="Toggle navigation"
>
<span></span> <span></span>
</button>
</template>
<script>
export default {
name: 'close-button',
props: {
target: {
type: [String, Number],
description: 'Close button target element'
},
expanded: {
type: Boolean,
description: 'Whether button is expanded (aria-expanded attribute)'
}
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,84 @@
<template>
<div
id="accordion"
role="tablist"
aria-multiselectable="true"
class="accordion"
>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'collapse',
props: {
animationDuration: {
type: Number,
default: 250,
description: 'Collapse animation duration'
},
multipleActive: {
type: Boolean,
default: true,
description: 'Whether you can have multiple collapse items opened at the same time'
},
activeIndex: {
type: Number,
default: -1,
description: 'Active collapse item index'
}
},
provide() {
return {
animationDuration: this.animationDuration,
multipleActive: this.multipleActive,
addItem: this.addItem,
removeItem: this.removeItem,
deactivateAll: this.deactivateAll
};
},
data() {
return {
items: []
};
},
methods: {
addItem(item) {
const index = this.$slots.default.indexOf(item.$vnode);
if (index !== -1) {
this.items.splice(index, 0, item);
}
},
removeItem(item) {
const items = this.items;
const index = items.indexOf(item);
if (index > -1) {
items.splice(index, 1);
}
},
deactivateAll() {
this.items.forEach(item => {
item.active = false;
});
},
activateItem() {
if (this.activeIndex !== -1) {
this.items[this.activeIndex].active = true;
}
}
},
mounted() {
this.$nextTick(() => {
this.activateItem();
});
},
watch: {
activeIndex() {
this.activateItem();
}
}
};
</script>
<style scoped></style>

View File

@ -0,0 +1,91 @@
<template>
<div class="card">
<div role="tab" class="card-header" :aria-expanded="active">
<a
data-toggle="collapse"
data-parent="#accordion"
:href="`#${itemId}`"
@click.prevent="activate"
:aria-controls="`content-${itemId}`"
>
<slot name="title"> {{ title }} </slot>
<i class="tim-icons icon-minimal-down"></i>
</a>
</div>
<collapse-transition :duration="animationDuration">
<div
v-show="active"
:id="`content-${itemId}`"
role="tabpanel"
:aria-labelledby="title"
class="collapsed"
>
<div class="card-body"><slot></slot></div>
</div>
</collapse-transition>
</div>
</template>
<script>
import { CollapseTransition } from 'vue2-transitions';
export default {
name: 'collapse-item',
components: {
CollapseTransition
},
props: {
title: {
type: String,
default: '',
description: 'Collapse item title'
},
id: String
},
inject: {
animationDuration: {
default: 250
},
multipleActive: {
default: false
},
addItem: {
default: () => {}
},
removeItem: {
default: () => {}
},
deactivateAll: {
default: () => {}
}
},
computed: {
itemId() {
return this.id || this.title;
}
},
data() {
return {
active: false
};
},
methods: {
activate() {
let wasActive = this.active;
if (!this.multipleActive) {
this.deactivateAll();
}
this.active = !wasActive;
}
},
mounted() {
this.addItem(this);
},
destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
this.removeItem(this);
}
};
</script>
<style></style>

View File

@ -0,0 +1,50 @@
<template>
<date-range-picker
:startDate="startDate"
:endDate="endDate"
@update="console.log(value)"
:locale-data="locale"
:opens="opens"
>
<!--Optional scope for the input displaying the dates -->
<div slot="input" slot-scope="picker">...</div>
</date-range-picker>
</template>
<script>
export default {
components: { DateRangePicker },
data() {
return {
startDate: '2017-09-05',
endDate: '2017-09-15',
opens: "center",//which way the picker opens, default "center", can be "left"/"right"
locale: {
direction: 'ltr', //direction of text
format: 'DD-MM-YYYY', //fomart of the dates displayed
separator: ' - ', //separator between the two ranges
applyLabel: 'Apply',
cancelLabel: 'Cancel',
weekLabel: 'W',
customRangeLabel: 'Custom Range',
daysOfWeek: moment.weekdaysMin(), //array of days - see moment documenations for details
monthNames: moment.monthsShort(), //array of month names - see moment documenations for details
firstDay: 1 //ISO first day of week - see moment documenations for details
showWeekNumbers: true //show week numbers on each row of the calendar
},
ranges: { //default value for ranges object (if you set this to false ranges will no be rendered)
'Today': [moment(), moment()],
'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
'This month': [moment().startOf('month'), moment().endOf('month')],
'This year': [moment().startOf('year'), moment().endOf('year')],
'Last week': [moment().subtract(1, 'week').startOf('week'), moment().subtract(1, 'week').endOf('week')],
'Last month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
}
}
}
}
</script>
//you need to import the CSS manually (in case you want to override it)
import 'vue2-daterange-picker/dist/lib/vue-daterange-picker.min.css'

View File

@ -1,23 +0,0 @@
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Example Component</div>
<div class="panel-body">
I'm an example component!
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted.')
}
}
</script>

View File

@ -0,0 +1,50 @@
<template>
<div class="media media-comment">
<img alt="Image placeholder" class="avatar avatar-lg media-comment-avatar rounded-circle" :src="userImage">
<div class="media-body">
<div class="media-comment-text">
<h6 class="h5 mt-0">{{userName}}</h6>
<p class="text-sm lh-160" v-html="text"></p>
<div class="icon-actions">
<a href="#" class="like active">
<i class="ni ni-like-2"></i>
<span class="text-muted">{{likeCount}} likes</span>
</a>
<a href="#">
<i class="ni ni-curved-next"></i>
<span class="text-muted">{{shareCount}} shares</span>
</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'comment',
props: {
userImage: {
type: String,
default: 'img/theme/team-1.jpg'
},
userName: {
type: String,
default: 'Michael Lewis'
},
text: {
type: String,
default: 'Cras sit amet nibh libero nulla vel metus scelerisque ante sollicitudin. Cras purus odio vestibulum in vulputate viverra turpis.'
},
likeCount: {
type: Number,
default: 0
},
shareCount: {
type: Number,
default: 0
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,69 @@
<template>
<div>
<component v-for="(field, index) in schema"
:key="index"
:is="field.fieldType"
v-bind="field">
</component>
</div>
</template>
<script>
import { SlideYUpTransition } from "vue2-transitions";
export default {
props: {
show: Boolean,
title: {
type: String,
default: '',
description: "Modal header title"
},
message: {
type: String,
default: '',
description: "Modal body message"
},
button_cancel: {
type: String,
default: '',
description: "Modal footer cancel button text"
},
button_delete: {
type: String,
default: '',
description: "Modal footer delete button text"
},
animationDuration: {
type: Number,
default: 800,
description: "Modal transition duration"
}
},
methods: {
closeModal() {
this.$emit("update:show", false);
this.$emit("close");
},
onConfirm() {
this.$emit("confirm");
},
onCancel() {
this.$emit("cancel");
}
},
watch: {
show(val) {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
} else {
documentClasses.remove("modal-open");
}
}
}
}
</script>

View File

@ -0,0 +1,77 @@
<template>
<div class="custom-control custom-checkbox"
:class="[
{disabled: disabled},
{[`custom-checkbox-${type}`]: type},inlineClass]">
<input :id="cbId"
class="custom-control-input"
:class="inputClasses"
type="checkbox"
:disabled="disabled"
v-model="model"/>
<label :for="cbId" class="custom-control-label">
<slot>
<span v-if="inline">&nbsp;</span>
</slot>
</label>
</div>
</template>
<script>
export default {
name: "base-checkbox",
model: {
prop: "checked"
},
props: {
checked: {
type: [Array, Boolean],
description: "Whether checkbox is checked"
},
disabled: {
type: Boolean,
description: "Whether checkbox is disabled"
},
inline: {
type: Boolean,
description: "Whether checkbox is inline"
},
inputClasses: {
type: [String, Object, Array],
description: "Checkbox input classes"
},
type: {
type: String,
description: 'Checkbox type (e.g info, danger etc)'
}
},
data() {
return {
cbId: "",
touched: false
};
},
computed: {
model: {
get() {
return this.checked;
},
set(check) {
if (!this.touched) {
this.touched = true;
}
this.$emit("input", check);
}
},
inlineClass() {
if (this.inline) {
return `form-check-inline`;
}
}
},
created() {
this.cbId = Math.random()
.toString(16)
.slice(2);
}
};
</script>

View File

@ -0,0 +1,176 @@
<template>
<div class="form-group" :class="[
{'has-error': error},
formClasses
]">
<slot name="label">
<label v-if="label" :class="labelClasses">
{{label}}
</label>
</slot>
<div :class="[
{'input-group input-group-merge': hasIcon},
{'focused': focused},
{'input-group-alternative': alternative},
{'has-label': label || $slots.label},
inputGroupClasses
]">
<div v-if="prependIcon || $slots.prepend" class="input-group-prepend">
<span class="input-group-text">
<slot name="prepend">
<i :class="prependIcon"></i>
</slot>
</span>
</div>
<slot v-bind="slotData">
<input
:value="value"
:type="type"
v-on="listeners"
v-bind="$attrs"
:valid="!error"
:required="required"
class="form-control"
:class="[{'is-valid': valid === true}, {'is-invalid': error}, inputClasses]">
</slot>
<div v-if="appendIcon || $slots.append" class="input-group-append">
<span class="input-group-text">
<slot name="append">
<i :class="appendIcon"></i>
</slot>
</span>
</div>
<slot name="infoBlock"></slot>
<slot name="error">
<div v-if="error" class="invalid-feedback d-block"
v-html="error">
</div>
</slot>
<slot name="success">
<div class="valid-feedback" v-if="!error && valid">
{{successMessage}}
</div>
</slot>
</div>
</div>
</template>
<script>
export default {
inheritAttrs: false,
name: "base-input",
props: {
required: {
type: Boolean,
description: "Whether input is required (adds an asterix *)"
},
group: {
type: Boolean,
description: "Whether input is an input group (manual override in special cases)"
},
valid: {
type: Boolean,
description: "Whether is valid",
default: undefined
},
alternative: {
type: Boolean,
description: "Whether input is of alternative layout"
},
label: {
type: String,
description: "Input label (text before input)"
},
error: {
type: String,
description: "Input error (below input)"
},
successMessage: {
type: String,
description: "Input success message",
default: 'Looks good!'
},
formClasses: {
type: String,
description: "Input form css classes"
},
labelClasses: {
type: String,
description: "Input label css classes",
default: "form-control-label"
},
inputClasses: {
type: String,
description: "Input css classes"
},
inputGroupClasses: {
type: String,
description: "Input group css classes"
},
value: {
type: [String, Number],
description: "Input value"
},
type: {
type: String,
description: "Input type",
default: "text"
},
appendIcon: {
type: String,
description: "Append icon (right)"
},
prependIcon: {
type: String,
description: "Prepend icon (left)"
}
},
data() {
return {
focused: false
};
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
focus: this.onFocus,
blur: this.onBlur
};
},
slotData() {
return {
focused: this.focused,
error: this.error,
...this.listeners
};
},
hasIcon() {
const { append, prepend } = this.$slots;
return (
append !== undefined ||
prepend !== undefined ||
this.appendIcon !== undefined ||
this.prependIcon !== undefined ||
this.group
);
}
},
methods: {
updateValue(evt) {
let value = evt.target.value;
this.$emit("input", value);
},
onFocus(evt) {
this.focused = true;
this.$emit("focus", evt);
},
onBlur(evt) {
this.focused = false;
this.$emit("blur", evt);
}
}
};
</script>
<style>
</style>

View File

@ -0,0 +1,68 @@
<template>
<div
class="custom-control custom-radio"
:class="[inlineClass, { disabled: disabled }]">
<input
:id="cbId"
class="custom-control-input"
type="radio"
:disabled="disabled"
:value="name"
v-model="model"
/>
<label :for="cbId" class="custom-control-label">
<slot>
<span v-if="inline">&nbsp;</span>
</slot>
</label>
</div>
</template>
<script>
export default {
name: 'base-radio',
props: {
name: {
type: [String, Number],
description: 'Radio label'
},
disabled: {
type: Boolean,
description: 'Whether radio is disabled'
},
value: {
type: [String, Boolean],
description: 'Radio value'
},
inline: {
type: Boolean,
description: 'Whether radio is inline'
}
},
data() {
return {
cbId: ''
};
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit('input', value);
}
},
inlineClass() {
if (this.inline) {
return `form-check-inline`;
}
return '';
}
},
created() {
this.cbId = Math.random()
.toString(16)
.slice(2);
}
};
</script>

View File

@ -0,0 +1,125 @@
<template>
<div class="dropzone mb-3 dz-clickable"
:class="[multiple ? 'dropzone-multiple': 'dropzone-single']">
<div class="fallback">
<div class="custom-file">
<input type="file"
class="custom-file-input"
id="projectCoverUploads"
:multiple="multiple">
<label class="custom-file-label" for="projectCoverUploads">Choose file</label>
</div>
</div>
<div class="dz-preview dz-preview-single"
v-if="!multiple"
:class="previewClasses"
ref="previewSingle">
<div class="dz-preview-cover">
<img class="dz-preview-img" data-dz-thumbnail>
</div>
</div>
<ul v-else
class="dz-preview dz-preview-multiple list-group list-group-lg list-group-flush"
:class="previewClasses"
ref="previewMultiple">
<li class="list-group-item px-0">
<div class="row align-items-center">
<div class="col-auto">
<div class="avatar">
<img class="avatar-img rounded" data-dz-thumbnail>
</div>
</div>
<div class="col ml--3">
<h4 class="mb-1" data-dz-name>...</h4>
<p class="small text-muted mb-0" data-dz-size>...</p>
</div>
<div class="col-auto">
<button data-dz-remove="true" class="btn btn-danger btn-sm">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'dropzone-file-upload',
props: {
options: {
type: Object,
default: () => ({})
},
value: [String, Object, Array],
url: {
type: String,
default: 'http://'
},
multiple: Boolean,
previewClasses: [String, Object, Array]
},
model: {
prop: 'value',
event: 'change'
},
data() {
return {
currentFile: null,
files: [],
showList: false,
}
},
methods: {
async initDropzone() {
let Dropzone = await import('dropzone')
Dropzone = Dropzone.default || Dropzone
Dropzone.autoDiscover = false
let preview = this.multiple ? this.$refs.previewMultiple : this.$refs.previewSingle;
let self = this
let finalOptions = {
...this.options,
url: this.url,
thumbnailWidth: null,
thumbnailHeight: null,
previewsContainer: preview,
previewTemplate: preview.innerHTML,
maxFiles: (!this.multiple) ? 1 : null,
acceptedFiles: (!this.multiple) ? 'image/*' : null,
init: function () {
this.on("addedfile", function (file) {
if (!self.multiple && self.currentFile) {
// this.removeFile(this.currentFile);
}
self.currentFile = file;
})
}
}
this.dropzone = new Dropzone(this.$el, finalOptions)
preview.innerHTML = ''
let evtList = ['drop', 'dragstart', 'dragend', 'dragenter', 'dragover', 'addedfile', 'removedfile', 'thumbnail', 'error', 'processing', 'uploadprogress', 'sending', 'success', 'complete', 'canceled', 'maxfilesreached', 'maxfilesexceeded', 'processingmultiple', 'sendingmultiple', 'successmultiple', 'completemultiple', 'canceledmultiple', 'totaluploadprogress', 'reset', 'queuecomplete']
evtList.forEach(evt => {
this.dropzone.on(evt, (data) => {
this.$emit(evt, data);
if (evt === 'addedfile') {
this.files.push(data)
this.$emit('change', this.files);
} else if (evt === 'removedfile') {
let index = this.files.findIndex(f => f.upload.uuid === data.upload.uuid)
if (index !== -1) {
this.files.splice(index, 1);
}
this.$emit('change', this.files);
}
})
})
}
},
async mounted() {
this.initDropzone()
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,54 @@
<template>
<div class="custom-file">
<input type="file"
class="custom-file-input"
id="customFileLang"
lang="en"
v-bind="$attrs"
v-on="listeners"
/>
<label class="custom-file-label" for="customFileLang">
{{label}}
</label>
</div>
</template>
<script>
export default {
name: 'file-input',
inheritAttrs: false,
props: {
initialLabel: {
type: String,
default: 'Select file'
}
},
data() {
return {
files: []
}
},
computed: {
listeners() {
return {
...this.$listeners,
change: this.fileChange
}
},
label() {
let fileNames = [];
for (let file of this.files) {
fileNames.push(file.name)
}
return fileNames.length ? fileNames.join(', ') : this.initialLabel
}
},
methods: {
fileChange(evt) {
this.files = evt.target.files
this.$emit('change', this.files)
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,96 @@
<template>
<div class="quill">
<div :id="toolbarId">
<div class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<button class="ql-underline"></button>
<button class="ql-link"></button>
<button class="ql-blockquote"></button>
<button class="ql-code"></button>
<button class="ql-image"></button>
<button type="button" class="ql-list" value="ordered"></button>
<button type="button" class="ql-list" value="bullet"></button>
</div>
</div>
<div :id="editorId" :name="name" class="" ref="editor">
</div>
</div>
</template>
<script>
export default {
name: 'html-editor',
props: {
value: {
type: String,
default: ''
},
name: String
},
data () {
return {
editor: null,
content: null,
lastHtmlValue: '',
editorId: null,
toolbarId: null
}
},
methods: {
initialize (Quill) {
this.editor = new Quill(`#${this.editorId}`, {
theme: 'snow',
modules: {
toolbar: `#${this.toolbarId}`
}
})
if (this.value.length > 0) {
this.editor.pasteHTML(this.value)
}
let editorRef = this.$refs.editor;
let node = editorRef.children[0];
this.editor.on('text-change', () => {
let html = node.innerHTML
if (html === '<p><br></p>') {
html = '';
}
this.content = html
this.$emit('input', this.content);
})
},
pasteHTML () {
if (!this.editor) {
return
}
this.editor.pasteHTML(this.value)
},
randomString() {
let text = "";
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (let i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
},
async mounted () {
let Quill = await import('quill')
Quill = Quill.default || Quill
this.editorId = this.randomString();
this.toolbarId = this.randomString();
this.$nextTick(() => {
this.initialize(Quill)
});
},
watch: {
value (newVal) {
if (newVal !== this.content) {
this.pasteHTML(newVal);
}
}
}
}
</script>

View File

@ -0,0 +1,45 @@
<template>
<div
class="choice"
:class="{ active: checked }"
data-toggle="wizard-checkbox"
@click="updateValue"
>
<input
type="checkbox"
:name="name"
:disabled="disabled"
:checked="checked"
/>
<div class="icon">
<slot name="icon"> <i :class="icon"></i> </slot>
</div>
<slot name="title">
<h6>{{ title }}</h6>
</slot>
</div>
</template>
<script>
export default {
name: 'icon-checkbox',
model: {
prop: 'checked'
},
props: {
checked: {
type: Boolean,
default: false
},
name: String,
title: String,
icon: String,
disabled: Boolean
},
methods: {
updateValue() {
this.$emit('input', !this.checked);
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,95 @@
<template>
<div class="tags-input__wrapper">
<el-tag
v-for="(tag, index) in dynamicTags"
:key="tag + index"
size="small"
:type="tagType"
:closable="true"
:close-transition="false"
@close="handleClose(tag)"
>
{{ tag }}
</el-tag>
<input
type="text"
placeholder="Add new tag"
class="form-control"
v-model="inputValue"
ref="saveTagInput"
size="mini"
@input="onInput"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
</div>
</template>
<script>
import { Tag } from 'element-ui';
export default {
name: 'tags-input',
components: {
[Tag.name]: Tag
},
props: {
value: {
type: Array,
default: () => [],
description: 'List of tags'
},
tagType: {
type: String,
default: 'primary',
description: 'Tag type (primary|danger etc)'
}
},
model: {
prop: 'value',
event: 'change'
},
data() {
return {
dynamicTags: [],
inputVisible: false,
inputValue: ''
};
},
methods: {
handleClose(tag) {
this.dynamicTags.splice(this.dynamicTags.indexOf(tag), 1);
this.$emit('change', this.dynamicTags);
},
showInput() {
this.inputVisible = true;
this.$nextTick(() => {
this.$refs.saveTagInput.$refs.input.focus();
});
},
handleInputConfirm() {
let inputValue = this.inputValue;
if (inputValue) {
this.dynamicTags.push(inputValue);
this.$emit('change', this.dynamicTags);
}
this.inputVisible = false;
this.inputValue = '';
},
onInput(evt) {
this.$emit('input', evt.target.value);
}
},
created() {
this.$watch(
'value',
newVal => {
this.dynamicTags = [...newVal];
},
{ immediate: true }
);
}
};
</script>

View File

@ -0,0 +1,25 @@
<template>
<div class="row" v-loading="true" id="loading"></div>
</template>
<script>
import Vue from 'vue';
import { Loading } from 'element-ui';
Vue.use(Loading.directive);
export default {};
</script>
<style>
#loading {
min-height: 200px;
display: flex;
align-items: center;
}
.el-loading-spinner .path {
stroke: #66615b !important;
}
.el-loading-mask {
background: transparent !important;
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<SlideYUpTransition :duration="animationDuration">
<div class="modal fade"
@click.self="closeModal"
:class="[{'show d-block': show}, {'d-none': !show}, {'modal-mini': type === 'mini'}]"
v-show="show"
tabindex="-1"
role="dialog"
:aria-hidden="!show">
<div class="modal-dialog modal-dialog-centered"
:class="[{'modal-notice': type === 'notice', [`modal-${size}`]: size}, modalClasses]">
<div class="modal-content" :class="[gradient ? `bg-gradient-${gradient}` : '',modalContentClasses]">
<div class="card-header" :class="[headerClasses]" v-if="$slots.header">
<slot name="header"></slot>
<slot name="close-button">
<button type="button"
class="close"
v-if="showClose"
@click="closeModal"
data-dismiss="modal"
aria-label="Close">
<span :aria-hidden="!show">×</span>
</button>
</slot>
</div>
<div class="modal-body" :class="bodyClasses">
<slot></slot>
</div>
<div class="card-footer" :class="footerClasses" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</div>
</SlideYUpTransition>
</template>
<script>
import { SlideYUpTransition } from "vue2-transitions";
export default {
name: "modal",
components: {
SlideYUpTransition
},
props: {
show: Boolean,
showClose: {
type: Boolean,
default: true
},
type: {
type: String,
default: "",
validator(value) {
let acceptedValues = ["", "notice", "mini"];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Modal type (notice|mini|"") '
},
modalClasses: {
type: [Object, String],
description: "Modal dialog css classes"
},
size: {
type: String,
description: 'Modal size',
validator(value) {
let acceptedValues = ["", "sm", "lg"];
return acceptedValues.indexOf(value) !== -1;
},
},
modalContentClasses: {
type: [Object, String],
description: "Modal dialog content css classes"
},
gradient: {
type: String,
description: "Modal gradient type (danger, primary etc)"
},
headerClasses: {
type: [Object, String],
description: "Modal Header css classes"
},
bodyClasses: {
type: [Object, String],
description: "Modal Body css classes"
},
footerClasses: {
type: [Object, String],
description: "Modal Footer css classes"
},
animationDuration: {
type: Number,
default: 500,
description: "Modal transition duration"
}
},
methods: {
closeModal() {
this.$emit("update:show", false);
this.$emit("close");
}
},
watch: {
show(val) {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
} else {
documentClasses.remove("modal-open");
}
}
}
};
</script>
<style>
.modal.show {
background-color: rgba(0, 0, 0, 0.3);
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<nav :class="classes" class="navbar">
<div :class="containerClasses">
<slot name="brand"></slot>
<slot name="toggle-button">
<button
class="navbar-toggler collapsed"
v-if="hasMenu"
type="button"
@click="toggleMenu"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-bar navbar-kebab"></span>
<span class="navbar-toggler-bar navbar-kebab"></span>
<span class="navbar-toggler-bar navbar-kebab"></span>
</button>
</slot>
<button class="navbar-toggler" @click.stop="toggleMenu">
<span class="navbar-toggler-icon"></span>
</button>
<div
class="navbar-collapse navbar-custom-collapse collapse show"
:class="menuClasses"
v-show="show"
v-click-outside="closeMenu">
<slot :close-menu="closeMenu"></slot>
</div>
</div>
</nav>
</template>
<script>
export default {
name: 'base-nav',
props: {
show: {
type: Boolean,
default: false,
description:
'Whether navbar menu is shown (valid for viewports < specified by `expand` prop)'
},
transparent: {
type: Boolean,
default: false,
description: 'Whether navbar is transparent'
},
expand: {
type: String,
default: 'lg',
description: 'Breakpoint where nav should expand'
},
menuClasses: {
type: [String, Object, Array],
default: '',
description:
'Navbar menu (items) classes. Can be used to align menu items to the right/left'
},
containerClasses: {
type: [String, Object, Array],
default: 'container',
description:
'Container classes. Can be used to control container classes (contains both navbar brand and menu items)'
},
type: {
type: String,
default: 'white',
validator(value) {
return [
'',
'dark',
'success',
'danger',
'warning',
'white',
'primary',
'light',
'info',
'vue'
].includes(value);
},
description: 'Navbar color type'
}
},
model: {
prop: 'show',
event: 'change'
},
computed: {
classes() {
let color = `bg-${this.type}`;
let classes = [
{ 'navbar-transparent': this.transparent },
{ [`navbar-expand-${this.expand}`]: this.expand }
];
if (this.position) {
classes.push(`navbar-${this.position}`);
}
if (!this.transparent) {
classes.push(color);
}
return classes;
},
hasMenu() {
return this.$slots.default;
}
},
methods: {
toggleMenu() {
this.$emit('change', !this.show);
},
closeMenu() {
this.$emit('change', false);
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,21 @@
<template>
<button
type="button"
class="navbar-toggler collapsed"
data-toggle="collapse"
data-target="#navbar"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-bar bar1"></span>
<span class="navbar-toggler-bar bar2"></span>
<span class="navbar-toggler-bar bar3"></span>
</button>
</template>
<script>
export default {
name: 'navbar-toggle-button'
};
</script>
<style></style>

View File

@ -0,0 +1,29 @@
<template>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
:data-target="target"
:aria-controls="target"
:aria-expanded="toggled"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
</template>
<script>
export default {
props: {
target: {
type: [String, Number],
description: 'Button target element'
},
toggled: {
type: Boolean,
default: false,
description: 'Whether button is toggled'
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,207 @@
<template>
<div
@click="tryClose"
data-notify="container"
class="alert alert-notify alert-dismissible"
:class="[
{ 'alert-with-icon': icon },
verticalAlign,
horizontalAlign,
alertType
]"
role="alert"
:style="customPosition"
data-notify-position="top-center"
>
<template v-if="icon || $slots.icon">
<slot name="icon">
<span class="alert-icon" data-notify="icon">
<i :class="icon"></i>
</span>
</slot>
</template>
<span class="alert-text">
<span v-if="title" class="title">
<b>{{ title }}<br/></b>
</span>
<span v-if="message" v-html="message"></span>
<content-render
v-if="!message && component"
:component="component"
></content-render>
</span>
<slot name="dismiss-icon">
<button type="button"
class="close"
data-dismiss="alert"
aria-label="Close"
@click="close">
<span aria-hidden="true">×</span>
</button>
</slot>
</div>
</template>
<script>
export default {
name: 'notification',
components: {
contentRender: {
props: ['component'],
render: h => h(this.component)
}
},
props: {
message: String,
title: {
type: String,
description: 'Notification title'
},
icon: {
type: String,
description: 'Notification icon'
},
verticalAlign: {
type: String,
default: 'top',
validator: value => {
let acceptedValues = ['top', 'bottom'];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Vertical alignment of notification (top|bottom)'
},
horizontalAlign: {
type: String,
default: 'right',
validator: value => {
let acceptedValues = ['left', 'center', 'right'];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Horizontal alignment of notification (left|center|right)'
},
type: {
type: String,
default: 'info',
validator: value => {
let acceptedValues = [
'default',
'info',
'primary',
'danger',
'warning',
'success'
];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Notification type of notification (default|info|primary|danger|warning|success)'
},
timeout: {
type: Number,
default: 5000,
validator: value => {
return value >= 0;
},
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)'
},
timestamp: {
type: Date,
default: () => new Date(),
description: 'Notification timestamp (used internally to handle notification removal correctly)'
},
component: {
type: [Object, Function],
description: 'Custom content component. Cane be a `.vue` component or render function'
},
showClose: {
type: Boolean,
default: true,
description: 'Whether to show close button'
},
closeOnClick: {
type: Boolean,
default: true,
description: 'Whether to close notification when clicking it\' body'
},
clickHandler: {
type: Function,
description: 'Custom notification click handler'
}
},
data() {
return {
elmHeight: 0
};
},
computed: {
hasIcon() {
return this.icon && this.icon.length > 0;
},
alertType() {
return `alert-${this.type}`;
},
customPosition() {
let initialMargin = 20;
let alertHeight = this.elmHeight + 10;
let sameAlertsCount = this.$notifications.state.filter(alert => {
return (
alert.horizontalAlign === this.horizontalAlign &&
alert.verticalAlign === this.verticalAlign &&
alert.timestamp <= this.timestamp
);
}).length;
if (this.$notifications.settings.overlap) {
sameAlertsCount = 1;
}
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin;
let styles = {};
if (this.verticalAlign === 'top') {
styles.top = `${pixels}px`;
} else {
styles.bottom = `${pixels}px`;
}
return styles;
}
},
methods: {
close() {
this.$emit('close', this.timestamp);
},
tryClose(evt) {
if (this.clickHandler) {
this.clickHandler(evt, this);
}
if (this.closeOnClick) {
this.close();
}
}
},
mounted() {
this.elmHeight = this.$el.clientHeight;
if (this.timeout) {
setTimeout(this.close, this.timeout);
}
}
};
</script>
<style lang="scss">
.notifications .alert {
position: fixed;
z-index: 10000;
&[data-notify='container'] {
max-width: 500px;
}
&.center {
margin: 0 auto;
}
&.left {
left: 20px;
}
&.right {
right: 20px;
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<div class="notifications">
<slide-y-up-transition :duration="transitionDuration"
group
mode="out-in">
<notification
v-for="notification in notifications"
v-bind="notification"
:clickHandler="notification.onClick"
:key="notification.timestamp.getTime()"
@close="removeNotification"
>
</notification>
</slide-y-up-transition>
</div>
</template>
<script>
import Notification from './Notification.vue';
import { SlideYUpTransition } from 'vue2-transitions';
export default {
components: {
SlideYUpTransition,
Notification
},
props: {
transitionDuration: {
type: Number,
default: 200
},
overlap: {
type: Boolean,
default: false
}
},
data() {
return {
notifications: this.$notifications.state
};
},
methods: {
removeNotification(timestamp) {
this.$notifications.removeNotification(timestamp);
}
},
created() {
this.$notifications.settings.overlap = this.overlap;
},
watch: {
overlap: function (newVal) {
this.$notifications.settings.overlap = newVal;
}
}
};
</script>

View File

@ -0,0 +1,66 @@
import Notifications from './Notifications.vue';
const NotificationStore = {
state: [], // here the notifications will be added
settings: {
overlap: false,
verticalAlign: 'top',
horizontalAlign: 'right',
type: 'info',
timeout: 5000,
closeOnClick: true,
showClose: true
},
setOptions(options) {
this.settings = Object.assign(this.settings, options);
},
removeNotification(timestamp) {
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp);
if (indexToDelete !== -1) {
this.state.splice(indexToDelete, 1);
}
},
addNotification(notification) {
if (typeof notification === 'string' || notification instanceof String) {
notification = { message: notification };
}
notification.timestamp = new Date();
notification.timestamp.setMilliseconds(
notification.timestamp.getMilliseconds() + this.state.length
);
notification = Object.assign({}, this.settings, notification);
this.state.push(notification);
},
notify(notification) {
if (Array.isArray(notification)) {
notification.forEach(notificationInstance => {
this.addNotification(notificationInstance);
});
} else {
this.addNotification(notification);
}
}
};
const NotificationsPlugin = {
install(Vue, options) {
let app = new Vue({
data: {
notificationStore: NotificationStore
},
methods: {
notify(notification) {
this.notificationStore.notify(notification);
}
}
});
Vue.prototype.$notify = app.notify;
Vue.prototype.$notifications = app.notificationStore;
Vue.component('Notifications', Notifications);
if (options) {
NotificationStore.setOptions(options);
}
}
};
export default NotificationsPlugin;

View File

@ -0,0 +1,123 @@
<template>
<div class="sidenav navbar navbar-vertical fixed-left navbar-expand-xs navbar-light bg-white"
@mouseenter="$sidebar.onMouseEnter()"
@mouseleave="$sidebar.onMouseLeave()"
:data="backgroundColor">
<div class="scrollbar-inner" ref="sidebarScrollArea">
<div class="sidenav-header d-flex align-items-center">
<a class="navbar-brand" href="#">
<img :src="logo" class="navbar-brand-img" alt="Sidebar logo">
</a>
<div class="ml-auto">
<!-- Sidenav toggler -->
<div class="sidenav-toggler d-none d-xl-block"
:class="{'active': !$sidebar.isMinimized }"
@click="minimizeSidebar">
<div class="sidenav-toggler-inner">
<i class="sidenav-toggler-line"></i>
<i class="sidenav-toggler-line"></i>
<i class="sidenav-toggler-line"></i>
</div>
</div>
</div>
</div>
<slot></slot>
<div class="navbar-inner">
<ul class="navbar-nav">
<slot name="links">
<sidebar-item
v-for="(link, index) in sidebarLinks"
:key="link.name + index"
:link="link"
>
<sidebar-item
v-for="(subLink, index) in link.children"
:key="subLink.name + index"
:link="subLink"
>
</sidebar-item>
</sidebar-item>
</slot>
</ul>
<slot name="links-after"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'sidebar',
props: {
title: {
type: String,
default: 'Creative Tim',
description: 'Sidebar title'
},
shortTitle: {
type: String,
default: 'CT',
description: 'Sidebar short title'
},
logo: {
type: String,
default: 'https://demos.creative-tim.com/vue-argon-dashboard-pro/img/brand/green.png',
description: 'Sidebar app logo'
},
backgroundColor: {
type: String,
default: 'vue',
validator: value => {
let acceptedValues = [
'',
'vue',
'blue',
'green',
'orange',
'red',
'primary'
];
return acceptedValues.indexOf(value) !== -1;
},
description:
'Sidebar background color (vue|blue|green|orange|red|primary)'
},
sidebarLinks: {
type: Array,
default: () => [],
description:
"List of sidebar links as an array if you don't want to use components for these."
},
autoClose: {
type: Boolean,
default: true,
description:
'Whether sidebar should autoclose on mobile when clicking an item'
}
},
provide() {
return {
autoClose: this.autoClose
};
},
methods: {
minimizeSidebar() {
if (this.$sidebar) {
this.$sidebar.toggleMinimize();
}
}
},
beforeDestroy() {
if (this.$sidebar.showSidebar) {
this.$sidebar.showSidebar = false;
}
}
};
</script>
<style>
@media (min-width: 992px) {
.navbar-search-form-mobile,
.nav-mobile-menu {
display: none;
}
}
</style>

View File

@ -0,0 +1,198 @@
<template>
<component
:is="baseComponent"
:to="link.path ? link.path : '/'"
class="nav-item"
:class="{ active: isActive }"
tag="li"
>
<a
v-if="isMenu"
class="sidebar-menu-item nav-link"
:class="{ active: isActive }"
:aria-expanded="!collapsed"
data-toggle="collapse"
@click.prevent="collapseMenu"
>
<template v-if="addLink">
<span class="nav-link-text">
{{ link.name }} <b class="caret"></b>
</span>
</template>
<template v-else>
<i :class="link.icon"></i>
<span class="nav-link-text">{{ link.name }} <b class="caret"></b></span>
</template>
</a>
<collapse-transition>
<div
v-if="$slots.default || this.isMenu"
v-show="!collapsed"
class="collapse show"
>
<ul class="nav nav-sm flex-column">
<slot></slot>
</ul>
</div>
</collapse-transition>
<slot
name="title"
v-if="children.length === 0 && !$slots.default && link.path"
>
<component
:to="link.path"
@click.native="linkClick"
:is="elementType(link, false)"
class="nav-link"
:class="{ active: link.active }"
:target="link.target"
:href="link.path"
>
<template v-if="addLink">
<span class="nav-link-text">{{ link.name }}</span>
</template>
<template v-else>
<i :class="link.icon"></i>
<span class="nav-link-text">{{ link.name }}</span>
</template>
</component>
</slot>
</component>
</template>
<script>
import { CollapseTransition } from 'vue2-transitions';
export default {
name: 'sidebar-item',
components: {
CollapseTransition
},
props: {
menu: {
type: Boolean,
default: false,
description:
"Whether the item is a menu. Most of the item it's not used and should be used only if you want to override the default behavior."
},
link: {
type: Object,
default: () => {
return {
name: '',
path: '',
children: []
};
},
description:
'Sidebar link. Can contain name, path, icon and other attributes. See examples for more info'
}
},
provide() {
return {
addLink: this.addChild,
removeLink: this.removeChild
};
},
inject: {
addLink: { default: null },
removeLink: { default: null },
autoClose: {
default: true
}
},
data() {
return {
children: [],
collapsed: true
};
},
computed: {
baseComponent() {
return this.isMenu || this.link.isRoute ? 'li' : 'router-link';
},
linkPrefix() {
if (this.link.name) {
let words = this.link.name.split(' ');
return words.map(word => word.substring(0, 1)).join('');
}
},
isMenu() {
return this.children.length > 0 || this.menu === true;
},
isActive() {
if (this.$route && this.$route.path) {
let matchingRoute = this.children.find(c =>
this.$route.path.startsWith(c.link.path)
);
if (matchingRoute !== undefined) {
return true;
}
}
return false;
}
},
methods: {
addChild(item) {
const index = this.$slots.default.indexOf(item.$vnode);
this.children.splice(index, 0, item);
},
removeChild(item) {
const tabs = this.children;
const index = tabs.indexOf(item);
tabs.splice(index, 1);
},
elementType(link, isParent = true) {
if (link.isRoute === false) {
return isParent ? 'li' : 'a';
} else {
return 'router-link';
}
},
linkAbbreviation(name) {
const matches = name.match(/\b(\w)/g);
return matches.join('');
},
linkClick() {
if (
this.autoClose &&
this.$sidebar &&
this.$sidebar.showSidebar === true
) {
this.$sidebar.displaySidebar(false);
}
},
collapseMenu() {
this.collapsed = !this.collapsed;
},
collapseSubMenu(link) {
link.collapsed = !link.collapsed;
}
},
mounted() {
if (this.addLink) {
this.addLink(this);
}
if (this.link.collapsed !== undefined) {
this.collapsed = this.link.collapsed;
}
if (this.isActive && this.isMenu) {
this.collapsed = false;
}
},
destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
if (this.removeLink) {
this.removeLink(this);
}
}
};
</script>
<style>
.sidebar-menu-item {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,70 @@
import Sidebar from './SideBar.vue';
import SidebarItem from './SidebarItem.vue';
const SidebarStore = {
showSidebar: true,
sidebarLinks: [],
isMinimized: false,
breakpoint: 1200,
displaySidebar(value) {
if (window.innerWidth > this.breakpoint) {
return;
}
this.showSidebar = value;
let docClasses = document.body.classList
if (value) {
docClasses.add('g-sidenav-pinned')
docClasses.add('g-sidenav-show')
docClasses.remove('g-sidenav-hidden')
} else {
docClasses.add('g-sidenav-hidden')
docClasses.remove('g-sidenav-pinned')
}
},
toggleMinimize() {
this.isMinimized = !this.isMinimized;
let docClasses = document.body.classList
if (this.isMinimized) {
docClasses.add('g-sidenav-hidden')
docClasses.remove('g-sidenav-pinned')
} else {
docClasses.add('g-sidenav-pinned')
docClasses.remove('g-sidenav-hidden')
}
},
onMouseEnter() {
if (this.isMinimized) {
document.body.classList.add('g-sidenav-show')
document.body.classList.remove('g-sidenav-hidden')
}
},
onMouseLeave() {
if (this.isMinimized) {
let docClasses = document.body.classList
docClasses.remove('g-sidenav-show')
docClasses.add('g-sidenav-hide')
setTimeout(() => {
docClasses.remove('g-sidenav-hide')
docClasses.add('g-sidenav-hidden')
}, 300)
}
}
};
const SidebarPlugin = {
install(Vue, options) {
if (options && options.sidebarLinks) {
SidebarStore.sidebarLinks = options.sidebarLinks;
}
let app = new Vue({
data: {
sidebarStore: SidebarStore
}
});
Vue.prototype.$sidebar = app.sidebarStore;
Vue.component('side-bar', Sidebar);
Vue.component('sidebar-item', SidebarItem);
}
};
export default SidebarPlugin;

View File

@ -0,0 +1,33 @@
<template>
<div
class="tab-pane"
v-show="active"
:id="id || title"
:class="{ active: active }"
:aria-expanded="active"
>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'tab-pane',
props: ['title', 'id'],
inject: ['addTab', 'removeTab'],
data() {
return {
active: false
};
},
mounted() {
this.addTab(this);
},
destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
this.removeTab(this);
}
};
</script>
<style></style>

View File

@ -0,0 +1,168 @@
<template>
<div>
<div
:class="[
{ 'col-md-4': vertical && !tabNavWrapperClasses },
{ 'col-12': centered && !tabNavWrapperClasses },
tabNavWrapperClasses
]"
>
<ul
class="nav nav-pills"
role="tablist"
:class="[
`nav-pills-${type}`,
{ 'flex-column': vertical },
{ 'justify-content-center': centered },
tabNavClasses
]"
>
<li
v-for="tab in tabs"
class="nav-item active"
data-toggle="tab"
role="tablist"
aria-expanded="true"
:key="tab.id"
>
<a
data-toggle="tab"
role="tablist"
:href="`#${tab.id}`"
@click.prevent="activateTab(tab)"
:aria-expanded="tab.active"
class="nav-link"
:class="{ active: tab.active }"
>
<tab-item-content :tab="tab"> </tab-item-content>
</a>
</li>
</ul>
</div>
<div
class="tab-content"
:class="[
{ 'tab-space': !vertical },
{ 'col-md-8': vertical && !tabContentClasses },
tabContentClasses
]"
>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'tabs',
components: {
TabItemContent: {
props: ['tab'],
render(h) {
return h('div', [this.tab.$slots.title || this.tab.title]);
}
}
},
provide() {
return {
addTab: this.addTab,
removeTab: this.removeTab
};
},
props: {
type: {
type: String,
default: 'primary',
validator: value => {
let acceptedValues = [
'primary',
'info',
'success',
'warning',
'danger'
];
return acceptedValues.indexOf(value) !== -1;
}
},
activeTab: {
type: String,
default: '',
description: 'Active tab name'
},
tabNavWrapperClasses: {
type: [String, Object],
default: '',
description: 'ul wrapper css classes'
},
tabNavClasses: {
type: [String, Object],
default: '',
description: 'ul css classes'
},
tabContentClasses: {
type: [String, Object],
default: '',
description: 'tab content css classes'
},
vertical: Boolean,
centered: Boolean,
value: String
},
data() {
return {
tabs: []
};
},
methods: {
findAndActivateTab(title) {
let tabToActivate = this.tabs.find(t => t.title === title);
if (tabToActivate) {
this.activateTab(tabToActivate);
}
},
activateTab(tab) {
if (this.handleClick) {
this.handleClick(tab);
}
this.deactivateTabs();
tab.active = true;
},
deactivateTabs() {
this.tabs.forEach(tab => {
tab.active = false;
});
},
addTab(tab) {
const index = this.$slots.default.indexOf(tab.$vnode);
if (!this.activeTab && index === 0) {
tab.active = true;
}
if (this.activeTab === tab.name) {
tab.active = true;
}
this.tabs.splice(index, 0, tab);
},
removeTab(tab) {
const tabs = this.tabs;
const index = tabs.indexOf(tab);
if (index > -1) {
tabs.splice(index, 1);
}
}
},
mounted() {
this.$nextTick(() => {
if (this.value) {
this.findAndActivateTab(this.value);
}
});
},
watch: {
value(newVal) {
this.findAndActivateTab(newVal);
}
}
};
</script>
<style scoped></style>

View File

@ -0,0 +1,17 @@
<template>
<div class="timeline" :class="{[`timeline-${type}`]: type}">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'time-line',
props: {
type: {
type: String,
default: ''
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,30 @@
<template>
<div class="timeline-block" :class="{ 'timeline-inverted': inverted }">
<slot name="badge">
<span class="timeline-step" :class="`badge-${badgeType}`">
<i :class="badgeIcon"></i>
</span>
</slot>
<div class="timeline-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'time-line-item',
props: {
inverted: Boolean,
title: String,
badgeType: {
type: String,
default: 'success'
},
badgeIcon: {
type: String,
default: ''
}
}
};
</script>
<style></style>

View File

@ -0,0 +1,28 @@
<template>
<div class="world-map-container">
<world-map v-bind="$attrs" v-on="$listeners"></world-map>
</div>
</template>
<script>
/* We lazy load (async) the VectorMaps component because it contains 2 big libraries (jquery and jquery vector maps)
If the component is not loaded within 200ms, we display a loading component in the meanwhile.
This way, we don't bloat the main bundle with 2 unnecessary libs that we only need for this page :)
*/
import { LoadingPanel } from '@/components';
const WorldMap = () => ({
component: import('./WorldMap.vue'),
loading: LoadingPanel,
delay: 200
});
export default {
inheritAttrs: false,
components: {
WorldMap
}
};
</script>
<style>
.world-map-container {
min-height: 500px;
}
</style>

View File

@ -0,0 +1,127 @@
<template>
<div :id="id" class="world-map"></div>
</template>
<script>
import 'd3';
import * as d3 from 'd3';
import 'topojson';
import { throttle } from '@/util/throttle';
export default {
name: 'world-map',
props: {
mapData: {
type: Object,
default: () => ({})
},
points: {
type: Array,
default: () => []
}
},
data() {
return {
id: this.randomString(),
color1: '#f6f9fc',
color2: '#adb5bd',
highlightFillColor: '#ced4da',
borderColor: 'white',
highlightBorderColor: 'white',
bubbleHighlightFillColor: '#11cdef',
bubbleFillColor: '#fb6340'
};
},
methods: {
generateColors(length) {
return d3
.scaleLinear()
.domain([0, length])
.range([this.color1, this.color2]);
},
generateMapColors() {
let mapDataValues = Object.values(this.mapData);
let maxVal = Math.max(...mapDataValues);
let colors = this.generateColors(maxVal);
let mapData = {};
let fills = {
defaultFill: '#EDF0F2'
};
for (let key in this.mapData) {
let val = this.mapData[key];
fills[key] = colors(val);
mapData[key] = {
fillKey: key,
value: val
};
}
return {
mapData,
fills
};
},
async initVectorMap() {
let DataMap = await import('datamaps');
DataMap = DataMap.default || DataMap
let { fills, mapData } = this.generateMapColors();
let worldMap = new DataMap({
scope: 'world',
element: document.getElementById(this.id),
fills,
data: mapData,
responsive: true,
geographyConfig: {
borderColor: this.borderColor,
borderWidth: 1,
borderOpacity: 1,
highlightFillColor: this.highlightFillColor,
highlightBorderColor: this.highlightBorderColor,
highlightBorderWidth: 1,
highlightBorderOpacity: 1
}
});
let bubbleOptions = {
radius: 2,
borderWidth: 4,
highlightBorderWidth: 4,
fillKey: this.bubbleFillColor,
fillColor: this.bubbleFillColor,
borderColor: this.bubbleFillColor,
highlightFillColor: this.bubbleHighlightFillColor,
highlightBorderColor: this.bubbleHighlightFillColor
}
let bubblePoints = this.points.map(point => {
return {
...bubbleOptions,
...point
}
})
worldMap.bubbles(bubblePoints, {
popupTemplate: function(geo, data) {
return '<div class="hoverinfo">' + data.name
}
});
let resizeFunc = worldMap.resize.bind(worldMap);
window.addEventListener(
'resize',
() => {
throttle(resizeFunc, 40);
},
false
);
},
randomString() {
let text = "";
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (let i = 0; i < 5; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
},
async mounted() {
this.initVectorMap();
}
};
</script>
<style></style>

View File

@ -0,0 +1,36 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<div class="input-checkbox">
<label :for="name" class="form-control-label">{{ text }}</label>
</div>
<div class="row">
<div class="col-md-4" v-for="(key, item, index) in items">
<div class="input-checkbox">
<input type="checkbox" :name="name" :value="key[id]"> <small>{{ key[value] }}</small>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-checkbox-group',
props: {
name: '',
text: '',
items: [],
id: '',
value: '',
selected: '',
attributes: [],
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,33 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<input type="email" :name="name" :value="value" :id="name" class="form-control" v-bind="attributes">
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-email-group',
props: {
name: '',
text: '',
icon: '',
attributes: [],
value: '',
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,34 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge custom-file">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<input type="file" :name="name" :value="value" :id="name" class="form-control" v-bind="attributes">
<label :for="name" class="custom-file-label">{{ text }}</label>
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-file-group',
props: {
name: '',
text: '',
icon: '',
attributes: [],
value: '',
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,36 @@
<template>
<div class="form-group" :class="col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<input type="text" :name="input_name" :value="input_value" :id="input_name" class="form-control" v-bind="attributes">
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-invoice-text-group',
props: {
name: '',
input_name: '',
input_value: '',
text: '',
icon: '',
attributes: [],
values: [],
selected: '',
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,33 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<input type="number" :name="name" :value="value" :id="name" class="form-control" v-bind="attributes">
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-number-group',
props: {
name: '',
text: '',
icon: '',
attributes: [],
value: '',
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,33 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<input type="password" :name="name" :value="value" :id="name" class="form-control" v-bind="attributes">
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-password-group',
props: {
name: '',
text: '',
icon: '',
attributes: [],
value: '',
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,87 @@
<template>
<div class="form-group" :class="col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="tab-pane tab-example-result fade show active" role="tabpanel" aria-labelledby="-component-tab">
<div class="btn-group btn-group-toggle" data-toggle="buttons" v-on:click="onClick">
<label class="btn btn-success"
:class="[{'active': value === 1}]">
<input type="radio"
:name="name"
value="1"
:value="real_value = 1"
v-on="listeners"
:id="name + '-1'"
> {{ enable }}
</label>
<label class="btn btn-danger"
:class="[{'active': value === 0}]">
<input type="radio"
:name="name"
value="0"
:value="real_value = 0"
v-on="listeners"
:id="name + '-0'"> {{ disable }}
</label>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-radio-group',
props: {
name: '',
text: '',
value: '',
enable: '',
disable: '',
col: ''
},
data() {
return {
focused: false,
real_value: this.value
};
},
computed: {
listeners() {
return {
...this.$listeners,
change: this.onChange,
click: this.onClick,
input: this.updateValue,
focus: this.onFocus,
blur: this.onBlur
};
}
},
methods: {
updateValue(evt) {
let val = evt.target.value;
this.$emit("input", val);
},
onChange(evt) {
let val = evt.target.control.value;
this.value= val;
this.$emit("change", val);
},
onClick(evt) {
let val = evt.target.control.value;
this.value= val;
this.$emit("change", val);
},
onFocus(evt) {
this.focused = true;
this.$emit("focus", evt);
},
onBlur(evt) {
this.focused = false;
this.$emit("blur", evt);
}
}
}
</script>

View File

@ -0,0 +1,54 @@
<template>
<div class="col-md-12">
<a href="http://localhost/Ak-Dev/Beta/v2.0.0/common/items" class="btn btn-icon btn-outline-secondary">
<span class="btn-inner--icon"><i class="fas fa-times"></i></span>
<span class="btn-inner--text">Cancel</span>
</a>
<loading :active.sync="isLoading"
:can-cancel="false"
:on-cancel="onCancel"
:is-full-page="fullPage"
></loading>
<button type="button" v-on:click="doLoading" class="btn btn-icon btn-success button-submit">
<span class="btn-inner--icon"><i class="fas fa-save"></i></span>
<span class="btn-inner--text"> Save</span>
</button>
</div>
</template>
<script>
import Vue from 'vue';
// Import component
import Loading from 'vue-loading-overlay';
// Import stylesheet
import 'vue-loading-overlay/dist/vue-loading.css';
export default {
name: 'akaunting-save-buttons',
components: {
Loading
},
props: {
formSubmit: Function,
loading: false
},
data () {
return {
isLoading: loading,
fullPage: true
}
},
methods: {
doLoading() {
this.isLoading = true;
this.formSubmit();
},
onCancel() {
console.log('User cancelled the loader.')
}
}
}
</script>

View File

@ -0,0 +1,37 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<select :name="name" :id="name" class="form-control" v-bind="attributes">
<option value="" disabled>{{ attributes.placeholder }}</option>
<option v-for="(key, value, index) in values" :value="value" :selected="selected == value">{{ key }}</option>
</select>
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-select-group',
props: {
name: '',
text: '',
icon: '',
values: [],
selected: '',
attributes: [],
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

View File

@ -0,0 +1,34 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa" :class="'fa-' + icon"></i>
</span>
</div>
<input type="text" :name="name" :value="value" :id="name" class="form-control" v-bind="attributes" v-on:input="onChange" v-model:input="forms.data[name]">
<div class="text-danger invalid-feedback" v-text="" style="display: block;" v-if="errors[name]">
{{ errors[name][0] }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-text-group',
props: {
name: '',
text: '',
icon: '',
attributes: [],
value: '',
col: ''
},
data: {
},
created() {
}
}
</script>

View File

@ -0,0 +1,26 @@
<template>
<div class="form-group" :class="(attributes.required) ? col + ' required' : col">
<label :for="name" class="form-control-label">{{ text }}</label>
<textarea :name="name" :id="name" class="form-control" cols="50" v-bind="attributes">{{ value }}</textarea>
</div>
</template>
<script>
export default {
name: 'akaunting-textarea-group',
props: {
name: '',
text: '',
icon: '',
value: '',
attributes: [],
col: ''
},
data () {
return {
}
},
created() {
}
}
</script>

70
resources/assets/js/components/index.js vendored Normal file
View File

@ -0,0 +1,70 @@
import BaseCheckbox from './Inputs/BaseCheckbox.vue';
import BaseAlert from './BaseAlert.vue';
import IconCheckbox from './Inputs/IconCheckbox.vue';
import BaseRadio from './Inputs/BaseRadio.vue';
import BaseInput from './Inputs/BaseInput.vue';
import TagsInput from './Inputs/TagsInput.vue';
import BaseSwitch from './BaseSwitch.vue';
import Badge from './Badge';
import BaseProgress from './BaseProgress.vue';
import BaseButton from './BaseButton.vue';
import BaseDropdown from './BaseDropdown.vue';
import BaseTable from './BaseTable.vue';
import Card from './Cards/Card.vue';
import StatsCard from './Cards/StatsCard.vue';
import BaseNav from './Navbar/BaseNav';
import NavbarToggleButton from './Navbar/NavbarToggleButton';
import Breadcrumb from './Breadcrumb/Breadcrumb.vue';
import BreadcrumbItem from './Breadcrumb/BreadcrumbItem.vue';
import RouteBreadCrumb from './Breadcrumb/RouteBreadcrumb.vue';
import TimeLine from './Timeline/TimeLine.vue';
import TimeLineItem from './Timeline/TimeLineItem.vue';
import TabPane from './Tabs/Tab.vue';
import Tabs from './Tabs/Tabs.vue';
import Collapse from './Collapse/Collapse.vue';
import CollapseItem from './Collapse/CollapseItem.vue';
import Modal from './Modal.vue';
import BaseSlider from './BaseSlider.vue';
import LoadingPanel from './LoadingPanel.vue';
import AsyncWorldMap from './WorldMap/AsyncWorldMap.vue';
import BasePagination from './BasePagination.vue';
import SidebarPlugin from './SidebarPlugin';
export {
BaseCheckbox,
IconCheckbox,
BaseSwitch,
Badge,
BaseAlert,
BaseProgress,
BasePagination,
BaseRadio,
BaseInput,
TagsInput,
Card,
StatsCard,
BaseTable,
BaseDropdown,
SidebarPlugin,
BaseNav,
NavbarToggleButton,
Breadcrumb,
BreadcrumbItem,
RouteBreadCrumb,
TimeLine,
TimeLineItem,
TabPane,
Tabs,
Modal,
BaseSlider,
BaseButton,
Collapse,
CollapseItem,
LoadingPanel,
AsyncWorldMap
};

View File

@ -0,0 +1,15 @@
export default {
bind: function(el, binding, vnode) {
el.clickOutsideEvent = function(event) {
// here I check that click was outside the el and his childrens
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unbind: function(el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
};

71
resources/assets/js/install.js vendored Normal file
View File

@ -0,0 +1,71 @@
require('./bootstrap');
import Vue from 'vue';
import VueRouter from 'vue-router';
import DashboardPlugin from './plugins/dashboard-plugin';
import Install from './Install.vue';
import Form from './plugins/form';
// plugin setup
Vue.use(DashboardPlugin);
Vue.use(VueRouter);
import Requirements from './views/install/Requirements';
import Language from './views/install/Language';
import Database from './views/install/Database';
import Settings from './views/install/Settings';
var base_path = url.replace(window.location.origin, '');
const router = new VueRouter({
mode: 'history',
base: base_path,
routes: [
{
path: '/',
name: 'requirements',
component: Requirements
},
{
path: '/install/requirements',
name: 'requirements',
component: Requirements
},
{
path: '/install/language',
name: 'language',
component: Language
},
{
path: '/install/database',
name: 'database',
component: Database
},
{
path: '/install/settings',
name: 'settings',
component: Settings
}
],
linkActiveClass: 'active',
scrollBehavior: (to, from ,savedPosition) => {
if (savedPosition) {
return savedPosition;
}
if (to.hash) {
return { selector: to.hash };
}
return { x: 0, y: 0 };
}
});
/* eslint-disable no-new */
new Vue({
el : '#app',
render: h => h(Install),
router
});

241
resources/assets/js/mixins/global.js vendored Normal file
View File

@ -0,0 +1,241 @@
import Vue from 'vue';
import DashboardPlugin from './../plugins/dashboard-plugin';
import axios from 'axios';
import AkauntingSearch from './../components/AkauntingSearch';
import AkauntingModal from './../components/AkauntingModal';
import AkauntingRadioGroup from './../components/forms/AkauntingRadioGroup';
import AkauntingSelect from './../components/AkauntingSelect';
import AkauntingDate from './../components/AkauntingDate';
import AkauntingRecurring from './../components/AkauntingRecurring';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import NProgressAxios from './../plugins/nprogress-axios';
import {VMoney} from 'v-money';
import { Select, Option } from 'element-ui';
import { isThisSecond } from 'date-fns';
// plugin setup
Vue.use(DashboardPlugin);
export default {
components: {
AkauntingSearch,
AkauntingRadioGroup,
AkauntingSelect,
AkauntingModal,
AkauntingDate,
AkauntingRecurring,
[Select.name]: Select,
[Option.name]: Option
},
data: function () {
return {
addNew: {
modal: false,
title: '',
html: null
},
confirm: {
url: '',
title: '',
message: '',
button_cancel: '',
button_delete: '',
show: false
},
money: {
decimal: '.',
thousands: ',',
prefix: '$ ',
suffix: '',
precision: 2,
masked: false /* doesn't work with directive */
}
}
},
directives: {
money: VMoney
},
mounted() {
this.checkNotify();
if (aka_currency) {
this.money.decimal = aka_currency.decimal_mark;
this.money.thousands = aka_currency.thousands_separator;
this.money.prefix = (aka_currency.symbol_first) ? aka_currency.symbol : '';
this.money.suffix = !(aka_currency.symbol_first) ? aka_currency.symbol : '';
this.money.precision = aka_currency.precision;
}
},
methods: {
// Check Default set notify > store / update action
checkNotify: function () {
if (!flash_notification) {
return false;
}
flash_notification.forEach(notify => {
let type = notify.level;
this.$notify({
message: notify.message,
timeout: 5000,
icon: 'fas fa-bell',
type
});
});
},
// Form Submit
onSubmit() {
this.form.submit();
},
onHandleFileUpload(key, event) {
this.form[key] = '';
this.form[key] = event.target.files[0];
},
// Bulk Action Select all
onSelectAll() {
this.bulk_action.selectAll();
},
// Bulk Action Select checked/ unchecked
onSelect() {
this.bulk_action.select();
},
// Bulk Action use selected Change
onChange(event) {
var result = this.bulk_action.change(event);
},
// Bulk Action use selected Action
onAction() {
this.bulk_action.action();
window.location.reload(false);
},
// Bulk Action modal cancel
onCancel() {
this.bulk_action.modal = false;
},
// Bulk Action Clear selected items
onClear() {
this.bulk_action.modal = false;
this.bulk_action.clear();
},
// List Enabled column status changes
onStatus(item_id, event) {
this.bulk_action.status(item_id, event, this.$notify);
},
// Actions > Delete
confirmDelete(url, title, message, button_cancel, button_delete) {
this.confirm.url = url;
this.confirm.title = title;
this.confirm.message = message;
this.confirm.button_cancel = button_cancel;
this.confirm.button_delete = button_delete;
this.confirm.show = true;
},
// Delete action post
onDelete() {
axios({
method: 'DELETE',
url: this.confirm.url,
})
.then(response => {
if (response.data.redirect) {
this.confirm.url = '';
this.confirm.title = '';
this.confirm.message = '';
this.confirm.show = false;
window.location.href = response.data.redirect;
}
})
.catch(error => {
this.success = false;
});
},
// Close modal empty default value
cancelDelete() {
this.confirm.url = '';
this.confirm.title = '';
this.confirm.message = '';
this.confirm.show = false;
},
onNewItem(event) {
console.log(event);
axios.get(event.path)
.then(response => {
this.addNew.modal = true;
this.addNew.title = event.title;
this.addNew.html = response.data.html;
/*
this.selectOptions[3] = value;
let newOption = {
value: "3",
currentLabel: value,
label: value
};
this.$children[0].$children[0].handleOptionSelect(newOption);
this.$children[0].$children[0].onInputChange('3');
this.real_model = "3";
this.$emit('change', this.real_model);
*/
})
.catch(e => {
this.errors.push(e)
})
.finally(function () {
// always executed
});
},
// Change bank account get money and currency rate
onChangeAccount(account_id) {
axios.get(url + '/banking/accounts/currency', {
params: {
account_id: account_id
}
})
.then(response => {
this.form.currency_code = response.data.currency_code;
this.form.currency_rate = response.data.currency_rate;
this.money.decimal = response.data.decimal_mark;
this.money.thousands = response.data.thousands_separator;
this.money.prefix = (response.data.symbol_first) ? response.data.symbol : '';
this.money.suffix = !(response.data.symbol_first) ? response.data.symbol : '';
this.money.precision = response.data.precision;
})
.catch(error => {
});
}
}
}

View File

@ -0,0 +1,128 @@
import axios from "axios";
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import NProgressAxios from './nprogress-axios';
export default class BulkAction {
constructor(path) {
//This path use action url
this['path'] = path;
// Count selected items
this['count'] = '';
// Select action value ex: delete, export
this['value'] = '*';
// Select action message
this['message'] = '';
// Bulk action view status
this['show'] = false;
// Bulk action modal status
this['modal'] = false;
// Bulk action modal action
this['loading'] = false;
// Selected item list
this['selected'] = [];
// Select all items
this['select_all'] = false;
}
// Change checkbox status
select() {
this.show = true;
this.select_all = false;
this.count = this.selected.length;
if (this.count == document.querySelectorAll('[data-bulk-action]').length) {
this.select_all = true;
}
if (!this.count) {
this.show = false;
}
}
// Select all items action
selectAll() {
this.show = false;
this.selected = [];
if (!this.select_all) {
this.show = true;
for (let input of document.querySelectorAll('[data-bulk-action]')) {
this.selected.push(input.getAttribute('value'));
}
}
this.count = this.selected.length;
}
change(event) {
this.message = event.target.options[event.target.options.selectedIndex].dataset.message;
if (typeof(this.message) == "undefined") {
this.message = '';
}
return this.message;
}
// Selected item use action
action() {
var path = document.getElementsByName("bulk_action_path")[0].getAttribute('value');
this.loading = true;
axios.post('bulk-actions/' + path, {
'handle': this.value,
'selected': this.selected
})
.then(response => {
//this.loading = false;
//this.modal = false;
window.location.reload(false);
})
.catch(error => {
//this.loading = false;
//this.modal = false;
window.location.reload(false);
});
}
// Selected items clear
clear() {
this.show = false;
this.select_all = false;
this.selected = [];
}
// Change enabled status
status(item_id, event, notify) {
var item = event.target;
var status = (event.target.checked) ? 'enable' : 'disable';
axios.get(this.path + '/' + item_id + '/' + status)
.then(response => {
var type = (response.data.success) ? 'success' : 'warning';
if (!response.data.success) {
if (item.checked) {
item.checked = false;
} else {
item.checked = true;
}
}
notify({
message: response.data.message,
timeout: 5000,
icon: 'fas fa-bell',
type
});
})
.catch(error => {
});
}
}

View File

@ -0,0 +1,41 @@
// Polyfills for js features used in the Dashboard but not supported in some browsers (mainly IE)
import './../polyfills';
// Notifications plugin. Used on Notifications page
import Notifications from './../components/NotificationPlugin';
// Validation plugin used to validate forms
import VeeValidate from 'vee-validate';
// A plugin file where you could register global components used across the app
import GlobalComponents from './globalComponents';
// A plugin file where you could register global directives
import GlobalDirectives from './globalDirectives';
// Sidebar on the right. Used as a local plugin in DashboardLayout.vue
import SideBar from './../components/SidebarPlugin';
// element ui language configuration
import lang from 'element-ui/lib/locale/lang/en';
import locale from 'element-ui/lib/locale';
locale.use(lang);
// asset imports
import './../../sass/argon.scss';
import './../../css/nucleo/css/nucleo.css';
import 'element-ui/lib/theme-chalk/index.css';
export default {
install(Vue) {
Vue.use(GlobalComponents);
Vue.use(GlobalDirectives);
Vue.use(SideBar);
Vue.use(Notifications);
Vue.use(VeeValidate, {
fieldsBagName: 'veeFields',
classes : true,
validity : true,
classNames : {
valid : 'is-valid',
invalid: 'is-invalid'
}
});
}
};

36
resources/assets/js/plugins/error.js vendored Normal file
View File

@ -0,0 +1,36 @@
export default class Errors {
constructor() {
this.errors = {};
}
has(field) {
// if this.errors contains as "field" property.
return this.errors.hasOwnProperty(field);
}
any() {
return Object.keys(this.errors).length > 0;
}
set(key, field) {
return this.errors[key] = field;
}
get(field) {
if (this.errors[field]) {
return this.errors[field][0];
}
}
record(errors) {
this.errors = errors;
}
clear(field) {
if (field) {
return delete this.errors[field];
}
this.errors = {};
}
}

179
resources/assets/js/plugins/form.js vendored Normal file
View File

@ -0,0 +1,179 @@
import Errors from './error';
import axios from "axios";
export default class Form {
constructor(form_id) {
let form = document.getElementById(form_id);
if (!form) {
return;
}
this['method'] = form.getAttribute('method').toLowerCase();
this['action'] = form.getAttribute('action');
for (let form_element of document.getElementById(form_id).getElementsByTagName("input")) {
if (form_element.getAttribute('id') == 'global-search') {
continue;
}
var name = form_element.getAttribute('name');
var type = form_element.getAttribute('type');
if (name == 'method') {
continue;
}
if (form_element.getAttribute('data-item')) {
if (!this['items']) {
var item = {};
var row = {};
item[0] = row;
this['items'] = item;
}
if (!this['items'][0][form_element.getAttribute('data-item')]) {
this['items'][0][form_element.getAttribute('data-item')] = '';
}
this['item_backup'] = this['items'];
continue;
}
if (type == 'radio') {
if (!this[name]) {
this[name] = form_element.getAttribute('value') || '';
}
} else if (type == 'checkbox') {
if (this[name]) {
if (!this[name].push) {
this[name] = [this[name]];
}
if (form_element.checked) {
this[name].push(form_element.value);
}
} else {
if (form_element.checked) {
this[name] = form_element.value;
} else {
this[name] = [];
}
}
} else {
this[name] = form_element.getAttribute('value') || '';
}
}
for (let form_element of document.getElementById(form_id).getElementsByTagName("textarea")) {
var name = form_element.getAttribute('name');
if (name == 'method') {
continue;
}
if (this[name]) {
if (!this[name].push) {
this[name] = [this[name]];
}
this[name].push(form_element.value || '');
} else {
this[name] = form_element.value || '';
}
}
for (let form_element of document.getElementById(form_id).getElementsByTagName("select")) {
var name = form_element.getAttribute('name');
if (name == 'method') {
continue;
}
if (this[name]) {
if (!this[name].push) {
this[name] = [this[name]];
}
this[name].push(form_element.getAttribute('value') || '');
} else {
this[name] = form_element.getAttribute('value') || '';
}
}
this.errors = new Errors();
this.loading = false;
this.response = {};
}
data() {
let data = Object.assign({}, this);
delete data.method;
delete data.action;
delete data.errors;
delete data.loading;
delete data.response;
return data;
}
reset() {
for (let form_element of document.getElementsByTagName("input")) {
var name = form_element.getAttribute('name');
if (this[name]) {
this[name] = '';
}
}
for (let form_element of document.getElementsByTagName("textarea")) {
var name = form_element.getAttribute('name');
if (this[name]) {
this[name] = '';
}
}
for (let form_element of document.getElementsByTagName("select")) {
var name = form_element.getAttribute('name');
if (this[name]) {
this[name] = '';
}
}
}
submit() {
this.loading = true;
axios[this.method](this.action, this.data())
.then(this.onSuccess.bind(this))
.catch(this.onFail.bind(this));
}
onSuccess(response) {
this.errors.clear();
this.loading = false;
if (response.data.redirect) {
this.loading = true;
window.location.href = response.data.redirect;
}
this.response = response.data;
}
// Form fields check validation issue
onFail(error) {
this.errors.record(error.response.data.errors);
this.loading = false;
}
}

View File

@ -0,0 +1,46 @@
import BaseInput from './../components/Inputs/BaseInput';
import BaseDropdown from './../components/BaseDropdown.vue';
import Card from './../components/Cards/Card.vue';
import Modal from './../components/Modal.vue';
import StatsCard from './../components/Cards/StatsCard.vue';
import BaseButton from './../components/BaseButton.vue';
import Badge from './../components/Badge.vue';
import RouteBreadcrumb from './../components/Breadcrumb/RouteBreadcrumb';
import BaseCheckbox from './../components/Inputs/BaseCheckbox.vue';
import BaseSwitch from './../components/BaseSwitch.vue';
import BaseRadio from "./../components/Inputs/BaseRadio";
import BaseProgress from "./../components/BaseProgress";
import BasePagination from "./../components/BasePagination";
import BaseAlert from "./../components/BaseAlert";
import BaseNav from "./../components/Navbar/BaseNav";
import BaseHeader from './../components/BaseHeader';
import { Input, Tooltip, Popover } from 'element-ui';
/**
* You can register global components here and use them as a plugin in your main Vue instance
*/
const GlobalComponents = {
install(Vue) {
Vue.component(Badge.name, Badge);
Vue.component(BaseAlert.name, BaseAlert);
Vue.component(BaseButton.name, BaseButton);
Vue.component(BaseCheckbox.name, BaseCheckbox);
Vue.component(BaseHeader.name, BaseHeader);
Vue.component(BaseInput.name, BaseInput);
Vue.component(BaseDropdown.name, BaseDropdown);
Vue.component(BaseNav.name, BaseNav);
Vue.component(BasePagination.name, BasePagination);
Vue.component(BaseProgress.name, BaseProgress);
Vue.component(BaseRadio.name, BaseRadio);
Vue.component(BaseSwitch.name, BaseSwitch);
Vue.component(Card.name, Card);
Vue.component(Modal.name, Modal);
Vue.component(StatsCard.name, StatsCard);
Vue.component(RouteBreadcrumb.name, RouteBreadcrumb);
Vue.component(Input.name, Input);
Vue.use(Tooltip);
Vue.use(Popover);
}
};
export default GlobalComponents;

View File

@ -0,0 +1,13 @@
import clickOutside from './../directives/click-ouside.js';
/**
* You can register global directives here and use them as a plugin in your main Vue instance
*/
const GlobalDirectives = {
install(Vue) {
Vue.directive('click-outside', clickOutside);
}
};
export default GlobalDirectives;

View File

@ -0,0 +1,28 @@
import axios from "axios";
import NProgress from "nprogress";
axios.interceptors.request.use(function (config) {
// Do something before request is sent
NProgress.start();
return config;
}, function (error) {
// Do something with request error
console.log(error);
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
NProgress.done();
return response;
}, function (error) {
NProgress.done();
// Do something with response error
console.log(error);
return Promise.reject(error);
});

View File

@ -0,0 +1,57 @@
import Vue from 'vue';
/*
// Initialize the annoying-background directive.
export const SelectTwo = {
twoWay: true,
bind(el, binding, vnode) {
var variblee = this;
var selectbox = el.getAttribute('id');
var binding2 = binding;
var vnode2 = vnode;
$(vnode.elm).select2()
.on("select2:select", function(e) {
//this.$set($(vnode.elm).val());
}.bind($(vnode.elm)));
},
update: function(nv, ov) {
$('#' + nv.id).trigger("change");
}
}
// You can also make it available globally.
Vue.directive('select-two', SelectTwo);
*/
Vue.component('select2', {
props: ['options', 'value'],
template: '#select2-template',
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({ data: this.options })
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
})
},
watch: {
value: function (value) {
// update value
$(this.$el)
.val(value)
.trigger('change')
},
options: function (options) {
// update options
$(this.$el).empty().select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
})

96
resources/assets/js/polyfills.js vendored Normal file
View File

@ -0,0 +1,96 @@
/* eslint-disable */
import 'es6-promise/auto'
export default (function initPollyFills () {
if (!Array.prototype.find) {
Object.defineProperty(Array.prototype, 'find', {
value: function (predicate) {
// 1. Let O be ? ToObject(this value).
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
var thisArg = arguments[1];
// 5. Let k be 0.
var k = 0;
// 6. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(O, Pk).
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
// d. If testResult is true, return kValue.
var kValue = o[k];
if (predicate.call(thisArg, kValue, k, o)) {
return kValue;
}
// e. Increase k by 1.
k++;
}
// 7. Return undefined.
return undefined;
}
});
}
if (typeof Object.assign !== 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign (target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search, pos) {
return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
};
}
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
'use strict';
if (typeof start !== 'number') {
start = 0;
}
if (start + search.length > this.length) {
return false;
} else {
return this.indexOf(search, start) !== -1;
}
};
}
}())

View File

@ -0,0 +1,26 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('item')
}
}
});

44
resources/assets/js/views/auth/login.js vendored Normal file
View File

@ -0,0 +1,44 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('login')
}
},
watch:{
'form.response'(notify) {
/*
if (!notify.message) {
return {};
}
var type = (notify.success) ? 'success' : 'warning';
this.$notify({
message: notify.message,
timeout: 5000,
icon: 'ni ni-bell-55',
type
});
*/
}
}
});

View File

@ -0,0 +1,27 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('permission'),
bulk_action: new BulkAction('permissions')
}
}
});

26
resources/assets/js/views/auth/reset.js vendored Normal file
View File

@ -0,0 +1,26 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('item')
}
}
});

112
resources/assets/js/views/auth/roles.js vendored Normal file
View File

@ -0,0 +1,112 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
mounted() {
if (!this.form.permissions.length) {
this.form.permissions = [];
}
},
data: function () {
return {
form: new Form('role'),
bulk_action: new BulkAction('roles'),
permissions: {
'all': $('input:checkbox').serializeAll().permissions,
'read': $('#tab-read input:checkbox').serializeAll(),
'create': $('#tab-create input:checkbox').serializeAll(),
'update': $('#tab-update input:checkbox').serializeAll(),
'delete': $('#tab-delete input:checkbox').serializeAll(),
'read_admin_panel': $('#read-admin-panel').val(),
'read_client_portal': $('#read-client-portal').val(),
}
}
},
methods:{
permissionSelectAll() {
var is_admin = false;
var is_portal = false;
if (this.permissions.all.length) {
for (var i = 0; i < this.permissions.all.length; i++) {
var value = this.permissions.all[i];
if ((is_admin && value == this.permissions.read_client_portal) ||
(is_portal && value == this.permissions.read_admin_panel)) {
} else {
this.form.permissions.push(value);
}
if (value == this.permissions.read_admin_panel) {
is_admin = true;
} else if (value == this.permissions.read_client_portal) {
is_portal = true;
}
}
}
},
permissionUnselectAll() {
this.form.permissions = [];
},
select(type) {
var is_admin = false;
var is_portal = false;
var values = this.permissions[type].permissions;
if (values.length) {
for (var i = 0; i < values.length; i++) {
var value = values[i];
if ((is_admin && value == this.permissions.read_client_portal) ||
(is_portal && value == this.permissions.read_admin_panel)) {
} else {
this.form.permissions.push(value);
}
if (value == this.permissions.read_admin_panel) {
is_admin = true;
} else if (value == this.permissions.read_client_portal) {
is_portal = true;
}
}
}
},
unselect(type) {
var values = this.permissions[type].permissions;
if (values.length) {
for (var i = 0; i < values.length; i++) {
var index = this.form.permissions.indexOf(values[i]);
if (index > -1) {
this.form.permissions.splice(index, 1);
}
}
}
}
}
});

27
resources/assets/js/views/auth/users.js vendored Normal file
View File

@ -0,0 +1,27 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('user'),
bulk_action: new BulkAction('users')
}
}
});

View File

@ -0,0 +1,45 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('account'),
bulk_action: new BulkAction('accounts')
}
},
methods:{
onChangeCurrency(currency_code) {
axios.get(url + '/settings/currencies/currency', {
params: {
code: currency_code
}
})
.then(response => {
this.money.decimal = response.data.decimal_mark;
this.money.thousands = response.data.thousands_separator;
this.money.prefix = (response.data.symbol_first) ? response.data.symbol : '';
this.money.suffix = !(response.data.symbol_first) ? response.data.symbol : '';
this.money.precision = response.data.precision;
})
.catch(error => {
});
}
}
});

View File

@ -0,0 +1,27 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('reconciliation'),
bulk_action: new BulkAction('reconciliations')
}
}
});

View File

@ -0,0 +1,25 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
}
}
});

View File

@ -0,0 +1,27 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./../../bootstrap');
import Vue from 'vue';
import Global from './../../mixins/global';
import Form from './../../plugins/form';
import BulkAction from './../../plugins/bulk-action';
const app = new Vue({
el: '#app',
mixins: [
Global
],
data: function () {
return {
form: new Form('transfer'),
bulk_action: new BulkAction('transfers')
}
}
});

Some files were not shown because too many files have changed in this diff Show More