akaunting 3.0 (the last dance)

This commit is contained in:
Burak Civan
2022-06-01 10:15:55 +03:00
parent cead09f6d4
commit d9c0764572
3812 changed files with 126831 additions and 102949 deletions

View File

@ -50,75 +50,3 @@
},
};
</script>
<style>
.document-loading {
width: 1140px;
display: flex;
align-items: center;
justify-content: center;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 9999;
}
.document-loading div {
margin-top: unset;
margin-left: unset;
}
.current-tab {
background-color: #f6f9fc;
}
.current-tab-btn {
text-align: right;
padding: 0 40px;
}
.form-container {
flex-flow: row wrap;
}
.form-container .invalid-feedback {
position: absolute;
bottom: -18px;
}
.form-container .has-error {
position: relative;
margin-bottom: unset !important;
}
.form-container .has-error .form-control {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-right: 1px solid;
}
.el-step__icon {
-webkit-transition: unset;
transition: unset;
}
@media screen and (max-width: 991px) {
.form-container .has-error {
position: relative;
margin-bottom: 1.5rem !important;
}
.current-tab-btn {
padding: 0 15px;
}
.form-container {
flex-direction: column;
}
.form-container .form-group {
width: 100%;
}
}
</style>

View File

@ -7,14 +7,6 @@ window._ = require('lodash');
* code may be modified to fit the specific needs of your application.
*/
try {
if (!window.$) {
window.$ = window.jQuery = require('jquery');
}
//require('bootstrap-sass');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
@ -41,72 +33,3 @@ window.axios.defaults.headers.common['Content-Type'] = 'multipart/form-data';
// broadcaster: 'pusher',
// key: 'your-pusher-key'
// });
//(function ($) {
jQuery.fn.serializeFormJSON = function () {
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
jQuery.fn.serializeFormJSONShow = function () {
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(true);
} else {
o[this.name] = true;
}
});
return o;
};
jQuery.fn.serializeAll = function () {
var o = {};
var a = this;
$.each(this, function () {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
//})(jQuery);
jQuery(document).ready(function () {
jQuery('input[type="radio"]').each(function () {
if (jQuery(this).parent().parent().hasClass('radio-yes-no')) {
if (jQuery(this).val() == 1) {
jQuery(this).parent().trigger('click');
}
}
});
});

View File

@ -1,159 +0,0 @@
<template>
<div v-if="video || screenshots">
<el-carousel :height="height"
:initial-index="initial_index"
:trigger="trigger" :autoplay="autoplay"
:indicator-position="indicator_position"
:type="type" :loop="loop" :direction="direction"
:interval="interval" :arrow="arrow">
<el-carousel-item v-if="video">
<iframe class="carousel-frame w-100" height="365px"
:src="'https://www.youtube-nocookie.com/embed/' + video"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<div class="carousel-description py-2">
{{ name }}
</div>
</el-carousel-item>
<el-carousel-item v-for="(screenshot, index) in screenshots" :key="index">
<img @click="openGallery(index)" class="d-block w-100 carousel-frame" height="365px" :src="screenshot.path_string" :alt="screenshot.alt_attribute">
<div class="carousel-description py-2" v-if="screenshot.description">
{{ screenshot.description }}
</div>
<div class="carousel-description py-2" v-else>
{{ name }}
</div>
</el-carousel-item>
</el-carousel>
<LightBox
v-if="media.length"
ref="lightbox"
:media="media"
:show-caption="true"
:show-light-box="false"
/>
</div>
</template>
<script>
import Vue from 'vue';
import {Image, Carousel, CarouselItem} from 'element-ui';
import LightBox from 'vue-image-lightbox';
import 'vue-image-lightbox/dist/vue-image-lightbox.min.css';
import VueLazyLoad from 'vue-lazyload';
Vue.use(VueLazyLoad);
export default {
name: "akaunting-carousel",
components: {
[Image.name]: Image,
[Carousel.name]: Carousel,
[CarouselItem.name]: CarouselItem,
LightBox
},
props: {
name: {
type: String,
default: null,
description: "App Name"
},
video: {
type: String,
default: null,
description: "App Video"
},
screenshots: {
type: Array,
default: false,
description: "App Screenshots"
},
height: {
type: String,
default: null,
description: "height of the carousel"
},
initial_index: {
type: Number,
default: 0,
description: "index of the initially active slide (starting from 0)"
},
trigger: {
type: String,
default: 'hover',
description: "how indicators are triggered (hover/click)"
},
autoplay: {
type: Boolean,
default: false,
description: "whether automatically loop the slides"
},
interval: {
type: Number,
default: 3000,
description: "interval of the auto loop, in milliseconds"
},
indicator_position: {
type: String,
default: 'none',
description: "position of the indicators (outside/none)"
},
arrow: {
type: String,
default: 'hover',
description: "when arrows are shown (always/hover/never)"
},
type: {
type: String,
default: '',
description: "type of the Carousel (card)"
},
loop: {
type: Boolean,
default: true,
description: "display the items in loop"
},
direction: {
type: String,
default: 'horizontal',
description: "display direction (horizontal/vertical)"
}
},
mounted() {
let media = [];
if (this.screenshots.length) {
let name = this.name;
this.screenshots.forEach(function(screenshot) {
media.push({ // For image
thumb: screenshot.path_string,
src: screenshot.path_string,
caption: (screenshot.description.length) ? screenshot.description : name,
});
});
}
this.media = media;
},
data: function () {
return {
media: [],
}
},
methods: {
openGallery(index) {
this.$refs.lightbox.showImage(index)
}
}
}
</script>

View File

@ -0,0 +1,222 @@
<template>
<base-input :label="title" :name="name"
:readonly="readonly"
:disabled="disabled"
:class="[
{'readonly': readonly},
{'disabled': disabled},
formClasses
]"
:error="formError">
<div class="flex justify-between relative mt-1">
<input type="text" @change="change" :name="name" :id="name" v-model="color" @keyup="addColor" class="form-element">
<div class="absolute w-7 h-7 flex rounded-full my-auto bottom-2 right-2 cursor-pointer"
ref="dropdownMenu"
@click="openPalette"
:class="`bg-${color}`"
:style="{ backgroundColor: color }"
></div>
<transition name="fade">
<div v-show="isOpen" class="w-full border border-gray-300 origin-top-right absolute left-0 top-full mt-2 rounded-md shadow-lg z-10">
<div class="rounded-md bg-white shadow-xs p-2">
<div class="flex">
<div class="w-full flex flex-wrap justify-between">
<div v-for="color in colors" :key="color">
<div v-for="variant in variants"
:key="variant"
:colorId="`${color}-${variant}`"
class="rounded-full m-1 color cursor-pointer"
:class="[`bg-${color}-${variant}`, small ? 'w-6 h-6 lg:w-4 lg:h-4' : 'w-8 h-8 xl:w-6 xl:h-6 2xl:w-8 2xl:h-8']"
@click="setColor($event)"
></div>
</div>
</div>
</div>
</div>
</div>
</transition>
</div>
</base-input>
</template>
<script>
export default {
name: 'akaunting-color',
props: {
title: {
type: String,
default: '',
description: "Selectbox label text"
},
placeholder: {
type: String,
default: '',
description: "Selectbox input placeholder text"
},
formClasses: {
type: Array,
default: null,
description: "Selectbox input class name"
},
formError: {
type: String,
default: null,
description: "Selectbox input error message"
},
icon: {
type: String,
description: "Prepend icon (left)"
},
name: {
type: String,
default: null,
description: "Selectbox attribute name"
},
value: {
type: [String, Number, Array, Object],
default: '',
description: "Selectbox selected value"
},
model: {
type: [String, Number, Array, Object],
default: '',
description: "Selectbox selected model"
},
readonly: {
type: Boolean,
default: false,
description: "Selectbox disabled status"
},
disabled: {
type: Boolean,
default: false,
description: "Selectbox disabled status"
},
small: {
type: [Boolean, String],
default: false,
},
},
data() {
return {
isOpen: false,
color: 'green-500',
hexCode: null,
colors: [
'gray',
'red',
'yellow',
'green',
'blue',
'indigo',
'purple',
'pink',
],
variants: [
50,
100,
200,
300,
400,
500,
600,
700,
800,
900,
],
}
},
created () {
document.addEventListener('click', this.closeIfClickedOutside);
},
mounted() {
// Check Here..
if (this.value) {
this.color = this.value;
}
this.$emit('interface', this.color);
setTimeout(function() {
this.change();
}.bind(this), 800);
},
methods: {
change() {
this.$emit('interface', this.color);
this.$emit('change', this.color);
},
openPalette() {
this.isOpen = ! this.isOpen;
},
setColor(event) {
this.isOpen = false;
this.color = event.target.getAttribute('colorid');
this.hexCode = null;
},
addColor() {
let code = this.color;
this.hexCode = code.includes('#') ? code : '#' + code;
},
closeIfClickedOutside(event) {
let el = this.$refs.dropdownMenu;
let target = event.target;
if (el !== target && ! el.contains(target)) {
this.isOpen = false;
}
},
},
watch: {
color: function (value) {
this.change();
},
value: function (value) {
this.color = value;
},
model: function (value) {
this.color = value;
},
}
}
</script>
<style scoped>
.fade-enter-active, .fade-leave-active {
transition: opacity .2s
}
.fade-enter, .fade-leave-to
/* .fade-leave-active in <2.1.8 */
{
opacity: 0
}
</style>

View File

@ -1,35 +1,35 @@
<template>
<div class="document-add-info-content-info-business fs-exclude">
<div class="table-responsive">
<table class="table table-borderless p-0">
<div class="">
<div class="flex items-start">
<table>
<tbody>
<tr>
<th class="text-right p-0">
<strong class="text-strong">{{ company.name }}</strong>
<th class="text-left p-0">
<span class="font-medium text-left text-sm p-0">{{ company.name }}</span>
</th>
</tr>
<tr v-if="company.address">
<th class="text-right p-0">
<th class="font-normal text-sm text-left p-0">
{{ company.address }}
</th>
</tr>
<tr v-if="company.location">
<th class="text-right p-0">
<th class="font-normal text-sm text-left p-0">
{{ company.location }}
</th>
</tr>
<tr v-if="company.tax_number">
<th class="text-right p-0">
<th class="font-normal text-sm text-left p-0">
{{ taxNumberText }}: {{ company.tax_number }}
</th>
</tr>
<tr v-if="company.phone">
<th class="text-right p-0">
<th class="font-normal text-sm text-left p-0">
{{ company.phone }}
</th>
</tr>
<tr>
<th class="text-right p-0">
<th class="font-normal text-sm text-left p-0">
{{ company.email }}
</th>
</tr>
@ -37,7 +37,11 @@
</table>
</div>
<button type="button" class="btn btn-link text-right" @click="onEditCompany">{{ buttonText }}</button>
<div class="absolute right-0 top-0 group">
<div class="w-6 h-7 flex items-center justify-center rounded-lg p-0 group-hover:bg-gray-100">
<span class="material-icons-outlined text-lg opacity-70 group-hover:text-gray-500 cursor-pointer" @click="onEditCompany">edit</span>
</div>
</div>
<component v-bind:is="company_html" @submit="onSubmit" @cancel="onCancel"></component>
</div>
@ -51,7 +55,7 @@ import { Select, Option, OptionGroup, ColorPicker } from 'element-ui';
import AkauntingModalAddNew from './AkauntingModalAddNew';
import AkauntingModal from './AkauntingModal';
import AkauntingMoney from './AkauntingMoney';
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
@ -74,11 +78,6 @@ export default {
},
props: {
buttonText: {
type: String,
default: 'Edit your business address ',
description: 'Input placeholder'
},
taxNumberText: {
type: String,
default: 'Tax Number',
@ -223,7 +222,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
})
.catch(error => {
@ -240,7 +239,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
},
},
};

View File

@ -0,0 +1,456 @@
<template>
<SlideYUpTransition :duration="animationDuration">
<div class="modal w-full h-full fixed top-0 left-0 right-0 z-50 overflow-y-auto overflow-hidden modal-add-new fade items-center justify-center"
:class="[{'show flex flex-wrap modal-background': show}, {'hidden': !show}]"
v-show="show"
tabindex="-1"
role="dialog"
:aria-hidden="!show">
<div class="w-full my-10 m-auto flex flex-col" :class="modalDialogClass ? modalDialogClass : 'max-w-screen-sm'">
<slot name="modal-content">
<div class="modal-content">
<div class="p-5 bg-body rounded-tl-lg rounded-tr-lg">
<div class="flex items-center justify-between border-b pb-5">
<slot name="card-header">
<h4 class="text-base font-medium">
{{ translations.title }}
</h4>
<button type="button" class="text-lg" @click="onCancel" aria-hidden="true">
<span class="rounded-md border-b-2 px-2 py-1 text-sm bg-gray-100">esc</span>
</button>
</slot>
</div>
</div>
<slot name="modal-body">
<div class="px-5 bg-body">
<template v-if="transaction">
<div class="flex flex-col items-start gap-y-3">
<div class="text-left text-sm">
<div class="font-medium">
{{ translations.contact }}
</div>
<span>
{{ transaction.contact.name }}
</span>
</div>
<div class="text-left text-sm">
<div class="font-medium">
{{ translations.category }}
</div>
<span>
{{ transaction.category.name }}
</span>
</div>
<div class="text-left text-sm">
<div class="font-medium">
{{ translations.account }}
</div>
<span>
{{ transaction.account.name }}
</span>
</div>
</div>
</template>
<div class="relative sm:col-span-6 mt-3">
<div style="table-layout: fixed;">
<div class="overflow-x-visible overflow-y-hidden">
<table class="w-full" id="items" style="table-layout: fixed">
<thead class="border-b">
<tr>
<th colspan="3" class="w-12/12 px-0 text-left border-t-0 border-r-0 border-b-0">
{{ translations.document }}
</th>
</tr>
</thead>
<colgroup>
<col style="width: 20%;">
<col style="width: 80%;">
</colgroup>
<tbody>
<tr v-for="(row, index) in form.items" :index="index" class="border-b border-gray-200">
<td class="px-0 border-r-0 border-b-0 truncate">
<div class="text-sm">
<div class="truncate">
<b>{{ translations.number }}:</b> {{ row.number }}
</div>
<div class="truncate" v-if="row.notes">
<b>{{ translations.notes }}:</b> {{ row.notes }}
</div>
</div>
<div class="row" style="font-size: 13px;">
<div class="col-md-12 long-texts">
<b>{{ translations.contact }}:</b> {{ row.contact }}
</div>
</div>
</td>
<td class="px-0 border-l-0 border-b-0 border-r-0">
<div class="flex items-center justify-end">
<akaunting-money
:col="''"
:masked="true"
name="amount"
title=""
:currency="currency"
:dynamic-currency="currency"
:value="row.amount"
:row-input="true"
:money-class="'text-right input-price'"
@input="checkAmount(index, $event)"
></akaunting-money>
<akaunting-money
class="hidden"
:masked="true"
name="max_amount"
title=""
:group_class="null"
:currency="currency"
v-model="row.max_amount"
:dynamic-currency="currency"
:row-input="true"
:disabled="true"
></akaunting-money>
<div class="pl-2 group">
<button type="button" @click="onDeleteItem(index)" class="w-6 h-7 flex items-center rounded-lg p-0 group-hover:bg-gray-100">
<span class="w-full material-icons-outlined text-lg text-gray-300 group-hover:text-gray-500">delete</span>
</button>
</div>
</div>
</td>
</tr>
<tr id="addItem">
<td colspan="3" class="w-12/12 p-0">
<akaunting-document-button
:items="documents"
:selectedItems="form.items"
:dynamic-currency="currency"
@document-selected="onAddItem($event)"
:no-data-text="translations.no_data"
:placeholder="translations.placeholder_search"
:add-item-text="translations.add_an"
></akaunting-document-button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="sm:col-span-6">
<div class="overflow-y-hidden py-5">
<table id="totals" class="float-right">
<colgroup>
<col style="width: 51.5%;">
<col style="width: 30%;">
<col style="width: 18.5%;">
</colgroup>
<tbody id="document-total-rows">
<tr id="tr-total">
<td class="border-t-0 p-0"></td>
<td class="font-medium text-sm text-right border-r-0 border-b-0 align-middle py-0 pr-0">
{{ translations.total }}
</td>
<td class="text-sm text-right border-b-0 p-0">
<div>
<money
:name="'total_amount'"
:value="total_amount"
v-bind="money"
masked
disabled
class="px-0 disabled-money text-right banking-price-text"
style="height: unset;"
></money>
</div>
</td>
</tr>
<tr id="tr-transaction-amount">
<td class="border-t-0 p-0"></td>
<td class="font-medium text-sm text-right border-r-0 border-b-0 align-middle py-0 pr-0">
{{ translations.transaction + ' ' + translations.amount }}
</td>
<td class="text-sm text-right border-b-0 p-0">
<div>
<money
:name="'transaction_amount'"
:value="transaction.amount"
v-bind="money"
masked
disabled
class="px-0 disabled-money text-right banking-price-text"
style="height: unset;"
v-if="transaction"
></money>
</div>
<akaunting-money
class="hidden"
:masked="true"
name="transaction_amount"
title=""
:group_class="null"
:currency="currency"
:value="transaction_amount"
@input="transaction_amount = $event"
:dynamic-currency="currency"
:row-input="true"
:disabled="true"
></akaunting-money>
</td>
</tr>
<tr id="tr-difference">
<td class="border-t-0 p-0"></td>
<td class="font-medium text-sm text-right border-r-0 border-b-0 align-middle py-0 pr-0">
{{ translations.difference }}
</td>
<td class="text-right text-sm border-b-0 p-0">
<div>
<money
:name="'difference_amount'"
:value="difference_amount"
v-bind="money"
masked
disabled
class="px-0 disabled-money text-right banking-price-text"
style="height: unset;"
></money>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</slot>
<div class="p-5 bg-body rounded-bl-lg rounded-br-lg border-gray-300">
<slot name="card-footer">
<div class="flex items-center justify-end">
<button type="button" class="px-6 py-1.5 mr-2 hover:bg-gray-200 rounded-lg" @click="onCancel">
{{ translations.cancel }}
</button>
<button type="button"
:disabled="this.difference_amount != 0 || (this.difference_amount == 0 && form.loading)"
class="relative px-6 py-1.5 bg-green hover:bg-green-700 text-white rounded-lg disabled:bg-green-100"
@click="onConfirm"
>
<i
v-if="form.loading"
class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"
>
</i>
<span :class="[{'opacity-0': this.difference_amount != 0}]">{{ translations.save }}</span>
</button>
</div>
</slot>
</div>
</div>
</slot>
</div>
</div>
</SlideYUpTransition>
</template>
<script>
import { SlideYUpTransition } from "vue2-transitions";
import AkauntingSelect from './AkauntingSelect';
import AkauntingMoney from './AkauntingMoney';
import AkauntingDocumentButton from './AkauntingDocumentButton';
import {Money} from 'v-money';
export default {
name: 'akaunting-connect-transactions',
components: {
SlideYUpTransition,
AkauntingSelect,
AkauntingMoney,
AkauntingDocumentButton,
Money,
},
props: {
show: Boolean,
transaction: Object,
currency: Object,
documents: Array,
translations: Object,
modalDialogClass: {
type: String,
default: '',
description: "Modal Body size Class"
},
animationDuration: {
type: Number,
default: 800,
description: "Modal transition duration"
},
},
data() {
return {
form: {
items: [],
loading: false,
},
transaction_amount: "",
money: {},
};
},
mounted() {
window.addEventListener('keyup',(e) => {
if (e.key === 'Escape') {
this.onCancel();
}
});
},
methods: {
onConfirm() {
this.form.loading = true;
this.$emit("confirm");
this.onConnectTransaction();
},
onCancel() {
this.$emit('close-modal');
},
onAddItem(document) {
this.form.items.push(
Object.assign({}, {
id: document.id,
document_id: document.id,
number: document.document_number,
contact: document.contact_name,
notes: document.notes,
amount: (document.amount - document.paid).toFixed(this.currency.precision),
max_amount: (document.amount - document.paid).toFixed(this.currency.precision),
})
);
},
onDeleteItem(index) {
this.form.items.splice(index, 1);
},
async onConnectTransaction() {
let self = this;
let connect_transaction = Promise.resolve(window.axios.post(url + '/banking/transactions/' + this.transaction.id + '/connect', {
data: {
items: this.form.items
}
}));
connect_transaction.then(response => {
if (response.data.redirect) {
window.location.href = response.data.redirect;
}
})
.catch(error => {
})
.finally(function () {
self.$emit("close-modal");
});
},
convertMoneyToFloat(money) {
// "$198.4"
if (typeof(money) != "string") {
money = money.toString();
}
// 198.4
money = money.replace(this.currency.symbol, '').replaceAll(this.currency.thousands_separator, '').replace(this.currency.decimal_mark, '.');
// "198.40"
money = parseFloat(money).toFixed(this.currency.precision);
// 198.40
return parseFloat(money);
},
checkAmount(index, amount) {
let max_amount = this.convertMoneyToFloat(this.form.items[index].max_amount);
let changed_amount = this.convertMoneyToFloat(amount);
this.form.items[index].amount = changed_amount.toFixed(this.currency.precision);
setTimeout(function () {
if (changed_amount > max_amount || changed_amount == 0) {
this.form.items[index].amount = max_amount.toFixed(this.currency.precision);
}
}.bind(this), 50);
}
},
watch: {
show: function (newValue) {
if (newValue) {
this.form.items = [];
}
},
transaction: function (transaction) {
this.transaction_amount = transaction.amount;
},
currency: function (currency) {
this.money = {
decimal: currency.decimal_mark,
thousands: currency.thousands_separator,
prefix: (currency.symbol_first) ? currency.symbol : '',
suffix: (!currency.symbol_first) ? currency.symbol : '',
precision: parseInt(currency.precision),
};
}
},
computed: {
total_amount: function () {
let amount = 0;
this.form.items.forEach(function(item) {
amount += this.convertMoneyToFloat(item.amount);
}, this);
return parseFloat(amount.toFixed(this.currency.precision));
},
difference_amount: function () {
if (!this.transaction_amount) {
return 0;
}
let transaction_amount = this.convertMoneyToFloat(this.transaction_amount);
return parseFloat((this.total_amount - transaction_amount).toFixed(this.currency.precision));
}
},
}
</script>

View File

@ -1,122 +1,110 @@
<template>
<div :id="'select-contact-card-' + _uid" class="document-add-body-form-contact ml-3">
<div class="document-contact" :class="[{'fs-exclude': show.contact_selected}]">
<div class="document-contact-without-contact">
<div v-if="!show.contact_selected" class="document-contact-without-contact-box-contact-select fs-exclude">
<div class="aka-select aka-select--medium is-open" tabindex="0">
<div>
<div class="aka-box aka-box--large" :class="[{'aka-error': error}]">
<div class="aka-box-content">
<div class="document-contact-without-contact-box">
<button type="button" class="btn-aka-link aka-btn--fluid document-contact-without-contact-box-btn" @click="onContactList">
<i class="far fa-user fa-2x"></i> &nbsp; <span class="text-add-contact"> {{ addContactText }} </span>
</button>
</div>
</div>
</div>
<div :id="'select-contact-card-' + _uid">
<div class="relative" :class="[{'fs-exclude': show.contact_selected}]">
<div v-if="!show.contact_selected">
<div class="aka-select aka-select--medium is-open" tabindex="0">
<div class="w-full h-33 bg-white hover:bg-gray-100 rounded-lg border border-light-gray disabled:bg-gray-200 mt-1 text-purple font-medium" :class="[{'border-red': error}]">
<div class="text-black h-full">
<button type="button" class="w-full h-full flex flex-col items-center justify-center" @click="onContactList">
<span class="material-icons-outlined text-7xl text-black-400">person_add</span>
<span class="text-add-contact"> {{ addContactText }} </span>
</button>
</div>
</div>
<div v-if="error" class="invalid-feedback d-block mt--2 mb-2"
v-html="error">
</div>
<div v-if="error" class="text-red text-sm mt-1 block mb-2"
v-html="error">
</div>
<div class="absolute top-0 left-0 right-0 bg-white border rounded-lg" style="z-index: 999;" v-if="show.contact_list">
<div class="relative">
<span class="material-icons-round absolute left-4 top-3 text-lg">search</span>
<input
type="text"
data-input="true"
class="form-element px-10 border-t-0 border-l-0 border-r-0 border-gray-200 rounded-none"
autocapitalize="default" autocorrect="ON"
:placeholder="placeholder"
:ref="'input-contact-field-' + _uid"
v-model="search"
@input="onInput"
@keyup.enter="onInput"
/>
</div>
<div class="aka-select-menu" v-if="show.contact_list">
<div class="aka-select-search-container">
<span class="aka-prefixed-input aka-prefixed-input--fluid">
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-search"></i>
</span>
</div>
<input
type="text"
data-input="true"
class="form-control"
autocapitalize="default" autocorrect="ON"
:placeholder="placeholder"
:ref="'input-contact-field-' + _uid"
v-model="search"
@input="onInput"
@keyup.enter="onInput"
/>
</div>
</span>
<ul class="form-element p-0 border-0 mt-0 cursor-pointer">
<div class="hover:bg-gray-100 px-4 py-2" v-for="(contact, index) in sortContacts" :key="index" @click="onContactSeleted(index, contact.id)">
<span>{{ contact.name }}</span>
</div>
<ul class="aka-select-menu-options">
<div class="aka-select-menu-option" v-for="(contact, index) in sortContacts" :key="index" @click="onContactSeleted(index, contact.id)">
<div>
<strong class="text-strong"><span>{{ contact.name }}</span></strong>
</div>
<div class="hover:bg-gray-100 px-4 py-2" v-if="!sortContacts.length">
<div>
<span v-if="!contacts.length && !search">{{ noDataText }}</span>
<span v-else>{{ noMatchingDataText }}</span>
</div>
<div class="aka-select-menu-option" v-if="!sortContacts.length">
<div>
<strong class="text-strong" v-if="!contacts.length && !search"><span>{{ noDataText }}</span></strong>
<strong class="text-strong" v-else><span>{{ noMatchingDataText }}</span></strong>
</div>
</div>
</ul>
<div class="aka-select-footer" tabindex="0" @click="onContactCreate">
<span>
<i class="fas fa-plus"></i> {{ createNewContactText }}
</span>
</div>
</ul>
<div class="flex items-center justify-center h-11 text-center text-purple font-bold border border-l-0 border-r-0 border-b-0 rounded-bl-lg rounded-br-lg hover:bg-gray-100 cursor-pointer" tabindex="0" @click="onContactCreate">
<span class="text-sm"> {{ createNewContactText }} </span>
</div>
</div>
</div>
</div>
<div v-else class="document-contact-with-contact-bill-to">
<div>
<span class="aka-text aka-text--block-label">{{ contactInfoText }}</span>
</div>
<div v-else class="document-contact-with-contact-bill-to">
<div>
<span class="text-sm">{{ contactInfoText }}</span>
</div>
<div class="table-responsive">
<table class="table table-borderless p-0">
<tbody>
<tr>
<th class="p-0" style="text-align:left;">
<strong class="d-block">{{ contact.name }}</strong>
</th>
</tr>
<tr v-if="contact.address">
<th class="p-0" style="text-align:left; white-space: normal;">
<div class="overflow-x-visible mt-0">
<table class="table table-borderless p-0">
<tbody>
<tr>
<th class="font-medium text-left text-sm p-0">
<span class="block">{{ contact.name }}</span>
</th>
</tr>
<tr v-if="contact.address">
<th class="font-normal text-xs text-left p-0">
<div class="w-60 truncate">
{{ contact.address }}
</th>
</tr>
<tr v-if="contact.location">
<th class="p-0" style="text-align:left; white-space: normal;">
{{ contact.location }}
</th>
</tr>
<tr v-if="contact.tax_number">
<th class="p-0" style="text-align:left;">
{{ taxNumberText }}: {{ contact.tax_number }}
</th>
</tr>
<tr v-if="contact.phone">
<th class="p-0" style="text-align:left;">
{{ contact.phone }}
</th>
</tr>
<tr v-if="contact.email">
<th class="p-0" style="text-align:left;">
{{ contact.email }}
</th>
</tr>
</tbody>
</table>
</div>
</div>
</th>
</tr>
<tr v-if="contact.location">
<th class="font-normal text-xs text-left p-0">
{{ contact.location }}
</th>
</tr>
<tr v-if="contact.tax_number">
<th class="font-normal text-xs text-left p-0">
{{ taxNumberText }}: {{ contact.tax_number }}
</th>
</tr>
<tr v-if="contact.phone">
<th class="font-normal text-xs text-left p-0">
{{ contact.phone }} &nbsp;
<span v-if="contact.email">
- {{ contact.email }}
</span>
</th>
</tr>
</tbody>
</table>
</div>
<button type="button" class="btn btn-link p-0" @click="onContactEdit">
{{ editContactText.replace(':contact_name', contact.name).replace(':field', contact.name) }}
</button>&nbsp;&nbsp;
<button type="button" class="btn btn-link p-0" @click="onContactList">
{{ chooseDifferentContactText }}
<div class="absolute flex flex-col mt-2">
<button type="button" class="p-0 text-xs text-purple ltr:text-left rtl:text-right" @click="onContactEdit">
<span class="border-b border-transparent transition-all hover:border-purple">
{{ editContactText.replace(':contact_name', contact.name).replace(':field', contact.name) }}
</span>
</button>
<button type="button" class="p-0 text-xs text-purple ltr:text-left rtl:text-right" @click="onContactList">
<span class="border-b border-transparent transition-all hover:border-purple">
{{ chooseDifferentContactText }}
</span>
</button>
</div>
</div>
@ -134,7 +122,7 @@ import { Select, Option, OptionGroup, ColorPicker } from 'element-ui';
import AkauntingModalAddNew from './AkauntingModalAddNew';
import AkauntingModal from './AkauntingModal';
import AkauntingMoney from './AkauntingMoney';
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
@ -204,7 +192,7 @@ export default {
description: 'List of Contacts'
},
contacts: {
type: Array,
type: [Array, Object],
default: () => [],
description: 'List of Contacts'
},
@ -539,7 +527,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
})
.catch(error => {
@ -556,7 +544,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
},
closeIfClickedOutside(event) {
@ -666,10 +654,3 @@ export default {
},
};
</script>
<style>
.aka-error, .aka-error:hover {
border-color: #ef3232 !important;
background-color: #fb634038;
}
</style>

View File

@ -1,31 +1,16 @@
<template>
<div class="countdown">
<section class="flex text-6xl justify-center content-center">
<div class="days mr-2 relative">
{{ displayDays}}
<div class="label text-sm absolut bottom-0"> {{ textDays }} </div>
</div>
<div>
<section class="flex text-xl justify-center content-center">
<div class="days mr-2 relative"> {{ displayDays}} </div>
<span class="leading-snug">:</span>
<div class="hours mx-2 relative">
{{ displayHours }}
<div class="label text-sm absolut bottom-0">{{ textHours }} </div>
</div>
<div class="hours mx-2 relative"> {{ displayHours }} </div>
<span class="leading-snug">:</span>
<div class="minutes mx-2 relative">
{{ displayMinutes }}
<div class="label text-sm absolut bottom-0"> {{ textMinutes }} </div>
</div>
<div class="minutes mx-2 relative"> {{ displayMinutes }} </div>
<span class="leading-snug">:</span>
<div class="seconds ml-2 relative">
{{ displaySeconds }}
<div class="label text-sm absolut bottom-0"> {{ textSeconds }} </div>
</div>
<div class="seconds ml-2 relative"> {{ displaySeconds }} </div>
</section>
</div>
</template>
@ -42,24 +27,6 @@ export default {
description: "Modal header title"
},
textHours: {
type: String,
default: 'hours',
description: "Modal header title"
},
textMinutes: {
type: String,
default: 'minutes',
description: "Modal header title"
},
textSeconds: {
type: String,
default: 'seconds',
description: "Modal header title"
},
year: {
type: Number,
default: 0,
@ -172,30 +139,3 @@ export default {
}
</script>
<style scoped>
.countdown {
text-align: justify;
}
.relative {
position: relative;
}
.flex {
display: flex;
}
.justify-center {
justify-content: center;
}
.content-center {
align-content: center;
}
.seconds {
max-width: 60px;
}
.leading-snug {
line-height: 1.375;
}
.days, .hours, .minutes, .seconds {
text-align: center;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="d-flex align-items-center justify-content-end mt-3">
<div class="flex items-center justify-end text-xs mt-3">
<span>{{ texts[0] }}</span>
<money v-bind="{
decimal: this.currencySymbol.decimal_mark,
@ -8,9 +8,9 @@
suffix: (!this.currencySymbol.symbol_first) ? this.currencySymbol.symbol : '',
precision: parseInt(this.currencySymbol.precision),
masked: true
}" :value="price" disabled size="5" masked class="disabled-money text-right mr-2 js-conversion-input"></money>
}" :value="price" disabled size="5" masked class="disabled-money text-right mr-1 js-conversion-input text-xs px-1"></money>
<span class="mr-2">{{ texts[1] }}</span>
<input name="currency_rate" v-model="rate" @input="onChange" class="form-control text-right mwpx-100 h-auto js-conversion-input" />
<input name="currency_rate" v-model="rate" @input="onChange" class="form-element w-16 h-10 text-right js-conversion-input" />
</div>
</template>

View File

@ -9,7 +9,7 @@
formClasses
]"
:footer-error="formError"
:prependIcon="icon"
:appendIcon="icon"
:readonly="readonly"
:disabled="disabled"
@focus="focus"
@ -19,7 +19,7 @@
@on-open="focus"
@on-close="blur"
:config="dateConfig"
class="form-control datepicker"
class="form-element datepicker"
v-model="real_model"
@input="change"
:readonly="readonly"
@ -135,6 +135,7 @@ export default {
if (this.locale !== 'en') {
try {
const lang = require(`flatpickr/dist/l10n/${this.locale}.js`).default[this.locale];
this.dateConfig.locale = lang;
}
catch (e) {
@ -148,6 +149,7 @@ export default {
if (this.model) {
this.real_model = this.model;
}
this.$emit('interface', this.real_model);
},

View File

@ -0,0 +1,299 @@
<template>
<div :id="'select-item-button-' + _uid" class="w-full border-b">
<button type="button" class="w-full h-10 flex items-center justify-center text-purple font-medium disabled:bg-gray-200 hover:bg-gray-100" @click="showItems">
<span class="material-icons-outlined text-base font-bold ltr:mr-1 rtl:ml-1">
add
</span>
{{ addItemText }}
</button>
<div :class="[{'is-open': show.item_list}]" tabindex="-1">
<div class="-mt-10.5 left-0 right-0 bg-white border rounded-lg" v-if="show.item_list">
<div class="relative">
<span class="material-icons-round absolute left-4 top-3 text-lg">search</span>
<input
type="text"
data-input="true"
class="form-element px-10 border-t-0 border-l-0 border-r-0 border-gray-200 rounded-none"
autocapitalize="default"
autocorrect="ON"
:placeholder="placeholder"
v-model="search"
@input="onInput"
:ref="'input-item-field-' + _uid"
/>
</div>
<ul class="form-element p-0 mt-0 border-0 cursor-pointer">
<div
class="hover:bg-gray-100 px-4"
v-for="(item, index) in sortedItems"
:key="index"
@click="onItemSelected(item)"
>
<div class="w-full flex items-center justify-between">
<span>{{ item.name }}</span>
<money
:name="'item-id-' + item.id"
:value="item.amount"
v-bind="money"
masked
disabled
class="text-right disabled-money text-gray"
></money>
</div>
</div>
<div class="hover:bg-gray-100 text-center py-2 px-4" v-if="!sortedItems.length">
<div class="text-center">
<span>{{ noDataText }}</span>
</div>
</div>
</ul>
</div>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import {Money} from 'v-money';
export default {
name: 'akaunting-document-button',
components: {
Money,
},
props: {
placeholder: {
type: String,
default: 'Type an item name',
description: 'Input placeholder'
},
items: {
type: Array,
default: () => [],
description: 'List of Items'
},
selectedItems: {
type: Array,
default: () => [],
description: 'List of Selected Items'
},
addItemText: {
type: String,
default: 'Add an item',
description: ""
},
noDataText: {
type: String,
default: 'No Data',
description: "Selectbox empty options message"
},
dynamicCurrency: {
type: Object,
default: function () {
return {
decimal_mark: '.',
thousands_separator: ',',
symbol_first: 1,
symbol: '$',
precision: 2,
};
},
description: "Dynamic currency"
},
currency: {
type: Object,
default: function () {
return {
decimal_mark: '.',
thousands_separator: ',',
symbol_first: 1,
symbol: '$',
precision: 2,
};
},
description: "Default currency"
},
},
data() {
return {
item_list: [],
search: '', // search column model
show: {
item_list: false,
},
money: {
decimal: this.currency.decimal_mark,
thousands: this.currency.thousands_separator,
prefix: (this.currency.symbol_first) ? this.currency.symbol : '',
suffix: (!this.currency.symbol_first) ? this.currency.symbol : '',
precision: parseInt(this.currency.precision),
masked: this.masked
}
};
},
created() {
this.setItemList(this.items);
},
mounted() {
if (this.dynamicCurrency.code != this.currency.code) {
if (!this.dynamicCurrency.decimal) {
this.money = {
decimal: this.dynamicCurrency.decimal_mark,
thousands: this.dynamicCurrency.thousands_separator,
prefix: (this.dynamicCurrency.symbol_first) ? this.dynamicCurrency.symbol : '',
suffix: (!this.dynamicCurrency.symbol_first) ? this.dynamicCurrency.symbol : '',
precision: parseInt(this.dynamicCurrency.precision),
masked: this.masked
};
} else {
this.money = this.dynamicCurrency;
}
}
},
methods: {
setItemList(items) {
this.item_list = [];
// Option set sort_option data
if (Array.isArray(items)) {
items.forEach(function (item, index) {
let selected = this.selectedItems.find(function (selected_item) {
return selected_item.id === item.id;
}, this);
if (selected) {
return;
}
this.item_list.push({
id: item.id,
name: item.document_number + ' | ' + item.contact_name + (item.notes ? ' | ' + item.notes : ''),
amount: item.amount,
});
}, this);
}
},
showItems() {
this.show.item_list = true;
setTimeout(function() {
this.$refs['input-item-field-' + this._uid].focus();
}.bind(this), 100);
},
onInput() {
//to optimize performance we kept the condition that checks for if search exists or not
if (!this.search) {
return;
}
//condition that checks if input is below the given character limit
this.setItemList(this.items); //once the user deletes the search input, we show the overall item list
this.sortItems(); // we order it as wanted
this.$emit('input', this.search); // keep the input binded to v-model
},
onItemSelected(param_item) {
let selected_item = this.items.find(function (item) {
return item.id === param_item.id
}, this);
this.$emit('document-selected', selected_item);
this.show.item_list = false;
this.search = '';
// Set default item list
this.setItemList(this.items);
},
closeIfClickedOutside(event) {
if (!document.getElementById('select-item-button-' + this._uid).contains(event.target)) {
this.show.item_list = false;
this.search = '';
document.removeEventListener('click', this.closeIfClickedOutside);
this.setItemList(this.items);
}
},
sortItems() {
this.item_list.sort(function (a, b) {
var nameA = a.name.toUpperCase(); // ignore upper and lowercase
var nameB = b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
});
const sortedItemList = this.item_list.filter(item =>
item.name.toLowerCase().includes(this.search.toLowerCase())
);
return sortedItemList;
},
},
computed: {
sortedItems() {
return this.sortItems();
},
},
watch: {
dynamicCurrency: function (currency) {
if (!currency) {
return;
}
this.money = {
decimal: currency.decimal_mark,
thousands: currency.thousands_separator,
prefix: (currency.symbol_first) ? currency.symbol : '',
suffix: (!currency.symbol_first) ? currency.symbol : '',
precision: parseInt(currency.precision),
masked: this.masked
};
},
show: {
handler: function(newValue) {
if (newValue) {
document.addEventListener('click', this.closeIfClickedOutside);
}
},
deep: true
},
items: function (items) {
this.setItemList(items);
},
selectedItems: function () {
this.setItemList(this.items);
}
},
};
</script>

View File

@ -1,5 +1,5 @@
<template>
<div :id="'dropzone-' + _uid" class="dropzone mb-3 dz-clickable" :class="[preview == 'single' ? 'dropzone-single': 'dropzone-multiple']">
<div :id="'dropzone-' + _uid" class="dropzone dz-clickable" :class="[preview == 'single' ? 'dropzone-single': 'dropzone-multiple', singleWidthClasses ? 'w-full': 'w-37']">
<div class="fallback">
<div class="custom-file">
<input type="file" class="custom-file-input" :id="'projectCoverUploads' + _uid" :multiple="multiple">
@ -11,39 +11,40 @@
<div v-if="preview == 'single'" class="dz-preview dz-preview-single" :class="previewClasses" ref="previewSingle">
<div class="dz-preview-cover">
<img class="dz-preview-img" data-dz-thumbnail>
<i class="fas fa-file-image display-3 fa-2x mt-2 d-none" data-dz-thumbnail-image></i>
<i class="far fa-file-pdf display-3 fa-2x mt-2 d-none" data-dz-thumbnail-pdf></i>
<i class="far fa-file-word fa-2x mt-2 d-none" data-dz-thumbnail-word></i>
<i class="far fa-file-excel fa-2x mt-2 d-none" data-dz-thumbnail-excel></i>
<span class="mb-1 d-none" data-dz-name>...</span>
<span class="material-icons hidden" data-dz-thumbnail-image>crop_original</span>
<span class="material-icons-outlined avatar hidden">file_present</span>
<span class="material-icons-outlined avatar hidden" data-dz-thumbnail-pdf>picture_as_pdf</span>
<span class="material-icons-outlined avatar hidden" data-dz-thumbnail-word>content_paste</span>
<span class="material-icons-outlined avatar hidden" data-dz-thumbnail-excel>table_chart</span>
<span class="mb-1 text-sm ml-3 text-gray-500 hidden" data-dz-name>...</span>
</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">
<li class="list-group-item border-b py-4">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="avatar">
<img class="avatar-img rounded" data-dz-thumbnail>
<i class="fas fa-file-image display-3 d-none" data-dz-thumbnail-image></i>
<i class="far fa-file-pdf display-3 d-none" data-dz-thumbnail-pdf></i>
<i class="far fa-file-word d-none" data-dz-thumbnail-word></i>
<i class="far fa-file-excel d-none" data-dz-thumbnail-excel></i>
<span class="material-icons hidden" data-dz-thumbnail-image>crop_original</span>
<span class="material-icons-outlined display-3 hidden" data-dz-thumbnail-pdf>picture_as_pdf</span>
<span class="material-icons-outlined hidden" data-dz-thumbnail-word>content_paste</span>
<span class="material-icons-outlined hidden" data-dz-thumbnail-excel>table_chart</span>
</div>
<div class="col text-gray-500 ml-3">
<h4 class="text-sm mb-1" data-dz-name>...</h4>
<p class="text-xs text-muted mb-0" data-dz-size>...</p>
</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">
<div class="gap-x-1">
<button data-dz-remove="true" class="btn btn-danger btn-sm">
<i class="fas fa-trash"></i>
<span class="material-icons text-base text-red">delete</span>
</button>
<a href="#" type="button" class="btn btn-sm btn-info text-white d-none" data-dz-download>
<i class="fas fa-file-download"></i>
<a href="#" type="button" class="btn btn-sm btn-info hidden" data-dz-download>
<span class="material-icons-round text-base">download</span>
</a>
</div>
</div>
@ -72,7 +73,7 @@ export default {
description: 'Choose file text'
},
options: {
type: Object,
type: [Object, Array],
default: () => ({})
},
value: [String, Object, Array, File],
@ -86,6 +87,10 @@ export default {
description: 'Multiple file Upload'
},
previewClasses: [String, Object, Array],
singleWidthClasses: {
type: [Boolean, String],
default: false
},
preview: {
type: String,
default: function () {
@ -146,17 +151,17 @@ export default {
if (file.type.indexOf("image") == -1) {
let ext = file.name.split('.').pop();
file.previewElement.querySelector("[data-dz-thumbnail]").classList.add("d-none");
file.previewElement.querySelector("[data-dz-name]").classList.remove("d-none");
file.previewElement.querySelector("[data-dz-thumbnail]").classList.add("hidden");
file.previewElement.querySelector("[data-dz-name]").classList.remove("hidden");
if (ext == "pdf") {
file.previewElement.querySelector("[data-dz-thumbnail-pdf]").classList.remove("d-none");
file.previewElement.querySelector("[data-dz-thumbnail-pdf]").classList.remove("hidden");
} else if ((ext.indexOf("doc") != -1) || (ext.indexOf("docx") != -1)) {
file.previewElement.querySelector("[data-dz-thumbnail-word]").classList.remove("d-none");
file.previewElement.querySelector("[data-dz-thumbnail-word]").classList.remove("hidden");
} else if ((ext.indexOf("xls") != -1) || (ext.indexOf("xlsx") != -1)) {
file.previewElement.querySelector("[data-dz-thumbnail-excel]").classList.remove("d-none");
file.previewElement.querySelector("[data-dz-thumbnail-excel]").classList.remove("hidden");
} else {
file.previewElement.querySelector("[data-dz-thumbnail-image]").classList.remove("d-none");
file.previewElement.querySelector("[data-dz-thumbnail-image]").classList.remove("hidden");
}
}
}),
@ -208,7 +213,7 @@ export default {
self.files.forEach(async (attachment) => {
if (attachment.download) {
attachment.previewElement.querySelector("[data-dz-download]").href = attachment.download;
attachment.previewElement.querySelector("[data-dz-download]").classList.remove("d-none");
attachment.previewElement.querySelector("[data-dz-download]").classList.remove("hidden");
}
});
@ -253,7 +258,7 @@ export default {
this.files.forEach(async (attachment) => {
if (attachment.download) {
attachment.previewElement.querySelector("[data-dz-download]").href = attachment.download;
attachment.previewElement.querySelector("[data-dz-download]").classList.remove("d-none");
attachment.previewElement.querySelector("[data-dz-download]").classList.remove("hidden");
}
});
@ -271,4 +276,7 @@ export default {
</script>
<style>
.avatar.hidden {
display: none;
}
</style>

View File

@ -1,11 +1,12 @@
<template>
<div class="item-columns-edit">
<i class="fas fa-pencil-alt"></i>&nbsp;
<div class="item-columns-edit group">
<button
type="button"
class="btn-aka-link"
@click="onEditItemColumns">
{{ editColumn.text }}
class="w-6 h-7 flex items-center rounded-lg p-0 group-hover:bg-gray-100"
style="color: rgb(136, 152, 170);"
@click="onEditItemColumns"
>
<span class="material-icons-outlined w-full text-lg text-gray-300 group-hover:text-gray-500">edit</span>
</button>
<component v-bind:is="edit_html" @submit="onSubmit" @cancel="onCancel"></component>
@ -164,7 +165,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
},
},
};

View File

@ -1,105 +1,53 @@
<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 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>
<vue-editor :id="editorId" v-model="content" :editorToolbar="customToolbar" :disabled="disabled"></vue-editor>
</div>
</template>
<script>
import Quill from 'quill';
//var Block = Quill.import('blots/block');
//Block.tagName = 'div';
//Quill.register(Block);
import { VueEditor } from "vue2-editor";
export default {
name: 'akaunting-html-editor',
components: {
VueEditor
},
props: {
name: String,
name: {
type: String,
default: '',
description: 'The name of the field',
},
value: {
type: String,
default: ''
},
theme: {
type: String,
default: 'snow'
model: {
type: [String, Number, Array, Object],
default: '',
description: "Selectbox selected model"
},
readonly: {
disabled: {
type: Boolean,
default: false
default: false,
description: "Selectbox disabled status"
},
},
data () {
return {
editor: null,
editorValue: this.value,
content: null,
lastHtmlValue: '',
editorId: null,
toolbarId: null
customToolbar: [
["bold", "italic", "underline"],
[{ list: "ordered" }, { list: "bullet" }],
],
}
},
methods: {
initialize (Quill) {
let theme = this.theme;
this.editor = new Quill(`#${this.editorId}`, {
theme: theme,
modules: {
toolbar: `#${this.toolbarId}`
},
readOnly: this.readonly
});
if (this.editorValue.length > 0) {
this.editorValue = this.editorValue.replace(new RegExp('<p><br></p>', 'g'), '<p>&nbsp;</p>');
this.editor.pasteHTML(this.editorValue);
}
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 = '';
} else {
html = html.replace(new RegExp('<p><br></p>', 'g'), '<p>&nbsp;</p>');
}
this.content = html;
this.$emit('input', this.content);
});
},
pasteHTML () {
if (!this.editor) {
return;
}
this.editorValue = this.editorValue.replace(new RegExp('<p><br></p>', 'g'), '<p>&nbsp;</p>');
this.editor.pasteHTML(this.editorValue);
},
randomString() {
let text = "";
let possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@ -113,26 +61,21 @@ export default {
},
async mounted () {
this.content = this.editorValue;
this.editorId = this.randomString();
this.toolbarId = this.randomString();
this.$nextTick(() => {
this.initialize(Quill)
});
this.content = this.value;
},
watch: {
value (newVal) {
if (newVal !== this.content) {
this.pasteHTML(newVal);
this.content = newVal;
}
},
editorValue (newVal) {
model (newVal) {
if (newVal !== this.content) {
this.pasteHTML(newVal);
this.content = newVal;
}
},

View File

@ -1,81 +1,62 @@
<template>
<div :id="'select-item-button-' + _uid" class="product-select">
<div class="item-add-new">
<button type="button" class="btn btn-link w-100" @click="showItems">
<i class="fas fa-plus-circle"></i> &nbsp; {{ addItemText }}
</button>
</div>
<div :id="'select-item-button-' + _uid" class="w-full border-b">
<button type="button" class="w-full h-10 flex items-center justify-center text-purple font-medium disabled:bg-gray-200 hover:bg-gray-100" @click="showItems">
<span class="material-icons-outlined text-base font-bold ltr:mr-1 rtl:ml-1">add</span>
{{ addItemText }}
</button>
<div class="aka-select aka-select--fluid" :class="[{'is-open': show.item_list}]" tabindex="-1">
<div class="aka-select-menu" v-if="show.item_list">
<div class="aka-select-search-container">
<span class="aka-prefixed-input aka-prefixed-input--fluid">
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-search"></i>
</span>
</div>
<div :class="[{'is-open': show.item_list}]" tabindex="-1">
<div class="-mt-10.5 left-0 right-0 bg-white border rounded-lg" v-if="show.item_list">
<div class="relative">
<span class="material-icons-round absolute left-4 top-3 text-lg">search</span>
<input
type="text"
data-input="true"
class="form-element px-10 border-t-0 border-l-0 border-r-0 border-gray-200 rounded-none"
autocapitalize="default"
autocorrect="ON"
:placeholder="placeholder"
v-model="search"
@input="onInput"
:ref="'input-item-field-' + _uid"
@keydown.enter="inputEnterEvent"
/>
</div>
<input
type="text"
data-input="true"
class="form-control"
autocapitalize="default"
autocorrect="ON"
:placeholder="placeholder"
v-model="search"
@input="onInput"
:ref="'input-item-field-' + _uid"
@keydown.enter="inputEnterEvent"
/>
</div>
</span>
</div>
<ul class="aka-select-menu-options">
<ul class="form-element p-0 mt-0 border-0 cursor-pointer">
<div
class="aka-select-menu-option"
class="hover:bg-gray-100 px-4"
v-for="(item, index) in sortedItems"
:key="index"
:class="isItemMatched ? 'highlightItem' : ''"
@click="onItemSelected(item)"
>
<div class="item-select w-100">
<div class="item-select-column item-select-info w-75">
<b class="item-select-info-name"><span>{{ item.name }}</span></b>
</div>
<div class="w-full flex items-center justify-between">
<span>{{ item.name }}</span>
<div class="item-select-column item-select-price w-25">
<money
:name="'item-id-' + item.id"
:value="item.price"
v-bind="money"
masked
disabled
class="text-right disabled-money"
></money>
</div>
<money
:name="'item-id-' + item.id"
:value="item.price"
v-bind="money"
masked
disabled
class="text-right disabled-money text-gray"
></money>
</div>
</div>
<div class="aka-select-menu-option" v-if="!sortedItems.length">
<div>
<strong class="text-strong" v-if="!items.length && !search">
<span>{{ noDataText }}</span>
</strong>
<div class="hover:bg-gray-100 text-center py-2 px-4" v-if="!sortedItems.length">
<div class="text-center">
<span v-if="!items.length && !search">{{ noDataText }}</span>
<strong class="text-strong" v-else>
<span>{{ noMatchingDataText }}</span>
</strong>
<span v-else>{{ noMatchingDataText }}</span>
</div>
</div>
</ul>
<div class="aka-select-footer" @click="onItemCreate">
<span>
<i class="fas fa-plus"></i> &nbsp; {{ createNewItemText }}
</span>
<div class="flex items-center justify-center h-11 text-center text-purple font-bold border border-l-0 border-r-0 border-b-0 rounded-bl-lg rounded-br-lg hover:bg-gray-100 cursor-pointer" @click="onItemCreate">
<span class="material-icons text-lg font-bold mr-1">add</span>
{{ createNewItemText }}
</div>
</div>
</div>
@ -91,7 +72,7 @@ import {Money} from 'v-money';
import AkauntingModalAddNew from './AkauntingModalAddNew';
import AkauntingModal from './AkauntingModal';
import AkauntingMoney from './AkauntingMoney';
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
@ -149,7 +130,7 @@ export default {
},
addItemText: {
type: String,
default: 'Add an item',
default: 'Add New Item',
description: ""
},
createNewItemText: {
@ -204,6 +185,7 @@ export default {
return {
item_list: [],
selected_items: [],
changeBackground: true,
search: '', // search column model
show: {
item_selected: false,
@ -361,6 +343,7 @@ export default {
const indexeditem = { ...item, index: this.currentIndex };
this.addItem(indexeditem, 'oldItem');
this.changeBackground = false;
},
addItem(item, itemType) {
@ -390,7 +373,7 @@ export default {
price: 0,
tax_ids: [],
};
this.newItems.push(item);
this.addItem(item, 'newItem');
@ -461,7 +444,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
})
.catch(error => {
@ -480,7 +463,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
},
closeIfClickedOutside(event) {
@ -524,7 +507,6 @@ export default {
sortedItems() {
return this.sortItems();
},
currentIndex() {
return this.$root.form.items.length;
},
@ -560,6 +542,6 @@ export default {
<style scoped>
.highlightItem:first-child {
background-color: #F5F7FA;
background-color: #F5F7FA;
}
</style>

View File

@ -1,37 +1,43 @@
<template>
<SlideYUpTransition :duration="animationDuration">
<div class="modal fade"
<div class="modal fade w-full h-full fixed top-0 left-0 right-0 z-50 overflow-y-auto overflow-hidden modal-add-new fade items-center justify-center"
@click.self="closeModal"
:class="[{'show d-block': show}, {'d-none': !show}]"
:class="[{'show flex flex-wrap modal-background': show}, {'hidden': !show}]"
v-show="show"
tabindex="-1"
role="dialog"
:aria-hidden="!show">
<div class="modal-dialog" :class="modalDialogClass">
<div class="w-full my-10 m-auto flex flex-col" :class="modalDialogClass ? modalDialogClass : 'max-w-screen-sm'">
<slot name="modal-content">
<div class="modal-content">
<div class="card-header pb-2">
<slot name="card-header">
<h4 class="float-left"> {{ title }} </h4>
<div class="p-5 bg-body rounded-tl-lg rounded-tr-lg">
<div class="flex items-center justify-between border-b pb-5">
<slot name="card-header">
<h4 class="text-base font-medium">
{{ title }}
</h4>
<button type="button" class="close" @click="onCancel" aria-hidden="true">&times;</button>
</slot>
<button type="button" class="text-lg" @click="onCancel" aria-hidden="true">
<span class="rounded-md border-b-2 px-2 py-1 text-sm bg-gray-100">esc</span>
</button>
</slot>
</div>
</div>
<slot name="modal-body">
<div class="modal-body" v-html="message">
</div>
<div class="py-1 px-5 bg-body" v-html="message"></div>
</slot>
<div class="card-footer border-top-0 pt-0">
<div class="p-5 bg-body rounded-bl-lg rounded-br-lg border-gray-300">
<slot name="card-footer">
<div class="float-right">
<button type="button" class="btn btn-outline-secondary" @click="onCancel">
{{ button_cancel }}
<div class="flex items-center justify-end">
<button type="button" class="px-6 py-1.5 ltr:mr-2 rtl:ml-2 hover:bg-gray-200 rounded-lg" @click="onCancel">
{{ button_cancel }}
</button>
<button :disabled="form.loading" type="button" class="btn btn-danger button-submit" @click="onConfirm">
<div class="aka-loader"></div><span>{{ button_delete }}</span>
<button :disabled="form.loading" type="button" class="relative flex items-center justify-center bg-red hover:bg-red-700 px-6 py-1.5 text-base rounded-lg disabled:opacity-50 text-white" @click="onConfirm">
<i v-if="form.loading" class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"></i>
<span :class="[{'opacity-0': form.loading}]">{{ button_delete }}</span>
</button>
</div>
</slot>
@ -45,7 +51,7 @@
<script>
import { SlideYUpTransition } from "vue2-transitions";
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
import AkauntingRecurring from './AkauntingRecurring';
@ -64,7 +70,9 @@ export default {
},
props: {
show: Boolean,
show: {
type: Boolean,
},
modalDialogClass: {
type: String,
default: '',
@ -94,7 +102,7 @@ export default {
type: Number,
default: 800,
description: "Modal transition duration"
}
},
},
data() {
@ -112,8 +120,22 @@ export default {
if (this.show) {
let documentClasses = document.body.classList;
documentClasses.add("modal-open");
documentClasses.add("overflow-hidden");
}
if (this.modalDialogClass) {
let modal_size = this.modalDialogClass.replace('modal-', 'max-w-screen-');
this.modalDialogClass = modal_size;
}
},
mounted() {
window.addEventListener('keyup',(e) => {
if (e.key === 'Escape') {
this.onCancel();
}
});
},
methods: {
@ -131,7 +153,7 @@ export default {
onCancel() {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
this.$emit("cancel");
}
@ -142,21 +164,11 @@ export default {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
documentClasses.add("overflow-hidden");
} else {
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
}
}
}
</script>
<style>
.modal.show {
background-color: rgba(0, 0, 0, 0.3);
}
.modal-md {
max-width: 650px;
}
</style>
</script>

View File

@ -1,45 +1,52 @@
<template>
<SlideYUpTransition :duration="animationDuration">
<div class="modal modal-add-new fade"
<div class="modal w-full h-full fixed top-0 left-0 right-0 z-50 overflow-y-auto overflow-hidden modal-add-new fade items-center justify-center"
@click.self="closeModal"
:class="[{'show d-block': show}, {'d-none': !show}]"
:class="[{'show flex flex-wrap modal-background': show}, {'hidden': !show}]"
v-show="show"
tabindex="-1"
role="dialog"
:aria-hidden="!show">
<div class="modal-dialog" :class="modalDialogClass">
<div class="w-full my-10 m-auto flex flex-col" :class="modalDialogClass ? modalDialogClass : 'max-w-screen-sm'">
<slot name="modal-content">
<div class="modal-content">
<div class="card-header pb-2">
<slot name="card-header">
<h4 class="float-left"> {{ title }} </h4>
<button type="button" class="close" @click="onCancel" aria-hidden="true">&times;</button>
</slot>
<div class="p-5 bg-body rounded-tl-lg rounded-tr-lg">
<div class="flex items-center justify-between border-b pb-5">
<slot name="card-header">
<h4 class="text-base font-medium">
{{ title }}
</h4>
<button type="button" class="text-lg" @click="onCancel" aria-hidden="true">
<span class="rounded-md border-b-2 px-2 py-1 text-sm bg-gray-100">esc</span>
</button>
</slot>
</div>
</div>
<slot name="modal-body">
<div class="modal-body pb-0" v-if="!is_component" v-html="message">
</div>
<div class="modal-body pb-0" v-else>
<div class="py-1 px-5 bg-body" v-if="!is_component" v-html="message"></div>
<div class="py-1 px-5 bg-body" v-else>
<form id="form-create" method="POST" action="#"/>
<component v-bind:is="component"></component>
</div>
</slot>
<div class="card-footer border-top-0 pt-0">
<div class="p-5 bg-body rounded-bl-lg rounded-br-lg border-gray-300">
<slot name="card-footer">
<div class="float-right">
<button type="button" class="btn btn-outline-secondary" :class="buttons.cancel.class" @click="onCancel">
<div class="flex items-center justify-end">
<button type="button" class="px-6 py-1.5 mr-2 hover:bg-gray-200 rounded-lg" :class="buttons.cancel.class" @click="onCancel">
{{ buttons.cancel.text }}
</button>
<a v-if="buttons.payment" :href="buttons.payment.url" class="btn btn-white" :class="buttons.payment.class">
<a v-if="buttons.payment" :href="buttons.payment.url" class="px-3 py-1.5 mb-3 sm:mb-0 text-xs bg-transparent hover:bg-transparent font-medium leading-6 ltr:mr-2 rtl:ml-2" :class="buttons.payment.class">
{{ buttons.payment.text }}
</a>
<button :disabled="form.loading" type="button" class="btn button-submit" :class="buttons.confirm.class" @click="onSubmit">
<div class="aka-loader"></div><span>{{ buttons.confirm.text }}</span>
<button :disabled="form.loading" type="button" class="relative px-6 py-1.5 bg-green hover:bg-green-700 text-white rounded-lg" :class="buttons.confirm.class" @click="onSubmit">
<i v-if="form.loading" class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"></i>
<span :class="[{'opacity-0': form.loading}]">{{ buttons.confirm.text }}</span>
</button>
</div>
</slot>
@ -57,7 +64,7 @@ import Vue from 'vue';
import { SlideYUpTransition } from "vue2-transitions";
import AkauntingModal from './AkauntingModal';
import AkauntingMoney from './AkauntingMoney';
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingSelectRemote from './AkauntingSelectRemote';
import AkauntingDate from './AkauntingDate';
@ -83,7 +90,6 @@ export default {
props: {
show: Boolean,
modalDialogClass: '',
is_component: Boolean,
title: {
@ -108,7 +114,7 @@ export default {
},
confirm: {
text: 'Save',
class: 'btn-success',
class: 'disabled:bg-green-100',
}
};
},
@ -119,7 +125,13 @@ export default {
type: Number,
default: 800,
description: "Modal transition duration"
}
},
modalDialogClass: {
type: String,
default: '',
description: "Modal Body size Class"
},
},
data() {
@ -142,7 +154,13 @@ export default {
created: function () {
let documentClasses = document.body.classList;
documentClasses.add("modal-open");
documentClasses.add("overflow-hidden");
if (this.modalDialogClass) {
let modal_size = this.modalDialogClass.replace('modal-', 'max-w-screen-');
this.modalDialogClass = modal_size;
}
},
mounted() {
@ -255,6 +273,12 @@ export default {
})
});
}
window.addEventListener('keyup',(e) => {
if (e.key === 'Escape') {
this.onCancel();
}
});
},
methods: {
@ -273,7 +297,7 @@ export default {
onCancel() {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
this.$emit("cancel");
}
@ -284,21 +308,11 @@ export default {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
documentClasses.add("overflow-hidden");
} else {
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
}
}
}
</script>
<style>
.modal.show {
background-color: rgba(0, 0, 0, 0.3);
}
.modal-md {
max-width: 650px;
}
</style>

View File

@ -1,19 +1,19 @@
<template>
<div v-if="!rowInput" class="form-group"
<div v-if="! rowInput" class="form-group"
:class="[{'has-error': error}, {'required': required}, {'readonly': readonly}, {'disabled': disabled}, col]">
<label v-if="title" :for="name" class="form-control-label">{{ title }}</label>
<div class="input-group input-group-merge" :class="group_class">
<div v-if="icon" class="input-group-prepend">
<div class="relative" :class="group_class">
<!-- <div class="input-group-prepend absolute left-2 bottom-3 text-light-gray">
<span class="input-group-text">
<i :class="'fa fa-' + icon"></i>
<span class="material-icons w-4 h-5 text-sm">{{ icon }}</span>
</span>
</div>
</div> -->
<money :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-control" :class="moneyClass"></money>
<money :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-element" :class="moneyClass"></money>
</div>
<div class="invalid-feedback d-block" v-if="error" v-html="error"></div>
<div class="text-red text-sm mt-1 block" v-if="error" v-html="error"></div>
</div>
<div v-else
@ -27,12 +27,12 @@
</span>
</div>
<money :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-control" :class="moneyClass"></money>
<money :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-element" :class="moneyClass"></money>
</div>
<money v-else :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-control" :class="moneyClass"></money>
<money v-else :name="name" @input="input" :placeholder="placeholder" v-bind="money" :value="model" :disabled="disabled" :masked="masked" class="form-element" :class="moneyClass"></money>
<div class="invalid-feedback d-block" v-if="error" v-html="error"></div>
<div class="text-red text-sm mt-1 block" v-if="error" v-html="error"></div>
</div>
</template>
@ -134,7 +134,7 @@ export default {
description: "Money result value"
},
rowInput: {
type: Boolean,
type: [Boolean, Number],
default: false,
description: "Money result value"
},
@ -158,12 +158,12 @@ export default {
//this.model = this.value;
if (this.dynamicCurrency.code != this.currency.code) {
if (!this.dynamicCurrency.decimal) {
if (! this.dynamicCurrency.decimal) {
this.money = {
decimal: this.dynamicCurrency.decimal_mark,
thousands: this.dynamicCurrency.thousands_separator,
prefix: (this.dynamicCurrency.symbol_first) ? this.dynamicCurrency.symbol : '',
suffix: (!this.dynamicCurrency.symbol_first) ? this.dynamicCurrency.symbol : '',
suffix: (! this.dynamicCurrency.symbol_first) ? this.dynamicCurrency.symbol : '',
precision: parseInt(this.dynamicCurrency.precision),
masked: this.masked
};

View File

@ -4,7 +4,7 @@
<div class="tab-pane tab-example-result fade show active" role="tabpanel" aria-labelledby="-component-tab">
<div class="btn-group btn-group-toggle radio-yes-no" data-toggle="buttons" v-on:click="onClick">
<label class="btn btn-success"
<label class="btn disabled:bg-green-100"
:class="[{'active': value === 1}]">
<input type="radio"
:name="name"

View File

@ -1,73 +1,117 @@
<template>
<div class="row pr-0" :class="formClasses">
<base-input :label="title"
name="recurring_frequency"
:class="frequencyClasses"
:error="frequencyError">
<template slot="label">
<label v-if="title" :class="labelClasses">
{{ title }}
<el-tooltip class="item" effect="dark" placement="top-start">
<div slot="content" v-html="tooltip"></div>
<i class="far fa-question-circle fa-xs" style="vertical-align: top;"></i>
</el-tooltip>
</label>
</template>
<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>
<div class="sm:col-span-6 space-y-8 sm:space-y-2">
<div class="flex items-center" :class="{ 'justify-between sm:justify-start': frequency == 'custom' }">
<div class="w-60 px-2 text-sm">
{{ frequencyText }}
</div>
<el-option v-for="(label, value) in frequencyOptions"
:key="label"
<el-select class="w-36" v-model="frequency" @input="change">
<el-option
v-for="(label, value) in frequencies"
:key="value"
:label="label"
:value="value">
</el-option>
</el-select>
</base-input>
<base-input :label="titleInterval"
name="recurring_interval"
type="number"
:value="0"
@input="change"
:class="invertalClasses"
:error="intervalError"
v-model="recurring_interval"
<div class="w-20 px-2 text-sm text-center" v-if="frequency == 'custom'">
{{ frequencyEveryText }}
</div>
<input type="text" class="w-20 form-element" v-model="interval" @input="change" v-if="frequency == 'custom'">
<el-select class="w-36 ml-2" v-model="customFrequency" @input="change" v-if="frequency == 'custom'">
<el-option
v-for="(label, value) in customFrequencies"
:key="value"
:label="label"
:value="value">
</el-option>
</el-select>
</div>
<div class="flex items-center" :class="{ 'justify-between sm:justify-start': limit !== 'never' }">
<div class="w-60 px-2 text-sm">
{{ startText }}
</div>
<el-date-picker
class="w-36 recurring-invoice-data"
v-model="started_at"
@input="change"
type="date"
align="right"
:format="formatDate"
value-format="yyyy-MM-dd"
:picker-options="{
disabledDate(time) {
return time.getTime() < Date.now();
},
shortcuts: [
{
text: dateRangeText['today'],
onClick(picker) {
picker.$emit('pick', new Date());
}
},
{
text: dateRangeText['yesterday'],
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24);
picker.$emit('pick', date);
}
},
{
text: dateRangeText['week_ago'],
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', date);
}
}
]
}">
</el-date-picker>
<div class="w-20 px-2 text-sm text-center">
{{ middleText }}
</div>
<el-select class="w-20" v-model="limit" @input="change">
<el-option
v-for="(label, value) in limits"
:key="value"
:label="label"
:value="value">
</el-option>
</el-select>
<input type="text" class="w-36 form-element ml-2" v-model="limitDate" v-if="limit == 'after'" @input="change">
<div class="pl-2 text-sm" v-if="limit == 'after'">
{{ endText }}
</div>
<el-date-picker
class="w-36 ml-2 recurring-invoice-data"
v-model="limitDate"
type="date"
align="right"
:format="formatDate"
value-format="yyyy-MM-dd"
v-if="limit == 'on'"
@input="change"
>
</base-input>
<base-input :label="titleFrequency"
name="recurring_custom_frequency"
: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="titleCount"
name="recurring_count"
type="number"
:value="0"
@input="change"
:class="countClasses"
:error="countError"
v-model="recurring_count">
</base-input>
</el-date-picker>
</div>
</div>
</template>
<script>
import { Select, Option, Tooltip } from 'element-ui'
import { Select, Option, DatePicker } from 'element-ui';
export default {
name: 'akaunting-recurring',
@ -75,144 +119,172 @@ export default {
components: {
[Select.name]: Select,
[Option.name]: Option,
[Tooltip.name]: Tooltip,
[DatePicker.name]: DatePicker,
},
props: {
title: {
startText: {
type: String,
default: '',
description: "Modal header title"
default: 'Create first invoice on',
description: "Default reccuring text"
},
titleInterval: {
dateRangeText: {
type: Object,
default: {
today : 'Today',
yesterday : 'Yesterday',
week_ago : 'A week ago',
},
description: "Default reccuring text"
},
middleText: {
type: String,
default: '',
description: "Title of interval"
default: 'and end',
description: "Default reccuring text"
},
titleFrequency: {
endText: {
type: String,
default: '',
description: "Title of frequency"
default: 'invoices',
description: "Default reccuring text"
},
titleCount: {
frequencies: null,
frequencyText: {
type: String,
default: '',
description: "Title of count"
default: 'Repeat this invoice',
description: "Default reccuring text"
},
tooltip: {
frequencyEveryText: {
type: String,
default: '',
description: "Tooltip message"
default: 'every',
description: "Default reccuring text"
},
placeholder: {
frequencyValue: {
type: String,
default: '',
description: "Modal header title"
default: 'monthly',
description: "Default reccuring type"
},
formClasses: {
default: 'col-md-6',
},
formError: null,
frequencyOptions: null,
frequencyValue: null,
frequencyError: null,
customFrequencies: null,
customFrequencyValue: {
type: String,
default: 'monthly',
description: "Default reccuring type"
},
intervalValue: {
startedValue: {
type: String,
default: 'never',
description: "Default reccuring limit"
},
limits: null,
limitValue: {
type: String,
default: 'never',
description: "Default reccuring limit"
},
limitCountValue: {
type: [Number, String],
default: 0,
description: "Default interval value"
description: "Default reccuring limit"
},
intervalError: null,
customFrequencyOptions: null,
customFrequencyValue: null,
customFrequencyError: null,
countValue: {
type: [Number, String],
default: 0,
description: "Default count value"
},
countError: null,
icon: {
limitDateValue: {
type: String,
description: "Prepend icon (left)"
default: '',
description: "Default reccuring limit"
},
labelClasses: {
dateFormat: {
type: String,
description: "Input label css classes",
default: "form-control-label"
default: 'dd MM yyyy',
description: "Default date format"
},
},
data() {
return {
recurring_frequency: null,
recurring_interval: null,
recurring_custom_frequency: null,
recurring_count: null,
frequencyClasses: 'col-md-12',
invertalClasses: 'col-md-2 d-none',
customFrequencyClasses: 'col-md-4 d-none',
countClasses: 'col-md-2 d-none'
frequency: '',
interval: '',
customFrequency: '',
started_at: '',
limit: '',
limitCount: '',
limitDate: '',
formatDate: 'dd MM YYYY',
}
},
created() {
this.recurring_frequency = this.frequencyValue;
this.recurring_interval = this.intervalValue;
this.recurring_custom_frequency = this.customFrequencyValue;
this.recurring_count = this.countValue;
created() {
this.formatDate = this.convertToDarteFormat(this.dateFormat);
},
mounted() {
this.recurring_frequency = this.frequencyValue;
this.frequency = this.frequencyValue;
this.customFrequency = this.customFrequencyValue;
this.started_at = this.startedValue;
if (this.recurring_frequency != 'custom') {
this.recurring_custom_frequency = '';
this.recurring_interval = '0';
this.limit = this.limitValue;
this.limitCount = this.limitCountValue;
this.limitDate = this.limitDateValue;
if (this.limit == 'count') {
if (typeof this.limitDate == 'string') {
this.limit = 'never';
} else {
this.limit = 'after';
}
} else {
this.limit = 'on';
}
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);
setTimeout(function() {
this.change();
}.bind(this), 800);
},
methods: {
change() {
this.$emit('change', this.recurring_frequency);
this.$emit('change', this.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.$emit('frequency', this.frequency);
this.$emit('interval', this.interval);
this.$emit('custom_frequency', this.customFrequency);
this.$emit('started', this.started_at);
this.frequencyChanges();
switch (this.limit) {
case 'after':
this.$emit('limit', 'count');
this.$emit('limit_count', this.limitCount);
break;
case 'on':
this.$emit('limit', 'date');
this.$emit('limit_date', this.limitDate);
break;
case 'never':
default:
this.$emit('limit', 'count');
this.$emit('limit_count', 0);
break;
}
},
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';
}
}
convertToDarteFormat(format) {
return format.replace('d', 'dd')
.replace('M', 'MMM')
.replace('m', 'MM')
.replace('F', 'MMMM')
.replace('y', 'yyyy')
.replace('Y', 'yyyy');
},
}
}
</script>
<style>
.el-input__inner {
height: 42px;
}
</style>

View File

@ -1,45 +1,53 @@
<template>
<div :id="'search-field-' + _uid" class="searh-field tags-input__wrapper js-search">
<div class="tags-group" v-for="(filter, index) in filtered" :index="index">
<span v-if="filter.option" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-option">
<div
:id="'search-field-' + _uid"
class="h-12 my-5 searh-field flex border-b transition-all js-search"
:class="input_focus ? 'border-gray-500' : 'border-gray-300'"
>
<div class="tags-group group items-center" style="display:contents;" v-for="(filter, index) in filtered" :index="index">
<span v-if="filter.option" class="flex items-center bg-purple-lighter text-black border-0 mt-3 px-3 py-4 text-sm cursor-pointer el-tag el-tag--small el-tag-option">
{{ filter.option }}
<i v-if="!filter.operator && !filter.value" class="el-tag__close el-icon-close" @click="onFilterDelete(index)"></i>
<i v-if="!filter.operator && !filter.value" class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close" style="font-size: 16px;" @click="onFilterDelete(index)"></i>
</span>
<span v-if="filter.operator" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-operator">
<i v-if="filter.operator == '='" class="fas fa-equals"></i>
<i v-else-if="filter.operator == '><'" class="fas fa-arrows-alt-h"></i>
<i v-else class="fas fa-not-equal"></i>
<span v-if="filter.operator" class="flex items-center bg-purple-lighter text-black border-2 border-body border-l border-r border-t-0 border-b-0 mt-3 px-3 py-4 text-sm cursor-pointer el-tag el-tag--small el-tag-operator" style="margin-left:0; margin-right:0;">
<span v-if="filter.operator == '='" class="material-icons text-2xl">drag_handle</span>
<span v-else-if="filter.operator == '><'" class="material-icons text-2xl transform rotate-90">height</span>
<i v-if="!filter.value" class="el-tag__close el-icon-close" @click="onFilterDelete(index)"></i>
<img v-else :src="equal_image" class="w-5 h-5 object-cover block" />
<i v-if="!filter.value" class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close " style="font-size: 16px;" @click="onFilterDelete(index)"></i>
</span>
<span v-if="filter.value" class="el-tag el-tag--primary el-tag--small el-tag--light el-tag-value">
<span v-if="filter.value" class="flex items-center bg-purple-lighter text-black border-0 mt-3 px-3 py-4 text-sm cursor-pointer el-tag el-tag--small el-tag-value">
{{ filter.value }}
<i class="el-tag__close el-icon-close" @click="onFilterDelete(index)"></i>
<i class="mt-1 ltr:-right-2 rtl:left-0 rtl:right-0 el-tag__close el-icon-close " style="font-size: 16px;" @click="onFilterDelete(index)"></i>
</span>
</div>
<div class="dropdown input-full">
<div class="relative w-full h-full flex">
<input
v-if="!show_date"
type="text"
class="form-control"
:placeholder="placeholder"
:ref="'input-search-field-' + _uid"
v-model="search"
@focus="onInputFocus"
@input="onInput"
@keyup.enter="onInputConfirm"
v-if="!show_date"
type="text"
class="w-full bg-transparent text-black text-sm border-0 px-10 pb-0 focus:outline-none focus:ring-transparent focus:border-purple-100"
:class="[{'px-4' : !show_icon}]"
:placeholder="placeholder"
:ref="'input-search-field-' + _uid"
v-model="search"
@focus="onInputFocus"
@input="onInput"
@blur="onBlur"
@keyup.enter="onInputConfirm"
/>
<flat-picker
v-else
@on-open="onInputFocus"
@blur="onBlur"
:config="dateConfig"
class="form-control datepicker"
class="w-full bg-transparent text-black text-sm border-0 px-10 pb-0 focus:outline-none focus:ring-transparent focus:border-purple-100 datepicker"
:placeholder="placeholder"
:ref="'input-search-date-field-' + _uid"
value=""
@ -48,42 +56,53 @@
@keyup.enter="onInputConfirm"
>
</flat-picker>
<span class="material-icons absolute bottom-1 ltr:left-3 rtl:right-3 text-lg text-black" style="z-index:-1;">search</span>
<button type="button" class="btn btn-link clear" @click="onSearchAndFilterClear">
<i class="el-tag__close el-icon-close"></i>
<button type="button" class="absolute ltr:right-0 rtl:left-0 top-2 clear" v-if="show_close_icon" @click="onSearchAndFilterClear">
<span class="material-icons text-sm">close</span>
</button>
<div :id="'search-field-option-' + _uid" class="dropdown-menu" :class="[{'show': visible.options}]">
<li ref="" class="dropdown-item" v-for="option in filteredOptions" :data-value="option.key">
<button type="button" class="btn btn-link" @click="onOptionSelected(option.key)">{{ option.value }}</button>
<div :id="'search-field-option-' + _uid" class="absolute top-12 ltr:left-8 rtl:right-8 py-2 bg-white rounded-md border border-gray-200 shadow-xl z-20 list-none dropdown-menu" :class="[{'show': visible.options}]">
<li ref="" class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap" v-for="option in filteredOptions" :data-value="option.key">
<button type="button" class="w-full h-full flex items-center rounded-md px-2 text-sm hover:bg-lilac-100" data-btn="btn btn-link" @click="onOptionSelected(option.key)">{{ option.value }}</button>
</li>
<li ref="" v-if="search" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onInputConfirm">{{ searchText }}</button>
<li ref="" v-if="search" class="p-2 hover:bg-lilac-900 dropdown-item">
<button type="button" class="text-left" @click="onInputConfirm">{{ searchText }}</button>
</li>
</div>
<div :id="'search-field-operator-' + _uid" class="dropdown-menu operator" :class="[{'show': visible.operator}]">
<li ref="" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onOperatorSelected('=')"><i class="fas fa-equals"></i><span class="btn-helptext d-none">{{ operatorIsText }}</span></button>
<div :id="'search-field-operator-' + _uid" class="absolute top-12 ltr:left-8 rtl:right-8 py-2 bg-white rounded-md border border-gray-200 shadow-xl z-20 list-none dropdown-menu operator" :class="[{'show': visible.operator}]">
<li ref="" class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap">
<button type="button" class="w-full h-full flex items-center rounded-md px-2 text-sm hover:bg-lilac-100" @click="onOperatorSelected('=')">
<span class="material-icons text-2xl transform">drag_handle</span>
<span class="text-gray hidden">{{ operatorIsText }}
</span>
</button>
</li>
<li ref="" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onOperatorSelected('!=')"><i class="fas fa-not-equal"></i><span class="btn-helptext d-none">{{ operatorIsNotText }}</span></button>
<li ref="" class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap">
<button type="button" class="w-full h-full flex items-center rounded-md px-2 text-sm hover:bg-lilac-100" @click="onOperatorSelected('!=')">
<img :src="equal_image" class="w-6 h-6 block m-auto" />
<span class="text-gray hidden">{{ operatorIsNotText }}</span>
</button>
</li>
<li v-if="range" ref="" class="dropdown-item">
<button type="button" class="btn btn-link" @click="onOperatorSelected('><')"><i class="fas fa-arrows-alt-h"></i><span class="btn-helptext d-none">{{ operatorIsNotText }}</span></button>
<li v-if="range" ref="" class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap">
<button type="button" class="w-full h-full flex items-center rounded-md px-2 text-sm hover:bg-lilac-100" @click="onOperatorSelected('><')">
<span class="material-icons text-2xl transform rotate-90">height</span>
<span class="text-gray hidden">{{ operatorIsNotText }}</span>
</button>
</li>
</div>
<div :id="'search-field-value-' + _uid" class="dropdown-menu" :class="[{'show': visible.values}]">
<li ref="" class="dropdown-item" v-for="(value) in filteredValues" :data-value="value.key">
<button type="button" class="btn btn-link" @click="onValueSelected(value.key)">{{ value.value }}</button>
<div :id="'search-field-value-' + _uid" class="absolute top-12 ltr:left-8 rtl:right-8 py-2 bg-white rounded-md border border-gray-200 shadow-xl z-20 list-none dropdown-menu" :class="[{'show': visible.values}]">
<li ref="" class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap" v-for="(value) in filteredValues" :data-value="value.key">
<button type="button" class="w-full h-full flex items-center rounded-md px-2 text-sm hover:bg-lilac-100" @click="onValueSelected(value.key)">{{ value.value }}</button>
</li>
<li ref="" class="dropdown-item" v-if="!filteredValues.length">
<button type="button" class="btn btn-link">{{ noMatchingDataText }}</button>
<li ref="" class="w-full flex items-center text-purple px-2 h-9 leading-9 whitespace-nowrap" v-if="!filteredValues.length">
<button type="button" class="w-full h-full flex items-center rounded-md px-2 text-sm hover:bg-lilac-100">{{ noMatchingDataText }}</button>
</li>
</div>
</div>
@ -149,7 +168,7 @@ export default {
},
dateConfig: null
},
model: {
@ -178,6 +197,10 @@ export default {
values: [],
current_value: null,
show_date: false,
show_close_icon: false,
show_icon: true,
equal_image: app_url + "/public/img/tailwind_icons/not-equal.svg",
input_focus: false
};
},
@ -201,7 +224,11 @@ export default {
});
}
//console.log('Focus :' + this.filter_last_step);
this.input_focus = true;
},
onBlur() {
this.input_focus = false;
},
onInputDateSelected(selectedDates, dateStr, instance) {
@ -218,7 +245,7 @@ export default {
date = dates.join('-to-');
}
this.selected_values.push({
key: date,
value: dateStr,
@ -343,6 +370,7 @@ export default {
},
onOptionSelected(value) {
this.show_icon = false;
this.current_value = value;
this.range = false;
@ -474,6 +502,7 @@ export default {
},
onValueSelected(value) {
this.show_close_icon = true;
let select_value = false;
for (let i = 0; i < this.values.length; i++) {
@ -517,6 +546,8 @@ export default {
},
onFilterDelete(index) {
this.show_icon = true;
this.show_close_icon = false;
this.filter_list.push(this.selected_options[index]);
if (this.filter_last_step == 'options') {
@ -567,7 +598,7 @@ export default {
},
closeIfClickedOutside(event) {
if (!document.getElementById('search-field-' + this._uid).contains(event.target) && event.target.className != 'btn btn-link') {
if (!document.getElementById('search-field-' + this._uid).contains(event.target) && event.target.getAttribute('data-btn') != 'btn btn-link') {
this.visible.options = false;
this.visible.operator = false;
this.visible.values = false;
@ -592,8 +623,6 @@ export default {
let search_string = this.value.replace('not ', '').replace(' not ', ' ');
console.log(search_string);
search_string = search_string.split(' ');
search_string.forEach(function (string) {
@ -646,7 +675,7 @@ export default {
value_assigned = true
}
}, this);
if (!value_assigned && (cookie != undefined && cookie[_filter.key])) {
this.selected_values.push(cookie[_filter.key]);
}
@ -672,8 +701,6 @@ export default {
let filter_values = this.convertOption(_filter.values);
if (_filter.key == filter.option) {
console.log('Filter YES');
option = _filter.value;
operator = filter.operator;
@ -685,8 +712,6 @@ export default {
this.selected_options.push(this.filter_list[i]);
console.log(operator);
this.selected_operator.push({
key: operator,
});
@ -779,23 +804,6 @@ export default {
</script>
<style>
.searh-field {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
.searh-field .tags-group {
display: contents;
}
.searh-field .el-tag.el-tag--primary {
background: #f6f9fc;
background-color: #f6f9fc;
border-color: #f6f9fc;
color: #8898aa;
margin-top: 7px;
}
.searh-field .tags-group:hover > span {
background:#cbd4de;
background-color: #cbd4de;
@ -804,64 +812,47 @@ export default {
.searh-field .el-tag.el-tag--primary .el-tag__close.el-icon-close {
color: #8898aa;
margin-top: -3px;
}
.searh-field .el-tag-option {
border-radius: 0.25rem 0 0 0.25rem;
.searh-field .el-tag.el-tag--primary .el-tag__close.el-icon-close:hover {
background-color: transparent;
}
html[dir='ltr'] .searh-field .el-tag-option {
border-radius: 0.50rem 0 0 0.50rem;
margin-left: 10px;
}
html[dir='rtl'] .searh-field .el-tag-option {
border-radius: 0.50rem;
margin-right: 10px;
}
.searh-field .el-tag-operator {
border-radius: 0;
margin-left: -1px;
margin-right: -1px;
}
.searh-field .el-tag-value {
border-radius: 0 0.25rem 0.25rem 0;
html[dir='ltr'] .searh-field .el-tag-value {
border-radius: 0 0.50rem 0.50rem 0;
margin-right: 10px;
}
html[dir='rtl'] .searh-field .el-tag-value {
border-radius: 0.50rem;
margin-left: 10px;
}
html[dir='rtl'] .searh-field .el-tag-operator {
border-radius: 0.50rem;
}
.searh-field .el-select.input-new-tag {
width: 100%;
}
.searh-field .input-full {
width: 100%;
}
.searh-field .btn.btn-link {
width: inherit;
text-align: left;
display: flex;
margin: 0;
text-overflow: inherit;
text-align: left;
text-overflow: ellipsis;
padding: unset;
color: #212529;
}
.searh-field .btn.btn-link.clear {
position: absolute;
top: 0;
right: 0;
width: 45px;
height: 45px;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
color: #adb5bd;
opacity: 1;
}
.searh-field .btn.btn-link.clear:hover {
color: #8898aa;
}
.searh-field .btn-helptext {
margin-left: auto;
color: var(--gray);
@ -884,4 +875,30 @@ export default {
.searh-field .dropdown-menu.operator .btn i:not(:last-child), .btn svg:not(:last-child) {
margin-right: inherit !important;
}
.dropdown-menu {
z-index: 1000;
display: none;
min-width: 10rem;
}
.dropdown-menu li {
margin-bottom: 5px;
}
.dropdown-menu.operator li {
margin-bottom: 5px;
}
.dropdown-menu li:last-child {
margin-bottom: 0;
}
.dropdown-menu > li > button {
width: 100%;
}
.dropdown-menu.show {
display: block;
}
</style>

View File

@ -17,23 +17,24 @@
:readonly="readonly"
:collapse-tags="collapse"
:loading="loading"
class="forms"
>
<div v-if="loading" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty loading">
{{ loadingText }}
<p class="el-select-dropdown__empty pt-2 pb-0 loading">
<span class="material-icons form-spin text-lg animate-spin">data_usage</span>
</p>
</div>
<div v-if="addNew.status && options.length != 0 && sortedOptions.length == 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
<p class="el-select-dropdown__empty pt-2 pb-0">
{{ noMatchingDataText }}
</p>
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item el-select__footer">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<li class="el-select-dropdown__item el-select__footer bg-purple">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -42,14 +43,14 @@
</div>
<div v-else-if="addNew.status && options.length == 0" slot="empty">
<p class="el-select-dropdown__empty">
<p class="el-select-dropdown__empty pt-2 pb-0">
{{ noDataText }}
</p>
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item el-select__footer">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<li class="el-select-dropdown__item el-select__footer bg-purple">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -68,7 +69,7 @@
:disabled="disabledOptions.includes(option.key)"
:label="option.value"
:value="option.key">
<span class="float-left">{{ option.value }}</span>
<span class="float-left" :style="'padding-left: ' + (10 * option.level).toString() + 'px;'"><i v-if="option.level != 0" class="material-icons align-middle text-lg ltr:mr-2 rtl:ml-2">subdirectory_arrow_right</i>{{ option.value }}</span>
<span class="badge badge-pill badge-success float-right mt-2" v-if="new_options[option.key]">{{ addNew.new_text }}</span>
</el-option>
@ -88,10 +89,10 @@
</el-option>
</el-option-group>
<el-option v-if="!loading && addNew.status && options.length != 0 && sortedOptions.length > 0" class="el-select__footer select-add-new" disabled value="">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<el-option v-if="!loading && addNew.status && options.length != 0 && sortedOptions.length > 0" class="el-select-dropdown__item el-select__footer select-add-new bg-purple" disabled value="">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -101,12 +102,11 @@
<component v-bind:is="add_new_html" @submit="onSubmit" @cancel="onCancel"></component>
<span slot="infoBlock" class="badge badge-success badge-resize float-right" v-if="new_options[selected]">{{ addNew.new_text }}</span>
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="new_options[selected]">{{ addNew.new_text }}</span>
<select :name="name" :id="name" v-model="selected" class="d-none">
<option v-for="option in sortedOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
</select>
</base-input>
</template>
@ -118,7 +118,7 @@ import { Select, Option, OptionGroup, ColorPicker } from 'element-ui';
import AkauntingModalAddNew from './AkauntingModalAddNew';
import AkauntingModal from './AkauntingModal';
import AkauntingMoney from './AkauntingMoney';
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
import AkauntingRecurring from './AkauntingRecurring';
@ -266,12 +266,6 @@ export default {
description: "Selectbox collapse status"
},
loadingText: {
type: String,
default: 'Loading...',
description: "Selectbox loading message"
},
noDataText: {
type: String,
default: 'No Data',
@ -392,7 +386,8 @@ export default {
for (const [key, value] of Object.entries(options)) {
values.push({
key: key.toString(),
value: value
value: value,
level: 0
});
}
@ -407,13 +402,15 @@ export default {
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sorted_options.push({
index: index,
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -424,7 +421,8 @@ export default {
for (const [key, value] of Object.entries(created_options)) {
this.sorted_options.push({
key: key.toString(),
value: value
value: value,
level: 0
});
}
} else {
@ -433,13 +431,15 @@ export default {
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sorted_options.push({
index: index,
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -693,7 +693,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
})
.catch(error => {
@ -712,7 +712,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
},
addModal() {
@ -812,13 +812,14 @@ export default {
for (const [key, value] of Object.entries(_options)) {
values.push({
key: key,
value: value
key: key.toString(),
value: value,
level: 0
});
}
this.sorted_options.push({
key: index,
key: index.toString(),
value: values
});
}
@ -828,13 +829,15 @@ export default {
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sorted_options.push({
index: index,
key: option.id,
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -844,8 +847,9 @@ export default {
if (!Array.isArray(options)) {
for (const [key, value] of Object.entries(options)) {
this.sorted_options.push({
key: key,
value: value
key: key.toString(),
value: value,
level: 0
});
}
} else {
@ -854,13 +858,15 @@ export default {
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sorted_options.push({
index: index,
key: option.id,
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -870,57 +876,3 @@ export default {
},
}
</script>
<style scoped>
.form-group .modal {
z-index: 3050;
}
.el-select-dropdown__empty {
padding: 10px 0 0 !important;
}
.el-select-dropdown__empty.loading {
padding: 10px 0 !important;
}
.el-select__footer {
text-align: center !important;
border-top: 1px solid #dee2e6 !important;
padding: 0px !important;
cursor: pointer !important;
color: #3c3f72 !important;
font-weight: bold !important;
height: 38px !important;
line-height: 38px !important;
margin-top: 5px !important;
margin-bottom: -6px !important;
border-bottom-left-radius: 4px !important;
border-bottom-right-radius: 4px !important;
}
.el-select__footer.el-select-dropdown__item.hover {
background-color: inherit !important;
}
.el-select__footer.el-select-dropdown__item:hover, .el-select__footer.el-select-dropdown__item:focus {
background-color: #3c3f72 !important;
color: white !important;
border-top-color: #3c3f72;
}
.el-select__footer div span {
margin-left: 5px;
}
.badge-resize {
float: right;
margin-top: -32px;
margin-right: 35px;
position: relative;
}
.badge.badge-pill.badge-success {
border-radius: 0.375rem;
}
</style>

View File

@ -20,21 +20,21 @@
:loading="loading"
>
<div v-if="loading" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty loading">
{{ loadingText }}
<p class="el-select-dropdown__empty pt-2 pb-0 loading">
<span class="material-icons form-spin text-lg animate-spin">data_usage</span>
</p>
</div>
<div v-if="!loading && addNew.status && options.length != 0 && sortOptions.length == 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
<div v-if="!loading && addNew.status && options.length != 0 && sortedOptions.length == 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty pt-2 pb-0">
{{ noMatchingDataText }}
</p>
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item el-select__footer" disabled value="">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<li class="el-select-dropdown__item el-select__footer bg-purple" disabled value="">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -45,10 +45,10 @@
<div v-if="!loading && addNew.status && options.length == 0">
<el-option class="text-center" disabled :label="noDataText" value="value"></el-option>
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item el-select__footer">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<li class="el-select-dropdown__item el-select__footer bg-purple">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -62,18 +62,18 @@
</span>
</template>
<el-option v-if="!group" v-for="(option, index) in sortOptions"
<el-option v-if="!group" v-for="(option, index) in sortedOptions"
:key="option.key"
:disabled="disabledOptions.includes(option.key)"
:label="option.value"
:value="option.key">
<span class="float-left">{{ option.value }}</span>
<span class="float-left" :style="'padding-left: ' + (10 * option.level).toString() + 'px;'"><i v-if="option.level != 0" class="material-icons align-middle text-lg ltr:mr-2 rtl:ml-2">subdirectory_arrow_right</i>{{ option.value }}</span>
<span class="badge badge-pill badge-success float-right mt-2" v-if="new_options[option.key]">{{ addNew.new_text }}</span>
</el-option>
<el-option-group
v-if="group"
v-for="(group_options, group_index) in sortOptions"
v-for="(group_options, group_index) in sortedOptions"
:key="group_index"
:label="group_options.key">
<el-option
@ -82,15 +82,15 @@
:disabled="disabledOptions.includes(option.key)"
:label="option.value"
:value="option.key">
<span class="float-left">{{ option.value }}</span>
<span class="float-left" :style="'padding-left: ' + (10 * option.level).toString() + 'px;'"><i v-if="option.level != 0" class="material-icons align-middle text-lg ltr:mr-2 rtl:ml-2">subdirectory_arrow_right</i>{{ option.value }}</span>
<span class="badge badge-pill badge-success float-right mt-2" v-if="new_options[option.key]">{{ addNew.new_text }}</span>
</el-option>
</el-option-group>
<el-option v-if="!loading && addNew.status && options.length != 0 && sortOptions.length > 0" class="el-select__footer" :disabled="disabled" value="">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<el-option v-if="!loading && addNew.status && options.length != 0 && sortedOptions.length > 0" class="el-select__footer bg-purple" :disabled="disabled" value="">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -100,10 +100,10 @@
<component v-bind:is="add_new_html" @submit="onSubmit" @cancel="onCancel"></component>
<span slot="infoBlock" class="badge badge-success badge-resize float-right" v-if="new_options[selected]">{{ addNew.new_text }}</span>
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="new_options[selected]">{{ addNew.new_text }}</span>
<select :name="name" :id="name" v-model="selected" class="d-none">
<option v-for="option in sortOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
<option v-for="option in sortedOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
</select>
</base-input>
@ -120,21 +120,21 @@
:loading="loading"
>
<div v-if="loading" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty loading">
{{ loadingText }}
<p class="el-select-dropdown__empty pt-2 pb-0 loading">
<span class="material-icons form-spin text-lg animate-spin">data_usage</span>
</p>
</div>
<div v-if="!loading && addNew.status && options.length != 0 && sortOptions.length == 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty">
<div v-if="!loading && addNew.status && options.length != 0 && sortedOptions.length == 0" class="el-select-dropdown__wrap" slot="empty">
<p class="el-select-dropdown__empty pt-2 pb-0">
{{ noMatchingDataText }}
</p>
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item el-select__footer" disabled value="">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<li class="el-select-dropdown__item el-select__footer bg-purple" disabled value="">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -145,10 +145,10 @@
<div v-if="!loading && addNew.status && options.length == 0">
<el-option class="text-center" disabled :label="noDataText" value="value"></el-option>
<ul class="el-scrollbar__view el-select-dropdown__list">
<li class="el-select-dropdown__item el-select__footer">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<li class="el-select-dropdown__item el-select__footer bg-purple">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -162,18 +162,18 @@
</span>
</template>
<el-option v-if="!group" v-for="(option, index) in sortOptions"
<el-option v-if="!group" v-for="(option, index) in sortedOptions"
:key="option.key"
:disabled="disabledOptions.includes(option.key)"
:label="option.value"
:value="option.key">
<span class="float-left">{{ option.value }}</span>
<span class="float-left" :style="'padding-left: ' + (10 * option.level).toString() + 'px;'"><i v-if="option.level != 0" class="material-icons align-middle text-lg ltr:mr-2 rtl:ml-2">subdirectory_arrow_right</i>{{ option.value }}</span>
<span class="badge badge-pill badge-success float-right mt-2" v-if="new_options[option.key]">{{ addNew.new_text }}</span>
</el-option>
<el-option-group
v-if="group"
v-for="(group_options, group_index) in sortOptions"
v-for="(group_options, group_index) in sortedOptions"
:key="group_index"
:label="group_options.key">
<el-option
@ -182,15 +182,15 @@
:disabled="disabledOptions.includes(option.key)"
:label="option.value"
:value="option.key">
<span class="float-left">{{ option.value }}</span>
<span class="float-left" :style="'padding-left: ' + (10 * option.level).toString() + 'px;'"><i v-if="option.level != 0" class="material-icons align-middle text-lg ltr:mr-2 rtl:ml-2">subdirectory_arrow_right</i>{{ option.value }}</span>
<span class="badge badge-pill badge-success float-right mt-2" v-if="new_options[option.key]">{{ addNew.new_text }}</span>
</el-option>
</el-option-group>
<el-option v-if="!loading && addNew.status && options.length != 0 && sortOptions.length > 0" class="el-select__footer" disabled value="">
<div @click="onAddItem">
<i class="fas fa-plus"></i>
<span>
<el-option v-if="!loading && addNew.status && options.length != 0 && sortedOptions.length > 0" class="el-select__footer bg-purple" disabled value="">
<div class="w-full flex items-center" @click="onAddItem">
<span class="material-icons text-xl text-purple">add</span>
<span class="flex-1 font-bold text-purple">
{{ addNew.text }}
</span>
</div>
@ -200,10 +200,10 @@
<component v-bind:is="add_new_html" @submit="onSubmit" @cancel="onCancel"></component>
<span slot="infoBlock" class="badge badge-success badge-resize float-right" v-if="new_options[selected]">{{ addNew.new_text }}</span>
<span slot="infoBlock" class="absolute right-8 top-3 bg-green text-white px-2 py-1 rounded-md text-xs" v-if="new_options[selected]">{{ addNew.new_text }}</span>
<select :name="name" :id="name" v-model="selected" class="d-none">
<option v-for="option in sortOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
<option v-for="option in sortedOptions" :key="option.key" :value="option.key">{{ option.value }}</option>
</select>
</span>
</template>
@ -216,7 +216,7 @@ import { Select, Option, OptionGroup, ColorPicker } from 'element-ui';
import AkauntingModalAddNew from './AkauntingModalAddNew';
import AkauntingModal from './AkauntingModal';
import AkauntingMoney from './AkauntingMoney';
import AkauntingRadioGroup from './forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './AkauntingRadioGroup';
import AkauntingSelect from './AkauntingSelect';
import AkauntingDate from './AkauntingDate';
import AkauntingRecurring from './AkauntingRecurring';
@ -300,6 +300,12 @@ export default {
description: "Option Sortable type (key|value)"
},
sortOptions: {
type: Boolean,
default: true,
description: 'Sort options by the option_sortable prop, or sorting is made server-side',
},
model: {
type: [String, Number, Array, Object],
default: '',
@ -358,12 +364,6 @@ export default {
description: "Selectbox collapse status"
},
loadingText: {
type: String,
default: 'Loading...',
description: "Selectbox loading message"
},
noDataText: {
type: String,
default: 'No Data',
@ -403,29 +403,33 @@ export default {
selected: this.model,
form: {},
sort_options: [],
sorted_options: [],
new_options: {},
loading: false,
}
},
created() {
this.setSortOptions();
this.setSortedOptions();
},
computed: {
sortOptions() {
if (this.group) {
this.sort_options.sort(this.sortBy("key"));
sortedOptions() {
if (!this.sortOptions) {
return this.sorted_options;
}
for (const [index, options] of Object.entries(this.sort_options)) {
if (this.group) {
this.sorted_options.sort(this.sortBy("key"));
for (const [index, options] of Object.entries(this.sorted_options)) {
options.value.sort(this.sortBy(this.option_sortable));
}
} else {
this.sort_options.sort(this.sortBy(this.option_sortable));
this.sorted_options.sort(this.sortBy(this.option_sortable));
}
return this.sort_options;
return this.sorted_options;
},
},
@ -476,9 +480,9 @@ export default {
}
},
setSortOptions() {
// Reset sort_options
this.sort_options = [];
setSortedOptions() {
// Reset sorted_options
this.sorted_options = [];
let created_options = (this.dynamicOptions) ? this.dynamicOptions : this.options;
@ -491,11 +495,12 @@ export default {
for (const [key, value] of Object.entries(options)) {
values.push({
key: key,
value: value
value: value,
level: 0
});
}
this.sort_options.push({
this.sorted_options.push({
key: index,
value: values
});
@ -503,16 +508,18 @@ export default {
} else {
created_options.forEach(function (option, index) {
if (typeof(option) == 'string') {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: option.id,
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -521,24 +528,27 @@ export default {
// Option set sort_option data
if (!Array.isArray(created_options)) {
for (const [key, value] of Object.entries(created_options)) {
this.sort_options.push({
this.sorted_options.push({
key: key,
value: value
value: value,
level: 0
});
}
} else {
created_options.forEach(function (option, index) {
if (typeof(option) == 'string') {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: option.id,
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -558,7 +568,7 @@ export default {
// Option changed sort_option data
if (this.group) {
this.sort_options.forEach(function (option_group, group_index) {
this.sorted_options.forEach(function (option_group, group_index) {
option_group.value.forEach(function (option, index) {
if (this.multiple) {
let indexs = [];
@ -590,7 +600,7 @@ export default {
}, this);
}, this);
} else {
this.sort_options.forEach(function (option, index) {
this.sorted_options.forEach(function (option, index) {
if (this.multiple) {
let indexs = [];
let values = [];
@ -680,12 +690,12 @@ export default {
if (response.data.data) {
let data = response.data.data;
//this.sort_options = [];
//this.sorted_options = [];
data.forEach(function (option) {
let check = false;
this.sort_options.forEach(function (sort_option) {
this.sorted_options.forEach(function (sort_option) {
if (sort_option.key == option.id) {
check = true;
return;
@ -693,7 +703,7 @@ export default {
});
if (!check) {
this.sort_options.push({
this.sorted_options.push({
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
});
@ -701,12 +711,12 @@ export default {
}, this);
this.sort_options = this.sort_options.filter(item => {
this.sorted_options = this.sorted_options.filter(item => {
return item.value.toLowerCase()
.indexOf(query.toLowerCase()) > -1;
});
} else {
this.sortOptions = [];
this.sortedOptions = [];
}
})
.catch(e => {
@ -715,7 +725,7 @@ export default {
// always executed
});
} else {
this.setSortOptions();
this.setSortedOptions();
}
},
@ -776,7 +786,7 @@ export default {
},
onModal(value) {
this.setSortOptions();
this.setSortedOptions();
let add_new = this.add_new;
@ -874,7 +884,7 @@ export default {
this.form.loading = false;
if (response.data.success) {
this.sort_options.push({
this.sorted_options.push({
key: response.data.data[this.add_new.field.key].toString(),
value: response.data.data[this.add_new.field.value],
});
@ -898,7 +908,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
})
.catch(error => {
@ -917,7 +927,7 @@ export default {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
},
addModal() {
@ -1003,7 +1013,7 @@ export default {
},
dynamicOptions: function(options) {
this.sort_options = [];
this.sorted_options = [];
this.selected = [];
if (this.group) {
@ -1015,11 +1025,12 @@ export default {
for (const [key, value] of Object.entries(_options)) {
values.push({
key: key,
value: value
value: value,
level: 0
});
}
this.sort_options.push({
this.sorted_options.push({
key: index,
value: values
});
@ -1027,16 +1038,18 @@ export default {
} else {
options.forEach(function (option, index) {
if (typeof(option) == 'string') {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: option.id,
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -1045,24 +1058,27 @@ export default {
// Option set sort_option data
if (!Array.isArray(options)) {
for (const [key, value] of Object.entries(options)) {
this.sort_options.push({
this.sorted_options.push({
key: key,
value: value
value: value,
level: 0
});
}
} else {
options.forEach(function (option, index) {
if (typeof(option) == 'string') {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: index.toString(),
value: option
value: option,
level: 0
});
} else {
this.sort_options.push({
this.sorted_options.push({
index: index,
key: option.id,
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name
key: option.id.toString(),
value: (option.title) ? option.title : (option.display_name) ? option.display_name : option.name,
level: (option.level) ? option.level : 0
});
}
}, this);
@ -1072,57 +1088,3 @@ export default {
},
}
</script>
<style scoped>
.form-group .modal {
z-index: 3050;
}
.el-select-dropdown__empty {
padding: 10px 0 0 !important;
}
.el-select-dropdown__empty.loading {
padding: 10px 0 !important;
}
.el-select__footer {
text-align: center !important;
border-top: 1px solid #dee2e6 !important;
padding: 0px !important;
cursor: pointer !important;
color: #3c3f72 !important;
font-weight: bold !important;
height: 38px !important;
line-height: 38px !important;
margin-top: 5px !important;
margin-bottom: -6px !important;
border-bottom-left-radius: 4px !important;
border-bottom-right-radius: 4px !important;
}
.el-select__footer.el-select-dropdown__item.hover {
background-color: inherit !important;
}
.el-select__footer.el-select-dropdown__item:hover, .el-select__footer.el-select-dropdown__item:focus {
background-color: #3c3f72 !important;
color: white !important;
border-top-color: #3c3f72;
}
.el-select__footer div span {
margin-left: 5px;
}
.badge-resize {
float: right;
margin-top: -32px;
margin-right: 35px;
position: relative;
}
.badge.badge-pill.badge-success {
border-radius: 0.375rem;
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<div v-if="video || screenshots">
<div class="swiper-carousel overflow-hidden">
<div class="swiper-wrapper" style="flex-wrap: nowrap !important;">
<div class="swiper-slide" v-for="(screenshot, index) in screenshots" :key="index">
<a :href="screenshot.path_string" class="glightbox">
<img class="rounded-lg object-cover cursor-pointer" :src="screenshot.path_string" :alt="screenshot.alt_attribute" />
<div class="text-gray-700 text-sm my-2">{{ screenshot.description }}</div>
</a>
</div>
</div>
<div v-if="pagination" class="swiper-pagination w-full flex justify-center gap-1"></div>
<div v-if="arrow" class="swiper-button-next ltr:-right-8 rtl:-left-8 top-12">
<span class="material-icons text-5xl">chevron_right</span>
</div>
<div v-if="arrow" class="swiper-button-prev ltr:-left-8 rtl:-right-8 top-12">
<span class="material-icons text-5xl">chevron_left</span>
</div>
</div>
</div>
</template>
<script>
import Swiper, { Navigation, Pagination } from 'swiper';
Swiper.use([Navigation, Pagination]);
import GLightbox from 'glightbox';
import 'glightbox/dist/css/glightbox.min.css';
export default {
name: "akaunting-slider",
props: {
name: {
type: String,
default: null,
description: "App Name"
},
video: {
type: String,
default: null,
description: "App Video"
},
screenshots: {
type: Array,
default: false,
description: "App Screenshots"
},
height: {
type: String,
default: null,
description: "height of the carousel"
},
arrow: {
type: [Boolean, String],
default: false,
},
pagination: {
type: [Boolean, String],
default: false,
},
type: {
type: String,
default: '',
description: "type of the Carousel (card)"
},
sliderView: {
type: [Number, String],
default: 4,
},
sliderRow: {
type: [Number, String],
default: 2,
}
},
mounted() {
let media = [];
if (this.screenshots.length) {
this.screenshots.forEach(function(screenshot) {
media.push({ // For image
thumb: screenshot.path_string,
src: screenshot.path_string,
});
});
}
this.media = media;
new Swiper(".swiper-carousel", {
loop: false,
grid: {
rows: this.sliderRow,
},
spaceBetween: 30,
breakpoints: {
640: {
slidesPerView: 1
},
768: {
slidesPerView: this.sliderView,
}
},
pagination: {
el: ".swiper-pagination",
clickable: true
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
GLightbox({
touchNavigation: true,
loop: false,
autoplayVideos: false,
selector: ".glightbox"
});
},
data: function () {
return {
media: [],
}
},
}
</script>

View File

@ -0,0 +1,68 @@
<template>
<div class="lg:absolute w-12 hidden lg:flex items-center lg:ltr:right-0 lg:rtl:left-0 xl:-top-12 cursor-pointer">
<input type="radio" :name="name" v-show="selected == '0'" @click="enabled = 1" value="1" id="enabled-1" v-model="selected" class="w-full h-full absolute right-0 z-20 opacity-0 cursor-pointer">
<input type="radio" :name="name" v-show="selected == '1'" @click="enabled = 0" value="0" id="enabled-0" v-model="selected" class="w-full h-full absolute left-0 z-20 opacity-0 cursor-pointer">
<div class="absolute left-1 top-1 bg-white w-5 h-5 rounded-full transition transform" :class="selected == '1' ? 'translate-x-full' : 'translate-x-0'"></div>
<div class="block w-full h-7 rounded-full transition transition-color" :class="selected == '1' ? 'bg-green' : 'bg-green-200'"></div>
</div>
</template>
<script>
export default {
name: 'akaunting-switch',
props: {
name: null,
value: {
type: [String, Number, Array, Object, Boolean],
default: '1',
description: "Selectbox selected value"
},
model: {
type: [String, Number, Array, Object, Boolean],
default: '',
description: "Selectbox selected model"
},
inputData: {
type: [String, Number, Array, Object, Boolean],
},
},
data() {
return {
enabled: 1,
selected: this.value,
}
},
methods: {
change() {
this.$emit('interface', this.selected);
this.$emit('change', this.selected);
}
},
mounted() {
this.$emit('interface', this.selected);
setTimeout(function() {
this.change();
}.bind(this), 800);
},
watch: {
value: function(val) {
this.selected = val;
},
selected: function(val) {
this.change();
},
}
}
</script>

View File

@ -5,15 +5,14 @@
@cancel="onCancel"
v-if="display">
<template #modal-body>
<div class="modal-body text-left">
<div class="row">
<div class="col-md-12">
<div class="py-1 px-5 bg-body">
<div class="grid sm:grid-cols-6 gap-x-8 gap-y-6 my-3.5">
<div class="form-group sm:col-span-3">
<base-input
required
class="required"
v-model="form.name"
:label="text.name"
prepend-icon="fas fa-font"
:placeholder="placeholder.name"
:error="form.errors.name[0]"
@input="form.errors.name[0] = ''"
@ -21,59 +20,44 @@
</base-input>
</div>
<div class="col-md-12">
<div class="form-group sm:col-span-3">
<base-input
required
class="required"
:error="form.errors.class[0]"
: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"
@change="form.errors.class[0] = ''"
v-model="form.class" filterable
:placeholder="placeholder.type">
<el-option v-for="(name, value) in types"
class="select-primary"
:key="name"
:label="name"
:value="value">
:key="name"
:label="name"
:value="value">
</el-option>
</el-select>
</base-input>
</div>
<div class="col-md-6">
<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>
<div class="form-group sm:col-span-3">
<base-input :label="text.width">
<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">
:key="option.label"
:label="option.label"
:value="option.value">
</el-option>
</el-select>
</base-input>
</div>
<div class="col-md-6">
<div class="form-group sm:col-span-3">
<base-input
v-model="form.sort"
:label="text.sort"
prepend-icon="fas fa-sort"
:placeholder="placeholder.sort"
:error="form.errors.sort[0]"
@input="form.errors.sort[0] = ''"
@ -84,33 +68,20 @@
</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">
{{ text.cancel }}
</button>
<div class="flex items-center justify-end">
<button type="button" class="flex items-center justify-center px-6 py-1.5 text-base rounded-lg mr-2 bg-transparent hover:bg-gray-300 disabled:bg-gray-200" @click="onCancel">
{{ text.cancel }}
</button>
<button :disabled="form.loading" type="button" class="btn btn-icon btn-success button-submit" @click="onSave">
<span v-if="form.loading" class="btn-inner--icon"><i class="aka-loader"></i></span>
<span :class="[{'ml-0': form.loading}]" class="btn-inner--text">{{ text.save }}</span>
</button>
</div>
</div>
<button :disabled="form.loading" type="button" class="relative flex items-center justify-center bg-green hover:bg-green-700 text-white px-6 py-1.5 text-base rounded-lg disabled:bg-green-100" @click="onSave">
<i v-if="form.loading" class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"></i>
<span :class="[{'opacity-0': form.loading}]">{{ text.save }}</span>
</button>
</div>
</template>
</akaunting-modal>
</template>
<style>
.el-input__prefix
{
left: 20px;
z-index: 999;
top: 2px;
}
</style>
<script>
import axios from 'axios';
import { Select, Option } from 'element-ui';
@ -197,19 +168,19 @@ export default {
widthOptions: [
{
label: '25%',
value: 'col-md-3'
value: 'w-full lg:w-1/4 px-6'
},
{
label: '33%',
value: 'col-md-4'
value: 'w-full lg:w-1/3 px-6'
},
{
label: '50%',
value: 'col-md-6'
value: 'w-full lg:w-2/4 px-12'
},
{
label: '100%',
value: 'col-md-12'
value: 'w-full px-12'
}
],
form: {
@ -282,7 +253,7 @@ export default {
onCancel() {
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
this.display = false;
this.form.name = '';
@ -301,9 +272,9 @@ export default {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
documentClasses.add("overflow-hidden");
} else {
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
}
}

View File

@ -48,6 +48,4 @@
}
}
};
</script>
<style>
</style>
</script>

View File

@ -68,14 +68,4 @@ export default {
}
}
};
</script>
<style scoped lang="scss">
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
}
i {
padding: 0 3px;
}
</style>
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -68,5 +68,3 @@
}
};
</script>
<style>
</style>

View File

@ -45,5 +45,4 @@
iconClasses: [String, Array]
}
};
</script>
<style></style>
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,76 +1,58 @@
<template>
<div class="card-item" :class="{ '-active' : isCardFlipped }">
<div class="card-item__side -front">
<div class="card-item relative w-2/4 lg:w-3/4 h-48 m-auto" :class="{ '-active' : isCardFlipped }">
<div class="card-item__side h-full rounded-lg shadow-lg overflow-hidden" style="transform: perspective(2000px) rotateY(0deg) rotateX(0deg) rotate(0deg);
transform-style: preserve-3d;
transition: all 0.8s cubic-bezier(0.71, 0.03, 0.56, 0.85);
backface-visibility: hidden;">
<div
class="card-item__focus"
:class="{'-active' : focusElementStyle }"
class="absolute w-full h-full left-0 right-0 top-0 rounded-sm overflow-hidden z-10 pointer-events-none opacity-0"
style="transition: all 0.35s cubic-bezier(0.71, 0.03, 0.56, 0.85);"
:class="{'opacity-100' : focusElementStyle }"
:style="focusElementStyle"
ref="focusElement"
></div>
<div class="card-item__cover">
<div class="absolute w-full h-full bg-black left-0 top-0 rounded-lg overflow-hidden" style="background-image: linear-gradient(147deg, #354fce 0%, #0c296b 74%);">
<img
v-if="currentCardBackground"
:src="currentCardBackground"
class="card-item__bg"
class="w-full h-full block object-cover"
/>
</div>
<div class="card-item__wrapper">
<div class="card-item__top">
<div class="relative h-full py-6 px-4 select-none">
<div class="flex items-start justify-between px-4">
<img
src="https://raw.githubusercontent.com/muhammederdem/credit-card-form/master/src/assets/images/chip.png"
class="card-item__chip"
class="w-12"
/>
<div class="card-item__type">
<div class="relative w-full h-12 flex flex-end">
<transition name="slide-fade-up">
<img
:src="'https://raw.githubusercontent.com/muhammederdem/credit-card-form/master/src/assets/images/' + cardType + '.png'"
v-if="cardType"
:key="cardType"
alt
class="card-item__typeImg"
class="w-full h-full object-right-top object-contain"
/>
</transition>
</div>
</div>
<label :for="fields.cardNumber" class="card-item__number" :ref="fields.cardNumber">
<template>
<span v-for="(n, $index) in currentPlaceholder" :key="$index">
<transition name="slide-fade-up">
<div class="card-item__numberItem" v-if="getIsNumberMasked($index, n)">*</div>
<div
class="card-item__numberItem"
:class="{ '-active' : n.trim() === '' }"
:key="currentPlaceholder"
v-else-if="labels.cardNumber.length > $index"
>{{labels.cardNumber[$index]}}</div>
<div
class="card-item__numberItem"
:class="{ '-active' : n.trim() === '' }"
v-else
:key="currentPlaceholder + 1"
>{{n}}</div>
</transition>
</span>
</template>
</label>
<div class="card-item__content">
<label :for="fields.cardName" class="card-item__info" :ref="fields.cardName">
<div class="card-item__holder">Card Holder</div>
<div class="flex items-start justify-between text-white">
<label :for="fields.cardName" class="p-2 block cursor-pointer text-white" :ref="fields.cardName">
<transition name="slide-fade-up">
<div class="card-item__name" v-if="labels.cardName.length" key="1">
<div class="text-lg overflow-hidden" v-if="labels.cardName.length" key="1">
<transition-group name="slide-fade-right">
<span
class="card-item__nameItem"
class="text-lg overflow-hidden"
v-for="(n, $index) in labels.cardName.replace(/\s\s+/g, ' ')"
:key="$index + 1"
>{{n}}</span>
</transition-group>
</div>
<div class="card-item__name" v-else key="2">Full Name</div>
<div class="text-lg overflow-hidden" v-else key="2">Full Name</div>
</transition>
</label>
<div class="card-item__date" ref="cardDate">
<label :for="fields.cardMonth" class="card-item__dateTitle">Expires</label>
<div class="flex flex-wrap shrink-0 text-lg p-2 cursor-pointer" ref="cardDate">
<label :for="fields.cardMonth" class="card-item__dateItem">
<transition name="slide-fade-up">
<span v-if="labels.cardMonth" :key="labels.cardMonth">{{labels.cardMonth}}</span>
@ -86,27 +68,49 @@
</label>
</div>
</div>
<label :for="fields.cardNumber" class="flex justify-around font-medium text-white text-lg p-3 cursor-pointer" :ref="fields.cardNumber">
<template>
<span v-for="(n, $index) in currentPlaceholder" :key="$index">
<transition name="slide-fade-up">
<div class="w-2" v-if="getIsNumberMasked($index, n)">*</div>
<div
class="w-2"
:class="{ '-active' : n.trim() === '' }"
:key="currentPlaceholder"
v-else-if="labels.cardNumber.length > $index"
>{{labels.cardNumber[$index]}}</div>
<div
class="w-2"
:class="{ '-active' : n.trim() === '' }"
v-else
:key="currentPlaceholder + 1"
>{{n}}</div>
</transition>
</span>
</template>
</label>
</div>
</div>
<div class="card-item__side -back">
<div class="card-item__cover">
<div class="card-item__side -back absolute top-0 left-0 w-full p-0 h-full">
<div class="absolute w-full h-full bg-black left-0 top-0 rounded-lg overflow-hidden" style="background-image: linear-gradient(147deg, #354fce 0%, #0c296b 74%);">
<img
v-if="currentCardBackground"
:src="currentCardBackground"
class="card-item__bg"
class="w-full h-full block object-cover"
/>
</div>
<div class="card-item__band"></div>
<div class="card-item__cvv">
<div class="card-item__cvvTitle">CVV</div>
<div class="card-item__cvvBand">
<div class="absolute w-full h-32 mt-12 bg-black"></div>
<div class="relative p-4 text-right">
<div class="pr-4 text-white mb-3">CVV</div>
<div class="h-12 flex items-center justify-end text-black rounded-sm shadow-lg bg-white text-right">
<span v-for="(n, $index) in labels.cardCvv" :key="$index">*</span>
</div>
<div class="card-item__type">
<div class="relative w-24 h-12 flex justify-end">
<img
:src="'https://raw.githubusercontent.com/muhammederdem/credit-card-form/master/src/assets/images/' + cardType + '.png'"
v-if="cardType"
class="card-item__typeImg"
class="w-full h-full object-right-top object-contain"
/>
</div>
</div>
@ -244,3 +248,33 @@ export default {
}
}
</script>
<style scoped>
.card-item.-active .card-item__side.-front {
transform: perspective(1000px) rotateY(180deg) rotateX(0deg) rotateZ(0deg);
}
.card-item.-active .card-item__side.-back {
transform: perspective(1000px) rotateY(0) rotateX(0deg) rotateZ(0deg);
}
.card-item__side {
border-radius: 15px;
overflow: hidden;
transform: perspective(2000px) rotateY(0deg) rotateX(0deg) rotate(0deg);
transform-style: preserve-3d;
transition: all 0.8s cubic-bezier(0.71, 0.03, 0.56, 0.85);
backface-visibility: hidden;
height: 100%;
}
.card-item__side.-back {
position: absolute;
top: 0;
left: 0;
width: 100%;
transform: perspective(2000px) rotateY(-180deg) rotateX(0deg) rotate(0deg);
z-index: 2;
padding: 0;
height: 100%;
}
</style>

View File

@ -1,337 +1,195 @@
<template>
<div>
<div class="row align-items-center" v-if="Object.keys(cards).length">
<div class="col-md-12">
<div class="form-group">
<label for="item_name" class="form-control-label">{{ textCard }}</label>
<div class="input-group-invoice-text" v-for="(name, key, id) in cards">
<div class="custom-radio mb-2">
<button type="button"
:id="'card-'+ key + '-' + id"
class="btn btn-outline-default w-100"
@click="onSelectedCard(key)"
:disabled="loading">
<div class="description text-center">
<i v-if="loading" class="fa fa-spinner fa-spin fa-1x checkout-spin"></i>
{{ name }}
</div>
</button>
</div>
</div>
<div class="input-group-invoice-text">
<div class="custom-radio mb-2">
<button type="button"
id="card-new-card"
class="btn btn-outline-default w-100"
data-toggle="collapse"
data-target="#collapseNewCard"
aria-expanded="false"
aria-controls="collapseNewCard"
:disabled="loading">
<div class="description text-center">
<i v-if="loading" class="fa fa-spinner fa-spin fa-1x checkout-spin"></i>
{{ textNewCard }}
</div>
</button>
</div>
</div>
</div>
</div>
<div class="collapse w-100" id="collapseNewCard">
<div class="row">
<div class="col-md-6 p-5">
<div class="form-group">
<label for="cardName" class="form-control-label">{{ textCardName }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-font"></i>
</span>
</div>
<input
type="text"
:id="fields.cardName"
v-letter-only
@input="changeName"
class="form-control"
:placeholder="placeholderCardName"
:value="formData.cardName"
data-card-field
autocomplete="off"
/>
</div>
</div>
<div class="form-group">
<label for="cardNumber" class="form-control-label">{{ textCardNumber }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-credit-card"></i>
</span>
</div>
<input
type="tel"
:id="fields.cardNumber"
@input="changeNumber"
@focus="focusCardNumber"
@blur="blurCardNumber"
class="form-control"
:placeholder="placeholderCardNumber"
:value="formData.cardNumber"
:maxlength="cardNumberMaxLength"
data-card-field
autocomplete="off"
/>
</div>
</div>
<div class="row">
<div class="col-md-7">
<label for="cardMonth" class="form-control-label">{{ textExpirationDate }}</label>
<div class="card-form__group">
<select
class="card-input__input -select"
:id="fields.cardMonth"
v-model="formData.cardMonth"
@change="changeMonth"
data-card-field
>
<option value disabled selected>{{ textMonth }}</option>
<option
v-bind:value="n < 10 ? '0' + n : n"
v-for="n in 12"
v-bind:disabled="n < minCardMonth"
v-bind:key="n"
>{{generateMonthValue(n)}}</option>
</select>
<select
class="card-input__input -select"
:id="fields.cardYear"
v-model="formData.cardYear"
@change="changeYear"
data-card-field
>
<option value disabled selected>{{ textYear }}</option>
<option
v-bind:value="$index + minCardYear"
v-for="(n, $index) in 12"
v-bind:key="n"
>{{$index + minCardYear}}</option>
</select>
</div>
</div>
<div class="col-md-5">
<div class="form-group">
<label for="cardCvv" class="form-control-label">{{ textCvv }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-key"></i>
</span>
</div>
<input
type="tel"
class="form-control"
:placeholder="placeholderCvv"
v-number-only
:id="fields.cardCvv"
maxlength="4"
:value="formData.cardCvv"
@input="changeCvv"
data-card-field
autocomplete="off"
/>
</div>
</div>
</div>
</div>
<div class="form-group" v-if="storeCard">
<div class="custom-control custom-checkbox">
<input @input="changeStoreCard" :id="'store_card' + _uid" name="store_card" type="checkbox" value="true" class="custom-control-input">
<label :for="'store_card' + _uid" class="custom-control-label">
<strong>{{ textStoreCard }}</strong>
</label>
</div>
</div>
<div class="form-group">
<button class="btn btn-icon btn-success" v-on:click="invaildCard" :disabled="loading">
<div v-if="loading" class="aka-loader-frame">
<div class="aka-loader"></div>
</div>
<span v-if="!loading" class="btn-inner--text">{{ textButton }}</span>
</button>
</div>
</div>
<div class="col-md-6 mt-6">
<Card
:fields="fields"
:labels="formData"
:isCardNumberMasked="isCardNumberMasked"
:randomBackgrounds="randomBackgrounds"
:backgroundImage="backgroundImage"
/>
</div>
</div>
</div>
</div>
<div class="row align-items-center" v-if="!Object.keys(cards).length">
<div class="col-md-6 p-5">
<div class="form-group">
<label for="cardNumber" class="form-control-label">{{ textCardNumber }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-credit-card"></i>
</span>
</div>
<input
type="tel"
:id="fields.cardNumber"
@input="changeNumber"
@focus="focusCardNumber"
@blur="blurCardNumber"
class="form-control"
:placeholder="placeholderCardNumber"
:value="formData.cardNumber"
:maxlength="cardNumberMaxLength"
data-card-field
autocomplete="off"
/>
</div>
<div class="invalid-feedback d-block" v-if="validations.card_number" v-html="validations.card_number[0]"></div>
</div>
<div class="form-group">
<label for="cardName" class="form-control-label">{{ textCardName }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-font"></i>
</span>
</div>
<input
type="text"
:id="fields.cardName"
v-letter-only
@input="changeName"
class="form-control"
:placeholder="placeholderCardName"
:value="formData.cardName"
data-card-field
autocomplete="off"
/>
</div>
<div class="invalid-feedback d-block" v-if="validations.card_name" v-html="validations.card_name[0]"></div>
</div>
<div class="row">
<div class="col-md-7">
<label for="cardMonth" class="form-control-label">{{ textExpirationDate }}</label>
<div class="form-group d-flex">
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-calendar-alt"></i>
</span>
</div>
<select
class="form-control w-50"
:id="fields.cardMonth"
v-model="formData.cardMonth"
@change="changeMonth"
data-card-field
>
<option value="" disabled>{{ textMonth }}</option>
<option
v-bind:value="n < 10 ? '0' + n : n"
v-for="n in 12"
v-bind:disabled="n < minCardMonth"
v-bind:key="n"
>{{generateMonthValue(n)}}</option>
</select>
</div>
<div class="input-group input-group-merge ml-4">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-calendar-alt"></i>
</span>
</div>
<select
class="form-control w-50"
:id="fields.cardYear"
v-model="formData.cardYear"
@change="changeYear"
data-card-field
>
<option value="" disabled>{{ textYear }}</option>
<option
v-bind:value="$index + minCardYear"
v-for="(n, $index) in 12"
v-bind:key="n"
>{{$index + minCardYear}}</option>
</select>
</div>
</div>
</div>
<div class="col-md-5">
<div class="form-group">
<label for="cardCvv" class="form-control-label">{{ textCvv }}</label>
<div class="input-group input-group-merge">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fas fa-key"></i>
</span>
</div>
<input
type="tel"
class="form-control"
:placeholder="placeholderCvv"
v-number-only
:id="fields.cardCvv"
maxlength="4"
:value="formData.cardCvv"
@input="changeCvv"
data-card-field
autocomplete="off"
/>
</div>
<div class="invalid-feedback d-block" v-if="validations.card_cvv" v-html="validations.card_cvv[0]"></div>
</div>
</div>
</div>
<div class="form-group" v-if="storeCard">
<div class="custom-control custom-checkbox">
<input @input="changeStoreCard" :id="'store_card' + _uid" name="store_card" type="checkbox" value="true" class="custom-control-input">
<label :for="'store_card' + _uid" class="custom-control-label">
<strong>{{ textStoreCard }}</strong>
<div>
<div class="flex flex-col" v-if="Object.keys(cards).length">
<div class="gap-y-2">
<div
class="py-2 border-b hover:bg-gray-100 cursor-pointer"
v-for="(name, key, id) in cards" :key="key"
@click="onRegisterCard(id)"
>
<label>
<input
type="radio"
:disabled="loading"
:checked="register_card == id"
>
<span class="ltr:ml-2 rtl:mr-2">{{ name }}</span>
</label>
</div>
</div>
<div class="form-group">
<button class="btn btn-icon btn-success" v-on:click="invaildCard" :disabled="loading">
<div v-if="loading" class="aka-loader-frame">
<div class="aka-loader"></div>
</div>
<span v-if="!loading" class="btn-inner--text">{{ textButton }}</span>
<div class="py-2 border-b hover:bg-gray-100 cursor-pointer" @click="onAddNewCard()">
<label>
<input
type="radio"
id="card-new-card"
name="new-card"
:disabled="loading"
:checked="new_card"
>
<span class="ltr:ml-2 rtl:mr-2">{{ textNewCard }}</span>
</label>
</div>
<div class="flex justify-end" v-for="(name, key, id) in cards" :key="key">
<button
v-if="register_card == id"
type="button"
:id="'card-'+ key + '-' + id"
@click="onSelectedCard(key)"
class="relative flex items-center justify-center px-6 py-1.5 my-2 bg-green hover:bg-green-700 text-white rounded-lg"
:disabled="loading"
>
<i
v-if="loading || register_card_loading"
class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"
>
</i>
<span :class="[{'opacity-0': loading || register_card_loading}]">
{{ textButton }}
</span>
</button>
</div>
<div v-if="new_card" class="w-full mt-3" id="collapseNewCard">
<Card
:fields="fields"
:labels="formData"
:isCardNumberMasked="isCardNumberMasked"
:randomBackgrounds="randomBackgrounds"
:backgroundImage="backgroundImage"
/>
<div class="grid sm:grid-cols-8 gap-x-4 gap-y-6 my-3.5">
<div class="sm:col-span-8">
<label for="cardName" class="form-control-label text-black text-sm font-medium">
{{ textCardName }}
</label>
<input
type="text"
:id="fields.cardName"
v-letter-only
@input="changeName"
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:placeholder="placeholderCardName"
:value="formData.cardName"
data-card-field
autocomplete="off"
/>
<div class="text-red text-sm mt-1 block" v-if="validations.card_name" v-html="validations.card_name[0]"></div>
</div>
<div class="sm:col-span-8">
<label for="cardNumber" class="form-control-label text-black text-sm font-medium">
{{ textCardNumber }}
</label>
<input
type="tel"
:id="fields.cardNumber"
@input="changeNumber"
@focus="focusCardNumber"
@blur="blurCardNumber"
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:placeholder="placeholderCardNumber"
:value="formData.cardNumber"
:maxlength="cardNumberMaxLength"
data-card-field
autocomplete="off"
/>
<div class="text-red text-sm mt-1 block" v-if="validations.card_number" v-html="validations.card_number[0]"></div>
</div>
<div class="sm:col-span-3">
<label for="cardMonth" class="form-control-label text-black text-sm font-medium">
{{ textExpirationDate }}
</label>
<select
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:id="fields.cardMonth"
v-model="formData.cardMonth"
@change="changeMonth"
data-card-field
>
<option value disabled selected>{{ textMonth }}</option>
<option
v-bind:value="n < 10 ? '0' + n : n"
v-for="n in 12"
v-bind:disabled="n < minCardMonth"
v-bind:key="n"
>{{generateMonthValue(n)}}</option>
</select>
</div>
<div class="sm:col-span-3 flex items-end">
<select
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:id="fields.cardYear"
v-model="formData.cardYear"
@change="changeYear"
data-card-field
>
<option value="" disabled>{{ textYear }}</option>
<option
v-bind:value="$index + minCardYear"
v-for="(n, $index) in 12"
v-bind:key="n"
>{{$index + minCardYear}}</option>
</select>
</div>
<div class="sm:col-span-2">
<label for="cardCvv" class="form-control-label text-black text-sm font-medium">
{{ textCvv }}
</label>
<input
type="tel"
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:placeholder="placeholderCvv"
v-number-only
:id="fields.cardCvv"
maxlength="4"
:value="formData.cardCvv"
@input="changeCvv"
data-card-field
autocomplete="off"
/>
<div class="text-red text-sm mt-1 block" v-if="validations.card_cvv" v-html="validations.card_cvv[0]"></div>
</div>
<div class="sm:col-span-8 flex" :class="storeCard ? 'justify-between' : 'justify-end'">
<div class="flex items-center" v-if="storeCard">
<input @input="changeStoreCard" :id="'store_card' + _uid" name="store_card" type="checkbox" value="true" class="rounded-sm text-purple border-gray-300 cursor-pointer disabled:bg-gray-200 focus:outline-none focus:ring-transparent">
<label :for="'store_card' + _uid" class="form-control-label ltr:ml-2 rtl:ml-2">
<strong>{{ textStoreCard }}</strong>
</label>
</div>
<button class="relative flex items-center justify-center px-6 py-1.5 bg-green hover:bg-green-700 text-white rounded-lg" v-on:click="invaildCard" :disabled="loading">
<i
v-if="loading"
class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"
>
</i>
<span :class="[{'opacity-0': loading}]">
{{ textButton }}
</span>
</button>
</div>
</div>
</div>
</div>
<div class="col-md-6 mt--6">
<div class="flex flex-col" v-if="! Object.keys(cards).length">
<Card
:fields="fields"
:labels="formData"
@ -339,16 +197,141 @@
:randomBackgrounds="randomBackgrounds"
:backgroundImage="backgroundImage"
/>
<div class="grid sm:grid-cols-8 gap-x-4 gap-y-6 my-3.5">
<div class="sm:col-span-8">
<label for="cardName" class="form-control-label text-black text-sm font-medium">
{{ textCardName }}
</label>
<input
type="text"
:id="fields.cardName"
v-letter-only
@input="changeName"
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:placeholder="placeholderCardName"
:value="formData.cardName"
data-card-field
autocomplete="off"
/>
<div class="text-red text-sm mt-1 block" v-if="validations.card_name" v-html="validations.card_name[0]"></div>
</div>
<div class="sm:col-span-8">
<label for="cardNumber" class="form-control-label text-black text-sm font-medium">
{{ textCardNumber }}
</label>
<input
type="tel"
:id="fields.cardNumber"
@input="changeNumber"
@focus="focusCardNumber"
@blur="blurCardNumber"
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:placeholder="placeholderCardNumber"
:value="formData.cardNumber"
:maxlength="cardNumberMaxLength"
data-card-field
autocomplete="off"
/>
<div class="text-red text-sm mt-1 block" v-if="validations.card_number" v-html="validations.card_number[0]"></div>
</div>
<div class="sm:col-span-3">
<label for="cardMonth" class="form-control-label text-black text-sm font-medium">
{{ textExpirationDate }}
</label>
<div>
<select
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:id="fields.cardMonth"
v-model="formData.cardMonth"
@change="changeMonth"
data-card-field
>
<option value="" disabled>{{ textMonth }}</option>
<option
v-bind:value="n < 10 ? '0' + n : n"
v-for="n in 12"
v-bind:disabled="n < minCardMonth"
v-bind:key="n"
>{{generateMonthValue(n)}}</option>
</select>
</div>
</div>
<div class="sm:col-span-3 flex items-end">
<select
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:id="fields.cardYear"
v-model="formData.cardYear"
@change="changeYear"
data-card-field
>
<option value="" disabled>{{ textYear }}</option>
<option
v-bind:value="$index + minCardYear"
v-for="(n, $index) in 12"
v-bind:key="n"
>{{$index + minCardYear}}</option>
</select>
</div>
<div class="sm:col-span-2">
<label for="cardCvv" class="form-control-label text-black text-sm font-medium">
{{ textCvv }}
</label>
<input
type="tel"
class="w-full text-sm px-3 py-2.5 mt-1 rounded-lg border border-light-gray text-black placeholder-light-gray bg-white disabled:bg-gray-200 focus:outline-none focus:ring-transparent focus:border-purple"
:placeholder="placeholderCvv"
v-number-only
:id="fields.cardCvv"
maxlength="4"
:value="formData.cardCvv"
@input="changeCvv"
data-card-field
autocomplete="off"
/>
<div class="text-red text-sm mt-1 block" v-if="validations.card_cvv" v-html="validations.card_cvv[0]"></div>
</div>
<div class="sm:col-span-8 flex" :class="storeCard ? 'justify-between' : 'justify-end'">
<div class="flex items-center" v-if="storeCard">
<input @input="changeStoreCard" :id="'store_card' + _uid" name="store_card" type="checkbox" value="true" class="rounded-sm text-purple border-gray-300 cursor-pointer disabled:bg-gray-200 focus:outline-none focus:ring-transparent">
<label :for="'store_card' + _uid" class="form-control-label ltr:ml-2 rtl:ml-2">
<strong>{{ textStoreCard }}</strong>
</label>
</div>
<button class="relative flex items-center justify-center px-6 py-1.5 bg-green hover:bg-green-700 text-white rounded-lg" v-on:click="invaildCard" :disabled="loading">
<i
v-if="loading"
class="animate-submit delay-[0.28s] absolute w-2 h-2 rounded-full left-0 right-0 -top-3.5 m-auto before:absolute before:w-2 before:h-2 before:rounded-full before:animate-submit before:delay-[0.14s] after:absolute after:w-2 after:h-2 after:rounded-full after:animate-submit before:-left-3.5 after:-right-3.5 after:delay-[0.42s]"
>
</i>
<span :class="[{'opacity-0': loading}]">
{{ textButton }}
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import axios from 'axios';
import Card from './Card';
import './../../../css/creditcard/style.scss';
export default {
name: 'CardForm',
@ -369,6 +352,7 @@ export default {
el.addEventListener('keypress', checkValue);
}
},
'letter-only': {
bind (el) {
function checkValue (event) {
@ -496,7 +480,9 @@ export default {
}
}
},
backgroundImage: [String, Object],
randomBackgrounds: {
type: Boolean,
default: true
@ -524,6 +510,9 @@ export default {
mainCardNumber: this.cardNumber,
cardNumberMaxLength: 19,
card_id: 0,
new_card: false,
register_card: 0,
register_card_loading: false
}
},
@ -550,6 +539,22 @@ export default {
},
methods: {
onRegisterCard(id) {
this.register_card = id;
this.new_card = false;
this.register_card_loading = true;
setTimeout(() => {
this.register_card_loading = false;
}, 800);
},
onAddNewCard() {
this.new_card = true;
this.register_card = null;
},
onSelectedCard(card_id) {
this.card_id = card_id;
this.formData.card_id = card_id;
@ -696,6 +701,9 @@ export default {
} else {
this.unMaskCardNumber();
}
},
showNewCard() {
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -8,17 +8,18 @@
{{label}}
</label>
</slot>
<div :class="[
<div class="relative" :class="[
{'input-group input-group-merge': hasIcon},
{'focused': focused},
{'input-group-alternative': alternative},
{'has-label': label || $slots.label},
{'prepend-input-icon': prependIcon},
inputGroupClasses
]">
<div v-if="prependIcon || $slots.prepend" class="input-group-prepend">
<div v-if="prependIcon || $slots.prepend" class="input-group-prepend absolute left-2 bottom-3 text-light-gray">
<span class="input-group-text">
<slot name="prepend">
<i :class="prependIcon"></i>
<span class="material-icons w-4 h-5 text-sm">{{ prependIcon }}</span>
</slot>
</span>
</div>
@ -30,30 +31,30 @@
v-bind="$attrs"
:valid="!error"
:required="required"
class="form-control"
class="form-element"
:class="[{'is-valid': valid === true}, {'is-invalid': error}, inputClasses]">
</slot>
<div v-if="appendIcon || $slots.append" class="input-group-append">
<div v-if="appendIcon || $slots.append" class="input-group-append absolute ltr:right-2 rtl:left-2 bottom-2 text-light-gray">
<span class="input-group-text">
<slot name="append">
<i :class="appendIcon"></i>
<span class="material-icons w-4 h-5 text-sm">{{ appendIcon }}</span>
</slot>
</span>
</div>
<slot name="infoBlock"></slot>
<slot name="error">
<div v-if="error" class="invalid-feedback d-block"
<div v-if="error" class="text-red text-sm mt-1 block"
v-html="error">
</div>
</slot>
<slot name="success">
<div class="valid-feedback" v-if="!error && valid">
<div class="text-green text-sm mt-1" v-if="!error && valid">
{{successMessage}}
</div>
</slot>
</div>
<slot name="error">
<div v-if="footerError" class="invalid-feedback d-block"
<div v-if="footerError" class="text-red text-sm mt-1 block"
v-html="footerError">
</div>
</slot>
@ -181,5 +182,3 @@
}
};
</script>
<style>
</style>

View File

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

View File

@ -1,126 +0,0 @@
<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>
import Dropzone from 'dropzone';
Dropzone.autoDiscover = false;
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 preview = this.multiple ? this.$refs.previewMultiple : this.$refs.previewSingle;
let self = this
let finalOptions = {
...this.options,
url: this.url,
thumbnailWidth: null,
thumbnailHeight: null,
previewsContainer: preview,
previewTemplate: preview.innerHTML,
maxFiles: (!this.multiple) ? 1 : null,
acceptedFiles: (!this.multiple) ? 'image/*' : null,
init: function () {
this.on("addedfile", function (file) {
if (!self.multiple && self.currentFile) {
// this.removeFile(this.currentFile);
}
self.currentFile = file;
})
}
}
this.dropzone = new Dropzone(this.$el, finalOptions)
preview.innerHTML = ''
let evtList = ['drop', 'dragstart', 'dragend', 'dragenter', 'dragover', 'addedfile', 'removedfile', 'thumbnail', 'error', 'processing', 'uploadprogress', 'sending', 'success', 'complete', 'canceled', 'maxfilesreached', 'maxfilesexceeded', 'processingmultiple', 'sendingmultiple', 'successmultiple', 'completemultiple', 'canceledmultiple', 'totaluploadprogress', 'reset', 'queuecomplete']
evtList.forEach(evt => {
this.dropzone.on(evt, (data) => {
this.$emit(evt, data);
if (evt === 'addedfile') {
this.files.push(data)
this.$emit('change', this.files);
} else if (evt === 'removedfile') {
let index = this.files.findIndex(f => f.upload.uuid === data.upload.uuid)
if (index !== -1) {
this.files.splice(index, 1);
}
this.$emit('change', this.files);
}
})
})
}
},
async mounted() {
this.initDropzone()
}
}
</script>
<style>
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -110,9 +110,9 @@
show(val) {
let documentClasses = document.body.classList;
if (val) {
documentClasses.add("modal-open");
documentClasses.add("overflow-hidden");
} else {
documentClasses.remove("modal-open");
documentClasses.remove("overflow-hidden");
}
}
}

View File

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

View File

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

View File

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

View File

@ -2,7 +2,8 @@
<div
@click="tryClose"
data-notify="container"
class="alert alert-notify alert-dismissible"
class="alert alert-notify fixed flex items-center justify-between ltr:right-4 rtl:left-4 p-4 text-black font-bold rounded-lg z-30"
style="width: 500px;"
:class="[
{ 'alert-with-icon': icon },
verticalAlign,
@ -13,16 +14,16 @@
:style="customPosition"
data-notify-position="top-center"
>
<div class="flex items-center ltr:pr-3 rtl:pl-3">
<template v-if="icon || $slots.icon">
<slot name="icon">
<span class="alert-icon" data-notify="icon">
<i :class="icon"></i>
<span class="alert-icon flex items-center ltr:mr-2 rtl:ml-2" data-notify="icon">
<span class="material-icons text-2xl">{{ icon }}</span>
</span>
</slot>
</template>
<span class="alert-text">
<span v-if="title" class="title">
<b>{{ title }}<br/></b>
</span>
@ -32,10 +33,11 @@
:component="component"
></content-render>
</span>
</div>
<slot name="dismiss-icon">
<button type="button"
class="close"
class="close text-2xl"
data-dismiss="alert"
aria-label="Close"
@click="close">
@ -95,7 +97,7 @@
];
return acceptedValues.indexOf(value) !== -1;
},
description: 'Notification type of notification (default|info|primary|danger|warning|success)'
description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)'
},
timeout: {
type: Number,
@ -131,7 +133,23 @@
},
data() {
return {
elmHeight: 0
elmHeight: 0,
typeByClass: {
'default': 'black-100',
'info': 'blue-100',
'primary': 'black-100',
'danger': 'red-100',
'warning': 'orange-100',
'success': 'green-100',
},
textByClass: {
'default': 'black-600',
'info': 'blue-600',
'primary': 'black-600',
'danger': 'red-600',
'warning': 'orange-600',
'success': 'green-600',
}
};
},
computed: {
@ -139,7 +157,7 @@
return this.icon && this.icon.length > 0;
},
alertType() {
return `alert-${this.type}`;
return `bg-${this.typeByClass[this.type]} text-${this.textByClass[this.type]}`;
},
customPosition() {
let initialMargin = 20;
@ -184,24 +202,4 @@
}
}
};
</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>
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,34 +0,0 @@
<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 d-block" v-text="" v-if="errors[name]">
{{ errors[name][0] }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'akaunting-text-group',
props: {
name: '',
text: '',
icon: '',
attributes: [],
value: '',
col: ''
},
data: {
},
created() {
}
}
</script>

View File

@ -1,26 +0,0 @@
<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>

View File

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

View File

@ -17,6 +17,9 @@ import Language from './views/install/Language';
import Database from './views/install/Database';
import Settings from './views/install/Settings';
import Swiper, { Navigation, Pagination, Autoplay } from 'swiper';
Swiper.use([Navigation, Pagination, Autoplay]);
var global_path = new URL(url).protocol + '//' + window.location.host;
var base_path = url.replace(global_path, '');
@ -68,5 +71,19 @@ const router = new VueRouter({
new Vue({
el : '#app',
render: h => h(Install),
router
router,
mounted() {
new Swiper(".swiper-container", {
loop: true,
speed: 1000,
allowTouchMove: true,
autoplay: {
delay: 3000,
},
pagination: {
el: ".swiper-pagination",
clickable: true,
},
});
}
});

View File

@ -7,11 +7,12 @@ import AkauntingContactCard from './../components/AkauntingContactCard';
import AkauntingCompanyEdit from './../components/AkauntingCompanyEdit';
import AkauntingEditItemColumns from './../components/AkauntingEditItemColumns';
import AkauntingItemButton from './../components/AkauntingItemButton';
import AkauntingDocumentButton from './../components/AkauntingDocumentButton';
import AkauntingSearch from './../components/AkauntingSearch';
import AkauntingModal from './../components/AkauntingModal';
import AkauntingMoney from './../components/AkauntingMoney';
import AkauntingModalAddNew from './../components/AkauntingModalAddNew';
import AkauntingRadioGroup from './../components/forms/AkauntingRadioGroup';
import AkauntingRadioGroup from './../components/AkauntingRadioGroup';
import AkauntingSelect from './../components/AkauntingSelect';
import AkauntingSelectRemote from './../components/AkauntingSelectRemote';
import AkauntingDate from './../components/AkauntingDate';
@ -19,6 +20,10 @@ import AkauntingRecurring from './../components/AkauntingRecurring';
import AkauntingHtmlEditor from './../components/AkauntingHtmlEditor';
import AkauntingCountdown from './../components/AkauntingCountdown';
import AkauntingCurrencyConversion from './../components/AkauntingCurrencyConversion';
import AkauntingConnectTransactions from './../components/AkauntingConnectTransactions';
import AkauntingSwitch from './../components/AkauntingSwitch';
import AkauntingSlider from './../components/AkauntingSlider';
import AkauntingColor from './../components/AkauntingColor';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
@ -27,6 +32,10 @@ import NProgressAxios from './../plugins/nprogress-axios';
import { Select, Option, Steps, Step, Button, Link, Tooltip, ColorPicker } from 'element-ui';
import Form from './../plugins/form';
import Swiper, { Navigation, Pagination } from 'swiper';
import GLightbox from 'glightbox';
Swiper.use([Navigation, Pagination]);
export default {
components: {
@ -35,6 +44,7 @@ export default {
AkauntingCompanyEdit,
AkauntingEditItemColumns,
AkauntingItemButton,
AkauntingDocumentButton,
AkauntingSearch,
AkauntingRadioGroup,
AkauntingSelect,
@ -47,6 +57,10 @@ export default {
AkauntingHtmlEditor,
AkauntingCountdown,
AkauntingCurrencyConversion,
AkauntingConnectTransactions,
AkauntingSwitch,
AkauntingSlider,
AkauntingColor,
[Select.name]: Select,
[Option.name]: Option,
[Steps.name]: Steps,
@ -71,6 +85,7 @@ export default {
"thousands_separator":",",
},
all_currencies: [],
content_loading: true
}
},
@ -79,8 +94,32 @@ export default {
},
mounted() {
setTimeout(() => {
this.content_loading = false;
}, 1500);
this.checkNotify();
GLightbox({
touchNavigation: true,
loop: false,
autoplayVideos: false,
selector: ".glightbox-video",
plyr: {
config: {
ratio: '16:9', // or '4:3'
muted: false,
hideControls: true,
youtube: {
noCookie: true,
rel: 0,
showinfo: 0,
iv_load_policy: 3
},
},
},
})
if (aka_currency) {
this.currency = aka_currency;
}
@ -88,12 +127,32 @@ export default {
if (typeof all_currencies !== 'undefined' && all_currencies) {
this.all_currencies = all_currencies;
}
GLightbox({
touchNavigation: true,
loop: false,
autoplayVideos: false,
selector: ".glightbox"
});
new Swiper(".swiper-container", {
loop: false,
slidesPerView: 2,
pagination: {
el: ".swiper-pagination",
clickable: true
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
},
methods: {
// Check Default set notify > store / update action
checkNotify: function () {
if (!flash_notification) {
if (! flash_notification) {
return false;
}
@ -108,7 +167,7 @@ export default {
this.$notify({
message: notify.message,
timeout: timeout,
icon: 'fas fa-bell',
icon: 'error_outline',
type
});
});
@ -130,42 +189,58 @@ export default {
},
// Bulk Action Select all
onSelectAll() {
onSelectAllBulkAction() {
this.bulk_action.selectAll();
},
// Bulk Action Select checked/ unchecked
onSelect() {
// Bulk Action Checkbox checked/ unchecked
onSelectBulkAction() {
this.bulk_action.select();
},
// Bulk Action use selected Change
onChange(event) {
var result = this.bulk_action.change(event);
onChangeBulkAction(type) {
this.bulk_action.change(type);
if (this.bulk_action.message.length) {
this.bulk_action.modal=true;
} else {
this.onActionBulkAction();
}
},
// Bulk Action use selected Action
onAction() {
onActionBulkAction() {
this.bulk_action.action();
},
// Bulk Action modal cancel
onCancel() {
onCancelBulkAction() {
this.bulk_action.modal = false;
},
// Bulk Action Clear selected items
onClear() {
onClearBulkAction() {
this.bulk_action.modal = false;
this.bulk_action.clear();
},
// List Enabled column status changes
onStatus(item_id, event) {
onStatusBulkAction(item_id, event) {
this.bulk_action.status(item_id, event, this.$notify);
},
onDeleteViaConfirmation(delete_id) {
let action = document.getElementById(delete_id).getAttribute('data-action');
let title = document.getElementById(delete_id).getAttribute('data-title');
let message = document.getElementById(delete_id).getAttribute('data-message');
let button_cancel = document.getElementById(delete_id).getAttribute('data-cancel');
let button_delete = document.getElementById(delete_id).getAttribute('data-delete');
this.confirmDelete(action, title, message, button_cancel, button_delete);
},
// Actions > Delete
confirmDelete(url, title, message, button_cancel, button_delete) {
let confirm = {
@ -222,7 +297,7 @@ export default {
// Change bank account get money and currency rate
onChangeAccount(account_id) {
if (!account_id) {
if (! account_id) {
return;
}
@ -230,7 +305,7 @@ export default {
params: {
account_id: account_id
}
})
})
.then(response => {
this.currency = response.data;
@ -243,31 +318,40 @@ export default {
// Change currency get money
onChangeCurrency(currency_code) {
if (!currency_code) {
if (! currency_code) {
return;
}
if (!this.all_currencies.length) {
if (! this.all_currencies.length) {
let currency_promise = Promise.resolve(window.axios.get((url + '/settings/currencies')));
currency_promise.then(response => {
if ( response.data.success) {
if (response.data.success) {
this.all_currencies = response.data.data;
}
this.all_currencies.forEach(function (currency, index) {
if (currency_code == currency.code) {
this.currency = currency;
this.form.currency_code = currency.code;
this.form.currency_rate = currency.rate;
}
}, this);
})
.catch(error => {
this.onChangeCurrency(currency_code);
});
} else {
this.all_currencies.forEach(function (currency, index) {
if (currency_code == currency.code) {
this.currency = currency;
this.form.currency_code = currency.code;
this.form.currency_rate = currency.rate;
}
}, this);
}
this.all_currencies.forEach(function (currency, index) {
if (currency_code == currency.code) {
this.currency = currency;
this.form.currency_code = currency.code;
this.form.currency_rate = currency.rate;
}
}, this);
},
// Pages limit change
@ -294,25 +378,24 @@ export default {
}
if (query_partials[0] == 'limit') {
path += 'limit=' + event.target.value;
path += 'limit=' + event.target.getAttribute("value");
} else {
path += query_partials[0] + '=' + query_partials[1];
}
});
} else {
path = window.location.href + '&limit=' + event.target.value;
path = window.location.href + '&limit=' + event.target.getAttribute("value");
}
} else {
path = window.location.href + '?limit=' + event.target.value;
path = window.location.href + '?limit=' + event.target.getAttribute("value");
}
window.location.href = path;
},
// Dynamic component get path view and show it.
onDynamicComponent(path)
{
onDynamicComponent(path) {
axios.get(path)
.then(response => {
let html = response.data.html;
@ -487,5 +570,92 @@ export default {
this.onChangeCurrency(currency_code);
},
onShareLink(url) {
let share = {
modal: false,
url: url,
title: '',
html: '',
buttons:{}
};
let share_promise = Promise.resolve(window.axios.get(share.url));
share_promise.then(response => {
share.modal = true;
share.title = response.data.data.title;
share.success_message = response.data.data.success_message;
share.html = response.data.html;
share.buttons = response.data.data.buttons;
this.component = Vue.component('add-new-component', (resolve, reject) => {
resolve({
template: '<div id="dynamic-share-component"><akaunting-modal-add-new modal-dialog-class="max-w-screen-md" :show="share.modal" @submit="onCopyLink" @cancel="onCancel" :buttons="share.buttons" :is_component=true :title="share.title" :message="share.html"></akaunting-modal-add-new></div>',
components: {
AkauntingModalAddNew,
},
data: function () {
return {
share: share,
form: {},
}
},
methods: {
onCopyLink(event) {
let type = 'success';
let copyText = document.querySelector('#dynamic-share-component #hidden-share');
copyText.select();
document.execCommand("copy");
this.$notify({
message: this.share.success_message,
timeout: 5000,
icon: 'error_outline',
type
});
this.onCancel();
},
onCancel() {
this.share.modal = false;
this.share.html = null;
let documentClasses = document.body.classList;
documentClasses.remove("modal-open");
},
}
})
});
})
.catch(error => {
})
.finally(function () {
// always executed
});
},
onCopyLink() {
let copy_html = document.getElementById('share');
let copy_badge = document.querySelector('[data-copied]');
copy_html.select();
document.execCommand('copy');
copy_badge.classList.remove('hidden');
copy_badge.classList.add('flex');
copy_html.classList.add('hidden');
setTimeout(() => {
copy_badge.classList.add('hidden');
copy_badge.classList.remove('flex');
copy_html.classList.remove('hidden');
}, 800);
}
}
}

View File

@ -11,6 +11,8 @@ export default {
enabled: 1
},
error_field: {},
create_tax_text: true,
button_loading: false,
}
},
methods: {
@ -62,10 +64,12 @@ export default {
this.$notify({
message: response.data.message,
timeout: timeout,
icon: "fas fa-bell",
icon: "error_outline",
type,
});
this.button_loading = false;
this.onDataChange();
},
@ -80,24 +84,17 @@ export default {
this.$notify({
message: event.message,
timeout: timeout,
icon: "fas fa-bell",
icon: "",
type,
});
this.onDataChange();
},
onStatusControl(status_form, status_item, event) {
status_form.forEach((status) => {
if (status.id == status_item) {
status.enabled = event.target.checked;
}
});
},
onSubmitEvent(form_method, form_url, plus_data, form_list, form_id) {
const formData = new FormData(this.$refs["form"]);
const data = {};
this.button_loading = true;
for (let [key, val] of formData.entries()) {
Object.assign(data, {
@ -154,7 +151,7 @@ export default {
}, this);
this.component = "";
document.body.classList.remove("modal-open");
document.body.classList.remove("overflow-hidden");
this.onDeleteItemMessage(event);
},
@ -166,6 +163,7 @@ export default {
onFailError(error) {
this.error_field = error.response.data.errors;
this.button_loading = false;
}
}
}

View File

@ -37,8 +37,9 @@ export default class BulkAction {
this.select_all = true;
}
if (!this.count) {
if (! this.count) {
this.show = false;
this.hideSearchHTML();
}
}
@ -49,7 +50,7 @@ export default class BulkAction {
this.selected = [];
this.hideSearchHTML();
if (!this.select_all) {
if (! this.select_all) {
this.show = true;
for (let input of document.querySelectorAll('[data-bulk-action]')) {
@ -60,23 +61,23 @@ export default class BulkAction {
this.count = this.selected.length;
}
change(event) {
this.message = event.target.options[event.target.options.selectedIndex].dataset.message;
change(type) {
let action = document.getElementById('button-bulk-action-' + type);
this.value = type;
this.message = action.getAttribute('data-message');
if (typeof(this.message) == "undefined") {
this.message = '';
}
this.path = document.getElementsByName("bulk_action_path")[0].getAttribute('value');
if (event.target.options[event.target.options.selectedIndex].dataset.path) {
this.path = event.target.options[event.target.options.selectedIndex].dataset.path;
}
this.path = action.getAttribute('data-path');
this.type = '*';
if (event.target.options[event.target.options.selectedIndex].dataset.type) {
this.type = event.target.options[event.target.options.selectedIndex].dataset.type;
if (action.getAttribute('data-type')) {
this.type = action.getAttribute('data-type');
}
return this.message;
@ -146,6 +147,7 @@ export default class BulkAction {
window.location.reload(false);
});
break;
default:
let type_promise = Promise.resolve(window.axios.post(this.path, {
@ -177,12 +179,14 @@ export default class BulkAction {
this.show = false;
this.select_all = false;
this.selected = [];
this.hideSearchHTML();
}
hideSearchHTML() {
setInterval(() => {
const search_box_html = document.querySelector('.js-search-box-hidden');
if (search_box_html) {
search_box_html.classList.add('d-none');
}
@ -191,14 +195,14 @@ export default class BulkAction {
// Change enabled status
status(item_id, event, notify) {
var item = event.target;
var status = (event.target.checked) ? 'enable' : 'disable';
let item = event.target;
let status = (event.target.checked) ? 'enable' : 'disable';
window.axios.get(this.path + '/' + item_id + '/' + status)
.then(response => {
var type = (response.data.success) ? 'success' : 'warning';
let type = (response.data.success) ? 'success' : 'warning';
if (!response.data.success) {
if (! response.data.success) {
if (item.checked) {
item.checked = false;
} else {

View File

@ -8,8 +8,6 @@ import VeeValidate from 'vee-validate';
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';
@ -18,13 +16,11 @@ import locale from 'element-ui/lib/locale';
locale.use(lang);
// asset imports
import './../../sass/argon.scss';
export default {
install(Vue) {
Vue.use(GlobalComponents);
Vue.use(GlobalDirectives);
Vue.use(SideBar);
Vue.use(Notifications);
Vue.use(VeeValidate, {
fieldsBagName: 'veeFields',

View File

@ -122,7 +122,15 @@ export default class Form {
this[form_element.getAttribute('data-field')][name] = form_element.value;
}
} else {
this[form_element.getAttribute('data-field')][name] = [];
if (form_element.dataset.type != undefined) {
if (form_element.dataset.type == 'multiple') {
this[form_element.getAttribute('data-field')][name] = [];
} else {
this[form_element.getAttribute('data-field')][name] = '';
}
} else {
this[form_element.getAttribute('data-field')][name] = '';
}
}
}
} else {
@ -163,7 +171,16 @@ export default class Form {
this[name] = form_element.value;
}
} else {
this[name] = [];
if (form_element.dataset.type != undefined) {
if (form_element.dataset.type == 'multiple') {
this[name] = [];
} else {
this[name] = '';
}
} else {
this[name] = '';
}
}
}
} else {

View File

@ -1,19 +1,10 @@
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 BaseAlert from './../components/BaseAlert';
import { Input, Tooltip, Popover } from 'element-ui';
/**
* You can register global components here and use them as a plugin in your main Vue instance
@ -24,19 +15,11 @@ const GlobalComponents = {
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(Badge.name, Badge);
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);

View File

@ -1,57 +0,0 @@
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')
}
})

View File

@ -9,24 +9,44 @@ require('./../../bootstrap');
import Vue from 'vue';
import Form from './../../plugins/form';
import Swiper, { Navigation, Pagination, Autoplay } from 'swiper';
const app = new Vue({
const login = new Vue({
el: '#app',
data: function () {
return {
form: new Form('reset')
form: new Form('auth'),
}
},
mounted() {
Swiper.use([Navigation, Pagination, Autoplay]);
new Swiper(".swiper-container", {
loop: true,
speed: 1000,
allowTouchMove: true,
autoplay: {
delay: 3000,
},
pagination: {
el: ".swiper-pagination",
clickable: true,
},
});
this.checkNotify();
},
methods: {
onSubmit() {
this.form.submit();
},
// Check Default set notify > store / update action
checkNotify: function () {
if (!flash_notification) {
if (! flash_notification) {
return false;
}
@ -36,15 +56,10 @@ const app = new Vue({
this.$notify({
message: notify.message,
timeout: 5000,
icon: 'fas fa-bell',
icon: '',
type
});
});
},
// Form Submit
onSubmit() {
this.form.submit();
},
}
});

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