v2 first commit
This commit is contained in:
8
resources/assets/js/Install.vue
Normal file
8
resources/assets/js/Install.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
</script>
|
22
resources/assets/js/app.js
vendored
22
resources/assets/js/app.js
vendored
@ -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'
|
||||
});
|
72
resources/assets/js/bootstrap.js
vendored
72
resources/assets/js/bootstrap.js
vendored
@ -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');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
213
resources/assets/js/components/AkauntingDashboard.vue
Normal file
213
resources/assets/js/components/AkauntingDashboard.vue
Normal 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>
|
70
resources/assets/js/components/AkauntingDate.vue
Normal file
70
resources/assets/js/components/AkauntingDate.vue
Normal 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>
|
120
resources/assets/js/components/AkauntingModal.vue
Normal file
120
resources/assets/js/components/AkauntingModal.vue
Normal 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">×</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>
|
164
resources/assets/js/components/AkauntingRecurring.vue
Normal file
164
resources/assets/js/components/AkauntingRecurring.vue
Normal 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>
|
114
resources/assets/js/components/AkauntingSearch.vue
Normal file
114
resources/assets/js/components/AkauntingSearch.vue
Normal 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>
|
160
resources/assets/js/components/AkauntingSelect.vue
Normal file
160
resources/assets/js/components/AkauntingSelect.vue
Normal 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>
|
365
resources/assets/js/components/AkauntingWidget.vue
Normal file
365
resources/assets/js/components/AkauntingWidget.vue
Normal 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>
|
53
resources/assets/js/components/Badge.vue
Normal file
53
resources/assets/js/components/Badge.vue
Normal 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>
|
74
resources/assets/js/components/BaseAlert.vue
Normal file
74
resources/assets/js/components/BaseAlert.vue
Normal 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>
|
81
resources/assets/js/components/BaseButton.vue
Normal file
81
resources/assets/js/components/BaseButton.vue
Normal 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>
|
100
resources/assets/js/components/BaseDropdown.vue
Normal file
100
resources/assets/js/components/BaseDropdown.vue
Normal 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>
|
23
resources/assets/js/components/BaseHeader.vue
Normal file
23
resources/assets/js/components/BaseHeader.vue
Normal 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>
|
134
resources/assets/js/components/BasePagination.vue
Normal file
134
resources/assets/js/components/BasePagination.vue
Normal 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>
|
90
resources/assets/js/components/BaseProgress.vue
Normal file
90
resources/assets/js/components/BaseProgress.vue
Normal 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>
|
96
resources/assets/js/components/BaseSlider.vue
Normal file
96
resources/assets/js/components/BaseSlider.vue
Normal 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>
|
49
resources/assets/js/components/BaseSwitch.vue
Normal file
49
resources/assets/js/components/BaseSwitch.vue
Normal 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>
|
70
resources/assets/js/components/BaseTable.vue
Normal file
70
resources/assets/js/components/BaseTable.vue
Normal 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>
|
26
resources/assets/js/components/Breadcrumb/Breadcrumb.vue
Normal file
26
resources/assets/js/components/Breadcrumb/Breadcrumb.vue
Normal 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>
|
16
resources/assets/js/components/Breadcrumb/BreadcrumbItem.vue
Normal file
16
resources/assets/js/components/Breadcrumb/BreadcrumbItem.vue
Normal 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>
|
@ -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>
|
39
resources/assets/js/components/ButtonCheckbox.vue
Normal file
39
resources/assets/js/components/ButtonCheckbox.vue
Normal 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>
|
47
resources/assets/js/components/ButtonRadioGroup.vue
Normal file
47
resources/assets/js/components/ButtonRadioGroup.vue
Normal 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>
|
72
resources/assets/js/components/Cards/Card.vue
Normal file
72
resources/assets/js/components/Cards/Card.vue
Normal 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>
|
49
resources/assets/js/components/Cards/StatsCard.vue
Normal file
49
resources/assets/js/components/Cards/StatsCard.vue
Normal 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>
|
30
resources/assets/js/components/Charts/BarChart.js
vendored
Normal file
30
resources/assets/js/components/Charts/BarChart.js
vendored
Normal 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 }
|
||||
);
|
||||
}
|
||||
};
|
30
resources/assets/js/components/Charts/DoughnutChart.js
vendored
Normal file
30
resources/assets/js/components/Charts/DoughnutChart.js
vendored
Normal 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 }
|
||||
);
|
||||
}
|
||||
};
|
30
resources/assets/js/components/Charts/LineChart.js
vendored
Normal file
30
resources/assets/js/components/Charts/LineChart.js
vendored
Normal 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 }
|
||||
);
|
||||
}
|
||||
};
|
30
resources/assets/js/components/Charts/PieChart.js
vendored
Normal file
30
resources/assets/js/components/Charts/PieChart.js
vendored
Normal 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 }
|
||||
);
|
||||
}
|
||||
};
|
493
resources/assets/js/components/Charts/config.js
vendored
Normal file
493
resources/assets/js/components/Charts/config.js
vendored
Normal 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'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
7
resources/assets/js/components/Charts/globalOptionsMixin.js
vendored
Normal file
7
resources/assets/js/components/Charts/globalOptionsMixin.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import Chart from 'chart.js';
|
||||
import { initGlobalOptions } from "./../../components/Charts/config";
|
||||
export default {
|
||||
mounted() {
|
||||
initGlobalOptions(Chart);
|
||||
}
|
||||
}
|
10
resources/assets/js/components/Charts/optionHelpers.js
vendored
Normal file
10
resources/assets/js/components/Charts/optionHelpers.js
vendored
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
35
resources/assets/js/components/CloseButton.vue
Normal file
35
resources/assets/js/components/CloseButton.vue
Normal 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>
|
84
resources/assets/js/components/Collapse/Collapse.vue
Normal file
84
resources/assets/js/components/Collapse/Collapse.vue
Normal 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>
|
91
resources/assets/js/components/Collapse/CollapseItem.vue
Normal file
91
resources/assets/js/components/Collapse/CollapseItem.vue
Normal 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>
|
50
resources/assets/js/components/DataRangePicker.vue
Normal file
50
resources/assets/js/components/DataRangePicker.vue
Normal 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'
|
@ -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>
|
50
resources/assets/js/components/Feed/Comment.vue
Normal file
50
resources/assets/js/components/Feed/Comment.vue
Normal 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>
|
69
resources/assets/js/components/Field-Attributes.vue
Normal file
69
resources/assets/js/components/Field-Attributes.vue
Normal 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>
|
77
resources/assets/js/components/Inputs/BaseCheckbox.vue
Normal file
77
resources/assets/js/components/Inputs/BaseCheckbox.vue
Normal 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"> </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>
|
176
resources/assets/js/components/Inputs/BaseInput.vue
Normal file
176
resources/assets/js/components/Inputs/BaseInput.vue
Normal 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>
|
68
resources/assets/js/components/Inputs/BaseRadio.vue
Normal file
68
resources/assets/js/components/Inputs/BaseRadio.vue
Normal 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"> </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>
|
125
resources/assets/js/components/Inputs/DropzoneFileUpload.vue
Normal file
125
resources/assets/js/components/Inputs/DropzoneFileUpload.vue
Normal 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>
|
54
resources/assets/js/components/Inputs/FileInput.vue
Normal file
54
resources/assets/js/components/Inputs/FileInput.vue
Normal 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>
|
96
resources/assets/js/components/Inputs/HtmlEditor.vue
Normal file
96
resources/assets/js/components/Inputs/HtmlEditor.vue
Normal 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>
|
45
resources/assets/js/components/Inputs/IconCheckbox.vue
Normal file
45
resources/assets/js/components/Inputs/IconCheckbox.vue
Normal 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>
|
95
resources/assets/js/components/Inputs/TagsInput.vue
Normal file
95
resources/assets/js/components/Inputs/TagsInput.vue
Normal 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>
|
25
resources/assets/js/components/LoadingPanel.vue
Normal file
25
resources/assets/js/components/LoadingPanel.vue
Normal 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>
|
125
resources/assets/js/components/Modal.vue
Normal file
125
resources/assets/js/components/Modal.vue
Normal 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>
|
120
resources/assets/js/components/Navbar/BaseNav.vue
Normal file
120
resources/assets/js/components/Navbar/BaseNav.vue
Normal 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>
|
21
resources/assets/js/components/Navbar/NavbarToggleButton.vue
Normal file
21
resources/assets/js/components/Navbar/NavbarToggleButton.vue
Normal 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>
|
29
resources/assets/js/components/NavbarToggleButton.vue
Normal file
29
resources/assets/js/components/NavbarToggleButton.vue
Normal 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>
|
@ -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>
|
@ -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>
|
66
resources/assets/js/components/NotificationPlugin/index.js
vendored
Normal file
66
resources/assets/js/components/NotificationPlugin/index.js
vendored
Normal 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;
|
123
resources/assets/js/components/SidebarPlugin/SideBar.vue
Normal file
123
resources/assets/js/components/SidebarPlugin/SideBar.vue
Normal 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>
|
198
resources/assets/js/components/SidebarPlugin/SidebarItem.vue
Normal file
198
resources/assets/js/components/SidebarPlugin/SidebarItem.vue
Normal 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>
|
70
resources/assets/js/components/SidebarPlugin/index.js
vendored
Normal file
70
resources/assets/js/components/SidebarPlugin/index.js
vendored
Normal 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;
|
33
resources/assets/js/components/Tabs/Tab.vue
Normal file
33
resources/assets/js/components/Tabs/Tab.vue
Normal 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>
|
168
resources/assets/js/components/Tabs/Tabs.vue
Normal file
168
resources/assets/js/components/Tabs/Tabs.vue
Normal 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>
|
17
resources/assets/js/components/Timeline/TimeLine.vue
Normal file
17
resources/assets/js/components/Timeline/TimeLine.vue
Normal 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>
|
30
resources/assets/js/components/Timeline/TimeLineItem.vue
Normal file
30
resources/assets/js/components/Timeline/TimeLineItem.vue
Normal 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>
|
28
resources/assets/js/components/WorldMap/AsyncWorldMap.vue
Normal file
28
resources/assets/js/components/WorldMap/AsyncWorldMap.vue
Normal 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>
|
127
resources/assets/js/components/WorldMap/WorldMap.vue
Normal file
127
resources/assets/js/components/WorldMap/WorldMap.vue
Normal 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>
|
@ -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>
|
33
resources/assets/js/components/forms/AkauntingEmailGroup.vue
Normal file
33
resources/assets/js/components/forms/AkauntingEmailGroup.vue
Normal 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>
|
34
resources/assets/js/components/forms/AkauntingFileGroup.vue
Normal file
34
resources/assets/js/components/forms/AkauntingFileGroup.vue
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
87
resources/assets/js/components/forms/AkauntingRadioGroup.vue
Normal file
87
resources/assets/js/components/forms/AkauntingRadioGroup.vue
Normal 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>
|
@ -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>
|
@ -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>
|
34
resources/assets/js/components/forms/AkauntingTextGroup.vue
Normal file
34
resources/assets/js/components/forms/AkauntingTextGroup.vue
Normal 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>
|
@ -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
70
resources/assets/js/components/index.js
vendored
Normal 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
|
||||
};
|
15
resources/assets/js/directives/click-ouside.js
vendored
Normal file
15
resources/assets/js/directives/click-ouside.js
vendored
Normal 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
71
resources/assets/js/install.js
vendored
Normal 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
241
resources/assets/js/mixins/global.js
vendored
Normal 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 => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
128
resources/assets/js/plugins/bulk-action.js
vendored
Normal file
128
resources/assets/js/plugins/bulk-action.js
vendored
Normal 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 => {
|
||||
});
|
||||
}
|
||||
}
|
41
resources/assets/js/plugins/dashboard-plugin.js
vendored
Normal file
41
resources/assets/js/plugins/dashboard-plugin.js
vendored
Normal 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
36
resources/assets/js/plugins/error.js
vendored
Normal 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
179
resources/assets/js/plugins/form.js
vendored
Normal 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;
|
||||
}
|
||||
}
|
46
resources/assets/js/plugins/globalComponents.js
vendored
Normal file
46
resources/assets/js/plugins/globalComponents.js
vendored
Normal 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;
|
13
resources/assets/js/plugins/globalDirectives.js
vendored
Normal file
13
resources/assets/js/plugins/globalDirectives.js
vendored
Normal 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;
|
28
resources/assets/js/plugins/nprogress-axios.js
vendored
Normal file
28
resources/assets/js/plugins/nprogress-axios.js
vendored
Normal 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);
|
||||
});
|
57
resources/assets/js/plugins/selecttwo.js
vendored
Normal file
57
resources/assets/js/plugins/selecttwo.js
vendored
Normal 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
96
resources/assets/js/polyfills.js
vendored
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}())
|
26
resources/assets/js/views/auth/forgot.js
vendored
Normal file
26
resources/assets/js/views/auth/forgot.js
vendored
Normal 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
44
resources/assets/js/views/auth/login.js
vendored
Normal 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
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
});
|
27
resources/assets/js/views/auth/permissions.js
vendored
Normal file
27
resources/assets/js/views/auth/permissions.js
vendored
Normal 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
26
resources/assets/js/views/auth/reset.js
vendored
Normal 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
112
resources/assets/js/views/auth/roles.js
vendored
Normal 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
27
resources/assets/js/views/auth/users.js
vendored
Normal 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')
|
||||
}
|
||||
}
|
||||
});
|
45
resources/assets/js/views/banking/accounts.js
vendored
Normal file
45
resources/assets/js/views/banking/accounts.js
vendored
Normal 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 => {
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
27
resources/assets/js/views/banking/reconciliations.js
vendored
Normal file
27
resources/assets/js/views/banking/reconciliations.js
vendored
Normal 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')
|
||||
}
|
||||
}
|
||||
});
|
25
resources/assets/js/views/banking/transactions.js
vendored
Normal file
25
resources/assets/js/views/banking/transactions.js
vendored
Normal 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 {
|
||||
}
|
||||
}
|
||||
});
|
27
resources/assets/js/views/banking/transfers.js
vendored
Normal file
27
resources/assets/js/views/banking/transfers.js
vendored
Normal 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
Reference in New Issue
Block a user