mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-28 04:01:10 -04:00
init crater
This commit is contained in:
175
resources/assets/js/components/base/BaseButton.vue
Normal file
175
resources/assets/js/components/base/BaseButton.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<button :type="type" :class="btnClass" :disabled="disabled" @click="handleClick">
|
||||
<font-awesome-icon v-if="icon && !loading && !rightIcon" :class="iconClass" :icon="icon" class="vue-icon icon-left" />
|
||||
<font-awesome-icon v-if="loading" :class="iconClass" icon="spinner" class="fa-spin"/>
|
||||
<slot />
|
||||
<font-awesome-icon v-if="icon && !loading && rightIcon" :class="iconClass" :icon="icon" class="vue-icon icon-right" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
round: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
outline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'default'
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
block: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
iconButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'button'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
btnClass () {
|
||||
if (this.isCustomStyle) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let btnClass = 'base-button '
|
||||
|
||||
switch (this.color) {
|
||||
case 'success':
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-success `
|
||||
} else {
|
||||
btnClass += `btn btn-success `
|
||||
}
|
||||
break
|
||||
|
||||
case 'danger':
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-danger `
|
||||
} else {
|
||||
btnClass += `btn btn-danger `
|
||||
}
|
||||
break
|
||||
|
||||
case 'warning':
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-warning `
|
||||
} else {
|
||||
btnClass += `btn btn-warning `
|
||||
}
|
||||
break
|
||||
|
||||
case 'info':
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-info `
|
||||
} else {
|
||||
btnClass += `btn btn-info `
|
||||
}
|
||||
break
|
||||
|
||||
case 'theme':
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-primary `
|
||||
} else {
|
||||
btnClass += `btn btn-primary `
|
||||
}
|
||||
break
|
||||
|
||||
case 'theme-light':
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-light `
|
||||
} else {
|
||||
btnClass += `btn btn-light `
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
if (this.outline) {
|
||||
btnClass += `btn btn-outline-dark `
|
||||
} else {
|
||||
btnClass += `btn btn-dark `
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
switch (this.size) {
|
||||
case 'large':
|
||||
btnClass += 'btn-lg '
|
||||
break
|
||||
|
||||
case 'small':
|
||||
btnClass += 'btn-sm '
|
||||
break
|
||||
|
||||
default:
|
||||
btnClass += 'default-size '
|
||||
}
|
||||
|
||||
if (this.block) {
|
||||
btnClass += 'btn-block '
|
||||
}
|
||||
|
||||
if (this.disabled) {
|
||||
btnClass += ' btn-cursor-not-allowed'
|
||||
}
|
||||
|
||||
return btnClass
|
||||
},
|
||||
iconClass () {
|
||||
if (this.loading || !this.iconButton) {
|
||||
if (this.rightIcon) {
|
||||
return 'ml-2'
|
||||
}
|
||||
return 'mr-2'
|
||||
}
|
||||
return 'icon-button'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick (e) {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
60
resources/assets/js/components/base/BaseCustomerSelect.vue
Normal file
60
resources/assets/js/components/base/BaseCustomerSelect.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="item-selector">
|
||||
<base-select
|
||||
ref="baseSelect"
|
||||
v-model="customerSelect"
|
||||
:options="customers"
|
||||
:show-labels="false"
|
||||
:preserve-search="false"
|
||||
:placeholder="$t('customers.type_or_click')"
|
||||
label="name"
|
||||
class="multi-select-item"
|
||||
@close="checkCustomers"
|
||||
@value="onTextChange"
|
||||
@select="(val) => $emit('select', val)"
|
||||
@remove="deselectCustomer"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
customerSelect: null,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('customer', [
|
||||
'customers'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
...mapActions('customer', [
|
||||
'fetchCustomers'
|
||||
]),
|
||||
async searchCustomers (search) {
|
||||
this.loading = true
|
||||
|
||||
await this.fetchCustomers({search})
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
onTextChange (val) {
|
||||
this.searchCustomers(val)
|
||||
},
|
||||
checkCustomers (val) {
|
||||
if (!this.customers.length) {
|
||||
this.fetchCustomers()
|
||||
}
|
||||
},
|
||||
deselectCustomer () {
|
||||
this.customerSelect = null
|
||||
this.$emit('deselect')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
133
resources/assets/js/components/base/BaseInput.vue
Normal file
133
resources/assets/js/components/base/BaseInput.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="base-input">
|
||||
<font-awesome-icon v-if="icon && isAlignLeftIcon" :icon="icon" class="left-icon"/>
|
||||
<input
|
||||
ref="baseInput"
|
||||
v-model="inputValue"
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:readonly="readOnly"
|
||||
:name="name"
|
||||
:tabindex="tabIndex"
|
||||
:class="[{'input-field-left-icon': icon && isAlignLeftIcon ,'input-field-right-icon': icon && !isAlignLeftIcon ,'invalid': isFieldValid, 'disabled': disabled, 'small-input': small}, inputClass]"
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
class="input-field"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@keyup="handleKeyupEnter"
|
||||
@keydown="handleKeyDownEnter"
|
||||
@blur="handleFocusOut"
|
||||
>
|
||||
<font-awesome-icon v-if="icon && !isAlignLeftIcon" :icon="icon" class="right-icon" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
tabIndex: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, File],
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
invalid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
inputClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
alignIcon: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: 'on'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inputValue: this.value,
|
||||
focus: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isFieldValid () {
|
||||
return this.invalid
|
||||
},
|
||||
isAlignLeftIcon () {
|
||||
if (this.alignIcon === 'left') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'value' () {
|
||||
this.inputValue = this.value
|
||||
},
|
||||
focus () {
|
||||
this.focusInput()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.focusInput()
|
||||
},
|
||||
methods: {
|
||||
focusInput () {
|
||||
if (this.focus) {
|
||||
this.$refs.baseInput.focus()
|
||||
}
|
||||
},
|
||||
handleInput (e) {
|
||||
this.$emit('input', this.inputValue)
|
||||
},
|
||||
handleChange (e) {
|
||||
this.$emit('change', this.inputValue)
|
||||
},
|
||||
handleKeyupEnter (e) {
|
||||
this.$emit('keyup', this.inputValue)
|
||||
},
|
||||
handleKeyDownEnter (e) {
|
||||
this.$emit('keydown', e, this.inputValue)
|
||||
},
|
||||
handleFocusOut (e) {
|
||||
this.$emit('blur', this.inputValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
28
resources/assets/js/components/base/BaseLoader.vue
Normal file
28
resources/assets/js/components/base/BaseLoader.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="base-loader">
|
||||
<div class="spinner"/>
|
||||
<div class="overlay">
|
||||
<div class="loader-inner ball-scale-ripple-multiple">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.overlay {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: rgba(255,255,255,0.4);
|
||||
position: absolute;
|
||||
top: 7%;
|
||||
left: 13%;
|
||||
}
|
||||
</style>
|
||||
66
resources/assets/js/components/base/BaseSwitch.vue
Normal file
66
resources/assets/js/components/base/BaseSwitch.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="base-switch">
|
||||
<input
|
||||
:id="uniqueId"
|
||||
v-model="checkValue"
|
||||
type="checkbox"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@keyup="handleKeyupEnter"
|
||||
@blur="handleFocusOut"
|
||||
>
|
||||
<label class="switch-label" :for="uniqueId"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
id: null,
|
||||
checkValue: this.value
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uniqueId () {
|
||||
return '_' + Math.random().toString(36).substr(2, 9)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'value' () {
|
||||
this.checkValue = this.value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInput (e) {
|
||||
this.$emit('input', e.target.checked)
|
||||
},
|
||||
handleChange (e) {
|
||||
this.$emit('change', this.checkValue)
|
||||
},
|
||||
handleKeyupEnter (e) {
|
||||
this.$emit('keyup', this.checkValue)
|
||||
},
|
||||
handleFocusOut (e) {
|
||||
this.$emit('blur', this.checkValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* .switch-label {
|
||||
margin-bottom: 3px !important
|
||||
} */
|
||||
</style>
|
||||
79
resources/assets/js/components/base/BaseTextArea.vue
Normal file
79
resources/assets/js/components/base/BaseTextArea.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<textarea
|
||||
v-model="inputValue"
|
||||
:rows="rows"
|
||||
:cols="cols"
|
||||
:disabled="disabled"
|
||||
:class="['base-text-area',{'invalid': isFieldValid, 'disabled': disabled}, inputClass]"
|
||||
:placeholder="placeholder"
|
||||
class="text-area-field"
|
||||
@input="handleInput"
|
||||
@change="handleChange"
|
||||
@keyup="handleKeyupEnter"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
rows: {
|
||||
type: String,
|
||||
default: '4'
|
||||
},
|
||||
cols: {
|
||||
type: String,
|
||||
default: '10'
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
invalid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
inputClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inputValue: this.value
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isFieldValid () {
|
||||
return this.invalid
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'value' () {
|
||||
this.inputValue = this.value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInput (e) {
|
||||
this.$emit('input', this.inputValue)
|
||||
},
|
||||
handleChange (e) {
|
||||
this.$emit('change', this.inputValue)
|
||||
},
|
||||
handleKeyupEnter (e) {
|
||||
this.$emit('keyup', this.inputValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
649
resources/assets/js/components/base/base-date-picker/BaseDatepicker.vue
Executable file
649
resources/assets/js/components/base/base-date-picker/BaseDatepicker.vue
Executable file
@ -0,0 +1,649 @@
|
||||
<template>
|
||||
<div :class="[wrapperClass, isRtl ? 'rtl' : '']" class="base-date-input">
|
||||
<date-input
|
||||
:selected-date="selectedDate"
|
||||
:reset-typed-date="resetTypedDate"
|
||||
:format="customFormatter"
|
||||
:translation="translation"
|
||||
:inline="inline"
|
||||
:id="id"
|
||||
:name="name"
|
||||
:ref-name="refName"
|
||||
:open-date="openDate"
|
||||
:placeholder="placeholder"
|
||||
:input-class="inputClass"
|
||||
:typeable="typeable"
|
||||
:clear-button="clearButton"
|
||||
:clear-button-icon="clearButtonIcon"
|
||||
:calendar-button="calendarButton"
|
||||
:calendar-button-icon="calendarButtonIcon"
|
||||
:calendar-button-icon-content="calendarButtonIconContent"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:class="{'required-date': invalid}"
|
||||
:bootstrap-styling="bootstrapStyling"
|
||||
:use-utc="useUtc"
|
||||
@showCalendar="showCalendar"
|
||||
@closeCalendar="close"
|
||||
@typedDate="setTypedDate"
|
||||
@clearDate="clearDate">
|
||||
<slot slot="afterDateInput" name="afterDateInput"/>
|
||||
</date-input>
|
||||
|
||||
<!-- Day View -->
|
||||
<picker-day
|
||||
v-if="allowedToShowView('day')"
|
||||
:page-date="pageDate"
|
||||
:selected-date="selectedDate"
|
||||
:show-day-view="showDayView"
|
||||
:full-month-name="fullMonthName"
|
||||
:allowed-to-show-view="allowedToShowView"
|
||||
:disabled-dates="disabledDates"
|
||||
:highlighted="highlighted"
|
||||
:calendar-class="calendarClass"
|
||||
:calendar-style="calendarStyle"
|
||||
:translation="translation"
|
||||
:page-timestamp="pageTimestamp"
|
||||
:is-rtl="isRtl"
|
||||
:monday-first="mondayFirst"
|
||||
:day-cell-content="dayCellContent"
|
||||
:use-utc="useUtc"
|
||||
@changedMonth="handleChangedMonthFromDayPicker"
|
||||
@selectDate="selectDate"
|
||||
@showMonthCalendar="showMonthCalendar"
|
||||
@selectedDisabled="selectDisabledDate">
|
||||
<slot slot="beforeCalendarHeader" name="beforeCalendarHeader"/>
|
||||
</picker-day>
|
||||
|
||||
<!-- Month View -->
|
||||
<picker-month
|
||||
v-if="allowedToShowView('month')"
|
||||
:page-date="pageDate"
|
||||
:selected-date="selectedDate"
|
||||
:show-month-view="showMonthView"
|
||||
:allowed-to-show-view="allowedToShowView"
|
||||
:disabled-dates="disabledDates"
|
||||
:calendar-class="calendarClass"
|
||||
:calendar-style="calendarStyle"
|
||||
:translation="translation"
|
||||
:is-rtl="isRtl"
|
||||
:use-utc="useUtc"
|
||||
@selectMonth="selectMonth"
|
||||
@showYearCalendar="showYearCalendar"
|
||||
@changedYear="setPageDate">
|
||||
<slot slot="beforeCalendarHeader" name="beforeCalendarHeader"/>
|
||||
</picker-month>
|
||||
|
||||
<!-- Year View -->
|
||||
<picker-year
|
||||
v-if="allowedToShowView('year')"
|
||||
:page-date="pageDate"
|
||||
:selected-date="selectedDate"
|
||||
:show-year-view="showYearView"
|
||||
:allowed-to-show-view="allowedToShowView"
|
||||
:disabled-dates="disabledDates"
|
||||
:calendar-class="calendarClass"
|
||||
:calendar-style="calendarStyle"
|
||||
:translation="translation"
|
||||
:is-rtl="isRtl"
|
||||
:use-utc="useUtc"
|
||||
@selectYear="selectYear"
|
||||
@changedDecade="setPageDate">
|
||||
<slot slot="beforeCalendarHeader" name="beforeCalendarHeader"/>
|
||||
</picker-year>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import en from './src/locale/translations/en'
|
||||
import DateInput from './DateInput'
|
||||
import PickerDay from './PickerDay.vue'
|
||||
import PickerMonth from './PickerMonth.vue'
|
||||
import PickerYear from './PickerYear.vue'
|
||||
import utils, { makeDateUtils } from './src/DateUtils'
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DateInput,
|
||||
PickerDay,
|
||||
PickerMonth,
|
||||
PickerYear
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
validator: val => utils.validateDateInput(val)
|
||||
},
|
||||
name: String,
|
||||
refName: String,
|
||||
id: String,
|
||||
// format: {
|
||||
// type: [String, Function],
|
||||
// default: 'dd MMM yyyy'
|
||||
// },
|
||||
language: {
|
||||
type: Object,
|
||||
default: () => en
|
||||
},
|
||||
openDate: {
|
||||
validator: val => utils.validateDateInput(val)
|
||||
},
|
||||
dayCellContent: Function,
|
||||
fullMonthName: Boolean,
|
||||
disabledDates: Object,
|
||||
highlighted: Object,
|
||||
placeholder: String,
|
||||
inline: Boolean,
|
||||
calendarClass: [String, Object, Array],
|
||||
inputClass: [String, Object, Array],
|
||||
wrapperClass: [String, Object, Array],
|
||||
mondayFirst: Boolean,
|
||||
clearButton: Boolean,
|
||||
clearButtonIcon: String,
|
||||
calendarButton: Boolean,
|
||||
calendarButtonIcon: String,
|
||||
calendarButtonIconContent: String,
|
||||
bootstrapStyling: Boolean,
|
||||
initialView: String,
|
||||
disabled: Boolean,
|
||||
required: Boolean,
|
||||
invalid: Boolean,
|
||||
typeable: Boolean,
|
||||
useUtc: Boolean,
|
||||
minimumView: {
|
||||
type: String,
|
||||
default: 'day'
|
||||
},
|
||||
maximumView: {
|
||||
type: String,
|
||||
default: 'year'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const startDate = this.openDate ? new Date(this.openDate) : new Date()
|
||||
const constructedDateUtils = makeDateUtils(this.useUtc)
|
||||
const pageTimestamp = constructedDateUtils.setDate(startDate, 1)
|
||||
return {
|
||||
/*
|
||||
* Vue cannot observe changes to a Date Object so date must be stored as a timestamp
|
||||
* This represents the first day of the current viewing month
|
||||
* {Number}
|
||||
*/
|
||||
pageTimestamp,
|
||||
/*
|
||||
* Selected Date
|
||||
* {Date}
|
||||
*/
|
||||
selectedDate: null,
|
||||
/*
|
||||
* Flags to show calendar views
|
||||
* {Boolean}
|
||||
*/
|
||||
showDayView: false,
|
||||
showMonthView: false,
|
||||
showYearView: false,
|
||||
/*
|
||||
* Positioning
|
||||
*/
|
||||
calendarHeight: 0,
|
||||
resetTypedDate: new Date(),
|
||||
utils: constructedDateUtils
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (value) {
|
||||
this.setValue(value)
|
||||
},
|
||||
openDate () {
|
||||
this.setPageDate()
|
||||
},
|
||||
initialView () {
|
||||
this.setInitialView()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('preferences', {
|
||||
'format': 'getMomentDateFormat'
|
||||
}),
|
||||
customFormatter () {
|
||||
let newDate = new Date(this.value)
|
||||
return moment(newDate).format(this.format)
|
||||
},
|
||||
computedInitialView () {
|
||||
if (!this.initialView) {
|
||||
return this.minimumView
|
||||
}
|
||||
|
||||
return this.initialView
|
||||
},
|
||||
pageDate () {
|
||||
return new Date(this.pageTimestamp)
|
||||
},
|
||||
|
||||
translation () {
|
||||
return this.language
|
||||
},
|
||||
|
||||
calendarStyle () {
|
||||
return {
|
||||
position: this.isInline ? 'static' : undefined
|
||||
}
|
||||
},
|
||||
isOpen () {
|
||||
return this.showDayView || this.showMonthView || this.showYearView
|
||||
},
|
||||
isInline () {
|
||||
return !!this.inline
|
||||
},
|
||||
isRtl () {
|
||||
return this.translation.rtl === true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Called in the event that the user navigates to date pages and
|
||||
* closes the picker without selecting a date.
|
||||
*/
|
||||
resetDefaultPageDate () {
|
||||
if (this.selectedDate === null) {
|
||||
this.setPageDate()
|
||||
return
|
||||
}
|
||||
this.setPageDate(this.selectedDate)
|
||||
},
|
||||
/**
|
||||
* Effectively a toggle to show/hide the calendar
|
||||
* @return {mixed}
|
||||
*/
|
||||
showCalendar () {
|
||||
if (this.disabled || this.isInline) {
|
||||
return false
|
||||
}
|
||||
if (this.isOpen) {
|
||||
return this.close(true)
|
||||
}
|
||||
this.setInitialView()
|
||||
},
|
||||
/**
|
||||
* Sets the initial picker page view: day, month or year
|
||||
*/
|
||||
setInitialView () {
|
||||
const initialView = this.computedInitialView
|
||||
if (!this.allowedToShowView(initialView)) {
|
||||
throw new Error(`initialView '${this.initialView}' cannot be rendered based on minimum '${this.minimumView}' and maximum '${this.maximumView}'`)
|
||||
}
|
||||
switch (initialView) {
|
||||
case 'year':
|
||||
this.showYearCalendar()
|
||||
break
|
||||
case 'month':
|
||||
this.showMonthCalendar()
|
||||
break
|
||||
default:
|
||||
this.showDayCalendar()
|
||||
break
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Are we allowed to show a specific picker view?
|
||||
* @param {String} view
|
||||
* @return {Boolean}
|
||||
*/
|
||||
allowedToShowView (view) {
|
||||
const views = ['day', 'month', 'year']
|
||||
const minimumViewIndex = views.indexOf(this.minimumView)
|
||||
const maximumViewIndex = views.indexOf(this.maximumView)
|
||||
const viewIndex = views.indexOf(view)
|
||||
|
||||
return viewIndex >= minimumViewIndex && viewIndex <= maximumViewIndex
|
||||
},
|
||||
/**
|
||||
* Show the day picker
|
||||
* @return {Boolean}
|
||||
*/
|
||||
showDayCalendar () {
|
||||
if (!this.allowedToShowView('day')) {
|
||||
return false
|
||||
}
|
||||
this.close()
|
||||
this.showDayView = true
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* Show the month picker
|
||||
* @return {Boolean}
|
||||
*/
|
||||
showMonthCalendar () {
|
||||
if (!this.allowedToShowView('month')) {
|
||||
return false
|
||||
}
|
||||
this.close()
|
||||
this.showMonthView = true
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* Show the year picker
|
||||
* @return {Boolean}
|
||||
*/
|
||||
showYearCalendar () {
|
||||
if (!this.allowedToShowView('year')) {
|
||||
return false
|
||||
}
|
||||
this.close()
|
||||
this.showYearView = true
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* Set the selected date
|
||||
* @param {Number} timestamp
|
||||
*/
|
||||
setDate (timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
this.selectedDate = date
|
||||
this.setPageDate(date)
|
||||
this.$emit('selected', date)
|
||||
this.$emit('input', date)
|
||||
},
|
||||
/**
|
||||
* Clear the selected date
|
||||
*/
|
||||
clearDate () {
|
||||
this.selectedDate = null
|
||||
this.setPageDate()
|
||||
this.$emit('selected', null)
|
||||
this.$emit('input', null)
|
||||
this.$emit('cleared')
|
||||
},
|
||||
/**
|
||||
* @param {Object} date
|
||||
*/
|
||||
selectDate (date) {
|
||||
this.setDate(date.timestamp)
|
||||
if (!this.isInline) {
|
||||
this.close(true)
|
||||
}
|
||||
this.resetTypedDate = new Date()
|
||||
},
|
||||
/**
|
||||
* @param {Object} date
|
||||
*/
|
||||
selectDisabledDate (date) {
|
||||
this.$emit('selectedDisabled', date)
|
||||
},
|
||||
/**
|
||||
* @param {Object} month
|
||||
*/
|
||||
selectMonth (month) {
|
||||
const date = new Date(month.timestamp)
|
||||
if (this.allowedToShowView('day')) {
|
||||
this.setPageDate(date)
|
||||
this.$emit('changedMonth', month)
|
||||
this.showDayCalendar()
|
||||
} else {
|
||||
this.selectDate(month)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Object} year
|
||||
*/
|
||||
selectYear (year) {
|
||||
const date = new Date(year.timestamp)
|
||||
if (this.allowedToShowView('month')) {
|
||||
this.setPageDate(date)
|
||||
this.$emit('changedYear', year)
|
||||
this.showMonthCalendar()
|
||||
} else {
|
||||
this.selectDate(year)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Set the datepicker value
|
||||
* @param {Date|String|Number|null} date
|
||||
*/
|
||||
setValue (date) {
|
||||
if (typeof date === 'string' || typeof date === 'number') {
|
||||
let parsed = new Date(date)
|
||||
date = isNaN(parsed.valueOf()) ? null : parsed
|
||||
}
|
||||
if (!date) {
|
||||
this.setPageDate()
|
||||
this.selectedDate = null
|
||||
return
|
||||
}
|
||||
this.selectedDate = date
|
||||
this.setPageDate(date)
|
||||
},
|
||||
/**
|
||||
* Sets the date that the calendar should open on
|
||||
*/
|
||||
setPageDate (date) {
|
||||
if (!date) {
|
||||
if (this.openDate) {
|
||||
date = new Date(this.openDate)
|
||||
} else {
|
||||
date = new Date()
|
||||
}
|
||||
}
|
||||
this.pageTimestamp = this.utils.setDate(new Date(date), 1)
|
||||
},
|
||||
/**
|
||||
* Handles a month change from the day picker
|
||||
*/
|
||||
handleChangedMonthFromDayPicker (date) {
|
||||
this.setPageDate(date)
|
||||
this.$emit('changedMonth', date)
|
||||
},
|
||||
/**
|
||||
* Set the date from a typedDate event
|
||||
*/
|
||||
setTypedDate (date) {
|
||||
this.setDate(date.getTime())
|
||||
},
|
||||
/**
|
||||
* Close all calendar layers
|
||||
* @param {Boolean} emitEvent - emit close event
|
||||
*/
|
||||
close (emitEvent) {
|
||||
this.showDayView = this.showMonthView = this.showYearView = false
|
||||
if (!this.isInline) {
|
||||
if (emitEvent) {
|
||||
this.$emit('closed')
|
||||
}
|
||||
document.removeEventListener('click', this.clickOutside, false)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Initiate the component
|
||||
*/
|
||||
init () {
|
||||
if (this.value) {
|
||||
this.setValue(this.value)
|
||||
}
|
||||
if (this.isInline) {
|
||||
this.setInitialView()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
</script>
|
||||
<style lang="css">
|
||||
.rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.required-date {
|
||||
border: 1px solid #FB7178;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.vdp-datepicker {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vdp-datepicker * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
width: 300px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header {
|
||||
display: block;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header span {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 71.42857142857143%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .prev,
|
||||
.vdp-datepicker__calendar header .next {
|
||||
width: 14.285714285714286%;
|
||||
float: left;
|
||||
text-indent: -10000px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .prev:after,
|
||||
.vdp-datepicker__calendar header .next:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
border: 6px solid transparent;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .prev:after {
|
||||
border-right: 10px solid #000;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .prev.disabled:after {
|
||||
border-right: 10px solid #ddd;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .next:after {
|
||||
border-left: 10px solid #000;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .next.disabled:after {
|
||||
border-left: 10px solid #ddd;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .prev:not(.disabled),
|
||||
.vdp-datepicker__calendar header .next:not(.disabled),
|
||||
.vdp-datepicker__calendar header .up:not(.disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar header .prev:not(.disabled):hover,
|
||||
.vdp-datepicker__calendar header .next:not(.disabled):hover,
|
||||
.vdp-datepicker__calendar header .up:not(.disabled):hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .disabled {
|
||||
color: #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .flex-rtl {
|
||||
display: flex;
|
||||
width: inherit;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
width: 14.285714285714286%;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).day,
|
||||
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).month,
|
||||
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).year {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).day:hover,
|
||||
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).month:hover,
|
||||
.vdp-datepicker__calendar .cell:not(.blank):not(.disabled).year:hover {
|
||||
border: 1px solid #4bd;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.selected {
|
||||
background: #4bd;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.selected:hover {
|
||||
background: #4bd;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.selected.highlighted {
|
||||
background: #4bd;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.highlighted {
|
||||
background: #cae5ed;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.highlighted.disabled {
|
||||
color: #a3a3a3;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.grey {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.grey:hover {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.day-header {
|
||||
font-size: 75%;
|
||||
white-space: nowrap;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .cell.day-header:hover {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.vdp-datepicker__calendar .month,
|
||||
.vdp-datepicker__calendar .year {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
.vdp-datepicker__clear-button,
|
||||
.vdp-datepicker__calendar-button {
|
||||
cursor: pointer;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.vdp-datepicker__clear-button.disabled,
|
||||
.vdp-datepicker__calendar-button.disabled {
|
||||
color: #999;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
158
resources/assets/js/components/base/base-date-picker/DateInput.vue
Executable file
158
resources/assets/js/components/base/base-date-picker/DateInput.vue
Executable file
@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div :class="{'input-group' : bootstrapStyling}">
|
||||
<!-- Calendar Button -->
|
||||
<span v-if="calendarButton" class="vdp-datepicker__calendar-button" :class="{'input-group-prepend' : bootstrapStyling}" @click="showCalendar" v-bind:style="{'cursor:not-allowed;' : disabled}">
|
||||
<span :class="{'input-group-text' : bootstrapStyling}">
|
||||
<font-awesome-icon :icon="calendarButtonIcon"/>
|
||||
</span>
|
||||
</span>
|
||||
<!-- Input -->
|
||||
<input
|
||||
:type="inline ? 'hidden' : 'text'"
|
||||
:class="[computedInputClass, {'invalid': isFieldValid}]"
|
||||
:name="name"
|
||||
:ref="refName"
|
||||
:id="id"
|
||||
:value="formattedValue"
|
||||
:open-date="openDate"
|
||||
:placeholder="placeholder"
|
||||
:clear-button="clearButton"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:readonly="!typeable"
|
||||
class="date-field"
|
||||
@click="showCalendar"
|
||||
@keyup="parseTypedDate"
|
||||
@blur="inputBlurred"
|
||||
autocomplete="off">
|
||||
<!-- Clear Button -->
|
||||
<span v-if="clearButton && selectedDate" class="vdp-datepicker__clear-button" :class="{'input-group-append' : bootstrapStyling}" @click="clearDate()">
|
||||
<span :class="{'input-group-text' : bootstrapStyling}">
|
||||
<!-- <i :class="clearButtonIcon">
|
||||
<span v-if="!clearButtonIcon">×</span>
|
||||
</i> -->
|
||||
<font-awesome-icon :icon="clearButtonIcon"/>
|
||||
</span>
|
||||
</span>
|
||||
<slot name="afterDateInput"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { makeDateUtils } from './src/DateUtils'
|
||||
export default {
|
||||
props: {
|
||||
selectedDate: Date,
|
||||
resetTypedDate: [Date],
|
||||
format: [String, Function],
|
||||
translation: Object,
|
||||
inline: Boolean,
|
||||
id: String,
|
||||
name: String,
|
||||
refName: String,
|
||||
openDate: Date,
|
||||
placeholder: String,
|
||||
inputClass: [String, Object, Array],
|
||||
clearButton: Boolean,
|
||||
clearButtonIcon: String,
|
||||
calendarButton: Boolean,
|
||||
calendarButtonIcon: String,
|
||||
calendarButtonIconContent: String,
|
||||
disabled: Boolean,
|
||||
required: Boolean,
|
||||
typeable: Boolean,
|
||||
bootstrapStyling: Boolean,
|
||||
useUtc: Boolean,
|
||||
invalid: Boolean
|
||||
},
|
||||
data () {
|
||||
const constructedDateUtils = makeDateUtils(this.useUtc)
|
||||
return {
|
||||
input: null,
|
||||
typedDate: false,
|
||||
utils: constructedDateUtils
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedValue () {
|
||||
if (!this.selectedDate) {
|
||||
return null
|
||||
}
|
||||
if (this.typedDate) {
|
||||
return this.typedDate
|
||||
}
|
||||
return typeof this.format === 'function'
|
||||
? this.format(this.selectedDate)
|
||||
: this.utils.formatDate(new Date(this.selectedDate), this.format, this.translation)
|
||||
},
|
||||
|
||||
computedInputClass () {
|
||||
if (this.bootstrapStyling) {
|
||||
if (typeof this.inputClass === 'string') {
|
||||
return [this.inputClass, 'form-control'].join(' ')
|
||||
}
|
||||
return {'form-control': true, ...this.inputClass}
|
||||
}
|
||||
return this.inputClass
|
||||
},
|
||||
|
||||
isFieldValid () {
|
||||
return this.invalid
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
resetTypedDate () {
|
||||
this.typedDate = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showCalendar () {
|
||||
this.$emit('showCalendar')
|
||||
},
|
||||
/**
|
||||
* Attempt to parse a typed date
|
||||
* @param {Event} event
|
||||
*/
|
||||
parseTypedDate (event) {
|
||||
// close calendar if escape or enter are pressed
|
||||
if ([
|
||||
27, // escape
|
||||
13 // enter
|
||||
].includes(event.keyCode)) {
|
||||
this.input.blur()
|
||||
}
|
||||
|
||||
if (this.typeable) {
|
||||
const typedDate = Date.parse(this.input.value)
|
||||
if (!isNaN(typedDate)) {
|
||||
this.typedDate = this.input.value
|
||||
this.$emit('typedDate', new Date(this.typedDate))
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* nullify the typed date to defer to regular formatting
|
||||
* called once the input is blurred
|
||||
*/
|
||||
inputBlurred () {
|
||||
if (this.typeable && isNaN(Date.parse(this.input.value))) {
|
||||
this.clearDate()
|
||||
this.input.value = null
|
||||
this.typedDate = null
|
||||
}
|
||||
|
||||
this.$emit('closeCalendar')
|
||||
},
|
||||
/**
|
||||
* emit a clearDate event
|
||||
*/
|
||||
clearDate () {
|
||||
this.$emit('clearDate')
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.input = this.$el.querySelector('input')
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
</script>
|
||||
375
resources/assets/js/components/base/base-date-picker/PickerDay.vue
Executable file
375
resources/assets/js/components/base/base-date-picker/PickerDay.vue
Executable file
@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showDayView" :style="calendarStyle" @mousedown.prevent>
|
||||
<slot name="beforeCalendarHeader"></slot>
|
||||
<header>
|
||||
<span
|
||||
@click="isRtl ? nextMonth() : previousMonth()"
|
||||
class="prev"
|
||||
:class="{'disabled': isLeftNavDisabled}"><</span>
|
||||
<span class="day__month_btn" @click="showMonthCalendar" :class="allowedToShowView('month') ? 'up' : ''">{{ isYmd ? currYearName : currMonthName }} {{ isYmd ? currMonthName : currYearName }}</span>
|
||||
<span
|
||||
@click="isRtl ? previousMonth() : nextMonth()"
|
||||
class="next"
|
||||
:class="{'disabled': isRightNavDisabled}">></span>
|
||||
</header>
|
||||
<div :class="isRtl ? 'flex-rtl' : ''">
|
||||
<span class="cell day-header" v-for="d in daysOfWeek" :key="d.timestamp">{{ d }}</span>
|
||||
<template v-if="blankDays > 0">
|
||||
<span class="cell day blank" v-for="d in blankDays" :key="d.timestamp"></span>
|
||||
</template><!--
|
||||
--><span class="cell day"
|
||||
v-for="day in days"
|
||||
:key="day.timestamp"
|
||||
:class="dayClasses(day)"
|
||||
v-html="dayCellContent(day)"
|
||||
@click="selectDate(day)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { makeDateUtils } from './src/DateUtils'
|
||||
export default {
|
||||
props: {
|
||||
showDayView: Boolean,
|
||||
selectedDate: Date,
|
||||
pageDate: Date,
|
||||
pageTimestamp: Number,
|
||||
fullMonthName: Boolean,
|
||||
allowedToShowView: Function,
|
||||
dayCellContent: {
|
||||
type: Function,
|
||||
default: day => day.date
|
||||
},
|
||||
disabledDates: Object,
|
||||
highlighted: Object,
|
||||
calendarClass: [String, Object, Array],
|
||||
calendarStyle: Object,
|
||||
translation: Object,
|
||||
isRtl: Boolean,
|
||||
mondayFirst: Boolean,
|
||||
useUtc: Boolean
|
||||
},
|
||||
data () {
|
||||
const constructedDateUtils = makeDateUtils(this.useUtc)
|
||||
return {
|
||||
utils: constructedDateUtils
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* Returns an array of day names
|
||||
* @return {String[]}
|
||||
*/
|
||||
daysOfWeek () {
|
||||
if (this.mondayFirst) {
|
||||
const tempDays = this.translation.days.slice()
|
||||
tempDays.push(tempDays.shift())
|
||||
return tempDays
|
||||
}
|
||||
return this.translation.days
|
||||
},
|
||||
/**
|
||||
* Returns the day number of the week less one for the first of the current month
|
||||
* Used to show amount of empty cells before the first in the day calendar layout
|
||||
* @return {Number}
|
||||
*/
|
||||
blankDays () {
|
||||
const d = this.pageDate
|
||||
let dObj = this.useUtc
|
||||
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
|
||||
: new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
|
||||
if (this.mondayFirst) {
|
||||
return this.utils.getDay(dObj) > 0 ? this.utils.getDay(dObj) - 1 : 6
|
||||
}
|
||||
return this.utils.getDay(dObj)
|
||||
},
|
||||
/**
|
||||
* @return {Object[]}
|
||||
*/
|
||||
days () {
|
||||
const d = this.pageDate
|
||||
let days = []
|
||||
// set up a new date object to the beginning of the current 'page'
|
||||
let dObj = this.useUtc
|
||||
? new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
|
||||
: new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
|
||||
let daysInMonth = this.utils.daysInMonth(this.utils.getFullYear(dObj), this.utils.getMonth(dObj))
|
||||
for (let i = 0; i < daysInMonth; i++) {
|
||||
days.push({
|
||||
date: this.utils.getDate(dObj),
|
||||
timestamp: dObj.getTime(),
|
||||
isSelected: this.isSelectedDate(dObj),
|
||||
isDisabled: this.isDisabledDate(dObj),
|
||||
isHighlighted: this.isHighlightedDate(dObj),
|
||||
isHighlightStart: this.isHighlightStart(dObj),
|
||||
isHighlightEnd: this.isHighlightEnd(dObj),
|
||||
isToday: this.utils.compareDates(dObj, new Date()),
|
||||
isWeekend: this.utils.getDay(dObj) === 0 || this.utils.getDay(dObj) === 6,
|
||||
isSaturday: this.utils.getDay(dObj) === 6,
|
||||
isSunday: this.utils.getDay(dObj) === 0
|
||||
})
|
||||
this.utils.setDate(dObj, this.utils.getDate(dObj) + 1)
|
||||
}
|
||||
return days
|
||||
},
|
||||
/**
|
||||
* Gets the name of the month the current page is on
|
||||
* @return {String}
|
||||
*/
|
||||
currMonthName () {
|
||||
const monthName = this.fullMonthName ? this.translation.months : this.translation.monthsAbbr
|
||||
return this.utils.getMonthNameAbbr(this.utils.getMonth(this.pageDate), monthName)
|
||||
},
|
||||
/**
|
||||
* Gets the name of the year that current page is on
|
||||
* @return {Number}
|
||||
*/
|
||||
currYearName () {
|
||||
const yearSuffix = this.translation.yearSuffix
|
||||
return `${this.utils.getFullYear(this.pageDate)}${yearSuffix}`
|
||||
},
|
||||
/**
|
||||
* Is this translation using year/month/day format?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isYmd () {
|
||||
return this.translation.ymd && this.translation.ymd === true
|
||||
},
|
||||
/**
|
||||
* Is the left hand navigation button disabled?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isLeftNavDisabled () {
|
||||
return this.isRtl
|
||||
? this.isNextMonthDisabled(this.pageTimestamp)
|
||||
: this.isPreviousMonthDisabled(this.pageTimestamp)
|
||||
},
|
||||
/**
|
||||
* Is the right hand navigation button disabled?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isRightNavDisabled () {
|
||||
return this.isRtl
|
||||
? this.isPreviousMonthDisabled(this.pageTimestamp)
|
||||
: this.isNextMonthDisabled(this.pageTimestamp)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectDate (date) {
|
||||
if (date.isDisabled) {
|
||||
this.$emit('selectedDisabled', date)
|
||||
return false
|
||||
}
|
||||
this.$emit('selectDate', date)
|
||||
},
|
||||
/**
|
||||
* @return {Number}
|
||||
*/
|
||||
getPageMonth () {
|
||||
return this.utils.getMonth(this.pageDate)
|
||||
},
|
||||
/**
|
||||
* Emit an event to show the month picker
|
||||
*/
|
||||
showMonthCalendar () {
|
||||
this.$emit('showMonthCalendar')
|
||||
},
|
||||
/**
|
||||
* Change the page month
|
||||
* @param {Number} incrementBy
|
||||
*/
|
||||
changeMonth (incrementBy) {
|
||||
let date = this.pageDate
|
||||
this.utils.setMonth(date, this.utils.getMonth(date) + incrementBy)
|
||||
this.$emit('changedMonth', date)
|
||||
},
|
||||
/**
|
||||
* Decrement the page month
|
||||
*/
|
||||
previousMonth () {
|
||||
if (!this.isPreviousMonthDisabled()) {
|
||||
this.changeMonth(-1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Is the previous month disabled?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isPreviousMonthDisabled () {
|
||||
if (!this.disabledDates || !this.disabledDates.to) {
|
||||
return false
|
||||
}
|
||||
let d = this.pageDate
|
||||
return this.utils.getMonth(this.disabledDates.to) >= this.utils.getMonth(d) &&
|
||||
this.utils.getFullYear(this.disabledDates.to) >= this.utils.getFullYear(d)
|
||||
},
|
||||
/**
|
||||
* Increment the current page month
|
||||
*/
|
||||
nextMonth () {
|
||||
if (!this.isNextMonthDisabled()) {
|
||||
this.changeMonth(+1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Is the next month disabled?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isNextMonthDisabled () {
|
||||
if (!this.disabledDates || !this.disabledDates.from) {
|
||||
return false
|
||||
}
|
||||
let d = this.pageDate
|
||||
return this.utils.getMonth(this.disabledDates.from) <= this.utils.getMonth(d) &&
|
||||
this.utils.getFullYear(this.disabledDates.from) <= this.utils.getFullYear(d)
|
||||
},
|
||||
/**
|
||||
* Whether a day is selected
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isSelectedDate (dObj) {
|
||||
return this.selectedDate && this.utils.compareDates(this.selectedDate, dObj)
|
||||
},
|
||||
/**
|
||||
* Whether a day is disabled
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isDisabledDate (date) {
|
||||
let disabledDates = false
|
||||
|
||||
if (typeof this.disabledDates === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof this.disabledDates.dates !== 'undefined') {
|
||||
this.disabledDates.dates.forEach((d) => {
|
||||
if (this.utils.compareDates(date, d)) {
|
||||
disabledDates = true
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to && date < this.disabledDates.to) {
|
||||
disabledDates = true
|
||||
}
|
||||
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from && date > this.disabledDates.from) {
|
||||
disabledDates = true
|
||||
}
|
||||
if (typeof this.disabledDates.ranges !== 'undefined') {
|
||||
this.disabledDates.ranges.forEach((range) => {
|
||||
if (typeof range.from !== 'undefined' && range.from && typeof range.to !== 'undefined' && range.to) {
|
||||
if (date < range.to && date > range.from) {
|
||||
disabledDates = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (typeof this.disabledDates.days !== 'undefined' && this.disabledDates.days.indexOf(this.utils.getDay(date)) !== -1) {
|
||||
disabledDates = true
|
||||
}
|
||||
if (typeof this.disabledDates.daysOfMonth !== 'undefined' && this.disabledDates.daysOfMonth.indexOf(this.utils.getDate(date)) !== -1) {
|
||||
disabledDates = true
|
||||
}
|
||||
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
|
||||
disabledDates = true
|
||||
}
|
||||
return disabledDates
|
||||
},
|
||||
/**
|
||||
* Whether a day is highlighted (only if it is not disabled already except when highlighted.includeDisabled is true)
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isHighlightedDate (date) {
|
||||
if (!(this.highlighted && this.highlighted.includeDisabled) && this.isDisabledDate(date)) {
|
||||
return false
|
||||
}
|
||||
|
||||
let highlighted = false
|
||||
|
||||
if (typeof this.highlighted === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof this.highlighted.dates !== 'undefined') {
|
||||
this.highlighted.dates.forEach((d) => {
|
||||
if (this.utils.compareDates(date, d)) {
|
||||
highlighted = true
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.isDefined(this.highlighted.from) && this.isDefined(this.highlighted.to)) {
|
||||
highlighted = date >= this.highlighted.from && date <= this.highlighted.to
|
||||
}
|
||||
|
||||
if (typeof this.highlighted.days !== 'undefined' && this.highlighted.days.indexOf(this.utils.getDay(date)) !== -1) {
|
||||
highlighted = true
|
||||
}
|
||||
|
||||
if (typeof this.highlighted.daysOfMonth !== 'undefined' && this.highlighted.daysOfMonth.indexOf(this.utils.getDate(date)) !== -1) {
|
||||
highlighted = true
|
||||
}
|
||||
|
||||
if (typeof this.highlighted.customPredictor === 'function' && this.highlighted.customPredictor(date)) {
|
||||
highlighted = true
|
||||
}
|
||||
|
||||
return highlighted
|
||||
},
|
||||
dayClasses (day) {
|
||||
return {
|
||||
'selected': day.isSelected,
|
||||
'disabled': day.isDisabled,
|
||||
'highlighted': day.isHighlighted,
|
||||
'today': day.isToday,
|
||||
'weekend': day.isWeekend,
|
||||
'sat': day.isSaturday,
|
||||
'sun': day.isSunday,
|
||||
'highlight-start': day.isHighlightStart,
|
||||
'highlight-end': day.isHighlightEnd
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Whether a day is highlighted and it is the first date
|
||||
* in the highlighted range of dates
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isHighlightStart (date) {
|
||||
return this.isHighlightedDate(date) &&
|
||||
(this.highlighted.from instanceof Date) &&
|
||||
(this.utils.getFullYear(this.highlighted.from) === this.utils.getFullYear(date)) &&
|
||||
(this.utils.getMonth(this.highlighted.from) === this.utils.getMonth(date)) &&
|
||||
(this.utils.getDate(this.highlighted.from) === this.utils.getDate(date))
|
||||
},
|
||||
/**
|
||||
* Whether a day is highlighted and it is the first date
|
||||
* in the highlighted range of dates
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isHighlightEnd (date) {
|
||||
return this.isHighlightedDate(date) &&
|
||||
(this.highlighted.to instanceof Date) &&
|
||||
(this.utils.getFullYear(this.highlighted.to) === this.utils.getFullYear(date)) &&
|
||||
(this.utils.getMonth(this.highlighted.to) === this.utils.getMonth(date)) &&
|
||||
(this.utils.getDate(this.highlighted.to) === this.utils.getDate(date))
|
||||
},
|
||||
/**
|
||||
* Helper
|
||||
* @param {mixed} prop
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isDefined (prop) {
|
||||
return typeof prop !== 'undefined' && prop
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
</script>
|
||||
200
resources/assets/js/components/base/base-date-picker/PickerMonth.vue
Executable file
200
resources/assets/js/components/base/base-date-picker/PickerMonth.vue
Executable file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showMonthView" :style="calendarStyle" @mousedown.prevent>
|
||||
<slot name="beforeCalendarHeader"></slot>
|
||||
<header>
|
||||
<span
|
||||
@click="isRtl ? nextYear() : previousYear()"
|
||||
class="prev"
|
||||
:class="{'disabled': isLeftNavDisabled}"><</span>
|
||||
<span class="month__year_btn" @click="showYearCalendar" :class="allowedToShowView('year') ? 'up' : ''">{{ pageYearName }}</span>
|
||||
<span
|
||||
@click="isRtl ? previousYear() : nextYear()"
|
||||
class="next"
|
||||
:class="{'disabled': isRightNavDisabled}">></span>
|
||||
</header>
|
||||
<span class="cell month"
|
||||
v-for="month in months"
|
||||
:key="month.timestamp"
|
||||
:class="{'selected': month.isSelected, 'disabled': month.isDisabled}"
|
||||
@click.stop="selectMonth(month)">{{ month.month }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { makeDateUtils } from './src/DateUtils'
|
||||
export default {
|
||||
props: {
|
||||
showMonthView: Boolean,
|
||||
selectedDate: Date,
|
||||
pageDate: Date,
|
||||
pageTimestamp: Number,
|
||||
disabledDates: Object,
|
||||
calendarClass: [String, Object, Array],
|
||||
calendarStyle: Object,
|
||||
translation: Object,
|
||||
isRtl: Boolean,
|
||||
allowedToShowView: Function,
|
||||
useUtc: Boolean
|
||||
},
|
||||
data () {
|
||||
const constructedDateUtils = makeDateUtils(this.useUtc)
|
||||
return {
|
||||
utils: constructedDateUtils
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
months () {
|
||||
const d = this.pageDate
|
||||
let months = []
|
||||
// set up a new date object to the beginning of the current 'page'
|
||||
let dObj = this.useUtc
|
||||
? new Date(Date.UTC(d.getUTCFullYear(), 0, d.getUTCDate()))
|
||||
: new Date(d.getFullYear(), 0, d.getDate(), d.getHours(), d.getMinutes())
|
||||
for (let i = 0; i < 12; i++) {
|
||||
months.push({
|
||||
month: this.utils.getMonthName(i, this.translation.months),
|
||||
timestamp: dObj.getTime(),
|
||||
isSelected: this.isSelectedMonth(dObj),
|
||||
isDisabled: this.isDisabledMonth(dObj)
|
||||
})
|
||||
this.utils.setMonth(dObj, this.utils.getMonth(dObj) + 1)
|
||||
}
|
||||
return months
|
||||
},
|
||||
/**
|
||||
* Get year name on current page.
|
||||
* @return {String}
|
||||
*/
|
||||
pageYearName () {
|
||||
const yearSuffix = this.translation.yearSuffix
|
||||
return `${this.utils.getFullYear(this.pageDate)}${yearSuffix}`
|
||||
},
|
||||
/**
|
||||
* Is the left hand navigation disabled
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isLeftNavDisabled () {
|
||||
return this.isRtl
|
||||
? this.isNextYearDisabled(this.pageTimestamp)
|
||||
: this.isPreviousYearDisabled(this.pageTimestamp)
|
||||
},
|
||||
/**
|
||||
* Is the right hand navigation disabled
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isRightNavDisabled () {
|
||||
return this.isRtl
|
||||
? this.isPreviousYearDisabled(this.pageTimestamp)
|
||||
: this.isNextYearDisabled(this.pageTimestamp)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Emits a selectMonth event
|
||||
* @param {Object} month
|
||||
*/
|
||||
selectMonth (month) {
|
||||
if (month.isDisabled) {
|
||||
return false
|
||||
}
|
||||
this.$emit('selectMonth', month)
|
||||
},
|
||||
/**
|
||||
* Changes the year up or down
|
||||
* @param {Number} incrementBy
|
||||
*/
|
||||
changeYear (incrementBy) {
|
||||
let date = this.pageDate
|
||||
this.utils.setFullYear(date, this.utils.getFullYear(date) + incrementBy)
|
||||
this.$emit('changedYear', date)
|
||||
},
|
||||
/**
|
||||
* Decrements the year
|
||||
*/
|
||||
previousYear () {
|
||||
if (!this.isPreviousYearDisabled()) {
|
||||
this.changeYear(-1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Checks if the previous year is disabled or not
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isPreviousYearDisabled () {
|
||||
if (!this.disabledDates || !this.disabledDates.to) {
|
||||
return false
|
||||
}
|
||||
return this.utils.getFullYear(this.disabledDates.to) >= this.utils.getFullYear(this.pageDate)
|
||||
},
|
||||
/**
|
||||
* Increments the year
|
||||
*/
|
||||
nextYear () {
|
||||
if (!this.isNextYearDisabled()) {
|
||||
this.changeYear(1)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Checks if the next year is disabled or not
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isNextYearDisabled () {
|
||||
if (!this.disabledDates || !this.disabledDates.from) {
|
||||
return false
|
||||
}
|
||||
return this.utils.getFullYear(this.disabledDates.from) <= this.utils.getFullYear(this.pageDate)
|
||||
},
|
||||
/**
|
||||
* Emits an event that shows the year calendar
|
||||
*/
|
||||
showYearCalendar () {
|
||||
this.$emit('showYearCalendar')
|
||||
},
|
||||
/**
|
||||
* Whether the selected date is in this month
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isSelectedMonth (date) {
|
||||
return (this.selectedDate &&
|
||||
this.utils.getFullYear(this.selectedDate) === this.utils.getFullYear(date) &&
|
||||
this.utils.getMonth(this.selectedDate) === this.utils.getMonth(date))
|
||||
},
|
||||
/**
|
||||
* Whether a month is disabled
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isDisabledMonth (date) {
|
||||
let disabledDates = false
|
||||
|
||||
if (typeof this.disabledDates === 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to) {
|
||||
if (
|
||||
(this.utils.getMonth(date) < this.utils.getMonth(this.disabledDates.to) && this.utils.getFullYear(date) <= this.utils.getFullYear(this.disabledDates.to)) ||
|
||||
this.utils.getFullYear(date) < this.utils.getFullYear(this.disabledDates.to)
|
||||
) {
|
||||
disabledDates = true
|
||||
}
|
||||
}
|
||||
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from) {
|
||||
if (
|
||||
(this.utils.getMonth(date) > this.utils.getMonth(this.disabledDates.from) && this.utils.getFullYear(date) >= this.utils.getFullYear(this.disabledDates.from)) ||
|
||||
this.utils.getFullYear(date) > this.utils.getFullYear(this.disabledDates.from)
|
||||
) {
|
||||
disabledDates = true
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
|
||||
disabledDates = true
|
||||
}
|
||||
return disabledDates
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
</script>
|
||||
174
resources/assets/js/components/base/base-date-picker/PickerYear.vue
Executable file
174
resources/assets/js/components/base/base-date-picker/PickerYear.vue
Executable file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div :class="[calendarClass, 'vdp-datepicker__calendar']" v-show="showYearView" :style="calendarStyle" @mousedown.prevent>
|
||||
<slot name="beforeCalendarHeader"></slot>
|
||||
<header>
|
||||
<span
|
||||
@click="isRtl ? nextDecade() : previousDecade()"
|
||||
class="prev"
|
||||
:class="{'disabled': isLeftNavDisabled}"><</span>
|
||||
<span>{{ getPageDecade }}</span>
|
||||
<span
|
||||
@click="isRtl ? previousDecade() : nextDecade()"
|
||||
class="next"
|
||||
:class="{'disabled': isRightNavDisabled}">></span>
|
||||
</header>
|
||||
<span
|
||||
class="cell year"
|
||||
v-for="year in years"
|
||||
:key="year.timestamp"
|
||||
:class="{ 'selected': year.isSelected, 'disabled': year.isDisabled }"
|
||||
@click.stop="selectYear(year)">{{ year.year }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { makeDateUtils } from './src/DateUtils'
|
||||
export default {
|
||||
props: {
|
||||
showYearView: Boolean,
|
||||
selectedDate: Date,
|
||||
pageDate: Date,
|
||||
pageTimestamp: Number,
|
||||
disabledDates: Object,
|
||||
highlighted: Object,
|
||||
calendarClass: [String, Object, Array],
|
||||
calendarStyle: Object,
|
||||
translation: Object,
|
||||
isRtl: Boolean,
|
||||
allowedToShowView: Function,
|
||||
useUtc: Boolean
|
||||
},
|
||||
computed: {
|
||||
years () {
|
||||
const d = this.pageDate
|
||||
let years = []
|
||||
// set up a new date object to the beginning of the current 'page'7
|
||||
let dObj = this.useUtc
|
||||
? new Date(Date.UTC(Math.floor(d.getUTCFullYear() / 10) * 10, d.getUTCMonth(), d.getUTCDate()))
|
||||
: new Date(Math.floor(d.getFullYear() / 10) * 10, d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
|
||||
for (let i = 0; i < 10; i++) {
|
||||
years.push({
|
||||
year: this.utils.getFullYear(dObj),
|
||||
timestamp: dObj.getTime(),
|
||||
isSelected: this.isSelectedYear(dObj),
|
||||
isDisabled: this.isDisabledYear(dObj)
|
||||
})
|
||||
this.utils.setFullYear(dObj, this.utils.getFullYear(dObj) + 1)
|
||||
}
|
||||
return years
|
||||
},
|
||||
/**
|
||||
* @return {String}
|
||||
*/
|
||||
getPageDecade () {
|
||||
const decadeStart = Math.floor(this.utils.getFullYear(this.pageDate) / 10) * 10
|
||||
const decadeEnd = decadeStart + 9
|
||||
const yearSuffix = this.translation.yearSuffix
|
||||
return `${decadeStart} - ${decadeEnd}${yearSuffix}`
|
||||
},
|
||||
/**
|
||||
* Is the left hand navigation button disabled?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isLeftNavDisabled () {
|
||||
return this.isRtl
|
||||
? this.isNextDecadeDisabled(this.pageTimestamp)
|
||||
: this.isPreviousDecadeDisabled(this.pageTimestamp)
|
||||
},
|
||||
/**
|
||||
* Is the right hand navigation button disabled?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isRightNavDisabled () {
|
||||
return this.isRtl
|
||||
? this.isPreviousDecadeDisabled(this.pageTimestamp)
|
||||
: this.isNextDecadeDisabled(this.pageTimestamp)
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const constructedDateUtils = makeDateUtils(this.useUtc)
|
||||
return {
|
||||
utils: constructedDateUtils
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectYear (year) {
|
||||
if (year.isDisabled) {
|
||||
return false
|
||||
}
|
||||
this.$emit('selectYear', year)
|
||||
},
|
||||
changeYear (incrementBy) {
|
||||
let date = this.pageDate
|
||||
this.utils.setFullYear(date, this.utils.getFullYear(date) + incrementBy)
|
||||
this.$emit('changedDecade', date)
|
||||
},
|
||||
previousDecade () {
|
||||
if (this.isPreviousDecadeDisabled()) {
|
||||
return false
|
||||
}
|
||||
this.changeYear(-10)
|
||||
},
|
||||
isPreviousDecadeDisabled () {
|
||||
if (!this.disabledDates || !this.disabledDates.to) {
|
||||
return false
|
||||
}
|
||||
const disabledYear = this.utils.getFullYear(this.disabledDates.to)
|
||||
const lastYearInPreviousPage = Math.floor(this.utils.getFullYear(this.pageDate) / 10) * 10 - 1
|
||||
return disabledYear > lastYearInPreviousPage
|
||||
},
|
||||
nextDecade () {
|
||||
if (this.isNextDecadeDisabled()) {
|
||||
return false
|
||||
}
|
||||
this.changeYear(10)
|
||||
},
|
||||
isNextDecadeDisabled () {
|
||||
if (!this.disabledDates || !this.disabledDates.from) {
|
||||
return false
|
||||
}
|
||||
const disabledYear = this.utils.getFullYear(this.disabledDates.from)
|
||||
const firstYearInNextPage = Math.ceil(this.utils.getFullYear(this.pageDate) / 10) * 10
|
||||
return disabledYear < firstYearInNextPage
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the selected date is in this year
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isSelectedYear (date) {
|
||||
return this.selectedDate && this.utils.getFullYear(this.selectedDate) === this.utils.getFullYear(date)
|
||||
},
|
||||
/**
|
||||
* Whether a year is disabled
|
||||
* @param {Date}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isDisabledYear (date) {
|
||||
let disabledDates = false
|
||||
if (typeof this.disabledDates === 'undefined' || !this.disabledDates) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof this.disabledDates.to !== 'undefined' && this.disabledDates.to) {
|
||||
if (this.utils.getFullYear(date) < this.utils.getFullYear(this.disabledDates.to)) {
|
||||
disabledDates = true
|
||||
}
|
||||
}
|
||||
if (typeof this.disabledDates.from !== 'undefined' && this.disabledDates.from) {
|
||||
if (this.utils.getFullYear(date) > this.utils.getFullYear(this.disabledDates.from)) {
|
||||
disabledDates = true
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof this.disabledDates.customPredictor === 'function' && this.disabledDates.customPredictor(date)) {
|
||||
disabledDates = true
|
||||
}
|
||||
|
||||
return disabledDates
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
</script>
|
||||
252
resources/assets/js/components/base/base-date-picker/src/DateUtils.js
Executable file
252
resources/assets/js/components/base/base-date-picker/src/DateUtils.js
Executable file
@ -0,0 +1,252 @@
|
||||
import en from './locale/translations/en'
|
||||
|
||||
const utils = {
|
||||
/**
|
||||
* @type {Boolean}
|
||||
*/
|
||||
useUtc: false,
|
||||
/**
|
||||
* Returns the full year, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
getFullYear (date) {
|
||||
return this.useUtc ? date.getUTCFullYear() : date.getFullYear()
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the month, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
getMonth (date) {
|
||||
return this.useUtc ? date.getUTCMonth() : date.getMonth()
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the date, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
getDate (date) {
|
||||
return this.useUtc ? date.getUTCDate() : date.getDate()
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the day, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
getDay (date) {
|
||||
return this.useUtc ? date.getUTCDay() : date.getDay()
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the hours, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
getHours (date) {
|
||||
return this.useUtc ? date.getUTCHours() : date.getHours()
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the minutes, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
getMinutes (date) {
|
||||
return this.useUtc ? date.getUTCMinutes() : date.getMinutes()
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the full year, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
setFullYear (date, value, useUtc) {
|
||||
return this.useUtc ? date.setUTCFullYear(value) : date.setFullYear(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the month, using UTC or not
|
||||
* @param {Date} date
|
||||
*/
|
||||
setMonth (date, value, useUtc) {
|
||||
return this.useUtc ? date.setUTCMonth(value) : date.setMonth(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the date, using UTC or not
|
||||
* @param {Date} date
|
||||
* @param {Number} value
|
||||
*/
|
||||
setDate (date, value, useUtc) {
|
||||
return this.useUtc ? date.setUTCDate(value) : date.setDate(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if date1 is equivalent to date2, without comparing the time
|
||||
* @see https://stackoverflow.com/a/6202196/4455925
|
||||
* @param {Date} date1
|
||||
* @param {Date} date2
|
||||
*/
|
||||
compareDates (date1, date2) {
|
||||
const d1 = new Date(date1.getTime())
|
||||
const d2 = new Date(date2.getTime())
|
||||
|
||||
if (this.useUtc) {
|
||||
d1.setUTCHours(0, 0, 0, 0)
|
||||
d2.setUTCHours(0, 0, 0, 0)
|
||||
} else {
|
||||
d1.setHours(0, 0, 0, 0)
|
||||
d2.setHours(0, 0, 0, 0)
|
||||
}
|
||||
return d1.getTime() === d2.getTime()
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates a date object
|
||||
* @param {Date} date - an object instantiated with the new Date constructor
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isValidDate (date) {
|
||||
if (Object.prototype.toString.call(date) !== '[object Date]') {
|
||||
return false
|
||||
}
|
||||
return !isNaN(date.getTime())
|
||||
},
|
||||
|
||||
/**
|
||||
* Return abbreviated week day name
|
||||
* @param {Date}
|
||||
* @param {Array}
|
||||
* @return {String}
|
||||
*/
|
||||
getDayNameAbbr (date, days) {
|
||||
if (typeof date !== 'object') {
|
||||
throw TypeError('Invalid Type')
|
||||
}
|
||||
return days[this.getDay(date)]
|
||||
},
|
||||
|
||||
/**
|
||||
* Return name of the month
|
||||
* @param {Number|Date}
|
||||
* @param {Array}
|
||||
* @return {String}
|
||||
*/
|
||||
getMonthName (month, months) {
|
||||
if (!months) {
|
||||
throw Error('missing 2nd parameter Months array')
|
||||
}
|
||||
if (typeof month === 'object') {
|
||||
return months[this.getMonth(month)]
|
||||
}
|
||||
if (typeof month === 'number') {
|
||||
return months[month]
|
||||
}
|
||||
throw TypeError('Invalid type')
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an abbreviated version of the month
|
||||
* @param {Number|Date}
|
||||
* @return {String}
|
||||
*/
|
||||
getMonthNameAbbr (month, monthsAbbr) {
|
||||
if (!monthsAbbr) {
|
||||
throw Error('missing 2nd paramter Months array')
|
||||
}
|
||||
if (typeof month === 'object') {
|
||||
return monthsAbbr[this.getMonth(month)]
|
||||
}
|
||||
if (typeof month === 'number') {
|
||||
return monthsAbbr[month]
|
||||
}
|
||||
throw TypeError('Invalid type')
|
||||
},
|
||||
|
||||
/**
|
||||
* Alternative get total number of days in month
|
||||
* @param {Number} year
|
||||
* @param {Number} m
|
||||
* @return {Number}
|
||||
*/
|
||||
daysInMonth (year, month) {
|
||||
return /8|3|5|10/.test(month) ? 30 : month === 1 ? (!(year % 4) && year % 100) || !(year % 400) ? 29 : 28 : 31
|
||||
},
|
||||
|
||||
/**
|
||||
* Get nth suffix for date
|
||||
* @param {Number} day
|
||||
* @return {String}
|
||||
*/
|
||||
getNthSuffix (day) {
|
||||
switch (day) {
|
||||
case 1:
|
||||
case 21:
|
||||
case 31:
|
||||
return 'st'
|
||||
case 2:
|
||||
case 22:
|
||||
return 'nd'
|
||||
case 3:
|
||||
case 23:
|
||||
return 'rd'
|
||||
default:
|
||||
return 'th'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Formats date object
|
||||
* @param {Date}
|
||||
* @param {String}
|
||||
* @param {Object}
|
||||
* @return {String}
|
||||
*/
|
||||
formatDate (date, format, translation) {
|
||||
translation = (!translation) ? en : translation
|
||||
let year = this.getFullYear(date)
|
||||
let month = this.getMonth(date) + 1
|
||||
let day = this.getDate(date)
|
||||
let str = format
|
||||
.replace(/dd/, ('0' + day).slice(-2))
|
||||
.replace(/d/, day)
|
||||
.replace(/yyyy/, year)
|
||||
.replace(/yy/, String(year).slice(2))
|
||||
.replace(/MMMM/, this.getMonthName(this.getMonth(date), translation.months))
|
||||
.replace(/MMM/, this.getMonthNameAbbr(this.getMonth(date), translation.monthsAbbr))
|
||||
.replace(/MM/, ('0' + month).slice(-2))
|
||||
.replace(/M(?!a|ä|e)/, month)
|
||||
.replace(/su/, this.getNthSuffix(this.getDate(date)))
|
||||
.replace(/D(?!e|é|i)/, this.getDayNameAbbr(date, translation.days))
|
||||
return str
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an array of dates for each day in between two dates.
|
||||
* @param {Date} start
|
||||
* @param {Date} end
|
||||
* @return {Array}
|
||||
*/
|
||||
createDateArray (start, end) {
|
||||
let dates = []
|
||||
while (start <= end) {
|
||||
dates.push(new Date(start))
|
||||
start = this.setDate(new Date(start), this.getDate(new Date(start)) + 1)
|
||||
}
|
||||
return dates
|
||||
},
|
||||
|
||||
/**
|
||||
* method used as a prop validator for input values
|
||||
* @param {*} val
|
||||
* @return {Boolean}
|
||||
*/
|
||||
validateDateInput (val) {
|
||||
return val === null || val instanceof Date || typeof val === 'string' || typeof val === 'number'
|
||||
}
|
||||
}
|
||||
|
||||
export const makeDateUtils = useUtc => ({...utils, useUtc})
|
||||
|
||||
export default {
|
||||
...utils
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
57
resources/assets/js/components/base/base-date-picker/src/locale/Language.js
Executable file
57
resources/assets/js/components/base/base-date-picker/src/locale/Language.js
Executable file
@ -0,0 +1,57 @@
|
||||
export default class Language {
|
||||
constructor (language, months, monthsAbbr, days) {
|
||||
this.language = language
|
||||
this.months = months
|
||||
this.monthsAbbr = monthsAbbr
|
||||
this.days = days
|
||||
this.rtl = false
|
||||
this.ymd = false
|
||||
this.yearSuffix = ''
|
||||
}
|
||||
|
||||
get language () {
|
||||
return this._language
|
||||
}
|
||||
|
||||
set language (language) {
|
||||
if (typeof language !== 'string') {
|
||||
throw new TypeError('Language must be a string')
|
||||
}
|
||||
this._language = language
|
||||
}
|
||||
|
||||
get months () {
|
||||
return this._months
|
||||
}
|
||||
|
||||
set months (months) {
|
||||
if (months.length !== 12) {
|
||||
throw new RangeError(`There must be 12 months for ${this.language} language`)
|
||||
}
|
||||
this._months = months
|
||||
}
|
||||
|
||||
get monthsAbbr () {
|
||||
return this._monthsAbbr
|
||||
}
|
||||
|
||||
set monthsAbbr (monthsAbbr) {
|
||||
if (monthsAbbr.length !== 12) {
|
||||
throw new RangeError(`There must be 12 abbreviated months for ${this.language} language`)
|
||||
}
|
||||
this._monthsAbbr = monthsAbbr
|
||||
}
|
||||
|
||||
get days () {
|
||||
return this._days
|
||||
}
|
||||
|
||||
set days (days) {
|
||||
if (days.length !== 7) {
|
||||
throw new RangeError(`There must be 7 days for ${this.language} language`)
|
||||
}
|
||||
this._days = days
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
105
resources/assets/js/components/base/base-date-picker/src/locale/index.js
Executable file
105
resources/assets/js/components/base/base-date-picker/src/locale/index.js
Executable file
@ -0,0 +1,105 @@
|
||||
import af from './translations/af'
|
||||
import ar from './translations/ar'
|
||||
import bg from './translations/bg'
|
||||
import bs from './translations/bs'
|
||||
import ca from './translations/ca'
|
||||
import cs from './translations/cs'
|
||||
import da from './translations/da'
|
||||
import de from './translations/de'
|
||||
import ee from './translations/ee'
|
||||
import el from './translations/el'
|
||||
import en from './translations/en'
|
||||
import es from './translations/es'
|
||||
import fa from './translations/fa'
|
||||
import fi from './translations/fi'
|
||||
import fo from './translations/fo'
|
||||
import fr from './translations/fr'
|
||||
import ge from './translations/ge'
|
||||
import gl from './translations/gl'
|
||||
import he from './translations/he'
|
||||
import hr from './translations/hr'
|
||||
import hu from './translations/hu'
|
||||
import id from './translations/id'
|
||||
import is from './translations/is'
|
||||
import it from './translations/it'
|
||||
import ja from './translations/ja'
|
||||
import kk from './translations/kk'
|
||||
import ko from './translations/ko'
|
||||
import lb from './translations/lb'
|
||||
import lt from './translations/lt'
|
||||
import lv from './translations/lv'
|
||||
import mk from './translations/mk'
|
||||
import mn from './translations/mn'
|
||||
import nbNO from './translations/nb-NO'
|
||||
import nl from './translations/nl'
|
||||
import pl from './translations/pl'
|
||||
import ptBR from './translations/pt-BR'
|
||||
import ro from './translations/ro'
|
||||
import ru from './translations/ru'
|
||||
import sk from './translations/sk'
|
||||
import slSI from './translations/sl-SI'
|
||||
import srCYRL from './translations/sr-CYRL'
|
||||
import sr from './translations/sr'
|
||||
import sv from './translations/sv'
|
||||
import th from './translations/th'
|
||||
import tr from './translations/tr'
|
||||
import uk from './translations/uk'
|
||||
import ur from './translations/ur'
|
||||
import vi from './translations/vi'
|
||||
import zh from './translations/zh'
|
||||
import zhHK from './translations/zh-HK'
|
||||
|
||||
export {
|
||||
af,
|
||||
ar,
|
||||
bg,
|
||||
bs,
|
||||
ca,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
ee,
|
||||
el,
|
||||
en,
|
||||
es,
|
||||
fa,
|
||||
fi,
|
||||
fo,
|
||||
fr,
|
||||
ge,
|
||||
gl,
|
||||
he,
|
||||
hr,
|
||||
hu,
|
||||
id,
|
||||
is,
|
||||
it,
|
||||
ja,
|
||||
kk,
|
||||
ko,
|
||||
lb,
|
||||
lt,
|
||||
lv,
|
||||
mk,
|
||||
mn,
|
||||
nbNO,
|
||||
nl,
|
||||
pl,
|
||||
ptBR,
|
||||
ro,
|
||||
ru,
|
||||
sk,
|
||||
slSI,
|
||||
srCYRL,
|
||||
sr,
|
||||
sv,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
ur,
|
||||
vi,
|
||||
zh,
|
||||
zhHK
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Afrikaans',
|
||||
['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'],
|
||||
['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
|
||||
['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,14 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Arabic',
|
||||
['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوڤمبر', 'ديسمبر'],
|
||||
['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوڤمبر', 'ديسمبر'],
|
||||
['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت']
|
||||
)
|
||||
|
||||
language.rtl = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Bulgarian',
|
||||
['Януари', 'Февруари', 'Март', 'Април', 'Май', 'Юни', 'Юли', 'Август', 'Септември', 'Октомври', 'Ноември', 'Декември'],
|
||||
['Ян', 'Фев', 'Мар', 'Апр', 'Май', 'Юни', 'Юли', 'Авг', 'Сеп', 'Окт', 'Ное', 'Дек'],
|
||||
['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Bosnian',
|
||||
['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Juni', 'Juli', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Catalan',
|
||||
['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
|
||||
['Gen', 'Feb', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Oct', 'Nov', 'Des'],
|
||||
['Diu', 'Dil', 'Dmr', 'Dmc', 'Dij', 'Div', 'Dis']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Czech',
|
||||
['leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'],
|
||||
['led', 'úno', 'bře', 'dub', 'kvě', 'čer', 'čec', 'srp', 'zář', 'říj', 'lis', 'pro'],
|
||||
['ne', 'po', 'út', 'st', 'čt', 'pá', 'so']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Danish',
|
||||
['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'German',
|
||||
['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
|
||||
['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
|
||||
['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Estonian',
|
||||
['Jaanuar', 'Veebruar', 'Märts', 'Aprill', 'Mai', 'Juuni', 'Juuli', 'August', 'September', 'Oktoober', 'November', 'Detsember'],
|
||||
['Jaan', 'Veebr', 'Märts', 'Apr', 'Mai', 'Juuni', 'Juuli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dets'],
|
||||
['P', 'E', 'T', 'K', 'N', 'R', 'L']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Greek',
|
||||
['Ιανουάριος', 'Φεβρουάριος', 'Μάρτιος', 'Απρίλιος', 'Μάϊος', 'Ιούνιος', 'Ιούλιος', 'Αύγουστος', 'Σεπτέμβριος', 'Οκτώβριος', 'Νοέμβριος', 'Δεκέμβριος'],
|
||||
['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαι', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπ', 'Οκτ', 'Νοε', 'Δεκ'],
|
||||
['Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'English',
|
||||
['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Spanish',
|
||||
['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
|
||||
['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
|
||||
['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Persian',
|
||||
['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند'],
|
||||
['فرو', 'ارد', 'خرد', 'تیر', 'مرد', 'شهر', 'مهر', 'آبا', 'آذر', 'دی', 'بهم', 'اسف'],
|
||||
['یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Finnish',
|
||||
['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'],
|
||||
['tammi', 'helmi', 'maalis', 'huhti', 'touko', 'kesä', 'heinä', 'elo', 'syys', 'loka', 'marras', 'joulu'],
|
||||
['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Faroese',
|
||||
['Januar', 'Februar', 'Mars', 'Apríl', 'Mai', 'Juni', 'Juli', 'August', 'Septembur', 'Oktobur', 'Novembur', 'Desembur'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
|
||||
['Sun', 'Mán', 'Týs', 'Mik', 'Hós', 'Frí', 'Ley']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'French',
|
||||
['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
|
||||
['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sep', 'Oct', 'Nov', 'Déc'],
|
||||
['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Georgia',
|
||||
['იანვარი', 'თებერვალი', 'მარტი', 'აპრილი', 'მაისი', 'ივნისი', 'ივლისი', 'აგვისტო', 'სექტემბერი', 'ოქტომბერი', 'ნოემბერი', 'დეკემბერი'],
|
||||
['იან', 'თებ', 'მარ', 'აპრ', 'მაი', 'ივნ', 'ივლ', 'აგვ', 'სექ', 'ოქტ', 'ნოე', 'დეკ'],
|
||||
['კვი', 'ორშ', 'სამ', 'ოთხ', 'ხუთ', 'პარ', 'შაბ']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Galician',
|
||||
['Xaneiro', 'Febreiro', 'Marzo', 'Abril', 'Maio', 'Xuño', 'Xullo', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Decembro'],
|
||||
['Xan', 'Feb', 'Mar', 'Abr', 'Mai', 'Xuñ', 'Xul', 'Ago', 'Set', 'Out', 'Nov', 'Dec'],
|
||||
['Dom', 'Lun', 'Mar', 'Mér', 'Xov', 'Ven', 'Sáb']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,14 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Hebrew',
|
||||
['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר'],
|
||||
['ינו', 'פבר', 'מרץ', 'אפר', 'מאי', 'יונ', 'יול', 'אוג', 'ספט', 'אוק', 'נוב', 'דצמ'],
|
||||
['א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ש']
|
||||
)
|
||||
|
||||
language.rtl = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Croatian',
|
||||
['Siječanj', 'Veljača', 'Ožujak', 'Travanj', 'Svibanj', 'Lipanj', 'Srpanj', 'Kolovoz', 'Rujan', 'Listopad', 'Studeni', 'Prosinac'],
|
||||
['Sij', 'Velj', 'Ožu', 'Tra', 'Svi', 'Lip', 'Srp', 'Kol', 'Ruj', 'Lis', 'Stu', 'Pro'],
|
||||
['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Hungarian',
|
||||
['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
|
||||
['Jan', 'Febr', 'Márc', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szept', 'Okt', 'Nov', 'Dec'],
|
||||
['Vas', 'Hét', 'Ke', 'Sze', 'Csü', 'Pén', 'Szo']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Indonesian',
|
||||
['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'],
|
||||
['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Icelandic',
|
||||
['Janúar', 'Febrúar', 'Mars', 'Apríl', 'Maí', 'Júní', 'Júlí', 'Ágúst', 'September', 'Október', 'Nóvember', 'Desember'],
|
||||
['Jan', 'Feb', 'Mars', 'Apr', 'Maí', 'Jún', 'Júl', 'Ágú', 'Sep', 'Okt', 'Nóv', 'Des'],
|
||||
['Sun', 'Mán', 'Þri', 'Mið', 'Fim', 'Fös', 'Lau']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Italian',
|
||||
['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
|
||||
['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
|
||||
['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,15 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Japanese',
|
||||
['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
['日', '月', '火', '水', '木', '金', '土']
|
||||
)
|
||||
|
||||
language.yearSuffix = '年'
|
||||
language.ymd = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Kazakh',
|
||||
['Қаңтар', 'Ақпан', 'Наурыз', 'Сәуір', 'Мамыр', 'Маусым', 'Шілде', 'Тамыз', 'Қыркүйек', 'Қазан', 'Қараша', 'Желтоқсан'],
|
||||
['Қаң', 'Ақп', 'Нау', 'Сәу', 'Мам', 'Мау', 'Шіл', 'Там', 'Қыр', 'Қаз', 'Қар', 'Жел'],
|
||||
['Жк', 'Дй', 'Сй', 'Ср', 'Бй', 'Жм', 'Сн']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,14 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Korean',
|
||||
['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
['일', '월', '화', '수', '목', '금', '토']
|
||||
)
|
||||
language.yearSuffix = '년'
|
||||
language.ymd = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Luxembourgish',
|
||||
['Januar', 'Februar', 'Mäerz', 'Abrëll', 'Mee', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
|
||||
['Jan', 'Feb', 'Mäe', 'Abr', 'Mee', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
|
||||
['So.', 'Mé.', 'Dë.', 'Më.', 'Do.', 'Fr.', 'Sa.']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,14 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Lithuanian',
|
||||
['Sausis', 'Vasaris', 'Kovas', 'Balandis', 'Gegužė', 'Birželis', 'Liepa', 'Rugpjūtis', 'Rugsėjis', 'Spalis', 'Lapkritis', 'Gruodis'],
|
||||
['Sau', 'Vas', 'Kov', 'Bal', 'Geg', 'Bir', 'Lie', 'Rugp', 'Rugs', 'Spa', 'Lap', 'Gru'],
|
||||
['Sek', 'Pir', 'Ant', 'Tre', 'Ket', 'Pen', 'Šeš']
|
||||
)
|
||||
|
||||
language.ymd = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Latvian',
|
||||
['Janvāris', 'Februāris', 'Marts', 'Aprīlis', 'Maijs', 'Jūnijs', 'Jūlijs', 'Augusts', 'Septembris', 'Oktobris', 'Novembris', 'Decembris'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jūn', 'Jūl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
['Sv', 'Pr', 'Ot', 'Tr', 'Ce', 'Pk', 'Se']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Macedonian',
|
||||
['Јануари', 'Февруари', 'Март', 'Април', 'Мај', 'Јуни', 'Јули', 'Август', 'Септември', 'Октомври', 'Ноември', 'Декември'],
|
||||
['Јан', 'Фев', 'Мар', 'Апр', 'Мај', 'Јун', 'Јул', 'Авг', 'Сеп', 'Окт', 'Ное', 'Дек'],
|
||||
['Нед', 'Пон', 'Вто', 'Сре', 'Чет', 'Пет', 'Саб']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,14 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Mongolia',
|
||||
['1 дүгээр сар', '2 дугаар сар', '3 дугаар сар', '4 дүгээр сар', '5 дугаар сар', '6 дугаар сар', '7 дугаар сар', '8 дугаар сар', '9 дүгээр сар', '10 дугаар сар', '11 дүгээр сар', '12 дугаар сар'],
|
||||
['1-р сар', '2-р сар', '3-р сар', '4-р сар', '5-р сар', '6-р сар', '7-р сар', '8-р сар', '9-р сар', '10-р сар', '11-р сар', '12-р сар'],
|
||||
['Ня', 'Да', 'Мя', 'Лх', 'Пү', 'Ба', 'Бя']
|
||||
)
|
||||
|
||||
language.ymd = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Norwegian Bokmål',
|
||||
['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'],
|
||||
['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Dutch',
|
||||
['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
|
||||
['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
|
||||
['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Polish',
|
||||
['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
|
||||
['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
|
||||
['Nd', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Brazilian',
|
||||
['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
|
||||
['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
|
||||
['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Romanian',
|
||||
['Ianuarie', 'Februarie', 'Martie', 'Aprilie', 'Mai', 'Iunie', 'Iulie', 'August', 'Septembrie', 'Octombrie', 'Noiembrie', 'Decembrie'],
|
||||
['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Iul', 'Aug', 'Sep', 'Oct', 'Noi', 'Dec'],
|
||||
['D', 'L', 'Ma', 'Mi', 'J', 'V', 'S']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Russian',
|
||||
['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
|
||||
['Янв', 'Февр', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сент', 'Окт', 'Нояб', 'Дек'],
|
||||
['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Slovakian',
|
||||
['január', 'február', 'marec', 'apríl', 'máj', 'jún', 'júl', 'august', 'september', 'október', 'november', 'december'],
|
||||
['jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl', 'aug', 'sep', 'okt', 'nov', 'dec'],
|
||||
['ne', 'po', 'ut', 'st', 'št', 'pi', 'so']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Sloveian',
|
||||
['Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij', 'Avgust', 'September', 'Oktober', 'November', 'December'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
['Ned', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Serbian in Cyrillic script',
|
||||
['Јануар', 'Фебруар', 'Март', 'Април', 'Мај', 'Јун', 'Јул', 'Август', 'Септембар', 'Октобар', 'Новембар', 'Децембар'],
|
||||
['Јан', 'Феб', 'Мар', 'Апр', 'Мај', 'Јун', 'Јул', 'Авг', 'Сеп', 'Окт', 'Нов', 'Дец'],
|
||||
['Нед', 'Пон', 'Уто', 'Сре', 'Чет', 'Пет', 'Суб']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Serbian',
|
||||
['Januar', 'Februar', 'Mart', 'April', 'Maj', 'Jun', 'Jul', 'Avgust', 'Septembar', 'Oktobar', 'Novembar', 'Decembar'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
['Ned', 'Pon', 'Uto', 'Sre', 'Čet', 'Pet', 'Sub']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Swedish',
|
||||
['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
|
||||
['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
|
||||
['Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Thai',
|
||||
['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'],
|
||||
['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'],
|
||||
['อา', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Turkish',
|
||||
['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
|
||||
['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
|
||||
['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Ukraine',
|
||||
['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
|
||||
['Січ', 'Лют', 'Бер', 'Квіт', 'Трав', 'Чер', 'Лип', 'Серп', 'Вер', 'Жовт', 'Лист', 'Груд'],
|
||||
['Нд', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,14 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Urdu',
|
||||
['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'],
|
||||
['جنوری', 'فروری', 'مارچ', 'اپریل', 'مئی', 'جون', 'جولائی', 'اگست', 'سپتمبر', 'اکتوبر', 'نومبر', 'دسمبر'],
|
||||
['اتوار', 'پیر', 'منگل', 'بدھ', 'جمعرات', 'جمعہ', 'ہفتہ']
|
||||
)
|
||||
|
||||
language.rtl = true
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,10 @@
|
||||
import Language from '../Language'
|
||||
|
||||
export default new Language(
|
||||
'Vietnamese',
|
||||
['Tháng 1', 'Tháng 2', 'Tháng 3', 'Tháng 4', 'Tháng 5', 'Tháng 6', 'Tháng 7', 'Tháng 8', 'Tháng 9', 'Tháng 10', 'Tháng 11', 'Tháng 12'],
|
||||
['T 01', 'T 02', 'T 03', 'T 04', 'T 05', 'T 06', 'T 07', 'T 08', 'T 09', 'T 10', 'T 11', 'T 12'],
|
||||
['CN', 'Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7']
|
||||
)
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
@ -0,0 +1,11 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Chinese_HK',
|
||||
['壹月', '贰月', '叁月', '肆月', '伍月', '陆月', '柒月', '捌月', '玖月', '拾月', '拾壹月', '拾贰月'],
|
||||
['壹月', '贰月', '叁月', '肆月', '伍月', '陆月', '柒月', '捌月', '玖月', '拾月', '拾壹月', '拾贰月'],
|
||||
['日', '壹', '贰', '叁', '肆', '伍', '陆']
|
||||
)
|
||||
language.yearSuffix = '年'
|
||||
|
||||
export default language
|
||||
@ -0,0 +1,13 @@
|
||||
import Language from '../Language'
|
||||
|
||||
const language = new Language(
|
||||
'Chinese',
|
||||
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
['日', '一', '二', '三', '四', '五', '六']
|
||||
)
|
||||
language.yearSuffix = '年'
|
||||
|
||||
export default language
|
||||
// eslint-disable-next-line
|
||||
;
|
||||
368
resources/assets/js/components/base/base-select/BaseSelect.vue
Executable file
368
resources/assets/js/components/base/base-select/BaseSelect.vue
Executable file
@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<div
|
||||
:tabindex="searchable ? -1 : tabindex"
|
||||
:class="{'multiselect--active': isOpen, 'multiselect--disabled': disabled, 'multiselect--above': isAbove }"
|
||||
:aria-owns="'listbox-'+id"
|
||||
class="base-select multiselect"
|
||||
role="combobox"
|
||||
@focus="activate()"
|
||||
@blur="searchable ? false : deactivate()"
|
||||
@keydown.self.down.prevent="pointerForward()"
|
||||
@keydown.self.up.prevent="pointerBackward()"
|
||||
@keypress.enter.tab.stop.self="addPointerElement($event)"
|
||||
@keyup.esc="deactivate()"
|
||||
>
|
||||
<slot :toggle="toggle" name="caret">
|
||||
<div class="multiselect__select" @mousedown.prevent.stop="toggle()" />
|
||||
</slot>
|
||||
<!-- <slot name="clear" :search="search"></slot> -->
|
||||
<div ref="tags" :class="{'in-valid': invalid}" class="multiselect__tags">
|
||||
<slot
|
||||
:search="search"
|
||||
:remove="removeElement"
|
||||
:values="visibleValues"
|
||||
:is-open="isOpen"
|
||||
name="selection"
|
||||
>
|
||||
<div v-show="visibleValues.length > 0" class="multiselect__tags-wrap">
|
||||
<template v-for="(option, index) of visibleValues" @mousedown.prevent>
|
||||
<slot :option="option" :search="search" :remove="removeElement" name="tag">
|
||||
<span :key="index" class="multiselect__tag">
|
||||
<span v-text="getOptionLabel(option)"/>
|
||||
<i class="multiselect__tag-icon" tabindex="1" @keypress.enter.prevent="removeElement(option)" @mousedown.prevent="removeElement(option)"/>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="internalValue && internalValue.length > limit">
|
||||
<slot name="limit">
|
||||
<strong class="multiselect__strong" v-text="limitText(internalValue.length - limit)"/>
|
||||
</slot>
|
||||
</template>
|
||||
</slot>
|
||||
<transition name="multiselect__loading">
|
||||
<slot name="loading">
|
||||
<div v-show="loading" class="multiselect__spinner"/>
|
||||
</slot>
|
||||
</transition>
|
||||
<input
|
||||
ref="search"
|
||||
:name="name"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
:style="inputStyle"
|
||||
:value="search"
|
||||
:disabled="disabled"
|
||||
:tabindex="tabindex"
|
||||
:aria-controls="'listbox-'+id"
|
||||
:class="['multiselect__input']"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@input="updateSearch($event.target.value)"
|
||||
@focus.prevent="activate()"
|
||||
@blur.prevent="deactivate()"
|
||||
@keyup.esc="deactivate()"
|
||||
@keydown.down.prevent="pointerForward()"
|
||||
@keydown.up.prevent="pointerBackward()"
|
||||
@keypress.enter.prevent.stop.self="addPointerElement($event)"
|
||||
@keydown.delete.stop="removeLastElement()"
|
||||
>
|
||||
<span
|
||||
v-if="isSingleLabelVisible"
|
||||
class="multiselect__single"
|
||||
@mousedown.prevent="toggle"
|
||||
>
|
||||
<slot :option="singleValue" name="singleLabel">
|
||||
<template>{{ currentOptionLabel }}</template>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<transition name="multiselect">
|
||||
<div
|
||||
v-show="isOpen"
|
||||
ref="list"
|
||||
:style="{ maxHeight: optimizedHeight + 'px' }"
|
||||
class="multiselect__content-wrapper"
|
||||
tabindex="-1"
|
||||
@focus="activate"
|
||||
@mousedown.prevent
|
||||
>
|
||||
<ul :style="contentStyle" :id="'listbox-'+id" class="multiselect__content" role="listbox">
|
||||
<slot name="beforeList"/>
|
||||
<li v-if="multiple && max === internalValue.length">
|
||||
<span class="multiselect__option">
|
||||
<slot name="maxElements"> {{ $t('validation.maximum_options_error', { max: max }) }} </slot>
|
||||
</span>
|
||||
</li>
|
||||
<template v-if="!max || internalValue.length < max">
|
||||
<li
|
||||
v-for="(option, index) of filteredOptions"
|
||||
:key="index"
|
||||
:id="id + '-' + index"
|
||||
:role="!(option && (option.$isLabel || option.$isDisabled)) ? 'option' : null"
|
||||
class="multiselect__element"
|
||||
>
|
||||
<span
|
||||
v-if="!(option && (option.$isLabel || option.$isDisabled))"
|
||||
:class="optionHighlight(index, option)"
|
||||
:data-select="option && option.isTag ? tagPlaceholder : selectLabelText"
|
||||
:data-selected="selectedLabelText"
|
||||
:data-deselect="deselectLabelText"
|
||||
class="multiselect__option"
|
||||
@click.stop="select(option)"
|
||||
@mouseenter.self="pointerSet(index)"
|
||||
>
|
||||
<slot :option="option" :search="search" name="option">
|
||||
<span>{{ getOptionLabel(option) }}</span>
|
||||
</slot>
|
||||
</span>
|
||||
<span
|
||||
v-if="option && (option.$isLabel || option.$isDisabled)"
|
||||
:data-select="groupSelect && selectGroupLabelText"
|
||||
:data-deselect="groupSelect && deselectGroupLabelText"
|
||||
:class="groupHighlight(index, option)"
|
||||
class="multiselect__option"
|
||||
@mouseenter.self="groupSelect && pointerSet(index)"
|
||||
@mousedown.prevent="selectGroup(option)"
|
||||
>
|
||||
<slot :option="option" :search="search" name="option">
|
||||
<span>{{ getOptionLabel(option) }}</span>
|
||||
</slot>
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="showNoOptions && (options.length === 0 && !search && !loading)">
|
||||
<span class="multiselect__option">
|
||||
<slot name="noOptions">{{ $t('general.list_is_empty') }}</slot>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<slot name="afterList"/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import multiselectMixin from './multiselectMixin'
|
||||
import pointerMixin from './pointerMixin'
|
||||
|
||||
export default {
|
||||
name: 'vue-multiselect',
|
||||
mixins: [multiselectMixin, pointerMixin],
|
||||
props: {
|
||||
/**
|
||||
* name attribute to match optional label element
|
||||
* @default ''
|
||||
* @type {String}
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an option
|
||||
* @default 'Press enter to select'
|
||||
* @type {String}
|
||||
*/
|
||||
selectLabel: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an option
|
||||
* @default 'Press enter to select'
|
||||
* @type {String}
|
||||
*/
|
||||
selectGroupLabel: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* String to show next to selected option
|
||||
* @default 'Selected'
|
||||
* @type {String}
|
||||
*/
|
||||
selectedLabel: {
|
||||
type: String,
|
||||
default: 'Selected'
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an already selected option
|
||||
* @default 'Press enter to remove'
|
||||
* @type {String}
|
||||
*/
|
||||
deselectLabel: {
|
||||
type: String,
|
||||
default: 'Press enter to remove'
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an already selected option
|
||||
* @default 'Press enter to remove'
|
||||
* @type {String}
|
||||
*/
|
||||
deselectGroupLabel: {
|
||||
type: String,
|
||||
default: 'Press enter to deselect group'
|
||||
},
|
||||
/**
|
||||
* Decide whether to show pointer labels
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
showLabels: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* Limit the display of selected options. The rest will be hidden within the limitText string.
|
||||
* @default 99999
|
||||
* @type {Integer}
|
||||
*/
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 99999
|
||||
},
|
||||
/**
|
||||
* Sets maxHeight style value of the dropdown
|
||||
* @default 300
|
||||
* @type {Integer}
|
||||
*/
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
/**
|
||||
* Function that process the message shown when selected
|
||||
* elements pass the defined limit.
|
||||
* @default 'and * more'
|
||||
* @param {Int} count Number of elements more than limit
|
||||
* @type {Function}
|
||||
*/
|
||||
limitText: {
|
||||
type: Function,
|
||||
default: count => `and ${count} more`
|
||||
},
|
||||
/**
|
||||
* Set true to trigger the loading spinner.
|
||||
* @default False
|
||||
* @type {Boolean}
|
||||
*/
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Disables the multiselect if true.
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Fixed opening direction
|
||||
* @default ''
|
||||
* @type {String}
|
||||
*/
|
||||
openDirection: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* Shows slot with message about empty options
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
showNoOptions: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showNoResults: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tabindex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
invalid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSingleLabelVisible () {
|
||||
return (
|
||||
(this.singleValue || this.singleValue === 0) &&
|
||||
(!this.isOpen || !this.searchable) &&
|
||||
!this.visibleValues.length
|
||||
)
|
||||
},
|
||||
isPlaceholderVisible () {
|
||||
return !this.internalValue.length && (!this.searchable || !this.isOpen)
|
||||
},
|
||||
visibleValues () {
|
||||
return this.multiple ? this.internalValue.slice(0, this.limit) : []
|
||||
},
|
||||
singleValue () {
|
||||
return this.internalValue[0]
|
||||
},
|
||||
deselectLabelText () {
|
||||
return this.showLabels ? this.deselectLabel : ''
|
||||
},
|
||||
deselectGroupLabelText () {
|
||||
return this.showLabels ? this.deselectGroupLabel : ''
|
||||
},
|
||||
selectLabelText () {
|
||||
return this.showLabels ? this.selectLabel : ''
|
||||
},
|
||||
selectGroupLabelText () {
|
||||
return this.showLabels ? this.selectGroupLabel : ''
|
||||
},
|
||||
selectedLabelText () {
|
||||
return this.showLabels ? this.selectedLabel : ''
|
||||
},
|
||||
inputStyle () {
|
||||
if (
|
||||
this.searchable ||
|
||||
(this.multiple && this.value && this.value.length)
|
||||
) {
|
||||
// Hide input by setting the width to 0 allowing it to receive focus
|
||||
|
||||
return this.isOpen
|
||||
? { width: '100%' }
|
||||
: ((this.value) ? { width: '0', position: 'absolute', padding: '0' } : '')
|
||||
}
|
||||
},
|
||||
contentStyle () {
|
||||
return this.options.length
|
||||
? { display: 'inline-block' }
|
||||
: { display: 'block' }
|
||||
},
|
||||
isAbove () {
|
||||
if (this.openDirection === 'above' || this.openDirection === 'top') {
|
||||
return true
|
||||
} else if (
|
||||
this.openDirection === 'below' ||
|
||||
this.openDirection === 'bottom'
|
||||
) {
|
||||
return false
|
||||
} else {
|
||||
return this.preferredOpenDirection === 'above'
|
||||
}
|
||||
},
|
||||
showSearchInput () {
|
||||
return (
|
||||
this.searchable &&
|
||||
(this.hasSingleSelectedSlot &&
|
||||
(this.visibleSingleValue || this.visibleSingleValue === 0)
|
||||
? this.isOpen
|
||||
: true)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
7
resources/assets/js/components/base/base-select/index.js
Executable file
7
resources/assets/js/components/base/base-select/index.js
Executable file
@ -0,0 +1,7 @@
|
||||
import Multiselect from './Multiselect'
|
||||
import multiselectMixin from './multiselectMixin'
|
||||
import pointerMixin from './pointerMixin'
|
||||
|
||||
export default Multiselect
|
||||
|
||||
export { Multiselect, multiselectMixin, pointerMixin }
|
||||
722
resources/assets/js/components/base/base-select/multiselectMixin.js
Executable file
722
resources/assets/js/components/base/base-select/multiselectMixin.js
Executable file
@ -0,0 +1,722 @@
|
||||
function isEmpty (opt) {
|
||||
if (opt === 0) return false
|
||||
if (Array.isArray(opt) && opt.length === 0) return true
|
||||
return !opt
|
||||
}
|
||||
|
||||
function not (fun) {
|
||||
return (...params) => !fun(...params)
|
||||
}
|
||||
|
||||
function includes (str, query) {
|
||||
/* istanbul ignore else */
|
||||
if (str === undefined) str = 'undefined'
|
||||
if (str === null) str = 'null'
|
||||
if (str === false) str = 'false'
|
||||
const text = str.toString().toLowerCase()
|
||||
return text.indexOf(query.trim()) !== -1
|
||||
}
|
||||
|
||||
function filterOptions (options, search, label, customLabel) {
|
||||
return options.filter(option => includes(customLabel(option, label), search))
|
||||
}
|
||||
|
||||
function stripGroups (options) {
|
||||
return options.filter(option => !option.$isLabel)
|
||||
}
|
||||
|
||||
function flattenOptions (values, label) {
|
||||
return (options) =>
|
||||
options.reduce((prev, curr) => {
|
||||
/* istanbul ignore else */
|
||||
if (curr[values] && curr[values].length) {
|
||||
prev.push({
|
||||
$groupLabel: curr[label],
|
||||
$isLabel: true
|
||||
})
|
||||
return prev.concat(curr[values])
|
||||
}
|
||||
return prev
|
||||
}, [])
|
||||
}
|
||||
|
||||
function filterGroups (search, label, values, groupLabel, customLabel) {
|
||||
return (groups) =>
|
||||
groups.map(group => {
|
||||
/* istanbul ignore else */
|
||||
if (!group[values]) {
|
||||
console.warn(`Options passed to vue-multiselect do not contain groups, despite the config.`)
|
||||
return []
|
||||
}
|
||||
const groupOptions = filterOptions(group[values], search, label, customLabel)
|
||||
|
||||
return groupOptions.length
|
||||
? {
|
||||
[groupLabel]: group[groupLabel],
|
||||
[values]: groupOptions
|
||||
}
|
||||
: []
|
||||
})
|
||||
}
|
||||
|
||||
const flow = (...fns) => x => fns.reduce((v, f) => f(v), x)
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
search: '',
|
||||
isOpen: false,
|
||||
preferredOpenDirection: 'below',
|
||||
optimizedHeight: this.maxHeight
|
||||
}
|
||||
},
|
||||
props: {
|
||||
initialSearch: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* Decide whether to filter the results based on search query.
|
||||
* Useful for async filtering, where we search through more complex data.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
internalSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* Array of available options: Objects, Strings or Integers.
|
||||
* If array of objects, visible label will default to option.label.
|
||||
* If `labal` prop is passed, label will equal option['label']
|
||||
* @type {Array}
|
||||
*/
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* Equivalent to the `multiple` attribute on a `<select>` input.
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Presets the selected options value.
|
||||
* @type {Object||Array||String||Integer}
|
||||
*/
|
||||
value: {
|
||||
type: null,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Key to compare objects
|
||||
* @default 'id'
|
||||
* @type {String}
|
||||
*/
|
||||
trackBy: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* Label to look for in option Object
|
||||
* @default 'label'
|
||||
* @type {String}
|
||||
*/
|
||||
label: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* Enable/disable search in options
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
searchable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* Clear the search input after `)
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
clearOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* Hide already selected options
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
hideSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Equivalent to the `placeholder` attribute on a `<select>` input.
|
||||
* @default 'Select option'
|
||||
* @type {String}
|
||||
*/
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Select option'
|
||||
},
|
||||
/**
|
||||
* Allow to remove all selected values
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
allowEmpty: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* Reset this.internalValue, this.search after this.internalValue changes.
|
||||
* Useful if want to create a stateless dropdown.
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
resetAfter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Enable/disable closing after selecting an option
|
||||
* @default true
|
||||
* @type {Boolean}
|
||||
*/
|
||||
closeOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* Function to interpolate the custom label
|
||||
* @default false
|
||||
* @type {Function}
|
||||
*/
|
||||
customLabel: {
|
||||
type: Function,
|
||||
default (option, label) {
|
||||
if (isEmpty(option)) return ''
|
||||
return label ? option[label] : option
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Disable / Enable tagging
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
taggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* String to show when highlighting a potential tag
|
||||
* @default 'Press enter to create a tag'
|
||||
* @type {String}
|
||||
*/
|
||||
tagPlaceholder: {
|
||||
type: String,
|
||||
default: 'Press enter to create a tag'
|
||||
},
|
||||
/**
|
||||
* By default new tags will appear above the search results.
|
||||
* Changing to 'bottom' will revert this behaviour
|
||||
* and will proritize the search results
|
||||
* @default 'top'
|
||||
* @type {String}
|
||||
*/
|
||||
tagPosition: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
/**
|
||||
* Number of allowed selected options. No limit if 0.
|
||||
* @default 0
|
||||
* @type {Number}
|
||||
*/
|
||||
max: {
|
||||
type: [Number, Boolean],
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Will be passed with all events as second param.
|
||||
* Useful for identifying events origin.
|
||||
* @default null
|
||||
* @type {String|Integer}
|
||||
*/
|
||||
id: {
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* Limits the options displayed in the dropdown
|
||||
* to the first X options.
|
||||
* @default 1000
|
||||
* @type {Integer}
|
||||
*/
|
||||
optionsLimit: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
/**
|
||||
* Name of the property containing
|
||||
* the group values
|
||||
* @default 1000
|
||||
* @type {String}
|
||||
*/
|
||||
groupValues: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* Name of the property containing
|
||||
* the group label
|
||||
* @default 1000
|
||||
* @type {String}
|
||||
*/
|
||||
groupLabel: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* Allow to select all group values
|
||||
* by selecting the group label
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
groupSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Array of keyboard keys to block
|
||||
* when selecting
|
||||
* @default 1000
|
||||
* @type {String}
|
||||
*/
|
||||
blockKeys: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Prevent from wiping up the search value
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
preserveSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* Select 1st options if value is empty
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
preselectFirst: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
/* istanbul ignore else */
|
||||
if (!this.multiple && this.max) {
|
||||
console.warn('[Vue-Multiselect warn]: Max prop should not be used when prop Multiple equals false.')
|
||||
}
|
||||
if (
|
||||
this.preselectFirst &&
|
||||
!this.internalValue.length &&
|
||||
this.options.length
|
||||
) {
|
||||
this.select(this.filteredOptions[0])
|
||||
}
|
||||
|
||||
if (this.initialSearch) {
|
||||
this.search = this.initialSearch
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
internalValue () {
|
||||
return this.value || this.value === 0
|
||||
? Array.isArray(this.value) ? this.value : [this.value]
|
||||
: []
|
||||
},
|
||||
filteredOptions () {
|
||||
const search = this.search || ''
|
||||
const normalizedSearch = search.toLowerCase().trim()
|
||||
|
||||
let options = this.options.concat()
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (this.internalSearch) {
|
||||
options = this.groupValues
|
||||
? this.filterAndFlat(options, normalizedSearch, this.label)
|
||||
: filterOptions(options, normalizedSearch, this.label, this.customLabel)
|
||||
} else {
|
||||
options = this.groupValues ? flattenOptions(this.groupValues, this.groupLabel)(options) : options
|
||||
}
|
||||
|
||||
options = this.hideSelected
|
||||
? options.filter(not(this.isSelected))
|
||||
: options
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (this.taggable && normalizedSearch.length && !this.isExistingOption(normalizedSearch)) {
|
||||
if (this.tagPosition === 'bottom') {
|
||||
options.push({ isTag: true, label: search })
|
||||
} else {
|
||||
options.unshift({ isTag: true, label: search })
|
||||
}
|
||||
}
|
||||
|
||||
return options.slice(0, this.optionsLimit)
|
||||
},
|
||||
valueKeys () {
|
||||
if (this.trackBy) {
|
||||
return this.internalValue.map(element => element[this.trackBy])
|
||||
} else {
|
||||
return this.internalValue
|
||||
}
|
||||
},
|
||||
optionKeys () {
|
||||
const options = this.groupValues ? this.flatAndStrip(this.options) : this.options
|
||||
return options.map(element => this.customLabel(element, this.label).toString().toLowerCase())
|
||||
},
|
||||
currentOptionLabel () {
|
||||
return this.multiple
|
||||
? this.searchable ? '' : this.placeholder
|
||||
: this.internalValue.length
|
||||
? this.getOptionLabel(this.internalValue[0])
|
||||
: this.searchable ? '' : this.placeholder
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
internalValue () {
|
||||
/* istanbul ignore else */
|
||||
if (this.resetAfter && this.internalValue.length) {
|
||||
this.search = ''
|
||||
this.$emit('input', this.multiple ? [] : null)
|
||||
}
|
||||
},
|
||||
search () {
|
||||
this.$emit('search-change', this.search, this.id)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Returns the internalValue in a way it can be emited to the parent
|
||||
* @returns {Object||Array||String||Integer}
|
||||
*/
|
||||
getValue () {
|
||||
return this.multiple
|
||||
? this.internalValue
|
||||
: this.internalValue.length === 0
|
||||
? null
|
||||
: this.internalValue[0]
|
||||
},
|
||||
/**
|
||||
* Filters and then flattens the options list
|
||||
* @param {Array}
|
||||
* @returns {Array} returns a filtered and flat options list
|
||||
*/
|
||||
filterAndFlat (options, search, label) {
|
||||
return flow(
|
||||
filterGroups(search, label, this.groupValues, this.groupLabel, this.customLabel),
|
||||
flattenOptions(this.groupValues, this.groupLabel)
|
||||
)(options)
|
||||
},
|
||||
/**
|
||||
* Flattens and then strips the group labels from the options list
|
||||
* @param {Array}
|
||||
* @returns {Array} returns a flat options list without group labels
|
||||
*/
|
||||
flatAndStrip (options) {
|
||||
return flow(
|
||||
flattenOptions(this.groupValues, this.groupLabel),
|
||||
stripGroups
|
||||
)(options)
|
||||
},
|
||||
/**
|
||||
* Updates the search value
|
||||
* @param {String}
|
||||
*/
|
||||
updateSearch (query) {
|
||||
this.search = query
|
||||
this.$emit('value', this.search)
|
||||
},
|
||||
/**
|
||||
* Finds out if the given query is already present
|
||||
* in the available options
|
||||
* @param {String}
|
||||
* @returns {Boolean} returns true if element is available
|
||||
*/
|
||||
isExistingOption (query) {
|
||||
return !this.options
|
||||
? false
|
||||
: this.optionKeys.indexOf(query) > -1
|
||||
},
|
||||
/**
|
||||
* Finds out if the given element is already present
|
||||
* in the result value
|
||||
* @param {Object||String||Integer} option passed element to check
|
||||
* @returns {Boolean} returns true if element is selected
|
||||
*/
|
||||
isSelected (option) {
|
||||
const opt = this.trackBy
|
||||
? option[this.trackBy]
|
||||
: option
|
||||
return this.valueKeys.indexOf(opt) > -1
|
||||
},
|
||||
/**
|
||||
* Finds out if the given option is disabled
|
||||
* @param {Object||String||Integer} option passed element to check
|
||||
* @returns {Boolean} returns true if element is disabled
|
||||
*/
|
||||
isOptionDisabled (option) {
|
||||
return !!option.$isDisabled
|
||||
},
|
||||
/**
|
||||
* Returns empty string when options is null/undefined
|
||||
* Returns tag query if option is tag.
|
||||
* Returns the customLabel() results and casts it to string.
|
||||
*
|
||||
* @param {Object||String||Integer} Passed option
|
||||
* @returns {Object||String}
|
||||
*/
|
||||
getOptionLabel (option) {
|
||||
if (isEmpty(option)) return ''
|
||||
/* istanbul ignore else */
|
||||
if (option.isTag) return option.label
|
||||
/* istanbul ignore else */
|
||||
if (option.$isLabel) return option.$groupLabel
|
||||
|
||||
let label = this.customLabel(option, this.label)
|
||||
/* istanbul ignore else */
|
||||
if (isEmpty(label)) return ''
|
||||
return label
|
||||
},
|
||||
/**
|
||||
* Add the given option to the list of selected options
|
||||
* or sets the option as the selected option.
|
||||
* If option is already selected -> remove it from the results.
|
||||
*
|
||||
* @param {Object||String||Integer} option to select/deselect
|
||||
* @param {Boolean} block removing
|
||||
*/
|
||||
select (option, key) {
|
||||
/* istanbul ignore else */
|
||||
if (option.$isLabel && this.groupSelect) {
|
||||
this.selectGroup(option)
|
||||
return
|
||||
}
|
||||
if (this.blockKeys.indexOf(key) !== -1 ||
|
||||
this.disabled ||
|
||||
option.$isDisabled ||
|
||||
option.$isLabel
|
||||
) return
|
||||
/* istanbul ignore else */
|
||||
if (this.max && this.multiple && this.internalValue.length === this.max) return
|
||||
/* istanbul ignore else */
|
||||
if (key === 'Tab' && !this.pointerDirty) return
|
||||
if (option.isTag) {
|
||||
this.$emit('tag', option.label, this.id)
|
||||
this.search = ''
|
||||
if (this.closeOnSelect && !this.multiple) this.deactivate()
|
||||
} else {
|
||||
const isSelected = this.isSelected(option)
|
||||
|
||||
if (isSelected) {
|
||||
if (key !== 'Tab') this.removeElement(option)
|
||||
return
|
||||
}
|
||||
|
||||
this.$emit('select', option, this.id)
|
||||
|
||||
if (this.multiple) {
|
||||
this.$emit('input', this.internalValue.concat([option]), this.id)
|
||||
} else {
|
||||
this.$emit('input', option, this.id)
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (this.clearOnSelect) this.search = ''
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (this.closeOnSelect) this.deactivate()
|
||||
},
|
||||
/**
|
||||
* Add the given group options to the list of selected options
|
||||
* If all group optiona are already selected -> remove it from the results.
|
||||
*
|
||||
* @param {Object||String||Integer} group to select/deselect
|
||||
*/
|
||||
selectGroup (selectedGroup) {
|
||||
const group = this.options.find(option => {
|
||||
return option[this.groupLabel] === selectedGroup.$groupLabel
|
||||
})
|
||||
|
||||
if (!group) return
|
||||
|
||||
if (this.wholeGroupSelected(group)) {
|
||||
this.$emit('remove', group[this.groupValues], this.id)
|
||||
|
||||
const newValue = this.internalValue.filter(
|
||||
option => group[this.groupValues].indexOf(option) === -1
|
||||
)
|
||||
|
||||
this.$emit('input', newValue, this.id)
|
||||
} else {
|
||||
const optionsToAdd = group[this.groupValues].filter(
|
||||
option => !(this.isOptionDisabled(option) || this.isSelected(option))
|
||||
)
|
||||
|
||||
this.$emit('select', optionsToAdd, this.id)
|
||||
this.$emit(
|
||||
'input',
|
||||
this.internalValue.concat(optionsToAdd),
|
||||
this.id
|
||||
)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Helper to identify if all values in a group are selected
|
||||
*
|
||||
* @param {Object} group to validated selected values against
|
||||
*/
|
||||
wholeGroupSelected (group) {
|
||||
return group[this.groupValues].every(option => this.isSelected(option) || this.isOptionDisabled(option)
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Helper to identify if all values in a group are disabled
|
||||
*
|
||||
* @param {Object} group to check for disabled values
|
||||
*/
|
||||
wholeGroupDisabled (group) {
|
||||
return group[this.groupValues].every(this.isOptionDisabled)
|
||||
},
|
||||
/**
|
||||
* Removes the given option from the selected options.
|
||||
* Additionally checks this.allowEmpty prop if option can be removed when
|
||||
* it is the last selected option.
|
||||
*
|
||||
* @param {type} option description
|
||||
* @returns {type} description
|
||||
*/
|
||||
removeElement (option, shouldClose = true) {
|
||||
/* istanbul ignore else */
|
||||
if (this.disabled) return
|
||||
/* istanbul ignore else */
|
||||
if (option.$isDisabled) return
|
||||
/* istanbul ignore else */
|
||||
if (!this.allowEmpty && this.internalValue.length <= 1) {
|
||||
this.deactivate()
|
||||
return
|
||||
}
|
||||
|
||||
const index = typeof option === 'object'
|
||||
? this.valueKeys.indexOf(option[this.trackBy])
|
||||
: this.valueKeys.indexOf(option)
|
||||
|
||||
this.$emit('remove', option, this.id)
|
||||
if (this.multiple) {
|
||||
const newValue = this.internalValue.slice(0, index).concat(this.internalValue.slice(index + 1))
|
||||
this.$emit('input', newValue, this.id)
|
||||
} else {
|
||||
this.$emit('input', null, this.id)
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (this.closeOnSelect && shouldClose) this.deactivate()
|
||||
},
|
||||
/**
|
||||
* Calls this.removeElement() with the last element
|
||||
* from this.internalValue (selected element Array)
|
||||
*
|
||||
* @fires this#removeElement
|
||||
*/
|
||||
removeLastElement () {
|
||||
/* istanbul ignore else */
|
||||
if (this.blockKeys.indexOf('Delete') !== -1) return
|
||||
/* istanbul ignore else */
|
||||
if (this.search.length === 0 && Array.isArray(this.internalValue) && this.internalValue.length) {
|
||||
this.removeElement(this.internalValue[this.internalValue.length - 1], false)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Opens the multiselect’s dropdown.
|
||||
* Sets this.isOpen to TRUE
|
||||
*/
|
||||
activate () {
|
||||
/* istanbul ignore else */
|
||||
if (this.isOpen || this.disabled) return
|
||||
|
||||
this.adjustPosition()
|
||||
/* istanbul ignore else */
|
||||
if (this.groupValues && this.pointer === 0 && this.filteredOptions.length) {
|
||||
this.pointer = 1
|
||||
}
|
||||
|
||||
this.isOpen = true
|
||||
/* istanbul ignore else */
|
||||
if (this.searchable) {
|
||||
if (!this.preserveSearch) this.search = ''
|
||||
this.$nextTick(() => this.$refs.search && this.$refs.search.focus())
|
||||
} else {
|
||||
this.$el.focus()
|
||||
}
|
||||
this.$emit('open', this.id)
|
||||
},
|
||||
/**
|
||||
* Closes the multiselect’s dropdown.
|
||||
* Sets this.isOpen to FALSE
|
||||
*/
|
||||
deactivate () {
|
||||
/* istanbul ignore else */
|
||||
if (!this.isOpen) return
|
||||
this.isOpen = false
|
||||
/* istanbul ignore else */
|
||||
if (this.searchable) {
|
||||
this.$refs.search && this.$refs.search.blur()
|
||||
} else {
|
||||
this.$el.blur()
|
||||
}
|
||||
if (!this.preserveSearch) this.search = ''
|
||||
this.$emit('close', this.getValue(), this.id)
|
||||
},
|
||||
/**
|
||||
* Call this.activate() or this.deactivate()
|
||||
* depending on this.isOpen value.
|
||||
*
|
||||
* @fires this#activate || this#deactivate
|
||||
* @property {Boolean} isOpen indicates if dropdown is open
|
||||
*/
|
||||
toggle () {
|
||||
this.isOpen
|
||||
? this.deactivate()
|
||||
: this.activate()
|
||||
},
|
||||
/**
|
||||
* Updates the hasEnoughSpace variable used for
|
||||
* detecting where to expand the dropdown
|
||||
*/
|
||||
adjustPosition () {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const spaceAbove = this.$el.getBoundingClientRect().top
|
||||
const spaceBelow = window.innerHeight - this.$el.getBoundingClientRect().bottom
|
||||
const hasEnoughSpaceBelow = spaceBelow > this.maxHeight
|
||||
|
||||
if (hasEnoughSpaceBelow || spaceBelow > spaceAbove || this.openDirection === 'below' || this.openDirection === 'bottom') {
|
||||
this.preferredOpenDirection = 'below'
|
||||
this.optimizedHeight = Math.min(spaceBelow - 40, this.maxHeight)
|
||||
} else {
|
||||
this.preferredOpenDirection = 'above'
|
||||
this.optimizedHeight = Math.min(spaceAbove - 40, this.maxHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
resources/assets/js/components/base/base-select/pointerMixin.js
Executable file
140
resources/assets/js/components/base/base-select/pointerMixin.js
Executable file
@ -0,0 +1,140 @@
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
pointer: 0,
|
||||
pointerDirty: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* Enable/disable highlighting of the pointed value.
|
||||
* @type {Boolean}
|
||||
* @default true
|
||||
*/
|
||||
showPointer: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
optionHeight: {
|
||||
type: Number,
|
||||
default: 40
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pointerPosition () {
|
||||
return this.pointer * this.optionHeight
|
||||
},
|
||||
visibleElements () {
|
||||
return this.optimizedHeight / this.optionHeight
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filteredOptions () {
|
||||
this.pointerAdjust()
|
||||
},
|
||||
isOpen () {
|
||||
this.pointerDirty = false
|
||||
},
|
||||
pointer () {
|
||||
this.$refs.search.setAttribute('aria-activedescendant', this.id + '-' + this.pointer.toString())
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
optionHighlight (index, option) {
|
||||
return {
|
||||
'multiselect__option--highlight': index === this.pointer && this.showPointer,
|
||||
'multiselect__option--selected': this.isSelected(option)
|
||||
}
|
||||
},
|
||||
groupHighlight (index, selectedGroup) {
|
||||
if (!this.groupSelect) {
|
||||
return ['multiselect__option--group', 'multiselect__option--disabled']
|
||||
}
|
||||
|
||||
const group = this.options.find(option => {
|
||||
return option[this.groupLabel] === selectedGroup.$groupLabel
|
||||
})
|
||||
|
||||
return group && !this.wholeGroupDisabled(group) ? [
|
||||
'multiselect__option--group',
|
||||
{ 'multiselect__option--highlight': index === this.pointer && this.showPointer },
|
||||
{ 'multiselect__option--group-selected': this.wholeGroupSelected(group) }
|
||||
] : 'multiselect__option--disabled'
|
||||
},
|
||||
addPointerElement ({ key } = 'Enter') {
|
||||
/* istanbul ignore else */
|
||||
if (this.filteredOptions.length > 0) {
|
||||
this.select(this.filteredOptions[this.pointer], key)
|
||||
}
|
||||
this.pointerReset()
|
||||
},
|
||||
pointerForward () {
|
||||
/* istanbul ignore else */
|
||||
if (this.pointer < this.filteredOptions.length - 1) {
|
||||
this.pointer++
|
||||
/* istanbul ignore next */
|
||||
if (this.$refs.list.scrollTop <= this.pointerPosition - (this.visibleElements - 1) * this.optionHeight) {
|
||||
this.$refs.list.scrollTop = this.pointerPosition - (this.visibleElements - 1) * this.optionHeight
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (
|
||||
this.filteredOptions[this.pointer] &&
|
||||
this.filteredOptions[this.pointer].$isLabel &&
|
||||
!this.groupSelect
|
||||
) this.pointerForward()
|
||||
}
|
||||
this.pointerDirty = true
|
||||
},
|
||||
pointerBackward () {
|
||||
if (this.pointer > 0) {
|
||||
this.pointer--
|
||||
/* istanbul ignore else */
|
||||
if (this.$refs.list.scrollTop >= this.pointerPosition) {
|
||||
this.$refs.list.scrollTop = this.pointerPosition
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (
|
||||
this.filteredOptions[this.pointer] &&
|
||||
this.filteredOptions[this.pointer].$isLabel &&
|
||||
!this.groupSelect
|
||||
) this.pointerBackward()
|
||||
} else {
|
||||
/* istanbul ignore else */
|
||||
if (
|
||||
this.filteredOptions[this.pointer] &&
|
||||
this.filteredOptions[0].$isLabel &&
|
||||
!this.groupSelect
|
||||
) this.pointerForward()
|
||||
}
|
||||
this.pointerDirty = true
|
||||
},
|
||||
pointerReset () {
|
||||
/* istanbul ignore else */
|
||||
if (!this.closeOnSelect) return
|
||||
this.pointer = 0
|
||||
/* istanbul ignore else */
|
||||
if (this.$refs.list) {
|
||||
this.$refs.list.scrollTop = 0
|
||||
}
|
||||
},
|
||||
pointerAdjust () {
|
||||
/* istanbul ignore else */
|
||||
if (this.pointer >= this.filteredOptions.length - 1) {
|
||||
this.pointer = this.filteredOptions.length
|
||||
? this.filteredOptions.length - 1
|
||||
: 0
|
||||
}
|
||||
|
||||
if (this.filteredOptions.length > 0 &&
|
||||
this.filteredOptions[this.pointer].$isLabel &&
|
||||
!this.groupSelect
|
||||
) {
|
||||
this.pointerForward()
|
||||
}
|
||||
},
|
||||
pointerSet (index) {
|
||||
this.pointer = index
|
||||
this.pointerDirty = true
|
||||
}
|
||||
}
|
||||
}
|
||||
64
resources/assets/js/components/base/base-table/classes/Column.js
Executable file
64
resources/assets/js/components/base/base-table/classes/Column.js
Executable file
@ -0,0 +1,64 @@
|
||||
import { pick } from '../helpers'
|
||||
|
||||
export default class Column {
|
||||
constructor (columnComponent) {
|
||||
const properties = pick(columnComponent, [
|
||||
'show', 'label', 'dataType', 'sortable', 'sortBy', 'filterable',
|
||||
'filterOn', 'hidden', 'formatter', 'cellClass', 'headerClass', 'sortAs'
|
||||
])
|
||||
|
||||
for (const property in properties) {
|
||||
this[property] = columnComponent[property]
|
||||
}
|
||||
|
||||
this.template = columnComponent.$scopedSlots.default
|
||||
}
|
||||
|
||||
isFilterable () {
|
||||
return this.filterable
|
||||
}
|
||||
|
||||
getFilterFieldName () {
|
||||
return this.filterOn || this.show
|
||||
}
|
||||
|
||||
isSortable () {
|
||||
return this.sortable
|
||||
}
|
||||
|
||||
getSortPredicate (sortOrder, allColumns) {
|
||||
const sortFieldName = this.getSortFieldName()
|
||||
|
||||
const sortColumn = allColumns.find(column => (column.sortAs === sortFieldName || column.show === sortFieldName))
|
||||
|
||||
const dataType = sortColumn.dataType
|
||||
|
||||
if (dataType.startsWith('date') || dataType === 'numeric') {
|
||||
return (row1, row2) => {
|
||||
const value1 = row1.getSortableValue(sortFieldName)
|
||||
const value2 = row2.getSortableValue(sortFieldName)
|
||||
|
||||
if (sortOrder === 'desc') {
|
||||
return value2 < value1 ? -1 : 1
|
||||
}
|
||||
|
||||
return value1 < value2 ? -1 : 1
|
||||
}
|
||||
}
|
||||
|
||||
return (row1, row2) => {
|
||||
const value1 = row1.getSortableValue(sortFieldName)
|
||||
const value2 = row2.getSortableValue(sortFieldName)
|
||||
|
||||
if (sortOrder === 'desc') {
|
||||
return value2.localeCompare(value1)
|
||||
}
|
||||
|
||||
return value1.localeCompare(value2)
|
||||
}
|
||||
}
|
||||
|
||||
getSortFieldName () {
|
||||
return this.sortBy || this.sortAs || this.show
|
||||
}
|
||||
}
|
||||
61
resources/assets/js/components/base/base-table/classes/Row.js
Executable file
61
resources/assets/js/components/base/base-table/classes/Row.js
Executable file
@ -0,0 +1,61 @@
|
||||
import moment from 'moment'
|
||||
import { get } from '../helpers'
|
||||
|
||||
export default class Row {
|
||||
constructor (data, columns) {
|
||||
this.data = data
|
||||
this.columns = columns
|
||||
}
|
||||
|
||||
getValue (columnName) {
|
||||
return get(this.data, columnName)
|
||||
}
|
||||
|
||||
getColumn (columnName) {
|
||||
return this.columns.find(column => (column.show === columnName || column.sortAs === columnName))
|
||||
}
|
||||
|
||||
getFilterableValue (columnName) {
|
||||
const value = this.getValue(columnName)
|
||||
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return value.toString().toLowerCase()
|
||||
}
|
||||
|
||||
getSortableValue (columnName) {
|
||||
const dataType = this.getColumn(columnName).dataType
|
||||
|
||||
let value = this.getValue(columnName)
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
value = value.toLowerCase()
|
||||
}
|
||||
|
||||
if (dataType.startsWith('date')) {
|
||||
const format = dataType.replace('date:', '')
|
||||
|
||||
return moment(value, format).format('YYYYMMDDHHmmss')
|
||||
}
|
||||
|
||||
if (dataType === 'numeric') {
|
||||
return value
|
||||
}
|
||||
|
||||
return value.toString()
|
||||
}
|
||||
|
||||
passesFilter (filter) {
|
||||
return this.columns
|
||||
.filter(column => column.isFilterable())
|
||||
.map(column => this.getFilterableValue(column.getFilterFieldName()))
|
||||
.filter(filterableValue => filterableValue.indexOf(filter.toLowerCase()) >= 0)
|
||||
.length
|
||||
}
|
||||
}
|
||||
120
resources/assets/js/components/base/base-table/components/Pagination.vue
Executable file
120
resources/assets/js/components/base/base-table/components/Pagination.vue
Executable file
@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<nav v-if="shouldShowPagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li :class="{ disabled: pagination.currentPage === 1 }">
|
||||
<a
|
||||
:class="{ disabled: pagination.currentPage === 1 }"
|
||||
@click="pageClicked( pagination.currentPage - 1 )"
|
||||
>
|
||||
<i class="left chevron icon">«</i>
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="hasFirst" :class="{ active: isActive(1) }" class="page-item">
|
||||
<a class="page-link" @click="pageClicked(1)">1</a>
|
||||
</li>
|
||||
<li v-if="hasFirstEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||
<li v-for="page in pages" :key="page" :class="{ active: isActive(page), disabled: page === '...' }" class="page-item">
|
||||
<a class="page-link" @click="pageClicked(page)">{{ page }}</a>
|
||||
</li>
|
||||
<li v-if="hasLastEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||
<li
|
||||
v-if="hasLast"
|
||||
:class="{ active: isActive(this.pagination.totalPages) }"
|
||||
class="page-item"
|
||||
>
|
||||
<a class="page-link" @click="pageClicked(pagination.totalPages)">
|
||||
{{ pagination.totalPages }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
:class="{ disabled: pagination.currentPage === pagination.totalPages }"
|
||||
@click="pageClicked( pagination.currentPage + 1 )"
|
||||
>
|
||||
<i class="right chevron icon">»</i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
pages () {
|
||||
return this.pagination.totalPages === undefined
|
||||
? []
|
||||
: this.pageLinks()
|
||||
},
|
||||
|
||||
hasFirst () {
|
||||
return this.pagination.currentPage >= 4 || this.pagination.totalPages < 10
|
||||
},
|
||||
|
||||
hasLast () {
|
||||
return this.pagination.currentPage <= this.pagination.totalPages - 3 || this.pagination.totalPages < 10
|
||||
},
|
||||
|
||||
hasFirstEllipsis () {
|
||||
return this.pagination.currentPage >= 4 && this.pagination.totalPages >= 10
|
||||
},
|
||||
|
||||
hasLastEllipsis () {
|
||||
return this.pagination.currentPage <= this.pagination.totalPages - 3 && this.pagination.totalPages >= 10
|
||||
},
|
||||
|
||||
shouldShowPagination () {
|
||||
if (this.pagination.totalPages === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.pagination.count === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.pagination.totalPages > 1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isActive (page) {
|
||||
const currentPage = this.pagination.currentPage || 1
|
||||
|
||||
return currentPage === page
|
||||
},
|
||||
pageClicked (page) {
|
||||
if (page === '...' ||
|
||||
page === this.pagination.currentPage ||
|
||||
page > this.pagination.totalPages ||
|
||||
page < 1) {
|
||||
return
|
||||
}
|
||||
this.$emit('pageChange', page)
|
||||
},
|
||||
|
||||
pageLinks () {
|
||||
const pages = []
|
||||
|
||||
let left = 2
|
||||
let right = this.pagination.totalPages - 1
|
||||
|
||||
if (this.pagination.totalPages >= 10) {
|
||||
left = Math.max(1, this.pagination.currentPage - 2)
|
||||
right = Math.min(this.pagination.currentPage + 2, this.pagination.totalPages)
|
||||
}
|
||||
for (let i = left; i <= right; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
24
resources/assets/js/components/base/base-table/components/TableCell.js
Executable file
24
resources/assets/js/components/base/base-table/components/TableCell.js
Executable file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
functional: true,
|
||||
|
||||
props: ['column', 'row', 'responsiveLabel'],
|
||||
|
||||
render (createElement, { props }) {
|
||||
const data = {}
|
||||
|
||||
if (props.column.cellClass) {
|
||||
data.class = props.column.cellClass
|
||||
}
|
||||
|
||||
if (props.column.template) {
|
||||
return createElement('td', data, props.column.template(props.row.data))
|
||||
}
|
||||
|
||||
data.domProps = {}
|
||||
data.domProps.innerHTML = props.column.formatter(props.row.getValue(props.column.show), props.row.data)
|
||||
|
||||
return createElement('td', [
|
||||
createElement('span', props.responsiveLabel), data.domProps.innerHTML
|
||||
])
|
||||
}
|
||||
}
|
||||
32
resources/assets/js/components/base/base-table/components/TableColumn.vue
Executable file
32
resources/assets/js/components/base/base-table/components/TableColumn.vue
Executable file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<!-- Never render the contents -->
|
||||
<!-- The scoped slot won't have the required data -->
|
||||
<div v-if="false">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import settings from '../settings'
|
||||
export default {
|
||||
props: {
|
||||
show: { required: false, type: String },
|
||||
label: { default: null, type: String },
|
||||
dataType: { default: 'string', type: String },
|
||||
|
||||
sortable: { default: true, type: Boolean },
|
||||
sortBy: { default: null },
|
||||
|
||||
filterable: { default: true, type: Boolean },
|
||||
sortAs: { default: null },
|
||||
filterOn: { default: null },
|
||||
|
||||
formatter: { default: v => v, type: Function },
|
||||
|
||||
hidden: { default: false, type: Boolean },
|
||||
|
||||
cellClass: { default: settings.cellClass },
|
||||
headerClass: { default: settings.headerClass },
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<th
|
||||
v-if="this.isVisible"
|
||||
slot-scope="col"
|
||||
:aria-sort="ariaSort"
|
||||
:aria-disabled="ariaDisabled"
|
||||
:class="headerClass"
|
||||
role="columnheader"
|
||||
@click="clicked"
|
||||
>
|
||||
{{ label }}
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { classList } from '../helpers'
|
||||
export default {
|
||||
props: ['column', 'sort'],
|
||||
|
||||
computed: {
|
||||
ariaDisabled () {
|
||||
if (!this.column.isSortable()) {
|
||||
return 'true'
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
ariaSort () {
|
||||
if (!this.column.isSortable ()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if ((this.column.sortAs || this.column.show) !== this.sort.fieldName) {
|
||||
return 'none'
|
||||
}
|
||||
|
||||
return this.sort.order === 'asc' ? 'ascending' : 'descending';
|
||||
},
|
||||
|
||||
headerClass () {
|
||||
if (!this.column.isSortable()) {
|
||||
return classList('table-component__th', this.column.headerClass);
|
||||
}
|
||||
|
||||
if ((this.column.sortAs || this.column.show) !== this.sort.fieldName) {
|
||||
return classList('table-component__th table-component__th--sort', this.column.headerClass);
|
||||
}
|
||||
|
||||
return classList(`table-component__th table-component__th--sort-${this.sort.order}`, this.column.headerClass);
|
||||
},
|
||||
|
||||
isVisible () {
|
||||
return !this.column.hidden
|
||||
},
|
||||
|
||||
label () {
|
||||
if (this.column.label === null) {
|
||||
return this.column.show
|
||||
}
|
||||
return this.column.label
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
clicked () {
|
||||
if (this.column.isSortable()) {
|
||||
this.$emit('click', this.column)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
330
resources/assets/js/components/base/base-table/components/TableComponent.vue
Executable file
330
resources/assets/js/components/base/base-table/components/TableComponent.vue
Executable file
@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<div class="table-component">
|
||||
<div v-if="showFilter && filterableColumnExists" class="table-component__filter">
|
||||
<input
|
||||
:class="fullFilterInputClass"
|
||||
v-model="filter"
|
||||
:placeholder="filterPlaceholder"
|
||||
type="text"
|
||||
>
|
||||
<a v-if="filter" class="table-component__filter__clear" @click="filter = ''">×</a>
|
||||
</div>
|
||||
|
||||
<div class="table-component__table-wrapper">
|
||||
<base-loader v-if="loading" class="table-loader" />
|
||||
|
||||
<table :class="fullTableClass">
|
||||
<caption
|
||||
v-if="showCaption"
|
||||
class="table-component__table__caption"
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
>{{ ariaCaption }}</caption>
|
||||
<thead :class="fullTableHeadClass">
|
||||
<tr>
|
||||
<table-column-header
|
||||
v-for="column in columns"
|
||||
:key="column.show || column.show"
|
||||
:sort="sort"
|
||||
:column="column"
|
||||
@click="changeSorting"
|
||||
/>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody :class="fullTableBodyClass">
|
||||
<table-row
|
||||
v-for="row in displayedRows"
|
||||
:key="row.vueTableComponentInternalRowId"
|
||||
:row="row"
|
||||
:columns="columns"
|
||||
@rowClick="emitRowClick"
|
||||
/>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<slot :rows="rows" name="tfoot" />
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="displayedRows.length === 0 && !loading" class="table-component__message">{{ filterNoResults }}</div>
|
||||
|
||||
<div style="display:none;">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<pagination v-if="pagination && !loading" :pagination="pagination" @pageChange="pageChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Column from '../classes/Column'
|
||||
import expiringStorage from '../expiring-storage'
|
||||
import Row from '../classes/Row'
|
||||
import TableColumnHeader from './TableColumnHeader'
|
||||
import TableRow from './TableRow'
|
||||
import settings from '../settings'
|
||||
import Pagination from './Pagination'
|
||||
import { classList, pick } from '../helpers'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TableColumnHeader,
|
||||
TableRow,
|
||||
Pagination
|
||||
},
|
||||
|
||||
props: {
|
||||
data: { default: () => [], type: [Array, Function] },
|
||||
|
||||
showFilter: { type: Boolean, default: true },
|
||||
showCaption: { type: Boolean, default: true },
|
||||
|
||||
sortBy: { default: '', type: String },
|
||||
sortOrder: { default: '', type: String },
|
||||
|
||||
cacheKey: { default: null },
|
||||
cacheLifetime: { default: 5 },
|
||||
|
||||
tableClass: { default: () => settings.tableClass },
|
||||
theadClass: { default: () => settings.theadClass },
|
||||
tbodyClass: { default: () => settings.tbodyClass },
|
||||
filterInputClass: { default: () => settings.filterInputClass },
|
||||
filterPlaceholder: { default: () => settings.filterPlaceholder },
|
||||
filterNoResults: { default: () => settings.filterNoResults }
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
columns: [],
|
||||
rows: [],
|
||||
filter: '',
|
||||
sort: {
|
||||
fieldName: '',
|
||||
order: ''
|
||||
},
|
||||
pagination: null,
|
||||
|
||||
loading: false,
|
||||
localSettings: {}
|
||||
}),
|
||||
|
||||
computed: {
|
||||
fullTableClass () {
|
||||
return classList('table-component__table', this.tableClass)
|
||||
},
|
||||
|
||||
fullTableHeadClass () {
|
||||
return classList('table-component__table__head', this.theadClass)
|
||||
},
|
||||
|
||||
fullTableBodyClass () {
|
||||
return classList('table-component__table__body', this.tbodyClass)
|
||||
},
|
||||
|
||||
fullFilterInputClass () {
|
||||
return classList('table-component__filter__field', this.filterInputClass)
|
||||
},
|
||||
|
||||
ariaCaption () {
|
||||
if (this.sort.fieldName === '') {
|
||||
return 'Table not sorted'
|
||||
}
|
||||
|
||||
return (
|
||||
`Table sorted by ${this.sort.fieldName} ` +
|
||||
(this.sort.order === 'asc' ? '(ascending)' : '(descending)')
|
||||
)
|
||||
},
|
||||
|
||||
usesLocalData () {
|
||||
return Array.isArray(this.data)
|
||||
},
|
||||
|
||||
displayedRows () {
|
||||
if (!this.usesLocalData) {
|
||||
return this.sortedRows
|
||||
}
|
||||
|
||||
if (!this.showFilter) {
|
||||
return this.sortedRows
|
||||
}
|
||||
|
||||
if (!this.columns.filter(column => column.isFilterable()).length) {
|
||||
return this.sortedRows
|
||||
}
|
||||
|
||||
return this.sortedRows.filter(row => row.passesFilter(this.filter))
|
||||
},
|
||||
|
||||
sortedRows () {
|
||||
if (!this.usesLocalData) {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
if (this.sort.fieldName === '') {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
if (this.columns.length === 0) {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
const sortColumn = this.getColumn(this.sort.fieldName)
|
||||
|
||||
if (!sortColumn) {
|
||||
return this.rows
|
||||
}
|
||||
|
||||
return this.rows.sort(
|
||||
sortColumn.getSortPredicate(this.sort.order, this.columns)
|
||||
)
|
||||
},
|
||||
|
||||
filterableColumnExists () {
|
||||
return this.columns.filter(c => c.isFilterable()).length > 0
|
||||
},
|
||||
|
||||
storageKey () {
|
||||
return this.cacheKey
|
||||
? `vue-table-component.${this.cacheKey}`
|
||||
: `vue-table-component.${window.location.host}${window.location.pathname}${this.cacheKey}`
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
filter () {
|
||||
if (!this.usesLocalData) {
|
||||
this.mapDataToRows()
|
||||
}
|
||||
|
||||
this.saveState()
|
||||
},
|
||||
|
||||
data () {
|
||||
if (this.usesLocalData) {
|
||||
this.mapDataToRows()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.sort.order = this.sortOrder
|
||||
|
||||
this.restoreState()
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
this.sort.fieldName = this.sortBy
|
||||
const columnComponents = this.$slots.default
|
||||
.filter(column => column.componentInstance)
|
||||
.map(column => column.componentInstance)
|
||||
|
||||
this.columns = columnComponents.map(column => new Column(column))
|
||||
|
||||
columnComponents.forEach(columnCom => {
|
||||
Object.keys(columnCom.$options.props).forEach(prop =>
|
||||
columnCom.$watch(prop, () => {
|
||||
this.columns = columnComponents.map(column => new Column(column))
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
await this.mapDataToRows()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async pageChange (page) {
|
||||
this.pagination.currentPage = page
|
||||
|
||||
await this.mapDataToRows()
|
||||
},
|
||||
|
||||
async mapDataToRows () {
|
||||
const data = this.usesLocalData
|
||||
? this.prepareLocalData()
|
||||
: await this.fetchServerData()
|
||||
|
||||
let rowId = 0
|
||||
|
||||
this.rows = data
|
||||
.map(rowData => {
|
||||
rowData.vueTableComponentInternalRowId = rowId++
|
||||
return rowData
|
||||
})
|
||||
.map(rowData => new Row(rowData, this.columns))
|
||||
},
|
||||
|
||||
prepareLocalData () {
|
||||
this.pagination = null
|
||||
|
||||
return this.data
|
||||
},
|
||||
|
||||
async fetchServerData () {
|
||||
const page = (this.pagination && this.pagination.currentPage) || 1
|
||||
this.loading = true
|
||||
|
||||
const response = await this.data({
|
||||
filter: this.filter,
|
||||
sort: this.sort,
|
||||
page: page
|
||||
})
|
||||
|
||||
this.pagination = response.pagination
|
||||
this.loading = false
|
||||
return response.data
|
||||
},
|
||||
|
||||
async refresh () {
|
||||
if (this.pagination) {
|
||||
this.pagination.currentPage = 1
|
||||
}
|
||||
await this.mapDataToRows()
|
||||
},
|
||||
|
||||
changeSorting (column) {
|
||||
if (this.sort.fieldName !== (column.sortAs || column.show)) {
|
||||
this.sort.fieldName = (column.sortAs || column.show)
|
||||
this.sort.order = 'asc'
|
||||
} else {
|
||||
this.sort.order = this.sort.order === 'asc' ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
if (!this.usesLocalData) {
|
||||
this.mapDataToRows()
|
||||
}
|
||||
|
||||
this.saveState()
|
||||
},
|
||||
|
||||
getColumn (columnName) {
|
||||
return this.columns.find(column => column.show === columnName)
|
||||
},
|
||||
|
||||
saveState () {
|
||||
expiringStorage.set(
|
||||
this.storageKey,
|
||||
pick(this.$data, ['filter', 'sort']),
|
||||
this.cacheLifetime
|
||||
)
|
||||
},
|
||||
|
||||
restoreState () {
|
||||
const previousState = expiringStorage.get(this.storageKey)
|
||||
|
||||
if (previousState === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this.sort = previousState.sort
|
||||
this.filter = previousState.filter
|
||||
|
||||
this.saveState()
|
||||
},
|
||||
|
||||
emitRowClick (row) {
|
||||
this.$emit('rowClick', row)
|
||||
this.$emit('row-click', row)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
38
resources/assets/js/components/base/base-table/components/TableRow.vue
Executable file
38
resources/assets/js/components/base/base-table/components/TableRow.vue
Executable file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<tr @click="onClick">
|
||||
<table-cell
|
||||
v-for="column in visibleColumns"
|
||||
:row="row"
|
||||
:column="column"
|
||||
:key="column.id"
|
||||
:responsive-label="column.label"
|
||||
></table-cell>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TableCell from './TableCell';
|
||||
|
||||
export default {
|
||||
props: ['columns', 'row'],
|
||||
|
||||
components: {
|
||||
TableCell,
|
||||
},
|
||||
|
||||
computed: {
|
||||
visibleColumns() {
|
||||
return this.columns.filter(column => ! column.hidden);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick(e) {
|
||||
this.$emit('rowClick', {
|
||||
e,
|
||||
row: this.row
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
34
resources/assets/js/components/base/base-table/expiring-storage.js
Executable file
34
resources/assets/js/components/base/base-table/expiring-storage.js
Executable file
@ -0,0 +1,34 @@
|
||||
class ExpiringStorage {
|
||||
get (key) {
|
||||
const cached = JSON.parse(
|
||||
localStorage.getItem(key)
|
||||
)
|
||||
|
||||
if (!cached) {
|
||||
return null
|
||||
}
|
||||
|
||||
const expires = new Date(cached.expires)
|
||||
|
||||
if (expires < new Date()) {
|
||||
localStorage.removeItem(key)
|
||||
return null
|
||||
}
|
||||
|
||||
return cached.value
|
||||
}
|
||||
|
||||
has (key) {
|
||||
return this.get(key) !== null
|
||||
}
|
||||
|
||||
set (key, value, lifeTimeInMinutes) {
|
||||
const currentTime = new Date().getTime()
|
||||
|
||||
const expires = new Date(currentTime + lifeTimeInMinutes * 60000)
|
||||
|
||||
localStorage.setItem(key, JSON.stringify({ value, expires }))
|
||||
}
|
||||
}
|
||||
|
||||
export default new ExpiringStorage()
|
||||
30
resources/assets/js/components/base/base-table/helpers.js
Executable file
30
resources/assets/js/components/base/base-table/helpers.js
Executable file
@ -0,0 +1,30 @@
|
||||
export function classList (...classes) {
|
||||
return classes
|
||||
.map(c => Array.isArray(c) ? c : [c])
|
||||
.reduce((classes, c) => classes.concat(c), [])
|
||||
}
|
||||
|
||||
export function get (object, path) {
|
||||
if (!path) {
|
||||
return object
|
||||
}
|
||||
|
||||
if (object === null || typeof object !== 'object') {
|
||||
return object
|
||||
}
|
||||
|
||||
const [pathHead, pathTail] = path.split(/\.(.+)/)
|
||||
|
||||
return get(object[pathHead], pathTail)
|
||||
}
|
||||
|
||||
export function pick (object, properties) {
|
||||
return properties.reduce((pickedObject, property) => {
|
||||
pickedObject[property] = object[property]
|
||||
return pickedObject
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function range (from, to) {
|
||||
return [...Array(to - from)].map((_, i) => i + from)
|
||||
}
|
||||
20
resources/assets/js/components/base/base-table/index.js
Executable file
20
resources/assets/js/components/base/base-table/index.js
Executable file
@ -0,0 +1,20 @@
|
||||
import TableComponent from './components/TableComponent'
|
||||
import TableColumn from './components/TableColumn'
|
||||
import Pagination from './components/Pagination'
|
||||
import { mergeSettings } from './settings'
|
||||
|
||||
export default {
|
||||
install (Vue, options = {}) {
|
||||
mergeSettings(options)
|
||||
|
||||
Vue.component('table-component', TableComponent)
|
||||
Vue.component('table-column', TableColumn)
|
||||
Vue.component('pagination', Pagination)
|
||||
},
|
||||
|
||||
settings (settings) {
|
||||
mergeSettings(settings)
|
||||
}
|
||||
}
|
||||
|
||||
export { TableComponent, TableColumn }
|
||||
18
resources/assets/js/components/base/base-table/settings.js
Executable file
18
resources/assets/js/components/base/base-table/settings.js
Executable file
@ -0,0 +1,18 @@
|
||||
const settings = {
|
||||
tableClass: '',
|
||||
theadClass: '',
|
||||
tbodyClass: '',
|
||||
headerClass: '',
|
||||
cellClass: '',
|
||||
filterInputClass: '',
|
||||
filterPlaceholder: 'Filter table…',
|
||||
filterNoResults: 'There are no matching rows'
|
||||
}
|
||||
|
||||
export function mergeSettings (newSettings) {
|
||||
for (const setting in newSettings) {
|
||||
settings[setting] = newSettings[setting]
|
||||
}
|
||||
}
|
||||
|
||||
export default settings
|
||||
35
resources/assets/js/components/base/index.js
Normal file
35
resources/assets/js/components/base/index.js
Normal file
@ -0,0 +1,35 @@
|
||||
import BaseButton from './BaseButton.vue'
|
||||
import ItemModal from './modal/ItemModal.vue'
|
||||
import BaseModal from './modal/BaseModal.vue'
|
||||
import BaseDatePicker from './base-date-picker/BaseDatePicker.vue'
|
||||
import BaseInput from './BaseInput.vue'
|
||||
import BaseSwitch from './BaseSwitch.vue'
|
||||
import BaseTextArea from './BaseTextArea.vue'
|
||||
import BaseSelect from './base-select/BaseSelect.vue'
|
||||
import BaseLoader from './BaseLoader.vue'
|
||||
import BaseCustomerSelect from './BaseCustomerSelect.vue'
|
||||
|
||||
import BasePopup from './popup/BasePopup.vue'
|
||||
import CustomerSelectPopup from './popup/CustomerSelectPopup.vue'
|
||||
import TaxSelectPopup from './popup/TaxSelectPopup.vue'
|
||||
|
||||
import {TableColumn, TableComponent} from './base-table/index'
|
||||
|
||||
Vue.component('base-button', BaseButton)
|
||||
Vue.component('item-modal', ItemModal)
|
||||
Vue.component('base-modal', BaseModal)
|
||||
Vue.component('base-date-picker', BaseDatePicker)
|
||||
Vue.component('base-input', BaseInput)
|
||||
Vue.component('base-switch', BaseSwitch)
|
||||
Vue.component('base-text-area', BaseTextArea)
|
||||
Vue.component('base-loader', BaseLoader)
|
||||
|
||||
Vue.component('table-component', TableComponent)
|
||||
Vue.component('table-column', TableColumn)
|
||||
|
||||
Vue.component('base-select', BaseSelect)
|
||||
Vue.component('base-customer-select', BaseCustomerSelect)
|
||||
|
||||
Vue.component('base-popup', BasePopup)
|
||||
Vue.component('customer-select-popup', CustomerSelectPopup)
|
||||
Vue.component('tax-select-popup', TaxSelectPopup)
|
||||
73
resources/assets/js/components/base/modal/BaseModal.vue
Normal file
73
resources/assets/js/components/base/modal/BaseModal.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div v-if="modalActive" class="base-modal" :class="'size-' + modalSize">
|
||||
<div class="modal-body">
|
||||
<div class="close-icon">
|
||||
<font-awesome-icon class="mr-2" icon="times" @click="closeModal"/>
|
||||
</div>
|
||||
<div class="modal-header p-3">
|
||||
<h5 class="modal-heading">{{ modalTitle }}</h5>
|
||||
</div>
|
||||
<component :is="component" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import TaxTypeModal from './TaxTypeModal'
|
||||
import ItemModal from './ItemModal'
|
||||
import EstimateTemplate from './EstimateTemplate'
|
||||
import InvoiceTemplate from './InvoiceTemplate'
|
||||
import CustomerModal from './CustomerModal'
|
||||
import CategoryModal from './CategoryModal'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TaxTypeModal,
|
||||
ItemModal,
|
||||
EstimateTemplate,
|
||||
InvoiceTemplate,
|
||||
CustomerModal,
|
||||
CategoryModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
component: '',
|
||||
hasFocus: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'modalActive',
|
||||
'modalTitle',
|
||||
'componentName',
|
||||
'modalSize',
|
||||
'modalData'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
componentName (component) {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
this.component = component
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal',
|
||||
'closeModal'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
172
resources/assets/js/components/base/modal/CategoryModal.vue
Normal file
172
resources/assets/js/components/base/modal/CategoryModal.vue
Normal file
@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div class="category-modal">
|
||||
<form action="" @submit.prevent="submitCategoryData">
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('expenses.category') }}<span class="required text-danger">*</span></label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
ref="name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
v-model="formData.name"
|
||||
type="text"
|
||||
@input="$v.formData.name.$touch()"
|
||||
/>
|
||||
|
||||
<div v-if="$v.formData.name.$error">
|
||||
<span v-if="!$v.formData.name.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label input-label">{{ $t('expenses.description') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-text-area
|
||||
v-model="formData.description"
|
||||
rows="4"
|
||||
cols="50"
|
||||
@input="$v.formData.description.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.description.$error">
|
||||
<span v-if="!$v.formData.name.maxLength" class="text-danger"> {{ $tc('validation.description_maxlength') }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<base-button
|
||||
:outline="true"
|
||||
class="mr-3"
|
||||
color="theme"
|
||||
@click="closeCategoryModal"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ !isEdit ? $t('general.save') : $t('general.update') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, minLength, maxLength } = require('vuelidate/lib/validators')
|
||||
export default {
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
isEdit: false,
|
||||
isLoading: false,
|
||||
formData: {
|
||||
id: null,
|
||||
name: null,
|
||||
description: null
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'modalDataID',
|
||||
'modalData',
|
||||
'modalActive'
|
||||
])
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required,
|
||||
minLength: minLength(3)
|
||||
},
|
||||
description: {
|
||||
maxLength: maxLength(255)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'modalDataID' (val) {
|
||||
if (val) {
|
||||
this.isEdit = true
|
||||
this.setData()
|
||||
} else {
|
||||
this.isEdit = false
|
||||
}
|
||||
},
|
||||
'modalActive' (val) {
|
||||
if (!this.modalActive) {
|
||||
this.resetFormData()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.name.focus = true
|
||||
if (this.modalDataID) {
|
||||
this.isEdit = true
|
||||
this.setData()
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'closeModal',
|
||||
'resetModalData'
|
||||
]),
|
||||
...mapActions('category', [
|
||||
'addCategory',
|
||||
'updateCategory'
|
||||
]),
|
||||
resetFormData () {
|
||||
this.formData = {
|
||||
id: null,
|
||||
name: null,
|
||||
description: null
|
||||
}
|
||||
this.$v.formData.$reset()
|
||||
},
|
||||
async submitCategoryData () {
|
||||
this.$v.formData.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
this.isLoading = true
|
||||
let response
|
||||
if (!this.isEdit) {
|
||||
response = await this.addCategory(this.formData)
|
||||
} else {
|
||||
response = await this.updateCategory(this.formData)
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('settings.expense_category.created_message'))
|
||||
window.hub.$emit('newCategory', response.data.category)
|
||||
this.closeCategoryModal()
|
||||
this.isLoading = false
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
},
|
||||
async setData () {
|
||||
this.formData = {
|
||||
id: this.modalData.id,
|
||||
name: this.modalData.name,
|
||||
description: this.modalData.description
|
||||
}
|
||||
},
|
||||
closeCategoryModal () {
|
||||
this.resetFormData()
|
||||
this.closeModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
681
resources/assets/js/components/base/modal/CustomerModal.vue
Normal file
681
resources/assets/js/components/base/modal/CustomerModal.vue
Normal file
@ -0,0 +1,681 @@
|
||||
<template>
|
||||
<div class="customer-modal">
|
||||
<form action="" @submit.prevent="submitCustomerData">
|
||||
<div class="card-body">
|
||||
<!-- tab-1 -->
|
||||
<tabs :options="{defaultTabHash: 'basic-home' }" class="tabs-simple">
|
||||
<tab id="basic-home" name="Basic Info">
|
||||
<div class="basic-info">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.display_name') }} <span class="required">*</span></label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
ref="name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
v-model.trim="formData.name"
|
||||
type="text"
|
||||
name="name"
|
||||
@input="$v.formData.name.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.name.$error">
|
||||
<span v-if="!$v.formData.name.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} </span>
|
||||
<span v-if="!$v.formData.name.alpha" class="text-danger">{{ $tc('validation.characters_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.primary_display_name') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="formData.contact_name"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('login.email') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
:invalid="$v.formData.email.$error"
|
||||
v-model.trim="formData.email"
|
||||
type="text"
|
||||
name="email"
|
||||
@input="$v.formData.email.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.email.$error">
|
||||
<span v-if="!$v.formData.email.email" class="text-danger"> {{ $t('validation.email_incorrect') }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $tc('settings.currencies.currency') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="currency"
|
||||
:options="currencies"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
label="name"
|
||||
track-by="id"
|
||||
placeholder="select currency"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.phone') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
:invalid="$v.formData.phone.$error"
|
||||
v-model.trim="formData.phone"
|
||||
type="text"
|
||||
name="phone"
|
||||
@input="$v.formData.phone.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.phone.$error">
|
||||
<span v-if="!$v.formData.phone.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.website') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="formData.website"
|
||||
:invalid="$v.formData.website.$error"
|
||||
type="url"
|
||||
@input="$v.formData.website.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.website.$error">
|
||||
<span v-if="!$v.formData.website.url" class="text-danger">{{ $tc('validation.invalid_url') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</tab>
|
||||
|
||||
<!-- tab-2 -->
|
||||
<tab id="basic-profile" name="Billing Address">
|
||||
<div class="basic-info">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.name') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="billing.name"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.phone') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
:invalid="$v.billing.phone.$error"
|
||||
v-model.trim="billing.phone"
|
||||
type="text"
|
||||
name="phone"
|
||||
@input="$v.billing.phone.$touch()"
|
||||
/>
|
||||
<div v-if="$v.billing.phone.$error">
|
||||
<span v-if="!$v.billing.phone.numberic" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.address') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-text-area
|
||||
v-model="billing.address_street_1"
|
||||
rows="2"
|
||||
cols="50"
|
||||
placeholder="Street 1"
|
||||
class="mb-1"
|
||||
@input="$v.billing.address_street_1.$touch()"
|
||||
/>
|
||||
<div v-if="$v.billing.address_street_1.$error">
|
||||
<span v-if="!$v.billing.address_street_1.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span>
|
||||
</div>
|
||||
|
||||
<base-text-area
|
||||
v-model="billing.address_street_2"
|
||||
rows="2"
|
||||
cols="50"
|
||||
placeholder="Street 2"
|
||||
@input="$v.billing.address_street_2.$touch()"
|
||||
/>
|
||||
|
||||
<div v-if="$v.billing.address_street_2.$error">
|
||||
<span v-if="!$v.billing.address_street_2.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.country') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="billingCountry"
|
||||
:options="countryList"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="select country"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.state') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="billingState"
|
||||
:options="billingStates"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:disabled="isDisabledBillingState"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="select state"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.city') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="billingCity"
|
||||
:options="billingCities"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:disabled="isDisabledBillingCity"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="select city"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Zip Code</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="billing.zip"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.zip_code') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="billing.zip"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<!-- tab-3 -->
|
||||
<tab id="basic-message" name="Shipping Address">
|
||||
<div class="basic-info">
|
||||
<div class="form-group row ">
|
||||
<div class="col-sm-12 copy-address-button">
|
||||
<base-button ref="sameAddress" icon="copy" class="mr-2 btn-sm" color="theme" @click="copyAddress(true)">
|
||||
{{ $t('customers.copy_billing_address') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.name') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="shipping.name"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.phone') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
:invalid="$v.shipping.phone.$error"
|
||||
v-model.trim="shipping.phone"
|
||||
type="text"
|
||||
name="phone"
|
||||
@input="$v.shipping.phone.$touch()"
|
||||
/>
|
||||
<div v-if="$v.shipping.phone.$error">
|
||||
<span v-if="!$v.shipping.phone.numberic" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.address') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-text-area
|
||||
v-model="shipping.address_street_1"
|
||||
rows="2"
|
||||
cols="50"
|
||||
placeholder="Street 1"
|
||||
class="mb-1"
|
||||
@input="$v.shipping.address_street_1.$touch()"
|
||||
/>
|
||||
<div v-if="$v.shipping.address_street_1.$error">
|
||||
<span v-if="!$v.shipping.address_street_1.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span>
|
||||
</div>
|
||||
|
||||
<base-text-area
|
||||
v-model="shipping.address_street_2"
|
||||
rows="2"
|
||||
cols="50"
|
||||
placeholder="Street 2"
|
||||
@input="$v.shipping.address_street_2.$touch()"
|
||||
/>
|
||||
<div v-if="$v.shipping.address_street_2.$error">
|
||||
<span v-if="!$v.shipping.address_street_2.maxLength" class="text-danger">{{ $t('validation.address_maxlength') }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.country') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="shippingCountry"
|
||||
:options="countryList"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:allow-empty="false"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="select country"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.state') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="shippingState"
|
||||
:options="shippingStates"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:disabled="isDisabledShippingState"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="select state"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.city') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="shippingCity"
|
||||
:options="shippingCities"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
:disabled="isDisabledShippingCity"
|
||||
track-by="id"
|
||||
label="name"
|
||||
placeholder="select city"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('customers.zip_code') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
v-model="shipping.zip"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
</tabs>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<base-button :outline="true" class="mr-3" color="theme" @click="cancelCustomer">
|
||||
{{ $t('general.cancel') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tabs, Tab } from 'vue-tabs-component'
|
||||
import MultiSelect from 'vue-multiselect'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import AddressStub from '../../../stub/address'
|
||||
const { required, minLength, email, numeric, alpha, url, maxLength } = require('vuelidate/lib/validators')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'tabs': Tabs,
|
||||
'tab': Tab,
|
||||
MultiSelect
|
||||
},
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
countryList: [],
|
||||
billingStates: [],
|
||||
billingCities: [],
|
||||
billingCountry: null,
|
||||
billingState: null,
|
||||
billingCity: null,
|
||||
shippingStates: [],
|
||||
shippingCities: [],
|
||||
shippingCountry: null,
|
||||
shippingState: null,
|
||||
shippingCity: null,
|
||||
isCopyFromBilling: false,
|
||||
currencyList: [],
|
||||
currency: '',
|
||||
isDisabledBillingState: true,
|
||||
isDisabledBillingCity: true,
|
||||
isDisabledShippingState: true,
|
||||
isDisabledShippingCity: true,
|
||||
formData: {
|
||||
id: null,
|
||||
name: null,
|
||||
currency_id: null,
|
||||
phone: null,
|
||||
website: null,
|
||||
contact_name: null,
|
||||
addresses: []
|
||||
},
|
||||
billing: {...AddressStub},
|
||||
shipping: {...AddressStub}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required,
|
||||
minLength: minLength(3),
|
||||
alpha
|
||||
},
|
||||
email: {
|
||||
email
|
||||
},
|
||||
phone: {
|
||||
numeric
|
||||
},
|
||||
website: {
|
||||
url
|
||||
}
|
||||
},
|
||||
billing: {
|
||||
phone: {
|
||||
numeric
|
||||
},
|
||||
address_street_1: {
|
||||
maxLength: maxLength(255)
|
||||
},
|
||||
address_street_2: {
|
||||
maxLength: maxLength(255)
|
||||
}
|
||||
},
|
||||
shipping: {
|
||||
phone: {
|
||||
numeric
|
||||
},
|
||||
address_street_1: {
|
||||
maxLength: maxLength(255)
|
||||
},
|
||||
address_street_2: {
|
||||
maxLength: maxLength(255)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('currency', [
|
||||
'defaultCurrency',
|
||||
'currencies'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
billingCountry () {
|
||||
if (this.billingCountry) {
|
||||
this.billing.country_id = this.billingCountry.id
|
||||
this.isDisabledBillingState = false
|
||||
this.fetchBillingStates(this.billingCountry.id)
|
||||
this.billingState = null
|
||||
this.billingCity = null
|
||||
return true
|
||||
}
|
||||
},
|
||||
billingState () {
|
||||
if (this.billingState) {
|
||||
this.billing.state_id = this.billingState.id
|
||||
this.isDisabledBillingCity = false
|
||||
this.fetchBillingCities(this.billingState.id)
|
||||
this.billingCity = null
|
||||
return true
|
||||
}
|
||||
this.billingCity = null
|
||||
this.isDisabledBillingCity = true
|
||||
},
|
||||
billingCity () {
|
||||
if (this.billingCity) {
|
||||
this.billing.city_id = this.billingCity.id
|
||||
}
|
||||
},
|
||||
shippingCountry () {
|
||||
if (this.shippingCountry) {
|
||||
this.shipping.country_id = this.shippingCountry.id
|
||||
this.isDisabledShippingState = false
|
||||
this.fetchShippingStates(this.shippingCountry.id)
|
||||
if (this.isCopyFromBilling) {
|
||||
return true
|
||||
}
|
||||
this.shippingState = null
|
||||
this.shippingCity = null
|
||||
return true
|
||||
}
|
||||
},
|
||||
shippingState () {
|
||||
if (this.shippingState) {
|
||||
this.shipping.state_id = this.shippingState.id
|
||||
this.isDisabledShippingCity = false
|
||||
this.fetchShippingCities(this.shippingState.id)
|
||||
if (this.isCopyFromBilling) {
|
||||
this.isCopyFromBilling = false
|
||||
return true
|
||||
}
|
||||
this.shippingCity = null
|
||||
return true
|
||||
}
|
||||
this.shippingCity = null
|
||||
this.isDisabledShippingCity = true
|
||||
},
|
||||
shippingCity () {
|
||||
if (this.shippingCity) {
|
||||
this.shipping.city_id = this.shippingCity.id
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.name.focus = true
|
||||
this.currency = this.defaultCurrency
|
||||
this.fetchCountry()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('invoice', {
|
||||
setInvoiceCustomer: 'selectCustomer'
|
||||
}),
|
||||
...mapActions('estimate', {
|
||||
setEstimateCustomer: 'selectCustomer'
|
||||
}),
|
||||
...mapActions('customer', [
|
||||
'fetchCustomer',
|
||||
'addCustomer',
|
||||
'updateCustomer'
|
||||
]),
|
||||
...mapActions('modal', [
|
||||
'closeModal'
|
||||
]),
|
||||
resetData () {
|
||||
this.formData = {
|
||||
name: null,
|
||||
currency_id: null,
|
||||
phone: null,
|
||||
website: null,
|
||||
contact_name: null,
|
||||
addresses: []
|
||||
}
|
||||
|
||||
this.billingStates = []
|
||||
this.billingCities = []
|
||||
this.billingCountry = null
|
||||
this.billingState = null
|
||||
this.billingCity = null
|
||||
this.shippingStates = []
|
||||
this.shippingCities = []
|
||||
this.shippingCountry = null
|
||||
this.shippingState = null
|
||||
this.shippingCity = null
|
||||
|
||||
this.billing = {...AddressStub}
|
||||
this.shipping = {...AddressStub}
|
||||
this.$v.formData.$reset()
|
||||
},
|
||||
cancelCustomer () {
|
||||
this.resetData()
|
||||
this.closeModal()
|
||||
},
|
||||
copyAddress (val) {
|
||||
if (val === true) {
|
||||
this.isCopyFromBilling = true
|
||||
this.shipping = {...this.billing, type: 'shipping'}
|
||||
this.shippingCountry = this.billingCountry
|
||||
this.shippingState = this.billingState
|
||||
this.shippingCity = this.billingCity
|
||||
} else {
|
||||
this.shipping = {...AddressStub, type: 'shipping'}
|
||||
this.shippingCountry = null
|
||||
this.shippingState = null
|
||||
this.shippingCity = null
|
||||
}
|
||||
},
|
||||
async loadData () {
|
||||
let response = await this.fetchCustomer()
|
||||
this.currencyList = this.currencies
|
||||
this.formData.currency_id = response.data.currency.id
|
||||
return true
|
||||
},
|
||||
checkAddress () {
|
||||
const isBillingEmpty = Object.values(this.billing).every(val => (val === null || val === ''))
|
||||
const isShippingEmpty = Object.values(this.shipping).every(val => (val === null || val === ''))
|
||||
if (isBillingEmpty === true && isBillingEmpty === true) {
|
||||
this.formData.addresses = []
|
||||
return true
|
||||
}
|
||||
|
||||
if (isBillingEmpty === false && isShippingEmpty === false) {
|
||||
this.formData.addresses = [{...this.billing, type: 'billing'}, {...this.shipping, type: 'shipping'}]
|
||||
return true
|
||||
}
|
||||
|
||||
if (isBillingEmpty === false) {
|
||||
this.formData.addresses.push({...this.billing, type: 'billing'})
|
||||
return true
|
||||
}
|
||||
|
||||
this.formData.addresses = [{...this.shipping, type: 'shipping'}]
|
||||
return true
|
||||
},
|
||||
async submitCustomerData () {
|
||||
this.$v.formData.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
// this.checkAddress()
|
||||
this.formData.addresses = [{...this.shipping, type: 'shipping'}, {...this.billing, type: 'billing'}]
|
||||
this.isLoading = true
|
||||
|
||||
if (this.currency) {
|
||||
this.formData.currency_id = this.currency.id
|
||||
} else {
|
||||
this.formData.currency_id = this.defaultCurrency.id
|
||||
}
|
||||
let response = await this.addCustomer(this.formData)
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$tc('customers.created_message'))
|
||||
this.isLoading = false
|
||||
if (this.$route.name === 'invoices.create') {
|
||||
this.setInvoiceCustomer(response.data.customer.id)
|
||||
}
|
||||
if (this.$route.name === 'estimates.create') {
|
||||
this.setEstimateCustomer(response.data.customer.id)
|
||||
}
|
||||
this.resetData()
|
||||
this.closeModal()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
},
|
||||
async fetchCountry () {
|
||||
let res = await window.axios.get('/api/countries')
|
||||
if (res) {
|
||||
this.countryList = res.data.countries
|
||||
}
|
||||
},
|
||||
async fetchBillingStates (id) {
|
||||
let res = await window.axios.get(`/api/states/${id}`)
|
||||
if (res) {
|
||||
this.billingStates = res.data.states
|
||||
}
|
||||
},
|
||||
async fetchBillingCities (id) {
|
||||
let res = await window.axios.get(`/api/cities/${id}`)
|
||||
if (res) {
|
||||
this.billingCities = res.data.cities
|
||||
}
|
||||
},
|
||||
async fetchShippingStates (id) {
|
||||
let res = await window.axios.get(`/api/states/${id}`)
|
||||
if (res) {
|
||||
this.shippingStates = res.data.states
|
||||
}
|
||||
},
|
||||
async fetchShippingCities (id) {
|
||||
let res = await window.axios.get(`/api/cities/${id}`)
|
||||
if (res) {
|
||||
this.shippingCities = res.data.cities
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="template-modal">
|
||||
<div class="card-body">
|
||||
<div class="template-container">
|
||||
<div
|
||||
v-for="(template,index) in modalData"
|
||||
:key="index"
|
||||
:class="{'selected-template': selectedTemplate === template.id}"
|
||||
class="template-img"
|
||||
>
|
||||
<img
|
||||
:src="template.path"
|
||||
alt="template-image"
|
||||
height="200" width="140"
|
||||
@click="selectedTemplate = template.id"
|
||||
>
|
||||
<img
|
||||
v-if="selectedTemplate === template.id"
|
||||
class="check-icon"
|
||||
src="/assets/img/tick.png"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<base-button outline class="mr-3" color="theme" @click="closeEstimateModal">
|
||||
{{ $t('general.cancel') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
color="theme"
|
||||
@click="chooseTemplate()"
|
||||
>
|
||||
{{ $t('general.choose_template') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
selectedTemplate: 1,
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'modalData'
|
||||
]),
|
||||
...mapGetters('estimate', [
|
||||
'getTemplateId'
|
||||
])
|
||||
},
|
||||
mounted () {
|
||||
this.selectedTemplate = this.getTemplateId
|
||||
},
|
||||
methods: {
|
||||
...mapActions('estimate', [
|
||||
'setTemplate'
|
||||
]),
|
||||
...mapActions('modal', [
|
||||
'closeModal',
|
||||
'resetModalData'
|
||||
]),
|
||||
async chooseTemplate () {
|
||||
this.isLoading = true
|
||||
let resp = await this.setTemplate(this.selectedTemplate)
|
||||
if (resp) {
|
||||
this.isLoading = false
|
||||
this.resetModalData()
|
||||
this.closeModal()
|
||||
}
|
||||
},
|
||||
closeEstimateModal () {
|
||||
this.selectedTemplate = this.getTemplateId
|
||||
this.closeModal()
|
||||
this.resetModalData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="template-modal">
|
||||
<div class="card-body">
|
||||
<div class="template-container">
|
||||
<div
|
||||
v-for="(template,index) in modalData"
|
||||
:key="index"
|
||||
:class="{'selected-template': selectedTemplate === template.id}"
|
||||
class="template-img"
|
||||
>
|
||||
<img
|
||||
:src="template.path"
|
||||
alt="template-image"
|
||||
height="200" width="140"
|
||||
@click="selectedTemplate = template.id"
|
||||
>
|
||||
<img
|
||||
v-if="selectedTemplate === template.id"
|
||||
class="check-icon"
|
||||
src="/assets/img/tick.png"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<base-button outline class="mr-3" color="theme" @click="closeInvoiceModal">
|
||||
{{ $t('general.cancel') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
color="theme"
|
||||
@click="chooseTemplate()"
|
||||
>
|
||||
{{ $t('general.choose_template') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
selectedTemplate: 1,
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'modalData'
|
||||
]),
|
||||
...mapGetters('invoice', [
|
||||
'getTemplateId'
|
||||
])
|
||||
},
|
||||
mounted () {
|
||||
this.selectedTemplate = this.getTemplateId
|
||||
},
|
||||
methods: {
|
||||
...mapActions('invoice', [
|
||||
'setTemplate'
|
||||
]),
|
||||
...mapActions('modal', [
|
||||
'closeModal',
|
||||
'resetModalData'
|
||||
]),
|
||||
async chooseTemplate () {
|
||||
this.isLoading = true;
|
||||
let resp = await this.setTemplate(this.selectedTemplate)
|
||||
if (resp) {
|
||||
this.isLoading = false
|
||||
this.resetModalData()
|
||||
this.closeModal()
|
||||
}
|
||||
},
|
||||
closeInvoiceModal () {
|
||||
this.selectedTemplate = this.getTemplateId
|
||||
this.closeModal()
|
||||
this.resetModalData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
251
resources/assets/js/components/base/modal/ItemModal.vue
Normal file
251
resources/assets/js/components/base/modal/ItemModal.vue
Normal file
@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="item-modal">
|
||||
<form action="" @submit.prevent="submitItemData">
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">
|
||||
{{ $t('items.name') }}<span class="required">*</span>
|
||||
</label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
ref="name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
v-model="formData.name"
|
||||
type="text"
|
||||
@input="$v.formData.name.$touch()"
|
||||
/>
|
||||
|
||||
<div v-if="$v.formData.name.$error">
|
||||
<span v-if="!$v.formData.name.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.name.minLength" class="text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('items.price') }}<span class="required">*</span></label>
|
||||
<div class="col-sm-7">
|
||||
<div class="base-input">
|
||||
<money
|
||||
:class="{'invalid' : $v.formData.price.$error}"
|
||||
v-model="price"
|
||||
v-bind="defaultCurrencyForInput"
|
||||
class="input-field"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="$v.formData.price.$error">
|
||||
<span v-if="!$v.formData.price.required" class="text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.price.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('items.unit') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-select
|
||||
v-model="formData.unit"
|
||||
:options="units"
|
||||
:searchable="true"
|
||||
:show-labels="false"
|
||||
label="name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{{ $t('items.description') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-text-area
|
||||
v-model="formData.description"
|
||||
rows="4"
|
||||
cols="50"
|
||||
@input="$v.formData.description.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.description.$error">
|
||||
<span v-if="!$v.formData.description.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<base-button
|
||||
:outline="true"
|
||||
class="mr-3"
|
||||
color="theme"
|
||||
type="button"
|
||||
@click="closeItemModal"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
v-if="isEdit"
|
||||
:loading="isLoading"
|
||||
color="theme"
|
||||
@click="submitItemData"
|
||||
>
|
||||
{{ $t('general.update') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
v-else
|
||||
:loading="isLoading"
|
||||
icon="save"
|
||||
color="theme"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('general.save') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, minLength, numeric, maxLength, minValue } = require('vuelidate/lib/validators')
|
||||
export default {
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
isEdit: false,
|
||||
isLoading: false,
|
||||
tempData: null,
|
||||
units: [
|
||||
{ name: 'box', value: 'box' },
|
||||
{ name: 'cm', value: 'cm' },
|
||||
{ name: 'dz', value: 'dz' },
|
||||
{ name: 'ft', value: 'ft' },
|
||||
{ name: 'g', value: 'g' },
|
||||
{ name: 'in', value: 'in' },
|
||||
{ name: 'kg', value: 'kg' },
|
||||
{ name: 'km', value: 'km' },
|
||||
{ name: 'lb', value: 'lb' },
|
||||
{ name: 'mg', value: 'mg' }
|
||||
],
|
||||
formData: {
|
||||
name: null,
|
||||
price: null,
|
||||
description: null,
|
||||
unit: null
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required,
|
||||
minLength: minLength(3)
|
||||
},
|
||||
price: {
|
||||
required,
|
||||
numeric,
|
||||
minValue: minValue(0.1)
|
||||
},
|
||||
description: {
|
||||
maxLength: maxLength(255)
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('currency', [
|
||||
'defaultCurrencyForInput'
|
||||
]),
|
||||
price: {
|
||||
get: function () {
|
||||
return this.formData.price / 100
|
||||
},
|
||||
set: function (newValue) {
|
||||
this.formData.price = newValue * 100
|
||||
}
|
||||
},
|
||||
...mapGetters('modal', [
|
||||
'modalDataID'
|
||||
]),
|
||||
...mapGetters('item', [
|
||||
'getItemById'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
modalDataID () {
|
||||
this.isEdit = true
|
||||
this.fetchEditData()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.modalDataID) {
|
||||
this.isEdit = true
|
||||
this.fetchEditData()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.name.focus = true
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'closeModal',
|
||||
'resetModalData'
|
||||
]),
|
||||
...mapActions('item', [
|
||||
'addItem',
|
||||
'updateItem'
|
||||
]),
|
||||
...mapActions('invoice', [
|
||||
'setItem'
|
||||
]),
|
||||
resetFormData () {
|
||||
this.formData = {
|
||||
name: null,
|
||||
price: null,
|
||||
description: null,
|
||||
unit: null,
|
||||
id: null
|
||||
}
|
||||
|
||||
this.$v.$reset()
|
||||
},
|
||||
fetchEditData () {
|
||||
this.tempData = this.getItemById(this.modalDataID)
|
||||
if (this.tempData) {
|
||||
this.formData.name = this.tempData.name
|
||||
this.formData.price = this.tempData.price
|
||||
this.formData.description = this.tempData.description
|
||||
this.formData.unit = this.tempData.unit
|
||||
this.formData.id = this.tempData.id
|
||||
}
|
||||
},
|
||||
async submitItemData () {
|
||||
this.$v.formData.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
if (this.formData.unit) {
|
||||
this.formData.unit = this.formData.unit.name
|
||||
}
|
||||
this.isLoading = true
|
||||
let response
|
||||
if (this.isEdit) {
|
||||
response = await this.updateItem(this.formData)
|
||||
} else {
|
||||
response = await this.addItem(this.formData)
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$tc('items.created_message'))
|
||||
this.setItem(response.data.item)
|
||||
window.hub.$emit('newItem', response.data.item)
|
||||
this.isLoading = false
|
||||
this.resetModalData()
|
||||
this.resetFormData()
|
||||
this.closeModal()
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
},
|
||||
closeItemModal () {
|
||||
this.resetFormData()
|
||||
this.closeModal()
|
||||
this.resetModalData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
216
resources/assets/js/components/base/modal/TaxTypeModal.vue
Normal file
216
resources/assets/js/components/base/modal/TaxTypeModal.vue
Normal file
@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div class="tax-type-modal">
|
||||
<form action="" @submit.prevent="submitTaxTypeData">
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label input-label">{{ $t('tax_types.name') }} <span class="required"> *</span></label>
|
||||
<div class="col-sm-7">
|
||||
<base-input
|
||||
ref="name"
|
||||
:invalid="$v.formData.name.$error"
|
||||
v-model="formData.name"
|
||||
type="text"
|
||||
@input="$v.formData.name.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.name.$error">
|
||||
<span v-if="!$v.formData.name.required" class="form-group__message text-danger">{{ $tc('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.name.minLength" class="form-group__message text-danger"> {{ $tc('validation.name_min_length', $v.formData.name.$params.minLength.min, { count: $v.formData.name.$params.minLength.min }) }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label input-label">{{ $t('tax_types.percent') }} <span class="required"> *</span></label>
|
||||
<div class="col-sm-7">
|
||||
<div class="base-input">
|
||||
<money
|
||||
:class="{'invalid' : $v.formData.percent.$error}"
|
||||
v-model="formData.percent"
|
||||
v-bind="defaultInput"
|
||||
class="input-field"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="$v.formData.percent.$error">
|
||||
<span v-if="!$v.formData.percent.required" class="text-danger">{{ $t('validation.required') }}</span>
|
||||
<span v-if="!$v.formData.percent.between" class="form-group__message text-danger">{{ $t('validation.enter_valid_tax_rate') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label input-label">{{ $t('tax_types.description') }}</label>
|
||||
<div class="col-sm-7">
|
||||
<base-text-area
|
||||
v-model="formData.description"
|
||||
rows="4"
|
||||
cols="50"
|
||||
@input="$v.formData.description.$touch()"
|
||||
/>
|
||||
<div v-if="$v.formData.description.$error">
|
||||
<span v-if="!$v.formData.description.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label input-label">{{ $t('tax_types.compound_tax') }}</label>
|
||||
<div class="col-sm-7 mr-4">
|
||||
<base-switch
|
||||
v-model="formData.compound_tax"
|
||||
class="btn-switch compound-tax-toggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<base-button
|
||||
:outline="true"
|
||||
class="mr-3"
|
||||
color="theme"
|
||||
type="button"
|
||||
@click="closeTaxModal"
|
||||
>
|
||||
{{ $t('general.cancel') }}
|
||||
</base-button>
|
||||
<base-button
|
||||
:loading="isLoading"
|
||||
color="theme"
|
||||
icon="save"
|
||||
type="submit"
|
||||
>
|
||||
{{ !isEdit ? $t('general.save') : $t('general.update') }}
|
||||
</base-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
const { required, minLength, between, maxLength } = require('vuelidate/lib/validators')
|
||||
export default {
|
||||
mixins: [validationMixin],
|
||||
data () {
|
||||
return {
|
||||
isEdit: false,
|
||||
isLoading: false,
|
||||
formData: {
|
||||
id: null,
|
||||
name: null,
|
||||
percent: '',
|
||||
description: null,
|
||||
compound_tax: false,
|
||||
collective_tax: 0
|
||||
},
|
||||
defaultInput: {
|
||||
decimal: '.',
|
||||
thousands: ',',
|
||||
prefix: '% ',
|
||||
precision: 2,
|
||||
masked: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('modal', [
|
||||
'modalDataID',
|
||||
'modalData',
|
||||
'modalActive'
|
||||
])
|
||||
},
|
||||
validations: {
|
||||
formData: {
|
||||
name: {
|
||||
required,
|
||||
minLength: minLength(3)
|
||||
},
|
||||
percent: {
|
||||
required,
|
||||
between: between(0.10, 100)
|
||||
},
|
||||
description: {
|
||||
maxLength: maxLength(255)
|
||||
}
|
||||
}
|
||||
},
|
||||
// watch: {
|
||||
// 'modalDataID' (val) {
|
||||
// if (val) {
|
||||
// this.isEdit = true
|
||||
// this.setData()
|
||||
// } else {
|
||||
// this.isEdit = false
|
||||
// }
|
||||
// },
|
||||
// 'modalActive' (val) {
|
||||
// if (!this.modalActive) {
|
||||
// this.resetFormData()
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
async mounted () {
|
||||
this.$refs.name.focus = true
|
||||
if (this.modalDataID) {
|
||||
this.isEdit = true
|
||||
this.setData()
|
||||
// this.resetFormData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'closeModal',
|
||||
'resetModalData'
|
||||
]),
|
||||
...mapActions('taxType', [
|
||||
'addTaxType',
|
||||
'updateTaxType',
|
||||
'fetchTaxType'
|
||||
]),
|
||||
resetFormData () {
|
||||
this.formData = {
|
||||
id: null,
|
||||
name: null,
|
||||
percent: null,
|
||||
description: null,
|
||||
collective_tax: 0
|
||||
}
|
||||
this.$v.formData.$reset()
|
||||
},
|
||||
async submitTaxTypeData () {
|
||||
this.$v.formData.$touch()
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
this.isLoading = true
|
||||
let response
|
||||
if (!this.isEdit) {
|
||||
response = await this.addTaxType(this.formData)
|
||||
} else {
|
||||
response = await this.updateTaxType(this.formData)
|
||||
}
|
||||
if (response.data) {
|
||||
window.toastr['success'](this.$t('settings.sales_taxes.created_message'))
|
||||
window.hub.$emit('newTax', response.data.taxType)
|
||||
this.closeTaxModal()
|
||||
this.isLoading = false
|
||||
return true
|
||||
}
|
||||
window.toastr['error'](response.data.error)
|
||||
},
|
||||
async setData () {
|
||||
this.formData = {
|
||||
id: this.modalData.id,
|
||||
name: this.modalData.name,
|
||||
percent: this.modalData.percent,
|
||||
description: this.modalData.description,
|
||||
compound_tax: this.modalData.compound_tax ? true : false
|
||||
}
|
||||
},
|
||||
closeTaxModal () {
|
||||
this.resetModalData()
|
||||
this.resetFormData()
|
||||
this.closeModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
100
resources/assets/js/components/base/popup/BasePopup.vue
Normal file
100
resources/assets/js/components/base/popup/BasePopup.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<div v-click-outside="clickOutsideMenu" class="search-select" >
|
||||
<div
|
||||
class="activator"
|
||||
@click="toggleSearchMenu">
|
||||
<slot name="activator" />
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="showMenu"
|
||||
:class="{'selector-menu-above': isAbove}"
|
||||
class="selector-menu"
|
||||
>
|
||||
|
||||
<slot />
|
||||
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
toggle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
openDirection: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: 180
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showMenu: false,
|
||||
preferredOpenDirection: 'below',
|
||||
optimizedHeight: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isAbove () {
|
||||
if (this.openDirection === 'above' || this.openDirection === 'top') {
|
||||
return true
|
||||
} else if (this.openDirection === 'below' || this.openDirection === 'bottom') {
|
||||
return false
|
||||
} else {
|
||||
return this.preferredOpenDirection === 'above'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSearchMenu () {
|
||||
this.adjustPosition()
|
||||
if (this.toggle) {
|
||||
this.showMenu = !this.showMenu
|
||||
} else {
|
||||
this.showMenu = true
|
||||
}
|
||||
},
|
||||
clickOutsideMenu () {
|
||||
this.showMenu = false
|
||||
},
|
||||
open () {
|
||||
this.showMenu = true
|
||||
},
|
||||
close () {
|
||||
this.showMenu = false
|
||||
},
|
||||
adjustPosition () {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const spaceAbove = this.$el.getBoundingClientRect().top
|
||||
const spaceBelow = window.innerHeight - this.$el.getBoundingClientRect().bottom
|
||||
const hasEnoughSpaceBelow = spaceBelow > this.maxHeight
|
||||
|
||||
if (hasEnoughSpaceBelow || spaceBelow > spaceAbove || this.openDirection === 'below' || this.openDirection === 'bottom') {
|
||||
this.preferredOpenDirection = 'below'
|
||||
this.optimizedHeight = Math.min(spaceBelow - 20, this.maxHeight)
|
||||
} else {
|
||||
this.preferredOpenDirection = 'above'
|
||||
this.optimizedHeight = Math.min(spaceAbove - 20, this.maxHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="customer-select">
|
||||
<div class="main">
|
||||
<div class="search-bar">
|
||||
<base-input
|
||||
v-model="search"
|
||||
:placeholder="$t('general.search')"
|
||||
focus
|
||||
type="text"
|
||||
icon="search"
|
||||
@input="searchCustomer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="(customers.length > 0) && !loading" class="list">
|
||||
<div
|
||||
v-for="(customer, index) in customers"
|
||||
:key="index"
|
||||
class="list-item"
|
||||
@click="selectNewCustomer(customer.id)"
|
||||
>
|
||||
<span class="avatar" >{{ initGenerator(customer.name) }}</span>
|
||||
<div class="name">
|
||||
<label class="title">{{ customer.name }}</label>
|
||||
<label class="sub-title">{{ customer.contact_name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading" class="list flex justify-content-center align-items-center">
|
||||
<font-awesome-icon icon="spinner" class="fa-spin"/>
|
||||
</div>
|
||||
<div v-if="customers.length === 0" class="no-data-label">
|
||||
<label> {{ $t('customers.no_customers_found') }} </label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="list-add-button" @click="openCustomerModal">
|
||||
<font-awesome-icon class="icon" icon="user-plus" />
|
||||
<label>{{ $t('customers.add_new_customer') }}</label>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
search: null,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('customer', [
|
||||
'customers'
|
||||
])
|
||||
},
|
||||
created () {
|
||||
this.fetchInitialCustomers()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal'
|
||||
]),
|
||||
...mapActions('customer', [
|
||||
'fetchCustomers'
|
||||
]),
|
||||
...mapActions('invoice', {
|
||||
setInvoiceCustomer: 'selectCustomer'
|
||||
}),
|
||||
...mapActions('estimate', {
|
||||
setEstimateCustomer: 'selectCustomer'
|
||||
}),
|
||||
async fetchInitialCustomers () {
|
||||
await this.fetchCustomers({
|
||||
filter: {},
|
||||
orderByField: '',
|
||||
orderBy: ''
|
||||
})
|
||||
},
|
||||
async searchCustomer () {
|
||||
let data = {
|
||||
display_name: this.search,
|
||||
email: '',
|
||||
phone: '',
|
||||
orderByField: '',
|
||||
orderBy: '',
|
||||
page: 1
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
await this.fetchCustomers(data)
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
openCustomerModal () {
|
||||
this.openModal({
|
||||
title: 'Add Customer',
|
||||
componentName: 'CustomerModal',
|
||||
size: 'lg'
|
||||
})
|
||||
},
|
||||
initGenerator (name) {
|
||||
if (name) {
|
||||
let nameSplit = name.split(' ')
|
||||
let initials = nameSplit[0].charAt(0).toUpperCase()
|
||||
return initials
|
||||
}
|
||||
},
|
||||
selectNewCustomer (id) {
|
||||
if (this.type === 'estimate') {
|
||||
this.setEstimateCustomer(id)
|
||||
} else {
|
||||
this.setInvoiceCustomer(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
85
resources/assets/js/components/base/popup/TaxSelectPopup.vue
Normal file
85
resources/assets/js/components/base/popup/TaxSelectPopup.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="tax-select">
|
||||
<div class="main-section">
|
||||
<div class="search-bar">
|
||||
<base-input
|
||||
v-model="textSearch"
|
||||
:placeholder="$t('general.search')"
|
||||
focus
|
||||
icon="search"
|
||||
class="search-input"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="filteredTaxType.length > 0" class="list" >
|
||||
<div
|
||||
v-for="(taxType, index) in filteredTaxType"
|
||||
:key="index"
|
||||
:class="{'item-disabled': taxes.find(val => {return val.tax_type_id === taxType.id})}"
|
||||
class="list-item"
|
||||
@click="selectTaxType(index)"
|
||||
>
|
||||
<label>{{ taxType.name }}</label>
|
||||
<label>{{ taxType.percent }} %</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-data-label">
|
||||
<label>{{ $t('general.no_tax_found') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="list-add-button" @click="openTaxModal">
|
||||
<font-awesome-icon class="icon" icon="check-circle" />
|
||||
<label>{{ $t('invoices.add_new_tax') }}</label>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
taxes: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
textSearch: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('taxType', [
|
||||
'taxTypes'
|
||||
]),
|
||||
filteredTaxType () {
|
||||
if (this.textSearch) {
|
||||
var textSearch = this.textSearch
|
||||
return this.taxTypes.filter(function (el) {
|
||||
return el.name.toLowerCase().indexOf(textSearch.toLowerCase()) !== -1
|
||||
})
|
||||
} else {
|
||||
return this.taxTypes
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('modal', [
|
||||
'openModal'
|
||||
]),
|
||||
selectTaxType (index) {
|
||||
this.$emit('select', {...this.taxTypes[index]})
|
||||
},
|
||||
openTaxModal () {
|
||||
this.openModal({
|
||||
'title': 'Add Tax',
|
||||
'componentName': 'TaxTypeModal'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user